- New crates/platform (filmcraft-platform, L5): OS media FFI only. The one
crate allowed `unsafe` (own [lints] with unsafe_code = "deny",
#[allow(unsafe_code)] only on the videotoolbox module, SAFETY comment on
every block, safe Result-returning public API). AGENTS.md §0.3 names the
exception; docs/adr/0001-platform-ffi.md records why and the rules.
- VideoToolbox decoder for avcC / hvcC, 8/10-bit 4:2:0 and 4:2:2: session from
the sample entry's parameter sets with hardware required, asynchronous
decode (2 in flight), biplanar CVPixelBuffer copied to planar Yuv8/Yuv16,
reorder buffer of the stream's own depth, RASL skipped after a starting CRA.
Output callback under catch_unwind.
- HybridDecoder: transparent mid-stream fallback to the software decoder
(replays samples since the last restart point, drops already-returned
pictures). New codecs::software_video_decoder (built-in factories only) and
codecs::hw (switch, counters, NalStreamInfo shared with software decoders;
vui_color / sar_par helpers now used by both).
- Settings ▸ Playback ▸ Hardware decoding: Auto / Off (engine applies it to
the codecs switch); desktop app, CLI/MCP and bench call register().
- perf.stats decode.framesDecoded and decode.hardware counters.
- Tests: bit-exact parity with the software decoders (H.264 High, HEVC Main
open GOP, Main 10; resets, reseeks, flushes), forced-failure fallback,
mutation fuzz of samples and parameter sets, cross-platform fallback with a
stand-in decoder, setting Off, HEVC 4:2:2 vs ffmpeg.
- Bench --hw auto|off; results in docs/performance.md (4K CPU/frame
~86-119 ms -> ~4 ms).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The UI already turns a panic in the UI pass into an error window (ui-egui `crash` hook +
`catch_unwind` around the frame), and frame workers survive panics. Two gaps remained:
- commands run headless (CLI, MCP over stdio) or over the control channel had no guard: a panic
in one command ended the process and the unsaved session;
- background jobs (export, render previews/audio, proxies, Project Manager, mask tracking,
scene detection) ran their body unguarded: a panic killed the thread and left the job
"running" forever.
`Session::execute` now runs the command under `catch_unwind` and returns
`EngineError::Other("internal error in `<id>` …")`; every job body is wrapped by
`export_tools::guard_job`, which marks the job finished with an error. Regression test
`export_tools::guard_tests::panicking_job_is_reported_as_failed`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
timeline.slide (the Slide tool drag and the MCP/control-channel command)
only moved the clip it was given, so sliding a linked video clip left its
audio partner behind: the audio kept its old start and source In, a
visible A/V desync of the slide distance. Slip, Trim, Rate Stretch and the
keyboard slide (timeline.slideLeft/Right) already include linked partners.
Slide every linked partner (with_links) by a common delta, clamped across
the partners like timeline.trim does, so a shorter handle on one track
cannot slide the pair apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.exportMedia (and export.resolve / export.quick / export.queue.add)
document startSeconds/endSeconds next to range, but bare start/end times
without "range": "custom" were silently ignored and the whole sequence
(or In/Out) was exported. Start or end times now imply a custom range; a
start without an end is a clean error from the custom-range check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file.newSequence and sequence.settings accepted fps 0 (or a negative
rate). The next frame computation divided by zero; over MCP the panic
poisoned the session lock and every later call failed with "session lock
poisoned" until the server was restarted, losing unsaved work.
Both commands now reject rates whose frame duration is not positive.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Desktop/shared:
- Frame workers catch panics per job: a failing decode/render no longer kills the worker
or leaves its frame in flight forever (blank monitors). Counted as perf.stats failedJobs.
- UI pass catches panics and shows a "FilmCraft hit an error" window (Save Project /
Continue) instead of closing the app.
- Panic hook writes <data dir>/Logs/crash-<day>.log with thread, location and backtrace;
a window that can't start shows an error dialog instead of exiting silently.
- GPU compositor only on adapters that render/blend Rgba16Float with >= 4096 textures;
GPU validation errors and oversize frames switch to the CPU compositor instead of
wgpu's default panic.
- Dock layout and Media Browser splitter no longer panic on tiny panels/windows.
Web:
- Export mode / previews / voice-over / log reveal no longer call std::env::temp_dir
(panics on wasm32): new filmcraft_engine::temp_dir(); default export dir /exports.
- Fatal-error overlay with Reload; WebGPU device failure retries on WebGL2 (?webgl);
cache-busted wasm/glue URLs; webcodecs entries[0] guard.
Tests: robustness_ui (worker survives injected panics, UI panic recovery, tiny windows),
crash hook, dock split, GPU plan size, default export dir; web smoke test extended.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
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>
filmcraft-mxf gains a streaming writer (ST 377-1 partitions, primer, header
metadata, VBR index segments with temporal offsets, RIP; ST 378 OP1a
frame wrapping, ST 390 OP-Atom clip wrapping) for VC-3, ProRes, AVC byte
stream and PCM with start timecode and caller-supplied package UMIDs, plus
write_opatom_pcm. filmcraft-export adds mxf-op1a / mxf-opatom formats
(DNxHR, ProRes or H.264 + PCM; OP-Atom writes one mono file per channel),
presets, the engine mxfVideoCodec parameter and CLI .mxf inference.
Tests read our files back and check them against ffprobe / ffmpeg.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Premiere's AAF and OMF export options: embedded or separate WAV/AIFF audio,
sample rate and bit depth, trim with handles, rendered clip effects, breakout
to mono, AAF video mixdown and linked media; AAF/OMF import writes embedded
audio next to the document and links it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
List / Icon / Freeform views, Metadata Display, view presets, font size,
Preview Area, bin tabs and windows, hover-scrub In/Out; Media Browser tree,
Favorites, history, file types, thumbnails, Edit Columns, import and drag.
Engine: project_panel and media_browser modules, Services::list_entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Adds ~115 keyboard-only commands from Premiere's default keyboard (engine:
filmcraft_engine::keyboard, UI: panels::keyboard): edit-point navigation on
targeted/any track, go to selected clip start/end, reveal nested sequence,
select clip at playhead/next/previous, extend previous/next edit to playhead,
nudge/slip/slide clip selection, target and source-patch toggles, clip volume
keys, audio scrubbing toggle, text size/leading/alignment, object nudge,
Export Frame, poster frames, frame maximize/full screen/panel cycling, monitor
zoom, track heights, Project and Text panel keyboard navigation, play to Out.
Existing commands that lacked their Premiere key are added to the table;
panel-scoped keys live in PREMIERE_PANEL. docs/keyboard.md lists the 39
skipped shortcuts with reasons. Tests: table integrity (no dangling ids, no
duplicate keys per scope, no conflicts), engine behaviour with undo, headless
key presses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
- 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>