---
title: Make a faceless YouTube video yourself, with AI help
description: A faceless YouTube video from your own script: an AI voiceover, b-roll, captions and music under the voice, on a timeline you review.
---
You get a faceless YouTube video, also called a no-face or voiceover video, from a script you wrote. An AI voice reads your script, stock or generated b-roll plays under it, captions follow the words, and music sits under the voice. You review every cut on the timeline before it goes out.

Use invideo for this when you want to stay the editor: your agent builds the first cut from your script, and you change any line, shot or caption after on the timeline.

This page makes the video. It does not promise views, subscribers or money from a channel. Those depend on your ideas, your script and your audience.

## Quickest path

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

```text
Make a faceless YouTube video from my script in ./script.md. Use a calm
male narrator voice, stock b-roll for each paragraph, word-by-word
captions, and soft music that ducks under the voice. 16:9. Work on a
branch and show me frames before you export.
```

In a chat app, you paste the script into the chat. In a terminal, you name the file and the agent reads it.

## What you need

Write the script yourself, or edit a draft until it sounds like you.

- **The script.** Plain text, one paragraph per scene.
- **The voice.** A mood ("calm", "warm", "energetic"), or a voice you pick from the previews. Your own voice also works: give the agent a short clean clip of you, and it clones it with `seed_audio_1_0.ta2a`, with the clip as `reference_audio_1` and your lines tagged `@Audio1` in the prompt.
- **The b-roll.** Your own clips, stock footage, or shots to generate from a prompt.
- **The music.** A mood, a track from stock music, or your own file if you have the rights.
- **The shape.** 16:9 for YouTube. Add a 9:16 version for Shorts later.

## Steps

Your agent edits on a branch and tells you what changed at each step.

<Steps>
<Step title="Make the voiceover">
The agent runs `find_models` with "narration voice" and reads its pick with `get_model`. A good pick is `elevenlabs_text_to_speech.elevenlabs_tts_v4`. Your agent can search for others with `find_models`. It searches `elevenlabs_text_to_speech_voices` with `search_library`, for example "calm male narrator", and you pick a voice from the previews. It calls `generate` with `dry_run: true`, your script as `prompt` and the voice as `voice_id`, 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`, one file per section for a long script. Expected result: a sequence URL for each voice file, which the agent reads with `read`.
</Step>
<Step title="Get the word times">
The agent prices `audio.transcript` with `dry_run` on each voice file's sequence URL, starts it with `max_credits`, and calls again after `retry_after_s` for the words. Expected result: every word with its start and end time, for the captions and the b-roll cuts.
</Step>
<Step title="Find the b-roll">
For each paragraph, the agent searches `stock_video` with `search_library` and looks at the results with `media` and `video.frames`. For a shot stock does not have, it runs `find_models` with the shot in words and reads its pick with `get_model`. A good pick is `seedance_2_5.r2v`, where a `prompt` alone is enough. 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: one or more sequence URLs for each paragraph, which the agent reads with `read`.
</Step>
<Step title="Build the timeline">
The agent makes a 16:9 timeline with `new_timeline` and a branch with `timeline_history`. It places the voice files in order with `insert_media`, then the b-roll with `place_media`, each cut on the first word of its paragraph. Expected result: a first cut where the pictures change with the script.
</Step>
<Step title="Add the captions">
The agent adds the words with `add_captions`, with slides and the word times from the transcript. It styles them with `set_text_style`: font, size and color. The words of each line build up as they are spoken. Expected result: a caption clip that follows the voice word by word.
</Step>
<Step title="Put the music under the voice">
The agent adds a track from `stock_music` with `add_media` and `stock`, after you pick it from the previews. It adds the `ducker` effect to the music track with `add_audio_effect`. A new timeline ducks under `A1` by default, so the agent sets `set_sidechain_source` only when the voice is on another track. Expected result: the music drops when the voice speaks and comes back in the gaps.
</Step>
<Step title="Check, then hand it to you">
The agent runs `capture` at each paragraph, `check_timeline`, and `audio.loudness` on the voice. Then it gives you the timeline URL. You watch it, change what you want, and ask for the export. The agent runs `export` with `target_lufs` for YouTube. Expected result: an export card with status `completed`.
</Step>
</Steps>

The same job from a terminal:

```bash
invideo project new --title "Faceless: ocean facts"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo library search <project-url> \
  --library elevenlabs_text_to_speech_voices --query "calm narrator"
invideo gen models <project-url> --query "narration voice"
invideo gen model <project-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4
invideo gen run <page-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4 --dry-run \
  -i prompt=@part1.txt -i voice_id=<voice-id>
invideo gen run <page-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4 \
  --max-credits <price> --idempotency-key part1 \
  -i prompt=@part1.txt -i voice_id=<voice-id>
invideo status <thread-url> --wait
invideo notebook show <thread-url>
invideo media audio.transcript <sequence-url> --dry-run
invideo media audio.transcript <sequence-url> \
  --max-credits <price>
invideo library search <project-url> --library stock_video \
  --query "deep ocean whale"
invideo edit new <project-url> --name "Ocean facts v1"
invideo edit show <edit-url> --json
invideo branch new <edit-url> --name first-cut --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 0,12,40,75 --out ./frames
invideo edit check <branch-url>
invideo edit export <branch-url> --format video --quality 1080 \
  --target-lufs -14 --json
invideo status <card-url> --wait
invideo notebook show <card-url> --out ./out
```

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`.
- `<voice-id>` comes from the `elevenlabs_text_to_speech_voices` search. It works only with `elevenlabs_text_to_speech.elevenlabs_tts_v4`.
- `<page-url>` is a notebook page of the project, listed by `invideo notebook show <project-url>`. `<price>` is each dry run's `.estimated_credits`.
- `<thread-url>` is the `url` the paid `gen run` returns. `<sequence-url>` is the voice file that `notebook show` on it lists.
- `<state_token>` comes from `invideo edit show <edit-url>` for the branch command. `<branch-url>` is the `.branch_url` that `invideo branch new` returns. For `edit change`, `<state_token>` is read again with `invideo edit show <branch-url>`.
- `@ops.json` reads 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 puts the music under the voice. The voice is on `A1`, the default duck source on a new timeline, so no `set_sidechain_source` is needed. This is the full call. `<branch-url>` is the branch URL that `timeline_history` returns, and `<state_token>` comes from `get_timeline` on it.

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [
    { "op": "add_audio_effect", "target": "A2",
      "target_kind": "track", "effect": "ducker" },
    { "op": "set_audio_gain", "target": "A2",
      "gain_db": -12 }
  ]
}
```

## 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.

- **Costs credits:** the voice with `elevenlabs_text_to_speech.elevenlabs_tts_v4`, the transcript of the voice, paid stock clips and music, and any b-roll from `seedance_2_5.r2v`.
- **Tool ops:** `video.frames`, `audio.loudness`. The dry run shows its price before anything starts.
- **Free:** searching stock, the edits, the captions, the ducking and the export.

The first cut takes the agent a few minutes. Your review takes longer, and it is the part that makes the video yours. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Most problems come from b-roll that does not match the words, or a voice that reads a name wrong.

| 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. |
| 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. |
| The voice says a name wrong | Spell it the way it sounds in the script, then make that section again. |
| The b-roll does not match a line | Name the shot you want. The agent swaps it with `place_media` mode `replace`. |
| The music is too loud under the voice | Lower it with `set_audio_gain` on the music track. |
| The music does not duck | The voice is not on `A1`. Point `set_sidechain_source` at the voice track, not the music track. |
| `invideo status --wait` ends and the folder is empty | `status` does not download. Run `invideo notebook show <card-url> --out <dir>`. |
| `generate` refused for `max_credits` | The price is above the cap. Run the dry run again and agree a new cap. |
| CLI exit 5 | A limit on credits or quota. Check the balance. |
| Avoid: calling `generate` again to check on the voice | Instead: `read` the sequence URL, or call `status`. |
| Avoid: exporting before you watch it | Instead: you review the timeline first. The agent exports when you say so. |

## Prompts to try

Each prompt is a real variation of the job.

```text
Turn my history script into a faceless video with a deep, slow voice,
old map and archive stock footage, and captions at the bottom.
```

```text
Use my cloned voice for this script. Generate b-roll for the two scenes
stock cannot show, and use stock for the rest. Show me each shot first.
```

```text
Make a 9:16 Shorts version of the first minute of my faceless video,
with big captions in the middle and the same music.
```

## When to use something else

invideo does not upload to YouTube or pick titles and tags. Export the MP4 and upload it in YouTube Studio. YouTube asks you to label realistic AI-made content when you upload. Read its rules in YouTube Studio before you publish.

For a thumbnail, see the thumbnail page below. For a voice actor's own recording, record it in a tool like Audacity, then bring the file in with `add_media`.

## Related

- [Make a thumbnail for the video](/how-to/creators/thumbnail)
- [Add word-by-word captions](/how-to/creators/word-by-word-captions)
- [Add music and sound effects that clear rights](/how-to/creators/music-and-sound-effects)
- [Edit a long YouTube video](/how-to/creators/edit-a-long-youtube-video)
- [Know the cost before you spend](/how-to/cost-before-you-spend)