Getting started? Ask your coding agent (Claude Code) to read this
README.mdend-to-end and set up the project for you. It will install the Python and Node dependencies, guide you through filling in.env, and verify the pipeline is ready to run.
OVG is a Claude-Code-driven pipeline that turns a topic, brief, or raw narration into an animated Remotion video you can preview locally in your browser. Five specialized sub-agents β script, direction, audio, assets, code β collaborate under a Claude Code orchestrator that reviews each step's output, loops back to fix weak scenes, and only moves forward when the work clears the quality bar.
- Prerequisites
- One-time setup
- How video generation works (end-to-end)
- Generating a video
- Previewing and rendering
- Regenerating a single scene
- Project layout
- CLI reference
- Troubleshooting
-
Python 3.13
-
Node.js 18+ β for the Remotion Studio preview and the iconify asset index
-
Claude Code installed and authenticated (install guide)
-
API keys β at minimum:
- An Anthropic Claude Code OAuth token (
sk-ant-oat01-...) β get it viaclaude setup-token - An ElevenLabs API key for narration TTS
- Optional: a Mapbox token if any scene renders a map
See
example.envfor the full list with links and defaults. - An Anthropic Claude Code OAuth token (
From the repo root:
# 1. Configure secrets
cp example.env .env
# then edit .env and fill in the keys
# 2. Python deps for the orchestrator pipeline
pip install -r requirements.txt
# 3. Python deps for the tools CLI (asset fetching, validation, TTS, SFX)
pip install -r video-tools/requirements.txt
# 4. Node deps for the iconify icon library (used by the asset agent)
cd video-tools && npm install && cd ..
# 5. Node deps for the Remotion Studio preview
cd studio && npm install && cd ..OVG is built around two ideas:
-
A Claude Code orchestrator drives the pipeline. Inside your Claude Code session, the model reads
.claude/CLAUDE.mdand acts as a creative director. It does not blindly run a fixed workflow β it spawns specialized sub-agents, reviews their output against a quality bar, and loops back to fix weak work before moving on. -
The actual work runs as plain Python CLIs and Remotion code. No MCP server, no hidden services. Two CLIs do all the heavy lifting:
CLI Run from Purpose python -m scripts.cli_pipeline <subcommand>OVG root Pipeline orchestration: init, per-steppre/post,info,promptspython -m scripts.tools_cli <subcommand>video-tools/Worker tools: SVG/icon fetching, JSON/TSX validation, math-based SVG paths, sound effects
The pipeline produces, for each topic, a self-contained folder under
Outputs/{TOPIC}/ that holds the script, the manifest, the scene-by-scene
direction JSON, the narration MP3, the per-scene frame timestamps, all SVG/PNG
assets, every generated Remotion scene component, and the final composition
file Remotion Studio plays.
Each sub-agent is a focused Claude Code agent defined in .claude/agents/*.md.
The orchestrator spawns them via the Task tool. Every sub-agent owns its full
step end-to-end (its own pre and post runs) β the orchestrator never calls
cli_pipeline pre/post directly.
| # | Sub-agent | Input | What it does | Output |
|---|---|---|---|---|
| 0 | script-agent |
Topic / brief / raw user input | Drafts a voiceover script, self-rates it on 9 dimensions, revises until it scores 9.0+ (max 5 passes) | Outputs/{TOPIC}/script.md |
| 1 | direction-agent |
The script + style + ratio | Splits the script into scenes; for each scene writes a self-contained videoDescription (cinematic, no humans, no implementation details). Declares all required_assets. |
Outputs/{TOPIC}/Direction/Latest/latest.json |
| 2 | audio-agent |
The script | Inserts ElevenLabs emotion tags ([curious], [deadpan], [pause], ...) without changing any words. The post step then synthesizes the MP3 via ElevenLabs and writes a per-scene frame-timestamp transcript back into the direction JSON. |
Outputs/{TOPIC}/Audio/latest.mp3, Outputs/{TOPIC}/Transcript/latest.json |
| 3 | asset-agent |
required_assets from direction |
Picks the right kind of asset for each name (emoji, illustration, human-character, or company-logo) and fetches it via tools_cli get_asset. Falls back to creating a clean SVG from scratch only if nothing suitable is found. |
SVG/PNG files in Outputs/{TOPIC}/Assets/Latest/, mirrored into Outputs/{TOPIC}/public/ for Remotion |
| 4 | code-agent |
All previous outputs | Reads one prompt per scene (auto-generated with frame ranges from the transcript), writes a Remotion TSX component per scene, validates them in batch, and stitches them into a single Remotion composition. | Outputs/{TOPIC}/Video/Latest/scene_{i}.tsx and composition.tsx |
you say
"make a video about how WiFi works"
β
βΌ
ββββββββββββββββββββββββββ
β script-agent β draft β self-rate β revise (up to 5 passes)
β βββββββββββββββββ β produces: Outputs/{TOPIC}/script.md
ββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β cli_pipeline init β creates manifest.json, picks viewport
β --topic --script β from style + ratio (vox/9:16/etc.)
β --style --ratio β
ββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β direction-agent β pre β reads prompt, examples, hooks rules
β β gen β scene-by-scene JSON
β β post β versions, extracts narration
ββββββββββββββββββββββββββ βββββ orchestrator reviews each scene;
β weak scenes get re-spawned
βΌ
ββββββββββββββββββββββββββ
β audio-agent β pre β loads script
β β tag β adds [emotion] tags
β β post β ElevenLabs TTS β MP3 +
β β per-scene frame timestamps
ββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββββββββ
β asset-agent β pre β reads required_assets
β β fetch β tools_cli get_asset (iconify,
β β react-icons, simple-icons,
β β Logo.dev for company logos)
β β post β mirrors to public/ for staticFile()
ββββββββββββββββββββββββββ βββββ orchestrator can re-run
β a subset by asset name
βΌ
ββββββββββββββββββββββββββ
β code-agent β pre β builds one prompt_{i}.md per scene
β β using transcript frame ranges
β β gen β all scene_{i}.tsx in one batch
β β validate via tools_cli validate_tsx
β β post β writes composition.tsx
ββββββββββββββββββββββββββ βββββ orchestrator can re-spawn for
β a single scene index
βΌ
python studio.py {TOPIC}
β
βΌ
browser preview at http://localhost:3000
Because each artifact lives at a stable path under Outputs/{TOPIC}/, any step
can be re-run after editing a previous step's output. The most common loops:
- Script β Script β opening flat, closer summarises β re-spawn
script-agentwith feedback. - Direction β Direction β a few weak scenes β re-spawn
direction-agentwith the specific scene indices. - Assets β Direction β an asset can't be cleanly fetched/created β change the direction so it doesn't need that visual.
- Code β Direction β a TSX scene reveals the direction was too vague β tighten the direction β re-run code for that one scene.
Every re-run is targeted: the agents support regenerating a subset of scenes or assets without touching the rest.
The orchestrator reads every direction JSON before passing it on. Common
rejection causes (full list in .claude/CLAUDE.md):
- Humans, body parts, faces, or human silhouettes.
- Implementation details (hex colors, pixel values, easings).
- Scenes that reference each other ("same as scene 2").
- Vague descriptions that aren't visually specific.
- Static or empty backgrounds β backgrounds must "breathe".
- Scene 0 that doesn't stop the scroll (no hero element >50% screen, no mid-action first frame).
- Orphaned
@assetsor missingrequired_assets. - Non-ASCII characters anywhere in the JSON.
Open the project in Claude Code from the OVG root (claude from
C:\Outscal\video-generator) and just describe what you want. Two common
patterns:
In the Claude Code chat, say:
Generate a video for
how-wifi-works-v2. Topic: how home WiFi actually works. Portrait, vox style.
The orchestrator will:
- Spawn
script-agentto draft the narration (saved toOutputs/how-wifi-works-v2/script.md). - Run
python -m scripts.cli_pipeline init --topic how-wifi-works-v2 --script Outputs/how-wifi-works-v2/script.md --style vox --ratio 9:16. - Spawn
direction-agent, thenaudio-agent, thenasset-agent, thencode-agentin order, reviewing each step.
If you already have polished narration:
-
Save it to
Outputs/{TOPIC}/script.md(where{TOPIC}is a slug ending in-v2, e.g.how-wifi-works-v2). -
From the OVG root run:
python -m scripts.cli_pipeline init \ --topic how-wifi-works-v2 \ --script Outputs/how-wifi-works-v2/script.md \ --style vox \ --ratio 9:16
--styleβ one of4g5g | brutalism | glitch-art | memphis | neobrutalism | risograph | synthwave | typography-apple | vox--ratioβ9:16(portrait, 1080Γ1920) or16:9(landscape, 1920Γ1080)--voice-idβ optional ElevenLabs voice ID override
-
In the Claude Code chat, say: "Generate the video for
how-wifi-works-v2." The orchestrator picks up from the direction step.
Each sub-agent prints short progress messages and ends with one of:
Script complete/Direction complete/Audio complete/Assets complete/Code complete... failed: <reason>
Between steps, the orchestrator reads the produced files itself, evaluates them, and either moves on or re-spawns the agent with targeted feedback.
After code-agent reports Code complete:
python studio.py how-wifi-works-v2This launcher (studio.py at the repo root) sets OVG_TOPIC and runs
npm run studio inside studio/ for you on Windows, macOS, or Linux. Studio
opens at http://localhost:3000 and loads:
Outputs/{TOPIC}/Video/Latest/composition.tsxβ the Remotion compositionOutputs/{TOPIC}/public/β assets resolved viastaticFile()Outputs/{TOPIC}/public/audio/latest.mp3β narration track
To render to MP4:
cd studio
OVG_TOPIC=how-wifi-works-v2 npm run render
# Windows (cmd): set OVG_TOPIC=how-wifi-works-v2 && npm run renderThe output file lands in studio/out/video.mp4.
You don't have to re-run the whole pipeline to fix one weak scene.
Code only (direction is fine):
- In Claude Code, say: "Re-spawn the code-agent for scene 3 of
how-wifi-works-v2." - The agent re-runs
cli_pipeline pre, regenerates onlyscene_3.tsx, and re-writes the composition. Refresh Studio.
Direction + code:
- Edit the scene's entry directly in
Outputs/{TOPIC}/Direction/Latest/latest.json. - Ask Claude Code to re-spawn
code-agentfor that scene index.
Direction + new assets + code:
- Edit the scene in
latest.json, including any new@assetreferences andrequired_assetsentries. - Ask Claude Code to re-spawn
asset-agent(specify the new asset names) and thencode-agentfor the scene index.
Full direction rewrite (last resort):
Delete Direction/Latest/latest.json and ask the orchestrator to regenerate
everything. Audio and code will need to be re-run after.
OVG/
βββ scripts/ # Python orchestrator
β βββ cli_pipeline.py # the orchestrator CLI (init/pre/post/info/prompts)
β βββ claude_cli/ # per-step pre/post implementations
β β βββ content_video_direction/
β β βββ content_audio/
β β βββ asset_generator/
β β βββ content_video/
β βββ controllers/ # manifest, output, metadata controllers
β βββ server_agents/ # FastAPI server variant + agent helpers
β βββ utility/ # ElevenLabs TTS, audio batching, config
β
βββ video-tools/ # Python tools CLI (called by sub-agents)
β βββ scripts/
β β βββ tools_cli.py # entry point: get_asset, validate_*, svg_path, ...
β β βββ assets/ # icon search, company-logo lookup
β β βββ svg_gen/ # math-based SVG path generator
β β βββ validation/ # JSON / TSX / script-with-emotions validators
β β βββ sound_effect/ # ElevenLabs SFX generation
β βββ node_modules/ # iconify, react-icons, simple-icons (asset sources)
β
βββ prompts/ # Prompt templates the pre-step substitutes into
β βββ orchestrator/ # direction, audio, asset, code, script templates
β
βββ studio/ # Remotion Studio workspace for local preview
β βββ src/ # composition entry, alias resolver
β
βββ Outputs/ # Per-topic runtime artifacts (gitignored)
β βββ {TOPIC}/
β βββ manifest.json # topic metadata: style, ratio, viewport, step versions
β βββ script.md # final voiceover script (input to init)
β βββ Scripts/ # script-agent drafts + evals + emotion-tagged version
β β βββ drafts/script_v{N}.txt
β β βββ drafts/eval_v{N}.txt
β β βββ drafts/director_hints.txt # optional pass-through visual hints
β β βββ script-user-input.md # copy of the input init read
β β βββ script.md # narration extracted by direction post
β β βββ script-with-emotions.md # ElevenLabs-tag-annotated version
β βββ Direction/
β β βββ Prompts/prompt.md
β β βββ Latest/latest.json # the scene-by-scene plan
β βββ Audio/
β β βββ Prompts/prompt.md
β β βββ latest.mp3 # ElevenLabs TTS output
β βββ Transcript/latest.json # per-word frame timestamps
β βββ Assets/
β β βββ Prompts/prompt.md
β β βββ Latest/ # SVGs/PNGs + asset_description.json
β βββ Video/
β β βββ Prompts/prompt_{i}.md # one prompt per scene
β β βββ Latest/
β β βββ scene_{i}.tsx # generated Remotion scenes
β β βββ composition.tsx # the composition Studio loads
β βββ public/ # what Remotion's staticFile() serves
β βββ *.svg | *.png # mirrored assets
β βββ audio/latest.mp3 # mirrored narration
β
βββ .claude/
β βββ CLAUDE.md # the orchestrator's system prompt
β βββ agents/ # the five sub-agent definitions
β β βββ script-agent.md
β β βββ direction-agent.md
β β βββ audio-agent.md
β β βββ asset-agent.md
β β βββ code-agent.md
β βββ skills/ # reference banks the agents read on demand
β βββ script-writer/
β βββ video-director/ # scene examples by category, hooks, UI mockups, maps
β βββ remotion-best-practices/ # rules for fonts, transitions, mockups, maps, etc.
β
βββ studio.py # OS-agnostic launcher: `python studio.py {TOPIC}`
βββ example.env # template β copy to .env
βββ requirements.txt # orchestrator deps
βββ README.md # this file
Run from the OVG root.
| Subcommand | Purpose |
|---|---|
init --topic TOPIC --script PATH [--style STYLE] [--ratio RATIO] [--voice-id ID] |
Create manifest.json and copy the script into the topic folder. Run once per topic. |
pre --topic TOPIC --step {direction|audio|assets|code} |
Build the prompt file for that step. (Sub-agents call this themselves β you rarely need to.) |
post --topic TOPIC --step {direction|audio|assets|code} [--use-fallback] |
Validate, version, and finalize that step. (Sub-agents call this themselves.) |
info --topic TOPIC |
Print the parsed manifest JSON β shows style, ratio, viewport, and what's been generated. |
prompts --topic TOPIC --step STEP |
Print the prompt file path(s) for a step. |
Run from video-tools/. These are what the sub-agents call under the hood;
they're available for manual use too.
python -m scripts.tools_cli get_asset --payload payload.json
python -m scripts.tools_cli validate_json --file path.json [--topic TOPIC]
python -m scripts.tools_cli validate_tsx --payload payload.json
python -m scripts.tools_cli validate_script_with_emotions --file path.md --topic TOPIC
python -m scripts.tools_cli svg_path --equation PARABOLIC --params-json '{...}'
python -m scripts.tools_cli merge_paths --paths-json '["M ...","M ..."]'
python -m scripts.tools_cli generate_sound_effect --text "..." --duration 3 --output-dir .svg_path supports PARABOLIC, CIRCULAR, ELLIPTICAL, SINE_WAVE,
SPIRAL, S_CURVE, LINEAR, ARC, BEZIER, ZIGZAG, BOUNCE, SPLINE.
OUTSCAL_API_KEY not set, falling back to .env valuesβ informational, safe to ignore.OVG_TOPIC is not setβ set the env var beforenpm run studio, or usepython studio.py {TOPIC}which sets it for you.- Icon search is slow on first run β the index (
.icon_index.bin) is built once from@iconify/jsonand cached invideo-tools/node_modules/. @topic/...import red squiggles in your editor β harmless.@topicis a webpack alias resolved at Remotion build time fromOVG_TOPIC; TypeScript can't resolve it statically.- TTS fails on the primary voice β the audio agent retries automatically with the fallback model. You can also re-run manually:
python -m scripts.cli_pipeline post --topic {TOPIC} --step audio --use-fallback. - A scene renders blank in Studio β open
Outputs/{TOPIC}/Video/Latest/scene_{i}.tsxand check the browser console; then ask Claude Code to re-spawncode-agentfor that scene index. - Maps don't render β set
MAPBOX_TOKENSin.env. Without a token, scenes that use themapboxTokenprop will fail.