Commit Graph

48 Commits

Author SHA1 Message Date
Paul 9ed9650cad fix(media): preserve Unicode prefixes in image-sequence filenames
Compute the final ASCII frame-number boundary without slicing inside a UTF-8 character. Preserve Unicode prefixes and add parser and public image-sequence import regressions, including missing frames and timeline placement.

Fixes #1565

Co-authored-by: Codex <noreply@openai.com>
2026-10-14 17:43:05 -06:00
Brandon Thomas 9c00138b4b Merge origin/main into #1404 (gaps.md: keep only the PXR24 edits)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:19:55 -04:00
Brandon Thomas 192174c9ee #1304: rustfmt the module list after the merge
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:10:32 -04:00
Brandon Thomas c163983fc1 Merge origin/main into #1304
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:01:21 -04:00
vangel76 595f94f303 #1407: import Radiance HDR stills and image sequences
Problem: Premiere reads Radiance HDR (.hdr) pictures; FilmCraft listed
the extension nowhere and could not open them.

Fix: clean-room RGBE decoder in filmcraft-media from the public format
description: header with FORMAT, all eight axis orders, flat, old
run-length and new per-channel run-length scan lines. Frames are linear
float (Rec.709), like EXR. Allocations are bounded by the data present;
hostile headers and truncated data return errors. "hdr" joins the
importable still extensions; stills and image sequences share the decoder.
Output equals ffmpeg's decode exactly for flat and RLE files; EXPOSURE and
PRIMARIES lines are ignored and XYZE is refused.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 21:12:44 +02:00
vangel76 af5d29310e #828: import PXR24-compressed OpenEXR
Problem: OpenEXR files with PXR24 compression (a common lossy 24-bit
float format from renderers and compositors) were refused with "not
supported yet".

Fix: decode PXR24 chunks (16 lines, deflated byte planes of per-line
sample differences; floats keep their top 24 bits) into the existing
scan-line pipeline. Bounds-checked, no panics on damaged data. Tests:
hand-built chunks with half/float/uint channels and a partial last
chunk, damaged-file cases, and an OpenImageIO-written oracle comparing
our decode with OpenEXR's own (skips without oiiotool). Docs updated;
stale AVI / FLAC-in-MP4 entries removed from G10.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 20:56:26 +02:00
lloyd d6df5c6abd media: decode uncompressed and PackBits YCbCr TIFF stills (#1293)
TIFFs stored as YCbCr (PhotometricInterpretation 6) without JPEG
compression failed with "does not support the color type Unknown(24)":
the image crate reads YCbCr only inside JPEG compression. ffmpeg writes
these whenever the source is YUV, so an ordinary frame grab
(`ffmpeg -i clip.mp4 -frames:v 1 grab.tiff`) couldn't be imported.

`still::ycbcr_tiff` decodes them from TIFF 6.0 (§21 YCbCr images, §20
ReferenceBlackWhite, §9 PackBits), reading the directory with
filmcraft-dng's TIFF parser: 8-bit chunky data units with subsampling 1, 2
or 4 each way (the image padded to whole data units), converted with the
file's YCbCrCoefficients and ReferenceBlackWhite (or the §21 defaults).
Every size, offset and count is checked, the picture is capped at 2^28
pixels, and separate planes, tiles and other bit depths are an
"unsupported" error. JPEG-compressed and RGB TIFFs still go to the image
crate.

Tests: hand-built files for data-unit placement (4:2:0), video-range
reference levels with PackBits (4:4:4), sizes that aren't whole data
units, and truncated/bit-flipped/hostile files under catch_unwind; an
ffmpeg oracle compares ffmpeg-written 4:2:0, 4:2:2 and 4:4:4 TIFFs (95×63)
with ffmpeg's own RGB decode (every channel within 6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:06:04 +02:00
Brandon Thomas 59bf7cc21f Merge main into #837 (EXR next to DNG and APNG); cap EXR chunk size, fallible chunk buffers
A tiny header naming ~1000 channels on a 64M-pixel line sized each chunk
buffer at hundreds of GB and Vec::with_capacity aborted the process.
Chunks over 256 MB are refused, and the RLE / zlib buffers reserve with
try_reserve_exact. Regression test: huge_scan_lines_are_refused_before_allocating.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:18:05 -04:00
Brandon Thomas 192b24664e Merge main into #1068 (media dev-dependencies, still.rs docs after #931)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:59:05 -04:00
Brandon Thomas 97c640352c media: as_chunks for the alpha scan (clippy::chunks_exact_with_const_size)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:55:46 -04:00
Brandon Thomas 7c21ec5e13 Merge main into #1068 (engine dev-dependencies, docs)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:55:08 -04:00
Brandon Thomas 3284ddb993 Merge main into #931 (README, engine Cargo.toml)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:54:47 -04:00
Brandon Thomas dab9630d68 Merge pull request #846 from MrCloudy2/fix/export-hevc-stall-480-v2
#480: exports no longer stop for good when a clip's decoding uses rayon (rotated iPhone HEVC clips)
2026-10-11 20:29:56 -04:00
Brandon Thomas a470620b8d Merge pull request #842 from vangel76/gap-dpx-packed-ycbcr
#841: import packed 10/12-bit and Y'CbCr DPX stills
2026-10-11 19:18:33 -04:00
vangel76 dd7a16788e #1065: import DNG stills and CinemaDNG folders as one clip
Problem: .dng files were not importable, and a DNG (a TIFF) would have
gone to the TIFF decoder, which reads at best the embedded preview. A
CinemaDNG shot (a folder of numbered DNG frames) could not be edited.

Fix: "dng" joins the importable still extensions. filmcraft-media
develops DNGs with filmcraft-dng into scene-linear Rec. 709 float
frames (transfer Linear, like OpenEXR), so highlights above 1.0 reach
the compositor. A single DNG is a still; a numbered folder imports
through the existing image-sequence path as one clip, whose frame rate
and start time code come from the first frame's CinemaDNG FrameRate and
TimeCodes tags (else Settings > Media > Indeterminate Media Timebase).
Lossy and JPEG XL DNGs are refused as unsupported by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:44:07 +02:00
Mara Johnson e283f4dee7 fix(media): RF64 trailing metadata stays outside audio duration
Use ds64.dataSize for an RF64 data-size sentinel instead of the rest of
the file, so chunks after the audio are no longer decoded as samples.

Fixes #925
2026-10-10 23:57:14 -04:00
liangforstudy 979e9a0c56 #402: animated GIF and APNG import as clips, with transparency
APNG and animated GIF files opened as stills showing only their first
frame. They now open as movie clips (crates/media/src/animated.rs):

- every frame is decoded and composited onto the full canvas by the
  image crate (APNG blend/dispose ops, GIF disposal), keeping alpha
- each frame lasts its own delay; GIF delays <= 10 ms play as 100 ms
  and APNG delays < 10 ms as 10 ms, as browsers do
- consecutive identical frames are merged; the clip is one pass of the
  animation and its timebase is the most common frame length
- hostile input: at most 10,000 frames / 1 GiB of decoded pictures; a
  damaged frame ends the clip at the frames before it; files with one
  frame (or none that decode) still open as stills, as before

Tests: media unit tests (timing, transparency, merging, single-frame
files stay stills, truncated/bit-flipped files never panic) and an
engine test that imports an animated GIF and reopens it from a saved
project.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QkQ2PDrNFjs9Vm8T44tdjA
2026-10-11 10:44:15 +08:00
vangel76 42b990b72b #841: import packed 10/12-bit and Y'CbCr DPX stills
Problem: DPX import rejected the 10/12-bit packed layout (packing 0)
and the Y'CbCr descriptors (100 CbYCrY, 101 CbYACrYA, 102 CbYCr,
103 CbYCrA) with an "unsupported" error.

Fix: unpack packed components back to back, low bits first, each scan
line on a 32-bit boundary (as ffmpeg reads 12-bit packed files), and
convert Y'CbCr to RGB with the matrix the colorimetric field names
(BT.709; BT.601 for the 601 and composite codes; by picture size when
unspecified), in video range unless the reference codes span the full
code range; 4:2:2 chroma is shared by each pixel pair. Unit tests for
every new layout and hostile headers; an ffmpeg oracle test builds
packed 12-bit RGB and 8-bit 4:2:2 / 4:4:4 / 4:4:4:4 files from
ffmpeg's test picture and matches its decode within one 8-bit step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:26:31 +02:00
vangel76 74e1b1eb68 #828: import OpenEXR stills and image sequences
Problem: FilmCraft exports OpenEXR sequences (#806) but could not open
.exr files at all, so EXR plates and renders from other apps could not
be imported (docs/gaps.md G10).

Fix: a clean-room OpenEXR reader in filmcraft-media (still/exr.rs) for
single-part scan-line files with no, RLE, ZIPS or ZIP compression and
half / float / uint channels, with our own zlib/DEFLATE decoder (no new
dependency). R, G, B, A (or luminance Y) become premultiplied linear
RGBA float frames, the compositor's working format, so values above 1.0
survive; the data window is placed in the display window. Stills open
through open_bytes, sequences through ImageSequenceSource; tiled, deep,
multi-part, subsampled and PIZ / PXR24 / B44 / DWA files are refused
with a clear error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:18:11 +02:00
MrCloudy2 f79a51dd24 #480: exports no longer stop for good when a clip's decoding uses rayon
A GopCache decodes with its lock held, and that decoding starts rayon work
(rotating iPhone frames, cropping, slice-threaded decoders). On the global pool
a worker that took part of it could, waiting inside it, pick up another render
job asking the same cache for a frame and block on the lock, while the lock
holder waited for the work on that worker's stack: exports of rotated iPhone
HEVC clips stopped making frames in about half the runs.

The locked decoding now runs on a separate decode pool, whose threads never wait
for a cache's lock; a caller that is a rayon worker waits on a plain thread so it
does no other rayon work while holding the lock. The cancel/catch-up/draft/
background hints travel with it (filmcraft_media::cancel::Context). Intra-only
streams crop and rotate before taking the lock again.

Second version of #662 (reverted): the regression test's stand-in decoder summed
with overflow, which panics in debug builds; it now wraps explicitly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:16:43 +02:00
vangel76 fe52bc0b2c #821: import Targa and DPX stills and image sequences
Problem: Premiere imports Targa and DPX as stills and numbered sequences.
FilmCraft exports both (#779) but could not read either: the extensions
were not importable and the image crate has no DPX reader, so even our
own Targa / DPX renders could not come back in.

Fix: clean-room decoders in filmcraft-media. Targa from the TGA 2.0 spec:
image types 1/2/3 and RLE 9/10/11, colour maps, 15/16/24/32-bit, 8/16-bit
grayscale, any origin. DPX from SMPTE ST 268: first element, luma / RGB /
RGBA / ABGR, 8/16-bit, filled 10/12-bit (methods A and B), either byte
order, padded lines, orientations 0-3. Both bound allocations by the
data present and reject hostile headers with errors. Stills and image
sequences share one decode_raster entry point; "tga" and "dpx" join the
importable still extensions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:58:15 +02:00
Brandon Thomas fad5255dc9 media: importable tests after the items (clippy items_after_test_module, #677 follow-up)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:08:58 -04:00
Brandon Thomas c16e712e5a Merge origin/main into #677 (keep both import tests)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:07:04 -04:00
Brandon Thomas e78223db6d Revert "Merge pull request #662 from MrCloudy2/fix/export-hevc-stall-480"
This reverts commit aa8ad402cc, reversing
changes made to 5f9b5e1234.
2026-10-10 15:24:44 -04:00
Brandon Thomas 23d1a37da2 Merge pull request #693 from vangel76/fix-67-file-reader-pool
#67: media files share a bounded pool of open descriptors
2026-10-10 14:55:33 -04:00
Brandon Thomas 0cf7a51ada Merge pull request #691 from vangel76/fix-279-streaming-wav
#279: WAV opens in place and reads only the samples asked for
2026-10-10 14:55:20 -04:00
vangel76 d8f22e9908 #67: media files share a bounded pool of open descriptors
Problem: every FileReader owned its std::fs::File for its whole
lifetime, so each open media source pinned one descriptor. A project
with hundreds of sources hit the soft limit (256 on macOS, 1024 on
Linux) and the remaining media failed to open or showed as offline.

Fix: FileReader keeps its path and borrows the file from a shared LRU
pool of at most MAX_OPEN_FILES (128) open files. Past the cap the least
recently read file is closed and its reader reopens it on the next
read; a file that is now shorter than when it was opened is refused
instead of being read as the old one. Dropping a reader closes its file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:01:11 +02:00
vangel76 878000df07 #279: WAV opens in place and reads only the samples asked for
Problem: WAV had no streaming reader, so open_reader fell back to reading
the whole file into memory, and WavSource kept those bytes for its
lifetime. Importing a 90-minute WAV cost about its size in memory and
blocked until the whole file was read.

Fix: WavSource reads through a SharedReader. Opening walks the chunk
headers (fmt, data, bext, iXML) without touching the samples, and
audio() reads only the frame range a request needs, in bounded blocks.
open_reader takes WAV this way after the registered reader openers;
WavSource::parse keeps working on in-memory bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:54:30 +02:00
Vikram Bhaskaran ec7c43eea2 Treat media extensions as case-insensitive on import.
Camera files such as CLIP.MOV were hidden or skipped on Linux because sibling matching and tests assumed lowercase suffixes. Import, the media browser, and image-sequence numbering now accept mixed-case extensions.
2026-10-11 02:39:53 +11:00
MrCloudy2 69bb1dd33c #480: exports no longer stop for good when a clip's decoding uses rayon
A GopCache decodes with its lock held, and that decoding starts rayon work
(rotating iPhone frames, cropping, slice-threaded decoders). On the global pool
a worker that took part of it could, waiting inside it, pick up another render
job asking the same cache for a frame and block on the lock, while the lock
holder waited for the work on that worker's stack: exports of rotated iPhone
HEVC clips stopped making frames in about half the runs.

The locked decoding now runs on a separate decode pool, whose threads never wait
for a cache's lock; a caller that is a rayon worker waits on a plain thread so it
does no other rayon work while holding the lock. The cancel/catch-up/draft/
background hints travel with it (filmcraft_media::cancel::Context). Intra-only
streams crop and rotate before taking the lock again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:14:34 +02:00
Brandon Thomas 701d7c7111 Merge origin/main into multi-stream audio
Keeps #359's contiguous_starts audio timing, now per stream in Mp4Audio /
MkvAudio (starts and preroll in source sample frames). MP4 audio streams and
their decoders are built from the same (track, info) pairs so they stay
index-aligned. Adapts new main code to audio_streams / TrackItem::audio_stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 10:08:09 -04:00
Davi Andrei b485e2c180 Thumbnails decode with small decoders that are not kept
Right after importing many 4K clips the Project panel and the timeline ask
every clip for a thumbnail. Up to six frame workers decoded them at once,
each with a frame-threaded software H.264 decoder (16 threads and 18
pictures in flight here, about 0.5 GB at 4K), and the decoders stayed: 42
4K clips with hardware decoding Off took the process past 6 GB during import.

Thumbnail jobs (item frames at the Media Browser / thumbnail priorities) now
run under filmcraft_media::cancel::with_background: H.264 decoders made for
them use 2 threads (thread_limited), parallelism comes from the workers
decoding different files, and the decoders of caches only thumbnails used
are released after each thumbnail (the clip served last keeps its own). A
monitor or playback request on such a cache replaces the small decoder with
a full one. Sequence frames are never background (the scopes ask for the
program frame at 40 while playing).

42 4K clips (Windows), peak private bytes: hardware Off 6.1-6.8 GB (killed)
-> 3.1-3.2 GB; hardware Auto 2.7-2.8 GB (#381) -> 2.3-2.4 GB. Thumbnails of
12 / 42 clips ready in 0.6 / 2.2 s instead of 2.2 s / not finishing.
Example crates/ui-egui/examples/thumb_burst.rs reproduces the burst.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 04:32:43 -03: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
carlitosteso3 dd404568ae media browser: probing a file doesn't read it whole (#157)
Looking at a folder probes each visible file for its properties. A format
no streaming reader takes (AVI, which isn't supported at all, and WAV,
MP3 and the other byte-opened formats) fell through open_reader's
whole-file path: a 290 MB AVI took 518 MB of memory and a full read just
to report that it isn't supported, a 247 MB WAV 500 MB.

- filmcraft-media: open_reader_within(.., max_whole) refuses, after the
  sniffing head, a file no reader opener takes when it is larger than
  max_whole. open_reader is open_reader_within with no limit.
- engine: MediaPool::probe_file uses it with PROBE_WHOLE_FILE_MAX (64 MiB);
  media_browser::probe calls probe_file. Import and playback still use
  open_file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:21:53 -05: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
justjohn12345 ac8279d293 MEM1: desktop reads media files in place
The desktop host had no Services::reader, so MediaPool::open_file read every
clip whole and kept its bytes for as long as the source stayed open: 2.9 GB
for twelve clips on a timeline (malloc_history, 15 allocations in open_file).

FsServices::reader now returns a FileReader that keeps the file open and
serves positional reads, so MP4/MOV, Matroska and the other reader openers
load the index and then single samples, as the web host already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 16:39:48 -04:00
Brandon Thomas 694e40546c Merge pull request #27 from storytold/pr/never-crash-clean-crates
Never crash: deny the panicking lints in the already-clean crates and apps
2026-10-04 16:19:27 -04:00
Brandon Thomas 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 Thomas 3a392ae8da Clippy 1.99: fix new lints so cargo xtask ci is green again
Rust/clippy 1.99 added `chunks_exact_to_as_chunks` and tightened
`needless_late_init` and `drain_collect`, failing `clippy -D warnings`
on main.

- `chunks_exact(N)` / `chunks_exact_mut(N)` with a constant N become
  `as_chunks::<N>()` / `as_chunks_mut::<N>()` (fixed-size arrays, so
  several byte reads drop their per-element indexing). relink's
  fingerprint uses the `(chunks, remainder)` split; its hash output is
  unchanged.
- Late-initialised `let`s in ac3 bit allocation and opus CELT
  bands/rate become `let x = if/match …`.
- `dpb.drain(..).collect()` becomes `std::mem::take(&mut dpb)`.

No behaviour change; `cargo xtask ci` passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 15:55:23 -04:00
Brandon Thomas 2a199425d0 M8.10: HDR-aware Lumetri, mastering metadata tone-map peak, HSL Refine, Lumetri Presets grid, Comparison View match
- Lumetri grades PQ / HLG working spaces in their own signal normalised to
  HDR White (Basic Correction) / HDR Range (Curves) in cd/m²
  (filmcraft_color::GradeSpace): exposure in stops of light, highlights above
  HDR White kept, HDR Specular scales them; Rec. 709 unchanged.
- HDR static metadata (mdcv/clli, Matroska MasteringMetadata/MaxCLL/MaxFALL)
  on VideoStreamInfo::hdr; PQ media use MaxCLL / mastering peak as the
  BT.2390 source peak; media.colorInfo reports it.
- HSL Secondary Refine: Denoise (median of the key) and Blur (Gaussian).
- Lumetri Presets (18 original looks in 4 folders) with thumbnails rendered by
  our Lumetri on a procedural preview picture; Effects panel rows and a
  thumbnail grid when wide; lumetri.presets / applyPreset / presetThumbnails.
- Color Match: Comparison View toggle; Apply Match uses its reference frame.
- Goldens lumetri_hdr_pq and lumetri_presets_grid; D-Log M stays unsupported
  (no published formula).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:31:00 -04:00
Brandon Thomas 72e0dda167 Merge main into M9.15 2026-10-03 04:16:41 -04:00
Brandon Thomas 37008cab5e M9.15: import .vob/.mod/.tod/.m2t/.m2v/.m1v/.mpv video and standalone .mp2 audio
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:16:41 -04:00
Brandon Thomas 1f4546d549 M4.9: draft decoding for reduced-resolution playback (opt-in preference)
Settings > Playback > Draft decoding (off by default): the Program monitor
asks for draft frames only while playing at 1/2 resolution or lower
(FrameKey::draft, filmcraft_media::cancel::with_draft). The GOP cache runs
the decoder in draft mode for those requests and serves draft frames to draft
requests only, so paused frames, renders and exports always get exact
pictures (codecs test: exports after draft playback match ffmpeg). Draft
plans hand the GPU box-decimated YUV planes at the drawn size. perf.stats
reports draft frames, H.264 threads and the preference; the bench gains
2160p quarter and draft playback cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:33:03 -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 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 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 5823e22e89 M4.6: cancel frame jobs playback no longer needs (stale in-flight jobs, prefetch after Stop)
- filmcraft_media::cancel: a per-job cancellation flag passed through a thread-local, so the
  MediaSource API is unchanged. Frame workers run each job inside it.
- FrameServer::retain_queue (called by schedule_playback every refresh) now also cancels running
  jobs for frames the playhead has passed. Before, a worker blocked on a source's decoder lock
  went on to decode its frame after playback had moved 50–100 frames on, found it evicted and
  restarted the decoder at the keyframe (decoder trace: "seek i=32 key=0 next=142").
- GopCache gives up (CodecError/MediaError::Cancelled) before seeking or decoding for a cancelled
  job and between samples; decoder state stays consistent, so the next request carries on.
- The CPU compositor checks the flag between tracks and effects; cancelled results are never
  cached.
- Stop drops queued prefetch jobs and cancels running ones (FrameServer::stop_prefetch). They
  used to run to completion after Stop, e.g. 14 frames of live effects rendering competing with
  a Render In to Out started right after.

bench, same-session A/B (GPU path, 8 s, load avg 35–41):
  after-preview Full live play (1080p + Lumetri/Sharpen/Levels/Tint, CPU-bound):
    38/154 → 98/94 shown/dropped, 297 → 267 ms CPU/frame, 84 jobs cancelled
  after-preview plays from previews, h264-1080, stack3, h264-2160: unchanged within noise

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 01:54:44 -04:00
Brandon Thomas 8d83799cc0 M0.3–M0.8: geom, color, frame, media generators, project model, edit algebra, CPU renderer
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 05:58:41 -04:00