---
title: Turn a blog post URL into a video
description: Give your agent a blog post URL and get a narrated video: a script you approve, a voiceover, stock b-roll, text slides, captions and music.
---
Turn a blog post, article or newsletter issue into a video: blog to video, article to video, text to video from a URL. Your agent reads the post, writes a short script, and shows it to you first. Then it voices the script, lays stock b-roll and text slides under it, adds captions, and puts music under the voice that dips when the voice speaks.

Use invideo for this when you want the result on an editable timeline: you can swap one shot, fix one caption, or re-voice one line without starting over.

Use your own posts, or posts you have the right to adapt. The agent never publishes the video. You post it.

## Quickest path

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

```text
Turn https://acme.com/blog/onboarding-tips into a 60-second video.
Read the post, write a script in my voice, and show it to me before you
spend anything. Then voice it, use stock b-roll and text slides for the
key points, add captions and soft music under the voice. 16:9 and 9:16.
```

In a chat app or a terminal, the prompt is the same. The agent reads the URL with its own browser or fetch tool.

## What you need

These are the inputs the agent asks for.

- The post's URL, or the text pasted into the chat if the post is behind a login.
- The length: 30, 60 or 90 seconds.
- A voice. The agent finds one with `search_library` and sends you the preview.
- The shape: 16:9 for YouTube and your site, 9:16 for Reels, Shorts and TikTok.
- Your logo and brand colors, if you want them on the slides.
- A music mood, or no music.

## Steps

The agent works on a branch, so the main timeline stays clean until you approve.

<Steps>
<Step title="Read the post">
invideo does not fetch web pages. The agent reads the URL with its own browser or fetch tool, outside invideo. Expected result: the post's title, headings and main points in the chat.
</Step>
<Step title="Write the script and wait for your yes">
The agent writes a short script from the post: a hook, three or four key points, and a closing line. It marks which point each slide shows. It shows you the script before it spends anything. Expected result: your yes, or your changes.
</Step>
<Step title="Voice the script">
The agent runs `find_models` with the job in words, picks one, and reads its inputs with `get_model`. A good pick is `elevenlabs_text_to_speech.elevenlabs_tts_v4`. It picks a voice from `search_library` in `elevenlabs_text_to_speech_voices` and sends you the preview. It calls `generate` with `dry_run: true`, the script as `prompt` and that `voice_id`, and tells you the price. Then it calls `generate` with `max_credits` set to that price and an `idempotency_key`. Expected result: a sequence URL with the full script as audio. The agent reads that URL with `read` and never calls `generate` again to check.
</Step>
<Step title="Get the word times">
The agent prices `audio.transcript` on the voice's sequence URL with `dry_run`, starts it with `max_credits` set to that price, and calls again after `retry_after_s` for the words. Expected result: every word with its start and end time, which set the captions and the slide changes.
</Step>
<Step title="Find b-roll and build the timeline">
The agent searches `search_library` in `stock_video` with words from each point, and brings the picks in with `add_media` and `stock`. It makes a timeline with `new_timeline`, creates a branch with `timeline_history`, places the voice with `insert_media`, and the shots with `place_media`. It adds text slides for the key points with `add_text`. Expected result: a new `state_token` and a shot or slide under every line.
</Step>
<Step title="Add captions and music">
The agent adds the words with `add_captions`. It finds music in `stock_music`, places it on `A2` (Music) with `insert_media`, and adds `ducker` to that track with `add_audio_effect`. A new timeline ducks under `A1` (Voice) by default, so the ducker follows the voice with no more setup. The agent sets `set_sidechain_source` only when the voice sits on another track. Expected result: music that drops under the voice and comes back between lines.
</Step>
<Step title="Check, then export">
The agent runs `capture` at each slide and `check_timeline`, and checks the voice and music with `audio.loudness`. Then it runs `export` with `format` video and `target_lufs`. Expected result: an export card with status `completed`.
</Step>
</Steps>

The terminal version of the same job:

```bash
invideo project new --title "Onboarding tips video"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo gen models <project-url> --query "narration voice from a script"
invideo library search <project-url> \
  --library elevenlabs_text_to_speech_voices --query "warm narrator"
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=@script.txt -i voice_id=<voice-id>
invideo gen run <page-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4 \
  --max-credits <price> --idempotency-key onboarding-voice-v1 \
  -i prompt=@script.txt -i voice_id=<voice-id>
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 "team onboarding laptop"
invideo library search <project-url> --library stock_music \
  --query "soft upbeat background"
invideo asset add <project-url> --stock <stock-id>
invideo edit new <project-url> --name "Blog video 16x9"
invideo edit show <edit-url> --json
invideo branch new <edit-url> --name blog-v1 --token <state_token>
invideo edit show <branch-url> --json
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @build.json
invideo edit capture <branch-url> --at 2,15,30,55 --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 the values come from:

- `<project-url>` comes from `invideo project new`, and `<edit-url>` from `invideo edit new`. `<name>` is the name you choose in `invideo agent register`. `<page-url>` is a notebook page from `invideo notebook show <project-url>`.
- `<voice-id>` comes from the voice search. Each `<price>` is the dry run right before it: `.estimated_credits` for the voice, `credits` for the transcript.
- `<sequence-url>` is the voice's sequence URL, which the paid voice run returns.
- `<stock-id>` is a result's id from `invideo library search`.
- `<state_token>` comes from `invideo edit show <edit-url>`, and on the branch from `invideo edit show <branch-url>`; the main token is stale there. `<branch-url>` is the `.branch_url` that `invideo branch new` returns.
- `<card-url>` is `.sequences[0]` of what the export returns. `invideo status` waits and does not download; `invideo notebook show` saves the file as `./out/export.mp4`.

The file `./build.json` holds a bare list of operations. On the MCP path, the same list goes in the `operations` field of `edit_timeline`. It adds a text slide and the first caption on `V3` (Titles), and the `ducker` on `A2` (Music). `A1` is the voice track, and a new timeline ducks under `A1` by default.

```json
[
  { "op": "add_text", "track": "V3", "start_f": 90, "end_f": 240,
    "text": "Tip 1: Send the welcome email before day one",
    "style": { "font_size": 56, "color": "#ffffff",
      "align": "center" } },
  { "op": "add_captions", "track": "V3",
    "slides": [
      { "text": "New hires decide in a week", "start_f": 0,
        "end_f": 60 }
    ] },
  { "op": "add_audio_effect", "target": "A2",
    "target_kind": "track", "effect": "ducker" }
]
```

If the voice sits on another track, the agent adds `set_sidechain_source` to the same list, with `timeline` `T1` and that track's id from `get_timeline`. It works in the same call as `add_audio_effect`.

For the 9:16 version, the agent creates a second branch, sets the size with `set_timeline_settings`, and fills the frame on each shot with `fill_frame`.

## 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: reading the post, the script, every edit, the ducker, `capture`, `export`.
- Costs credits: the TTS voice, `audio.transcript` on the voice, each stock shot and the stock music.

One video takes under an hour, most of it your review of the script. See [Credits](/account/credits) for prices.

## If something goes wrong

Most problems are a script that drifts from the post, or timing.

| 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 agent cannot open the URL | The page needs a login or blocks bots. Paste the text into the chat. |
| The script says something the post does not | Ask the agent to quote the post line behind each claim, and cut what has none. |
| The music does not drop under the voice | The ducker follows `A1` by default. If the voice is on another track, point it there with `set_sidechain_source`. |
| A slide changes mid-sentence | Move the slide's edges to word times from the transcript with `trim_clip`. |
| `timeout` on `generate` | Do not call it again. `read` the thread or call `status`. |
| CLI exit 3 | The edit changed since your read, or the token is from main. Run `invideo edit show <branch-url>` and use its `state_token`. |
| The `./out` folder is empty after `status` | `status` waits and does not download. Run `invideo notebook show <card-url> --out ./out`. |
| CLI exit 5 | Credits ran out. Stop and tell the person. |
| Avoid: voicing the script before the person reads it | Instead: show the script first. The voice costs credits. |
| Avoid: adapting someone else's article | Instead: use the person's own posts, or ask them to confirm the rights. |

## Prompts to try

Each prompt is a real variation of this job.

```text
Turn my last three posts into three 30-second Shorts, 9:16, same voice,
captions in the middle of the frame.
```

```text
Make a video from this newsletter issue. No voice: text slides and
captions only, with music. Pasted text below.
```

```text
Turn this listicle into a 90-second video with one stock shot per tip
and a title card for each number.
```

## When to use something else

invideo does not read web pages, so the reading happens in the agent's own browser or fetch tool. invideo does not publish to your blog, YouTube or social accounts. For a talking presenter instead of b-roll, see the avatar style in the video sales letter page. For a whiteboard drawing style, see the whiteboard explainer page.

## Related

- [Make a video sales letter with slides, b-roll or an avatar](/how-to/marketing/video-sales-letter)
- [Make a stock-footage commercial for your business, in short and long cuts](/how-to/marketing/stock-footage-commercial)
- [Make a faceless YouTube video yourself, with AI help](/how-to/creators/faceless-youtube-video)
- [Turn release notes or a changelog into a video every week](/how-to/training/release-notes-to-video)
- [Add music and sound effects that clear rights](/how-to/creators/music-and-sound-effects)
- [Know the cost before you spend](/how-to/cost-before-you-spend)