Commit Graph

189 Commits

Author SHA1 Message Date
Brandon Thomas 156535a6e4 Merge origin/main into i18n-spanish-panels
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	crates/codecs/tests/mpeg_oracle.rs
2026-10-09 09:59:28 -04:00
Brandon Thomas 1b4b6fbffa Merge remote-tracking branch 'origin/main' into fix-368
# Conflicts:
#	contributors/people.toml
2026-10-09 09:40:15 -04:00
Brandon Thomas c497b648f5 Merge origin/main into i18n-spanish-panels
Resolve conflicts: keep main's macOS/other hint text and mask vertex
tooltip variants (each wrapped in tl!), take main's shared keyframe_nav
helper and translate its tooltips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:34:01 -04:00
Brandon Thomas 45e951ff61 Merge remote-tracking branch 'origin/main' into fix-390
# Conflicts:
#	apps/filmcraft/src/main.rs
2026-10-09 09:33:32 -04:00
Brandon Thomas 96f76bac2a Merge remote-tracking branch 'origin/main' into fix-355
# Conflicts:
#	contributors/people.toml
2026-10-09 09:27:39 -04:00
Brandon Thomas 298391574e
Merge pull request #289 from midasdf/fix/vertical-grapheme-cells
M10: keep combining marks in one vertical text cell
2026-10-09 09:23:14 -04:00
Manuel Camacho 2b0f263418 GPU3: render exports on the GPU compositor (#30)
Add off-screen GPU export renderer (filmcraft-gpu::ExportRenderer) reading back
the linear float accumulator as premultiplied RGBA f32, plug it into the
export pipeline via FrameRenderer hook and GpuRendering setting, register
in desktop app and CLI, and record export.gpu_frames statistics.

🤖 Completed by agy (Antigravity Agent, Gemini 3.8 Flash) based on draft by Merlin
2026-10-09 06:18:17 -07:00
txiki739 43e3d26823 #218: System Language follows the operating system's language
Settings > General > Interface Language gains System Language
(`system`), now the default: a new installation shows the first of the
user's preferred languages that the interface has (es-419, pt_BR.UTF-8,
ja-JP...; any Portuguese gets pt-br), otherwise English. A language
already chosen, or saved in preferences.json, is kept.

The host supplies the preferred languages through
HostHooks::system_languages: sys-locale on the desktop (LANGUAGE,
LC_ALL, LC_MESSAGES, LANG on Linux; the OS lists on macOS/Windows) and
navigator.languages on the web. Without the hook (tests, headless),
System Language is English, so tests do not depend on the machine's
locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 11:06:43 +02:00
txiki739 5f3682fe7c #262: file dialogs list extensions in any capitalisation on Linux
rfd hands filter extensions to the XDG portal, GTK and Zenity as
case-sensitive `*.ext` globs, so `CLIP.MP4` and `clip.Mp4` were hidden.
`file_filters::extensions` keeps the given extensions first (the save
dialogs' default suffix) and, outside Windows and macOS, adds one
bracket pattern per extension (`mp4` -> `[mM][pP]4`), the form the
portal's FileChooser docs recommend.

Every filtered dialog uses it: import, relink, Open file, Open
Project, Save Project and Save As.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:06:35 +02:00
sanyamsingh001 0e0f0485da LINUX.VALIDATION: honor preset shortcuts and retain MPEG oracle frames
Resolve inherited physical key collisions when loading built-in presets,
while preserving established explicit winners and deliberate custom conflicts.
Read every ffprobe display-frame timestamp and register the requested credit.
Verify exact frame-plane reuse without depending on concurrent global metrics.

Co-authored-by: Codex <codex@openai.com>
2026-10-09 14:06:37 +10:00
JordyUSA e741267125 HW5: Linux VA-API HEVC hardware decoding, Main and Main 10 (#30)
HEVC through VA-API the same way as H.264: the software HEVC decoder's
parsers and DPB (now generic over the picture type in filmcraft-hevc:
POC, reference picture sets, reference lists, output order, RASL
handling) drive the stateless hardware. Main, Main 10 and Main Still
Picture, 4:2:0 8/10-bit (P010 read-back); scaling lists, tiles, WPP,
dependent slices, weighted prediction. Missing references and range
extensions go to the software decoder.

Tests: hardware parity on HEVC Main / Main 10 incl. open GOP, scaling
lists, slices+WPP, weighted prediction, tskip+AMP, 1080p/2160p (streamed
comparison to stay within memory); HEVC front end against a recording
stand-in on every OS; ABI checks extended to va_dec_hevc.h. Benchmark on
Iris Xe: 4K HEVC 203 -> 10 ms CPU per frame, 4K Main 10 212 -> 18 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:00:30 +07:00
swillunderscore 1f6f7d4c7d #172: sequence.settings scales Motion with the frame size and takes timecode, render quality and colour
- filmcraft_edit::frame_size::scale_motion: Sequence Settings' "Scale motion effects
  proportionally when changing frame size". Every video clip's Motion Position maps through
  p' = c1 + k(p - c0) and Scale / Scale Width multiply by k = min(W1/W0, H1/H0), static values
  and keyframes: a plain proportional scale for the same aspect, and on an aspect change the old
  frame fits inside the new one, centred. Automatic positions stay automatic, Scale to Frame
  Size clips keep their Scale (the renderer refits them), Anchor Point and Rotation stay.
- sequence.settings: new dropFrame, maxRenderQuality, workingSpace, wideGamut, autoToneMap and
  scaleMotion (default false, so existing callers are unchanged) params, in the same undo step;
  returns {"scaledClips": n}. Drop-frame on a rate without it is refused atomically.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:58:37 -05:00
JordyUSA 3496e553e7 HW5: Linux VA-API H.264 hardware decoding (#30)
Stateless VA-API decoding with FilmCraft's own H.264 host side: the
software decoder's SPS/PPS/slice-header parsers and its DPB (now generic
over the picture type in filmcraft-h264) drive the GPU, so reference
marking, reference lists and output order are shared and the pictures are
bit-exact with the software decoder. libva is loaded at run time; no libva
or driver means software decoding as before. frame_num gaps (open-GOP
seeks) get the software decoder's stand-in frames. HybridDecoder keeps the
transparent software fallback.

Tests: hardware parity on 7 H.264 streams incl. 1080p/2160p, seeks,
flushes, forced failures, fuzzing, concurrency; front end against a
recording stand-in on every OS; ABI checks against va.h. Docs: platform
README, ADR 0001 addendum, architecture, performance (H.264 2160p CPU per
frame 417 -> 11 ms on Iris Xe), roadmap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 08:35:57 +07:00
midasdf d77728008f Keep grapheme clusters together in vertical text
Co-Authored-By: Codex <noreply@openai.com>
2026-10-09 00:40:01 +09:00
Manuel Camacho abd62eefa9 Cargo.lock: tts crates at 0.4.0 after the version bump
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:38:19 -07:00
Manuel Camacho 036daaaf12 Merge upstream main (0.4.0 version bump) into tts-narration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:38:10 -07:00
Brandon Thomas 5231852443 Bump version to 0.4.0 (from 0.3.0) for release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:01:39 -04:00
Manuel Camacho beebdbcd39 M7.12: natural Text to Speech voices (Kokoro-82M in pure Rust)
- filmcraft-tts (feature kokoro): Kokoro-82M on candle, implemented from
  the StyleTTS 2 / iSTFTNet / ALBERT papers, config.json and the
  checkpoint's tensor shapes; official .pth read with candle's restricted
  pickle reader; voice packs from their zip. Neural Voice: tts-text
  phonemize -> Kokoro per chunk -> pauses -> pitch via audio-dsp.
- catalog: 9 US English voices (model card grade C+ or better) + CMUdict,
  every file pinned to a revision and SHA-256 (336 MB).
- speech: generic download_files (same verified downloader).
- engine: tts.render (background synthesis into Session::tts_cache),
  tts.downloadVoices (background job, cancellable), voices list with
  package state; feature neural-voices (default on in the desktop app).
- ui: natural/basic voice groups, consent box (size, sources, licences),
  download progress + Cancel, synthesis never blocks the window;
  ui.set {tts:{...}} for agents.

Measured: Whisper-base WER on 50 original sentences 2.1-3.6 % for all 9
voices (built-in robotic voice 96.6 %); 2.2-2.3x real time on CPU.
Gates: fmt, clippy, layers, assets, wasm pass; tests pass except
shortcuts_tests::assign_reassigns_conflicts_and_undoes (fails on main)
and the frame pool test (flaky under load, passes alone).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 06:52:30 -07:00
Manuel Camacho a38ddcbda4 M7.10: tts-text: text normalizer for narration (English)
New L2 crate filmcraft-tts-text: normalize(script, lang) -> Vec<Token> for the
Text to Speech text front end (P2a). English only (EnUs/EnGb), per the amended
brief. Handles cardinals to 999 999 999 999, negatives, decimals, ordinals,
money ($, €, £), percent, years (1100-2099), clock times, numeric and
abbreviated dates, phone-like digit groups, Mr./Mrs./Dr./St./Mt./vs./etc./e.g./
i.e./a.m./p.m., spelled acronyms, &/+@#/, and [pause Ns|ms] markers capped at
10 s. Pure Rust, deps serde + thiserror only, wasm-clean.

Never crashes (AGENTS.md §0): >1 MiB is NormalizeError::TooLong, oversized and
leading-zero numbers are read digit by digit, malformed pause markers become
plain text, all slicing is get()/boundary-safe, and a catch_unwind suite covers
empty/whitespace/oversized/10k-digit/$$$$/mixed-script/emoji/RTL/combining/NUL
inputs plus determinism.

Fix round (Claude review): a '.' after a number now ends the sentence (year
rule and money/decimal follow-through), so "in 1999." reads as a year with a
SentenceEnd and "$1,234,567." gets its final SentenceEnd; new table cases for
sentence-final numbers and the year rule under punctuation (., ,, !, ()); the
remaining direct indexes in non-test code converted to get()/first()/last().

Gates: fmt, clippy -p filmcraft-tts-text --all-targets -D warnings, tests
(25 suites, ~140 table cases), cargo xtask layers, cargo xtask wasm all green.
cargo xtask ci not run this round (Claude runs it when merging); it stops on a
pre-existing filmcraft-engine shortcuts test failure unrelated to this crate.

🤖 Written by Merlin (Hermes Agent, profile merlin)
2026-10-08 06:52:30 -07: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 08735f3103 Bump version to 0.3.0 (from 0.2.1) for release
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:12:49 -04:00
Brandon Thomas 87c4d1516e
About: contributor and model credits compiled in (grab bag + table, self-submitted names) (#215)
* About: contributors compiled in from contributors.json (grab bag + table, sortable, name toggle)

Help ▸ About FilmCraft gets tabs About · Contributors · Models, following the craftrules standard
(standards/contributors.md): contributors/contributors.json is generated by
craftrules/scripts/contributors.py and baked into the binary by crates/ui-egui/build.rs as static
tables (nothing read at run time; the web build has them too). Each contributor line: @username,
opt-in display/real name, PRs, commits, lines +/−/Δ, binary assets +/−, first/last commit.
Sortable by every column; alphabetical ignores case and the leading @. Tabs and credits controls
register automation ids (about.tab.*, about.credits.*).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors docs: craftrules is two levels up (../../craftrules)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: self-submitted names in contributors/people.toml; AGENTS.md credit-yourself section

build.rs reads contributors/people.toml next to the generated contributors.json and shows a name only
for entries the generator verified as written by that person (craftrules standards/contributors.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Contributors: regenerate (verified people.toml entries)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:16:52 -04:00
Brandon Thomas fcf1c45583 Merge pull request #141 from SaymonMaster14/feat/windows-media-foundation-hwdecode
Windows: Media Foundation/D3D11 hardware H.264 + HEVC decode

# Conflicts:
#	README.md
#	ROADMAP.md
#	crates/platform/Cargo.toml
#	crates/platform/src/lib.rs
#	docs/architecture.md
#	docs/performance.md
2026-10-07 17:48:06 -04:00
Dario Carnelutti 4cab6bc5da platform: opt-in hardware H.264 encoding with VideoToolbox
Export encodes H.264 on the CPU with the built-in encoder, which is most of the time
of an export (about two thirds of the busy CPU in a profile of a 20 s export). On
macOS the media engine can do it: a VideoToolbox compression session behind
`filmcraft_export::VideoEncoder`, registered by `filmcraft_platform::register()`.

- `ExportSettings::hardware_encoding` (`HardwareEncoding::Off` | `Auto`; Export > Video >
  Hardware Encoding, `"hardwareEncoding": "off|auto"` in `file.exportMedia`, a boolean is
  understood too), off by default: the built-in encoder's output is byte-identical on
  every machine, a hardware encoder's is not.
- `videotoolbox_encode` (FFI, same containment as the decoder: SAFETY comments, no
  panic across the callback, safe API) and `hardware_encode` (safe adapter and factory).
- The factory declines, so the built-in encoder is used, for other formats, MXF, two-pass
  VBR, HDR, odd sizes, non-square pixels and any configuration VideoToolbox cannot create a
  hardware session for. An encoder that fails mid-export stops it with an error: unlike a
  decoder, it cannot hand a half-written stream to another one.
- No B-frames: on real 1080p footage the quality is the same with and without them
  (+-0.3 dB at equal bitrate) and the bitrate lands closer to the target, and without
  them every frame is presented at its decode time (no composition offsets, no edit list).
- `export::register_encoder` ignores a factory registered twice; `encoder_registered`.

Apple M1, 8 cores, single runs: a 9:29 timeline (camera clip, ProRes 4444 overlays with
alpha, AAC, loudness) exports in 311 s instead of 793 s (2.5x faster); its densest 134 s
in 84 s instead of 395 s (4.7x). The outputs compare at SSIM 0.990 / PSNR 46.5 dB. The encoder
alone runs 200 fps at 1080p (docs/performance.md).

Tests (tests/hardware_encode.rs): what is taken and declined, a round trip through our
software decoder, an export that decodes in our decoder and in ffmpeg, the fallback for
everything hardware declines, exact output sizes, an empty range, hostile configurations
and encoders dropped at any point; the engine's `hardwareEncoding` parameter.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-07 13:34:51 -06:00
Davi Andrei 5e440a6607 Windows: Media Foundation / Direct3D 11 hardware H.264 + HEVC decode (#30)
New backend in crates/platform (media_foundation/): a Direct3D-aware decoder MFT is driven at the
level of single access units (Annex B), given the process's D3D11 video device through an
IMFDXGIDeviceManager so it decodes with DXVA, and its NV12 / P010 textures are read back through a
staging texture into planar Yuv8 / Yuv16. H.264 Baseline/Main/High and HEVC Main / Main 10, 4:2:0.

- Hardware is verified: non-D3D-aware or async MFTs, streams the GPU's DXVA decoder does not list,
  and system-memory output (Microsoft's MFT software fallback) are declined or fail the stream so
  HybridDecoder continues with our decoder.
- mfplat.dll is loaded at run time so Windows N editions still start.
- register() / registered() / hardware_decoder_for() cover Windows; perf.stats reports the backend.
- Tests (Windows): bit-exact parity with the software decoders at 640x360, 1080p and 2160p
  (resets, seeks, flush), forced mid-stream failure, damaged input, concurrency, repeated init.
- Bench: runs on Windows (ffmpeg on PATH, process/thread CPU clocks), HEVC Main 10 2160p row,
  hardware counters per row.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MMorBHwct72udS2LiC3ZHp
2026-10-07 10:07:40 -03:00
mweinbach 15787f7fe8 Codecs: add clean-room APV (RFC 9924) decoder, encoder, container support, and export presets 2026-10-06 17:24:34 -04:00
Brandon Thomas e659671bdf Release: FilmCraft v0.2.1
Fixes the Windows installer: the Start Menu shortcut and Add/Remove Programs icon were blank, and the shortcut description was wrong. Release builds no longer open a console window behind the app. Export can no longer panic (filmcraft-export never-crash).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 08:01:04 -04:00
Brandon Thomas c3ec0e85d3 HW1: platform crate + VideoToolbox H.264/HEVC hardware decode (#30)
- 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>
2026-10-05 04:53:28 -04:00
Brandon Thomas 798b1591ba Release: FilmCraft v0.2.0
Bump the workspace version 0.1.1 -> 0.2.0 (cargo xtask version set 0.2.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:45:36 -04:00
Brandon Thomas eaed9dbef6 Cargo.lock: filmcraft-cfb at 0.1.1 after merging the release bump
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 11:03:49 -04:00
Brandon Thomas b48c558e9e Merge branch 'main' into worktree-agent-a861ca53f77717930 2026-10-03 11:02:40 -04:00
Brandon Thomas e7e9ebe55e Release 0.1.1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 09:55:34 -04:00
Brandon Thomas 81873deedb Merge branch 'main' into worktree-agent-a861ca53f77717930
# Conflicts:
#	Cargo.toml
#	docs/architecture.md
#	xtask/src/main.rs
2026-10-03 08:55:10 -04:00
Brandon Thomas bd5f618c01 M11.4: AAF consolidated audio as OP-Atom MXF (audioFormat mxf) using the MXF writer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 07:14:32 -04:00
Brandon Thomas 9ec0c7213b Merge branch 'worktree-agent-a1a2bf10c26331f84' into worktree-agent-a861ca53f77717930
# Conflicts:
#	docs/architecture.md
2026-10-03 07:11:48 -04:00
Brandon Thomas 3ce2726a24 M11.4: Cargo.lock for the ui-egui interchange dev-dependency
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 05:04:00 -04:00
Brandon Thomas 09947712d0 M6.6: MXF OP1a / OP-Atom writer and MXF export formats
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>
2026-10-03 04:45:27 -04:00
Brandon Thomas 0658967a5b M11.4: AAF (Edit Protocol) and OMF 2.0 import/export in filmcraft-interchange
Shared mob-style composition model; AAF objects in structured storage over
filmcraft-cfb; OMF objects in a Bento container; embedded / consolidated audio
supplied by the caller (essence::audio_needs, MediaOptions).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:23:20 -04:00
Brandon Thomas 78c263a238 M9.15: AC-3 decoder from ATSC A/52 (all modes, coupling, rematrixing, block switching), wired into TS/PS/MP4/MKV
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:11:27 -04:00
Brandon Thomas 89c8565106 M9.15: filmcraft-cli probe reports MPEG TS/PS programs, streams and access units
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:57:23 -04:00
Brandon Thomas 02a7039ac5 M11.4: filmcraft-cfb, a Compound File Binary ([MS-CFB]) reader and writer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:51:18 -04:00
Brandon Thomas 1fc10b6949 M9.15: MPEG TS/PS/ES media source (MPEG-2, H.264, HEVC; MP2, AAC ADTS/LATM, LPCM) with ffmpeg oracle tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:49:33 -04:00
Brandon Thomas 2cbf91dded M9.15: MPEG-1/2 video decoder crate (frame/field pictures, 4:2:2, IEEE 1180 IDCT) with ffmpeg oracle tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 02:43:29 -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 84f7df6479 Merge branch 'main' into worktree-agent-a7998c724fdfa5d6b 2026-10-02 20:09:15 -04:00
Brandon Thomas 9dde8107ad Merge main into M6.5 export parity
# Conflicts:
#	crates/engine/src/lib.rs
#	crates/ui-egui/src/control.rs
#	docs/control-protocol.md
2026-10-02 19:59:52 -04:00
Brandon Thomas c59d21181f M6.5: export parity — presets, Export mode settings, queue, Quick Export, image sequences
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:27:19 -04:00
Brandon Thomas c98f5b76d3 Merge branch 'main' into worktree-agent-a1e148ad04b0018ef
# Conflicts:
#	ROADMAP.md
2026-10-02 17:33:45 -04:00
Brandon Thomas efa8167962 M9.14: MXF (OP1a/OP-Atom) and Ogg demuxers, image sequences, BWF timecode
- crates/mxf (L0): KLV walk, partitions, primer, header metadata, index tables, material ->
  file package resolution, AVC/VC-3/ProRes/MPEG-2 identification, PCM/AES3 (ST 331) sound,
  timecode; OP1a and OP-Atom (clip wrapping).
- crates/ogg (L0): RFC 3533 pages/CRC/packets, RFC 7845 Opus timing (granules, pre-skip, end trim).
- codecs: MxfSource (GOP cache, POC ordering for AVC without temporal offsets, MPEG-2 reported
  unsupported), OggSource (Opus with pre-roll seeking, Vorbis), Annex B H.264 decoder.
- media: image sequences (numbered stills, missing frames held, frame cache); BWF bext/iXML
  TimeReference -> start timecode.
- engine/UI: file.import {imageSequence}, auto-detect setting, file.importImageSequence,
  Media Browser "Import as Image Sequence"; CLI probe details for MXF/Ogg/BWF/sequences.
- Oracle tests vs ffmpeg for MXF, Ogg, image sequences, BWF; truncation/mutation tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 17:33:29 -04:00
Brandon Thomas 45b8c0c2a0 M8.9 + M12.6: Lumetri Scopes complete; Metadata, Timecode, Events, Progress, Reference Monitor panels
M8.9 (scopes):
- New L3 crate filmcraft-scopes (wasm-clean): Waveform RGB/Luma/YC/YC no Chroma,
  Parade RGB/YUV/RGB-White, Histogram, Vectorscope YUV (75/100 % targets, skin-tone
  line) and HLS; Rec. 601/709/2100 matrices, Clamp Signal, 8-bit/float/HDR (PQ cd/m2)
  scales; numeric summaries; density painting. < 1 ms per scope at 1080p.
- Lumetri Scopes panel rewritten: multi-scope grid, wrench / right-click menu (presets,
  scopes, parade/waveform type, targets, colour space, brightness, scale), footer
  (colour space, Clamp Signal, 8 Bit/Float/HDR). Settings in UiState::panels.
- scopes.read engine query returns levels, histogram bins, waveform/parade columns and
  vectorscope peaks.

M12.6 (panels):
- Metadata panel with editable Name/Label/log fields (metadata.get / metadata.set,
  one undo step, stored in ProjectItem::metadata; no schema change, stays v11).
- Timecode panel (rows: current/media/duration/in-out/remaining, any time display).
- Events panel on a new engine event log (failed/disabled commands, toasts, auto-save
  errors, jobs started/finished/failed/cancelled; events.list / events.clear).
- Progress panel (jobs with progress and Cancel).
- Reference Monitor (parked or ganged to Program; picture or scopes).
- All open from Window > menu, have automation ids; ui.set {"panels": ...} merges state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 17:32:22 -04:00
Brandon Thomas f5aa00ff7d Merge branch 'main' into worktree-agent-aacf40607c4b686e0
# Conflicts:
#	xtask/src/main.rs
2026-10-02 13:01:34 -04:00
Brandon Thomas 1f6562c08a M4.8: HEVC transform/SAO speed-up, catch-up decoding, perf.stats
- 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>
2026-10-02 13:01:16 -04:00
Brandon Thomas 4a931a3400 app icon: owl with ear tufts (owner's ArtCraft drawing), Dock/taskbar/launcher wiring, palette docs
- assets/app-icon/: canonical SVG + light SVG, 1024 PNG, macOS runtime PNG, .icns, .ico,
  hicolor theme, LICENSE.txt and README (palette, geometry, provenance, regeneration).
- packaging/icons.sh regenerates everything with resvg, iconutil and the new `cargo xtask ico`.
- Native app sets the window icon (with_icon) and the Wayland app id; build.rs embeds the .ico
  and VERSIONINFO into filmcraft.exe on Windows (winresource, Windows-host build-dep only).
- packaging/linux/ai.storyteller.filmcraft.desktop with Icon=ai.storyteller.filmcraft.
- Web app: favicon.svg replaced by favicon.png from the same artwork; sidecars and
  ATTRIBUTION.md updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:52:46 -04:00
Brandon Thomas f5ac5989f0 Merge branch 'worktree-agent-a7d751de0d72abab7' into worktree-agent-a6d2cbb6d6b3e5d7a
# Conflicts:
#	crates/edit/src/lib.rs
2026-10-01 23:31:39 -04:00
Brandon Thomas fc18a6962e M10.5: Text-based editing / transcripts
- 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>
2026-10-01 23:25:49 -04:00
Brandon Thomas bf67521c4d Merge branch 'main' into worktree-agent-a047c9696e3105eec 2026-10-01 20:43:25 -04:00
Brandon Thomas 243e95ab24 M13.1: wasm groundwork: range readers, pending reads, cooperative frames, stepped export
- 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>
2026-10-01 14:50:06 -04:00
Brandon Thomas fef248a527 M9.7: AV1 decode statistics (per-stage busy time) and perf test with stage breakdown
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>
2026-10-01 14:07:55 -04:00
Brandon Thomas dc9c5fd32c Merge branch 'main' into worktree-agent-a98dbfec8c9b949c7 2026-10-01 13:06:51 -04:00
Brandon Thomas 645cfd9ad1 Merge branch 'main' into worktree-agent-a98dbfec8c9b949c7 2026-10-01 12:50:04 -04:00
Brandon Thomas 44f93d615c Merge branch 'main' into worktree-agent-ac059806d5abf3b44 2026-10-01 12:46:50 -04:00
Brandon Thomas 7095a986ff fix: control channel never steals keyboard focus — UI requests and screenshots bring the window forward without activating the app (orderFrontRegardless); --control launches without activation; only ui.focus activates
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>
2026-10-01 11:53:19 -04:00
Brandon Thomas 35a2d2a861 M9.7: AV1 import from MP4 (av01/av1C) and Matroska/WebM (V_AV1)
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>
2026-10-01 11:18:49 -04:00
Brandon Thomas 1813bbaca2 M9.7: AV1 headers, symbol decoder, CDFs, intra decoding, transforms, intra prediction
New L0 crate filmcraft-av1 from the AV1 Bitstream & Decoding Process
Specification v1.0.0 with Errata 1 (tables extracted from the spec's Markdown
source by tools/extract_tables.py).

Stages: OBU parsing, sequence and uncompressed frame headers (all fields),
reference state (set_frame_refs, load_previous, reference update,
show_existing_frame), symbol decoder with CDF adaptation and
init/load/save/frame-end CDF handling, tiles, intra frame mode info
(partitions, segment ids, delta q/lf, CDEF index, modes, angle deltas, CfL,
palette, filter intra, tx size), coefficient decoding, dequantisation,
inverse transforms (DCT 4-64, ADST, identity, WHT), intra prediction (all
modes, edge filter/upsampling, recursive filter, CfL, palette), LR syntax.

All-intra libsvtav1 streams with in-loop filters disabled decode bit-exactly
against libdav1d (8/10-bit, 128x128..1280x720, odd sizes, presets 2-8).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:00:03 -04:00
Brandon Thomas 226c139741 M9.6: DNxHD/DNxHR (SMPTE ST 2019-1 VC-3) decoder and DNxHR encoder
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>
2026-10-01 10:53:02 -04:00
Brandon Thomas 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 Thomas 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 Thomas a1cc0a6838 M11.9: proxies (create / attach / toggle / reconnect) and ingest settings
- media.createProxies: background job (jobs.list progress) transcoding with our own
  encoders — ProRes 422 Proxy ¼/½, ProRes LT ½, H.264 ¼/½ (audio included) — into
  <media dir>/Proxies or a chosen folder; attached when the job finishes
  (Session::poll_persistence applies finished media jobs).
- media.attachProxies (duration ±1 frame and frame rate must match; size/aspect may
  differ), media.detachProxies, media.reconnectFullRes, media.toggleProxies
  (Preferences ▸ Media ▸ enableProxies; bumps the view revision without dirtying the
  project), media.proxyPresets.
- Proxy frames are smaller; ProxySource reports the original size and the compositor
  derives its pixel scale from the frame, so Motion and effects render the same
  picture (44 dB PSNR vs full res with Motion + Gaussian Blur, 48 dB at ½).
  Export always renders full resolution.
- ExportSettings::prores_profile (proxy / lt / standard / hq).
- project.ingestSettings + file.import: copy (fingerprint-verified), transcode,
  create proxies, copy and create proxies.
- UI: Toggle Proxies button on both monitors (lit when on), Create Proxies dialog,
  Project panel Proxy submenu and P badges.
- Measured (one core, 4K H.264 testsrc2): CPU composite 201 → 35 ms/frame at ¼ with
  ProRes Proxy ¼, 277 → 131 ms at ½ with H.264 ¼; decode 9.2 → 0.7 ms with H.264 ¼.
- Tests: create+attach+toggle same composite modulo scale, attach checks, export
  ignores proxies, ingest copy + proxies, ignored 4K benchmark, headless UI test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:06:56 -04:00
Brandon Thomas 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 Thomas c47c90f070 Merge branch 'main' into worktree-agent-a0342df5d4a386968 2026-10-01 09:03:47 -04:00
Brandon Thomas 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 Thomas 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 Thomas 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 Thomas 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 Thomas 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 Thomas 04e714510b M4.6: GPU path converts layer texels on the frame workers, not on the UI thread
GpuCompositor::upload converted f32 RGBA layers (effects rendered on the CPU, captions,
whole-frame fallbacks) and 16-bit YUV frames (ProRes render previews) to half floats on the UI
thread every time a frame was shown: 8.3 M values per 1080p RGBA layer, 4 M per ProRes frame.
filmcraft_gpu::prepare(plan) now does those conversions on the frame worker that built the plan
(parallel chunks; 16-bit codes through a cached per-bit-depth table) and the plan cache stores
them with the plan (frames::GpuPlan), bounded by count and bytes (1 GiB). composite_prepared()
only copies bytes into textures. The texels are bit-identical (new test compares the read-back
output of both paths for f32, 10-bit 4:2:2 and fallback-image plans).

bench, same-session A/B (GPU path, after-preview Full, 2 rounds, load avg 50–95):
  UI-thread present time p50/p95: plays from previews 11–31 / 34–77 ms → 3.6–5.8 / 4–14 ms;
    live effects play 17–28 / 31–56 ms → 4.1 / 12 ms (first run)
  plays from previews: 184/8, 183/9, 189/3, 169/23 → 192/0, 192/0, 191/1, 192/0 shown/dropped
  live effects play (CPU-bound, ~6 cores at this load): too noisy to compare (0–117 shown either way)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:10:14 -04:00
Brandon Thomas 97967d865a Merge main into M10 (text engine + graphics)
# Conflicts:
#	ROADMAP.md
2026-10-01 01:57:41 -04:00
Brandon Thomas daaee5072c M10.2: Type tool with on-monitor text editing, shape and pen tools, layer boxes and handles, graphic editor in Properties / Essential Graphics
- Program monitor overlay for graphic clips: Type tool click creates a text layer (in the
  selected graphic under the playhead or a new clip) and edits it in place with a caret,
  selection highlight, arrows/word/line navigation, Shift-select, Cmd+A/C/X/V, Return,
  Backspace/Delete, Esc; typing is one undo step. Selection tool selects, moves and scales
  layers (bounding box, handles, anchor); double-click edits text. Rectangle/Ellipse tools
  drag out shapes; Pen tool places a closed path. Automation ids program.layer.*.
- Properties (when a graphic is selected) and Essential Graphics: Layers list, Align and
  Transform (align / distribute), Text (source text, font family + style incl. system fonts,
  size, paragraph alignment, tracking, leading, baseline shift, box width, faux bold/italic,
  caps, underline, kerning/ligatures), Shape, Appearance (fill, 2 strokes, background,
  shadow). Automation ids graphics.*.
- Headless UI test (egui_kittest) drives the whole flow through the control channel; with
  FILMCRAFT_UI_SHOTS it renders PNGs via wgpu (works with a locked screen).
- docs/graphics.md; architecture, ROADMAP, ATTRIBUTION (code-drawn glyphs) updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:57:18 -04:00
Brandon Thomas 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 Thomas 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 5110b694d8 M4.5: fix UI hang after preview render (macOS App Nap starved the hidden app; ui.screenshot waits forever)
Root cause: not a lock. When the window is hidden or behind others (always when the app is
driven over the control channel), macOS App Nap drops every thread of the process to background
priority 4 and defers its timers. On a loaded machine (load avg ~150) a priority-4 process gets no
CPU: after Render In to Out + play the UI thread stayed runnable inside the run loop's timer
callout, every thread at PRI 4 (`ps -M`), 0 % CPU, control channel silent. Relaunched with App Nap
off the same script ran through: render 2–13 s instead of 15 s, every reply immediate (2/2 runs).

- apps/filmcraft/src/app_nap.rs: hold a user-initiated, latency-critical NSProcessInfo activity
  for the life of the process (= NSAppSleepDisabled for a bundle; idle system sleep allowed).
- ui.screenshot: a hidden window or sleeping display never presents, so the screenshot event
  never came and the UI repainted at full speed forever (100 % CPU). It now fails after 10 s with
  "no frame was presented (window hidden or display asleep)" and polls at 60 Hz meanwhile.

Regression test: app_nap::tests (the activity is taken on macOS). The starvation itself needs a
hidden window on a loaded machine and is checked with the control-channel script in the commit
notes (captions.newTrack/add, markIn/Out, Full, renderInToOut wait, stop, playhead.set,
renderBar, ui.screenshot): all replies within 10 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:17:13 -04:00
Brandon Thomas 0aaa4f41de Merge main into M9.8 (VP9): Opus + VP9 together in codecs/mkv; vp9 at opt-level 3 in dev builds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:05:53 -04:00
Brandon Thomas 68e5e91bfe Merge main into the playback branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:52:31 -04:00
Brandon Thomas 4eb1ffaa59 Merge branch 'main' into worktree-agent-a4e518618b8b67600 2026-10-01 00:46:36 -04:00
Brandon Thomas 5170376dcb M4.6: headless playback benchmark (cargo xtask bench-playback) and a correct dropped-frame count
- 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>
2026-10-01 00:44:23 -04:00
Brandon Thomas 9db963e0e6 M9.8: VP9 wired into codecs — WebM/MKV V_VP9 and MP4 vp09 (+vpcC) import, 8/10/12-bit 4:2:0/4:2:2/4:4:4 (4:4:0 expanded, RGB to RGBA) output, key-frame-checked GOP random access, Decoder::reset / is_keyframe, oracle seek tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:42:11 -04:00
Brandon Thomas 7dcdef6974 T5: headless scripted UI tests (egui_kittest + control channel)
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>
2026-10-01 00:39:14 -04:00
Brandon Thomas 8eb8f5ff68 T4: BS.1770 loudness oracle test against ffmpeg ebur128
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>
2026-10-01 00:32:25 -04:00
Brandon Thomas 7655447233 T3: golden-image tests for the renderer + GPU parity; fix stale GPU uploads
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>
2026-10-01 00:26:14 -04:00
Brandon Thomas 34f53fe9cf Merge main into M11.1–2 (autosave/recovery): previews follow save/open/recover; bare pre-envelope projects with version 1 or 2 load as schema v1 (regression test)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:23:01 -04:00
Brandon Thomas 3704002f89 T2: cargo xtask fixtures pre-generates the oracle fixture matrix
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>
2026-10-01 00:20:59 -04:00
Brandon Thomas 8780d5ffa2 T1: shared ffmpeg/ffprobe discovery in filmcraft-testkit
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>
2026-10-01 00:10:59 -04:00
Brandon Thomas 516978b4d4 M11.2: auto-save ring + crash-recovery journal
- Engine autosave module: Preferences (autoSave.enabled/intervalMinutes=5/maxVersions=20/
  saveCurrentProject/recoveryJournal/recoveryIntervalSeconds), persisted in the per-user data
  dir; prefs.get/set/reset commands.
- Background worker (filmcraft-autosave thread): the UI thread only hands over the Arc<Project>
  snapshot; JSON encoding and atomic writes happen on the worker. Premiere-style auto-saves to
  <project dir>/Auto-Save/<name>-YYYY-MM-DD_HH-MM-SS.fcproj (local time), only when changed,
  rotated to Maximum Project Versions, optionally also saving the project.
- Recovery journal: debounced snapshot of unsaved changes in <data>/Recovery/<session>/ guarded
  by an OS file lock; on launch, sessions whose lock is free and that left a snapshot are offered.
  Clean quit removes the journal (or keeps it, marked cleanExit, if changes were unsaved).
- Commands: file.recover, file.discardRecovery, file.recoveryList, file.autoSaveNow,
  file.autoSaveStatus, file.listAutoSaves.
- UI: Preferences ▸ Auto Save page (Edit ▸ Preferences, Cmd+,; FilmCraft ▸ Settings… on macOS),
  "Recover Unsaved Changes" dialog at startup, Revert confirmation; all with automation ids.
- Desktop: --recover / --no-recover / --data-dir; local-time offset via chrono (clock).
- Verified in the app: edits, kill -9, relaunch, dialog, Recover restores the edits (unsaved, original path).
- docs/project-files.md: auto-save, recovery and measured costs (2,000 clips: 2.5 ms encode,
  1.4 MB, ~30 ms atomic write+fsync, all off the UI thread). ROADMAP M11 updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 00:10:23 -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 Thomas b0ddca7169 Merge branch 'worktree-agent-a726528c34396e2d2'
# Conflicts:
#	ROADMAP.md
2026-10-01 00:07:18 -04:00
Brandon Thomas 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 Thomas ba4a04e031 M11.1: .fcproj schema versioning + migrations, atomic saves
- New crate filmcraft-format (L3): files are an envelope {format, schema_version, generator,
  project}; a chain of single-step JSON migrations (v1 bare Project → v2 envelope) upgrades old
  files on load; newer-than-supported files are refused with a clear "update FilmCraft" error;
  damaged/foreign files report as such.
- atomic_write: temp file in the same dir + fsync + rename + dir fsync; used for every project
  write (FsServices), so a crash mid-save never corrupts the project.
- Engine: file.save/saveAs/open go through the format crate; new file.saveCopy (Save a Copy…,
  Cmd+Alt+S) and file.revert; the first save over an upgraded file keeps
  "<name> (schema vN backup).fcproj". Project.version dropped (the envelope carries it).
- Hand-made v1 fixtures (minimal + edit without later serde-default fields) in
  crates/format/tests/fixtures; 2,000-clip encode/decode timing test.
- docs/project-files.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:54:04 -04:00
Brandon Thomas 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
Brandon Thomas 421af286dd M10.4: caption tracks in the sequence model (project v2) + filmcraft-captions (SRT/WebVTT/SCC read+write, CEA-608 pop-on/roll-up/paint-on decoder, pop-on encoder with frame-exact load scheduling, burn-in layout + own rasteriser over skrifa outlines with Inter) + caption edit ops (add/split/merge/trim/move/delete, sync-locked insert/extract)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:42:06 -04:00
Brandon Thomas 608a83f33e M9.13: Opus in WebM/MKV and MP4 import
crates/codecs decodes Opus with filmcraft-opus (48 kHz output):
- Matroska/WebM A_OPUS: CodecPrivate = OpusHead. CodecDelay removes the
  pre-skip exactly (header pre-skip when CodecDelay is absent); packet starts
  are accumulated from TOC durations because block timestamps are only
  tick-accurate. Random access decodes max(SeekPreRoll, 320 ms) first.
- MP4/MOV Opus + dOps: edit list removes pre-skip (dOps pre-skip without
  one); same pre-roll on random access.
- Family-1 surround reordered from Vorbis to WAV/SMPTE channel order;
  corrupt packets are concealed instead of failing.
Tests (ffmpeg/libopus fixtures, skipped when absent): WebM stereo, MKV mono
VoIP (SILK/hybrid), MKV 5.1 multistream, MP4 stereo; sample-aligned against
libopus (52-89 dB sequential, 45-89 dB after random access).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 23:37:19 -04:00