Skip to content

fix(parsers): find ffmpeg installed after a long-running preview started - #5439

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/ffmpeg-found-after-install
Oct 11, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/ffmpeg-found-after-install

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

What changes for a user

A Studio preview that was already running (for example one started with hyperframes preview --background) now finds FFmpeg when it is installed after the preview started. Before, Export kept answering "FFmpeg not found" until the preview was restarted.

Why

The shared FFmpeg/ffprobe lookup (findFfBinary in @hyperframes/parsers) cached a miss for the life of the process. It already searches PATH and the common install folders (/opt/homebrew/bin, /usr/local/bin, /usr/bin, ...), but after the first miss it never looked again. It now caches a found binary for the life of the process and remembers a miss for only 5 seconds, so a lookup a few seconds after an install finds it. Every FFmpeg caller (render, preflight, transcode, proxy) goes through this one lookup.

The 5-second window keeps per-file callers cheap on a machine without FFmpeg (Studio's preview probes each video on every rebuild): a burst of lookups costs one search, not one per file.

Checked

  • ffBinaries.test.ts: a miss, then the binary appears in a common install folder with PATH empty; it stays a miss within 5 seconds (no second search), then is found and cached. It fails on main (expected undefined to be '/opt/homebrew/bin/ffmpeg') and passes three runs in a row. Parsers suite: 1265 pass; the CLI and studio-server tests that use the lookup pass.
  • Real run on Linux: a background preview whose filesystem had no FFmpeg answered the render request with 503 "FFmpeg not found"; after FFmpeg was placed in /usr/bin for that process only, the same request started a render (200) with this change, and still answered 503 on main.

Not covered: on Windows the lookup searches the current directory and the process's PATH only, so an install that only adds itself to the user's PATH (winget) is not seen by a server that is already running.

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 2061 (base branch 2061), smooth 1549 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

@somanshreddy somanshreddy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving at ffa53eb5.

  • Found binaries are still cached for the process lifetime. Misses are now remembered for 5 s (lastMissAt) instead of forever, so a burst of per-file probes on a machine without FFmpeg costs one search per 5 s. The env override is still re-read on every call, and clearFfBinaryLookupCache clears both maps.
  • ffBinaries.test.ts: 8/8. All 4 mutants are killed:
    • caching a miss forever
    • no miss cache at all
    • no found-binary cache
    • TTL set to 0
  • CI: all 11 required checks are green. No CRs.
  • Heads-up: #5434 also edits findFfBinary/searchSystem in ffBinaries.ts (the npm installer fallback). Whichever lands second needs a rebase. The two compose cleanly as written: the installer lookup becomes part of the search, and its result is cached the same way.

Review by Somu

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed at ffa53eb5, focusing on cost on hot paths and how this interacts with #5434.

  • No timers. The miss window is a Date.now() comparison against lastMissAt. Nothing is scheduled, so there's nothing to leak or keep the process alive. clearFfBinaryLookupCache clears both maps.
  • Spawn cost. A hit is cached for the life of the process, as before, so a machine with FFmpeg never searches again. On a machine without it, each binary now re-runs searchSystem at most once every 5 s. On Unix that's one synchronous which (execFileSync, 5 s timeout) plus a few existsSync calls. It runs on the event loop, but which returns at once on a miss, and a burst of per-file probes (preview rebuilds, mediaMetadata, peakMap, waveform) still costs one search per window. Fine for a lookup that used to happen once per process.
  • The test uses fake timers. It shows no second search inside the window, a find after the window, and the hit staying cached (two which calls in total).
  • With #5434. Both rewrite lookupOnSystem and the doc comment in ffBinaries.ts, so whichever lands second needs a rebase. Behaviour composes cleanly: #5434's installer-package lookup sits inside the search, which only runs on a miss, and uses createRequire resolution with no spawn. A rebased #5434 will also re-check its installer package every 5 s while nothing is found, which is what you want after an npm i @ffmpeg-installer/ffmpeg.

Nit, non-blocking: if the wall clock is set back, Date.now() - lastMiss goes negative and the miss is trusted until the clock catches up. performance.now() would avoid that.

Required checks are green at this head.

— Rames

…r-install

# Conflicts:
#	packages/parsers/src/ffBinaries.test.ts
#	packages/parsers/src/ffBinaries.ts

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-approving at b6e6e4ae. That commit merges main (#5434) into ffa53eb5, the head I approved earlier.

I checked the merge resolution against a plain git merge-tree of the two parents. Both conflicts are resolved correctly:

  • ffBinaries.ts: the PR's split is kept. searchSystem runs the search, and lookupOnSystem caches a hit for good and a miss for MISS_TTL_MS. Main's lookup order is kept too: PATH, then .hyperframes/bin, then common dirs, with the @ffmpeg-installer package last (findInCommonDirsOrInstallerPackage). The installer path therefore gets the same short miss cache. clearFfBinaryLookupCache still clears both maps, and no conflict markers remain.
  • ffBinaries.test.ts: main's installer tests and its "caches the miss until cleared" test are kept alongside the PR's late-install test. The miss test makes two back-to-back calls inside the TTL, so it holds under the new miss cache.

All 11 required checks pass at this head.

— Rames

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 64c47cf Oct 11, 2026
79 checks passed
@miguel-heygen
miguel-heygen deleted the fix/ffmpeg-found-after-install branch October 11, 2026 01:03
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.

3 participants