Skip to content

feat(cli): render --start and --end render only that span of the film as its own MP4 - #6834

Merged
miguel-heygen merged 4 commits into
mainfrom
feat/render-start-end
Oct 30, 2026
Merged

miguel-heygen merged 4 commits into
mainfrom
feat/render-start-end

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

hyperframes render --start <s> --end <s> renders only that span of the film as an MP4 that starts at 0, with the audio of the span. Either flag alone works (--start to the end, --end from 0).

Why

render had no time range, so a caller that wants a few seconds of a film with sub-compositions (Desktop's video check) had to render the whole film.

How

  • No in-process capture or encode path takes a frame range, so the span reuses the one that does: plan(), then renderChunk() with a frameRange for each chunk the span touches, then the mp4 stitch (renderSpan in packages/producer/src/services/distributed/renderSpan.ts, exported from the producer). Frames are captured at their absolute film times, so sub-composition timing is the same as in a full render; each chunk part carries the audio packets it owns.
  • The CLI parses --start/--end in render/plan.ts and refuses what the chunked path cannot render, naming the limit: not MP4, an fps outside 24/25/30/60 or a rational one, --hdr. --docker forwards the flags.
  • The range rounds to whole frames (round(seconds * fps)), clamps to the film's last frame, and a range with no frame is an error that says how long the film is.

Test plan

fails without the fix: renders the frames of the span the way a full render renders them, sub-composition included in renderSpan.test.ts (a real render of a film with a sub-composition and a backdrop that brightens each frame: every frame of the span is within 1.5 grey levels of the same frame of a full render, and a span one frame off its place misses by about 20; with the start and end shifted by one frame it fails with Expected: < 1.5, Received: 20.15). spanFrameRange unit tests cover the frame arithmetic and the empty range; plan.test.ts covers parsing and each refusal.

What I measured

  • On a Linux box: the three spanFrameRange tests and the real-render comparison pass; the CLI plan.test.ts file passes (42 tests); tsc --noEmit for the producer is clean.

What I did NOT execute

  • A span of a film with a long source video, HEVC or a 4K source; the fixture has none.

  • --docker --start, the packaged Desktop app, and lint:quick (lefthook is not installed on the test box).

  • tsc for the CLI package: it stops on a missing @hyperframes/inspector/live build in that checkout, none of it in files this change touches.

  • Plan-time work: the whole film is still planned (probe and video extraction) before the span's chunks render, so a span of a long film still pays for the plan.

  • Unit tests added/updated

  • Manual testing performed

  • Documentation updated (if applicable)

  • Comments follow CONTRIBUTING.md "Comments"

Size

About 330 changed lines across the producer and the CLI.

… as its own MP4

The span reuses the chunked render path, so frames are captured at their absolute times and sub-composition timing matches a full render.
@mintlify

mintlify Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 30, 2026, 1:36 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

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

Unstable (1)

  • seqab-none-center-r0-root-z100: tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.03, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.03, 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 · 1 important · 0 nits

CI is red on this head: Producer: unit tests fails the named real-render test (no ffmpeg in that lane), so the fails without the fix: proof never runs in CI. The span path also ignores Ctrl-C/SIGTERM once planning ends. Details ↓ inline.

Scope

Read all 9 files. Checked how cancellation is wired through plan/renderChunk, the distributedConfigFromRequest defaults, and the chunk-audio sub-range mux. Did not run the render locally.

Comment thread packages/cli/src/commands/render.ts Resolved
…t runs in the integration lane

renderSpan checks the abort signal before each chunk part and before the stitch. The real-render tests move to their own file listed in the integration lane.
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Pushed 3470a347.

  • CI lane: the real-render tests are now in renderSpan.integration.test.ts, listed in scripts/integration-tests.json next to renderChunk.test.ts; renderSpan.test.ts keeps only the spanFrameRange cases.
  • Cancellation: renderSpan checks the abort signal before each chunk part and before the stitch and throws RenderCancelledError. New test aborts once the first part exists and expects a rejection and no output file; with the checks removed it fails (Received promise that resolved).
  • Fonts: renderSpan now plans with rejectOnSystemFonts: false and failClosedFontFetch: false, since the span captures in the same browser as a full render. I could not reproduce the failure the comment describes: with the old config, plan() accepted system-ui, -apple-system, serif, sans-serif and data-font-family declarations on a Linux box, so there is no red test for this part, and it stays as the guard-off default only. If you have a film that fails, I will add it as a test.
  • Placeholder size: commented in render.ts; plan() uses the film's own size.

@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 · 2d0449d → 3470a34. Both real-render tests pass in Producer: integration tests, and all workflows are green.

Nit: throwIfAborted was inserted between renderSpan's doc comment and renderSpan, so the /** Renders … */ line now documents the helper.

@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 — 1 resolved, 0 open, 0 new · 3470a34 → 4e555d5. Only change: the doc comment moved back above renderSpan. All workflows are green.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 5a164fb Oct 30, 2026
93 checks passed
@miguel-heygen
miguel-heygen deleted the feat/render-start-end branch October 30, 2026 15:02
@heygengenesis heygengenesis Bot mentioned this pull request Oct 30, 2026
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