Commit Graph

88 Commits

Author SHA1 Message Date
irgendwasmitfelix 7ac0269d2c Timeline: colour labels for tracks (#1186)
A track can carry a colour label, as many NLEs offer. The strip at the
left edge of a track header opens a menu with None and the 16 labels
(with the user's label names and colours, as in Edit > Label). The label
tints the header and the lane.

- `Track.label: Option<Label>`, serde default and skipped when None, so
  older projects load and plain tracks save as before
- `timeline.setTrack {label}` (a label name; "" / "None" / null clears),
  an undoable Track Settings step; unknown names are refused
- `sequence.inspect` reports each track's `label`
- automation ids `timeline.track.<V1|A1…>.color` and `.color.<Label>`

Renaming, adding and deleting tracks from the header are covered by the
open PRs for #654 and #1630, so this only adds the colour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8DvNHjchh5Gf2q24UsWQn
2026-10-19 09:41:56 +02:00
Brandon Thomas 9e9c3c315f Merge pull request #836 from vangel76/gap-parity-effects
#831: cargo xtask parity measures effects and transitions (--area)
2026-10-11 20:29:55 -04:00
Ikki Nishihara 7a3d003194 fix: サブクリップ配置で親の音声を保持 #913 2026-10-12 01:55:24 +09:00
vangel76 f8b37e1463 #831: cargo xtask parity measures effects and transitions (--area)
Problem: `cargo xtask parity` (#781, #782) measured only the menu bar; the
Effects panel's effects and transitions had no measured parity figure
(docs/gaps.md G7).

Fix: `--area effects|transitions` compares the catalogue the engine's
effect definitions list in the Effects panel (new `effect_catalog`
example in filmcraft-project, the panel's own top-folder filter) with a
reference of folder paths or bare names (default the local, never
committed plan/premiere/<area>.json). Same report, caps and `--min` as
menus; placement by folder; Presets / Lumetri Presets and the other
area's items are out of scope, so one panel snapshot serves both areas.
`--area menus` (default) is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:15:53 +02:00
vangel76 16493a7f70 #813: masks round their corners (rounded rectangle masks)
Problem: Premiere 26's redesigned mask tools give rectangle masks
rounded corners; FilmCraft masks only had sharp corners.

Fix: MaskPath::with_rounded_corners replaces each sharp corner between
straight edges by a circular fillet (two vertices joined by a cubic arc,
handles 4/3·tan(φ/4)·r, shrunk to fit half of each edge; hostile radii
and indices are clamped or ignored). masks.add takes `cornerRadius` for
a rounded rectangle, and the new masks.roundCorners command rounds an
existing mask's corners (or the listed vertices) in one undo step, on
every Mask Path keyframe so animated paths keep interpolating.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:47:09 +02:00
Brandon Thomas 6239d3d65e Merge pull request #751 from vangel76/fix-456-nest-bins
#456: a bin can be dragged into another bin and back out
2026-10-10 15:53:08 -04:00
vangel76 ae488523a9 Merge remote-tracking branch 'upstream/main' into fix-620-project-notes
# Conflicts:
#	crates/ui-egui/src/i18n/es.tsv
#	crates/ui-egui/src/i18n/ja.tsv
#	crates/ui-egui/src/i18n/zh-cn.tsv
2026-10-10 21:15:04 +02:00
vangel76 6f650a363a #456: a bin can be dragged into another bin and back out
Problem: a bin stayed wherever it was created. `project.moveToBin` only
moved project items, and bin rows and cards in the Project panel could not
be dragged, so the only way to nest bins was to create them inside each
other.

Fix: `project.moveToBin` also takes bin ids in `items` (as `project.delete`
does) and moves the bin with everything in it, skipping the root and any
move that would put a bin inside itself. In the Project panel, bin rows
(List view) and bin cards (Icon view) can be dragged: dropping one on
another bin nests it there, and dropping it on empty space moves it into
the bin the view shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:13:42 +02:00
vangel76 110c1783c3 #620: Project Notes panel, saved in the project
Problem: a project had nowhere to keep free text (to-dos, instructions
for whoever opens it next, frame numbers).

Fix: plain-text `Project::notes` (schema v15, no-op migration so older
builds refuse instead of dropping it), `project.notes` /
`project.setNotes {text, merge?}` engine commands (undoable, capped at
1 MiB; calls with one `merge` key share an undo step), and a
Window > Project Notes panel whose editor (`notes.text`) commits every
change, one typing session per undo step. Translations for es, ja,
zh-cn (and the panel title for pt-br, uk).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:09:13 +02:00
Brandon Thomas c11f20cf9c Merge pull request #708 from impostazioni/fix/caption-font
Captions: render the track font and add a font picker
2026-10-10 14:56:17 -04:00
Brandon Thomas ea33e3cb00 Merge pull request #679 from vbasky/fix/415-transform-shutter
Apply Transform shutter angle when the override is on
2026-10-10 14:54:38 -04:00
impostazioni 6aa4146c16 Captions: render the track font and add a font picker
The caption style's `font` was stored and settable through
captions.setStyle, but the burn-in renderer always drew Inter SemiBold.
Lay out, wrap and measure captions with the track's family (Inter when
the name is empty or not installed), so the Program monitor and burned-in
exports use the chosen font.

Add a font dropdown to the caption style strip in the Text panel's
Captions tab (automation id `text.captions.style.font`, options
`text.captions.style.font.option.<family>`, registered only while
visible). macOS-private faces (names starting with '.') are not offered:
some lack a space glyph and run words together.

Tests: renderer regression test (a non-Inter family changes the pixels;
failed before), fallback test for empty/unknown names, and a headless UI
test that picks Noto Serif from the dropdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:29:09 +01:00
Vikram Bhaskaran 9e77d9d786 Apply Transform shutter angle when the override is on.
The Transform effect listed a shutter angle but had no override, so motion blur never used it. Checking Use Effect Shutter Angle now samples the animated transform across that angle.
2026-10-11 02:48:52 +11:00
Vikram Bhaskaran 80db16ebd5 Apply Ultra Key Setting as a parameter preset.
Relaxed and Aggressive previously only scaled two internal multipliers, so the matte stayed at Default. Switching Setting now writes Premiere-like matte values and the renderer uses that set, including Custom for hand-tuned keys.
2026-10-11 02:34:08 +11:00
Brandon Thomas c030f78762 Merge origin/main into tts-narration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 08:13:05 -04:00
Brandon Thomas c9e803ff3b Merge origin/main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:24:39 -04:00
Brandon Thomas e831d0f248 Merge remote-tracking branch 'origin/main' into fix-434 2026-10-10 07:08:57 -04:00
Brandon Thomas 68c297b987 Arc<Sequence>: fix the missed &mut sites and closing braces; drop the unrelated key-dispatch test
source_monitor markers and format's audio-stream clamp now detach with Arc::make_mut;
restore the final braces in clip_ops.rs and project/src/lib.rs (cargo fmt);
scripted.rs is back to main (the test covered key dispatch, which this PR doesn't change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:08:56 -04:00
Brandon Thomas 664c13efeb Merge origin/main into #278; schema v14, neural-voices off by default, review fixes
- Narrations take schema v14 (main's v13 is audio streams): v13_to_v14, docs updated.
- neural-voices is off by default in the desktop app, like whisper; the model download stays
  an explicit, confirmed job.
- Removed the debug probe2 test module from tts-text.
- project_subset prunes narrations of items it drops.
- unique_wav_path is bounded (99 999 names) and returns an error instead of looping.
- narration.rs uses web_time::Instant (wasm clock test); tts.* commands get the
  "Text to Speech" shortcut category, with es/ja rows for it and Edit Narration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:55:59 -04:00
Brandon Thomas a20ac75ee6 Merge remote-tracking branch 'origin/main' into fix-434 2026-10-10 06:55:52 -04:00
Brandon Thomas a8e38c4e07 Merge remote-tracking branch 'origin/main' into fix-445 2026-10-10 05:47:31 -04:00
Brandon Thomas 4d3fe1bdb0 Merge pull request #506 from liangforstudy/fix/save-playhead
Project files: each sequence reopens with its playhead where it was left
2026-10-10 05:02:24 -04:00
Vikram Bhaskaran ac83bae223 Blend graphic gradients in linear light and keep per-character fills solid.
Stops were mixed in encoded sRGB, and any run with its own fill turned the whole layer into a solid colour. The ramp now blends in linear light, turns with the layer, and leaves a per-character fill solid.
2026-10-10 13:18:45 +11:00
Vikram Bhaskaran d55ebe94e5 Fill graphic shapes and text with a linear gradient.
Appearance › Fill Type paints a ramp across the layer bounds, and the ramp turns with the layer. A per-character fill colour still paints those characters as a solid.
2026-10-10 12:05:03 +11:00
liangforstudy 72e3eeb5de Project files: each sequence reopens with its playhead where it was left
The engine already keeps one playhead per sequence while a project is open, and the
project file's view already stores each sequence's zoom, scroll and track heights, but
not its playhead: after saving and reopening, every sequence started at zero.

ProjectView gains `playheads` (sequence id -> Tick), written beside the tabs and views
and restored with them when "Restore open sequences when opening projects" is on. Files
written before it have none and open as they did. Values from a file are not trusted:
ids that are not sequences are dropped, playheads are clamped to 0..ProjectView::MAX_PLAYHEAD
and snapped to a frame of their sequence.

Tests: each_sequence_reopens_with_its_playhead_where_it_was_left (fails without the
fix), hostile playheads in a_damaged_view_in_a_project_file_is_cleaned_up_or_ignored,
a deleted sequence's playhead is not written, and the format round trip carries it.

Also adds my contributor credit entry (people.toml).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qq8BK1LmohzE1ga4NgAdWM
2026-10-10 05:22:05 +08:00
lloyd ff8728af08 Anamorphic media: pixel aspect ratio in placement, monitors and New Sequence From Clip
A 1440x1080 clip with 4:3 pixels showed squeezed: the demuxers read its
pixel aspect ratio, but nothing after import used it. New Sequence From
Clip made a 1440x1080 sequence with square pixels, the Motion transform
sized clips from storage pixels (pillarboxed at 1440 wide in a 1920x1080
sequence), and the monitors drew the frame at its storage aspect.

- project: checked_par / sane_par / par_ratio / conformed_size treat
  file and project ratios as hostile (a zero term, or more than 8:1
  either way, is square). MediaClip::pixel_aspect (Interpret Footage
  wins over the file), Project::source_par (subclips follow the parent,
  nested sequences have their own, graphics and adjustment layers have
  none) and Project::conformed_source_size.
- render: motion_matrix takes the source's ratio and works in square
  display units (anchor in source pixels, position in sequence pixels,
  scale and rotation at the display aspect); Scale to Frame Size fits
  the display size. CPU, GPU plan, adjustment regions and the mask /
  graphics overlays share it.
- engine: New Sequence From Clip, New Sequence from an item and
  multicam sequences copy the clip's ratio; default media scaling and
  Fit / Fill frame use the display size; generators made for a
  non-square sequence get its pixels; transcodes render pixel for
  pixel. clip.interpretFootage takes pixelAspect ([num, den] or "file").
- ui: the Source and Program monitors draw the picture at its display
  aspect; overlays map both axes; Sequence Settings shows the display
  aspect (16:9 for 1440x1080 at 4:3).

Tests: render par_tests (fill, GPU plan parity, Scale to Frame Size,
non-square sequences, rotation, Interpret Footage, hostile ratios),
engine par_tests (New Sequence From Clip, overwrite into 1920x1080,
default scaling, Fit / Fill, Interpret Footage with undo and refused
values, hostile file ratios, generators, and ffmpeg-made MP4 and MKV
files), project unit tests, and the headless par_ui monitor test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 18:28:56 +02:00
David Campos 5f6c57984a project: share sequences across edit snapshots (lib.rs) 2026-10-09 19:25:46 +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
Manuel Camacho 66ba5bbb3c M7.9: Text to Speech narrations (panel, tts.* commands, built-in voice)
Write what a narrator says, pick a voice, pitch and pace, and add it to
the timeline. Selecting a narration clip loads it back into the panel;
Save regenerates it in place as one undo step, keeping the clip's start
and length (longer speech kept whole in the file, shorter speech padded
with silence).

- filmcraft-tts (L2): Voice trait, voice catalogue, pause markers
  ([pause 1s]), and an original formant synthesizer (Basic Female/Male;
  robotic placeholder until the neural voices land).
- project: Narration + Project::narrations (schema v13, no-op migration).
- engine: tts.voices/preview/create/edit/inspect; narrations never
  overwrite clips (free targeted track, free track, else a new track).
- ui: Text to Speech panel beside Properties, preview playback,
  click-to-edit, automation ids.

Gates: fmt, clippy -D warnings, layers, assets and wasm (rustup
toolchain) pass. Tests: all pass except three that fail identically on
main (61238bf) on this machine: shortcuts_tests::assign_reassigns_
conflicts_and_undoes, gpu fx_tests::gpu_effects_match_cpu_exactly, and
the frame pool test, which passes when run alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 06:52:23 -07:00
Brandon Thomas de70468ab6 #210 review: bound MXF reads by the file, keep legacy projects opening, allow 16K
- mxf: read_pcm bounds a request by the sample frames the file actually stores (plus 16M
  samples of zero padding) instead of a fixed 16,777,216-sample cap, so whole-clip reads of
  long stereo MXF (transcription) work again; PCM/AES3 chunk buffers are clamped to the source
  length instead of a 256 MiB cap; decode_pcm's output is bounded by its input, so its
  fixed cap goes (the AES3 >8-channel refusal stays; a request is also bounded by the source
  length divided by the channel count). read_sample keeps only the source-length bound.
- mxf: index_entries no longer fails open() past 1,048,576 entries (the sparse BTreeMap
  already bounds memory by real entries); long recordings open.
- project/format: new Sequence/Track/CaptionTrack::check_bounds() hold only the hostile-value
  bounds (settings, marks, overflowing time ranges); project load and export ranges use it, so
  projects that break only structural rules (overlap, zero duration) still open and export.
  check() = check_bounds() + the structural rules, as before.
- project: frame-size cap raised to 268,435,456 pixels, 32768 per side (16K, 16384x8192).
- engine: checked_u32_p accepts integer-valued floats (1920.0); renderable_sequence returns
  an EngineError (new Session::try_render_program_at, used by automation render) instead of
  eprintln.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:52:53 -04:00
cirocueto2013-wq 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
cirocueto2013-wq 1fd30aa29d engine: bound subclip media resolution and reject cycles
Co-authored-by: Codex <noreply@openai.com>
2026-10-07 19:56:58 -03:00
justjohn12345 8407f233c1 Graphics: point text and paragraph text, as Premiere has them
A text layer is now one of two types, each with Premiere Pro 26's handles:

- Point text (Type tool click): every handle scales the layer about its
  anchor point, both axes alike; the font size is untouched.
- Paragraph text (Type tool drag): the dragged rectangle is a box the text
  wraps in. Handles move that side or corner of the box, the opposite side
  stays, the text re-wraps at the same size. Lines below the box are not
  shown and the bottom-right handle turns into a red plus.
- The anchor point can be dragged with the Selection tool; the layer stays.
  It is picked before anything under it, so a shape dragged by its exact
  centre moves its anchor.
- Properties > Text > wrench opens Text Properties, where Text Layer Type
  switches a layer between the two (graphics.setTextType); the text stays
  where it is.

Shapes keep stretching away from their opposite side.

New: text layer parameter box_height, graphics.newText `box`, and
textType / box / overflow / anchor / scale in graphics.list. A layer saved
before a parameter existed can now be given it with graphics.set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:54:26 -04:00
Brandon Thomas d3c3b0f6e8 NEST: hostile project data cannot panic a nest's length, oversize its mix or multiply AAF needs
A negative clip duration no longer panics the clamp when a nest follows its contents; a nested
sequence's claimed sample rate is clamped before it sizes the mix buffer; audio_needs lists each
nested sequence once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 06:00:36 -04:00
Brandon Thomas 19d2f15148 Merge remote-tracking branch 'origin/main' into nest-116
# Conflicts:
#	crates/ui-egui/src/lib.rs
#	crates/ui-egui/src/panels/timeline.rs
2026-10-07 06:00:36 -04:00
Brandon Thomas b8cc3925c3 Merge pull request #68 from punkhop/fix/clear-deletes-a-bin
project: Clear deletes a bin it is given, with everything in it
2026-10-07 05:56:48 -04:00
justjohn12345 eeec772671 NEST14: a project reopens with its sequence tabs and their views
Premiere Pro 26.5.2 reopens a project with the same sequence tabs, in the
same order, the same one shown, each at the zoom it was left at. FilmCraft
opened every project on its first sequence; the "Restore open sequences when
opening projects" preference existed and nothing read it.

A saved project file now has a `view` beside `project`: the open sequences in
tab order, the active one, and each sequence's view from NEST12. It is not
part of the project: no undo step, no "edited" mark, and no schema bump (older
builds ignore it). `file.open` restores it when the preference is on.

Nothing in the view is trusted. Ids that are not sequences of the project and
repeats are dropped, numbers are clamped, and a view that cannot be read is
ignored instead of failing the open. A view with no open sequence leaves the
project on its first sequence, so a project saved by a script or the CLI does
not open on an empty Timeline.

Opening or recovering a project now tells the frontend which sequence is
shown, so the Timeline takes that sequence's view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:23:19 -04:00
justjohn12345 6aefdef661 NEST12: every sequence keeps its own Timeline zoom, scroll and track heights
The Timeline had one view for all sequence tabs, and showing another sequence
fitted it to the panel every time. In Premiere Pro 26.5.2 each sequence tab
has its own zoom and its own track heights (expanding the tracks of one
sequence leaves the others as they were).

Each sequence now has a `SequenceView` (zoom, horizontal scroll, the vertical
scroll of the video and audio halves, video and audio track height) in
`EditorState::timeline_views`. The panel's view is the active sequence's: it
is put away when another sequence is shown and taken back when the sequence is
shown again, also when a tab is closed. A sequence shown for the first time is
fitted, at the default track heights. A view changed in the session (a
command, the control channel) is taken over by the panel. The view of a
deleted sequence is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:21:19 -04:00
justjohn12345 fc557a1c10 NEST3: a nest follows its contents
A nest keeps its length when its sequence changes. Tests now pin what already
held: it trims out to reveal added material and no further, shows and plays
nothing past shortened contents, and the outer sequence shows edits made inside.

New: the Timeline hatches the part of a nest past the end of its contents, as
Premiere does (Project::nest_overhang, automation id timeline.clip.<id>.empty).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 17:23:38 -04:00
justjohn12345 29d9d0ac5a NEST1: a sequence can never contain itself
A sequence placed inside itself (directly or through another nest) crashed the
app: nested audio mixing and the frame plan's multi-camera shortcut recursed
until the stack overflowed.

- render: the audio mixer and the multi-camera shortcut follow nests to the same
  depth as the picture (MAX_NEST_DEPTH), then give silence / fall back.
- project: Project::nest_cycle finds a sequence that contains itself.
- engine: every edit that would create one is refused, whichever command asks
  (place, Source insert/overwrite, paste, ...). A project opened with a cycle
  already in it can still be edited and repaired.

Regression tests: both crashes (stack overflow before), the refusals, repair.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 17:23:38 -04:00
punkhop 8e867cb8c9 project: Clear deletes a bin it is given, with everything in it
Problem: no command could delete a bin. `project.delete` takes item
ids, and a bin's id passed to it answered ok and changed nothing, so a
bin emptied after an XML import (every clip moved to new bins) stayed
in the project for good.

Solution: `project.delete` also accepts bin ids in `items`. A bin
named there is taken out of the tree with its sub-bins, and every item
inside goes the way a named item goes (out of the project, off every
timeline). The project's own top bin is never removed. The reply now
says how many items and bins went. One undo step, as before.

Test: clear_deletes_a_bin_with_everything_in_it (fails before: the
bins stay).
2026-10-06 06:06:34 -07:00
punkhop ff8aa8749d engine: a project saved after an interchange import opens again
Problem: a Final Cut 7 XML whose Basic Motion filter sets only `scale`
makes the importer add the Motion effect with its defaults, where
`position` and `anchor` are the NaN "auto" sentinel. Placing a clip
resolves those with `resolve_auto_points`; the interchange import did
not (any format: the clips are built with the effect defaults).
serde_json writes NaN as `null`, and the reader then refused the file:
"project file is damaged: invalid type: null, expected f64". The project
could be saved but never opened again.

Solution:
- project: a point parameter's value (`ParamValue::Vec2`) reads a `null`
  coordinate back as NaN, so "auto" survives a save. Only there: `Vec2`
  itself is unchanged, and a `null` in a mask vertex or any other point
  is still a damaged file.
- project: `Project::resolve_placed_auto_points` resolves the points of
  the clips in chosen sequences. Points measured in the frame use the
  sequence size. `anchor` is measured in the clip's source
  (`Project::source_size`, a subclip through its parent, as the renderer
  resolves it) and is resolved only for sources the caller says have
  their final size.
- engine: the import resolves in the edits it already makes, so the undo
  steps are what they were. In "Import ...": everything but the anchors
  of file media, whose size is only what the document says so far (an
  EDL says nothing and the importer assumes the sequence size). In each
  "Link Media": the anchors of that file's clips, from the probed size.
  A clip whose file is not found keeps an "auto" anchor, which the
  renderer centres in whatever size the media has, as before this
  change.
- engine: a later Link Media (relink.rs `apply`, behind `media.relink`
  and `media.autoRelink`) resolves those waiting anchors in its own undo
  step, when it has read the file. Nothing else resolves them: a file
  that merely comes back at its old path, or a forced relink that does
  not read the file, leaves the anchor "auto" (still rendered centred).

Why no schema step and no repair on load: the file's shape is not
changed. Files with `null` points were written by every build before
this one, at the current schema version, so a migration (keyed on the
version) cannot select them, and a bump would only make every new
project unreadable to those builds. They open now and their points are
"auto", exactly as in the session that wrote them; resolving them on
load would mean guessing the size of media that was never linked.
docs/project-files.md describes `null` points.

Tests: project
`point_params_keep_auto_through_json_and_no_other_point_reads_null` and
`placed_auto_points_resolve_once_the_source_size_is_final`; format
`auto_points_written_as_null_open_again`; engine
(interchange_auto_points_tests.rs): import with the media missing, save,
reopen, undo, redo; a linked file whose real size differs from the
declared and the sequence size (undo of Link Media and of the import,
redo); an EDL clip smaller than its sequence; import with the media
missing, save, reopen, relink (anchor becomes a number, undo, redo, a
second relink leaves it). All fail without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:28:54 -07:00
Brandon Thomas 9cf892a7b0 Merge pull request #20 from storytold/pr/never-crash-importers
Importers: never crash (interchange, captions, project, text, automation)
2026-10-04 16:17:33 -04:00
Brandon Thomas 3a392ae8da Clippy 1.99: fix new lints so cargo xtask ci is green again
Rust/clippy 1.99 added `chunks_exact_to_as_chunks` and tightened
`needless_late_init` and `drain_collect`, failing `clippy -D warnings`
on main.

- `chunks_exact(N)` / `chunks_exact_mut(N)` with a constant N become
  `as_chunks::<N>()` / `as_chunks_mut::<N>()` (fixed-size arrays, so
  several byte reads drop their per-element indexing). relink's
  fingerprint uses the `(chunks, remainder)` split; its hash output is
  unchanged.
- Late-initialised `let`s in ac3 bit allocation and opus CELT
  bands/rate become `let x = if/match …`.
- `dpb.drain(..).collect()` becomes `std::mem::take(&mut dpb)`.

No behaviour change; `cargo xtask ci` passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:55:23 -04:00
Brandon Thomas f0e6b12819 Importers: never crash — interchange, captions, project, text, automation
- interchange: exporters (FCP7 XML, FCPXML, OTIO) skip a missing nested sequence or media with a
  report warning instead of `expect`/`unwrap`; EDL event grouping and M2 lines, the XML writer
  and the AAF/OMF dispatch arms have no panics. The WAVE header saturates instead of overflowing
  for >4 GiB descriptors (found by fuzzing: a mutated EDL exported to OMF), regression test
  `wav::tests::huge_wav_header_saturates`.
- captions: EBU STL frame rate read with `get()`; MCC rows, TTML text and the STL dispatch arm
  without `expect`/`unreachable!`.
- project: bin moves, 5.1 pan lanes and text/shape layer constructors without `expect`.
- text: font resolution and line layout without `expect` (the bundled-face lookup keeps one
  documented `#[allow(clippy::expect_used)]`: the faces are compiled in).
- automation: the MCP bridge returns an error instead of `expect` when not connected.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:38:13 -04:00
Brandon Thomas 2a199425d0 M8.10: HDR-aware Lumetri, mastering metadata tone-map peak, HSL Refine, Lumetri Presets grid, Comparison View match
- Lumetri grades PQ / HLG working spaces in their own signal normalised to
  HDR White (Basic Correction) / HDR Range (Curves) in cd/m²
  (filmcraft_color::GradeSpace): exposure in stops of light, highlights above
  HDR White kept, HDR Specular scales them; Rec. 709 unchanged.
- HDR static metadata (mdcv/clli, Matroska MasteringMetadata/MaxCLL/MaxFALL)
  on VideoStreamInfo::hdr; PQ media use MaxCLL / mastering peak as the
  BT.2390 source peak; media.colorInfo reports it.
- HSL Secondary Refine: Denoise (median of the key) and Blur (Gaussian).
- Lumetri Presets (18 original looks in 4 folders) with thumbnails rendered by
  our Lumetri on a procedural preview picture; Effects panel rows and a
  thumbnail grid when wide; lumetri.presets / applyPreset / presetThumbnails.
- Color Match: Comparison View toggle; Apply Match uses its reference frame.
- Goldens lumetri_hdr_pq and lumetri_presets_grid; D-Log M stays unsupported
  (no published formula).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:31:00 -04:00
Brandon Thomas ac4d458712 Merge main into M10.7 2026-10-03 01:02:27 -04:00
Brandon Thomas ee9fd53079 M10.7: graphics templates (.fcgt), rolls/crawls, responsive design, per-character styles, MCC/STL/TTML/DFXP captions
- Text engine: per-character style runs (layout_rich), per-run underline/colour index.
- Project: graphic_design (style runs, pins, roll/crawl maths, responsive time, source graphics),
  gtemplate (.fcgt format, validation, base64 resources, 8 original built-in templates; .mogrt refused).
- Render: item-level graphic evaluation (responsive time, pins, rolls), per-run fills.
- Engine: graphics.template.* (list/apply/export/install/remove/set/controls/thumbnail),
  graphics.setRoll/setResponsiveTime/pin/setCharStyle, Upgrade Caption to Graphic,
  Upgrade to Source Graphic (shared edits), Replace Fonts in Projects, fonts used.
- Captions: MacCaption MCC (CDP, CEA-608 + CEA-708), EBU STL (Tech 3264), TTML/IMSC1, DFXP.
- Schema v12 (no-op migration + fixture), docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:52:15 -04:00
Brandon Thomas 1fa560c1c7 M7.8: audio parity — 5.1 multichannel, voice-over record, Remix, Clip Mixer automation
5.1 / multichannel:
- audio-dsp::channels: layouts (L R C LFE Ls Rs), ITU-R BS.775 up/downmix, 5.1 Mixdown Types,
  equal-power 5.1 panner (puck, Center %, LFE).
- Mixer graph buses are 2 or 6 channels wide; 5.1 tracks/submixes/Mix; strips feeding a 5.1 bus
  use the 5.1 panner (lanes pan51.x/y/center/lfe stored in MixerStrip::lanes, no schema change);
  5.1 strips fold into stereo buses with BS.775; per-channel meters; clip effects on 6 channels.
- file.newSequence {mix, trackType}, sequence.settings {mix}; playback honours the 5.1 Mixdown
  Type preference on stereo devices and plays 6 channels on 5.1 devices.
- Export Audio Channels 5.1: WAV WAVE_FORMAT_EXTENSIBLE (mask 0x3F), QuickTime PCM with a chan
  atom, AAC channel configuration 6 (reordered to C L R Ls Rs LFE).
- Track Mixer: 5.1 panner puck with Center/LFE, N-channel meters, Show/Hide Tracks, Meter
  Input(s) Only; transport record button runs voice-over.

Voice-over: engine AudioInput trait + SyntheticInput, cpal input in the app, audio.voiceover.*
commands (punch-in, pre/post-roll, countdown cues, WAV next to project / scratch disk, one undo
step), track header mic button and Voice-Over Record Settings dialog.

Remix: beat tracking + chroma/cepstra self-similarity planner in audio-dsp, hidden `remix` clip
effect holding the plan, render::remix playback with equal-power crossfades, clip.remix.*
commands, Remix tool and Remix Properties dialog.

Clip Mixer automation: per-track Latch/Touch/Write (clipMixer.setMode/touch/release) heard live
and written as thinned clip Volume/Panner keyframes at the end of the pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:40:35 -04:00
Brandon Thomas feae349814 Merge remaining Premiere menu items (M3.11, schema v11) into integration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:47:51 -04:00