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>
- crates/speech (L2): Transcriber trait, FixedTranscriber, Whisper model catalogue (pinned,
SHA-256 verified) with optional downloader (feature `download`: ureq + rustls + RustCrypto),
pure-Rust Whisper inference on candle with word timestamps (feature `whisper`), log-mel, VAD
word tightening, MFCC speaker clustering. Both features off by default and for wasm.
- project: per-media-item transcripts (Project::transcripts); schema v9 with a no-op v8->v9
migration and a v8 fixture.
- edit::transcript: sequence transcript derived from clip transcripts, search, paragraphs,
word ranges, pause/filler detection, ripple delete of many ranges, caption layout.
- engine: transcript.* commands (generate, set, delete, inspect, search, select, extract, lift,
renameSpeaker, removeFillers, removePauses, createCaptions, models, downloadModel);
Session::transcriber for hosts/tests; features `whisper` / `speech-download` (also on the
desktop and CLI apps). Without whisper, transcript.generate fails with a clear error.
- ui: Text panel Transcript tab (Transcribe button, speaker paragraphs of clickable words,
Shift+click selection -> In/Out, extract/lift/remove fillers/create captions, search) with
text.transcript.* automation ids; headless UI test.
- docs/transcripts.md, crates/speech/README.md, architecture and project-files updates.
whisper-tiny measures 12.2% WER over 797 words on local samples.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sequence: Reverse Match Frame (Shift+R), Go to Gap > Next/Previous in
Sequence (Shift+; / Cmd+Shift+;) and in Track, Selection Follows Playhead,
Show Through Edits (bow-tie marks on the timeline, wrench toggle), Join
Through Edits (clip context menu), Make Subsequence (Shift+U), Delete
Tracks... (dialog: all empty tracks or one track per kind).
Markers: Mark Split and Go to Split (Video/Audio In/Out) on the sequence
and the Source monitor; source split points make J/L-cuts on Insert and
Overwrite. Add Range Marker (Ctrl+Shift+M), Add Range Marker to In and Out
(Ctrl+M), Add Chapter Marker..., Clear Markers label, Show All Marker
Colors with a Markers panel colour filter, Ripple Sequence Markers
(insert, paste insert, extract, ripple delete, close gap, ripple trim) and
Copy Paste Includes Sequence Markers.
Edit crate: through-edit detection/join, track and sequence gaps, range
clipping. Shifted punctuation keys (Shift+; arrives as ':') now match
their bindings.
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>
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>
Tiles decode in parallel into private region buffers (Plane / MiInfo with a
frame-coordinate origin) that are merged afterwards; intra block copy frames
keep whole-frame buffers. Post-filter row bands run on a rayon pool (par.rs,
behind the `threads` feature, sequential on wasm32). Frames become jobs that
wait on the reference slots they read and run on frame worker threads, so
independent GOP layers decode concurrently; output stays in order and each
Picture carries the pts of its temporal unit (Decoder::decode_pts, flush,
flush_result). codecs::Av1Decoder passes pts through and drains on flush.
Oracle tests now compare 1 thread, many threads and libdav1d (bit-exact),
with new multi-tile fixtures and an ignored 1080p set. README records fps
before/after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- eframe web runner on WebGPU (WebGL2 fallback; GPU compositor on WebGPU, CPU on WebGL2).
- fs: virtual file table of File/Blob handles, 1 MiB chunked async range reads with an
LRU cache and read-ahead (pending + retry instead of blocking), downloads for written
files, /opfs/ paths written to OPFS.
- import: File System Access picker / <input type=file>, drag-and-drop anywhere,
container index prefetch (moov wherever it is), retried imports; project open/save.
- recovery: OPFS auto-save snapshot while dirty, media copies, reopened on next visit.
- audio: AudioWorklet output fed ahead from the UI thread; played-frame audio clock.
- webcodecs: H.264/HEVC/VP9/AV1 MP4/MOV decoded by the browser VideoDecoder via a
reader opener (fallback to our decoders on any error), probed with isConfigSupported.
- api: window.filmcraft (execute/request/commands/screenshot/importFiles/importUrl/
openProject/files/readFile/info).
- cargo xtask web [--dev] [--serve PORT] (wasm-bindgen CLI); xtask wasm also checks
filmcraft-ui-egui and filmcraft-web; layer L6 for the web app.
- Original web icon (favicon.svg) with attribution sidecar; docs/web.md; headless-Chrome
smoke test (tests/smoke.mjs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- filmcraft-media: ByteReader/ReaderOpener (open containers without reading the whole
file) and the pending flag for asynchronous (web Blob) reads.
- filmcraft-codecs: MP4/MOV and Matroska open from a ByteReader; registry of reader
openers (platform sources first).
- filmcraft-engine: Services::reader / export_in_memory; streamed imports; media pool
never caches a still-loading file as offline; Session::pump_jobs advances stepped
exports on hosts without threads.
- filmcraft-export: Exporter (one batch per step, re-renders on pending reads) used by
export(); OutputSink for in-memory output.
- filmcraft-ui-egui: FrameServer::pump (time-budgeted cooperative frame jobs on wasm,
retries jobs whose media is loading); screenshots as base64 on wasm; web-time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bit-exact restructuring of the per-sample spec processes (all oracle and conformance
vectors still match libdav1d):
- Inter prediction: unscaled references use one filter phase per block, so the 8-tap
filters run tap-outer over whole rows (no per-tap clamps inside the frame, identity
phases copy, zero-tap rows skipped); scratch buffers are reused, not allocated.
- CDEF: padded 12x12 block with availability; blocks whose taps are all available run
branch-free in i16 lanes; zero-strength blocks are skipped.
- Inverse transforms: generic over lanes, 4 or 8 rows/columns at a time in 32-bit
arithmetic (exact up to 10-bit per the spec's intermediate range); all-zero rows skipped;
only the used dequant area is cleared.
- Deblocking: early exits before strength lookup; per-length sample filters; the wide
filters as sliding-window sums.
- Symbol decoder: a 64-bit window of inverted bits instead of bit-at-a-time refills.
- Coefficient contexts from a zero-padded level buffer (no bounds checks); mode info
stored with row fills; reconstruction on row slices.
Single-threaded 1080p (thread CPU): GOP 20.7 -> ~52 fps, high-quality GOP 9.6 -> ~20 fps,
4x2-tile GOP 22 -> ~48 fps, 10-bit GOP 17 -> ~44 fps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decoder::stats() reports frames, tiles and the busy time of setup, tile decoding,
deblocking, CDEF, superres, loop restoration and output. The ignored perf test
measures single-threaded throughput with the thread CPU clock (robust to machine
load), prints ms/frame per stage, and the all-cores wall-clock rate next to the
load average. Adds high-quality and 4x2-tile 1080p fixtures. Decoder::with_threads
and flush() are added as the API the threaded decoder will use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agents' ui.* requests used ViewportCommand::Focus, so the user's typing in another app landed in FilmCraft as shortcuts (stray Extract, Mark In/Out, markers…). Verified live: the frontmost app stays frontmost through launch, ui.click, ui.key and ui.screenshot, and the requests still take effect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Film grain (7.18.3) on output pictures, bit-exact against libdav1d on the libaom
23-film_grain-50 vectors (8/10-bit) and an SVT film-grain stream; super-resolution now
oracle-tested (SVT superres denominator 12) and bit-exact.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Inter mode info syntax, spatial/temporal MV candidate stacks, motion field estimation and MV
storage, scaled references, sub-pixel filters, global/local warp, OBMC, wedge/diffwtd/inter-intra
masks, distance weights; intra block copy; one output frame per temporal unit with spatial
layers. Bit-exact against libdav1d on SVT-AV1 GOPs and the libaom size/quantizer/cdfupdate/mv/
mfmv/svc/monochrome/intrabc vectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Av1Decoder in filmcraft-codecs: feeds the av1C configuration OBUs before the
first sample (and after every reset), converts pictures to 8/16-bit planar
YUV with the stream's colour description, and reports random-access points
from the first frame header of each temporal unit (shown key frame) so GOP
seeking ignores bogus container sync flags. Matroska V_AV1 tracks map to an
av01 sample entry; MP4 labels show the AV1 profile.
All-intra 8-bit MP4 and 10-bit WebM files seek sample-exactly against
libdav1d. Inter frames still return "unsupported" until inter prediction
lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
captions.setStyle {margin: 60} (pixels, as one would guess) was silently clamped to 0.45 and
put subtitles mid-frame. The parameter doc and docs/captions.md now give the unit and range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the Night of the Living Dead trailer screenshot with a real job: a 3:10
documentary cut of NASA's Apollo 11 film (images.nasa.gov, US Government works) built
through the control channel — 49 picture cuts with dissolves, B-roll, lower-third titles
set by the text engine, subtitles from the NASA air-to-ground transcript, mission audio,
a CC0 Chopin bed ducked by Essential Sound, a Lumetri grade with keyframes, markers and a
rendered section. Sidecar lists every source and the reproduction steps; NASA does not
endorse this project.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loop filter (7.14: all filter sizes, deltas, segment/ref/mode adjustments),
CDEF (7.15), superres upscaling (7.16) and loop restoration (7.17: Wiener,
self-guided, switchable, stripe handling) in decode_frame_wrapup.
All-intra libsvtav1 fixtures with every in-loop filter (64/128 superblocks,
edge filter on/off, 8/10-bit) and libaom's av1-1-b8-02-allintra conformance
vector (39 frames, downloaded on first use) are bit-exact against libdav1d.
Frames coded slightly larger than the sequence maximum are accepted, like
other decoders do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Waveforms are normalised to the source's peak so quiet recordings stay readable, but the
boost was unbounded: an empty audio stream at -52 dBFS (silent archive film) was drawn as
loud as dialogue. The normalisation is now capped at +12 dB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Waveform peaks were cached by item id and never cleared, and item ids repeat across
projects: after opening a project the timeline drew the previous project's waveforms
(a 7-minute mission-audio clip showed as silent). The cache is now reset with the media
pool, and an item whose source is not available is no longer left pending forever.
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>
Lumetri's Input LUT and Look LUT rows printed the Rust debug value (Text("")), and
curve params would print Curve([[0.0, 0.0], ...]). They now read None / the LUT's name,
the text itself, and Default / Custom (n points).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After file.saveAs to 'Apollo 11 - Tranquility.fcproj' the title bar, Project panel and
recovery list still said 'Untitled'. Save As now renames the project (and its root bin)
to the file stem, without an undo step or dirtying the project; Save a Copy keeps the name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>