Commit Graph
56 Commits
Author SHA1 Message Date
Brandon ThomasandClaude Opus 5.5 4aea06e2ae GPU2: standard effects on the GPU compositor (#30)
A media clip whose enabled standard effects all have a GPU implementation
plans as a GPU layer carrying them (render::plan::LayerFx): parameters are
evaluated on the CPU at the frame's time (render::gpufx::FxOp::eval, shared
with the CPU path so both clamp hostile values identically), the source is
drawn into an Rgba32Float working image at the size the CPU decodes it, the
effects run as WGSL compute passes (gpu::fx, fx.wgsl) with the CPU
reference's math, and the result is placed with Motion, opacity and blend
mode as before. Layers without effects keep the single draw.

GPU effects: Brightness & Contrast, ProcAmp, Tint, Black & White, Color
Balance, Leave Color, Change to Color, Color Pass, Color Replace, Channel
Mix, ASC CDL, Gamma Correction, Levels, Extract, Invert, Posterize, Alpha
Adjust, Gaussian / Camera / Directional Blur (+ legacy aliases), Sharpen,
Unsharp Mask, Crop, Edge Feather, Transform, H/V Flip, Mirror, Offset.
Masked effects, opacity masks, non-finite parameters and minifying
Transform/Crop zooms stay on the CPU. Devices without compute shaders run
the effects on the CPU inside the compositor.

Also fixes a debug-build overflow in Image::transformed for non-finite maps.
Parity tests (exact f32 checks and composited plans), a golden test that
plans effect chains as GPU layers, and the stack3-fx playback benchmark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 07:50:20 -04:00
Brandon ThomasandClaude Opus 5.5 6102a3794a GPU1: blend modes on the GPU compositor (#30)
All 27 blend modes now composite on the GPU instead of sending the
whole frame to the CPU.

- render::plan: PlanLayer carries its Blend; plan_frame no longer falls
  back to a CPU frame for non-Normal modes. Media, graphic clips,
  multicam angles and CPU-rendered layer images (standard effects) keep
  their clip's mode; inside a transition the mode is Normal, as in the
  CPU reference. execute_cpu composites with each layer's mode.
- gpu: Normal keeps the single fixed-function pass. Dissolve also uses
  fixed-function "over": the CPU's 64-bit pixel hash is reproduced in
  WGSL on u32 pairs (pattern bit-exact). The other 25 modes copy the
  accumulator region under the layer's quad into a backdrop texture and
  composite in fs_blend with the CPU math (sRGB-encoded straight colour,
  W3C formulas, back to linear, same premultiplied mix and clamping).
- Tests: per-mode GPU-vs-CPU parity (gpu crate: overlapping transformed
  layers, partial opacity/alpha, empty/partial backdrop; golden: every
  mode through plan_frame incl. an effect layer, blended clips in a
  cross dissolve), exact-value edge cases (black/white, Divide by 0,
  Dodge/Burn at 0 and 1, zero alpha) within 4.9e-4, Dissolve pattern
  exact, off-output layers, quad bounds.
- Bench: new stack3-blend playback scenario (V2 Screen, V3 Overlay);
  CPU ms/frame 151-214 -> 86-89, Full 19/173 and 138/54 -> 191/1, 192/0.
- Docs: architecture §5, gpu crate docs, testing, performance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 03:25:16 -04:00
Brandon Thomas b081fd20df Merge pull request #21 from storytold/pr/never-crash-export-render
render, gpu, cli: never crash (no expect/unreachable, deny the panicking lints)
2026-10-04 16:18:11 -04:00
Brandon ThomasandClaude Opus 5.5 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 ThomasandClaude Opus 5.5 5628fc55bc render, gpu, cli: never crash — no expect/unreachable, deny the panicking lints
- gpu: `composite` returns its output view by value (a cheap handle clone) taken from the
  render-target setup, so the accumulation/output views no longer `expect` their slots.
- render: lightning bolt subdivision, tracker pyramid and the Lumetri preset instance without
  `expect`.
- cli: dotted parameter insertion without `expect`; the render/mcp backend arms report a usage
  error instead of `unreachable!`.
- Each crate denies unwrap/expect/panic/unimplemented/todo/unreachable outside tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 14:04:42 -04:00
Brandon ThomasandClaude Opus 5.5 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 ThomasandClaude Opus 5.5 c025c42470 M3.13: multicam paging beyond 16 angles, view options, Edit Cameras thumbnails; subclip trim limits and Source monitor
Multi-Camera view: automatic / 2x2 / 3x3 / 4x4 grid layouts paged when the
angles don't fit (page arrows, keys 1-9 pick cameras on the shown page),
Multi-Camera Selection Top Down, Show Multi-Camera Preview Monitor (grid
fills the monitor when off), Auto-Adjust Multi-Camera Playback Quality
(lower cell decode scale while playing), Transmit Multi-Camera View (stored,
no device yet), Edit Cameras dialog with a thumbnail per angle. Only the
shown page's angles are decoded, at the cell scale. Engine commands
multicam.gridLayout/page/nextPage/prevPage/selectionTopDown/
showPreviewMonitor/autoAdjustQuality/transmitView/grid.

Subclips: restricted subclips limit trims, rolls, slides and slips on both
sides (EditCtx::media_start); the Source monitor shows a subclip's range
(or the whole master with the range as In/Out when unrestricted), opens at
its In and shows the master clip's markers inside it (source.inspect);
Convert to Master Clip keeps the range as In/Out and the inherited markers;
Make / Edit Subclip and Convert to Master Clip in the Project panel menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:42:21 -04:00
Brandon ThomasandClaude Opus 5.5 cb5b013812 M4.9: merge main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 02:14:33 -04:00
Brandon Thomas ac4d458712 Merge main into M10.7 2026-10-03 01:02:27 -04:00
Brandon ThomasandClaude Opus 5.5 0bc1d4e8b8 M4.9: merge main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 00:04:16 -04:00
Brandon ThomasandClaude Opus 5.5 1f4546d549 M4.9: draft decoding for reduced-resolution playback (opt-in preference)
Settings > Playback > Draft decoding (off by default): the Program monitor
asks for draft frames only while playing at 1/2 resolution or lower
(FrameKey::draft, filmcraft_media::cancel::with_draft). The GOP cache runs
the decoder in draft mode for those requests and serves draft frames to draft
requests only, so paused frames, renders and exports always get exact
pictures (codecs test: exports after draft playback match ffmpeg). Draft
plans hand the GPU box-decimated YUV planes at the drawn size. perf.stats
reports draft frames, H.264 threads and the preference; the bench gains
2160p quarter and draft playback cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:33:03 -04:00
Brandon ThomasandClaude Opus 5.5 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 ThomasandClaude Opus 5.5 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 ThomasandClaude Opus 5.5 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
Brandon ThomasandClaude Opus 5.5 cd4b61b9ee Integration: Mosaic keeps the original tile boundaries (fixes mask_inverted golden)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:37:32 -04:00
Brandon ThomasandClaude Opus 5.5 d67f101064 Merge video transitions (M5.10) into integration; effects.list keeps folder (leaf) and adds path
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:17:22 -04:00
Brandon Thomas 4a8c3d8076 Merge branch 'worktree-agent-afeed4478aa7a6497' into integrate
# Conflicts:
#	crates/project/src/effect.rs
2026-10-02 11:15:38 -04:00
Brandon ThomasandClaude Opus 5.5 bff5b723ee M5.10: Premiere 26 video transitions (84 in 10 folders + 21 Legacy)
- project: vtransition.rs registry. Animation, Dissolve, Grunge & Distort,
  Immersive Video, Lights & Blurs, Slide, Smart Tools, Text, Transformers
  and Wipe folders, plus Legacy > Video Transitions. Old ids are kept:
  modern versions reuse push/slide/split/whip/clock_wipe/radial_wipe/
  cross_zoom/page_peel, the old behaviour lives under *_legacy ids, and
  removed transitions move to a hidden Obsolete folder. Parameters are
  not animatable; find_transition looks up by kind (Mosaic/Mirror/
  Directional Blur names are shared with video effects).
- render: transitions/ module with procedural CPU implementations, a
  field-wipe engine (feather, border width/colour, anti-aliasing, centre),
  a 3D card renderer (flips, cube, folds, spins), page curl, blur/light/
  grunge families and flat VR approximations. Endpoints are exact,
  pixel params scale with the render resolution, and Reverse now plays
  B->A backwards. There is no GPU transition path, so the plan keeps
  only the symmetric dissolves on the GPU.
- engine: applyVideoTransition takes params/reverse; new
  sequence.setTransition; effects.list adds folder/kind filters,
  badges and detail paramInfo; sequence.inspect shows transition params.
- ui: the Effects panel shows the Legacy top folder and all badges;
  ui.set effectsSearch.
- tests: per-transition endpoint, continuity, midpoint, determinism,
  NaN, parameter-wiring and direction properties; golden fingerprints;
  tree counts; apply-every-transition via command; UI panel test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:14:59 -04:00
Brandon ThomasandClaude Opus 5.5 a34bc7f038 M3.11: remaining Premiere menu items — Scene Edit Detection, Normalize Mix Track, Simplify Sequence, Find / search bins, Automate to Sequence, Project Settings, ALE export…
- Scene Edit Detection: pure-Rust cut detection (render::scene: luma grid + RGB
  histogram difference, adaptive threshold with sensitivity, min shot length),
  background job with progress/cancel; applies cuts (linked audio too), clip
  markers and subclips in one undo step.
- Sequence: Normalize Mix Track…, Simplify Sequence…, Transcribe Sequence…,
  Captions ▸ Show Active Caption Tracks Only, Apply Default Transitions to
  Selection works on selected clips; Sequence menu in Premiere's order.
- Markers: Add Flash Cue Marker… (MarkerKind::FlashCue).
- File: New ▸ Search Bin (live saved queries), Close, Close All (Other)
  Projects, Save as Template… (+ new project from template), Import from Media
  Browser, Get Media File Properties for ▸ File…/Selection…, Project Settings ▸
  General…/Scratch Disks… (scratch disks drive previews, proxies, auto-save),
  Export ▸ Selection as FilmCraft Project…, Export ▸ Avid Log Exchange…
- Edit: Find… / Find Next (project columns/operators/all-any, timeline clips
  and markers), Edit Original (OS opener host hook).
- Clip: Edit Offline…, Source Settings…, Update Metadata… (XMP sidecars),
  Generate Audio Waveform, Restore Captions from Source Clip (disabled: no
  embedded caption reader), Automate to Sequence…
- View ▸ Dynamic Audio Waveforms; Help ▸ Reveal Log Files…, System
  Compatibility Report… (with the wgpu adapter).
- Dialogs for all of the above (panels::menu_dialogs, ui.set menuDialog).
- Project schema v11 (no-op v10_to_v11, fixture, docs row).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:08:58 -04:00
Brandon ThomasandClaude Opus 5.5 4c0f01166f M7.7: project/render/engine: 53 Premiere audio effects in folders, DSP mappings, clip and track application
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:04:47 -04:00
Brandon ThomasandClaude Opus 5.5 2e278c8ff0 M5.11: Premiere 26 video effects catalogue (93 in 16 folders, Legacy, obsolete) with CPU implementations and FxEnv for temporal/track effects
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:04:45 -04:00
Brandon ThomasandClaude Opus 5.5 e222d025cf Merge transcripts (M10.5) and clip/project commands (M3.10); clip schema renumbered to v10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:34:12 -04:00
Brandon ThomasandClaude Opus 5.5 c3d2f740d0 M3.10: Edit/Clip/File menu commands — labels, Paste/Remove Attributes, subclips, frame holds, time interpolation, audio options, Replace With Clip
Edit: Label ▸ 16 colours + Select Label Group, Paste Attributes… (⌥⌘V, with
Scale Attribute Times), Remove Attributes…, Select All Matching, Remove
Unused, Consolidate Duplicates. File: New ▸ Sequence From Clip, Bin From
Selection (⇧B), Offline File…; Close Project (⇧⌘W, asks to save), Save All.
Clip: Make Subclip… (⌘U, Restrict Trims), Edit Subclip…, Modify ▸ Audio
Channels… (⇧G) / Timecode…, Video Options ▸ Frame Hold Options…, Add Frame
Hold (now splits at the playhead), Insert Frame Hold Segment, Field
Options…, Time Interpolation (Frame Sampling / Frame Blending / Optical
Flow, blending rendered on CPU; optical flow falls back to blending),
Fit to frame / Fill frame; Audio Options ▸ Breakout to Mono, Extract Audio
(WAV next to the project, imported); Replace With Clip ▸ From Source
Monitor / Match Frame / From Bin. Hold Filters: effects animate through a
hold unless set. Menus support nested submenus (egui and native) and follow
Premiere's order. Dialogs in panels::clip_dialogs with automation ids and
ui.set {clipDialog}.

Project schema v9 (no-op migration v8→v9, v8-subclip fixture): clip
time_interpolation / hold_filters / field_options / source_channels,
Interpretation.audio_channels, Subclip.restrict_trims.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:28:37 -04:00
Brandon ThomasandClaude Opus 5.5 012bcf7aa5 M10.6: Graphics and Titles menu: New Layer (Vertical Text, Rectangle, Ellipse, Polygon, From file), Align to Video Frame / as Group / to Selection, Distribute (centres and gaps), Arrange, Select Next/Previous Graphic and Layer, Reset All Parameters, Reset Duration
Vertical text layout in the text engine (one line per character, columns right to left).
All edits are undoable engine commands; engine tests cover each menu group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 23:23:27 -04:00
Brandon ThomasandClaude Opus 5.5 a9278c81cc Merge M12 (multicam) into M5.5–7 (masks/presets): schema v7 = masks, v8 = multicam (WIP: CI not yet run)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:55:13 -04:00
Brandon ThomasandClaude Opus 5.5 68927178e2 M5.6: adjustment layers — Motion restricts the adjusted region; tests for below-only application, opacity/blend mix, effect and opacity masks (sequence pixels, any output scale) and nesting scope
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:21:02 -04:00
Brandon ThomasandClaude Opus 5.5 b351559ffe M5.5: mask tracking (Shi-Tomasi features, pyramidal Lucas-Kanade with forward-backward check, RANSAC similarity/rigid/translation fit) as a background job writing Mask Path keyframes; track buttons + method menu in Effect Controls
masks.track {direction, frames?, method?, wait?}: progress + cancel via jobs.*, keyframes written live as one undo step per run. Synthetic known-motion tests: <0.75 px drift over 10 frames (render), 0.29 px max vertex error over -8..+12 frames end to end (engine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:14:37 -04:00
Brandon ThomasandClaude Opus 5.5 650a266352 M12.2: multicam and sync — Synchronize (In/Out/timecode ± hours/clip marker/audio), Merge Clips, Create Multi-Camera Source Sequence (camera names, audio camera 1/all/switch, Processed Clips bin), multi-camera clips (angle render, Switch Audio mix, GPU plan passthrough), switch angle / Ctrl+N cut / keys 1–9, live recording as one undo step, Enable/Flatten/Edit Cameras, audio follows video; nested sequence audio now plays; project schema v7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:08:45 -04:00
Brandon ThomasandClaude Opus 5.5 fbaf487237 M5.5: effect masks (ellipse / 4-point polygon / pen Bezier) with keyframable path, feather, opacity, expansion, inverted, modes; opacity masks; WGSL coverage parity; on-monitor editing
- project:😷 MaskPath (cubic Bezier, clip pixels) as ParamValue::Path (vertex-wise keyframe interpolation), Mask, MaskMode, TrackMethod; schema v7 (no-op migration)
- render:😷 analytic antialiased coverage via signed distance + feather falloff in linear light; masked effect = lerp(original, effected, coverage); opacity masks before Motion; adjustment layers
- gpu::GpuMask: same coverage + mix in WGSL compute (max |cpu-gpu| ~3e-6)
- engine masks.* commands (add/remove/set/moveVertex/translate/addVertex/removeVertex/toggleVertexSmooth/select/list); keyframe commands take "mask"
- UI: Effect Controls mask rows; Program monitor overlay (vertices, handles, feather/expansion handles, body drag, pen tool) with automation ids
- goldens: mask_blur, mask_color, mask_inverted, mask_opacity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:48:47 -04:00
Brandon ThomasandClaude Opus 5.5 9f4208bb08 Merge main into M11.7–9 (relink, proxies, project manager): GopCache keeps main's continuation algorithm with a same-thread reentrancy guard (nested rayon requests decode privately instead of deadlocking); ProRes export combines profile choice with HDR signalling; project schema v6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:19:19 -04:00
Brandon ThomasandClaude Opus 5.5 a9af6fac71 Merge M8 (LUTs, colour management, Colour Match) into M4.6 playback; project schema v5 for colour management; architecture §5.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:05:55 -04:00
Brandon ThomasandClaude Opus 5.5 2ad8513898 Merge main into M4.6; mixer perf test measures thread CPU time (wall clock failed under machine load)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:04:19 -04:00
Brandon Thomas c47c90f070 Merge branch 'main' into worktree-agent-a0342df5d4a386968 2026-10-01 09:03:47 -04:00
Brandon ThomasandClaude Opus 5.5 3dab45f45a M8.5: Colour Match (Lumetri ▸ Color Wheels & Match ▸ Apply Match) with skin-tone protection
- render::color_match: non-ML statistics matching — Oklab means per tonal range (smoothstep
  shadow/midtone/highlight weights) plus mean chroma; "Face Detection" down-weights skin-tone
  pixels (Oklab hue 20–75°) in the colour statistics and then limits the skin hue shift to 8°;
  the grade is solved in Lumetri's own parameters (3 wheels, their lightness, saturation) by
  damped Gauss–Newton through the real Lumetri implementation, so the result stays editable
- render::render_clip: one clip's layer at a time (the shot as Lumetri sees it)
- lumetri.applyMatch {clip?, referenceTime|Frame|Timecode, faceDetection} sets the grade in one
  undo step and reports the statistics distance before/after; refuses a reference inside the
  clip itself
- Lumetri panel: Color Match row (reference timecode, Face Detection, Apply Match; automation
  ids lumetri.match.*)
- tests: Oklab reference values, self-match is a no-op, a known grade is recovered (residual
  −86 %), skin protection holds the skin hue shift to ≤ 8° (51° without), engine and headless
  UI tests
- docs: control protocol (colour commands and ids), testing; ROADMAP M8 row and log

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:02:54 -04:00
Brandon ThomasandClaude Opus 5.5 b98c886133 M11.7: offline media and relinking
- Missing media no longer fails: opening a project scans for missing files
  (`media.findMissing`, `file.open` → missingMedia). Missing clips render our own
  offline slate (render::offline: red striped field, warning triangle, "MEDIA NOT
  FOUND", file name, hint) with silent audio, and the Link Media dialog opens.
- Media identity: imports record the size and a fast content fingerprint (hash of
  size + first/last MiB) in MediaClip::identity (optional field, older builds
  ignore it, no schema bump).
- media.relink checks file name / extension / fingerprint ("Clip ID") / duration /
  media start / frame size+rate+channels, refuses a mismatched fingerprint unless
  forced, supports Align Timecode (Project::shift_media_time), and relinks the
  others automatically by the derived folder remap (Windows/macOS separators).
- media.autoRelink {from,to} | {folder}, media.search, media.replaceFootage,
  media.makeOffline {deleteFiles?}, media.offlineAll, media.status, media.linkMedia.
- Media pool caches sources per reference (path, offline flag), so relink, Make
  Offline and undo take effect at once; proxy-aware resolution and a full-res
  provider that export uses.
- codecs::gop no longer holds its lock while decoding: rayon decoders inside a
  parallel export could steal another frame of the same source and deadlock.
- UI: Link Media dialog (rows, match options, folder search with preview, Locate…,
  Link, Offline, Offline All, Cancel), Make Offline dialog, Project panel offline
  markers and context menu, media thumbnails keyed by media reference.
- Tests: moved media opens offline (slate rendered) and relinks others by folder,
  prefix remap and folder search, fingerprint mismatch refused, Make Offline,
  headless UI test of the dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:00:35 -04:00
Brandon ThomasandClaude Opus 5.5 1653430c42 M8.8: colour management — sequence working space (Rec. 709 / Rec. 2100 PQ / HLG, wide gamut), Interpret Footage colour space, log/HDR decode with BT.2390 tone mapping, HDR export signalling, HDR scopes
- model: SequenceSettings::color (ColorPipeline: working space, wide gamut, Auto Tone Map Media;
  serde default Rec. 709) and Interpretation::color_space (override; None = metadata)
- render::colorman: source colour space from the override or VUI/colr/MKV metadata, decode tables
  (camera log with super-whites, PQ, HLG + OOTF), gamut matrix, tone mapping into SDR working
  spaces, gamut mapping; working → SDR monitor transform at the top level
  (RenderOptions::working_output keeps working values for HDR export and scopes). Plain Rec. 709
  media in a Rec. 709 sequence keeps the old fast path and stays on the GPU; managed layers and
  HDR/wide-gamut sequences composite on the CPU
- export: H.264 and ProRes of a PQ/HLG sequence encode BT.2020 PQ/HLG and signal it: VUI / ProRes
  header, colr (nclx/nclc), and for PQ mdcv + clli boxes and H.264 SEI (payloadTypes 137/144);
  `sdr: true` (and render previews) export tone-mapped SDR. isobmff reads/writes mdcv and clli
- commands: sequence.colorSettings (Sequence ▸ Color Management…), clip.interpretFootage
  (Clip ▸ Modify ▸ Interpret Footage…, Project panel context menu), color.spaces,
  media.colorInfo; dialogs with automation ids colorDialog.*
- scopes: HDR sequences show a cd/m² waveform on a PQ scale (203 marked) and a BT.2020
  vectorscope from the working-space render (scopes.hdrWaveform)
- display-referred wide-gamut input is desaturated into gamut keeping in-gamut colours exactly;
  the soft compression is reserved for camera gamuts
- tests: colorman unit tests, engine (interpret/undo, PQ working space values and display),
  goldens log_to_rec709 and hdr_tone_map (own renders + sidecars), export PQ/HLG × ProRes/H.264
  read back by our demuxer and ffprobe (color_transfer/primaries/space, mastering display and
  content light side data), headless UI (dialogs, HDR scopes)
- docs: architecture §5.2 (pipeline, HDR export, display colour management: no macOS EDR yet)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:56:47 -04:00
Brandon ThomasandClaude Opus 5.5 a7c1cfd956 M8.2/M8.3: Lumetri Input LUT and Creative Look LUTs, project LUT library, GPU tetrahedral LUT, section bypass switches
- Lumetri gains input_lut (Basic Correction) and look_lut (Creative) references: lib:<id> for
  LUTs imported into the project's library (embedded .cube/.3dl text, parsed once and cached by
  content), builtin:<id> for LUTs generated from code: camera log → Rec. 709 conversions from the
  published curves, and the procedural Creative looks (no third-party LUTs)
- section switches basic_on/creative_on/curves_on/wheels_on/vignette_on (+ hsl_on) bypass their
  sections; older projects without them render as before
- commands lut.import, lut.list, lut.remove, lut.export (cube/3dl), lumetri.setInputLut,
  lumetri.setLook (lut ref or path; imports on the fly), lumetri.setSection; effects.setParam
  fills in parameters missing from instances saved by older builds
- Lumetri panel: Input LUT and Look LUT menus (None, built-ins, library, Browse… file dialog),
  working section switches (automation ids lumetri.input_lut, lumetri.look_lut,
  lumetri.switch.<section>); combo boxes no longer widen the panel
- filmcraft-gpu: GpuLut WGSL compute pass with the same tetrahedral interpolation; parity test
  vs Lut3d::apply for 2³–65³ and a non-unit domain (max |Δ| 1.8e-7)
- tests: engine (import/apply/bypass/undo/save, export→import round trips for both formats),
  render (built-ins reproduce the looks, library resolution), headless UI (color_ui.rs)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:33:26 -04:00
Brandon ThomasandClaude Opus 5.5 4b83e088eb Merge main into the playback branch (mixer recordStart moves to the end of preroll; GopCache keeps main's random-access key walk-back)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:30:04 -04:00
Brandon ThomasandClaude Opus 5.5 6a0a3add7f M7.4: Essential Sound model and commands — audio types, per-type settings as clip effects, presets, loudness auto-match, ducking keyframes
- project::essential: Dialogue/Music/SFX/Ambience settings (Loudness, Repair, Clarity, Creative,
  Ducking, Pan, Clip Volume, Mute), our own EQ/reverb/type presets, and apply(): settings become
  ordinary clip effects (EffectInstance::essential) in canonical order; only changed parameters
  are written, so keyframes survive; auto-match gain → clip gain, Clip Volume → Volume offset,
  Pan → Panner
- effect defs: DeEsser, DeReverb (params), Enhance Speech, Stereo Width; render mappings for them,
  and DeNoise amount / DeHummer gain now reach the DSP; Mute; render::audio::clip_signal
- engine essentialSound.* (inspect, setType, clearType, set, applyPreset, savePreset,
  deletePreset, autoMatch, generateDucking), undoable; per-type loudness targets and user presets
  in preferences
- tests: auto-match within 0.001 LU of target (−23 / −16, with and without a chain), ducking
  keyframes within 20 ms on a synthetic dialogue+music project and −15.00 dB in the mix, repair
  metrics (hum −39 dB, rumble −22 dB, noise floor −13 dB, sibilance −19 dB, reverb tail −10 dB),
  render chunking bit-identical and WAV export = render; full Dialogue chain 22× realtime (release)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:28:38 -04:00
Brandon ThomasandClaude Opus 5.5 9baa34fa45 Merge M10.1–2 + M3.2/M3.11 into M7.2 (audio mixer): preferences gain audio + playback + trim groups; ROADMAP refreshed
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:17:12 -04:00
Brandon ThomasandClaude Opus 5.5 c42fa205cd M7.5: Audio Gain dialog (set / adjust / normalize max peak / normalize all peaks) and default audio transitions
- clip.audioGain takes mode set|adjust|normalizeMax|normalizeAll (the
  old {db, relative} form still works); peaks are measured on the
  clips' source ranges. Normalize Max Peak applies one adjustment so the
  loudest clip lands on the target; Normalize All Peaks sets each clip.
- clip.audioPeak reports the selection's peak amplitude (with gain).
- Clip > Audio Gain... (G) opens the dialog with the four options, dB
  fields and the peak amplitude; automation ids audioGain.*.
- effects.setDefaultTransition sets the default video or audio
  transition (Constant Power / Constant Gain / Exponential Fade) that
  sequence.applyAudioTransition (Cmd+Shift+D) uses.
- tests: gain modes and peaks, dialog by automation id, transition
  curves (power / amplitude preservation, end points).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:11:57 -04:00
Brandon ThomasandClaude Opus 5.5 14ae01885f M7.2: Audio Track Mixer and Audio Clip Mixer panels, timeline track keyframes, live recording
- Audio Track Mixer: strips for audio tracks, submixes and the Mix with
  effect/send slots (disclosure), input mapping, output assignment, pan
  knob, automation mode, M/S/R (solo safe on submixes), fader with dB
  scale and our own taper, stereo meters fed by the playing mix, value
  hot text, footer transport; horizontal/vertical scrolling. Fader and
  knob drags are mixer.touch while held and mixer.release on let go, so
  they are heard live and recorded in Latch/Touch/Write.
- Audio Clip Mixer: per-track strips for the clip under the playhead
  (clip Volume / Panner, write-keyframes toggle), clipMixer.set with one
  undo step per drag (Session::edit_merged).
- Timeline: per-track "Show Keyframes" button picks clip keyframes or a
  track lane (volume, panner, mute, sends, insert params); the lane is
  drawn across the track, Pen / Cmd-click adds, diamonds drag in time and
  value, right-click deletes.
- Playback starts an automation pass and stopping writes it; the audio
  callback reads the newest project snapshot (LiveMix), so mixer moves
  are heard while playing. Audio Meters read the graph's live meters
  instead of re-mixing at the playhead.
- Automation ids on every control; headless kittest tests drive the
  panels by id and by multi-frame drags (FILMCRAFT_UI_SNAPSHOT_DIR renders
  offscreen PNGs through wgpu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:10:29 -04:00
Brandon ThomasandClaude Opus 5.5 5823e22e89 M4.6: cancel frame jobs playback no longer needs (stale in-flight jobs, prefetch after Stop)
- filmcraft_media::cancel: a per-job cancellation flag passed through a thread-local, so the
  MediaSource API is unchanged. Frame workers run each job inside it.
- FrameServer::retain_queue (called by schedule_playback every refresh) now also cancels running
  jobs for frames the playhead has passed. Before, a worker blocked on a source's decoder lock
  went on to decode its frame after playback had moved 50–100 frames on, found it evicted and
  restarted the decoder at the keyframe (decoder trace: "seek i=32 key=0 next=142").
- GopCache gives up (CodecError/MediaError::Cancelled) before seeking or decoding for a cancelled
  job and between samples; decoder state stays consistent, so the next request carries on.
- The CPU compositor checks the flag between tracks and effects; cancelled results are never
  cached.
- Stop drops queued prefetch jobs and cancels running ones (FrameServer::stop_prefetch). They
  used to run to completion after Stop, e.g. 14 frames of live effects rendering competing with
  a Render In to Out started right after.

bench, same-session A/B (GPU path, 8 s, load avg 35–41):
  after-preview Full live play (1080p + Lumetri/Sharpen/Levels/Tint, CPU-bound):
    38/154 → 98/94 shown/dropped, 297 → 267 ms CPU/frame, 84 jobs cancelled
  after-preview plays from previews, h264-1080, stack3, h264-2160: unchanged within noise

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:54:44 -04:00
Brandon ThomasandClaude Opus 5.5 4918555906 M10.2: graphic clips — text and shape layers in the project model, vector renderer (CPU + GPU plan), graphics.* commands
- project: ItemKind::Graphic (transparent canvas source, hidden from the Project panel,
  unlimited duration); graphic layers are hidden effect instances graphic_text /
  graphic_shape on the clip (serde for free, keyframes on every animatable property,
  Source Text holds), with appearance (fill, two strokes outer/centre/inner, background
  box with radius, drop shadow) and transform (position, anchor, scale, rotation,
  opacity); eval_layer gives a plain LayerSpec. No schema migration: all additive.
- render: graphic_clip rasterises each layer straight to the output transform (crisp at
  any Motion scale/rotation), caches layer rasters, composes a tight image for the GPU
  plan; standard effects on a graphic run at source resolution first.
- engine: graphics.newText (Cmd+T) / newShape (new clip above the footage at the playhead
  or a layer in an existing clip), setText (typing coalesces into one undo step), set,
  selectLayer, deleteLayer, arrangeLayer, align (frame / selection), distribute,
  graphics.list, fonts.list. EditorState.graphic_layers holds the layer selection.
- goldens: graphic_title, graphic_shapes (CPU and GPU parity).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:38:04 -04:00
Brandon ThomasandClaude Opus 5.5 6b00314e96 M7.2: audio mixer graph, track automation and mixer commands
- project: MixerStrip on every audio track, submix tracks and the Mix
  (automation mode Off/Read/Latch/Touch/Write, lanes for volume/pan/mute/
  send levels, effect-parameter automation in the insert's own keyframes,
  sends pre/post fader, output routing, record arm, solo safe, input map);
  pre/post-fader flag on inserts; serde defaults (no schema migration).
- render: mixer graph (tracks -> submixes -> Mix) in Premiere's order:
  pre-fader inserts, pre-fader sends, mute, fader, meter, post-fader
  inserts, post-fader sends, pan/balance. Per-sample automation, effect
  params on an absolute 64-sample grid (output independent of request
  cutting), route-level latency compensation, cached continuation state
  with pre-roll, solo/solo-safe logic, -3 dB constant-power pan law for
  mono tracks, balance for stereo. LiveMix carries held controls (Touch
  ramps back over the automatch time), meters and the newest project.
- engine: mixer.* commands (setStrip, setValue, touch/release,
  recordStart/recordStop, submixes, inserts, sends, track keyframes,
  writeAutomation, inspect) and the automation recorder (gesture streams
  thinned into keyframes, one undo step per pass, Switch to Touch after
  Write); Preferences > Audio (automatch time, keyframe thinning).
- tests: gain/pan law, block-boundary automation, solo/mute/solo-safe,
  sends, latency alignment, render-vs-playback identity, recorder modes,
  perf (24 tracks x EQ+Dynamics+Reverb + submix: 6.3x realtime, one core).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:29:49 -04:00
Brandon ThomasandClaude Opus 5.5 b141c406e9 M10.1: text engine (crates/text): font database with bundled OFL fonts and system-font discovery, harfrust shaping, bidi, line breaking, paragraph layout, glyph cache and rasteriser; captions and Timecode/Clip Name burn-ins use it
- New L1 crate filmcraft-text: fonts (bundled Inter Regular/Medium/SemiBold/Bold/Italic,
  JetBrains Mono, Noto Serif; DirectorySource scans system font folders reading only name/OS2
  tables; family/style resolution with synthetic bold/italic; per-char fallback), layout
  (harfrust kerning/ligatures, unicode-bidi, unicode-linebreak, alignment incl. justify,
  leading, tracking, baseline shift, all/small caps, underline, carets/hit-test/selection,
  cached), raster (moved from captions; active-edge list, shapes, emboldening), render
  (outline + sub-pixel glyph-mask caches; transformed-outline path for rotation/skew),
  mask (distance-transform strokes, blur).
- Captions burn-in now shapes and kerns via the engine (font.rs/raster.rs removed).
- Timecode / Clip Name effects draw real text (full clip names, not seven-segment digits);
  text_burnin golden re-blessed.
- UI takes its font bytes from the text crate (no duplicate copies in the binary).
- New fonts: Inter Bold, Inter Italic (OFL-1.1, rsms/inter v4.1), Noto Serif Regular
  (OFL-1.1, notofonts) with sidecars, ATTRIBUTION.md rows and licence text.
- Perf: 3-line 96 px title at 1080p ~1.6 ms cold / 0.15 ms warm (release).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:22:30 -04:00
Brandon Thomas 9e54812b2a Merge branch 'worktree-agent-ae361fad8d71a0eef'
# Conflicts:
#	crates/engine/src/commands.rs
#	crates/export/src/lib.rs
#	crates/ui-egui/src/frames.rs
#	crates/ui-egui/src/state.rs
2026-10-01 00:08:58 -04:00
Brandon ThomasandClaude Opus 5.5 5c5a10e1b9 M4.5: render bar painted from preview segments (green/yellow/red, merged spans, hover text, automation ids timeline.renderBar.N); playback, Program monitor (CPU and GPU plan paths), audio output and meters read render previews where valid; status-bar job progress with cancel (status.job.cancel); play after rendering previews; previews pre-opened when rendered. Measured on 1080p H.264 + Lumetri/Sharpen/Levels/Tint, 8 s at Full res: 189–191 of ~192 frames dropped before render, 0–1 dropped after (25–46 on the first play right after the render)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:06:38 -04:00
Brandon ThomasandClaude Opus 5.5 9eb12c0043 M10.4: caption commands in the engine (captions.* — tracks, add at playhead, split/merge/trim/move/delete, text/speaker/timing, style, next/previous, show/hide, list), file.import detects .srt/.vtt/.scc into a new caption track, File ▸ Export ▸ Captions… (SRT/VTT/SCC), Clear/Ripple Delete on selected captions; caption overlays in render + GPU frame plans; burn-in on export (ffmpeg-oracle test)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:06:29 -04:00
Brandon ThomasandClaude Opus 5.5 740fcd95cb M4.5: render-bar segments + render previews in the engine — segments cut at every video edit point, 128-bit content hash relative to the segment start (clips, sources, effects + keyframes, transitions, sequence settings; stable across save/load and ripple moves), measured yellow/red cost estimate; content-addressed ProRes preview cache per project (temp folder until first save), Render Effects In to Out (Enter), Render In to Out, Render Selection, Render Audio (float WAV previews mixed into playback), Delete Render Files (In to Out), sequence.renderBar query; renders run as cancellable jobs on the export pipeline
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:43:26 -04:00