@remotion/renderer: Render selected frames and ranges - #9888
Conversation
@remotion/renderer: Render selected frames@remotion/renderer: npx remotion render --frames supports individual frames
There was a problem hiding this comment.
Reviewed changes — adds a frames option to renderFrames() and the CLI for rendering arbitrary comma-separated frame selections as image sequences.
- Add
framesoption torenderFrames()— acceptsnumber[], validates/sorts, renders as image sequence with source frame numbers preserved in filenames - New
FrameSelectiontype — extendsFrameRangewith{type: 'frames'; frames: number[]}to represent comma-separated selections - CLI
--framescomma-parsing —--frames=0,42,69parses to aFrameSelectionshape, disables video output, and is gated out ofbenchmark - Validation —
validateSelectedFrames()enforces array-of-numbers, non-empty, finite, integer, non-negative, in-range, and no duplicates; plugs into both CLI option parsing and the renderer - Tests — unit tests for
validateSelectedFrames, CLI option parsing updates, integration test for CLI render path, and arenderFrames()integration test - Docs —
cli/render.mdxandrenderer/render-frames.mdxupdated withframesoption documentation
Important
The renderFrames() mutual-exclusion check for everyNthFrame will throw when called from the CLI with --frames=... and default --every-nth-frame=1, breaking the CLI render path. This needs a fix before merging.
🚨 renderFrames() throws on everyNthFrame !== undefined even for the default value 1, blocking the CLI render path
The CLI's renderVideoFlow always passes everyNthFrame as a number (typed as everyNthFrame: number in the function signature). In renderFrames(), the mutual-exclusion check at render-frames.ts:767 throws when everyNthFrame !== undefined — but everyNthFrame is never undefined from the CLI because it defaults to 1 before reaching renderVideoFlow.
The CLI check in render.tsx only blocks everyNthFrame !== 1, so the default value 1 passes through. The CLI then calls renderFrames({ frames: selectedFrames, everyNthFrame: 1, ... }), and renderFrames throws because everyNthFrame !== undefined is true.
The CLI integration test (rendering.test.ts:253-298 in the diff) would fail as written because the --frames=8,2,5 invocation would exit non-zero from the renderFrames throw.
Technical details
## Affected sites
- `packages/cli/src/render-flows/render.ts:627` — passes `everyNthFrame,` unconditionally to `renderFrames()`
- `packages/cli/src/render-flows/render.ts:170` — `everyNthFrame: number` type guarantees it is never `undefined`
- `packages/renderer/src/render-frames.ts:767-771` — throws when `everyNthFrame !== undefined`, treating even the default `1` as conflicting
- `packages/cli/src/render.tsx:137-141` — only blocks `everyNthFrame !== 1`, allowing the default through
## Required outcome
- The CLI render path must not throw when `--frames=0,5` is used without `--every-nth-frame`
- The `renderFrames()` API should still reject explicit `everyNthFrame` values that meaningfully conflict with `frames`
## Suggested approach
In `render-flows/render.ts`, omit `everyNthFrame` from the `renderFrames` call when `selectedFrames` is set:
```typescript
everyNthFrame: selectedFrames !== null ? undefined : everyNthFrame,
```
This avoids the renderer-level check entirely for the CLI path. The renderer's check can remain as-is for direct API callers, since API callers won't normally pass `everyNthFrame: 1` alongside `frames`.DeepSeek Pro (free via Pullfrog for OSS) (Claude Opus not used — the program covers this model; add its provider key to run your pick) | 𝕏
@remotion/renderer: npx remotion render --frames supports individual frames@remotion/renderer: Render selected frames and ranges

Summary
--framesselection syntax:--frames=0,30,60renders the specified frames as an image sequence--frames=0-99,150-199concatenates the selected ranges into one video--sequencerenders selected ranges as an image sequence with source-frame filenames--frames=0,30-59render a video unless--sequenceis setrenderFrames()andrenderMedia()calls to accept multiple ordered, non-overlapping frame rangescombineChunks()rendering single-range so their existing chunking and seamless-AAC behavior is unchangedTesting
bun run buildbun run stylecheckrenderMedia()multiple-range integration testCloses #9874
Closes #3158
Preview
renderFrames()renderMedia()renderMediaOnCloudrun()limitation