Skip to content

fix(render): probe the base-chain filters and fail fast when one is missing (#89) - #90

Merged
renezander030 merged 2 commits into
renezander030:masterfrom
Kubaklibre:fix/render-fps-filter-probe
Aug 20, 2026
Merged

fix(render): probe the base-chain filters and fail fast when one is missing (#89)#90
renezander030 merged 2 commits into
renezander030:masterfrom
Kubaklibre:fix/render-fps-filter-probe

Conversation

@Kubaklibre

@Kubaklibre Kubaklibre commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Closes #89.

What

render applies fps/scale/pad/setsar/format/concat/trim/setpts to every segment unconditionally — there's no flag that turns any of these off. But probeFfmpegCapabilities only ever checked the flag-gated ones (drawtext, overlay, libx264), so a build missing one of the unconditional filters reached spawnSync anyway and surfaced ffmpeg's own low-level parser error (No option name near '30', Failed to set value '...' for option 'filter_complex': Invalid argument) instead of naming the actual missing filter.

Reported and root-caused against Remotion's bundled compositor ffmpeg binary (@remotion/compositor-win32-x64-msvc), a minimal build that compiles in only an explicit --enable-filter= allowlist. Turns out that build is missing four of the eight, not just fps: fps, pad, setsar, setpts are absent; scale, format, trim, concat are present. The new probe + test fixture reflect that real shape rather than a fps-only guess.

How

  • probeFfmpegCapabilities now checks all eight base-chain filter names against the same -filters output it already fetches (no extra spawn).
  • renderDraft fails fast — before buildRenderPlan, same placement as the existing available check — naming exactly which filter(s) are missing, in the same "ffmpeg lacks X" style the drawtext/overlay fallback already uses.
  • --dry-run is unaffected by design: cmdRender routes it through buildRenderPlan directly rather than renderDraft, so a plan stays inspectable on a machine with no ffmpeg at all — same reason the pre-existing --dry-run test already ran ungated on ffmpeg-less machines. (renderDraft's own dryRun branch, used when calling it as a library, still probes first — unchanged, and already covered before this PR.)
  • Deliberately not in scope here (per the issue discussion): swapping fps= for an output-level -r. That would change per-segment CFR normalization ahead of concat, so fail-fast is the whole fix; an -r fallback can be a follow-up.

Testing

  • probeFfmpegCapabilities against a fake ffmpeg fixture (/bin/sh script, same pattern as detect-scenes.test.mjs's fake binaries) that reports scale/format/trim/concat present and omits fps/pad/setsar/setpts — mirrors the real Remotion binary's -filters output.
  • renderDraft against the same fixture: asserts the thrown message names exactly the four missing filters and never names a present one.
  • CLI --dry-run against the same fixture: asserts exit 0 + a valid plan, locking in that dry-run stays ffmpeg-free even when the probe would fail — this is the test the first commit had backwards (see review discussion below).
  • New tests skip on Windows (spawnSync can't exec a shebang script there directly — same constraint detect-scenes.test.mjs already works around); verified manually via explicit sh invocation + a standalone script exercising probeFfmpegCapabilities/buildRenderPlan/renderDraft/the CLI directly against the fake binary. Full existing suite still green except two pre-existing, unrelated Windows-only symlinkSync EPERM failures in atomic-temp.test.mjs (reproduced identically on master with these changes stashed — needs Windows Developer Mode or admin, not something this PR touches).
  • Manually reproduced the original bug against the real Remotion ffmpeg binary before the fix (raw parser error), then confirmed the fixed CLI reports missing the filters 'fps', 'pad', 'setsar', 'setpts' against that same binary after.

Test plan

  • npm run build
  • npm test (678 pass / 2 pre-existing unrelated fails / 18 skipped, same skip/fail set as master)
  • npx biome check on the changed files (clean; the project-wide CRLF noise from this Windows checkout is a core.autocrlf artifact, not present in the actual git blobs — confirmed via git diff)
  • Manual repro against the real Remotion ffmpeg binary, before and after

…issing (renezander030#89)

`render` applies fps/scale/pad/setsar/format/concat/trim/setpts to every
segment unconditionally, but probeFfmpegCapabilities only checked the
flag-gated drawtext/overlay/libx264. A minimal ffmpeg build missing one of
the unconditional filters (Remotion's bundled compositor binary, reported
in renezander030#89) reached ffmpeg anyway and surfaced its raw parser error instead of
naming the missing filter. The probe now checks all eight against the
existing -filters output, and render fails fast with the same "ffmpeg
lacks X" style message the drawtext/overlay fallback already uses.

Closes renezander030#89

@renezander030 renezander030 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Kubaklibre, thanks, the core fix is exactly what we scoped and the probe + renderDraft tests are solid. One thing before merge: the CLI test "--dry-run still fails fast" fails on Linux (exit 0). Root cause: cmdRender never routes --dry-run through renderDraft; it calls buildRenderPlan directly (src/index.ts ~L4412), and that's deliberate: dry-run is ffmpeg-free so plans stay inspectable on machines with no ffmpeg at all. The existing CLI dry-run test runs ungated on ffmpeg-less machines for the same reason. All three new tests skip on Windows, which is why it looked green locally.

Suggestion: keep dry-run ffmpeg-free, flip that test to assert exit 0 + a plan even with the minimal fake ffmpeg (locks the design in as a regression test), and trim the dry-run claim from the PR text and CHANGELOG entry. The renderDraft-internal dryRun fail-fast can stay as you have it; that path already probed before this PR. With that, happy to merge. Nice find on the real Remotion filter set.

…doesn't

cmdRender routes --dry-run through buildRenderPlan directly, never through
renderDraft/probeFfmpegCapabilities, so a plan stays inspectable on a
machine with no ffmpeg at all (the pre-existing --dry-run test already
relied on that). The new "--dry-run still fails fast" test from the first
commit assumed otherwise and would have failed on Linux/macOS; it only
looked green here because the fake-ffmpeg fixture skips on Windows.

Flips that test to assert what actually happens (exit 0 + a plan) and
corrects the CHANGELOG entry's --dry-run claim to match. The
renderDraft-internal dryRun fail-fast is untouched — that path already
probed before this PR and still does when renderDraft is called directly
as a library function.

Per review on renezander030#90.
@Kubaklibre

Copy link
Copy Markdown
Contributor Author

Good catch — you're right, I hadn't noticed cmdRender routes --dry-run through buildRenderPlan directly. Pushed a fix:

  • Flipped the CLI dry-run test to assert what actually happens (exit 0 + a valid plan) even with the minimal fake ffmpeg, so it now locks in "dry-run stays ffmpeg-free" as a regression test instead of asserting the opposite.
  • Trimmed the incorrect --dry-run included claim from both the CHANGELOG entry and the PR description.
  • renderDraft's own dryRun branch (used when calling it as a library) is untouched — it already probed before this PR, so no code change needed there.

Verified against the real Remotion binary and a local fake-ffmpeg script (can't run the new fixture through spawnSync directly on Windows, same constraint detect-scenes.test.mjs already documents, so I exercised it via explicit sh + a standalone script): capcut render --dry-run --ffmpeg-cmd <broken> returns exit 0 with a full plan, unaffected by capability probing, exactly as intended.

@renezander030
renezander030 merged commit 809e1da into renezander030:master Aug 20, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants