Skip to content

fix(engine): prefer ffmpeg.exe over a .cmd/.bat shim on Windows PATH [P1]#1937

Closed
miguel-heygen wants to merge 2 commits into
mainfrom
fix/ffmpeg-win-prefer-exe
Closed

fix(engine): prefer ffmpeg.exe over a .cmd/.bat shim on Windows PATH [P1]#1937
miguel-heygen wants to merge 2 commits into
mainfrom
fix/ffmpeg-win-prefer-exe

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

Root cause

When HYPERFRAMES_FFMPEG_PATH is unset, findOnPath() auto-detects ffmpeg/ffprobe via where (Windows) / which and used the first result. On Windows where ffmpeg frequently lists a .cmd/.bat wrapper (npm/scoop/winget shims) ahead of the real .exe, and Node's spawn without shell:true cannot execute a .cmd/.bat — it throws spawn EINVAL. So render capture/encode and audio muxing failed with "spawn EINVAL" even though FFmpeg was installed.

Two independent reports of this exact EINVAL, one explicitly tracing it to where returning ffmpeg.cmd before ffmpeg.exe (their workaround was renaming the .cmd wrappers so ffmpeg.exe resolved first).

Fix

Extract selectBinaryFromPathResults(output, platform):

  • win32: prefer the first directly-spawnable executable (.exe/.com) among the results, falling back to the first result only when none is listed (so a shell-runnable .cmd-only setup isn't dropped).
  • non-win32: unchanged (first result).

Scoped to selection. The .cmd-only case (still EINVALs on spawn) is left for a separate shell-spawn change — the common, reported case is both a .cmd shim and a real .exe on PATH, which this fixes.

Test plan

  • New selectBinaryFromPathResults unit tests: win32 prefers .exe over a leading .cmd and over a .bat; falls back to the first result when no .exe/.com is listed; returns the first on non-win32; returns undefined for empty output.
  • Existing env-resolution tests unchanged; bunx vitest run packages/engine/src/utils/ffmpegBinaries.test.ts — 8/8 passing.
  • Full bun run build clean; oxlint/oxfmt clean.

When HYPERFRAMES_FFMPEG_PATH is unset, findOnPath() auto-detects
ffmpeg/ffprobe via `where` (Windows) / `which` and used the FIRST
result. On Windows `where ffmpeg` frequently lists a .cmd/.bat wrapper
(npm/scoop/winget shims) ahead of the real .exe, and Node's spawn
without shell:true cannot execute a .cmd/.bat — it throws spawn EINVAL.
So render capture/encode AND audio muxing failed with "spawn EINVAL"
even though FFmpeg was installed; the reporter's own fix was to rename
the .cmd wrappers so ffmpeg.exe resolved first.

Two independent reports of this exact EINVAL (one explicitly tracing it
to `where` returning ffmpeg.cmd before ffmpeg.exe).

Fix: extract selectBinaryFromPathResults(output, platform) — on win32
it prefers the first directly-spawnable executable (.exe/.com) among
the results, falling back to the first result only when none is listed
(so a shell-runnable .cmd-only setup isn't dropped). Non-win32 is
unchanged (first result). Scoped to selection: the .cmd-only case
(would still EINVAL on spawn) is left for a separate shell-spawn change.

Test: selectBinaryFromPathResults unit tests — win32 prefers .exe over
a leading .cmd and over a .bat, falls back to the first when no exe is
listed, returns the first on non-win32, and undefined for empty output.
Existing env-resolution tests unchanged. Engine build green.
@miguel-heygen miguel-heygen changed the title fix(engine): prefer ffmpeg.exe over a .cmd/.bat shim on Windows PATH [P1] fix(engine): prefer ffmpeg.exe over a .cmd/.bat shim on Windows PATH Jul 7, 2026
@miguel-heygen
miguel-heygen marked this pull request as ready for review July 7, 2026 19:04
…anscribe/remove-bg)

The CLI had its own ffmpeg PATH resolver in packages/cli/src/browser/ffmpeg.ts,
separate from the engine resolver that already preferred spawnable Windows
binaries. Its results feed spawn/execFileSync without shell:true in
snapshot.ts, transcribe.ts, and background-removal/pipeline.ts, so a
.cmd/.bat shim ahead of ffmpeg.exe on Windows PATH still caused spawn EINVAL
there. Converges both resolvers on the engine's shared
selectBinaryFromPathResults (now re-exported from the engine package root)
instead of duplicating the selection logic a third time.
@miguel-heygen miguel-heygen changed the title [P1] fix(engine): prefer ffmpeg.exe over a .cmd/.bat shim on Windows PATH fix(engine): prefer ffmpeg.exe over a .cmd/.bat shim on Windows PATH [P1] Jul 7, 2026
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Closing as superseded — main already landed this exact fix in both resolvers while this branch sat on an older base:

  • CLI resolver (packages/cli/src/browser/ffmpeg.ts): fix(cli): prefer real ffmpeg exe over cmd shim #1958 fix(cli): prefer real ffmpeg exe over cmd shim added an inline chooseBestPathCandidate (preferredExe → exact → non-shell-shim → first) used by findOnPath. It's case-insensitive (.toLowerCase()), so @tai's uppercase FFMPEG.EXE nit is already covered. This is the resolver behind snapshot/transcribe/remove-background.
  • Engine resolver (packages/engine/src/utils/ffmpegBinaries.ts): b7dcb9e2 fix(engine): harden ffmpeg binary resolution added its own chooseBestPathCandidate + PATH/PATHEXT directory scan.

So the .exe-over-.cmd/.bat preference this PR adds (selectBinaryFromPathResults) is now redundant in both places; nothing here improves on main. The remaining .cmd-only-install EINVAL case @tai flagged is unaddressed on main too and needs the shell-spawn change — tracking that separately rather than reviving this branch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant