Commit Graph
75 Commits
Author SHA1 Message Date
tylerjenningsw c9a91d9ddf Merge remote-tracking branch 'origin/main' into pr1736-avi-backend 2026-10-29 18:49:01 -04:00
tylerjenningsw 4daff9a74a Export tests: say why the H.264 pin moved with FilmCraft 09aaf9b (#1701) 2026-10-29 18:20:09 -04:00
BFlat 07d1de1764 Merge pull request #2534 from klosixgaming-arch/fix/2022-render-banding
export: dither 16/32 bpc renders on the way to 8 bits, and let Render Settings Color Depth set the render's depth (#2022)
2026-10-30 05:09:48 +07:00
tylerjenningsw 393a7aa565 Export tests: pin the H.264 bytes FilmCraft 09aaf9b writes (#1701)
The encoder's first-frame user_data_unregistered SEI now lists its settings
("... - options: rc=vbr bitrate=10000 ... scenecut=1", 211 bytes where
"filmcraft-h264enc 0.1.1 - clean-room H.264 encoder" took 50): +161 bytes.
The same build gives (104322, 11051847956767423392), the pin Linux CI took on
a base before the 19-byte nclx colr box, when that box is left out, so the
bytes match across platforms.
2026-10-29 17:57:08 -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
midasmatterhorn 3217320c1c export: the yuv420 fast path checks the effective scale too 2026-10-29 23:48:55 +03:00
midasmatterhorn 7216e0eb6a Merge remote-tracking branch 'upstream/main' into fix-1568 2026-10-29 23:42:14 +03:00
BFlat 248818522a Merge pull request #1763 from Higgs32584/fix/1615-cpu-inflight-cap
Export: cap CPU frames in flight with EFFECTCRAFT_CPU_INFLIGHT (#1615)
2026-10-30 01:23:12 +07:00
BFlat b669e4a951 Merge pull request #2476 from ashokram-si/feat/targa-sequence-output
Export: Targa (.tga) sequence output, 24-bit RGB or 32-bit RGB + Alpha
2026-10-30 01:19:27 +07:00
bflatasticandClaude Opus 5.5 3f00560f00 Export tests: re-pin the H.264 MP4 bytes for its BT.709 nclx colr box (+19 bytes)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 01:10:42 +07: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
bflatasticandClaude Opus 5.5 3301c86f7f Export: the H.264 GPU-YUV frame loop takes the CPU cap too (Cx::inflight)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:19:41 +07:00
bflatastic f3f9be4be3 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-29 23:18:39 +07:00
Claude 3cac49374d G1: use as_chunks in the premultiplied dither test (clippy) (#2022) 2026-10-29 03:38:24 -05:00
Claude 9bfbce5117 G1: keep dithered premultiplied colour at or below alpha (#2022)
Dither can round a colour byte up while alpha rounds down (colour = alpha = 100.25/255 gives
alpha 100, colour 101), breaking the premultiplied invariant. Clamp each dithered colour byte
to the written alpha byte.

Test: dithered_premultiplied_colour_never_exceeds_alpha.
2026-10-29 03:06:31 -05:00
Claude 85e061fe0c Merge main into fix/2022-render-banding (#1631)
Resolve the export tests' module list (banding and h264_gpu_yuv both stay). #1631's
yuv420_frame checked Render Settings Color Depth itself; that depth is now the project's
depth for the whole render, so the GPU YUV path is taken only for 8 bpc renders. Above
8 bpc frames take the RGBA path, which dithers, so H.264 exports don't band either.
Test: renders_above_8_bpc_skip_the_planes.
2026-10-29 03:06:31 -05:00
Matej Kozlovský 6e69b91a80 Export: H.264 frames convert to YUV 4:2:0 on the GPU
Accelerator::comp_frame_yuv420 (default None) renders a top-level comp frame and
converts it over the background to the H.264 export's 8-bit BT.709 limited-range
4:2:0, every byte within one level of the CPU conversion (the GPU kernel follows
it operation for operation, including NaN and half-away-from-zero ties, but a
driver may contract its arithmetic). A 4K frame then reads back 12 MB of planes
instead of 133 MB of RGBA f32.

The H.264 export asks the accelerator for planes when a frame needs nothing after
the render (progressive, project depth, no crop/resize, rendered at the output
size) and otherwise renders and converts on the CPU as before. Deferred
readbacks (a browser worker) keep the pass-based path. Sizes the kernel can't
take (width not a multiple of 8, odd height) are declined before any GPU work.

Like Cx::frames, h264_frames caps GPU frames in flight only while the
accelerator is up (map_frames_while with the same liveness check): once the GPU
stops for good, the remaining frames render on the CPU side by side instead of
one at a time.

rgba_to_yuv420 moves to effectcraft_raster::yuv so the export and the GPU tests
share one reference. The GPU tests skip without an adapter (tests::gpu()), as
the crate's others do.
2026-10-28 13:19:24 +01:00
Dylan M. Taylor 072f2619bd Refactor export for strict Clippy checks 2026-10-28 06:09:21 +00:00
Claude 1c4e647d90 G1: dither only renders above 8 bpc (#2022)
An 8 bpc render has no finer values to dither, so its exports stay byte for byte what
they were (the pinned H.264 and ProRes files) and keep their size; 16 and 32 bpc renders
are still dithered on the way to 8 bits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYXhNN7W7nB6sKjwwU7CDM
2026-10-27 16:05:02 -05:00
Claude fa14090e72 G1: dither the final 8-bit conversion so gradients don't band (#2022)
Exports converted the float frame to 8 bits by plain rounding, so a smooth
gradient rendered at 16 or 32 bpc came out as flat bands, the same as at
8 bpc: raising the depth changed nothing visible. After Effects dithers
colours converted to 8 bpc for render and export; do the same.

- raster: dither_u8 and Image::to_rgba8_dithered / to_rgba8_over_dithered
  round each channel up or down against a hash of the pixel position with
  a fixed seed (deterministic, no clock or global RNG). A value already on
  an 8-bit code, as everything rendered at 8 bpc is, is unchanged.
- export: Cx::pixels dithers the colour for every 8-bit output (stills,
  H.264, ProRes, HEVC, AV1, VP9); GIF keeps plain rounding (palette).
- export: the Render Settings' Color Depth now overrides the project's
  depth for the whole render instead of only quantising the finished
  frame, so a 16 bpc render of an 8 bpc project keeps its precision.

Tests: crates/export/tests/it/banding.rs (ramp export at 8/16 bpc and with
Color Depth overrides), raster unit tests for dither_u8.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FYXhNN7W7nB6sKjwwU7CDM
2026-10-27 15:38:08 -05:00
NetterSilverandClaude Opus 5.5 94399e62ff Pin movie export bytes
H.264 and ProRes exports of a 60-frame comp (several render batches, a
rotating stroked ring, a fading solid at a fractional position, a
Screen-mode shape, 8 bpc) must keep the exact bytes main wrote before the
two previous commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:40:41 +01:00
NetterSilverandClaude Opus 5.5 d8bca3cdd9 Overlap movie encoding with rendering
Movie exports (H.264, HEVC, AV1, ProRes, uncompressed) rendered a batch of
frames on every core, then encoded it frame by frame while the render
threads waited. Each batch now encodes on a pool shared by all exports while the
next batch renders (Cx::frames_beside). With deferred GPU readbacks, or
where no thread can be started (wasm without threads), the two still take
turns. A panicking encoder becomes an export error.

H.264 frames are converted to 4:2:0 on the render threads, per frame, instead
of on the encoder's critical path. The conversion reads 8-bit levels from a
table and rounds without a libm call; both give the same values as before
(tested against the previous formula).

Demo comp (1920x1080, 300 frames) to H.264 on a 16-thread Ryzen, export
wall time -6% (median of 7 interleaved runs); output byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 17:40:41 +01:00
ashokram-si de27e1ea0a Merge main into feat/targa-sequence-output 2026-10-27 10:46:47 +05:30
ashokram-si c51a08ceb4 Export: Targa (.tga) sequence output, 24-bit RGB or 32-bit RGB + Alpha
After Effects lists Targa among its still-image output formats; EffectCraft
imported Targa (#745) but could not render to it.

- OutputFormat::TargaSequence ("Targa Sequence", `.tga`, numbered like the
  other sequences; `tga` / `targa` as a format name or output extension).
- Written by the `image` crate's Targa encoder (its `tga` feature, already
  enabled in effectcraft-media; no new crates, Cargo.lock unchanged), with
  run-length compression off: an 18-byte header, top-left origin, 24-bit BGR
  for RGB, 32-bit BGRA with 8 alpha bits in the image descriptor for
  RGB + Alpha (straight or premultiplied), 8-bit grey for Alpha.
- The render queue's size estimate counts the uncompressed bytes.
- CLI usage, the renderQueue command specs, README, architecture.md and
  file-format-parity.md name the new format.

Tests: each Targa frame holds exactly the pixels of the PNG sequence of the
same job, with the expected header, numbering and size; the frames read back
through the Targa importer as the same picture (straight, and premultiplied
when interpreted so); the image-sequence round trip, the format parser and
the renderQueue commands cover it too.
2026-10-26 15:34:12 +05:30
skylar taylor-barrick 69b9ce2bd1 M0.37: reconcile shared docking and qualify migration follow-up 2026-10-26 06:47:07 +01:00
tylerjenningsw 42b44c460a Merge main into enhance/filmcraft-avi-backend 2026-10-25 14:13:46 -04:00
Dylan M. TaylorandClaude Sonnet 5.5 4e9fa51f12 sonar: method paths instead of closures (rust:S1612)
Poison recovery, OsStr::to_str and char predicates only; S4325 casts are
f64 to f32 and stay.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 08:17:16 +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
Higgs32584andClaude Opus 5.5 88f940d54e Export: CPU cap bounds images waiting to encode and survives a GPU stop (#1615)
Review of #1763:
- With EFFECTCRAFT_CPU_INFLIGHT set, each window is the CPU cap instead of
  one frame per thread, so rendered images held before encoding count
  against the cap too (a cap of 2 on 16 threads held up to 16 images).
- Once the GPU stops for good, the GPU cap lapses and the CPU cap alone
  applies, instead of the smaller cap (often the GPU default of 1) holding
  for the rest of the batch.

The caps are now an `Inflight { gpu, cpu }` that `map_frames_while`
re-evaluates per window and chunk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:05:03 -04:00
Michael Higgins 14dd6d333d Merge branch 'storytold:main' into fix/1615-cpu-inflight-cap 2026-10-18 15:02:44 -04:00
Brandon Thomas 0867d837d4 Merge pull request #1753 2026-10-18 09:35:35 -04:00
Higgs32584andClaude Opus 5.5 46040928c8 #1615: cap CPU frames in flight during export (EFFECTCRAFT_CPU_INFLIGHT)
CPU renders kept one frame per thread in flight with no cap, so heavy comps
could be OOM-killed. EFFECTCRAFT_CPU_INFLIGHT=N renders at most N frames at
once through the existing capped path; unset keeps today's behaviour. With a
GPU cap too, the smaller cap holds for the whole batch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 21:56:54 -04:00
vangel76 743582700b Merge upstream/main into filmcraft-pin-bump 2026-10-16 19:17:11 +02:00
BFlat c6f6f9b373 Merge pull request #1511 from MatejKozlovsky/fallback-parallel
Export: render in parallel once the GPU stops for good
2026-10-16 19:01:38 +07:00
midasmatterhorn 30e02b06ca project: output_size clamps the scale, not the sides, and every renderer uses it
Clamping each side on its own broke the aspect ratio (a 30000x15000 comp
at 4x came out 30000x30000). The effective scale min(s, MAX_SIZE/w,
MAX_SIZE/h) keeps both sides within the comp limit, and the export
pipeline and the locked-aspect width derivation render at that same
scale, so nothing requests a frame larger than output_size reports.
2026-10-16 14:46:24 +03:00
vangel76andClaude Sonnet 5.5 93b06a6eab Bump FilmCraft to 4e2d0bc2 (part of #804)
Problem: the hardware-decoding work (#819) needs a newer FilmCraft than the
pinned 2c5ba7a, and the pin bump should be reviewable apart from it.

Fix: move all filmcraft-* deps to 4e2d0bc2. Adapt to MediaInfo.audio ->
audio_streams and VideoStreamInfo.hdr; ask the pool for a quarter into the
frame because FilmCraft now rounds requests half up; adapt rotation and the
AAC downmix; use Decoder::with_threads(1) in the VP9 tests. No
filmcraft-platform, no hardware setting.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 22:56:38 +02:00
Matej Kozlovský 2603a6f0e0 Export: render in parallel once the GPU stops for good 2026-10-15 01:30:54 +02:00
tylerjenningsw 8b536e597b Export: millisecond-rounded footage renders at its rate, each frame once (#1442) 2026-10-14 11:37:36 -04:00
Marco Arnulfo 78b4fe4170 Import: AIFF files import, including EffectCraft's own AIFF renders (fixes #1428) 2026-10-14 13:44:59 +02:00
Matej Kozlovský 2669e1a1de Export: cap GPU renders only, for GPU-accelerated projects (#879 review)
- The cap applied whenever an accelerator was attached, so Software Only
  projects (which render on the CPU) rendered one frame at a time. It
  now needs the project's GPU acceleration too, as #898 gates it.
- Only the render is capped: each window of max(cap, threads) frames
  renders in chunks of the cap, then encodes in parallel, holding at
  most one window of images.
- An invalid EFFECTCRAFT_GPU_INFLIGHT (0, negative, not a number, too
  large) logs a warning before using the default.
- The default stays one frame: the GPU backend reports no free or total
  device memory portably, so it is not sized from VRAM.
2026-10-12 16:51:36 +02:00
Matej Kozlovský 9d63ec19fc Export: one GPU frame in flight by default (#879)
Three frames at once still filled an 8 GB card on the #879 project at
1080p (7.9 GB, the same as no cap); one frame peaked at 6.3 GB and two at
7.0 to 7.8 GB. One matches how the export scheduler in #898 renders image
sequences (one GPU worker, synchronous readback). EFFECTCRAFT_GPU_INFLIGHT
still raises it.
2026-10-12 16:23:12 +02:00
Matej Kozlovský 742472440e Export: cap GPU frames rendering at once (#879)
With a GPU accelerator every frame of a batch rendered in parallel, each
holding its own working textures, so a 30-frame 1080p batch could take
the whole card (20+ GB on an RTX 3090) and run shared-memory GPUs out of
memory. Cx::frames now renders GPU frames in consecutive chunks of 3
(EFFECTCRAFT_GPU_INFLIGHT overrides it), the same cap as the export
scheduler in #898; output order and the CPU path are unchanged.

Fixes #879
2026-10-12 16:00:05 +02:00
vangel76andClaude Opus 5.5 575642a5ed #372: uncompressed QuickTime (raw RGB / ARGB) output module format
Problem: the Render Queue had no single-file uncompressed output, so
handing a render to an external encoder meant a lossy codec, ProRes,
or an image sequence plus a separate WAV.

Fix: add the "QuickTime (Uncompressed)" Output Module format. Frames
are stored as QuickTime 'raw ' samples: 8-bit RGB (depth 24, rows
padded to even bytes) or ARGB with RGB + Alpha (depth 32), written
through FilmCraft's existing QuickTime muxer with PCM audio
(16/24-bit or float) like ProRes. Format name `uncompressed` on the
control channel; disk-space estimate and Sample Format menu included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:43:33 +02:00
sanyamsingh001andCodex 34b894a249 G3: preserve exported audio sample positions
Co-authored-by: Codex <codex@openai.com>
2026-10-10 17:55:50 +10:00
bflatasticandClaude Opus 5.5 8c4a3cbfe0 Export: a Render Queue job renders every frame on the project's renderer (#414)
Cause: pipeline::Cx rendered with Backend::Auto, which picks the CPU or the
GPU compositor for each top-level frame from measured frame times
(render::AutoPick: warm-up on both sides, a re-probe every 48 frames). The
two compositors differ slightly, so frames came out differently depending
on which side the timings favoured in that run, and two GPU exports of the
same frames differed.

Fix: the job renders every frame on the project's renderer, as After
Effects renders a job with one renderer: Backend::Gpu with Mercury GPU
Acceleration (frames the GPU declines still render on the CPU, as they do
in every GPU render), Backend::Cpu with Mercury Software Only. The viewer
and previews keep Auto. Docs (architecture, parity, web) say so.

Tests: render_settings::a_job_renders_on_one_compositor exports ten frames
with a stand-in GPU compositor that keeps Auto history and draws solid
green: with Mercury GPU Acceleration every frame is green and two runs are
identical (with Auto the warm-up sent frames to the CPU: it fails), with
Software Only the stand-in is never asked. The web worker GPU job test's
comment no longer describes Auto.

Diagnosis by @pat-the-wizard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:26:17 +07:00
bflatasticandClaude Opus 5.5 1ccf7cb910 Export: Render Queue jobs keep a layer cache, so static layers render once (#413)
Cause: pipeline::Cx::render_at built the job's Renderer without a layer
cache, so every layer of every frame rendered from scratch (source, masks,
effects, styles), and the GPU compositor, whose uploads are keyed by the
cached buffers, uploaded every layer again on every frame.

Fix: each job (Cx) owns a LayerCache, the cache type the viewer uses, with
its default budget (1 GiB, the default Settings > Memory & CPU layer cache),
shared by the job's parallel frames and dropped with the job. With deferred
GPU readbacks it holds the accelerator's miss gate, as the job workers'
caches do. Cx::renderer builds the job's renderer (options, accelerator,
cache) for render_at and the test.

Tests: pipeline::tests::layers_render_once_per_job exports ten frames of a
solid animated only in Transform: the cache misses of the first frame are
the only ones (it fails without the cache: no layer is cached), and every
frame equals the frame rendered without the cache.

Diagnosis by @pat-the-wizard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:26:16 +07:00
Brandon Thomas d5beb4b3c4 Merge pull request #344 from MindlessRuff/fix/exr-over-range
fix(export): keep values above 1.0 in EXR output (#339)
2026-10-09 09:31:40 -04:00
Brandon Thomas f87f3b3e1a Merge pull request #357 from HEOJUNFO/fix-317-aiff-float
Export: AIFF 32-bit float writes AIFF-C fl32 instead of 24-bit (#317)
2026-10-09 09:31:38 -04:00
7b632bdbc1 MCP: report render progress and handle cancellation
Keep serving during long renders, recover failed workers, and remove only files opened by the interrupted export. Cover progress, cancellation, EOF and output ownership with regression tests.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 00:20:22 -07:00
heojunfoandClaude Opus 5.5 303056eec4 Export: AIFF 32-bit float writes AIFF-C fl32 instead of 24-bit (#317)
The Output Module offers 32 Bit Float for AIFF and the queue reports F32, but
audio_file silently wrote 24-bit PCM because plain AIFF has no float. Write
an AIFF-C file instead: FORM type AIFC, an FVER chunk (AIFC Version 1), and a
COMM chunk naming the fl32 compression type, with big-endian IEEE float
samples in SSND. 16- and 24-bit stay plain AIFF.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 15:33:04 +09:00