Skip to content

fix(cli): cloud render --json reports re-encode progress and times out a stalled cache check - #7755

Open
miguel-heygen wants to merge 1 commit into
mainfrom
fix/cloud-offload-progress
Open

miguel-heygen wants to merge 1 commit into
mainfrom
fix/cloud-offload-progress

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

  • hyperframes cloud render --json now writes a reencode_progress line on stderr while it re-encodes a video before upload, in the same shape as the existing upload_progress lines: {"event":"reencode_progress","file":"take.mkv","percent":42,"done_seconds":12.6,"total_seconds":30}. One line per whole percent, or one per second when the clip's length is unknown (percent and total_seconds are then null).
  • Checking whether a cached upload is still served (a HEAD on its asset URL) now gives up after 10 s and uploads the file again, instead of waiting forever.

Why

A big or unaccepted video is re-encoded with libx264 -preset slow -crf 18 before upload, which takes minutes on a long 4K take. Until it finished the command printed nothing, so a --json caller (an agent, Desktop) could not tell a long encode from a dead one. And the cache check's HEAD had no timeout, so an asset host that accepted the connection but never answered held the whole render.

Related work

None.

How

  • ffmpeg's -progress pipe:1 reader that Studio's preview copies already use moves to studio-server/src/helpers/ffmpegProgress.ts (exported as @hyperframes/studio-server/ffmpeg-progress), and both the preview-copy encoder and the cloud re-encode use it, so there is one parser of those lines.
  • The re-encode runs through spawn with -progress pipe:1 -nostats instead of execFile; on failure it reports ffmpeg's own stderr. Its total length comes from the same place the size limit already reads it (the played segments, else the probed duration).
  • stillServed aborts its HEAD after CACHE_CHECK_TIMEOUT_MS (10 s). A timed-out check counts as "not served", so the cost is one extra upload, never a wrong cached URL.
  • The encoder settings are unchanged.

Test plan

  • offload.progress.test.ts (real ffmpeg): a 2 s clip under a name the asset API refuses is re-encoded, and progress arrives with doneSeconds above 1.5 against a totalSeconds of 2.
  • offload.test.ts: a cached asset whose HEAD never answers is uploaded again once the timeout passes (fake timers).
  • render.test.ts: reencodeProgressLines writes one line per whole percent, and one per second when the length is unknown.
  • fails without the fix: with the re-encode's progress handler a no-op, the progress test hears nothing (expected 0 to be greater than 0); without the HEAD timeout, the cache test hangs until its 20 s limit.
  • offload.test.ts, offload.progress.test.ts, offload.undecodableTrack.test.ts (real ffmpeg re-encode end to end), offload.sizeLimit.test.ts, render.test.ts: 75 passed. studio-server proxyTranscoder tests: 80 passed. tsc --noEmit clean in cli and studio-server.
  • Unit tests added/updated
  • Manual testing performed
  • Comments follow CONTRIBUTING.md "Comments"

@github-actions

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 79 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 (8)

Unstable (1)

  • seqplainkeys-none-pct-r0-nested-z100: tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@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 · 0 important · 1 nit · 0fc9cd61

The code is sound, and both "fails without the fix" claims reproduce locally: the progress test fails with expected 0 to be greater than 0, and the cache test times out at 20 s. Two required checks are red on this head, though:

  • blocker: Build › verify:packed-manifests fails because @hyperframes/studio-server packed size 170334 bytes is over its budget of 168500 by more than 1%. Either shrink what the new entry adds to the tarball, or raise scripts/package-size-budgets.json and give the reason in the body.
  • blocker: Lint and Preflight fail with studio-server/package.json is out of sync. Details are inline. The Windows lanes and preview-regression are red only because Preflight failed.
Scope

Read the whole diff (9 files). Locally, the new and changed tests pass, along with studio-server's 80 proxyTranscoder tests. Five tests in render.test.ts --dry-run and offload.undecodableTrack fail here because of the environment, and they fail the same way on main.

— Claude

"import": "./src/helpers/outsideFile.ts",
"types": "./src/helpers/outsideFile.ts"
},
"./ffmpeg-progress": {

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.

blocker: bun run sync:package-subpaths builds these exports from packages/studio-server/package-subpaths.json, so it deletes this hand-written entry, and Lint/Preflight fail with "package.json is out of sync". Add "./ffmpeg-progress": { "source": "./src/helpers/ffmpegProgress.ts", "runtime": "./dist/helpers/ffmpegProgress.js", "types": "./dist/helpers/ffmpegProgress.d.ts" } next to ./outside-file in that file, then rerun the sync.

const probe = preset.contentType.startsWith("video/") ? await probeVideo(sourcePath) : undefined;
const decoder = probe ? alphaDecoderArgs(probe) : [];
const color = presetName === "hdrVideo" && probe ? hdrColorArgs(probe) : [];
const totalSeconds = onProgress ? await outputSeconds(sourcePath, segments) : null;

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.

nit: uploadPlanned always passes a callback here, so onProgress is never undefined. Every re-encode of an untrimmed clip therefore runs an extra ffprobe, even without --json. To skip it when nobody is listening, pass the callback only when ctx.opts.onReencodeProgress is set.

This branch has not been deployed

No deployments
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