Commit Graph
30 Commits
Author SHA1 Message Date
vangel76andClaude Opus 5.5 110c1783c3 #620: Project Notes panel, saved in the project
Problem: a project had nowhere to keep free text (to-dos, instructions
for whoever opens it next, frame numbers).

Fix: plain-text `Project::notes` (schema v15, no-op migration so older
builds refuse instead of dropping it), `project.notes` /
`project.setNotes {text, merge?}` engine commands (undoable, capped at
1 MiB; calls with one `merge` key share an undo step), and a
Window > Project Notes panel whose editor (`notes.text`) commits every
change, one typing session per undo step. Translations for es, ja,
zh-cn (and the panel title for pt-br, uk).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:09:13 +02:00
Brandon ThomasandClaude Opus 5.5 c030f78762 Merge origin/main into tts-narration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 08:13:05 -04:00
Brandon ThomasandClaude Opus 5.5 68c297b987 Arc<Sequence>: fix the missed &mut sites and closing braces; drop the unrelated key-dispatch test
source_monitor markers and format's audio-stream clamp now detach with Arc::make_mut;
restore the final braces in clip_ops.rs and project/src/lib.rs (cargo fmt);
scripted.rs is back to main (the test covered key dispatch, which this PR doesn't change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:08:56 -04:00
Brandon ThomasandClaude Opus 5.5 664c13efeb Merge origin/main into #278; schema v14, neural-voices off by default, review fixes
- Narrations take schema v14 (main's v13 is audio streams): v13_to_v14, docs updated.
- neural-voices is off by default in the desktop app, like whisper; the model download stays
  an explicit, confirmed job.
- Removed the debug probe2 test module from tts-text.
- project_subset prunes narrations of items it drops.
- unique_wav_path is bounded (99 999 names) and returns an error instead of looping.
- narration.rs uses web_time::Instant (wasm clock test); tts.* commands get the
  "Text to Speech" shortcut category, with es/ja rows for it and Edit Narration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:55:59 -04:00
Brandon Thomas a20ac75ee6 Merge remote-tracking branch 'origin/main' into fix-434 2026-10-10 06:55:52 -04:00
liangforstudyandClaude Opus 5.5 72e3eeb5de Project files: each sequence reopens with its playhead where it was left
The engine already keeps one playhead per sequence while a project is open, and the
project file's view already stores each sequence's zoom, scroll and track heights, but
not its playhead: after saving and reopening, every sequence started at zero.

ProjectView gains `playheads` (sequence id -> Tick), written beside the tabs and views
and restored with them when "Restore open sequences when opening projects" is on. Files
written before it have none and open as they did. Values from a file are not trusted:
ids that are not sequences are dropped, playheads are clamped to 0..ProjectView::MAX_PLAYHEAD
and snapped to a frame of their sequence.

Tests: each_sequence_reopens_with_its_playhead_where_it_was_left (fails without the
fix), hostile playheads in a_damaged_view_in_a_project_file_is_cleaned_up_or_ignored,
a deleted sequence's playhead is not written, and the format round trip carries it.

Also adds my contributor credit entry (people.toml).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qq8BK1LmohzE1ga4NgAdWM
2026-10-10 05:22:05 +08:00
David Campos 510156ea01 project: share sequences across edit snapshots (fixtures.rs) 2026-10-09 19:27:15 +04:00
Brandon ThomasandClaude Opus 5.5 bb4c6d0bb2 Format: repair out-of-range audio stream data on load instead of refusing the project
A clip with audio_stream >= MAX_AUDIO_STREAMS is reset to stream 0 and media
with more than MAX_AUDIO_STREAMS streams keeps the first ones, each with a
warning: one bad index must never lose a whole project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:08:10 -04:00
Idiotic f146d7ba9f feat(media,engine): implement multi-stream container audio support and schema v13
- Update MediaInfo to store all discovered audio streams in audio_streams

- Retain independent stream metadata and decoder state for MP4/MOV and Matroska/WebM

- Update audio_placement_specs to allocate successive tracks via ensure_audio_tracks

- Isolate clip rendering to read assigned audio stream rather than replaying stream 0

- Bump project schema to v13 with backwards compatibility for legacy v12 projects

Resolves #312

Fixes #200

Fixes #315
2026-10-08 22:36:45 -04:00
Manuel CamachoandClaude Opus 5.5 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 ThomasandClaude Opus 5.5 de70468ab6 #210 review: bound MXF reads by the file, keep legacy projects opening, allow 16K
- mxf: read_pcm bounds a request by the sample frames the file actually stores (plus 16M
  samples of zero padding) instead of a fixed 16,777,216-sample cap, so whole-clip reads of
  long stereo MXF (transcription) work again; PCM/AES3 chunk buffers are clamped to the source
  length instead of a 256 MiB cap; decode_pcm's output is bounded by its input, so its
  fixed cap goes (the AES3 >8-channel refusal stays; a request is also bounded by the source
  length divided by the channel count). read_sample keeps only the source-length bound.
- mxf: index_entries no longer fails open() past 1,048,576 entries (the sparse BTreeMap
  already bounds memory by real entries); long recordings open.
- project/format: new Sequence/Track/CaptionTrack::check_bounds() hold only the hostile-value
  bounds (settings, marks, overflowing time ranges); project load and export ranges use it, so
  projects that break only structural rules (overlap, zero duration) still open and export.
  check() = check_bounds() + the structural rules, as before.
- project: frame-size cap raised to 268,435,456 pixels, 32768 per side (16K, 16384x8192).
- engine: checked_u32_p accepts integer-valued floats (1920.0); renderable_sequence returns
  an EngineError (new Session::try_render_program_at, used by automation render) instead of
  eprintln.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:52:53 -04:00
cirocueto2013-wqandCodex 07d85b0f02 fix: validate hostile project, export and MXF inputs
Validate staged speed edits before adopting their timeline state.

Co-authored-by: Codex <noreply@openai.com>
2026-10-07 20:38:43 -04:00
justjohn12345andClaude Opus 5.5 eeec772671 NEST14: a project reopens with its sequence tabs and their views
Premiere Pro 26.5.2 reopens a project with the same sequence tabs, in the
same order, the same one shown, each at the zoom it was left at. FilmCraft
opened every project on its first sequence; the "Restore open sequences when
opening projects" preference existed and nothing read it.

A saved project file now has a `view` beside `project`: the open sequences in
tab order, the active one, and each sequence's view from NEST12. It is not
part of the project: no undo step, no "edited" mark, and no schema bump (older
builds ignore it). `file.open` restores it when the preference is on.

Nothing in the view is trusted. Ids that are not sequences of the project and
repeats are dropped, numbers are clamped, and a view that cannot be read is
ignored instead of failing the open. A view with no open sequence leaves the
project on its first sequence, so a project saved by a script or the CLI does
not open on an empty Timeline.

Opening or recovering a project now tells the frontend which sequence is
shown, so the Timeline takes that sequence's view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 20:23:19 -04:00
punkhopandClaude Opus 5.5 ff8aa8749d engine: a project saved after an interchange import opens again
Problem: a Final Cut 7 XML whose Basic Motion filter sets only `scale`
makes the importer add the Motion effect with its defaults, where
`position` and `anchor` are the NaN "auto" sentinel. Placing a clip
resolves those with `resolve_auto_points`; the interchange import did
not (any format: the clips are built with the effect defaults).
serde_json writes NaN as `null`, and the reader then refused the file:
"project file is damaged: invalid type: null, expected f64". The project
could be saved but never opened again.

Solution:
- project: a point parameter's value (`ParamValue::Vec2`) reads a `null`
  coordinate back as NaN, so "auto" survives a save. Only there: `Vec2`
  itself is unchanged, and a `null` in a mask vertex or any other point
  is still a damaged file.
- project: `Project::resolve_placed_auto_points` resolves the points of
  the clips in chosen sequences. Points measured in the frame use the
  sequence size. `anchor` is measured in the clip's source
  (`Project::source_size`, a subclip through its parent, as the renderer
  resolves it) and is resolved only for sources the caller says have
  their final size.
- engine: the import resolves in the edits it already makes, so the undo
  steps are what they were. In "Import ...": everything but the anchors
  of file media, whose size is only what the document says so far (an
  EDL says nothing and the importer assumes the sequence size). In each
  "Link Media": the anchors of that file's clips, from the probed size.
  A clip whose file is not found keeps an "auto" anchor, which the
  renderer centres in whatever size the media has, as before this
  change.
- engine: a later Link Media (relink.rs `apply`, behind `media.relink`
  and `media.autoRelink`) resolves those waiting anchors in its own undo
  step, when it has read the file. Nothing else resolves them: a file
  that merely comes back at its old path, or a forced relink that does
  not read the file, leaves the anchor "auto" (still rendered centred).

Why no schema step and no repair on load: the file's shape is not
changed. Files with `null` points were written by every build before
this one, at the current schema version, so a migration (keyed on the
version) cannot select them, and a bump would only make every new
project unreadable to those builds. They open now and their points are
"auto", exactly as in the session that wrote them; resolving them on
load would mean guessing the size of media that was never linked.
docs/project-files.md describes `null` points.

Tests: project
`point_params_keep_auto_through_json_and_no_other_point_reads_null` and
`placed_auto_points_resolve_once_the_source_size_is_final`; format
`auto_points_written_as_null_open_again`; engine
(interchange_auto_points_tests.rs): import with the media missing, save,
reopen, undo, redo; a linked file whose real size differs from the
declared and the sequence size (undo of Link Media and of the import,
redo); an EDL clip smaller than its sequence; import with the media
missing, save, reopen, relink (anchor becomes a number, undo, redo, a
second relink leaves it). All fail without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 20:28:54 -07:00
Brandon ThomasandClaude Opus 5.5 b16e0889f3 Never crash: deny the panicking lints in the already-clean crates and apps
aac, bitstream, cfb, color, format, frame, geom, isobmff, media, scopes, speech, the desktop
app and the web app have no production unwrap/expect/panic/unreachable (the web app's two
`expect`s — frame-cache eviction and chunked file reads — become `let … else`); each now
carries `#![deny(clippy::unwrap_used, …)]` outside tests so it stays that way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 16:08:23 -04:00
Brandon ThomasandClaude Opus 5.5 ee9fd53079 M10.7: graphics templates (.fcgt), rolls/crawls, responsive design, per-character styles, MCC/STL/TTML/DFXP captions
- Text engine: per-character style runs (layout_rich), per-run underline/colour index.
- Project: graphic_design (style runs, pins, roll/crawl maths, responsive time, source graphics),
  gtemplate (.fcgt format, validation, base64 resources, 8 original built-in templates; .mogrt refused).
- Render: item-level graphic evaluation (responsive time, pins, rolls), per-run fills.
- Engine: graphics.template.* (list/apply/export/install/remove/set/controls/thumbnail),
  graphics.setRoll/setResponsiveTime/pin/setCharStyle, Upgrade Caption to Graphic,
  Upgrade to Source Graphic (shared edits), Replace Fonts in Projects, fonts used.
- Captions: MacCaption MCC (CDP, CEA-608 + CEA-708), EBU STL (Tech 3264), TTML/IMSC1, DFXP.
- Schema v12 (no-op migration + fixture), docs.

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

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

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

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 10:48:47 -04:00
Brandon ThomasandClaude Opus 5.5 9f4208bb08 Merge main into M11.7–9 (relink, proxies, project manager): GopCache keeps main's continuation algorithm with a same-thread reentrancy guard (nested rayon requests decode privately instead of deadlocking); ProRes export combines profile choice with HDR signalling; project schema v6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:19:19 -04:00
Brandon ThomasandClaude Opus 5.5 a9af6fac71 Merge M8 (LUTs, colour management, Colour Match) into M4.6 playback; project schema v5 for colour management; architecture §5.3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:05:55 -04:00
Brandon ThomasandClaude Opus 5.5 59629eae0f M7.4: project schema v4 for Essential Sound (older builds refuse instead of silently dropping the settings on save)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 08:42:02 -04:00
Brandon ThomasandClaude Opus 5.5 07c584f1a2 Merge M3.2/M3.11 (Trim Monitor, dynamic trimming, keyboard shortcuts) into M10.1–2; project schema v3 for graphic clips so older builds refuse them cleanly
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:07:12 -04:00
Brandon ThomasandClaude Opus 5.5 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 ThomasandClaude Opus 5.5 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