Explainer Video — interview-driven e2e production
Turn a rough idea, script, or blog post into a finished narrated motion-graphics
video. The edit is text: because every overlay is cued to a transcript word,
the VO is a swappable input and the whole video is re-renderable from source.
Pipeline shape
script.md ──→ VO audio (TTS or recorded) ──→ Whisper word timestamps
↓
shared components + anim.tsx ──→ Remotion scenes (React)
↓
cue sheet times every overlay to the spoken word
↓
headless render ──→ out/final.mp4
The expensive half is graphics, not editing. Budget accordingly.
Phase 0 — Interview (mandatory, /ideate style)
Batch 3–4 questions per AskUserQuestion call. Cover these areas before
touching anything; target ~80% confidence:
- Audience & framing — who is this for, and what one scene matters most
to them? Every video has one scene the audience actually came for — find
it, name it in the plan, and give it the most visual care and room to
breathe.
- Arc & scenes — how many scenes, what's the one-line idea of each?
Target length? (~140 wpm spoken; TTS reads ~10% faster than estimate.)
- VO route — A: user records (~20 min, authentic) or B: ElevenLabs
TTS (instant, re-generatable while the script is still moving).
Recommend B for the first render, swap A in later — the graphics don't care.
If B: confirm
ELEVENLABS_API_KEY, then audition the voice before
committing: generate one script line in 2–3 candidate voices
(vo/samples/*.mp3), have the user afplay them, and pick via
AskUserQuestion. A voice change after scenes are built forces a full
re-cue of every scene (see Iterating below), so this cheap step earns
its round-trip.
- Design system source — a product screenshot to derive from beats an
invented look every time. Ask for one. Extract: palette, type rules (mono
for numerics is a strong default), signature components worth rebuilding
as animatable React.
- Logistics — repo location, resolution (default 3840×2160 @ 30fps),
deadline pressure. If the day is short, agree on a fast path now: which
scene subset is a coherent standalone cut?
Write the answers into production.md in the repo (scene briefs + design
system + fast path), and the narration into script.md. These two files are
the contract every downstream agent reads.
Phase 1 — Setup (hard-won gotchas, do not rediscover)
- Scaffold:
npm init -y, then npm i remotion @remotion/cli react react-dom
and npm i -D typescript@5 @types/react @types/react-dom.
- Pin
typescript@5. TypeScript 7 (the Go compiler) drops ts.sys,
which @remotion/bundler needs; the failure is an opaque
Cannot read properties of undefined (reading 'readFile') in esbuild-loader.
- Do not set
"type": "module" in package.json.
- Whisper:
brew install whisper-cpp, model via
curl -L -o work/models/ggml-base.en.bin https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin
(~142MB; base.en is plenty for clean VO). The old pip whisper binary on
this machine is dead (stale python3.9 shebang) — don't use it.
- Secrets: user exports live in
~/.zshrc, which non-interactive shells skip.
Snapshot with zsh -lic 'printf "ELEVENLABS_API_KEY=%s\n" "$ELEVENLABS_API_KEY" > .env'
(note -i), chmod 600, gitignore it.
- Renders and installs may need the sandbox disabled (TLS/cert + Chrome).
Remotion downloads its own headless Chrome on first render.
- Layout:
vo/ (audio + per-scene .txt), work/transcripts/, work/models/,
public/vo/ (copies for staticFile), src/scenes/, src/components/,
out/ (gitignored), refs/ (design reference screenshots).
Phase 2 — VO + word timestamps
- One
.txt per scene from the script's narration (blockquotes only, not
stage directions). Spell out tickers/initialisms phonetically for TTS
("P-S-B-T", "L-N-D") — the graphics show the real spelling.
- Sweep the script for heteronyms before generating — words whose
pronunciation depends on part of speech: invalid (adjective) vs invalid
(noun), live, record, present, content, object, produce. TTS
guesses wrong often enough to matter; reword the sentence (e.g. "render it
invalid" → "invalidate it") rather than fighting the model.
- Generate via the ElevenLabs API in a re-runnable
vo/generate.sh: for each
scene, jq -Rs '{text: ., model_id: "eleven_multilingual_v2", voice_settings: {stability: 0.5, similarity_boost: 0.75, style: 0.25}}' < vo/sceneN.txt
POSTed to /v1/text-to-speech/<VOICE_ID>?output_format=mp3_44100_128 with
the xi-api-key header. Voice "Brian" (nPczCjzI2devNBz1zQrb) is a good
narration default.
- Report total runtime vs target immediately — this is the moment to trim.
- Transcribe:
ffmpeg -i vo/sceneN.mp3 -ar 16000 -ac 1 work/sceneN.wav then
whisper-cli -m work/models/ggml-base.en.bin -f work/sceneN.wav -ml 1 -sow -oj -of work/transcripts/sceneN.
-ml 1 -sow gives one word per segment: .transcription[].offsets.from/to
in milliseconds. Frame = ms / 1000 * fps.
Phase 3 — Design system + shared components (single writer)
Build these yourself before fanning out scenes, so parallel agents never
fight over shared files:
src/anim.tsx — all global timing knobs (reveal/stagger/overlayIn/
overlayOut/easing), palette, fonts. "Make it snappier" must be a one-line
change. Eased motion only, never bouncy.
src/components/<system>.tsx — the signature components rebuilt from the
reference screenshot as animatable React (props expose the animatable bits,
e.g. a bar's fill: 0..1). Reuse components across scenes — one asset-bar
component can serve both a wallet view and a set of supply counters — so
the video reads as one product.
- Stubs for every scene +
Root.tsx + FinalEdit.tsx, typecheck, and render
one smoke still — prove the whole render path before the expensive phase.
Phase 4 — Scenes in parallel (Workflow fan-out)
One agent per scene (parallel, one phase). Each agent:
- owns exactly one file,
src/scenes/SceneN.tsx, keeps the propless
React.FC export;
- keeps every beat frame in one cue table (a single const object at the
top of the file, one named entry per narration word/phrase) and derives all
other timing (fade-outs, sequence durations) from those entries — never as
free-floating literals. This is what makes a later VO/voice swap a
mechanical retime instead of an archaeology dig;
- reads
production.md, script.md, its transcript JSON, anim.tsx,
the shared components;
- gets its frame budget (VO duration + ~0.9s tail) and a concrete visual
brief in the prompt — beats named against narration phrases;
- runs a mandatory verify loop, ≥2 iterations:
tsc --noEmit, then
npx remotion still SceneN out/review/sceneN-f<F>.png --frame <F> at 4–6
beat frames, Read the PNGs, critique, fix;
- returns structured output: beats + frame numbers, decisions, still paths.
After the fan-out: full-project tsc, personally Read a few stills from each
scene (cross-scene consistency is the orchestrator's job, not the agents'),
then commit.
Phase 5 — Cue sheet + render + QA
FinalEdit.tsx: <Series> of scenes, each wrapped with
<Audio src={staticFile('vo/sceneN.mp3')} />; boundaries from real
(ffprobe) durations + the tail pad, mirrored in Root.tsx.
- Render in the background:
npx remotion render FinalEdit out/final.mp4 --codec h264. ~4.7k 4K frames ≈ 10–25 min on an M-series; progress doesn't
stream through pipes, check the process not the log.
- QA from the mp4 itself, not the review stills:
ffprobe (duration, both streams present);
ffmpeg -ss <t> -i out/final.mp4 -frames:v 1 at ~10 timestamps including
scene boundaries, Read them; -af volumedetect on a slice (expect max
around −6dB, no clipping).
- Deliver the path + a hero frame (Substrate attachments are image-only —
don't try to attach the mp4).
Iterating after v1
Always git tag v1 (v2, …) before starting a revision — stakeholders ask
for the old cut back more often than you'd think.
- Voice or narrator swap — this re-times every word, not just the
changed scenes. Full pipeline re-run (VO → transcripts → durations), then
fan out one re-cue agent per scene with a hard constraint in the
prompt: "the visual design is APPROVED — do not redesign, restyle, or
restructure; only retime beat constants to the new transcript." Scenes
built with a proper cue table retime in minutes. Have each agent also
check its tail behavior: when the new slot runs longer, a retimed
exit can leave seconds of dead black — hold the final composition to the
cut instead.
- Stakeholder feedback round — treat it as a mini Phase 0:
- Research before asking. If the new direction needs facts or stats,
gather verifiable ones first (web search) so the clarifying questions
present real options, not placeholders.
- Ask with previews. Batch the questions via AskUserQuestion and give
concrete ASCII mockups of the competing visual treatments — a picked
preview doubles as the scene brief.
- Scope each scene as re-cue (design approved) vs rebuild (new story),
and run them as one parallel workflow with per-scene prompts.
- On-screen claims rule: sizzle stats must be independently verifiable
and carry their source in the frame (e.g. "River Lightning Report, Feb
2026"). Round numbers age; sourced numbers survive review.
- Watch runtime creep: new lines + per-scene pads add up — recheck total
against the target right after VO regen (TTS reads ~10% faster than the
140wpm estimate, but feedback rounds usually add words).
- "Make it snappier / calmer": edit
anim.tsx only.
- Script wording changes in one scene: regenerate only that scene's VO +
transcript, re-cue all of that scene's beats (the whole audio file is new,
so every offset in it moved — but only in that scene), update its duration.
Never re-run the full generate loop for a one-scene change: TTS output
is non-deterministic, so regenerating untouched scenes silently invalidates
their cues and costs API credits for nothing. Keep the generate script's
loop current, but do targeted regens inline.
- Adding a closing scene (CTA, launch plan, next steps) is the cheapest
structural edit: no existing scene re-times — new VO + transcript, a new
scene file, one more Series entry in the cue sheet, done. If the video
needs a call to action, put the docs/product URL on screen in the final
resting frame — it's the last thing the audience sees.
1---2name: explainer-video3description: Produce a narrated explainer video end to end — interview-driven, like /ideate but the output is a rendered mp4. Pipeline: script → TTS or recorded VO → Whisper word timestamps → Remotion scenes cued to the spoken word → headless 4K render. Use when asked to "make a video", "produce an explainer", or "turn this script/post into a video".4---56# Explainer Video — interview-driven e2e production78Turn a rough idea, script, or blog post into a finished narrated motion-graphics9video. The edit is *text*: because every overlay is cued to a transcript word,10the VO is a swappable input and the whole video is re-renderable from source.1112## Pipeline shape1314```15script.md ──→ VO audio (TTS or recorded) ──→ Whisper word timestamps16 ↓17 shared components + anim.tsx ──→ Remotion scenes (React)18 ↓19 cue sheet times every overlay to the spoken word20 ↓21 headless render ──→ out/final.mp422```2324The expensive half is **graphics**, not editing. Budget accordingly.2526## Phase 0 — Interview (mandatory, /ideate style)2728Batch 3–4 questions per `AskUserQuestion` call. Cover these areas before29touching anything; target ~80% confidence:30311. **Audience & framing** — who is this for, and what one scene matters most32 to them? Every video has one scene the audience actually came for — find33 it, name it in the plan, and give it the most visual care and room to34 breathe.352. **Arc & scenes** — how many scenes, what's the one-line idea of each?36 Target length? (~140 wpm spoken; TTS reads ~10% faster than estimate.)373. **VO route** — **A:** user records (~20 min, authentic) or **B:** ElevenLabs38 TTS (instant, re-generatable while the script is still moving).39 Recommend B for the first render, swap A in later — the graphics don't care.40 If B: confirm `ELEVENLABS_API_KEY`, then **audition the voice before41 committing**: generate one script line in 2–3 candidate voices42 (`vo/samples/*.mp3`), have the user `afplay` them, and pick via43 AskUserQuestion. A voice change after scenes are built forces a full44 re-cue of every scene (see Iterating below), so this cheap step earns45 its round-trip.464. **Design system source** — a product screenshot to derive from beats an47 invented look every time. Ask for one. Extract: palette, type rules (mono48 for numerics is a strong default), signature components worth rebuilding49 as animatable React.505. **Logistics** — repo location, resolution (default 3840×2160 @ 30fps),51 deadline pressure. If the day is short, agree on a **fast path** now: which52 scene subset is a coherent standalone cut?5354Write the answers into `production.md` in the repo (scene briefs + design55system + fast path), and the narration into `script.md`. These two files are56the contract every downstream agent reads.5758## Phase 1 — Setup (hard-won gotchas, do not rediscover)5960- Scaffold: `npm init -y`, then `npm i remotion @remotion/cli react react-dom`61 and `npm i -D typescript@5 @types/react @types/react-dom`.62 - **Pin `typescript@5`.** TypeScript 7 (the Go compiler) drops `ts.sys`,63 which `@remotion/bundler` needs; the failure is an opaque64 `Cannot read properties of undefined (reading 'readFile')` in esbuild-loader.65 - Do **not** set `"type": "module"` in package.json.66- Whisper: `brew install whisper-cpp`, model via67 `curl -L -o work/models/ggml-base.en.bin https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin`68 (~142MB; base.en is plenty for clean VO). The old pip `whisper` binary on69 this machine is dead (stale python3.9 shebang) — don't use it.70- Secrets: user exports live in `~/.zshrc`, which non-interactive shells skip.71 Snapshot with `zsh -lic 'printf "ELEVENLABS_API_KEY=%s\n" "$ELEVENLABS_API_KEY" > .env'`72 (note `-i`), `chmod 600`, gitignore it.73- Renders and installs may need the sandbox disabled (TLS/cert + Chrome).74 Remotion downloads its own headless Chrome on first render.75- Layout: `vo/` (audio + per-scene .txt), `work/transcripts/`, `work/models/`,76 `public/vo/` (copies for `staticFile`), `src/scenes/`, `src/components/`,77 `out/` (gitignored), `refs/` (design reference screenshots).7879## Phase 2 — VO + word timestamps8081- One `.txt` per scene from the script's narration (blockquotes only, not82 stage directions). **Spell out tickers/initialisms phonetically** for TTS83 ("P-S-B-T", "L-N-D") — the graphics show the real spelling.84- **Sweep the script for heteronyms before generating** — words whose85 pronunciation depends on part of speech: *invalid* (adjective) vs *invalid*86 (noun), *live*, *record*, *present*, *content*, *object*, *produce*. TTS87 guesses wrong often enough to matter; reword the sentence (e.g. "render it88 invalid" → "invalidate it") rather than fighting the model.89- Generate via the ElevenLabs API in a re-runnable `vo/generate.sh`: for each90 scene, `jq -Rs '{text: ., model_id: "eleven_multilingual_v2", voice_settings:91 {stability: 0.5, similarity_boost: 0.75, style: 0.25}}' < vo/sceneN.txt`92 POSTed to `/v1/text-to-speech/<VOICE_ID>?output_format=mp3_44100_128` with93 the `xi-api-key` header. Voice "Brian" (`nPczCjzI2devNBz1zQrb`) is a good94 narration default.95- Report total runtime vs target immediately — this is the moment to trim.96- Transcribe:97 `ffmpeg -i vo/sceneN.mp3 -ar 16000 -ac 1 work/sceneN.wav` then98 `whisper-cli -m work/models/ggml-base.en.bin -f work/sceneN.wav -ml 1 -sow -oj -of work/transcripts/sceneN`.99 `-ml 1 -sow` gives one word per segment: `.transcription[].offsets.from/to`100 in **milliseconds**. Frame = `ms / 1000 * fps`.101102## Phase 3 — Design system + shared components (single writer)103104Build these yourself *before* fanning out scenes, so parallel agents never105fight over shared files:106107- `src/anim.tsx` — all global timing knobs (reveal/stagger/overlayIn/108 overlayOut/easing), palette, fonts. "Make it snappier" must be a one-line109 change. Eased motion only, never bouncy.110- `src/components/<system>.tsx` — the signature components rebuilt from the111 reference screenshot as animatable React (props expose the animatable bits,112 e.g. a bar's `fill: 0..1`). Reuse components across scenes — one asset-bar113 component can serve both a wallet view and a set of supply counters — so114 the video reads as one product.115- Stubs for every scene + `Root.tsx` + `FinalEdit.tsx`, typecheck, and render116 one smoke still — prove the whole render path before the expensive phase.117118## Phase 4 — Scenes in parallel (Workflow fan-out)119120One agent per scene (`parallel`, one phase). Each agent:121122- owns exactly one file, `src/scenes/SceneN.tsx`, keeps the propless123 `React.FC` export;124- keeps **every beat frame in one cue table** (a single const object at the125 top of the file, one named entry per narration word/phrase) and derives all126 other timing (fade-outs, sequence durations) from those entries — never as127 free-floating literals. This is what makes a later VO/voice swap a128 mechanical retime instead of an archaeology dig;129- reads `production.md`, `script.md`, its transcript JSON, `anim.tsx`,130 the shared components;131- gets its frame budget (VO duration + ~0.9s tail) and a concrete visual132 brief *in the prompt* — beats named against narration phrases;133- runs a mandatory verify loop, ≥2 iterations: `tsc --noEmit`, then134 `npx remotion still SceneN out/review/sceneN-f<F>.png --frame <F>` at 4–6135 beat frames, Read the PNGs, critique, fix;136- returns structured output: beats + frame numbers, decisions, still paths.137138After the fan-out: full-project `tsc`, personally Read a few stills from each139scene (cross-scene consistency is the orchestrator's job, not the agents'),140then commit.141142## Phase 5 — Cue sheet + render + QA143144- `FinalEdit.tsx`: `<Series>` of scenes, each wrapped with145 `<Audio src={staticFile('vo/sceneN.mp3')} />`; boundaries from real146 (ffprobe) durations + the tail pad, mirrored in `Root.tsx`.147- Render in the background: `npx remotion render FinalEdit out/final.mp4148 --codec h264`. ~4.7k 4K frames ≈ 10–25 min on an M-series; progress doesn't149 stream through pipes, check the process not the log.150- QA **from the mp4 itself**, not the review stills:151 `ffprobe` (duration, both streams present);152 `ffmpeg -ss <t> -i out/final.mp4 -frames:v 1` at ~10 timestamps including153 scene boundaries, Read them; `-af volumedetect` on a slice (expect max154 around −6dB, no clipping).155- Deliver the path + a hero frame (Substrate attachments are image-only —156 don't try to attach the mp4).157158## Iterating after v1159160**Always `git tag v1` (v2, …) before starting a revision** — stakeholders ask161for the old cut back more often than you'd think.162163- **Voice or narrator swap** — this re-times *every word*, not just the164 changed scenes. Full pipeline re-run (VO → transcripts → durations), then165 fan out one **re-cue agent per scene** with a hard constraint in the166 prompt: *"the visual design is APPROVED — do not redesign, restyle, or167 restructure; only retime beat constants to the new transcript."* Scenes168 built with a proper cue table retime in minutes. Have each agent also169 check its **tail behavior**: when the new slot runs longer, a retimed170 exit can leave seconds of dead black — hold the final composition to the171 cut instead.172- **Stakeholder feedback round** — treat it as a mini Phase 0:173 1. *Research before asking.* If the new direction needs facts or stats,174 gather verifiable ones first (web search) so the clarifying questions175 present real options, not placeholders.176 2. *Ask with previews.* Batch the questions via AskUserQuestion and give177 concrete ASCII mockups of the competing visual treatments — a picked178 preview doubles as the scene brief.179 3. *Scope each scene* as re-cue (design approved) vs rebuild (new story),180 and run them as one parallel workflow with per-scene prompts.181- **On-screen claims rule**: sizzle stats must be independently verifiable182 and carry their source in the frame (e.g. "River Lightning Report, Feb183 2026"). Round numbers age; sourced numbers survive review.184- **Watch runtime creep**: new lines + per-scene pads add up — recheck total185 against the target right after VO regen (TTS reads ~10% faster than the186 140wpm estimate, but feedback rounds usually *add* words).187- "Make it snappier / calmer": edit `anim.tsx` only.188- Script wording changes in one scene: regenerate **only that scene's** VO +189 transcript, re-cue all of that scene's beats (the whole audio file is new,190 so every offset in it moved — but only in that scene), update its duration.191 **Never re-run the full generate loop for a one-scene change**: TTS output192 is non-deterministic, so regenerating untouched scenes silently invalidates193 their cues and costs API credits for nothing. Keep the generate script's194 loop current, but do targeted regens inline.195- **Adding a closing scene** (CTA, launch plan, next steps) is the cheapest196 structural edit: no existing scene re-times — new VO + transcript, a new197 scene file, one more Series entry in the cue sheet, done. If the video198 needs a call to action, put the docs/product URL on screen in the final199 resting frame — it's the last thing the audience sees.