---
title: "Music Video · MTV-grade — any track → beat-locked cinematic music video with lyric typewriter"
reliability: 4.2 # 5 − keyframe img gen .3 − i2v .6 − music gen .2 (skipped when you bring your own track: +.2) − 7-stage path .2 + .5 rendered E2E in production (the lyrics-vs-style bug was found by shipping); stitch/overlay stages are deterministic ffmpeg; cast face-swap pass OPTIONAL (−.2 when used)
---

# Music Video · MTV-grade · agent playbook ⭐ flagship

> Paste this whole file (with the BRIEF filled in) into Claude cowork, chat, or Code. **Bring any music** — a Suno track, a minimax-music generation, your own mp3 — Storyboard turns it into an MTV-grade music video with beat-locked visuals, smooth morph transitions, optional animated lyric typewriter sync'd to the audio, and one polished final reel ready to share.

## What you'll get

Whether the source is a Suno song you generated last night, a minimax-music track Storyboard generates on the spot, or an existing audio file you upload, the pipeline ends in one 1920×1080 cinema reel with:

- **6–9 cinematic scenes** matched to the song's structure (intro / verse / chorus / bridge / outro). Each scene is a krea-2-large keyframe in your chosen visual style.
- **6–9 i2v animations** (seedance-i2v default; ltx-q-i2v for higher fidelity; grok-imagine-video for premium camera moves — all at `resolution:"auto"`) — one per scene, beat-locked to the song's BPM so cuts land on downbeats.
- **Morph/crossfade transitions** between every scene via ffmpeg-overlay + xfade — no hard cuts, the visual story flows.
- **(Lyric variant)** Animated typewriter text overlay per lyric line via `hyperframes-caption`, beautiful serif font, sync'd to the song's structure. Each line reveals during its section, fades out at the section break.
- **(Instrumental variant)** Visual storytelling only — no text overlay, the visuals carry the entire narrative.
- **Clean audio mux** — the song plays at full volume, never ducked, and runs **every second of the reel**: the visual cut is timed to the song length so audio and picture end together (no silent tail, no music overrun). No competing voice-over (the song IS the voice).
- **Cinema 1920×1080** master + optional 1080×1920 vertical cut for socials.
- **Repeatable** — every step is reproducible. Re-render any scene without re-rendering the others (via `director_re_render`). Re-time any scene (via `director_retime`). The chassis stays.

**Time**: ~10–15 minutes wall clock. **Your attention**: ~5 minutes across 2 audit gates (after keyframes + after animation). **Cost**: ~$3.50–$5.50 e2e per video.

## Tell the agent about the music video

```yaml
piece_title:          # e.g. "City Light"
piece_subtitle:       # 1-sentence about the piece
mode:                 # "lyrics" OR "instrumental" — decides whether to overlay the typewriter

# Source music — pick ONE of these three paths
music_source:
  # Path A: bring your own URL
  audio_url:          # https://... direct mp3/wav/m4a, or @last_audio
  # Path B: generate on the spot
  generate:
    prompt:           # e.g. "anthemic indie-pop, 60s, 110 BPM, key A major, one warm female vocal, soft synths + acoustic guitar"
    bpm:              # 110
    duration_sec:     # 60 (or 90)
  # Path C: existing Suno track
  suno_url:           # direct download URL from Suno

# Lyrics — ONLY required if mode=lyrics
lyrics:               # multi-line YAML string OR an array of {text, start_sec, end_sec}
  # If a string, the playbook splits on line break and distributes evenly across the song.
  # If an array, it uses your exact timestamps for precise sync.
  # Example string:
  # |
  #   I drew a city light tonight
  #   and waited for the world to find
  #   the way you used to walk through rain
  #   and turn around to say my name
  #   the city sleeps, the city wakes
  #   I'm still here, for old time's sake
  # Example array:
  # - text: "I drew a city light tonight"
  #   start_sec: 0
  #   end_sec: 8

# Visual style
visual_style:         # e.g. "painterly noir + warm amber neon", "Studio Ghibli watercolour",
                      # "70mm film grain, 90s music-video aesthetic", "VHS dreamscape"
palette:              # 3-5 colour names matching the mood
scene_count:          # 6 (default for 60s songs) — 1 scene per 10s, beat-locked
beats_per_scene:      # auto from bpm × scene_count; tutorial assumes 4 beats per scene

# Cast (optional — for lyric-driven videos that follow one protagonist)
cast_name:            # e.g. "the woman in the cafe"
cast_anchor_url:      # 1 reference photo to face-swap onto every scene (optional)

# Typewriter (lyric mode only)
typewriter_font:      # e.g. "Cormorant Garamond" (serif elegant, default),
                      # "Inter" (sans-serif modern), "Cinzel" (display)
typewriter_position:  # "bottom" (default), "top", "center"
typewriter_color:     # hex, default "#f4e8d0" (warm cream against any palette)

# Finishing
add_letterbox:        # true (default — adds black bars top/bottom for cinema feel)
add_brand_endcard:    # OPTIONAL — append your brand's 2s end-card
output_aspect:        # "cinema" (1920×1080, default) OR "vertical" (1080×1920) OR "both"
piece_slug:           # kebab-case for filenames. e.g. city-light
```

## Quality controls (auto-applied)

- **Cost upfront.** Show estimate before any inference: typically ~$0.30 music + ~$0.44 keyframes + ~$2.52 i2v + ~$0.40 lyric overlays (if applicable) = **~$3.50–$5.50**. User approves first.
- **Cast lock when supplied.** If `cast_anchor_url` is set, every i2v keyframe gets the cast spec re-pasted; after animation, optional `face-swap-video` pass on each scene.
- **Audit gate after keyframes.** All N keyframes laid out in a grid against the style brief + palette. User approves → animation fires.
- **Audit gate after animation.** Each anim plays inline; user marks any with drift; flagged scenes go to `director_re_render` once before stitch.
- **Beat-lock invariant.** Every scene's `target_duration_sec` = `(60 / bpm) × beats_per_scene`. At 120 BPM × 4 beats/scene → 2s per scene. At 110 BPM × 4 → 2.18s per scene. The script enforces this in the i2v duration param.
- **Audio covers the full reel.** No ducking, no VO competition — the music IS the soundtrack. The total of all scene durations is set equal to the song length (`Σ scene_duration_sec = duration_sec`), so the mux step lands audio and picture together with no silent tail and no music overrun.
- **Letterbox by default.** Scales the 1920×1080 frame to 1920×816 and pads back to 1920×1080 with 132px black bars top + bottom → 2.35:1 cinematic feel. Off via `add_letterbox: false`.

## How the agent should run this (interaction contract)

1. **CONFIRM (one message, ≤1 question):** restate in 1 line ("60s MTV reel from your track, 6 beat-locked scenes + lyric typewriter, ~$3.50–5.50, ~10–15 min") + real choices: lyrics vs instrumental mode · seedance (default) vs ltx-q-i2v (fidelity) vs grok-imagine-video (premium camera) · cinema vs vertical vs both. The one question worth asking: which music source path (bring-URL / generate / Suno) if it isn't given.
2. **PREVIEW CHECKPOINT:** AUDIT GATE 1 — scene plan + the keyframe grid (~$0.44) before the ~$2.52 of i2v fires. On the generate path, play the TRACK first: a wrong song wastes every downstream dollar.
3. **NARRATE:** animate in waves of ≤4–5; per wave: "scenes 1–4 animating (seedance, ~2–4 min each), job …"; poll `get_create_media` every 10–15s, never silent >2 min. Say when you reach Stage 6 — the stitch is local ffmpeg and fast.
4. **FAIL GRACEFULLY:** the singer sings the style description → the lyrics-vs-style bug; re-run with real lyrics (or `"[Instrumental]"`) in `lyrics_prompt`; `music` cap fails → retry once → `sonilo-t2m` (licensed sibling); `seedance-i2v` hang → `ltx-q-i2v` (`num_frames:241`, `resolution:"auto"`) → Ken-Burns still; `hyperframes-caption` fail → retry once → ship without the typewriter + a note. ≤2 retries per scene; the beat-lock math survives any single swapped scene.
5. **DELIVER:** the 1920×1080 master (+ vertical if asked) + one honest line ("lyric sync is structural at the line level, not word-perfect; scene 4 was re-rendered once for drift") + ONE next step ("cut the vertical, or train a cast LoRA if this protagonist recurs").

## The 7-stage pipeline (canonical)

```
Stage 1 — UNDERSTAND THE MUSIC
  If music_source.audio_url → use directly
  If music_source.generate  → 1 × create_media(
                                action="music",
                                prompt=<style only — genre/bpm/mood/instrumentation>,
                                lyrics_prompt=<EITHER "[Instrumental]"
                                              OR the actual lyrics wrapped in
                                              [Verse]/[Chorus]/[Bridge]/[Outro] markup>,
                                duration=duration_sec)
                                ⚠ CRITICAL: NEVER pass the style description in
                                lyrics_prompt — minimax-music will literally sing it.
                                Pass real lyrics, or pass "[Instrumental]" for no vocals.
  If music_source.suno_url  → curl-download to local, then use
  Output: audio_url (mp3) + known bpm + known duration_sec
  Optional: if mode=lyrics and lyrics is a string with no timestamps,
            split lines and assign even start/end timestamps based on duration.

  Mode rules:
    mode="lyrics"       → lyrics_prompt MUST be the real song lyrics.
                          The singer sings these; the typewriter shows these. Both same.
    mode="instrumental" → lyrics_prompt MUST be "[Instrumental]".
                          No vocals on the audio; typewriter overlay (if any) is
                          a separate text track.

Stage 2 — PLAN THE SCENES
  scene_count = brief.scene_count (default 6 for 60s)
  scene_duration_sec = duration_sec / scene_count  (must align with beat-lock)
  For each scene: derive (description, camera_motion, dominant_color, lyric_line_if_any)
  Output: scenes:[1..N] with per-scene briefs

Stage 3 — KEYFRAMES (parallel)
  N × create_media(action="generate", model="krea-2-large",
                   prompt=[visual_style + palette + cast_spec_if_any + scene.description])
  Output: N × keyframe image URLs
  AUDIT GATE 1 — user reviews grid, approves or re-fires.

Stage 4 — ANIMATE (parallel)
  N × create_media(action="animate", model="seedance-i2v",
                   source_url=<keyframe>,
                   duration=scene_duration_sec,
                   prompt=[scene.description + camera_motion + cast_spec_if_any])
  Output: N × video clips, each ~scene_duration_sec
  AUDIT GATE 2 — user marks any drifters; director_re_render those.

Stage 5 — LYRIC TYPEWRITER (lyric mode ONLY, parallel)
  For each lyrics entry:
    1 × create_media(action="generate", model="hyperframes-caption",
                     prompt={
                       text: lyrics[i].text,
                       font: typewriter_font,
                       color: typewriter_color,
                       position: typewriter_position,
                       reveal_style: "typewriter",          # animated character-by-character
                       reveal_duration_sec: (lyrics[i].end - lyrics[i].start) * 0.7,
                       hold_duration_sec:   (lyrics[i].end - lyrics[i].start) * 0.3,
                       background: "transparent",
                     })
  Output: N × short caption video clips (transparent bg), each timed to its lyric line.

Stage 6 — STITCH (local ffmpeg, deterministic)
  Step 6a: cross-fade chain
    ffmpeg with N inputs + xfade between adjacent clips (0.4s crossfade by default)
    → concat_with_fades.mp4

  Step 6b: lyric overlay (lyric mode only)
    For each lyric clip i:
      ffmpeg-overlay onto concat_with_fades.mp4 at timestamp lyrics[i].start_sec
    → with_lyrics.mp4

  Step 6c: letterbox (if add_letterbox: true)
    ffmpeg scale=1920:816, pad=1920:1080:0:132:black  (132px bars top + bottom → 2.35:1)
    → letterboxed.mp4

  Step 6d: mux music — create_media model_override:"ffmpeg-mux"
    video + music_audio_url, music at 1.0 (no duck)
    Σ scene durations == song duration, so audio covers the FULL reel and both end together.
    (If the song is longer than the cut, trim audio to video; if shorter, the cut was mis-sized — re-time.)
    → final_master.mp4

Stage 7 — PUBLISH
  Output paths:
    /chapters/<piece_slug>/<piece_slug>-master.mp4         (1920×1080)
    /chapters/<piece_slug>/<piece_slug>-vertical.mp4       (1080×1920, if output_aspect includes vertical)
  Optional: brand-locked end-card via director_export with kit's finishing_chain.
```

## Why this is the flagship for music-driven content

- **It works with what creators already have.** A musician brings a Suno track → 10 minutes later they have a music video. No After Effects, no Premiere Pro, no $1000 freelance editor.
- **Repeatable production.** Same chassis works for indie-pop, lo-fi, classical, electronic, hip-hop, instrumental. Swap the style brief, swap the scene briefs.
- **Beat-locked from day 1.** Every cut lands on a downbeat because `target_duration_sec` is computed from BPM × beats-per-scene, not picked by hand. The viewer feels the rhythm even if they can't name why.
- **Lyric typewriter via `hyperframes-caption` is the differentiator.** Most AI tools either burn flat captions (ugly) or skip lyrics entirely. The typewriter character-by-character reveal at the right BPM is the move.
- **Cast lock optional but powerful.** A protagonist who appears in every scene (the singer, an actor, the user themselves) doesn't drift because the cast spec is re-pasted into every keyframe. Plus optional face-swap-video pass after.

## Real-use scenarios

| Persona | What they need | What this playbook gives |
|---|---|---|
| Indie musician | A music video for the song they uploaded to Suno | 10 minutes · $4 · MTV-grade with lyric typewriter |
| Lo-fi creator | Background videos for their 24/7 stream | Instrumental variant; 60-second loops, infinite restyling |
| Wedding videographer | A first-dance recap with the couple's song | Lyric variant + cast-anchor with the couple's faces |
| Indie filmmaker | An opening title sequence with custom score | Instrumental variant; cinematic letterbox + brand end-card |
| Brand marketer | An anthem ad for a product launch | Both modes available; brand-kit finishing chain for the close |
| Educator | A music-illustrated explainer | Lyric variant; the lyrics ARE the lesson, visuals carry the mood |

## Cross-surface parity

| Surface | How to invoke |
|---|---|
| **MCP** | `generate_project({ playbook: "music-video-mtv", music_source, visual_style, lyrics? })` |
| **Webapp chat** | `/mtv <song-name> --style "<keywords>" [--lyrics-file my-lyrics.txt]` or paste a Suno URL into chat |
| **CLI** | `livepeer playbook music-video-mtv --audio <url> --style "<keywords>" [--lyrics <file>]` |
| **Webapp right-click** | On any uploaded audio card → "Make a music video from this" |

## The lyrics-vs-style bug (don't repeat this)

`minimax-music/v2` (the model behind `action: "music"`) takes **two separate text inputs**:

| Field | What it controls |
|---|---|
| `prompt` | Style only — genre, BPM, instrumentation, vocal type, mood. |
| `lyrics_prompt` | The actual words the singer sings, wrapped in `[Verse]/[Chorus]/[Bridge]/[Outro]` markup. |

If you put the style description in `lyrics_prompt`, **the singer will sing the style description out loud**. ("Anthemic indie pop one ten bpm warm female vocal soft synths..." sung in melody. Real bug, shipped twice in June 2026.)

Rules:

1. For **vocal songs**: pass real lyrics in `lyrics_prompt`. The typewriter overlay must show **the same lyrics** (so audio and visual agree).
2. For **instrumental** tracks: pass `lyrics_prompt: "[Instrumental]"`. The typewriter overlay (if any) can be commentary text rather than song lyrics — but make clear in the UI that the music has no vocals.
3. Never let an agent auto-fabricate `lyrics_prompt` from the style prompt. The MCP `create_media` adapter now throws if `lyrics_prompt` is missing when `action="music"`.

## Honest limits

- **Lyric sync is "structural", not "word-perfect".** The typewriter reveals at the SCENE boundary, not on the syllable. For true word-by-word lip-sync, you'd need ASR (Whisper-style timestamps) — that's tracked as a Phase-2 extension. For most music videos, structural sync at the line level reads as professional.
- **Suno-URL auto-fetch needs a download helper.** For now, save the Suno mp3 locally + provide via `audio_url`. A future `ffmpeg-fetch` cap will close this gap.
- **Cross-shot character consistency without cast LoRA is unreliable past 4 shots.** If the song has a clear single protagonist who must look the same across all 6 scenes, train a LoRA first (~10 min, ~$2) — that's a separate playbook (`character-design.md`).
- **The 60s default is calibrated against typical Suno tracks.** For 90s or 180s songs, scene_count scales (10 scenes for 90s, 18 for 180s). Beat-lock formula stays the same.
- **Hyperframes-caption produces transparent-bg WebM/MOV.** ffmpeg-overlay handles the alpha channel automatically. If your finishing chain target doesn't support alpha, the playbook bakes the lyric onto a solid-color band first.

## Reproduce via MCP — sample call

```typescript
// 1. Music — STYLE goes in prompt; LYRICS go in lyrics_prompt.
//    For instrumental, pass lyrics_prompt: "[Instrumental]".
//    ⚠ Do NOT put style text in lyrics_prompt — the singer will sing it.
const music = await create_media({
  action: "music",
  prompt: "anthemic indie-pop, 60s, 110 BPM, one warm female vocal",
  lyrics_prompt: [
    "[Verse]",
    "I drew a city light tonight",
    "and waited for the world to find",
    "[Chorus]",
    "the way you used to walk through rain",
    "and turn around to say my name",
    "[Bridge]",
    "the city sleeps, the city wakes",
    "[Outro]",
    "I'm still here, for old time's sake",
  ].join("\n"),
  duration: 15,  // minimax-music ignores this above 60s; it returns a 60s+ track
});

// INSTRUMENTAL variant — same call, just:
//   lyrics_prompt: "[Instrumental]"

// 2. Scene briefs (agent-derived from song structure)
const sceneBriefs = [
  { description: "city street night, lone streetlamp, woman drawing on fogged window from inside cafe", camera: "static" },
  { description: "woman alone at cafe table, mug warm in hands, looking out fogged window", camera: "slow push-in" },
  // ...4 more
];

// 3. Keyframes in parallel
const keyframes = await Promise.all(sceneBriefs.map((s) =>
  create_media({ action: "generate", model_override: "krea-2-large",
    prompt: `${visual_style}, ${palette.join(", ")}, ${s.description}` })));

// AUDIT GATE 1

// 4. Animate in parallel
const anims = await Promise.all(sceneBriefs.map((s, i) =>
  create_media({ action: "animate", model_override: "seedance-i2v",
    source_url: keyframes[i].url, duration: 10,  // = 60s / 6 scenes
    prompt: `${s.description}, ${s.camera}` })));

// AUDIT GATE 2

// 5. Lyric typewriters (lyric mode)
const lyrics = [
  { text: "I drew a city light tonight", start: 0, end: 10 },
  { text: "and waited for the world to find", start: 10, end: 20 },
  // ...
];
const captions = await Promise.all(lyrics.map((l) =>
  create_media({ action: "generate", model_override: "hyperframes-caption",
    prompt: JSON.stringify({ text: l.text, font: "Cormorant Garamond",
      color: "#f4e8d0", position: "bottom", reveal_style: "typewriter",
      reveal_duration_sec: (l.end - l.start) * 0.7,
      hold_duration_sec:   (l.end - l.start) * 0.3,
      background: "transparent" }) })));

// 6. Local ffmpeg stitching (the deterministic part)
// See `scripts/mtv-stitch.sh` for the canonical recipe.
```

## Execution mode (autopilot / director / auto)

This is a multi-scene project, so pass a `mode` to `generate_project` to control the **pre-finish gate** (after every scene renders, before delivery): `autopilot` (certain brief → ship in one call, no pause), `director` (always pause for a human pick/steer before delivery), or `auto` (recommended — ship UNLESS the auto-critic is unsure: score < `confidence_threshold` (default 0.8), verdict `iterate`, or a flagged shot). Setting `mode` runs the project async (returns a `job_id`); resume a director/auto pause with `review_checkpoint` + `resume_from_checkpoint` (decision `continue` to deliver / `abort` to discard). Omit `mode` for legacy straight-through behavior.
