---
title: Make a quote video, nature loop or welcome video
description: A quote video, a calm nature loop or a welcome video from stock footage, with your text on screen and music that clears rights.
---
You get a quote video, a motivational video, a nature or background loop, or a welcome video for your page. Stock footage of a forest, the sea or rain plays under your words, soft music runs under it, and the loop runs as long as you need with no hard jump at the seam.

Use invideo for this when you want licensed stock and music in the same place as the edit: your agent finds the footage, loops it to the length you need, and every word stays editable on the timeline.

## Quickest path

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

```text
Make a 30 second quote video, 9:16. Find calm stock footage of a misty
forest and soft piano music. Show "Small steps every day." in the
center, with "Maya Sen" under it, smaller. Fade the words in and out.
Price the stock first and show me frames before you export.
```

In a chat app, you watch the stock previews in the chat and pick one. In a terminal, the agent lists the results with their preview links, and you open them.

## What you need

Most of this job is words and a mood. You do not need your own footage.

- **The text.** The quote and the author, or the welcome lines for your page.
- **The scene.** Forest, ocean, rain on a window, clouds, a fire, a city at night.
- **The music or the mood.** Calm, uplifting, cinematic, or no music.
- **The length.** A number of seconds for a post, or a longer loop for a background screen.
- **The shape.** 9:16 for Reels, Shorts and TikTok, 1:1 for a feed, 16:9 for YouTube.

## Steps

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

### Build the loop

The agent gets the footage to the length you want first.

<Steps>
<Step title="Make the timeline and a branch">
The agent makes a project with `new_project` and a timeline with `new_timeline` in the shape you asked for. It creates a branch with `timeline_history`. Expected result: a timeline URL on the new branch.
</Step>
<Step title="Find the footage and the music">
The agent searches `stock_video` and `stock_music` with `search_library`, using words such as "misty forest morning" or "soft piano". Each result has a low-resolution preview. It can look closer with `media` `video.frames` and the `stock` id. Expected result: a short list of results with previews for you to pick.
</Step>
<Step title="Generate the scene when stock has no fit">
The agent runs `find_models` with the scene in words. A good pick is `seedance_2_5.frames`. Your agent can search for others with `find_models`. The agent reads its inputs with `get_model`. It passes one still of the scene as both `first_frame` and `last_frame`, so the clip loops cleanly, and sets `generate_audio` false. The clip takes the still's shape, so the agent first fits the still to your shape with `media` `image.resizeAspectRatio` on the still's sequence URL from `add_media`. 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`. It gets a thread URL, and reads the result with `read`, never with a second `generate`. Expected result: a sequence URL for the scene.
</Step>
<Step title="Add the stock you picked">
The agent adds each pick with `add_media` and `stock`, on the timeline URL. Paid stock shows invideo's stock mark on the timeline, and the export renders it clean. Expected result: a sequence URL and a media bin id for each one.
</Step>
<Step title="Loop the footage to the length">
The agent runs `media` `video.loop` on the clip's sequence URL with `target_duration` in seconds. It makes a new looped file beside the clip. For a softer seam on the timeline, the agent instead copies the clip with `duplicate_clip` and adds a `fade` at the cut with `add_transition`. Expected result: footage that runs the full length.
</Step>
</Steps>

### Finish and export

Then the agent adds the words and the music, and checks the loop.

<Steps>
<Step title="Add the words">
The agent adds the quote with `add_text` and the author as a second text clip, smaller. It sets the style with `set_text_style`, colors one word with `style_text_range` if you ask, and fades each line in and out with `set_keyframes` on `opacity`. Expected result: the words appear and leave gently.
</Step>
<Step title="Lay the music under it">
The agent places the music with `insert_media` on an audio track, sets its level with `set_audio_gain`, and fades it in and out with `set_audio_fades`. Expected result: music that starts soft and ends soft.
</Step>
<Step title="Check before export">
The agent runs `capture` at the start, each line of text, the seam of the loop and the end, and runs `check_timeline`. It checks that the text is readable on a phone. Expected result: clean frames with no jump at the seam.
</Step>
<Step title="Export">
The agent runs `export` with `format` video and reads the export card until its status is `completed`. Expected result: a 1080p MP4.
</Step>
</Steps>

The same job from a terminal:

```bash
invideo project new --title "Quote: small steps"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo edit new <project-url> --name "Quote" --width 1080 --height 1920
invideo library search <project-url> --library stock_video \
  --query "misty forest morning"
invideo library search <project-url> --library stock_music \
  --query "soft piano"
invideo asset add <edit-url> --stock <video-id>
invideo asset add <edit-url> --stock <music-id>
invideo asset upload <edit-url> ./still.jpg
invideo gen models <project-url> --query "seamless looping nature scene"
invideo gen model <project-url> --model seedance_2_5.frames
invideo gen run <page-url> --model seedance_2_5.frames --dry-run \
  -i prompt="mist drifting through a pine forest" \
  -i first_frame=<sequence-url> -i last_frame=<sequence-url> \
  -i generate_audio=false
invideo gen run <page-url> --model seedance_2_5.frames \
  --max-credits <price> --idempotency-key forest-1 \
  -i prompt="mist drifting through a pine forest" \
  -i first_frame=<sequence-url> -i last_frame=<sequence-url> \
  -i generate_audio=false
invideo media video.loop <sequence-url> --target-duration 30 \
  --dry-run
invideo media video.loop <sequence-url> --target-duration 30 \
  --max-credits <price> --idempotency-key quote-loop-30
invideo edit show <edit-url> --json
invideo branch new <edit-url> --name quote-v1 --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,3,10,20,29.9 --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
```

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`.
- `<video-id>` and `<music-id>` are stock result ids from `invideo library search`.
- `<sequence-url>` in the `gen run` lines is the still's: the `url` that `invideo asset upload` returns, 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`.
- `<sequence-url>` in the `video.loop` lines is the footage's: the `url` that `invideo asset add` returns for stock, or the generated scene that `invideo notebook show` lists on the `gen run` thread. `video.loop` refuses a bin `--asset`.
- `<state_token>` comes from `invideo edit show`: on `<edit-url>` for the branch, then on `<branch-url>` for the change. `<branch-url>` is the `.branch_url` that `invideo branch new` returns.
- `@ops.json` reads a bare JSON list of ops, the `operations` list of the `edit_timeline` 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 loop as one `media` call. The agent sends it first with `dry_run: true`, then with `max_credits` set to the price. `<sequence-url>` is the footage's sequence URL from `add_media`.

```json
{
  "url": "<sequence-url>",
  "op": "video.loop",
  "target_duration": 30,
  "idempotency_key": "quote-loop-30"
}
```

The key `edit_timeline` call, shown as the full call, places the loop, adds the quote and lays the music. `<branch-url>` is the branch URL `timeline_history` returns, and `<state_token>` comes from `get_timeline` on it. `m3` is the looped file's bin id and `m2` the music's, from `get_timeline`. At 30 fps, one second is 30 frames.

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [
    { "op": "insert_media", "asset": "m3", "track": "V1", "at_f": 0,
      "ripple": false },
    { "op": "add_text", "track": "V2", "start_f": 30, "end_f": 840,
      "text": "Small steps every day.",
      "style": { "font_size": 88, "align": "center",
                 "color": "#ffffff", "shadow": true } },
    { "op": "insert_media", "asset": "m2", "track": "A2", "at_f": 0,
      "ripple": false }
  ]
}
```

The agent also sends `set_keyframes` on the text clip's `opacity`, `set_audio_gain` with `gain_db` on the music clip, and `set_audio_fades` with `fade_in_f` and `fade_out_f`. These can go later in the same call, with the clip ids the call makes, or in a second call after `get_timeline`.

## 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:** searching stock, looking at previews, the text, the fades, the checks and the export.
- **Tool ops:** `video.frames`, `video.loop`, `image.resizeAspectRatio`. The dry run shows its price before anything starts.
- **Costs credits:** each paid stock clip and stock song you add, and a scene made with `seedance_2_5.frames`. Your own footage and music cost nothing.

A quote video takes the agent a few minutes. Picking the footage takes you longer. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Most problems come from a hard jump at the loop seam, or text that is hard to read on a busy background.

| 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. |
| `invideo status --wait` ends and the folder is empty | `status` does not download. Run `invideo notebook show <card-url> --out <dir>`. |
| The seam shows in the check frames | Make the crossfade longer with `resize_transition`, then `capture` the seam again. |
| A jump where the loop starts again | Use `duplicate_clip` and a `fade` with `add_transition` at the cut, or pick footage with little motion. |
| The text is hard to read | Add `shadow` or a `background_color` with `set_text_style`, or pick a calmer clip. |
| `media` with `stock` refused for `video.loop` | A stock result has no thread. Add it with `add_media` first, then loop its sequence URL. |
| The music ends before the video | Ask for a longer song, or loop the song on the timeline the same way. |
| CLI exit 7 | Nothing ran. Run the same command again. |
| Avoid: the agent edits main | Instead: it works on a branch from `timeline_history`, and you accept the change. |
| Avoid: saying done without looking at the seam | Instead: `capture` a frame at the seam and one a second after it. |

## Prompts to try

Each prompt is a real variation of the job.

```text
Make a one hour rain-on-a-window loop for a study stream, 16:9. No
music, only the rain sound from the stock clip. Loop it so the seam
does not show.
```

```text
Make five quote videos for this week from the quotes in ./quotes.txt.
Ocean footage, the same font and layout on each, one timeline per
quote. 9:16.
```

```text
Make a welcome video for my yoga studio's page: sunrise over hills, the
lines "Breathe", "Move", "Rest", then our name. Soft music, 20 seconds,
1:1.
```

## When to use something else

invideo exports an MP4 that plays once. A screen at a venue or a stream has to repeat the file itself. Set the player or the streaming tool, such as OBS, to loop the file.

For many quote videos at once, see the bulk sleep and meditation page, which runs one timeline per row.

## Related

- [Make sleep, rain and meditation videos for a faceless channel, in bulk](/how-to/creators/sleep-and-meditation-videos-in-bulk)
- [Add music and sound effects that clear rights](/how-to/creators/music-and-sound-effects)
- [Make an intro, welcome video, outro or logo animation for your channel or business](/how-to/creators/intro-outro-logo-animation)
- [Make a Spotify Canvas and a music visualizer](/how-to/creators/spotify-canvas-and-visualizer)
- [Licensing and commercial use](/account/commercial-use)
- [Check your video before you say it is done](/how-to/check-your-video)