Skip to content

fix(cli): check warns when a video's browser-playable copy cannot be made - #5441

Merged
miguel-heygen merged 4 commits into
mainfrom
fix/vspeed-hevc-check
Oct 11, 2026
Merged

miguel-heygen merged 4 commits into
mainfrom
fix/vspeed-hevc-check

Conversation

@miguel-heygen

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

Copy link
Copy Markdown
Collaborator

What

hyperframes check now reports a warning for each video whose browser-playable copy (the H.264/VP8 preview proxy) could not be made, naming the file and the reason. Before, check printed one stderr line such as media proxy pre-resolve: 0/2 ready, 2 failed and then Check passed, with no finding and no reason.

Scope grew after review, to make that warning trustworthy:

  • Big projects no longer fail falsely. A project with more than 10 uncached hostile videos had its 11th refused with media proxy queue is full; retry shortly (the transcoder queue holds 2 running plus 8 waiting). Check's pre-resolve and publish's proxy bake both fired every file at once. They now go through one batch resolver (resolveProxies) that keeps at most the running-slot count of its own asks in flight, so a long batch waits for room instead of being refused. This also fixes publish failing outright on such projects.
  • One finding per failed copy. When the browser cannot decode the original (ProRes anywhere, HEVC without a platform decoder), the same failure also showed up as 4 http_error 502s on the ?hf-proxy= request (errors, failing check) plus two runtime info notes. Those repeats are dropped for a file that already has media_proxy_failed.
  • The reason survives for generic ffmpeg failures. ffmpeg exited with code 1 now carries ffmpeg's last error-looking stderr line (falling back to the last line), not the closing Conversion failed!.
  • An unusable ffmpeg is one finding. When ffmpeg is missing or cannot start, check reports one warning naming every affected video, without the "render does not need it" sentence (render needs that ffmpeg too).

Why

A user reported a project with two HEVC videos where check printed 0/2 ready, 2 failed and passed. The rejection reason was thrown away, so neither the user nor we could tell what went wrong.

Reproducing it: HDR HEVC sources (HLG or PQ, which is what phones record by default) need ffmpeg's zscale filter for the proxy's tone map. An ffmpeg built without libzimg rejects every HDR source with HDR proxying requires ffmpeg zscale/tonemap filters (libzimg). Homebrew's default ffmpeg formula has no zimg dependency, and Ubuntu 20.04's apt ffmpeg (4.2.7) has no zscale either. SDR HEVC (8-bit, 10-bit, 4:2:2, 4:4:4, hvc1 and hev1 tags, odd sizes) transcoded fine with every ffmpeg tried.

A default render of the same project still succeeds with that ffmpeg (it decodes the original with ffmpeg and never uses the proxy), so this is a warning, not an error. publish does stop on a failed proxy, which the message says.

Related work

Refs #3836 (moved the pre-resolve line to stderr).

How

  • proxyTranscoder.ts: resolveProxies(projectDir, sources) runs a small worker pool sized to the transcode slots, each request capped by the existing transcode timeout, and returns settled results. It bounds only its own requests; other callers in the same process can still fill the queue (none do today). The exit-code error message appends the last error-looking stderr line with trailing ./! trimmed. FfmpegUnavailableError is exported and now also covers spawn failures, so they are remembered only briefly like a missing ffmpeg.
  • checkBrowser.ts: preResolveHostileMediaProxies uses resolveProxies and returns { findings, failedPaths }: one media_proxy_failed warning per failed file, or one for all files when ffmpeg is unusable. runBrowserCheck starts its drafts from the findings; dropFailedProxyEchoes removes http_error/request_failed drafts for a failed file's ?hf-proxy= URL (percent-decoded) and its runtime fallback/unavailable notes.
  • publishProxyBake.ts: uses resolveProxies; per-file handling is unchanged.

Test plan

  • Unit tests added/updated, each shown red with its fix reverted:
    • resolveProxies with 11 sources through the real queue (fake ffmpeg spawn): all fulfilled; with an unbounded batch, the 11th is rejected.
    • An 11-source batch where source 3 fails: 10 fulfilled, 1 rejected; red if a rejection aborts the batch.
    • A multi-line ffmpeg stderr picks the last error-looking line, trimmed; red with first line, last line, or no trim.
    • A spawn failure is an FfmpegUnavailableError; an unusable ffmpeg gives one finding without the render sentence; red when reported per file.
    • dropFailedProxyEchoes keeps the warning plus unrelated errors and drops the 502s (including a percent-encoded my%20clip.mov) and notes for the failed file; red without the filter or without the decode.
    • runBrowserCheck carries a rejected proxy into runtimeFindings as a warning naming file and reason; red on main.
    • proxyTranscoder 41/41, checkBrowser 24/24, publishProxyBake 8/8, each 3 runs in a row. fallow audit --base origin/main is clean on the changed files.
  • Manual testing performed (real check runs on fixture projects):
    • HLG + PQ HEVC, ffmpeg with zscale hidden. Before: 0/2 ready, 2 failed, Runtime 0 errors, 0 warnings, Check passed. After: ⚠ media_proxy_failed: Could not make a browser-playable copy of media/hlg.mp4: HDR proxying requires ffmpeg zscale/tonemap filters (libzimg); ... for each file, check passes. check --json stdout stays valid JSON. A default render with the same ffmpeg completes.
    • 11 HEVC clips, cold cache. Before: 10/11 ready, 1 failed and ⚠ media_proxy_failed: ... media/c11.mp4: media proxy queue is full; retry shortly. After: 11/11 ready, 0 failed, no warning.
    • ffmpeg missing (ffprobe present), two HEVC clips: one ⚠ media_proxy_failed: Could not make browser-playable copies of media/h8_hvc1.mp4, media/h10.mp4: ffmpeg binary not found. Publish stops on them ....
    • ProRes clip with proxy encodes forced to fail. Before: 1 warning + 2 info + 4 ✗ http_error: 502 loading media/clip.mov, Check failed. After: one ⚠ media_proxy_failed: ... ffmpeg exited with code 1: <last stderr line>. ..., Check passed.
  • Documentation updated (if applicable)
  • Comments follow CONTRIBUTING.md "Comments"

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 2061 (base branch 2061), smooth 1529 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)

@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.

Approving at 6167fa43.

The batch fix has the right shape. resolveProxies runs min(running slots, N) workers that each pull the next index and wait for that copy, so a batch never touches the waiting queue and can't hit ProxyCapacityError.

  • Memory stays bounded, each index is taken once, and per-file dedupe still applies.
  • A per-file rejection stays a settled result.
  • The preview path and the other resolveProxy callers are unchanged. The new FfmpegUnavailableError extends ProxyTranscodeError, so existing instanceof handling still matches.

I checked the description's claim against the source. The defaults are 2 running + 8 waiting (proxyTranscoder.ts:43-44), and the 11th request gets exactly media proxy queue is full; retry shortly.

Tests run locally: proxyTranscoder 41/41, checkBrowser + publishProxyBake 32/32. 5 of 7 mutants were killed; the 2 survivors are covered below.

Should-fix (not blocking):

  1. stderrReason (proxyTranscoder.ts:403) splits on \n only. ffmpeg's progress lines end in \r, and the runner doesn't pass -nostats, so a failure mid-encode carries the whole frame=… \rframe=… \r[…] Error … run into the warning. The \rs also make terminal output overwrite itself. Splitting on /[\r\n]+/ and capping at ~160 chars fixes it, and the existing 41 tests still pass with that change.
  2. Nothing covers runBrowserCheck actually calling dropFailedProxyEchoes (checkBrowser.ts:242-245). Removing the call keeps every test green, and that call is what turns "check failed" into "one finding, check passes". A case where the fake page emits a 502 for a failed file's ?hf-proxy= URL would pin it.

Nits:

  • With ffmpeg missing, render can't succeed either, yet check now passes with a warning where the 502s used to fail it. Worth a line in the description if that's intended.
  • resolveProxies has no test for its 15-minute per-file wait cap. The publish test that covered it was removed here.

— Rames

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit bf7f6de Oct 11, 2026
81 checks passed
@miguel-heygen
miguel-heygen deleted the fix/vspeed-hevc-check branch October 11, 2026 02:14
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.

2 participants