---
title: Make a lyric video or text animation for your song
description: A lyric video, karaoke video or kinetic typography for your song, with each line timed to the singing and the beat.
---
You get a lyric video for your song, also called a karaoke video, a text animation or kinetic typography. Each line of the lyrics builds up word by word as it is sung, and big lines land on the beat. You can make it in one step with `lyrical_videos.audio`, or line by line on a timeline you can still edit.

Use invideo for this when you want the lyrics timed to your own recording: your agent reads when each word is sung, puts the lines on the timeline, and you can fix a word or move a line after.

## Quickest path

Set up your agent first with [invideo for AI agents](/agents). Then give it this prompt.

```text
Make a lyric video for my song ./song.wav, 16:9. The lyrics are in
./lyrics.txt. Show one line at a time, build each line up word by word
as it is sung, and put the chorus lines on the beat. Use my cover art
./cover.jpg as the background. Price it first and show me frames.
```

In a chat app, you drag the song, the lyrics and the art into the project's Media panel in invideo. In a terminal, you name the files and the agent uploads them.

## What you need

The song is the only thing you must have. The rest makes the result better.

- **The song.** WAV or MP3, a mix you have the rights to.
- **The lyrics as text.** The agent checks the timed words against them, so names and slang come out right.
- **A background.** Your cover art, a clip of the band, stock footage, or a plain color.
- **The shape.** 16:9 for YouTube, 9:16 for Reels, Shorts and TikTok, 1:1 for a feed.
- **The look.** Clean, karaoke, bold and moving, or handwritten.

## Steps

Your agent edits on a branch and tells you what changed at each step. Steps two and three are the one-step model path. Steps four to six are the timeline path, which you can edit word by word.

<Steps>
<Step title="Make the timeline and bring in the song and the art">
The agent makes a project with `new_project`, a timeline with `new_timeline` in the shape you asked for, and a branch with `timeline_history`. It calls `start_upload`, sends the bytes, then calls `add_media` with the `upload_id` and the branch URL. Expected result: a sequence URL and a media bin id for the song and the art.
</Step>
<Step title="Make the lyric video in one step">
The agent runs `find_models` with "lyric video from a song" and reads its pick with `get_model`. A good pick is `lyrical_videos.audio`. Your agent can search for others with `find_models`. It calls `generate` with `dry_run: true`, the song as `audio`, a `lyrical_style` of `ULTRA_BASIC`, `KARAOKE` or `POP_ANIMATION`, and an `aspect_ratio`, and tells you the price. For a price of 1 credit or more it waits for your yes. Then it calls `generate` with `max_credits` set to that price and an `idempotency_key`. Expected result: a sequence URL for the finished lyric video, which the agent reads with `read`.
</Step>
<Step title="Put the lyrics over your footage in one step">
For a video with the song in it, the agent runs `find_models` with "lyrics over a video" and reads its pick with `get_model`. A good pick is `lyrical_videos.video`, with the clip as `video` and a `lyrical_style` such as `KINETIC_LYRICS`, `MULTISIZE`, `PAPER_CUTOUT` or `SUBTITLE`. It calls `generate` with `dry_run: true` and tells you the price. For a price of 1 credit or more it waits for your yes. Then it calls `generate` with `max_credits` set to that price and an `idempotency_key`. Expected result: a sequence URL for your footage with the lyrics moving over it, which the agent reads with `read`.
</Step>
<Step title="Place the song and the art on the timeline">
The agent sends one `edit_timeline` call with two `insert_media` ops, `at_f` 0 and `ripple` false: the song's bin id on `A2` and the art's bin id on `V1`. It reads the art clip's end with `get_timeline`. In a second call, it stretches the art to the song's end with `trim_clip` on the `tail`, and covers the frame with `fill_frame`. Expected result: `capture` shows the art and the song starts at frame 0.
</Step>
<Step title="Time every word">
The agent prices `media` `audio.transcript` on the song with `dry_run`, starts it, and calls again after `retry_after_s` for the words with their times. It checks the words against your lyrics text and keeps your spelling. It runs `audio.beats` for the beats, `audio.markers` for the drops and phrases, and `audio.sections` for the chorus. Expected result: each lyric word with a start and end in seconds.
</Step>
<Step title="Place the lyric lines">
The agent adds the lyrics with `add_captions`: one slide per line, with `words` inside each slide, so the words of each line build up as they are sung. It turns seconds into frames at the timeline's rate. It sets the font, size and color with `set_text_style`, and fixes a misheard word with `set_caption_slide_text`. For big chorus lines, it adds `add_text` clips that start on a beat, and moves them with `set_keyframes` on `opacity` or `rotation`. Expected result: the lyrics on the timeline in your style, in time with the song.
</Step>
<Step title="Check, then export">
The agent runs `capture` at the first line, a chorus line and the last line, and runs `check_timeline`. Then it runs `export` with `format` video and reads the card. Expected result: each captured line matches the words sung at that time, and the card's status is `completed`.
</Step>
</Steps>

The same job from a terminal, with the one-step model:

```bash
invideo project new --title "Lyric video"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo edit new <project-url> --name "Lyric video"
invideo asset upload <edit-url> ./song.wav ./cover.jpg
invideo gen models <project-url> --query "lyric video from a song"
invideo gen model <project-url> --model lyrical_videos.audio
invideo gen run <page-url> --model lyrical_videos.audio --dry-run \
  -i audio=<sequence-url> -i lyrical_style=KARAOKE \
  -i aspect_ratio=16:9
invideo gen run <page-url> --model lyrical_videos.audio \
  --max-credits <price> --idempotency-key lyric-1 \
  -i audio=<sequence-url> \
  -i lyrical_style=KARAOKE -i aspect_ratio=16:9
invideo status <thread-url> --wait
invideo notebook show <thread-url>
```

Where each value comes from:

- `<name>` is the name you choose in `invideo agent register`. `INVIDEO_AS` passes it to every later command.
- `<project-url>` comes from `invideo project new`, and `<edit-url>` from `invideo edit new`.
- `<sequence-url>` is the `url` that `invideo asset upload` returns for the song, a `…/notebook/seq-N.M` URL.
- `<page-url>` is a notebook page of the project, listed by `invideo notebook show <project-url>`.
- `<price>` is the dry run's `.estimated_credits`.
- `<thread-url>` is the `url` the paid `gen run` returns. `notebook show` on it gives the finished video's sequence URL.

The timeline path from a terminal:

```bash
invideo media audio.transcript <sequence-url> --dry-run
invideo media audio.transcript <sequence-url> --max-credits <price>
invideo media audio.beats <sequence-url>
invideo edit show <edit-url> --json
invideo branch new <edit-url> --name lyrics --token <state_token>
invideo edit show <branch-url> --json
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @ops.json
invideo edit capture <branch-url> --at 12,48,95 --out ./frames
invideo edit check <branch-url>
invideo edit export <branch-url> --format video --quality 1080 --json
invideo status <card-url> --wait
invideo notebook show <card-url> --out ./out
```

`<sequence-url>` is the song's, as above, and `<price>` is the dry run's `credits`. `<state_token>` comes from `invideo edit show <edit-url>` for the branch command, then from `invideo edit show <branch-url>` for `edit change`. `<branch-url>` is the `.branch_url` that `invideo branch new` returns. `ops.json` holds a bare JSON list of ops, the `operations` list of the call below. `<card-url>` is `.sequences[0]` of the export result. `invideo status` waits but saves nothing. `invideo notebook show` saves the video as `./out/export.mp4`, and the agent renames it.

The key MCP call, in full, places the song and the art, then two lines with word timing, and styles them. `m1` and `m2` are the bin ids that `add_media` returns for the song and the art. At 30 fps, a word sung at 12.4 seconds starts on frame 372.

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [
    { "op": "insert_media", "asset": "m1", "track": "A2",
      "at_f": 0, "ripple": false },
    { "op": "insert_media", "asset": "m2", "track": "V1",
      "at_f": 0, "ripple": false },
    { "op": "add_captions", "track": "V2",
      "slides": [
        { "text": "Hold the light", "start_f": 372, "end_f": 450,
          "words": [
            { "text": "Hold", "start_f": 372, "end_f": 390 },
            { "text": "the", "start_f": 390, "end_f": 400 },
            { "text": "light", "start_f": 400, "end_f": 450 } ] },
        { "text": "till the morning", "start_f": 456, "end_f": 540 }
      ],
      "style": { "font_size": 84, "align": "center",
                 "color": "#ffffff" } }
  ]
}
```

`<branch-url>` is the branch URL that `timeline_history` returns, and `<state_token>` comes from `get_timeline` on it.

A text clip moves as one piece. For each word to move on its own, the agent makes one `add_text` clip per word.

## Cost and time

Edits and exports are free. Generation and transcripts cost credits. Paid stock costs a small credit charge, or one of your workspace's iStock licenses. Your agent runs a dry run first and tells you the price before it spends anything.

- **Free:** the upload, the captions, the text, the checks and the export.
- **Tool ops:** `audio.beats`, `audio.markers`, `audio.sections`, `audio.separate`. The dry run shows its price before anything starts.
- **Costs credits:** a run of `lyrical_videos.audio` or `lyrical_videos.video`, and the transcript of the song. Only the first call spends credits.

The one-step model takes a few minutes to render. The timeline path takes longer to set up, and you can change any word after. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Sung words are harder to hear than speech. Most problems come from a misheard word or a line that shows too early.

| Symptom | Fix |
| --- | --- |
| `get_model` or `generate` refuses the model as not found | Models retire. Run `find_models` with the job in words, use the best match, and tell the person which model you used. |
| The export has no song, or a black background | The song or the art was never placed. Run the place step: `insert_media` on `A2` and `V1`. |
| `invideo status --wait` ends and the folder is empty | `status` does not download. Run `invideo notebook show <card-url> --out <dir>`. |
| A word in the lyrics is wrong | Fix it with `set_caption_slide_text`. Words that did not change keep their timing. |
| Lines show before they are sung | The transcript times are in seconds of the song. Check the song starts at frame 0, or add its start to every time. |
| The transcript is missing words in a loud part | Run `media` `audio.separate` on the song for the vocal stem, then transcribe the stem. |
| `audio.transcript` returns a status, not words | It is still working. Call it again with the same file after `retry_after_s`. |
| A line shows a little early or late | The agent moves that slide with `move_caption_slide` and `delta_seconds`, or moves one edge with `trim_caption_slide`. |
| Exit code 6, or `status` is `held` | A safety review, not a refusal. Wait, then run `invideo status <url> --wait` again. Do not resend. If it ends `failed`, change the input the review flagged. |
| Avoid: running `generate` again to check on it | Instead: `read` the thread URL, or run `invideo status --wait`. |

## Prompts to try

Each prompt is a real variation of the job.

```text
Make a 9:16 lyric clip of the chorus of ./song.mp3, from 0:45 to 1:15,
for a TikTok teaser. Big bold words, one or two at a time, each one
landing on a beat.
```

```text
Put the lyrics over my band's live video ./live.mp4 in a kinetic style.
Price lyrical_videos.video first and show me the styles I can pick.
```

```text
Make a karaoke version: the words of each line in white, building up
as they are sung, two lines on screen at once, on a dark blurred
version of my cover art. 16:9.
```

## When to use something else

invideo burns the lyrics into the video. For a synced lyrics file on Spotify or Apple Music, submit the lyrics through your distributor or a lyrics service such as Musixmatch. The agent can write the line times out of the caption slides for you.

## Related

- [Make a Spotify Canvas and a music visualizer](/how-to/creators/spotify-canvas-and-visualizer)
- [Make a music video with lip sync and beat sync](/how-to/film/music-video-lip-sync)
- [Add word-by-word captions](/how-to/creators/word-by-word-captions)
- [Make an intro, welcome video, outro or logo animation for your channel or business](/how-to/creators/intro-outro-logo-animation)
- [Know the cost before you spend](/how-to/cost-before-you-spend)