- HEVC inverse transform: sum basis rows of non-zero coefficients with a
const-generic size (bit-exact, tested against the direct product);
SAO edge offset gets a check-free interior loop. HEVC CPU/frame -60%.
- H.264/HEVC/VP9 planes are moved into VideoFrames, not copied.
- Catch-up decoding: frame jobs tell sources which frames are late
(filmcraft_media::cancel::with_catch_up); the GOP cache skips
non-reference samples of late frames (H.264 nal_ref_idc 0, HEVC
sub-layer non-reference pictures). Scrubs keep the last 2 s decoded.
- perf.stats: engine query + UI/control method (decode counters,
playback shown/dropped, frame-worker decode/render ms, cache use).
- Timeline waveform source peak cached; bench seek section and --only.
- docs/performance.md with before/after results.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
One Preferences window (app menu ▸ Settings ▸ <category>, Edit ▸ Preferences in-window, General =
Cmd+,): category list, schema-driven pages, Help / Reset… / Cancel / OK. Settings live in the
per-user preferences.json (version 2, v1 migrated, bad values repaired), reachable via
prefs.get/set/reset{category} and the new prefs.schema; app.settings.<category> opens a page;
every control has a settings.<key> automation id.
Wired: still/transition default durations, auto-scroll, snap playhead, return to beginning, play
after rendering, step many, selection-tool roll/ripple, theme/highlight/contrast, tooltips, at
startup + recent projects, label names/colours/defaults, indeterminate timebase, default media
scaling, media cache location/policy/clean, frame cache budget, cpal device/buffer/rate/output
mapping, transcription defaults + auto-transcribe on import, text smart quotes/ligatures/font.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MonitorView state is serde and settable with ui.set {program|source}; view.* UI commands with
menu checkmarks; wrench and zoom menus with automation ids; headless kittest tests (view_ui.rs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New L0 crate filmcraft-dnx, written from SMPTE ST 2019-1:2016 + Amd 1:2023
(tables extracted from the standard's text by tools/extract_tables.py).
Decoder: every compression ID (DNxHD 1235-1260 incl. field-coded and
macroblock-adaptive interlace, thin rasters, RGB 4:4:4; DNxHR 1270-1274
LB/SQ/HQ/HQX/444), 8/10/12-bit, 4:2:0/4:2:2/4:4:4, DCT and lossless alpha,
macroblock rows in parallel. Matches ffmpeg within IDCT precision (max 2 LSB,
<=50 per million beyond 1, 87-99% identical) on 22 ffmpeg fixtures; follows
ffmpeg where it departs from the standard's text (reconstruction offset,
CID 1271 weights, 4:4:4 signalling) - documented in the README.
Encoder: DNxHR LB/SQ/HQ/HQX(10/12-bit)/444, CBR at the eq. 7.1 frame sizes,
uniform-qsf binary search + per-macroblock refinement; ffmpeg decodes every
profile with -xerror (HQ luma PSNR 58.8-66.3 dB).
Wiring: MOV AVdn/AVdh import (codec label from the CID), export
Format::DnxHr (MOV AVdh, dnxProfile lb|sq|hq|hqx) in the export dialog and
file.exportMedia.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
- crates/ui-egui/src/panels/essential_sound.rs: per-type sections (Loudness with Auto-Match,
Repair, Clarity, Creative, Ducking with Generate Keyframes, Duration note, Pan), preset dropdown
with save/delete, code-drawn type glyphs, automation ids for every control; every control runs an
essentialSound.* command and a slider drag is one undo step
- headless kittest test (essential_sound_ui.rs) with optional wgpu snapshots
- docs: architecture §5.2, control-protocol ids, testing, ROADMAP
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- crates/ui-egui/examples/bench_playback.rs drives the Program monitor's own FrameServer,
prefetch (now FrameServer::schedule_playback, shared with the monitor) and PlaybackMeter at a
simulated 60 Hz display, optionally compositing on a headless wgpu device. Scenarios:
h264-1080, stack3 (3 × 1080p, scaled/rotated/opacity), h264-2160, demo, after-preview
(live play → Render Effects In to Out → 2 plays), seek-storm. Reports latency percentiles,
on-time ratio, worker/decode wall and thread-CPU time, UI-thread present time, process CPU per
frame, decoder seeks/samples (codecs::gop_stats) and wasted jobs; --json for machine output.
Fixtures are generated with ffmpeg into target/fixtures/playback.
- FrameServer records per-job timings (thread CPU via rustix clocks) while profiling is on.
- Dropped/shown were counted per UI refresh: "shown" counted every refresh that had the frame
(322 "shown" in an 8 s play of 192 frames) and "dropped" counted a frame that arrived a few ms
after its first refresh. PlaybackMeter counts timeline frames: shown when the exact picture was
on screen while due, dropped otherwise, including frames passed over without a refresh.
Baseline (GPU path, 8 s, load avg 104–168 on 14 cores; CPU columns are load-robust):
h264-1080 Full 191/1 shown/dropped, 46 ms CPU/frame; Half 188/4
stack3 Full 189/3, 102 ms CPU/frame; Half 157/35 (3 extra decoder seeks)
h264-2160 Full 8/184, 313 ms CPU/frame, 10 seeks; demo Full 114/78, Half 191/1
after-preview: live 0/192; 1st play after render 124/67, 2nd 119/73 (UI-thread present
p50 21 ms / p95 45 ms); seek-storm: 0 of 40 seeks shown within 150 ms
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crates/ui-egui/tests/scripted.rs runs the real FilmcraftApp under
egui_kittest's eframe harness (no window/GPU) and drives it through
ControlRequests, stepping frames until each reply arrives; synthetic
ui.click input is fed through the app's raw_input_hook. Tests: demo
project + widget registry, razor/undo/redo with timeline locate,
source insert, apply Gaussian Blur and see it in Effect Controls,
playback toggle/stop, click a tool button by automation id, error
replies. docs/testing.md documents coverage and limits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Seven generated signals (sine, pink noise at 48/96 kHz, speech-like at
48/44.1 kHz, gated stereo with silence and sub-gate passages, fs/4
inter-sample peak) written as float WAV and measured by LoudnessMeter
and ffmpeg ebur128 (metadata, 3 decimals per 100 ms). Tolerances: M/S
+-0.1 LU every 100 ms, I +-0.1 LU, LRA +-0.5 LU, true peak +-0.2 dB.
Measured: M/S <= 0.0005, I <= 0.007, LRA <= 0.04 LU, TP <= 0.045 dB.
On the fs/4 tone ffmpeg's true peak is +0.6 dB above the analytic
value, so that case is checked against the analytic peak (ours -0.053).
testkit gains a float WAV writer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
crates/golden (test-only, L3): nine procedural 320x180 scenes (transform
and opacity, blend modes, Gaussian Blur, Lumetri basic, Crop, Cross
Dissolve / Dip to Black / Wipe mid-transition, timecode/clip-name text)
rendered on the CPU and compared with committed goldens (PSNR >= 45 dB,
max <= 12, p99 <= 2); FILMCRAFT_BLESS=1 regenerates them with
attribution sidecars. testkit::golden adds PNG I/O, diff metrics and
failure images. The same scenes are composited on the GPU and compared
with the CPU.
GPU parity exposed a real bug: GpuCompositor cached uploads by
pixel-buffer address without owning the buffer, so a new frame
allocated at a freed frame's address was drawn with a stale texture
(whole wrong frames, PSNR ~12 dB). The cache now keeps the buffer
alive; regression test upload_cache_keeps_buffers_alive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runs each crate's ignored generate_fixtures test (h264, hevc, isobmff,
matroska, prores) - the same generators the oracle tests use - and
prints made/cached/skipped per fixture plus a summary. Optional crate
filter: cargo xtask fixtures hevc prores.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
New dev-dependency-only crate crates/testkit (L0, no FilmCraft deps):
FILMCRAFT_FFMPEG/FILMCRAFT_FFPROBE, then PATH (incl. .exe), then known
dirs; SKIPPED lines and FILMCRAFT_REQUIRE_ORACLES=1 to make skips fail;
require_ffmpeg!/require_ffprobe!/require_oracles!; fixtures_dir() under
<workspace>/target/fixtures with per-thread temp names and atomic
generation. Oracle helpers in aac, opus, h264, hevc, h264enc, isobmff,
matroska, prores and export migrated (codecs left on its local lookup).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Public equivalents of the maintainer-local plan/: docs/architecture.md (layers as enforced by
xtask, Tick time base, data model, command system and undo, render/playback/export pipeline,
automation surfaces), docs/contributing.md (+ root CONTRIBUTING.md), docs/testing.md and
docs/agents.md (MCP/control channel usage, agent work loop, decision rules, Definition of Done).
CLAUDE.md points at the public docs wherever it references plan/; AGENTS.md gains a See also list;
README gains a Documentation section.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>