Commit Graph
64 Commits
Author SHA1 Message Date
gyro 25aeb4d8a4 Fix clip drags leaving transitions behind
Dragging a clip kept its transitions on their old spots, so they jumped to the
dropped position on release instead of moving with the clip. The drag preview now
draws each transition where the drop will leave it, applying the same rule the
edit does: a fade travels with its clip, and a cut transition travels only when
every clip it sits on moves to one track by one offset. A cut transition whose
one side stays behind is dropped on release; the preview keeps it on its cut
while dragging, and Option-drag copies show a ghost transition beside the
copies, duplicating only the transitions whose every clip is copied.

- edit: carried_transitions keeps only all-clips-move transitions; move_items
  and copy_items drop or skip the rest
- ui: move_transition_preview draws carried transitions where they land
- tests: timeline_transition_move_ui (fade, both-clips cut, XYZ partial drop,
  cross-track drop, Option-drag ghost); transitions_travel_with_moved_clips
  updated to the drop-on-partial rule
2026-10-12 00:41:05 -07:00
Brandon ThomasandClaude Opus 5.5 0f8533535d Merge main into #972: linked Rate Stretch also leaves clips on locked tracks alone (#934)
rate_stretch_items skips clips on locked tracks and refuses the edit when
every clip is locked, which is what the command loop on main did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:38:34 -04:00
Brandon Thomas 06348de6ee Merge pull request #970 from vbasky/fix/917-locked-track-clip-id
Refuse timeline.move when the source track is locked
2026-10-11 20:24:46 -04:00
Brandon Thomas c2cb113fb8 Merge pull request #969 from vbasky/fix/918-speed-fade-end
Move fade-out with a non-ripple Speed/Duration change
2026-10-11 20:24:45 -04:00
Vikram Bhaskaran df282bcdc1 #918: keep fade-out on the clip end after Speed/Duration
Non-ripple clip.speedDuration shortened the clip but left one-sided
out-edge transitions at the old timeline range. Reposition them with
the same edge-follow used for a regular trim.
2026-10-11 17:14:16 +11:00
Vikram Bhaskaran d79248489b Refuse timeline.move when the clip's source track is locked.
Moving off a locked track used to skip delete_items and overwrite a
second item with the same clip id (#917).
2026-10-11 17:12:33 +11:00
Vikram Bhaskaran 53b99ae335 Keep linked rate-stretch partners at one speed.
A neighbour on only one track used to clamp that partner alone, so picture
and sound stayed linked at different rates. The tightest room now limits
the whole group.
2026-10-11 17:11:52 +11:00
Vikram Bhaskaran 7fef64de6b #911: refuse timing edits on locked tracks
Trim, roll, slip, slide, rate stretch and Speed/Duration now fail with
"track is locked" instead of changing clips on a locked track, matching
same-track move. Linked partners on locked tracks are left alone.
2026-10-11 14:09:05 +11:00
Brandon Thomas bf7ef0e894 Merge pull request #794 from impostazioni/fix/alt-drag-duplicate
Timeline: Option-drag copies clips instead of moving them
2026-10-10 18:15:25 -04:00
Brandon Thomas c6dbc390a4 Merge pull request #756 from vangel76/fix-374-transition-follows-trim
#374: a clip's fades follow its edges on a regular trim
2026-10-10 17:00:43 -04:00
impostazioniandClaude Opus 5.5 a8b3d7f703 Timeline: Option-drag copies clips instead of moving them
Holding Option (Alt) while dragging timeline clips now leaves the
originals in place and drops copies where the pointer lets go, as in
Premiere. The copies are selected; copies of linked clips are linked to
each other, and transitions that would travel with a move are copied.

- edit: copy_items (overwrite / insert, new ids, links and transitions)
- engine: timeline.move takes `copy: true`, returns `copied`
- ui: the drag remembers Option, shows the copy under the pointer while
  the originals stay, copy cursor
- tests: engine copy/link/locked-track test, headless Option-drag UI test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:52:28 +01:00
Brandon ThomasandClaude Opus 5.5 e4f3cab5a0 Merge origin/main into #704 (gap and marker context menus side by side; contributors)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:27:48 -04:00
vangel76 2f49774895 Merge remote-tracking branch 'upstream/main' into fix-374-transition-follows-trim
# Conflicts:
#	crates/edit/src/tests.rs
2026-10-10 22:08:23 +02:00
Brandon ThomasandClaude Opus 5.5 e82f9bca71 Merge origin/main into #627 (keep both trim UI tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:02:33 -04:00
Brandon Thomas a6b323056f Merge pull request #755 from vangel76/fix-227-transitions-follow-move
#227: transitions travel with the clips they sit on when moved
2026-10-10 15:53:26 -04:00
vangel76andClaude Opus 5.5 2cab09e69b #374: a clip's fades follow its edges on a regular trim
Problem: dragging a clip's edge with the Selection tool (a regular trim)
moved the edge but left a fade in from nothing / fade out to nothing where
it was; only Ripple trims moved transitions along with the cut.

Fix: after a regular trim, the trimmed clip's one-sided transitions are put
back on its edges, and shortened when the clip gets shorter than them.
Transitions between two clips are untouched (a regular trim cannot move
their cut).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:29:55 +02:00
vangel76andClaude Opus 5.5 b86dcf7bb3 #227: transitions travel with the clips they sit on when moved
Problem: moving a clip dropped the transitions at its edges (#227, point 8
of #374). `edit::move_items` deletes the moved clips before placing them
again, and that deletion removes every transition that refers to them, so
a fade on a clip's edge, or the cut between two clips moved together,
vanished.

Fix: before the move, collect the transitions whose clips all move onto one
track by one offset, and put them back on that track shifted by the offset
once the clips have landed (overwrite and insert moves). A crossfade whose
partner stays behind is still dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:24:52 +02:00
vangel76andClaude Opus 5.5 ce6afffc6f #712: keyboard Slide keeps linked picture and sound aligned at a neighbour limit
Problem: timeline.slideLeft/Right(5) slid every expanded selection member by
the raw requested delta, so when a neighbour limited only one linked partner
(e.g. a short sound-only follower), picture and sound ended up at different
starts. timeline.slide already clamped to a common delta.

Fix: add edit::slide_items, which clamps the delta by the most restrictive
item and slides all items by it, and use it from both the keyboard Slide
commands and timeline.slide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:39:48 +02:00
Oskar 86d113e557 edit: Close Gap refuses a locked track
Closing a gap on a locked track skipped that track but still moved the
sync-locked ones, pulling them out of sync with it. It is now refused
with the usual "locked" error.
2026-10-10 19:15:30 +02:00
JordyUSAandClaude Opus 5.5 696f428faf #374: trim preview keeps the waveform in place instead of squeezing it
While an edge was dragged, the Timeline drew the clip at its new length
from the untrimmed item, so draw_waveform spread the clip's whole source
range over the shorter (or longer) box: the waveform squeezed and the
sound under the pointer moved, which made it hard to trim to where the
music starts (#374, item 13). Trim and roll previews now draw from the
item the edit will leave, so every timeline time still covered shows the
same media and the waveform is cut off at the moving edge. Video
thumbnails show the new first frame the same way. Rate Stretch, which
really fits the same media into a new length, still squeezes.

The item update is factored out of trim and ripple_trim_group into
filmcraft_edit::move_edge, which the preview uses too, so preview and
edit agree for speed-changed, reversed and frame-held clips.

Rendered mid-drag against the released result, the music clip's
waveform matched in 15% of pixel columns before and 100% after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:24:37 +07:00
Brandon ThomasandClaude Opus 5.5 8e0a824dcf Merge origin/main into fix-503 (keep both sides: Volume line + transitions)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:44:25 -04:00
Brandon Thomas 9a3692f239 Merge pull request #257 from MrCloudy2/feat/edit-point-menu-219
#219: right-clicking a cut opens the edit point menu
2026-10-10 05:01:25 -04:00
Brandon Thomas 45936c1d6c Merge pull request #575 from MarekSpodBiedry/issue-548
Slide: keep reversed neighbours' source ranges correct (#548)
2026-10-10 04:44:26 -04:00
Brandon Thomas 9e2ebd696e Merge pull request #562 from MarekSpodBiedry/issue-530
Frame-held Regular In trim stops at the previous clip instead of overlapping it (#530)
2026-10-10 04:42:59 -04:00
Brandon Thomas 0e11974976 Merge pull request #559 from MarekSpodBiedry/issue-527
fix(edit): reversed trims use the media handle their playback direction consumes (#527)
2026-10-10 04:42:34 -04:00
MarekSpodBiedry d23b33a9fe Frame-held Regular In trim stops at the previous clip instead of overlapping it (#530) 2026-10-10 09:14:41 +02:00
MarekSpodBiedry 7484343012 fix(edit): reversed trims use the media handle their playback direction consumes (#527) 2026-10-10 09:14:40 +02:00
MarekSpodBiedry 1ccb51fb78 Slide: keep reversed neighbours' source ranges correct (#548) 2026-10-10 09:14:35 +02:00
MarekSpodBiedry 0c66f5ffd3 Roll: reversed clips take the cut from the opposite source end (#526) 2026-10-10 09:14:03 +02:00
swillunderscoreandClaude Opus 5.5 f61669d208 #224: dragging a transition's end trims that end only
Adobe's Change transition duration page shows the Trim-In / Trim-Out icon on a transition's end
in the Timeline: the drag trims that end, and the other stays. A centred transition dragged by one
end therefore becomes Custom Start, rather than growing on both sides. The alignment rule ("how
the transition changes length depends on the alignment") stays for typed durations: Effect
Controls' Duration and Set Transition Duration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:20:25 -05:00
swillunderscoreandClaude Opus 5.5 d4d1830d99 #430: transitions can be selected, removed, resized and moved (engine)
- filmcraft_edit::transitions: where an applied transition may sit (over its cut, within the
  clips it joins; a fade to or from nothing keeps its clip edge), its alignment as Effect
  Controls shows it (Center at Cut / Start at Cut / End at Cut / Custom Start), set_span, remove,
  and drag_span for the Timeline (an end changes the duration, the middle slides it).
- EditorState::transition_selection; timeline.select {"transitions": [...]} replaces the clip,
  edit point and caption selections, and a clip selection clears it.
- edit.clear (Delete) removes the selected transitions when no clips are selected;
  sequence.removeTransition removes given or selected ones; Ripple Delete stays clip-only.
- sequence.setTransition also takes duration / frames (following the alignment: Center at Cut and
  Custom Start move both ends, Start at Cut the end, End at Cut the beginning), align, start, and
  merge / begin so a drag is one undo step. Out-of-range times are refused before any frame
  arithmetic, atomically.
- sequence inspection reports each transition's alignment and the transition selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 17:59:58 -05:00
MrCloudy2andClaude Opus 5.5 f80b6bb002 Merge origin/main into feat/edit-point-menu-219
Conflict in the timeline's right-click handling (crates/ui-egui/src/panels/timeline.rs):
#220 now remembers the right-clicked clip so Unlink keeps only it selected, and this branch
opens the edit point menu on a clip's edge. Both are kept: the clip is remembered on every
right-click, and an edge also stores the edit point menu's target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:12:49 +02:00
Brandon ThomasandClaude Opus 5.5 701d7c7111 Merge origin/main into multi-stream audio
Keeps #359's contiguous_starts audio timing, now per stream in Mp4Audio /
MkvAudio (starts and preroll in source sample frames). MP4 audio streams and
their decoders are built from the same (track, info) pairs so they stay
index-aligned. Adapts new main code to audio_streams / TrackItem::audio_stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:08:09 -04:00
Idiotic f146d7ba9f feat(media,engine): implement multi-stream container audio support and schema v13
- Update MediaInfo to store all discovered audio streams in audio_streams

- Retain independent stream metadata and decoder state for MP4/MOV and Matroska/WebM

- Update audio_placement_specs to allocate successive tracks via ensure_audio_tracks

- Isolate clip rendering to read assigned audio stream rather than replaying stream 0

- Bump project schema to v13 with backwards compatibility for legacy v12 projects

Resolves #312

Fixes #200

Fixes #315
2026-10-08 22:36:45 -04:00
swillunderscoreandClaude Opus 5.5 1f6f7d4c7d #172: sequence.settings scales Motion with the frame size and takes timecode, render quality and colour
- filmcraft_edit::frame_size::scale_motion: Sequence Settings' "Scale motion effects
  proportionally when changing frame size". Every video clip's Motion Position maps through
  p' = c1 + k(p - c0) and Scale / Scale Width multiply by k = min(W1/W0, H1/H0), static values
  and keyframes: a plain proportional scale for the same aspect, and on an aspect change the old
  frame fits inside the new one, centred. Automatic positions stay automatic, Scale to Frame
  Size clips keep their Scale (the renderer refits them), Anchor Point and Rotation stay.
- sequence.settings: new dropFrame, maxRenderQuality, workingSpace, wideGamut, autoToneMap and
  scaleMotion (default false, so existing callers are unchanged) params, in the same undo step;
  returns {"scaledClips": n}. Drop-frame on a rate without it is refused atomically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:58:37 -05:00
MrCloudy2andClaude Opus 5.5 fcdcc07359 #219: right-clicking a cut opens the edit point menu
Right-clicking a clip's edge on the timeline now selects that edit point and
opens Premiere's edit point menu instead of the clip menu: Ripple Trim In,
Ripple Trim Out, Roll Edit, Trim In and Trim Out (each re-selects the cut with
that trim type, greyed out when the needed side has no clip), Apply Default
Transitions, and Join Through Edits. The body of a clip still opens the clip
menu.

Join Through Edits from this menu joins only the right-clicked cut (and its
linked partner): `sequence.joinThroughEdits` takes a new `cut: [left, right]`
parameter, since `clips: [id]` also matched any other through edit touching
the same piece.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:33:19 +02:00
cirocueto2013-wqandCodex 07d85b0f02 fix: validate hostile project, export and MXF inputs
Validate staged speed edits before adopting their timeline state.

Co-authored-by: Codex <noreply@openai.com>
2026-10-07 20:38:43 -04:00
carlitosteso3andClaude Opus 5.5 e7e96b247f #164: ripple delete says which sync-locked track blocks it; playhead snaps to cuts
- A refused ripple delete / ripple trim now names the sync-locked track whose
  clip is in the way and how to proceed, instead of a generic "would break sync"
  (the edit is refused the same way Premiere refuses it).
- Dragging the playhead no longer snaps onto its own position (it stuck and moved
  in SNAP_PX jumps), snaps on the initial press too, and "Snap playhead in
  Timeline when Snap is enabled" is on by default, as in Premiere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:47:21 -05:00
Peet Guercio bf00c29b14 Merge remote-tracking branch 'origin/fix/ripple-trim-j-cut-head' into fix/ripple-trim-l-cut-head
# Conflicts:
#	crates/edit/src/lib.rs
#	crates/edit/src/tests.rs
#	crates/engine/src/split_edit_ripple_trim_tests.rs
2026-10-06 23:56:32 -07:00
Peet GuercioandClaude Opus 5.5 480b9e7655 Ripple trim: an L cut's sound stays only when its clip ends at the cut
A linked clip that spans the cut while its partner reaches past it too is
no L cut, and still refuses, like the same clip unlinked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 19:30:22 -07:00
Peet GuercioandClaude Opus 5.5 77d3b41a5c Ripple trim: a shorter head after an L cut keeps the earlier sound in place
A ripple trim that shortens a clip's head was refused when the previous
clip's linked sound ran on past the cut on a sync-locked track (an L cut).
That clip never moves, so its sound stays put and the L cut still ends
where it did. Unlinked material across the cut (a music bed) still refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 19:21:38 -07:00
Peet GuercioandClaude Opus 5.5 7f77bdfd3a Ripple trim: a head trim grabbed by its early sound uses the picture's cut
Sync-locked tracks rippled from the grabbed member's start, so grabbing a
J cut's early sound checked and shifted from before the picture's cut and
was refused. For a head trim the cut is the latest member's start. Adds the
test only the new stretch catches: a clip starting at the cut on a
sync-locked track, which the old stretch let fall out of sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:22:13 -07:00
Peet GuercioandClaude Opus 5.5 27124080bf Ripple trim: a shorter head closes the stretch after the cut
A ripple trim that shortens a clip's head was refused as a sync-lock
conflict whenever a sync-locked track held material just BEFORE the cut,
because the check looked at the stretch before it. With a J cut that is
the outgoing clip's sound on the other audio track, so the J-cut take
could never lose its head. The stretch that closes is the one just after
the cut; the clip's own linked partners stay the caller's to trim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 14:16:39 -07:00
Brandon Thomas e89feeac4d Merge pull request #58 from punkhop/fix/speed-ripples-linked-clips-once
edit: a speed change with ripple moves later clips once
2026-10-06 11:37:52 -04:00
Brandon Thomas 555bfe7c18 Merge pull request #57 from punkhop/fix/ripple-delete-split-edit
edit: ripple delete closes one gap for a clip with a split edit
2026-10-06 11:37:47 -04:00
Brandon ThomasandClaude Opus 5.5 2f49131f64 edit: speed changes refuse NaN and never overflow the timeline
set_speed_group accepted NaN (speed <= 0.0 is false for NaN), and a tiny speed (1e-7) saturated the new duration to i64::MAX, after which the ripple shift overflowed (panic in debug, wraparound in release). Non-finite speeds are refused, the new duration is capped at what the timeline can hold, and the ripple shift uses checked arithmetic; a refused edit changes nothing (it works on a copy). Regression test with hostile speeds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:46:30 -04:00
punkhopandClaude Opus 5.5 a8e4c815eb edit: ripple delete closes one gap for a clip with a split edit
Problem: `edit.rippleDelete` on a clip whose linked sound has a
different edge than its picture (a J or an L cut) treated the two
ranges as two gaps and closed both on every affected track. With two or
more clips after it the later ones were moved up twice and the edit was
refused ("items overlap"). With one clip after it the delete went
through, and sequence markers after it were moved by both gaps (one
that landed inside the second gap was removed).

Solution: one set of stretches closes on every track: the time the
deleted clips covered, less whatever stays on the tracks that lose a
clip. Those tracks move up by the same amount, into time that is free
on all of them, so later clips stay in sync and nothing is overwritten.
For a J or an L cut that is the picture's range. Straight cuts close as
before, also with the sounds on alternating tracks. A track that is
locked loses nothing and does not count. `ripple_delete_items` returns
the stretches it closed and the command ripples the markers by those,
so the two cannot disagree.

Things to know. A gap can stay on one track when a clip that stays on
another of the tracks covers part of the deleted time: a sound shorter
than its picture with other sound right up against it, or unlinked
clips on different tracks deleted in one call. When nothing can close
at all the edit is refused and nothing changes; some such calls went
through before, moving clips by a gap another track did not have. A
selection wholly on locked tracks is refused as locked; before, it
deleted nothing and still shifted the sync-locked tracks.

Tests: an edit-crate test (a split on both sides, two neighbours,
clips apart in time, sounds on alternating tracks, a short sound with
and without neighbours, a locked track, the refusal); engine tests
through `edit.rippleDelete` for a J cut with a marker, undo and redo,
an L cut, two neighbours with a split between them, two neighbours with
sounds on alternating tracks, and a straight cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:23:27 -07:00
punkhopandClaude Opus 5.5 4a0ab32480 edit: a ripple trim takes a later split edit along
Problem: `timeline.trim` with `mode: "ripple"` on the clip before a
split edit moved the later picture and left its linked sound behind.
With a J cut (the next clip's sound starts before its picture, on a
sync-locked track where the trimmed clip has no partner) a sound that
led its picture by 12 frames led by 20 after lengthening by 8, with no
error. Shortening was refused as a sync-lock conflict, because the
early sound sits in the stretch that closes.

Solution: in `ripple_trim_group`, a clip that starts before the cut,
reaches past it and is linked to a clip that shifts now shifts with it,
on the tracks that ripple (the members' tracks and sync-locked tracks).
It no longer counts as material in the way of a shortening. When it has
no room to follow (another clip right before it, or the sequence start)
the trim is refused with the sync-lock error and nothing changes (an
overlap on any track that ripples now reports that error instead of
the generic "items overlap"). A
linked clip that ends before the cut is left alone, as before, and so
are the trimmed clips' own partners. A transition into a follower from
a clip that stays behind would be left over a gap; it is removed.

Tests: an edit-crate test for longer, shorter, both refusals, the
clip that ends before the cut, the In edge, the trimmed clip's own
partners and the transition; engine tests through `timeline.trim` for
longer and shorter with undo and redo, and for both refusals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:23:27 -07:00
punkhopandClaude Opus 5.5 f06bfc4648 edit: a speed change with ripple moves later clips once
Problem: `clip.speedDuration` with `ripple: true` on a clip that has
linked sound set the speed on the picture and then on its sound, and
each one rippled the later material on its own. Slower (50%) moved the
later clips by twice the change and left a double gap. Faster (200%)
was refused ("items overlap") when more than one clip followed: the
second ripple pulled the farther clips onto the nearer ones.

Solution: `set_speed_group` changes a clip and its linked partners on
other tracks as one edit and ripples once, on the members' tracks and
on sync-locked tracks. The command groups the selected clips by link
(one clip per track in a group); clips that are not linked, or linked
clips of one track, still change one after the other. When the members'
lengths change by different amounts (a split edit), later material
moves by the largest change, so no member is overlapped and the tracks
stay in sync; the shorter member is followed by a gap. The group edit
works on a copy, so a failure changes nothing. `set_speed` keeps its
signature and behaviour for one clip.

Tests: an edit-crate test (slower, faster, a split edit both ways, two
clips of one track and a missing clip refused with nothing changed);
engine tests through `clip.speedDuration` for slower with undo and
redo, faster, two clips in one call, no ripple, picture and sound both
named with Linked Selection off, linked clips of one track, a split
edit, and a clip that is not in the sequence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:14:24 -07:00
Brandon ThomasandClaude Opus 5.5 31ae72f015 engine, edit: never crash — commands without expect/unwrap/panic
- Commands: place/nest/sequence settings/import/multicam/subclip/audio channels/extract audio,
  graphics reset duration, dynamic trim and Make Subsequence return errors instead of `expect`
  when an item, sequence or track is missing (stale ids from a damaged project or a racing
  agent); File ▸ Import detects caption/interchange documents without a second `expect`.
- `find_spec` (graphics) returns `UnknownCommand` instead of `expect`.
- Built-in effect lookups (presets, demo project) fall back to a bare instance instead of
  `panic!`/`expect`; the demo project falls back to an empty one.
- Multicam record base, shortcut presets and field parsing without `expect`.
- edit: caption merge/move and transcript caption blocks without `expect`.
- Both crates deny unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:02:01 -04:00