# Jeremy Voiceover Video

> Produce polished narrated tutorial/promo/demo/explainer VIDEOS (MP4 + colorful subtitles) voiced in Jeremy Schoemaker's cloned voice, intercut with real UI screenshots and cinematic B-roll of Jeremy generated from his Replicate face LoRA. Use this WHENEVER the user wants a video with a voiceover/narration — "make a tutorial video", "voiceover video", "narrated demo", "promo video", "explainer video", "walkthrough", "generate the voiceover", "add subtitles/SRT" — or wants several variant cuts juried down to a winner. Also use when only the voiceover + SRT are wanted (that's the first half). Covers script → Jeremy VO → face-LoRA stills → hybrid scenes (brand motion-graphics + real screenshots + Jeremy B-roll with fade transitions) → colorful animated subtitles → ffmpeg render → N variants → multi-model jury picks the winner → YouTube/Chrome-Web-Store MP4s. Orchestrate the whole run with Fable.

- Skill: `airanks-net/jeremy-voiceover-video` (Agent Skill, multi-file: 7 files)
- Install (CLI): `npx skillmds@latest add airanks-net/jeremy-voiceover-video`
- Raw SKILL.md: https://api.skillmd.com/api/skills/airanks-net/jeremy-voiceover-video/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: airanks-net (https://skillmd.com/u/airanks-net)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/airanks-net/jeremy-voiceover-video

---


# Jeremy Voiceover Video

Make a broadcast-quality narrated video end to end: write a scene-segmented script, narrate it in
Jeremy's **cloned voice**, generate cinematic **B-roll of Jeremy from his face LoRA** for the
"talking" moments, cut in **real product screenshots**, burn **colorful animated subtitles**, and
render it all with ffmpeg. When asked for options, produce **several distinct variant cuts** and let
a **jury pick the winner**. **Always orchestrate the production with Fable** (see Orchestration).

Segment by scene because the per-scene audio durations drive BOTH the subtitle timings AND how long
each visual stays up — known text + measured duration = perfect sync and cuts that land on the beat.

## 1. The voice-clone TTS (LAN)

- **Host:** `192.168.1.5`. `ssh shoemoney@192.168.1.5`. Toolkit: `~/Services/VoiceStudioLaravel`.
- **Engine:** mlx-audio (Qwen3-TTS), OpenAI-style `/v1/audio/speech` on `localhost:8200`. Confirm up:
  `curl -s -o /dev/null -w '%{http_code}' localhost:8200/v1/models` → `200`. If down, restart the
  mlx-audio server in the toolkit dir (see `server.log`) before synth.
- **Synthesize:** `./say <voice> "<text>" --no-play --mp3 --out <path.wav>` — voices: **jeremy, amber,
  brandi, trump**. `--no-play` REQUIRED over SSH; `--temperature` default 0.8 (livelier).
- **SRT/transcribe:** `./transcribe <audio> --srt` (local mlx-whisper) — quality check + fallback SRT.

## 2. Jeremy's face LoRA (Replicate)

Generate cinematic stills of Jeremy for the "when I'm talking" scenes with his **Flux face LoRA**:

- **Model:** `shoemoney/jshoe-flux-v3` (private) · **trigger word: `jshoe`** · version pinned in
  `scripts/gen_lora.py`. This is the correct one — **NOT** any Hunyuan3D / "h3" avatar.
- **Generate:** `REPLICATE_API_TOKEN=… scripts/gen_lora.py <out_dir> <prompts.txt>` (one prompt/line,
  fires all in parallel, downloads `jeremy-NN.png`). Token lives in `~/.claude/aigate/mcp-keys.env`.
- **Prompt style:** start with `jshoe,` then describe — bald, short beard, black t-shirt, dark studio,
  **subtle green rim light** (brand), cinematic, photorealistic, 16:9. Vary pose per scene (hero
  close-up, at desk with monitors, mid-gesture, arms-crossed CTA).
- Flux gives **stills**, not a lip-synced talking head. Animate them with Ken-Burns (`zoompan`) and
  **fade/dissolve into the screenshot** that follows — that's the "he's talking, then we cut to the
  product" rhythm the brand wants. (For a true talking head you'd add a lip-sync model — out of scope
  unless asked.)

## 3. Pipeline

### 3a. Script — one scene per line
First person for Jeremy ("I built this because…"). ~150 wpm (~2.5 w/s): a 12-word line ≈ 5s; a ~2-min
video is ~280–320 words / ~8–12 scenes. **AIR copy rule: say "AI", never "the machine"; never
"corpus".** Save one line per scene to `script.txt`.

### 3b. Voiceover + timing + SRT
`scripts/build_vo.sh <voice> script.txt <out_dir>` → `scene-NN.wav`, `voice.mp3`, `voice.srt`,
`manifest.json` (`[{scene,text,start,end,dur}]` — the timing map everything else reads). Transcribe a
scene back to catch garbled acronyms/numbers; respell in the script and regenerate just that scene.

### 3c. Jeremy B-roll stills
Generate the LoRA stills (§2) for the talking scenes into `<out>/jeremy/`.

### 3d. Scenes — hybrid, hyper-focused on the product
For each scene, build a clip **exactly `dur` seconds** long (read `manifest.json`):
- **Jeremy scenes:** his LoRA still, slow Ken-Burns, then **fade/dissolve** into the feature.
- **Feature scenes:** full-screen (1920×1080) animated HTML in brand identity **built around the real
  screenshot** — the product is the star. Callouts/highlights point at the exact feature named in the
  VO. Reuse brand tokens/fonts. No webfont links.
- Author scene HTML with subagents (Sonnet), render each with `render_scene.js` (Playwright recordVideo,
  full Chromium) → normalize to 1920×1080/30fps/h264 via ffmpeg at its `dur`.

### 3e. Colorful animated subtitles
Captions must be styled + animated, not plain white. **Preferred (no ffmpeg deps): inject an HTML
caption overlay into each scene before rendering** — `scripts/inject_caption.py scene.html "<text>"
<style> out.html` adds a brand-styled, animated caption (styles: `green` / `pop` / `lower`). Because
each scene is exactly one VO line, the caption timing is automatic and the pop/fade is real CSS. This
is what `render_variant.sh` uses. (Homebrew ffmpeg here has **no libass**, so `-vf ass=` fails — the
HTML route sidesteps that entirely.) `scripts/make_subs.py manifest.json out.ass <style>` still builds
a colorful ASS file for use as a **toggleable sidecar** on YouTube/store, and always ship `voice.srt`.

### 3f. Render + assemble
`scripts/build_video.sh` renders each scene, concats, muxes `voice.mp3`, and (with subs) burns the ASS.
Output: 1920×1080 H.264+AAC `+faststart` MP4. Reference mux:
```bash
ffmpeg -f concat -safe 0 -i clips.txt -i vo/voice.mp3 -map 0:v -map 1:a \
  -c:v libx264 -pix_fmt yuv420p -r 30 -c:a aac -b:a 192k -shortest -movflags +faststart out.mp4
```

## 4. Variants + final jury

When the user wants options, produce **N distinct variant cuts** — genuinely different treatments, not
recolors (e.g. *founder-cut*: heavy Jeremy B-roll + moody fades; *kinetic-cut*: high-energy motion
typography; *product-cut*: clean demo, screenshots front-and-center). Same VO/SRT across all.

**Final jury** picks the winner: extract a representative **contact sheet of frames per variant**
(`ffmpeg … -vf fps=1/2` or key timestamps), and have a vision-capable jury (this harness + optionally
OpenRouter vision models, or `/design-jury-loop`; use `/matrix-council` for a harder adversarial call)
score each on: clarity of the toolbar value-prop, scroll-stopping motion, brand fit, subtitle
legibility, and how well Jeremy's B-roll lands. **Rank them and name the outputs by rank:**
`1-<name>.mp4`, `2-<name>.mp4`, `3-<name>.mp4` (rank-prefixed, descriptive slug). Report why #1 won.

## 5. Orchestration — always with Fable

Run the whole thing as a **Fable-orchestrated** job. In the Workflow, use **`model: 'fable'`** for the
planning/design/jury stages (variant concepting, scene-brief design, final jury judgment) and
**`model: 'sonnet'`** for scene-HTML build; Haiku for any research. Fable plans the cut list and the
three variant treatments, Sonnet builds the scenes, the deterministic shell steps (VO, Replicate,
render, ffmpeg, subs) run inline, and Fable chairs the final jury. Drive the phases in order:
`script → VO → LoRA stills → (Fable) variant briefs → (Sonnet) scenes ×N → subs → render ×N → (Fable) jury → name`.

## Gotchas

- **TTS server must be up on 192.168.1.5:8200** before synth; check first, don't retry blindly.
- **Always `--no-play` over SSH.** TTS on .5, ffmpeg/Playwright/Replicate local — synth on .5, pull wavs.
- **Durations drive everything** — read `manifest.json`, never hand-guess scene lengths.
- **Replicate JSON carries control chars** in `logs` — parse with `json.loads(…, strict=False)` or the
  poll loop dies mid-run (the image is usually fine; re-fetch the prediction to recover the output URL).
- **LoRA = `jshoe-flux-v3`, trigger `jshoe`, NOT the h3/Hunyuan avatar.** Verify a test still looks like
  him before generating the batch.
- **Full Chromium** (`chromium-*`, not `headless_shell`) is needed for recordVideo; the global
  `playwright` module is at `/opt/homebrew/lib/node_modules` (set `NODE_PATH`).
- **Regenerate one scene, not the whole track**, when a line is wrong.
- Keep a scene ≲ ~18s; split longer lines so subtitles stay readable.

