Skip to content

fix(cli): a re-encode of a media file over the cap is limited to fit it - #7376

Merged
miguel-heygen merged 3 commits into
mainfrom
dcloudpro/reencode-fits-cap
Nov 7, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
dcloudpro/reencode-fits-cap

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

What

hyperframes cloud render re-encodes a media file the asset API caps, at crf 18. crf bounds quality, not size, so a source over the 200 MiB cap can come out of a long encode still over it, and the render then fails with Media upload failed: ... is N MB after re-encoding at visually lossless quality, over the 200.0 MB a HeyGen asset can hold. The person waits through the whole encode and then gets the refusal.

Why

The encode's size depends only on the quality target, so any re-encode can end over the cap and be refused after the whole encode. The cap is known before the encode starts, so the encode can be bounded by it.

How

Every video re-encode (x264, x265 and VP9) adds -maxrate and -bufsize for 85% of the cap over the clip's length (the trimmed length when only played segments are kept), less the preset's audio bitrate. crf stays the quality target, so the limit only bites where crf alone would exceed the budget; a source under the cap is covered too, because a file in an efficient codec can leave an x264 crf 18 encode larger than itself. On a very long video the one-second limit can also clamp a short peak that would have fit on average. VP9 (the alpha preset) takes the same rate as -b:v, because its -b:v 0 constant-quality mode ignores -maxrate. Audio and images are unchanged. sizeLimitArgs in cloud/offload.ts owns the arithmetic.

The buffer is one second on purpose: an average-bitrate target (-b:v at 95% of the cap with a 2 s buffer) overshot the cap on a 10 s noise clip (217 MiB against 200 MiB), so the limit is a one-second VBV cap on top of crf, not an average-bitrate target.

Test plan

  • cloud/offload.sizeLimit.test.ts: the arithmetic (200 MiB over 100 s with 320 kbit/s audio gives 13940k) and no limit when audio fills the budget; a real ffmpeg run on an 8 s noise clip that crf 18 alone leaves over a 1 MiB cap and the limited encode fits; limitFor limits any video by its (or its cut's) length, leaves audio and images alone, and adds -b:v for VP9. fails without the fix: without sizeLimitArgs the file does not import, and with the limit removed from the args the second case is over the cap.

  • All 16 files under src/cloud/ pass (180 tests), on Linux with ffmpeg.

  • Real repro through the CLI source (bun, stub API, no key): a 10 s 1920x1080 noise mp4 of 513 MiB. On main the re-encode ran 131 s and failed with 302.4 MB after re-encoding ... over the 200.0 MB. On this branch the same project passes the size check (the run then stops at the stub API's upload, which has no upload URL), in 77 s.

  • Unit tests added/updated

  • Manual testing performed

  • Documentation updated (not applicable)

  • Comments follow CONTRIBUTING.md "Comments"

Not exercised: the HDR (x265) and alpha (VP9) presets against a real over-cap source, a source whose audio is long and large, and Windows and macOS ffmpeg builds.

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

Verdict: REQUEST CHANGES · 2 blocker · 1 important · 0 nits · head 01af84cf

  • blocker The build/typecheck fails at offload.ts:409. preset.args.includes("libvpx-vp9") gives TS2345 ("libvpx-vp9" is not assignable to never): args is a const tuple union, so includes narrows to never. This breaks the Typecheck, Build, CLI smoke (required) and Windows render jobs. Fix: compare presetName === "alphaVideo", or widen with (preset.args as readonly string[]).
  • blocker The new ffmpeg test offload.sizeLimit.test.ts "fits a noise clip that crf alone leaves over the cap" times out at 20 s on CI, all 4 tries (Flake gate). Fix: shrink the clip (shorter or smaller, with a cap to match) or give it an explicit timeout.
  • blocker (gates) Fallow audit: limitFor CRAP 31.6 > 30. Comment ratchet: offload.ts comment share went from 5.4% to 5.6% (+5 lines).
  • important offload.ts:398: the limit applies only when the source itself is over the cap. A source under the cap in an efficient codec (for example 180 MiB HEVC or AV1, re-encoded to x264 crf 18) can still come out over 200 MiB and fail after the full encode, the same failure the PR targets. Consider a fallback for that case, or say in the body that it stays out of scope.
Scope

Read the diff and the ffmpegReencode/uploadPlanned call sites. The reuse cache is written only after a successful upload, so no over-cap output is cached under the unchanged key. The "fails without the fix" line is present.

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

Verdict: APPROVE — 3 resolved, 0 open, 0 new · 01af84cf → 19d1c7d9. Typecheck, flake gate (60 s timeout, 8 s clip), Fallow and comment ratchet all green; under-cap sources are now limited too. The trade-off that a very long video which used to fit can have its peaks clamped is stated in the body and accepted as a design call.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 57a7533 Nov 7, 2026
116 of 117 checks passed
@miguel-heygen
miguel-heygen deleted the dcloudpro/reencode-fits-cap branch November 7, 2026 20:35
@miguel-heygen miguel-heygen mentioned this pull request Nov 7, 2026
3 of 5 tasks
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