build¶
Assemble a reel from a storyboard: render each card to a frame, crossfade them
into a 1080×1920 H.264 video, and (unless --silent) mix the narration over the
music bed. Rendering shells out to ffmpeg behind the Renderer interface.
Usage¶
# Workspace build (the authoring loop): --workspace is a slug under the reel root
keryx reel build --workspace my-post
# Render without audio (cards use their storyboard `dur`)
keryx reel build --workspace my-post --silent
# Standalone build from a loose storyboard file
keryx reel build --storyboard board.json --out promo.mp4
Flags¶
| Flag | Description |
|---|---|
--workspace |
Reel workspace slug under the reel root (reels/<slug>), the same slug used by reel new and storyboard draft. The storyboard, VO takes, music bed, and cover are read from that workspace. |
--storyboard |
A standalone storyboard.json path, used as-is. Overrides the workspace board when both are given. |
--theme |
Reel theme keyword (palette, fonts, music gain). Defaults to the configured reel default. |
--out |
Output .mp4 path. Defaults to reels/<slug>/reel-<slug>.mp4 for a workspace build, or reel.mp4 for a standalone build. |
--cover |
Cover image path. Defaults to reels/<slug>/cover.png if present. |
--aspect |
Frame aspect as W:H. Defaults to the workspace's own aspect, then config reel.aspect, then 9:16. Rejected beyond 3:1 in either direction. |
--silent |
Render video only — no narration, no music. Each card must carry a dur. |
Media hydrate¶
With an object store configured (spec 0039), reel build first pulls any media
that media.lock pins but is missing locally — a fresh CI clone renders
without an explicit keryx media pull. Local edits are never overwritten;
unconfigured storage skips the step.
Timing¶
- With VO (promoted
vo/NN.mp3clips exist, not--silent): each clip is bound to its own card by line number — a card with a promoted clip gets a duration derived from the clip length plus lead and tail; cards crossfade. The music bed is mixed in under the VO at the theme's gain with an end fade. - Mixed boards (some cards VO'd, some not — the normal studio mid-authoring
state): VO-less cards fall back to their storyboard
dur, rendered silent. A card with neither VO nor aduris an error naming the card. - Silent / no VO: each card uses its storyboard
dur. A card with noduris an error in this mode.
When narration outlasts the reel¶
The reel's length is the video timeline — the cards laid end to end, less one
crossfade per join. The mix is assembled separately: each clip is delayed to its own
card's start and runs for its own length. Those two numbers can cross, and when they
do reel build refuses before any render begins:
mix ends at 130.973s, 1.200s past the 129.773s timeline — a card's padding is
trimmed shorter than its own narration
A card's duration is its narration plus lead and tail, so the usual cause is
padding trimmed to nothing on the last card — reasonable when a hand-recorded read
carries its own pauses, but it leaves the narration with nowhere to finish. The other
cause is a card carrying an explicit dur shorter than the clip it also has.
The check is refused rather than patched over on purpose. Left to a render backend the
outcome depends on the engine: one that enforces its output bound cuts the narration
short, one that does not renders long and says nothing. Neither is what the board asked
for, and only a refusal names which card to change. Give the card back its tail (or
raise its dur), or re-record the line shorter.
Progress: when does the percentage appear?¶
On an interactive terminal, reel build shows a live rendering… NN% line while the
reel assembles, measured against the reel's total duration. It is written to stderr
(so stdout and --output json stay clean) and cleared when the render finishes; piped
or non-TTY runs show nothing at all.
Both render backends stream progress — ffmpeg parses its own -progress output, and
afmpeg reads the NDJSON progress records the ffmpeg-wasi engine writes. A negative
percentage means the backend cannot determine completion, which is deliberately
distinct from 0%.
Engine advisories on a successful render¶
A render can be degraded and green at once: the engine reports a codec falling back,
timestamps being reconstructed, frames dropped or an option ignored, and still exits
zero. When the afmpeg backend's engine writes anything on a run that succeeded,
reel build logs it:
At info rather than debug, because these are the lines that explain a file which
decodes cleanly but is not what was asked for, and having to re-run under --debug to
see them means the reel has already shipped. Nothing is logged when the engine was
quiet, or for a backend that has no advisories to report.
Reproducibility¶
After rendering, reel build reports how many selected assets are locked (approved)
vs merely selected. When every selected asset is locked, the reel is reproducible from
committed inputs — see reel lock. The counts are also in the
--output json payload (selected, locked).
See the Testing page for the BDD scenario that exercises a silent workspace render end to end.