Video Editing Timelines Domain — implementation plan (TDD-first, comprehensive)
Video Editing Timelines Domain — Implementation Plan
Background
Muse treats "domain" as a first-class primitive (muse/domain.py::MuseDomainPlugin).
A domain plugin implements six methods — snapshot, diff, merge, drift, apply,
schema — and gets the full VCS engine for free: the commit DAG, branching, checkout,
lineage walking, log graph, and merge-base finder. muse.plugins.midi proved the model
for structured symbolic + binary-payload data. muse.plugins.code proved it for AST
symbols. muse.plugins.social/identity proved it for graph-shaped state. The CAD
domain plan (staging issue #66) is proving it for continuous geometry with causal
feature histories. The 3D Scenes domain plan (staging issue #67) is proving it for
hierarchical scene graphs with instanced assets and time-varying animation layered on
top of static structure.
Video editing timelines are a fifth distinct shape of problem: everything is
fundamentally about time. A timeline is a set of tracks, each holding an ordered
sequence of clips positioned at specific time ranges, referencing external source
media, layered with transitions/effects/keyframes, and often nesting sub-timelines
(compound clips) that recurse the whole model. Today this lives entirely inside
opaque, editor-specific project files (Premiere .prproj, Resolve's project database,
Final Cut's library bundle) or lossy interchange formats (EDL, XML, AAF) that flatten
most semantic structure away. If Muse can version, diff, merge, validate, and query a
video timeline with frame-accurate, clip-level semantic awareness — as it already does
for code and MIDI, and as the CAD (#66) and 3D Scenes (#67) plans are proving for
geometry and scene graphs — it proves the domain-plugin model generalizes to
temporal/layered creative data too.
This issue is the plan only. Each numbered phase in §10 is expected to become its own staging issue once reviewed and accepted.
Goal
Ship a muse/plugins/timeline/ domain plugin, structured like muse/plugins/midi/
and the planned muse/plugins/cad/ and muse/plugins/scene3d/ (a plugin.py
entrypoint plus focused private modules for diff/merge/query/invariants/import-export),
that:
- Registers in
muse domains --jsonalongsidecode,midi,identity,mist,social, and (once landed)cad/scene3d. - Passes
muse code test --jsonfor every module it touches. - Snapshots, diffs, and merges a video timeline with track/clip/trim/effect-level
semantic awareness — never as an opaque
.prproj/.fcpxml/AAF blob. - Supports a timeline query DSL (
muse timeline query "...", mirroringmuse code queryand the CAD/3D-Scenes plans'muse cad query/muse scene query) capable of answering the concrete query examples in §6. - Detects the drift/validation classes in §7 via
muse status/muse check. - Has test coverage across every tier in §8, with test IDs (
VID_01,VID_02, …) referenced from both this plan and the test files that satisfy them.
"Done" for the plan means: reviewed, decomposed into §10's issue breakdown, with acceptance criteria concrete enough for a different agent to implement test-first.
1. Domain Scope
Supported initial timeline targets
- Single-project, single-or-multi-sequence nonlinear timelines: the common model shared by Premiere, Resolve, Final Cut, and CapCut — video/audio tracks holding ordered clips with in/out points, transitions between adjacent clips, per-clip and per-track effects with keyframed parameters, and markers/captions anchored to time.
- v0 targets timelines in the tens to low-hundreds of clips per sequence range (a short film, a YouTube episode, a commercial) — not feature-length multi-hour timelines with thousands of clips, and not multi-user live-broadcast switching timelines.
Recommended MVP interchange strategy
Same two-layer strategy as the CAD/3D-Scenes plans (#66, #67), adapted for timelines:
- Canonical semantic layer (Muse-owned): a JSON-serializable timeline IR — the "Canonical Model" in §2 — that Muse snapshots, diffs, merges, and queries directly.
- Interchange/import-export boundary: OpenTimelineIO (OTIO) as the primary MVP interchange format. OTIO is the closest thing the industry has to "the glTF of editorial timelines": an open-source, actively-maintained project (Pixar/Academy Software Foundation) explicitly designed as a timeline interchange format, with existing adapters for Premiere XML, FCPXML, AAF, and EDL already maintained upstream, and already JSON-shaped — mapping cleanly onto Muse's snapshot-as-JSON model, directly analogous to why glTF was chosen for the 3D Scenes plan. Rather than Muse writing and maintaining N separate editor-format adapters, v0 imports/exports through OTIO's own adapter layer, so Muse's plugin only needs one native integration to reach Premiere XML, FCPXML, AAF, and EDL by extension — a materially different leverage point than the CAD/3D-Scenes plans, which each own their format adapters directly. This is the single most important scope decision in this plan.
Relationship to EDL, XML, FCPXML, OTIO, AAF, Premiere, Resolve, and Final Cut
| Format/tool | Role in this plan |
|---|---|
| OTIO | v0 primary interchange format and integration point — the adapter targets OTIO's Python object model directly, inheriting its adapter ecosystem rather than reimplementing it. |
| EDL (CMX3600) | Reached transitively via OTIO's EDL adapter. Lowest-common-denominator (no effects, no nested sequences, timecode-only) — a simple fixture source for cut-diff tests, not a rich round-trip target. |
| Premiere XML | Reached transitively via OTIO's adapter. Premiere's native .prproj (proprietary, no public spec) is not a v0 target — only its XML export path, same reasoning as the 3D Scenes plan excluding native .blend. |
| FCPXML | Reached transitively via OTIO's adapter. The richest transitively-supported format (nested sequences, keyframed effects, audio/caption roles) — recommended fixture source for v0's advanced features (§2). |
| AAF | Reached transitively via OTIO's adapter. Post-production-oriented (color metadata, multi-channel audio) — recommended fixture source for stress-testing §2's Color/Audio elements. |
| Resolve | Native OTIO import/export (unlike Premiere/FCP, which require XML first) — recommended secondary reference authoring tool for v0 fixtures. |
| Final Cut Pro | Recommended primary reference authoring tool (richest transitive FCPXML export) — mirrors the CAD plan's FreeCAD choice and the 3D Scenes plan's Blender choice. |
| CapCut | Out of scope in v0 — no public spec, no OTIO adapter. Mentioned only as a category example; addable later without changing the canonical model if an interchange path appears. |
What is in scope for v0
- Projects containing one or more sequences (mirrors OTIO's
Timeline/Stackconcepts). - Video and audio tracks, each holding an ordered, non-overlapping-within-track list
of clips (gaps between clips are explicit
Gapelements, not implicit). - Clips: an in/out time range into a piece of source media, plus per-clip enable/mute state.
- Source media references: content-identified pointers to external media files (not the media bytes themselves in v0 — see §3).
- Time ranges expressed in rational frame-rate-aware time (not raw float seconds) — see §3's frame-rate/timecode normalization.
- Transitions between adjacent clips on the same track (dissolve, wipe, cut — the common cross-editor subset).
- Effects: a per-clip or per-track ordered effect stack with named parameters, including keyframed parameter values over time.
- Markers: timeline-anchored, or clip-anchored, annotations with a name/comment/color.
- Captions/subtitles: time-ranged text cues, modeled as their own track type (mirrors
FCPXML's caption roles and OTIO's
Marker/subtitle handling). - Nested sequences (compound clips): a sequence used as a clip's source media within another sequence — modeled recursively, not flattened.
- External asset references and their resolution state.
What is explicitly out of scope for v0
- The actual media essence (video/audio frame data, waveform data, proxy files) — v0 treats source media as externally referenced, content-identified pointers, never as bytes Muse itself stores, transcodes, or renders (§3, Handling large binary media). This is the single largest scope-limiting decision in the plan: this domain is about the edit, not the footage.
- Multi-camera / multi-cam clip sync groups — deferred to a v1 follow-up.
- Color grading node graphs (Resolve's full color page) beyond a flattened summary color-metadata record per clip (LUT reference, basic wheels) — same treatment as the CAD plan's stance on freeform surfaces and the 3D Scenes plan's stance on shader graphs.
- Render/export presets and render-queue state, beyond cache/render metadata (§2) needed for staleness drift detection — no semantic modeling of render settings.
- Real-time collaborative (CRDT) timeline editing — v0 merge is offline/three-way only
(
merge_mode: three_way), consistent with every prior domain plan. - Multi-hour, thousand-clip broadcast/feature-length timelines and any performance work targeting that scale.
- Audio mixing automation beyond keyframed volume/pan (mixing-console state, sidechain routing, bus architecture) — treated as opaque effect parameters.
2. Canonical Model
All elements are content-addressed (sha256:<hex>) and referenced by ID, never
positional index — the same identity principle as code's AST symbols, midi's
notes, the CAD plan's Part/Feature/Body, and the 3D Scenes plan's Node — so that
reordering never produces a spurious diff, and time-range shifts are diffed as time-
range changes, not delete+insert.
| Element | Fields | Notes |
|---|---|---|
| Project | id, name, sequences: [Sequence.id], frame_rate (default, rational — §3), metadata |
One project may hold multiple sequences, since nested sequences reference siblings. |
| Sequence | id, name, tracks: [Track.id] (ordered — stacking order is semantically meaningful, since it affects compositing), frame_rate, duration: TimeRange, metadata |
A Sequence can be referenced as a clip's source media by another Sequence (nesting) — must remain acyclic (§7). |
| Track | id, kind (video|audio|caption), clips: [Clip.id \| Gap.id] (ordered, non-overlapping), enabled: bool, locked: bool |
Gaps are explicit elements, not absence — makes gap-finding (§6) a direct query. |
| Clip | id, name, source_ref: SourceMedia.id \| Sequence.id, source_range: TimeRange (in/out within source), timeline_range: TimeRange (position on track), enabled: bool, muted: bool, effects: [Effect.id] (ordered), transition_in/out: Transition.id \| null |
Splitting source_range from timeline_range is what makes trim vs. move vs. retime distinguishable in diff (§4). |
| Gap | id, duration: TimeRange |
Explicit absence-of-content on a track; content-addressed by duration. |
| SourceMedia | id, uri_or_path, content_id_at_link_time (hash if locally accessible, else null), frame_rate, sample_rate, duration: TimeRange, resolved: bool, codec_hint |
Media bytes are never stored (§1) — this is a resolved/content-identified pointer, promoted to first-class since Clip references it directly. |
| TimeRange | id, start_time: RationalTime, duration: RationalTime |
RationalTime is (value, rate) — e.g. (2400, 24000/1001) — never a raw float (§3). |
| Transition | id, kind (dissolve|wipe|cut|custom), duration: TimeRange, alignment (centered|from_start|from_end), params |
Sits between two adjacent clips; alignment drives trim-conflict interactions (§5). |
| Effect | id, kind (built_in:<name> or plugin:<vendor>:<name>), params: {key: value \| Keyframe.id list}, enabled: bool |
Named parameters generically, not a closed effect enum, mirroring the 3D Scenes plan's flattened-PBR approach. |
| Keyframe | id, time: RationalTime, value, interpolation (linear|hold|bezier) |
Discrete addressable elements, not a binary buffer — timeline keyframe counts are orders of magnitude smaller than mesh/animation data. |
| Marker | id, anchor: {kind: timeline\|clip, ref}, time: RationalTime, name, comment, color |
Review markers — distinct from Captions, which are time-ranged program content. |
| Caption | id, track_ref: Track.id, time_range: TimeRange, text, language, role (mirrors FCPXML roles) |
Track-anchored, since captions commonly span underlying clip cut points. |
| AudioLane | id, track_ref: Track.id, channel_layout (mono|stereo|5.1), gain_db, pan |
Track-level mix state, distinct from per-clip audio Effects. |
| ColorMetadata | id, clip_ref: Clip.id, lut_ref: content_id \| null, color_space, wheels: {lift, gamma, gain} |
Flattened summary per §1's scope cut — no node-graph modeling. |
| RenderCacheMetadata | id, sequence_ref: Sequence.id, render_hash, output_ref: content_id \| null, stale: bool |
stale computed by comparing render_hash to current content identity (§7) — mirrors the CAD plan's Measurement.stale. |
| ExternalReference | id, kind (linked_sequence|linked_media|linked_lut), uri_or_path, content_id_at_link_time, resolved: bool |
Same role as prior plans' ExternalReference — drives drift detection (§7). |
| Metadata | frame_rate, timecode_format (drop_frame|non_drop_frame), author, created_at, source_format (otio|fcpxml|aaf|edl|premiere_xml), source_checksum, schema_version |
timecode_format is load-bearing for NTSC drop-frame arithmetic (§3). |
Design invariant carried over from every prior domain plan: identity is content- and
reference-based, never positional, with one exception: Track stacking order
within a Sequence is semantically meaningful (compositing order) and is therefore
diffed as an order change — the one place this domain departs from the 3D Scenes
plan's "child order is never identity-bearing" rule.
3. Snapshot Design
Deterministic timeline normalization
snapshot() walks the live state (an OTIO-loadable project under state/, same
pattern as MIDI's .mid walk, the CAD plan's .FCStd/STEP walk, and the 3D Scenes
plan's .gltf/.glb walk) and produces the canonical IR in §2 as the
SnapshotManifest, applying normalization in a fixed order:
- Resolve every element to a stable ID (§3, Stable clip and track identity, below).
- Canonicalize all time values to
RationalTime(§3, Unit handling, below) — never store or compare raw floating-point seconds. - Canonicalize ordering:
Sequence.tracksorder is preserved (semantically meaningful, per §2);Track.clipsorder is derived fromtimeline_range.start_time(a track's clip list has exactly one canonical order — its position on the timeline — so there is no ambiguity to normalize away, unlike node-child lists in the 3D Scenes plan). Effect stacks preserve authored order (also semantically meaningful — effect application order changes the result). - Compute each
Sequence'sdurationand eachTrack's occupied-time summary at snapshot time — a cheap invariant used to short-circuit full-track diffs (§4) the same way the CAD plan'stopology_summaryand the 3D Scenes plan'svertex_count/bounding_boxshort-circuit their respective expensive diffs.
Stable clip and track identity
Like 3D Scenes nodes, OTIO/FCPXML/AAF clips generally have no globally stable ID
across re-exports. Clip identity is derived as a content hash of
(track_identity, source_ref content-id, source_range) — "this exact piece of this
exact source media, on this exact track" defines identity, independent of timeline
position. Directly analogous to the 3D Scenes plan's structural/display identity
split:
- A clip's
timeline_range.start_timeis explicitly excluded from its identity hash — otherwise every trim/ripple edit (which shifts every downstream clip's start time) would appear as a full delete+insert cascade, the same failure mode the 3D Scenes plan's rename-cascade fix avoids, applied here to temporal shifts. - Two clips using the same source media and range at two different timeline points
(e.g. a repeated shot) are only disambiguated by
track_identity; if on the same track they are genuinely identical by this scheme — same known-limitation pattern as the 3D Scenes plan's identical-sibling-node case, handled the same way: a documented, tested positional tiebreaker, not silently assumed away. Trackidentity is derived from(sequence_identity, kind, ordinal_position)— tracks have no content-based identity independent of position, since two otherwise- identical video tracks in different stacking positions are genuinely different (compositing order matters, §2). This is whyTrackorder is not excluded from identity the wayNodechild order is in the 3D Scenes plan.
Content-addressed source media references
Muse does not store media essence (§1). SourceMedia.content_id_at_link_time
records a hash of the referenced file's bytes at snapshot time, when the file is
locally accessible — a fingerprint for drift detection, not a stored object. If
the underlying media file changes on disk after linking (re-exporting a graded master,
swapping a placeholder for final VFX), the fingerprint mismatch powers the "missing/
changed source media" drift check (§7). When the file isn't locally accessible at
snapshot time (offline/proxy workflows), content_id_at_link_time is null and
resolved is false — explicit and visible, never silently assumed resolved.
Handling large binary media
This is the most consequential storage decision in the plan, and differs from every
prior one: CAD and 3D Scenes store their binary payloads (B-Rep geometry, mesh/
texture/keyframe buffers) as content-addressed blobs in .muse/objects/. Video
essence is orders of magnitude larger — a single hour of ProRes footage can be
tens of gigabytes — and Muse has no video-specific storage tier. Therefore: v0 never
stores media essence in .muse/objects/ at all. Source media is always an external
reference (SourceMedia/ExternalReference, §2), resolved against paths/URIs
recorded at commit time — the same way a build system references external
dependencies rather than vendoring them. The only binary payloads this plugin writes
to the object store are small, timeline-scoped artifacts: LUT files
(ColorMetadata.lut_ref, typically kilobytes) and, if populated, an optional low-
resolution proxy/thumbnail (RenderCacheMetadata.output_ref) — never a full-
resolution export. This boundary is load-bearing, not incidental, and should be
revisited only as an explicit, separately-reviewed decision (§10), not relaxed
incrementally during implementation.
Frame-rate and timecode normalization
- Every
RationalTimevalue is stored as(value, rate)— never a float — because standard video frame rates (23.976, 29.97, 59.94, allx*1000/1001NTSC rates) are not exactly representable in binary floating point, and naive float timecode arithmetic is a well-known source of off-by-one-frame bugs. This parallels the CAD plan's fixed-epsilon rationale, but the fix here is representational (rational arithmetic) rather than tolerance-based, because frame-accurate editing genuinely requires exact equality, not "close enough." Metadata.timecode_format(drop_frame|non_drop_frame) is recorded explicitly, since drop-frame timecode display (NTSC 29.97/59.94) skips certain timecode labels without skipping actual frames — display-layer formatting only, required for correct human-readable diff/conflict output (§4, §5).- A sequence's declared
frame_rateis authoritative for allRationalTimevalues within it; a clip whose source rate differs (mixed-frame-rate editing is normal — e.g. slow-motion footage shot at a higher rate) is not an error by itself, but is recorded so the frame-rate-mismatch check (§7) can distinguish intentional design from accidental mismatch.
Unit handling for frames, seconds, samples, and ticks
All units in circulation (frames, seconds, audio samples, and format-specific "ticks"
like AAF's edit units) are represented as RationalTime with an explicit rate
field, so a value is always unambiguous regardless of source-format unit. Conversion
is pure rational arithmetic (no precision loss), performed only at import/export
boundaries — the canonical IR itself needs no "current unit mode," eliminating an
entire category of unit-confusion bugs by construction (contrast with the CAD/3D-
Scenes plans, which must validate unit consistency after the fact, since their source
formats don't carry rate information as intrinsically as timecode-based formats do).
Deterministic serialization
OTIO's own JSON serialization is already close to deterministic, but adapter round- trips (e.g. FCPXML → OTIO → EDL) can reorder or drop fields depending on adapter version. Muse never hashes OTIO JSON bytes directly for content-addressing; it always re-serializes through the canonical IR's fixed field order and ID scheme (§2), same discipline as the CAD/3D-Scenes plans' "never hash the interchange format's bytes directly" rule. A golden-snapshot suite (§8) locks this down: re-snapshotting the same fixture twice, or the same project authored in Final Cut vs. round-tripped through Resolve's native OTIO export, must produce byte-identical canonical JSON.
4. Semantic Diff
diff(base, target, *, repo_root=None) returns a StructuredDelta of typed ops, same
contract as every prior domain plan.
- Clip add/remove/move/trim diffs:
InsertOp/DeleteOpkeyed on clip identity (§3). A move (timeline_range.start_timechanged) is reported distinctly from a trim (source_range/timeline_range.durationchanged) — "you slid this clip later" vs. "you shortened this clip" read very differently and conflating them would make diffs unreadable, mirroring the CAD plan's value-change vs. structural-change distinction. - Track diffs: added/removed tracks are structural; since
Trackorder is identity-bearing (§2, §3), reordering (e.g. moving a video track above another) is reported as a diff — the one deliberate departure from the "order never matters" pattern elsewhere in this plan and the 3D Scenes plan, because compositing order changes the rendered result. - Sequence diffs: added/removed sequences; a nested
Clip.source_refpointing at a differentSequenceis reported as a retarget, distinct from an ordinary source-media swap, since it can pull in an entirely different sub-timeline. - Transition diffs:
InsertOp/DeleteOp/ReplaceOpontransition_in/_out, withkind/durationdeltas — cheap, fully-diffable, no coarse-summary treatment needed (unlike geometry-scale data in the CAD/3D-Scenes plans). - Effect parameter diffs: field-level, same granularity as the 3D Scenes plan's material diffing — different-parameter edits on the same effect are independently reported and mergeable (§5).
- Keyframe diffs: diffed individually (
InsertOp/DeleteOp/ReplaceOpperKeyframe.id), unlike the CAD/3D-Scenes plans' coarse buffer-level treatment — timeline keyframe counts per parameter are orders of magnitude smaller than mesh/ animation data, so per-element diffing is cheap here. - Marker/comment diffs: field-level, keyed on
Marker.id(text vs. position vs. color changes reported distinctly). - Caption diffs: keyed on
Caption.id; a text edit is reported distinctly from a time-range edit (re-timing to match a re-cut vs. correcting a typo). - Audio edit diffs:
AudioLanefield-level diffs (gain/pan) reported separately from clip-level audioEffectdiffs — track mix state vs. per-clip processing are different layers. - Source media reference diffs: a
content_id_at_link_timechange ("source media changed") is reported distinctly from aresolvedflag flip (file went missing/came back) — same distinction the CAD/3D-Scenes plans draw forExternalReference. - Rename/move detection: a rename (identity unchanged,
namechanged) is a singleRenameOp; a move (thetrack_identitycomponent of a clip's identity changes — cut from one track, pasted onto another) is aMoveOp, not delete+insert — parallels the CAD plan's feature-move and the 3D Scenes plan's node-move detection. - Frame-accurate comparisons: every
RationalTimecomparison is exact rational equality, never epsilon-tolerant — the opposite of the CAD/3D-Scenes plans' floating-point tolerance, because video editing is frame-quantized: a "close but not exact" time value is either a real one-frame edit (must be shown) or a normalization bug (must be fixed, not tolerated).
5. Merge Strategy
merge(base, left, right, *, repo_root=None) — three-way, merge_mode: three_way,
consistent with every prior domain plan.
- Non-conflicting timeline edits (the common case): edits to disjoint
clips/tracks/sequences merge automatically via the engine's default address-keyed
map merge, driven by
schema()'sdimensions(§9). - Same-clip conflicts: both branches change the same
Clip'ssource_reforsource_rangedifferently — always a reported conflict (no auto-resolution of "which cut is correct," per every prior plan's rejection of silently picking a value neither author chose). - Same-track ordering conflicts: both branches insert a different clip into the same gap — reported conflict (ambiguous, and inserting both would overlap, itself invalid per §7). If the insertions don't overlap in time, both merge, and the track is re-validated as a strict non-overlapping partition post-merge (§7, now a post- merge gate too).
- Trim conflicts: both branches trim the same in/out point differently — conflict; but trimming different points of the same clip (in vs. out) auto-merges — same field-level-independence principle as the 3D Scenes plan's material merge.
- Transition conflicts: both branches change the same boundary's transition differently — conflict. A duration change extending past available source handle is flagged as an additional validation failure, not silently clamped.
- Effect/keyframe conflicts: field-level for parameters (independent edits auto-merge, same-parameter edits conflict); keyframe-level for keyframes (different new keyframes at different times auto-merge; same keyframe edited on both branches conflicts).
- Marker/comment merges: the one element class where v0 leans toward union-merge
by default rather than conflict-on-collision — two editors independently leaving
review markers at the same position are not expressing incompatible intent the way
conflicting trims are. Only an edit to the same
Marker.id's text/color conflicts. A deliberate, documented departure from this plan's default rule, justified by review annotations being qualitatively different from structural edits — call this out explicitly in the implementation and tests (§8). - Nested sequence conflicts: recurses — the merge engine runs the same three-way merge on the nested sequence's own tracks/clips rather than treating it as an atomic conflict unit (which would force a conflict on every nested edit). A non-conflicting nested edit auto-merges even if the outer referencing clip was untouched.
- Missing media conflicts: if a branch's edit relies on
SourceMediathat isresolved: falseat merge time, the merge is not blocked — offline media is normal, not an error — but the result carries a warning annotation, a third category beyond auto-merge/conflict specific to this domain's offline workflow. - Human-readable conflict reports: every conflict carries both branches' values,
the
basevalue, and a plain-language cause string in editorial terms (e.g."Both branches trimmed clip 'Interview_B-roll_04' out point: left=01:23:14:02, right=01:23:15:19, base=01:23:12:00"— formatted timecode, per §3), consistent with the workspace-wide conflict-marker convention and every prior plan's requirement.
6. Query Support
A timeline query DSL mirroring muse code query and the CAD/3D-Scenes plans'
muse cad query/muse scene query (muse timeline query "<predicate>" --json), plus
purpose-built subcommands for the most common questions.
Example video timeline query DSL
muse timeline query "clip.source_media == 'B-roll_Drone_02.mov'" --json
muse timeline query "effect.kind == 'built_in:gaussian_blur' and effect.enabled == true" --json
muse timeline query "clip.timeline_range.start > timecode('01:00:00:00')" --json
Find clips by source media
muse timeline query "clip.source_media == 'Interview_A_Take3.mov'" --json
muse timeline find-by-source "Interview_A_Take3.mov" --json # convenience alias
Find gaps
muse timeline query "element.type == 'gap' and element.duration > frames(1)" --json
muse timeline find-gaps --track V1 --json
Find overlaps
muse timeline find-overlaps --json # should normally return empty (§7 invariant);
# useful during import validation of a
# non-Muse-authored source file
Find muted/disabled clips
muse timeline query "clip.muted == true or clip.enabled == false" --json
muse timeline disabled-clips --json
Find clips using specific effects
muse timeline query "clip.effects contains 'plugin:redgiant:magic_bullet'" --json
muse timeline find-by-effect "plugin:redgiant:magic_bullet" --json
Find timeline regions with unresolved media
muse timeline query "clip.source_media.resolved == false" --json
muse timeline offline-media --json # mirrors the CAD/3D-Scenes plans' broken/
# missing-reference query pattern
Find edits after a timestamp
muse timeline query "clip.timeline_range.start > timecode('00:45:00:00')" --json
muse timeline edits-after "00:45:00:00" --json
Find all captions in a time range
muse timeline query "caption.time_range intersects (timecode('00:10:00:00'), timecode('00:12:00:00'))" --json
muse timeline captions-in-range --start "00:10:00:00" --end "00:12:00:00" --json
Find high-density edit regions
muse timeline query "region.cut_density(window=frames(150)) > 8" --json
muse timeline edit-density --window 150f --threshold 8 --json # mirrors `muse code
# hotspots` framing —
# rapid-cut montage
# detection
7. Validation and Drift Detection
Surfaces through muse status (via drift()) and muse check/muse code invariants
(via a timeline-specific invariant checker, same pattern as the CAD/3D-Scenes plans'
_invariants.py).
- Missing source media: any
SourceMediawithresolved: false, surfaced as a first-class, expected-to-be-common state (unlike the "hard error" framing in the CAD/3D-Scenes plans) — see §5's missing-media-conflict treatment for why this domain treats offline media as routine. Flagged clearly inmuse status, not commit- blocking by default. - Broken nested sequence references: a
Clip.source_refpointing at a missingSequence.id, or a nested-sequence cycle (AnestsBnestsA) — the cycle is commit-blocking (no legitimate editorial meaning); the missing-reference case follows the same "flagged, not blocking" policy as missing source media. - Invalid time ranges: overlapping clips on the same track (violates §2's strict-
partition invariant), negative/zero-duration ranges, and a
source_rangeextending beyondSourceMedia.duration(checkable only when resolved) — all commit-blocking, since these are genuinely malformed, not a normal offline-media gap. - Frame-rate mismatches: a clip's
SourceMedia.frame_ratediffering from its sequence's is informational by default (mixed-rate editing is normal, §3) but escalated to a warning if the ratio isn't a simple, commonly-intentional one (e.g. 60fps-in-24fps for slow motion is a clean 2.5x; 23.976fps misread as 24fps in a 29.97fps timeline is the subtle confusion this check targets) — specific ratio heuristics are an open question (§10), not guessed at here. - Audio sample-rate mismatches: analogous to frame-rate mismatches, but with no "intentional creative choice" analog (no editorial equivalent of slow motion), so always at least a warning, never merely informational.
- Unsupported effect parameters: an effect whose shape falls outside v0's generic named-parameter model (e.g. a proprietary plugin's binary blob) is flagged as lossy import at snapshot time, same "never silently drop data" principle as the 3D Scenes plan — raw data is preserved as an opaque blob, and the flag makes the fidelity loss visible.
- Non-deterministic importer output: re-importing the same source project twice must produce identical canonical IR — a hard-error regression gate, same treatment as every prior plan, since it would silently break content-addressing.
- Schema migration risks:
Metadata.schema_versionmismatches are flagged before diff/merge across the boundary, never auto-migrated silently, consistent with the workspace-wide rule thatmuse code migraterequires explicit permission.
8. TDD Plan
Test IDs use the VID_NN prefix, referenced from both this plan and the eventual test
files, per the workspace convention (IC_01, the CAD plan's CAD_NN, the 3D Scenes
plan's SCN_NN).
| Tier | Coverage | Example test IDs |
|---|---|---|
| Unit | Canonical model (de)serialization for every element type; RationalTime arithmetic (incl. NTSC drop-frame); clip identity hashing (position-exclusion fix). |
VID_01–VID_03 |
| Golden snapshot | Fixed fixtures (cuts-only sequence, multi-track w/ transitions, keyframed color-correction, nested-sequence project, captioned sequence) snapshotted once, checked byte-for-byte against committed golden JSON. | VID_10–VID_14 |
| Round-trip import/export | FCPXML/AAF round-trips must reproduce input within the format's fidelity ceiling and produce identical IR on re-import; EDL cross-format must produce a valid subset of the FCPXML-sourced IR — analog of the CAD/3D-Scenes plans' cross-format equivalence tests, adapted for lossy-by-format-choice reality. | VID_20–VID_22 |
| Timeline diff | One test per §4 diff category (clip add/remove/move/trim, track reorder, sequence retarget, transition, effect, keyframe, marker, caption, audio lane, source media, rename/move, frame-exact-equality). | VID_30–VID_41 |
| Merge | One test per §5 non-conflicting case and conflict category, incl. trim-field-independence, marker-union-merge, recursive nested-sequence merge, missing-media warning. | VID_50–VID_59 |
| Conflict | Conflict report shape/content, incl. timecode formatting (§3); muse resolve clears a timeline conflict. |
VID_60–VID_63 |
| Frame-accurate timecode | Property-based RationalTime generation across standard frame rates (23.976–60) asserting rational-equality never false-positives/negatives, and drop-frame display round-trips without altering frame counts. |
VID_70, VID_71 |
| Validation/drift | One test per §7 bullet (missing media informational, broken nested reference, cycle blocking, invalid range, rate-mismatch distinction, unsupported params, non-deterministic import, schema mismatch). | VID_80–VID_88 |
| Query | One test per §6 query example against a fixture with known results. | VID_90–VID_98 |
Fixture strategy with small canonical timeline projects
- Fixtures are authored once, by hand, in Final Cut Pro (primary, richest FCPXML export) and DaVinci Resolve (secondary, native OTIO export) — realistic tool output, including exporter non-determinism, is exactly what §3's normalization must absorb — never generated programmatically for the golden/round-trip tiers.
- A minimal fixture library: a two-clip cuts-only sequence; a multi-track sequence with a dissolve and a keyframed color-correction effect (3-4 keyframes); a nested- sequence project (a compound clip containing its own 2-clip sub-sequence); a captioned sequence with captions spanning a cut point; a sequence with an offline media reference; a sequence with a deliberate frame-rate mismatch (60fps clip in a 24fps timeline); an EDL export of the simplest fixture (lower-fidelity cross-format test).
- Frame-accurate timecode property-based tests use synthetic generators across the standard frame-rate table, not the hand-authored fixtures, for systematic coverage of rate-conversion edge cases.
9. Backend Integration
Domain plugin interface implementation
muse/plugins/timeline/ following the muse/plugins/midi/ layout (and the CAD/3D-
Scenes plans' equivalent layouts):
muse/plugins/timeline/
__init__.py
plugin.py # MuseDomainPlugin implementation: snapshot/diff/merge/drift/apply/schema
entity.py # Canonical model dataclasses (§2)
manifest.py # SnapshotManifest (de)serialization, normalization (§3)
rational_time.py # RationalTime arithmetic, drop-frame timecode formatting
timeline_diff.py # Diff algorithms (§4)
timeline_merge.py # Merge algorithms (§5)
_query.py # Query DSL implementation (§6)
_invariants.py # Validation/drift checks (§7)
_otio_bridge.py # OTIO <-> IR adapter (the single integration point reaching
# FCPXML/AAF/EDL/Premiere-XML transitively, per §1)
schema() declares (mirroring code/midi/identity/the CAD and 3D Scenes plans'
domains --json shape):
{
"schema_version": "<muse_version>",
"merge_mode": "three_way",
"description": "Semantic version control for nonlinear video editing timelines...",
"dimensions": [
{"name": "structure", "description": "Sequences, tracks, and their stacking order."},
{"name": "clips", "description": "Clip placement, trims, and source media references."},
{"name": "transitions", "description": "Transitions between adjacent clips."},
{"name": "effects", "description": "Effect stacks and their keyframed parameters."},
{"name": "markers_and_captions", "description": "Review markers and time-ranged caption cues."},
{"name": "audio", "description": "Per-track audio lanes and mix metadata."},
{"name": "color", "description": "Per-clip flattened color grading metadata."},
{"name": "metadata", "description": "Frame rate, timecode format, authorship, schema version."}
]
}
Storage implications
Unlike the CAD and 3D Scenes plans, this domain's storage footprint in
.muse/objects/ is deliberately small — no media essence is ever stored (§3); only
small metadata artifacts (LUTs, optional low-res proxies) use the object store at all.
muse count-objects/gc/prune need no timeline-specific changes. This is a
positive property of the design that should be preserved, not eroded, as the plugin
evolves.
API endpoints needed (MuseHub)
GET /:owner/:repo/timeline/:ref/sequences— sequence/track listing at a ref.GET /:owner/:repo/timeline/:ref/diff?base=&target=— structured timeline diff for proposal/review rendering.GET /:owner/:repo/timeline/:ref/offline-media— convenience endpoint surfacing unresolvedSourceMediafor a given ref, since this is expected to be a routinely- checked state (§7) rather than an exceptional one.- Reuses existing generic
muse hub proposal/issueendpoints unchanged, same as every prior domain plan.
CLI implications
New muse timeline subcommand group, structured like muse code/the planned muse cad/muse scene: muse timeline query, muse timeline find-gaps,
muse timeline find-overlaps, muse timeline disabled-clips,
muse timeline find-by-effect, muse timeline offline-media,
muse timeline edits-after, muse timeline captions-in-range,
muse timeline edit-density. All accept --json. No changes needed to domain-agnostic
commands (status, diff, merge, log, etc.).
MuseHub UI implications
- Proposal/diff view needs a timeline diff renderer: a horizontal multi-track view
(standard NLE metaphor) with changed clips/transitions/effects highlighted and a
scrubbable playhead. The structural view needs only timeline structure, not media
essence; an actual video preview requires resolving
SourceMedia(only possible when locally accessible), so the diff view must degrade gracefully to "structure- only" when media is offline. Conflict panel presented in timecode, not raw frames. - Repo browse view needs a sequence/track tree view analogous to the CAD/3D-Scenes
plans' part-tree/node-tree views, keyed on
Sequence/Track/Clip.
Timeline visualization hooks
muse timeline render-thumbnail-strip <ref> --json— server-side filmstrip thumbnail generation, only when source media is locally resolvable — unlike the 3D Scenes plan's equivalent (glTF scenes are self-contained; timelines are not, per §1/§3), this hook doesn't work in every environment. API/UI must handle "media offline, structure-only" as a first-class outcome, not an error state.- Diff-aware highlighting: the diff renderer accepts an optional overlay (changed
clips/regions highlighted), driven by the
StructuredDeltafrom §4 — same pattern as the 3D Scenes plan's diff-aware viewer hook.
MCP/tooling implications
Expose timeline_query, timeline_diff, timeline_find_gaps as MCP tools (mirroring
the CAD/3D-Scenes plans' equivalent exposure), so agentception workers can reason about
edit state programmatically — e.g. "are there any gaps or overlaps before this branch
merges," "does this cut still reference all its source media," or "how dense are the
edits in the cold open" as agent-callable checks before a merge or review is completed.
10. Staging Deliverables
Proposed issue breakdown
- VID-1 — Canonical model + snapshot + RationalTime (§2, §3): entity dataclasses,
rational-time arithmetic, drop-frame formatting, stable clip/track identity
(position-exclusion fix), OTIO import bridge, golden-snapshot tests
(
VID_01–VID_14). Load-bearing — nothing else starts until this lands. - VID-2 — OTIO export + round-trip adapters (§1, §3): depends on VID-1. FCPXML/
AAF/EDL round-trip via the OTIO bridge.
VID_20–VID_22. - VID-3 — Semantic diff (§4): depends on VID-1.
VID_30–VID_41. - VID-4 — Merge strategy (§5): depends on VID-1, VID-3.
VID_50–VID_63. - VID-5 — Validation & drift (§7): depends on VID-1; parallel with VID-3/VID-4.
VID_80–VID_88. - VID-6 — Query DSL + CLI (§6, §9): depends on VID-1, VID-3.
VID_90–VID_98. - VID-7 — Frame-accurate timecode property-based suite (§8): depends on VID-1
only (not diff/merge).
VID_70–VID_71. - VID-8 — MuseHub API endpoints (§9): depends on VID-1, VID-3.
- VID-9 — MuseHub UI: timeline diff viewer + visualization hooks (§9): depends on VID-8. Not scoped in detail until VID-3/VID-4 shapes are proven, and must design for the offline-media degraded case from the start, not retrofit it.
- VID-10 — MCP tooling exposure (§9): depends on VID-6.
Milestones
- M1 (Foundation): VID-1 merged,
muse domains --jsonliststimelineas active, golden-snapshot suite green,RationalTimearithmetic verified across the standard frame-rate table. - M2 (Interop): VID-2 merged; FCPXML/AAF/EDL round-trip fixtures pass at their respective fidelity ceilings.
- M3 (Semantic core): VID-3 + VID-4 merged; a two-branch conflict fixture (both
branches trim the same clip's out point differently) produces a correct, timecode-
formatted conflict report via
muse merge --dry-run; marker-union-merge and nested-sequence-recursive-merge verified working as designed. - M4 (Usable): VID-5 + VID-6 + VID-7 merged;
muse timeline query/find-gaps/find-overlaps/offline-media/edit-densityall answer correctly. - M5 (Ecosystem): VID-8, VID-9, VID-10 merged; a timeline proposal on staging MuseHub renders a real clip-level diff in a multi-track view, degrading gracefully to structure-only rendering when source media is offline.
Acceptance criteria (whole-plan level)
muse domains --jsonshowstimelinewithactive: trueand the schema in §9.- Every fixture round-trips through OTIO/FCPXML/AAF (§8) with zero diff against itself; the EDL cross-format test produces a valid fidelity-reduced subset, not a divergent result.
- Trimming a clip produces exactly one
TrimOpwith no spurious diffs on any other clip on the track — verifies the position-exclusion identity fix (§3). - The offline-media fixture is flagged as informational, not commit-blocking (§7); the overlapping-clips and nested-sequence-cycle fixtures are commit- blocking, with no false positives on well-formed fixtures.
- A merge of disjoint clip/track/effect edits auto-resolves with zero conflicts; a merge where one branch trims a clip's in-point and the other trims its out-point also auto-resolves; two branches adding different markers at the same position merge without conflict.
- A merge trimming the same out-point to different values reports a conflict with a correctly timecode-formatted cause string (§5).
muse timeline querycorrectly answers all examples in §6 against the fixtures.- No test depends on network access, a specific FCP/Resolve export's exact binary output, or wall-clock time (§3/§7).
Risks
- OTIO dependency-surface risk: routing all editor interop through OTIO's adapter layer (§1) is the plan's central leverage decision, but makes this domain's fidelity ceiling a function of OTIO's own per-format adapter maturity (AAF/Premiere-XML are less complete than FCPXML as of this writing). Mitigation: VID-2's round-trip tests are scoped per-format with fidelity-ceiling assertions (§8), not a blanket claim — a known OTIO gap should surface as a documented, tested ceiling, not a silent failure.
- Clip identity collision risk: same risk class as the 3D Scenes plan's identical- sibling-node case — a timeline reusing the same shot on the same track (e.g. a repeated flashback insert) hits the positional-tiebreaker path routinely, not as a rare corner case. Must be tested directly.
- Frame-rate mismatch heuristic risk: the informational-vs-warning distinction in §7 depends on a "commonly-intentional ratio" heuristic this plan declines to fully enumerate (open question below) — risk of false-positive warnings (normal slow motion) or false negatives (missed rate confusion) until real usage informs it.
- Offline-media-by-design risk: unusually among Muse's domains, a large fraction of normal operation involves data (media essence) the plugin deliberately never accesses. Every downstream feature must degrade gracefully rather than assume media is present (§5, §9) — easy to build a version that works only against an always- online fixture library and breaks on real offline workflows.
Open questions
- What frame-rate ratio table counts as "commonly intentional" for §7's informational-vs-warning distinction (e.g. 2x, 2.5x slow-motion ratios) — needs a decision, ideally from surveying real fixtures, before VID-5 implementation.
- Should the clip identity-collision tiebreaker (§3) match the 3D Scenes plan's
equivalent for consistency, or does this domain's track-ordinal-position concept
(already load-bearing for
Trackidentity) suggest a domain-specific answer? - Does
RenderCacheMetadata.output_ref's proxy storage need a retention policy beyond ordinarymuse gc/prunereachability, given proxies could still accumulate across a long-lived project's history? - Should VID-9's diff viewer attempt real frame preview when media is locally resolvable, or should v0 UI stay structure-only everywhere to avoid building two rendering paths before usage shows which is needed first?
- Is there a meaningful v1 target for multi-cam sync groups (deferred in §1), and
would it need a new element type or fit within
Clip/Sequence— flagged, not designed now.
Implementation order
VID-1 → (VID-2, VID-3, and VID-7 in parallel) → VID-4 → VID-5 → VID-6 → VID-8 → (VID-9 and VID-10 in parallel). Mirrors §1's dependency structure: nothing can diff, merge, validate, or query state that hasn't been normalized into the canonical model yet. VID-2 and VID-7 run in parallel with VID-3 since neither depends on diff/merge landing first.
This is a plan-only issue. Each deliverable in §10 should be filed as its own staging issue once reviewed, referencing the test IDs and criteria above.