Product ad video, made in code
A pipeline for 15–20 s product ads with no video editor: every frame, sound and voice line is generated by scripts, so the whole thing is reproducible and editable as text.
project facts ─► brief + storyboard ─► visuals ─────────────► Remotion render ─► MP4
├ 2D: React (Remotion) ▲
└ 3D: Blender (headless) ─► JPG frames
sound: scripts/score.py ─► WAV ┘
voice: scripts/kokoro_tts.py ─┘
Worked example (GhostCommits, real files): examples/ghostcommits/.
Step 1 — Understand the product, write the brief
Read the README, landing page and code. Extract: one-line pitch, 3–4 concrete proof points (numbers beat adjectives: "1 file", "0 logins"), the brand colours from the CSS, logo SVG, URL. Write down what must NOT be claimed (e.g. "testnet only", "not anonymous", no user counts). Every on-screen line and voice line is checked against this list. Ask the user to pick a palette/style only if the brand doesn't already define one.
Step 2 — Storyboard (write it to a .md file before building)
18 s at 30 fps = 540 frames. 5–6 shots of ~2.5 s (75 frames, = one bar at 96 BPM, so cuts land
on the beat). Each shot: frames, camera/motion, on-screen text, sound. End on a 3–4 s end card
(name, tagline, URL). Template: references/storyboard.md.
Step 3a — 2D style (Remotion)
Kinetic type + flat shapes, "Spotify Wrapped" energy. See references/remotion-2d.md.
npx create-video@latest --yes --blank --no-tailwind video then one component per ad,
driven only by useCurrentFrame() + interpolate with easing. No CSS transitions.
Step 3b — 3D style (Blender, headless)
Glossy hero object in a black void, macro close-ups, light streaks across the surface, hard
cuts, big stat + small caption lower-left. See references/blender-3d.md.
One Python script builds the scene, keyframes every shot and renders frames:
blender -b --factory-startup -P scene.py -- anim ../public/frames. Remotion overlays the
text on the frames (examples/ghostcommits/GhostAdV2.tsx). Render 2–3 stills first and
look at them before the full animation.
Step 4 — Sound (scripts/score.py)
Write a JSON cue sheet: one entry per visual event, keyed by frame number taken from the
animation code (cut → cut_hit, object flies in → whoosh, lands → click/lock, light
streak → glint, end card → impact, build-up → riser). Then python scripts/score.py cues.json.
It synthesizes everything (no samples, no licences), adds a 96 BPM music bed that drops at
break_frame and resolves on end_frame, and prints per-second loudness so you can sanity-check
without listening. Details and gain guidance: references/sound.md.
Step 5 — Voiceover (optional)
Short lines, ≤ 6 words, one per shot, matching on-screen text. Best free option:
python scripts/kokoro_tts.py lines.txt vo/ (hosted Kokoro, 3 voices to compare). Add the
files to the cue sheet's voiceover list with start times = when that shot's text appears;
score.py trims silence and ducks music under the voice. Measure each line's length and make
sure it ends before the next starts. Options and lessons: references/voiceover.md.
Step 6 — Render and deliver
<Html5Audio src={staticFile("audio/soundtrack.wav")} /> in the composition, then
npx remotion render <CompositionId> out/ad.mp4. Send the MP4 to the user. Say plainly that
you cannot hear audio: levels and timing were checked numerically, not by ear.
Rules that matter
- Honest copy: no claim the product can't back up; keep caveats (beta, testnet) on screen.
- Don't recreate a specific brand's ad (logos, music, characters) — borrow the style only.
- Text ≥ 48 px at 1080p, on screen ≥ 1.2 s, lower-left or centred, never over the hero's face.
- Render stills before the full render; check a frame from every shot.
- Keep generated frames/audio out of git (
.gitignore); commit scripts + storyboard.