55 Commits
Author SHA1 Message Date
JonRC 0de8d57cf8 Import: EDL source timecodes land on each file's own time (#2832)
FilmCraft's EDL reader keeps source timecodes as media time and leaves the
rebase to the caller after probing. The probe now keeps the file's start
timecode (Footage::file_timecode), and an EDL import rebases each found
file's clips with filmcraft_interchange::rebase_source_timecode before the
layers are made, as FilmCraft's own import does.
2026-11-02 11:46:03 -03:00
Brandon ThomasandClaude Opus 5.5 a926aa9182 test(media): measure the in-place movie's peak RSS in its own process
VmHWM is per process; the other tests in the binary ran on parallel threads
and their allocations failed the 64 MiB bound intermittently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:32:05 -04:00
tylerjenningsw 852f98d6a5 Merge enhance/filmcraft-avi-backend into enhance/avi-import
# Conflicts:
#	crates/export/tests/it/movie_bytes.rs
2026-10-29 18:58:57 -04:00
tylerjenningsw aecdffecb0 Media: read the LATM configuration only when FilmCraft plays that MPEG stream, not an ADTS one before it (#1701) 2026-10-29 18:44:34 -04:00
tylerjenningsw 800b555e51 Media: read the AAC configuration of the MP4 or Matroska track FilmCraft plays, not an empty one before it (#1701) 2026-10-29 18:44:05 -04:00
tylerjenningsw 799303ad68 Media tests: read ffmpeg 4.2's PCE fixtures with their real channel count (#1701)
ffmpeg 4.2 writes 2 in the sample entry of an AAC PCE layout and FilmCraft
decodes as many channels as that names, so the 6.1 check now reads a copy
naming all seven instead of skipping. Configuration 12 moves to the
synthetic layout tests.
2026-10-29 18:20:09 -04:00
tylerjenningsw ec86f24afe Media tests: every AAC layout mixes by speaker in MP4, Matroska and ADTS (#1701)
Tones encoded with FilmCraft's AAC encoder, with the configuration rewritten
to 11, 12 or ffmpeg's 6.1 PCE where the coded elements fit it, so nothing
depends on ffmpeg or skips. Expected gains come from each channel's speaker.
2026-10-29 18:20:09 -04:00
tylerjenningsw e675df7cab Media: downmix AAC by where each decoded channel plays (#1701)
The gains of an AAC source now come from the configuration FilmCraft decodes
it with, read from MP4/MOV (esds), Matroska (CodecPrivate) and LATM in MPEG
TS/PS (its first StreamMuxConfig). Channel configurations 3 to 6, 11 and 12
arrive reordered to WAVE order, 4 as L R C Cs, 7 in element order. A program
config element goes by its element positions, except the six structures in
which ffmpeg's encoder fills its own channel order across them (an LFE in a
side SCE, a back centre in a side pair), recognised by its "Lavc" comment.
ADTS can only signal configurations 1 to 7 that FilmCraft decodes, which the
channel count tells apart. A decode of fewer channels than the layout keeps
its first ones (7 of configuration 7's 8 in MPEG-TS LATM).
2026-10-29 18:20:08 -04:00
tylerjenningsw 8bb5dbb81e Media: downmix AAC in the order its channel configuration decodes in (#1701)
FilmCraft reorders AAC configurations 3 to 6, 11 and 12 to WAVE/SMPTE order
and leaves configuration 7 and program config elements as coded, so the
channel count alone can't say which order arrives: ffmpeg writes 7.1 as
configuration 7 (C L R first) and 6.1 as a PCE it fills in its own order
(L R C LFE Cs Ls Rs), while FilmCraft's encoder writes a centre-first PCE.
The order is now read from an MP4/MOV's AudioSpecificConfig when the source
opens; other containers keep a channel-count guess.

The ffmpeg oracle test gains configuration 12 (ffmpeg's 7.1 relabelled) and
skips PCE files ffmpeg 4.2 marks as stereo, which FilmCraft reads as two
channels.
2026-10-29 17:52:57 -04:00
tylerjenningsw 3390e2fff7 Merge remote-tracking branch 'origin/main' into pr1736-avi-backend
# Conflicts:
#	crates/media/src/pool.rs
2026-10-29 17:11:16 -04:00
tylerjenningsw 4439770a05 Merge main into enhance/avi-import
Conflicts:
- viewer.rs: the undecodable-footage notice moved into phase_scene on main; it now
  collects its layers in footage_problems, which keeps the proxy and collapsed-precomp
  rules of #1701.
- project.rs: main's row layout, with the problem marker going by item_problem.
- movie_bytes.rs: the H.264 pin is FilmCraft 09aaf9b's bytes plus main's 19-byte
  BT.709 colr box (104_322 + 19 = 104_341).
render/footage.rs imports ItemKind now that main names its imports.
2026-10-29 15:11:20 -04:00
Kaedo17 949736fe36 review: the H.264 oracle fixture uses options the picked encoder accepts
CodeRabbit's leftover nitpick: h264_mp4 hard-coded -preset medium -crf 18 for
whatever encoder the build ships, but libopenh264 (and the hardware backends)
reject both — so a build whose picked encoder is not libx264 failed the fixture
and silently skipped every check gated on it. libx264 keeps its constant-quality
pair; everything else gets -b:v. Pinned by a small test over the option sets.
2026-10-30 01:30:53 +08:00
Kaedo17 123095b6df export: tag BT.709 limited-range nclx; the ffmpeg oracles pick decoders by name and skip unbuildable fixtures
- Exported HEVC/AV1 nclx says BT.709 and limited range where the pipeline
  means it, matching what the players we tested assume; the round-trip
  tests pin the box values.
- The ffmpeg oracle helpers no longer read the flags column of
  `ffmpeg -decoders` for "is a decoder" — every row of that listing is a
  decoder, and the trailing D there is "supports direct rendering method
  1", which a build's hevc decoder may legitimately lack. Decoders are
  matched by the codec-name column alone.
- Fixtures a given ffmpeg build cannot actually encode (a listed encoder
  that fails at run time: hardware backends without their device, the
  noopenh264 stub) skip with a note instead of failing the run.
2026-10-30 01:30:53 +08:00
tylerjenningsw a2f3ac41b1 Merge main into enhance/avi-import 2026-10-25 14:26:28 -04:00
tylerjenningsw 42b44c460a Merge main into enhance/filmcraft-avi-backend 2026-10-25 14:13:46 -04:00
aiongg 74f7e76f41 Merge #1917: #1812: stills at reduced resolution are resampled once, and their layers cached (@ded-achtung) 2026-10-23 21:34:54 +00:00
aiongg 0173abde8c CI: dev-profile gate, one test binary per crate, nextest, line tables only (#2235)
* Dev profile: line tables only for workspace crates, no debug info for dependencies

* xtask ci: dev-profile gate, nextest, tests without examples, wasm --keep-going

* One integration-test binary per crate

* xtask ci: run effectcraft-gpu's shared-device tests in one cargo test process

* xtask ci: run every stage and report all failures

* xtask ci: name the stage type (clippy type_complexity)

* xtask ci: run every test pass and report all failures

* cargo fmt
2026-10-23 12:59:51 -04:00
dedandClaude Opus 5.5 4cf6580b19 G5: a reduced still's cache key includes its nominal size (#1812)
Review: two footage items of one file with different nominal sizes could share a sized entry,
though one is resampled and the other kept as decoded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:42:22 +03:00
dedandClaude Opus 5.5 ea1f2fe984 G5: a still decoded larger than its nominal size is resampled like the renderer does (#1812)
Review: still_at_size returned a smart object's pixels unresampled when they differed from the
footage's nominal size. It now follows footage_buf: pixels larger than nominal are resampled to
the requested size, smaller ones are left for the renderer to scale up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 04:21:14 +03:00
dedandClaude Opus 5.5 c33d5c20df G5: a still asked for at its own size is the unsized frame (#1812)
Review: frame_at_size at a still's native size made a second cache key, so the pool decoded
and kept a second full-size copy of a frame thumbnail() or frame() already had.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 04:01:07 +03:00
dedandClaude Opus 5.5 57326dc6a7 G5: stills at a reduced render size are resampled once and kept at that size (#1812)
The media pool honoured the requested size only for movies; stills came back
at full size and the renderer resampled them on every frame. A 21120 px wide
plate at Quarter was re-filtered each frame, and four of them (1.85 GB as f32)
did not fit the 1 GiB pool, so they were also decoded again and again.

Stills are now resampled once with effectcraft_raster::resample (the same
call the renderer made, so pixels are unchanged) and the pool keeps the
reduced copy under the sized key.

#1812 project at Quarter, CLI bench: 410 ms -> 33 ms a frame (26 ms with the
layer cache); desktop playback 2% -> 60-99% of real time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 03:43:33 +03:00
tylerjenningsw d2ae0bd503 Merge main into enhance/avi-import 2026-10-18 12:45:36 -04:00
Charles BaumerandClaude Opus 5.5 bd961131a4 Media tests: ffmpeg fixtures survive concurrent first runs on Windows
Tests that share a fixture each generate it on a first run and rename
their temp file into place. On Windows the losing rename fails with
PermissionDenied while the winner has the file open. Treat a failed
rename as "another test made it first" when the fixture exists, as
`png_sequence` already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:23:28 +00:00
Brandon ThomasandClaude Opus 5.5 f3f24ef189 Land batch: fix clippy lints and the #1424 test after #1413 changed layer_menu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:42:33 -04:00
tylerjenningsw bed49c4959 Media: import AVI (#1701)
One list of movie extensions, in effectcraft-project, with AVI added:
FilmCraft reads AVI 1.0 / OpenDML. Tests against ffmpeg fixtures: Motion
JPEG with PCM audio, an unsupported codec (imports and says why it has no
picture), a file that isn't an AVI (an import error), and a damaged one
(a truncated recording imports as far as it goes; a garbled packet loses
its own frame only).
2026-10-17 11:35:05 -04:00
tylerjenningsw ca724a3d84 Media: downmix 7.1 AAC in the order FilmCraft now delivers it (#1701)
FilmCraft reorders AAC channel configuration 12 (7.1) to WAVE/SMPTE order,
as it does 3 to 6, so 7.1 AAC's left channel was mixed as the centre. The
downmix oracle test gains 6.1 (a program config element, left in AAC's
order) and 7.1 AAC fixtures.
2026-10-17 10:53:05 -04:00
Kristian Myram 037a21d5a6 #1200: allow TGA proxies and add import regressions 2026-10-13 10:09:51 +00:00
Brandon Thomas 02c134fb41 Merge pull request #904 from vangel76/fix-903-multichannel-downmix
#903: downmix 5.1 and other multichannel audio to stereo

# Conflicts:
#	crates/media/src/lib.rs
2026-10-12 04:20:49 -04:00
Brandon Thomas ab12890a9e Merge pull request #887 from vangel76/fix-886-unsupported-video-notice
#886: say why footage the decoders can't read shows no picture

# Conflicts:
#	crates/media/src/pool.rs
#	crates/media/tests/footage.rs
#	crates/render/src/lib.rs
2026-10-12 04:20:43 -04:00
Brandon ThomasandClaude Opus 5.5 6d4e721d2c Media: Targa/HDR stills keep their Exif orientation; DPX test accepts a stated transfer
#755 decoded stills of an extension-decided format with load_from_memory_with_format,
bypassing #827's oriented decode: load_oriented now takes the format.
The DPX ffmpeg oracle test expected the bare codec name; ffmpeg builds that write
the transfer characteristic make it "DPX 8-bit RGB, linear".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 03:45:49 -04:00
Brandon Thomas c11740f461 Merge pull request #827 from pravdomil/claude/import-exif-orientation
Import: stills and movies respect their orientation

# Conflicts:
#	crates/media/src/pool.rs
2026-10-12 03:23:08 -04:00
Brandon Thomas 4e8e6e10b1 Merge pull request #796 from vangel76/fix-stream-video-open
#794: open movies in place instead of reading them whole

# Conflicts:
#	crates/media/src/lib.rs
2026-10-12 03:22:57 -04:00
Brandon Thomas 565fd4118d Merge pull request #784 from vangel76/fix-329-seek-latency
#329: scrubbing a long movie: superseded viewer frames call off their decodes

# Conflicts:
#	crates/media/tests/footage.rs
2026-10-12 03:22:50 -04:00
Brandon Thomas 78b32b61d4 Merge pull request #769 from vangel76/gap-mxf-import
#767: import MXF (OP1a / OP-Atom) footage

# Conflicts:
#	crates/ui-egui/src/menus.rs
#	docs/gaps.md
2026-10-12 03:20:05 -04:00
Brandon Thomas 3d02e65f2b Merge pull request #765 from vangel76/gap-dpx-import
#756: import DPX and Cineon stills and sequences

# Conflicts:
#	crates/media/tests/footage.rs
#	crates/ui-egui/src/menus.rs
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-12 03:20:04 -04:00
Brandon Thomas 05f0d9ec62 Merge pull request #755 from vangel76/gap-tga-hdr-import
#745: import Targa (.tga) and Radiance HDR (.hdr) stills and sequences

# Conflicts:
#	crates/ui-egui/src/menus.rs
2026-10-12 03:20:03 -04:00
Brandon Thomas d57459a7d0 Merge pull request #742 from vangel76/gap-fbx-import
#736: import FBX 3D models (binary and ASCII)

# Conflicts:
#	crates/engine/src/commands/app_more.rs
#	crates/engine/src/commands/model3d.rs
#	crates/engine/src/media_browser.rs
#	crates/media/src/lib.rs
#	crates/media/src/probe.rs
#	crates/model/Cargo.toml
#	crates/model/src/lib.rs
#	crates/ui-egui/src/menus.rs
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-12 03:20:00 -04:00
vangel76andClaude Sonnet 5.5 64fabbb2fc #903: downmix 5.1 and other multichannel audio to stereo
Problem: footage audio kept only its first two channels, so the centre, LFE and surrounds of a 5.1 file were dropped. AAC 5.1 was worse: FilmCraft's AAC-LC decoder delivers AAC's own order (C L R Ls Rs LFE), so the left output was the centre and front-right was lost.

Fix: crates/media/src/downmix.rs holds one ITU-R BS.775 stereo downmix (centre and surrounds at -3 dB, LFE left out, fixed normalisation by the largest row sum so full scale cannot clip), with channel orders for 3 to 8 channels in WAVE/SMPTE order and AAC's native order, and only the front pair for more than 8. MediaPool::read_stereo applies it, which preview, waveform and export mixdown all read through. Mono and stereo are unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 20:36:41 +02:00
vangel76andClaude Sonnet 5.5 74b95b0a9d #886: say why footage the decoders can't read shows no picture
Problem: a movie whose profile the software decoders lack (H.264 High 4:2:2 / 10-bit)
probes fine, then every frame read fails; MediaPool logged the error per frame and returned
None, so the layer rendered black with no sign why in the viewer, Project panel or for agents.

Fix: MediaPool records the reason once per file (codec from the probe plus the decoder's text
without error wrappers; I/O errors stay with missing-footage handling), logs it once, and
exposes it through the new FootageSource::problem. The Composition viewer shows it on the
affected layer, the Project panel marks the item with a warning whose tooltip is the reason,
and project.summary / footage.info carry a "problem" field. Cleared when the file is
forgotten; only set when decoding really failed, so a hardware decoder that handles the file
shows nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 18:49:54 +02:00
pravdomilandClaude Opus 5.5 c03b0a7aa7 Import: MP4/MOV movies respect their rotation
A movie shot with a phone held upright stores sideways frames and says so
in its video track header; it imported lying on its side. Probing now
reports the turned size, and decoded frames (full and reduced size) are
turned to match. Only the rotation is used, snapped to a quarter turn;
mirroring, scaling and offsets in the header are ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:30:44 +02:00
pravdomilandClaude Opus 5.5 683ec372d9 Import: stills respect their Exif orientation
A photo taken with the camera held sideways (or a mirrored scan) imported
as stored, lying on its side. Probing now reports the size the picture is
seen at, and the still and smart-object decodes apply the rotation or
mirroring to the pixels. A missing or invalid tag decodes as stored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:03:47 +02:00
vangel76andClaude Opus 5.5 d648026ffd #794: open movies in place instead of reading them whole
Problem: probe() and MediaPool's movie sources called std::fs::read and then
filmcraft_codecs::open_bytes, so a clip cost its full size in memory, twice
while probing (a 245 MiB MP4 peaked at 494 MiB just to import).

Fix: container footage on disk is opened through a positional-read
ByteReader (stream::FileReader) and FilmCraft's reader openers
(filmcraft_media::reader::open_reader; MP4/MOV, Matroska/WebM, MXF, Ogg,
MPEG-PS/TS), which read the index and then single samples. Probing takes this
path only when nothing probed before containers (still/model extensions,
PSD/SVG/PDF, image magic) claims the file; everything else, whole-file audio
formats, in-memory files (add_bytes) and wasm32 builds keep the byte path.
Regression test: probing and decoding a frame and audio of a movie with a
256 MiB tail grows peak RSS by <64 MiB (512 MiB before), same results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:42:24 +02:00
vangel76andClaude Opus 5.5 a0decc80dd #329: superseded viewer frames call off their footage decodes
Problem: scrubbing a long movie showed the frame under the pointer
seconds after the pointer stopped. Every viewer frame a free frame-pool
thread had started kept running: each seeked the movie's one decoder and
decoded up to a GOP, and the newest frame waited behind all of them on
the decoder's lock (45 min 1080p H.264/HEVC, GOP 250: 1.8-3.6 s for the
last frame of a drag; a single jump is 0.1-0.4 s). FilmCraft's decoder
already polls a cancellation flag, but nothing set one.

Fix: FootageSource::cancellable / called_off and CancellableFootage (a
per-render view whose frame reads run under the render's own flag, so
reads on other threads answer to the right render). MediaPool runs them
under filmcraft_media::cancel::with_cancel. The frame workers render
through it, and a newer viewer frame calls off the comp's running viewer
frames of other times. A called-off render is not cached (frames, disk,
layer cache). Drag latency is now 0.1-0.3 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:01:54 +02:00
vangel76andClaude Opus 5.5 f0bd916543 #767: import MXF (OP1a / OP-Atom) footage
Problem: the Media Browser lists .mxf files, but File > Import's dialog
filtered them out and media::is_importable refused them, although the
pinned FilmCraft codecs behind crates/media already demux MXF (AVC,
MPEG-2 / XDCAM / D-10, VC-3 / DNxHD, ProRes, PCM audio) by content.

Fix: add "mxf" to VIDEO_EXTENSIONS and to the File > Import and Set
Proxy dialog filters. Tests check MPEG-2 4:2:2 long-GOP + 24-bit PCM and
DNxHD 36 MXF against ffmpeg frames (>35 dB, measured 64-67 dB), audio
level, and that the Import dialog offers MXF. AVI stays open: FilmCraft
has no AVI demuxer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:30:51 +02:00
vangel76andClaude Opus 5.5 557c0bdbba #756: import DPX and Cineon stills and sequences
Problem: the Media Browser listed .dpx files and After Effects imports
DPX and Cineon scans (stills and numbered sequences), but import
refused them: nothing could decode either format, and .cin was not even
an image to the browser, so its runs never grouped into sequences.

Fix: a small pure-Rust reader written from the public SMPTE 268M and
Kodak Cineon specifications (crates/media/src/dpx.rs): luma, RGB, RGBA
and ABGR at 8/10/12/16 bits, 10-bit filled method A/B, 12-bit in 16-bit
words, both byte orders, lines padded to 32-bit words, unrotated
orientations and the pixel aspect ratio; the codec names the transfer
(log, linear, ...) and code values are shown as stored. RLE, signed,
float, Y'CbCr, packed 10/12-bit and transposed files are refused with a
clear error; every header field is bounds-checked and the data length
verified before allocating. Probe and frame decode use it first;
is_importable, the Media Browser kinds and the import dialog list
.dpx/.cin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:24:16 +02:00
vangel76andClaude Opus 5.5 1fdf817cc9 #745: import Targa (.tga) and Radiance HDR (.hdr) stills and sequences
Problem: the Media Browser lists .tga and .hdr files (and groups them into
image sequences), but media::is_importable had neither extension and the
still decoder never tried them, so importing them failed.

Fix: enable the image crate's built-in tga and hdr decoders (no new
dependency), add both to STILL_EXTENSIONS and the File > Import dialog, and
pick a still's format by signature with an extension fallback, since Targa
has no signature. Radiance HDR is linear float light and is sRGB-encoded like
OpenEXR (super-white kept); Targa alpha goes through the existing
unlabeled-alpha interpretation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:57:13 +02:00
vangel76andClaude Opus 5.5 977532f135 #736: import FBX 3D models (binary and ASCII)
Problem: After Effects 26 imports FBX models into Advanced 3D comps; File > Import rejected
.fbx, so FBX product shots and scenes could not be used.

Fix: a clean-room FBX 7.x reader in effectcraft-model (binary node records with 32/64-bit
offsets and zlib arrays, ASCII syntax) and a scene builder: meshes with normals, UVs and
per-polygon materials, the Model hierarchy with the SDK transform (pivots, offsets, pre/post
rotation, rotation order, geometric transform), Lambert/Phong materials onto
metallic-roughness with embedded or relative textures, and GlobalSettings axes turned to
Y-up. Wired into import, probing, the media browser and the file dialog. Hostile input
(truncation, corruption, huge arrays, deep nesting) fails with an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:46:43 +02:00
vangel76andClaude Opus 5.5 5661d5111c #729: import STL 3D models (binary and ASCII)
Problem: After Effects 26 imports STL models besides glTF and OBJ; we
refused .stl files, and the Media Browser and import dialog did not list
them as models.

Fix: a pure-Rust STL reader in effectcraft-model (binary and ASCII,
detected from the contents; several ASCII solids and polygon loops;
VisCAM/SolidView and Materialise Magics facet colours as plain
materials; Z-up turned to the model's Y-up; faceted normals from the
winding). Truncated, empty or non-finite input is an error, never a
crash. .stl is now a model extension for import, probing, the Media
Browser, the import dialog and the system report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:32:11 +02:00
Claude 616574439e Integrate community PRs: relink's params doc and menu rows, pt-BR in the language list, MCP docs, test fixes
- file.relinkFootage (#461): close the params doc so checked execution
  (MCP, control channel) rejects unknown keys; translate its menu entry.
- general.language lists Português (Brasil) (#217).
- docs/agents.md: set_property / add_keyframe take timeBase (#522).
- #299's MCP test no longer sends get_property a value.
- The Automatically Reload Footage sequence test uses PNG frames.
- h264_frames_at_reduced_sizes (#507): the bar depends on the fixture's
  encoder; 50 dB with ffmpeg 6.1's x264.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 15:15:11 +00:00
daijroandClaude Opus 5.5 74b151cedc Footage: reduced-resolution renders get movie frames converted at their size
Cause: at Half, Third and Quarter resolution (the viewer's Auto resolution
in most windows) every movie frame was converted from Y'CbCr to f32 RGBA at
full size and then resampled down. In the browser (wasm, one thread) that
was about 8 ms converting a 1080p frame, 6 ms resampling it and 2 ms
zeroing a 33 MB buffer, per layer and frame, for a frame shown at a
quarter or a ninth of its pixels; the footage frame cache held the
full-size frames.

Fix: FootageSource::frame_at_size asks for a frame shown at a given size
(default: the full frame, as before). The renderer asks for it where it
resampled footage (below 3/4 scale; not for proxies, fields or Interpret
Footage's pixel options, which need the full frame). MediaPool makes opaque
4:2:0 movie frames of the footage's own size there: it box-filters them to
half size in Y'CbCr before converting (each 2x2 block of luma, and the
[1 6 1]/8 neighbourhood of its chroma sample, which is the mean of the
upsampled chroma its pixels get), resamples the rest of the way and caches
the result per size. Conversion is linear until colours clip, so this is
the RGB box filter resample starts with, except in blocks where some
colours clip. Other frames convert at full size and resample as before.
In the browser the conversion and resampling take about 4 ms instead of
16. Docs (architecture).

Tests:
- convert::scaled_conversion_matches_resampling_the_converted_frame: half,
  third, quarter and odd sizes of an unclipped 4:2:0 frame are within 1e-5
  of resampling the converted frame; larger sizes, odd frame sizes and
  4:4:4 convert at full size.
- tests_footage_size::reduced_renders_take_footage_resampled_by_the_source:
  a source that resamples frames itself renders the same frames as one
  that doesn't, at 8 and 32 bpc and every resolution, and is asked only
  below 3/4 scale, and not with Invert Alpha.
- footage::h264_frames_at_reduced_sizes (ffmpeg fixture): 1080p H.264
  testsrc2 frames, whose saturated colours clip, at 960x540, 640x360,
  480x270 and 323x182 are 58-62 dB from the resampled full frames, and
  are cached per size.
- convert's ignored 1080p bench also times the conversion at a third.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 16:44:49 -05:00