---
title: Generate a missing shot or extend a clip inside your edit
description: Fill a gap in your cut with a generated shot that starts and ends on your own frames, or extend a clip that ends too early, then match it to the scene.
---
You get a new shot in the gap where your edit needs one, also called a pickup shot, an insert or a bridge shot. Or you get a clip that runs longer than the camera rolled. Your agent takes the last frame of the shot before the gap and the first frame of the shot after, generates the shot between them, places it on the timeline, and matches its color and size to the scene.

Use invideo for this when the gap is inside a cut you are still editing: the agent reads your timeline, makes the shot from your own frames, and drops it in place, and you can move or trim it like any other clip.

## Quickest path

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

```text
There is a gap at 01:12 on this timeline. Generate a shot that starts on
the last frame before the gap and ends on the first frame after it: she
walks from the door to the window. Run a dry run first and tell me the
price. Match the grade to the shots around it. Work on a branch.
```

In a chat app, you paste the timeline URL into the chat. In a terminal, you paste it into the prompt, and the agent saves the frames it checks to a folder.

## What you need

The frames around the gap do most of the work. Describe only what happens between them.

- **The timeline URL.** And the time of the gap, or the clip that ends too early.
- **What happens in the shot.** The action, the camera move, the length.
- **The look of the scene.** The LUT or grade the shots around it use.
- **Sound.** Say if the shot needs its own sound, or if the scene's sound runs under it.

## Steps

The agent works on a branch and shows you frames before and after.

<Steps>
<Step title="Read the gap">
The agent creates a branch with `timeline_history`, then reads the timeline with `get_timeline` mode `window` around the gap. It notes the clip before, the clip after, the gap's length in frames, and the timeline's frame rate. Expected result: the two clip ids and the gap's start and end frames.
</Step>
<Step title="Pull the frames on each side">
The agent runs `media` with `video.extractFrameLast` on the clip before the gap and `video.extractFrameFirst` on the clip after it. Both take the source file's sequence URL from `add_media` or `invideo asset upload`, and work on the whole source file. Expected result: two image sequences, one per frame.
</Step>
<Step title="Price the shot">
The agent runs `find_models` with the job in words, picks one, and reads its inputs with `get_model`. A good pick is `seedance_2_5.frames`, which takes a first frame and a last frame. Your agent can search for others with `find_models`. It calls `generate` with `dry_run: true` and the real inputs, and tells you the price. Expected result: a price and no errors in the inputs.
</Step>
<Step title="Generate the shot">
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 thread URL and, when the result lands, a sequence URL. The agent reads that URL with `read` and never calls `generate` again to check.
</Step>
<Step title="Place it in the gap">
The agent first compares the shot's length, from `metadata`, with the gap's length. If the shot is as long as the gap or shorter, it sends `insert_media` with the new sequence URL, the track, the gap's start frame and `ripple` false. If the shot is longer, it sends `place_media` with `source_start_seconds` and `source_end_seconds` that fit the gap. Expected result: the gap is filled, the next clip starts on its old frame, and a new `state_token`.
</Step>
<Step title="Match the size and the color">
The agent runs `media` with `metadata` on the new shot and on its neighbors. Then it applies the scene's LUT with `apply_lut`, or adds a grade with `edit_grade` and sets it with `set_grade_parameter`. Expected result: the same size and a close match in color.
</Step>
<Step title="Check, then export">
The agent runs `capture` a few frames before the gap, inside it, and a few frames after, and compares the three by eye. It runs `check_timeline`. Then it runs `export`. Expected result: no jump at either cut, and an export card with status `completed`.
</Step>
</Steps>

### Extend a clip or make a new angle

The same pattern covers two other jobs. Only the model and its inputs change.

- **A clip that ends too early.** A good pick is `seedance_2_5.extend`, with the clip as its `extend_video`. `flux_3.start_video` also continues a video.
- **A new angle of the same scene.** A good pick is `seedance_2_5.r2v`, with frames from the scene as reference images.

The agent reads each model's inputs with `get_model`, and prices each run with `dry_run: true` before the paid call.

The same job from a terminal:

```bash
invideo edit show <edit-url> --mode window --start-seconds 68 \
  --end-seconds 78
invideo branch new <edit-url> --name pickup-shot --token <state_token>
invideo edit show <branch-url>
invideo media video.extractFrameLast <before-clip-sequence-url>
invideo media video.extractFrameFirst <after-clip-sequence-url>
invideo gen models <project-url> --query "video from first and last frame"
invideo gen model <project-url> --model seedance_2_5.frames
invideo gen run <page-url> --model seedance_2_5.frames --dry-run \
  --input shot.json
invideo gen run <page-url> --model seedance_2_5.frames \
  --max-credits <price> --idempotency-key pickup-0112 --input shot.json
invideo status <thread-url> --wait
invideo notebook show <thread-url>
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @place.json
invideo edit capture <branch-url> --at 71,72.5,74 --out ./frames
invideo edit check <branch-url>
invideo edit export <branch-url> --format video --quality 1080 --json
```

Where the values come from: `<edit-url>` from `invideo edit new`. `<state_token>` comes from `invideo edit show` on the URL the next command runs on: `<edit-url>` for `branch new`, then `<branch-url>`, read fresh, because the main token is stale on the branch. `<branch-url>` is the `branch_url` that `branch new` returns. `<before-clip-sequence-url>` and `<after-clip-sequence-url>` are the `url` values that `invideo asset upload` returned for the two clips. `<project-url>` comes from `invideo project new`, and `<page-url>` from `invideo notebook show <project-url>`. `<price>` is the price the dry run returns, its `estimated_credits`, and `<thread-url>` is the `url` the paid run returns.

The file in `--input shot.json` is the bare inputs object, keyed by the names `get_model` lists, without the model id. The dry run and the paid run send the same file, so the run you priced is the run you pay for. `<frame-before-url>` and `<frame-after-url>` are the sequence URLs that the two frame pulls return.

```json
{
  "prompt": "She walks from the door to the window",
  "first_frame": "<frame-before-url>",
  "last_frame": "<frame-after-url>"
}
```

The file in `--operations @place.json` is a bare list of ops. The shot is longer than the gap here, so the ops use `place_media` with a source range. `at_f` is the gap's start frame, and the source range is as long as the gap.

```json
[
  { "op": "place_media", "asset": "<new-sequence-url>", "track": "V1",
    "at_f": 1728, "source_start_seconds": 0, "source_end_seconds": 2.5,
    "mode": "overwrite" }
]
```

`<new-sequence-url>` is the sequence URL of the generated shot, which `invideo notebook show <thread-url>` lists when the job is `completed`.

On the MCP path, the agent sends the same list as `operations` in `edit_timeline`, with the branch's URL as `url`, `timeline_id` `T1` and the branch's `expected_state_token` from `get_timeline`. The agent then reads the new clip's id with `get_timeline` and sends `apply_lut` on it in a second call.

## 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 shot from `seedance_2_5.frames`, `seedance_2_5.extend`, `flux_3.start_video` or `seedance_2_5.r2v`, and an upscale with `video.upscale`, priced per second of video.
- **Free:** the placing, the grade, `capture` and the export. The frame pulls and `metadata` show their price in the dry run before anything starts.

A shot takes a few minutes to generate. Matching it to the scene takes the agent a few minutes more. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Most problems come from the wrong frames or a shot that does not fit the gap.

| 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 shot starts on a different frame | The clip is trimmed. Check its trim with `inspect_objects`, then pull the frame with `video.extractFrameAtTime` and `time_seconds` at the trimmed edge. |
| The next clip lost its first frames | The shot was longer than the gap, and `insert_media` covered the next clip. Restore the branch's checkpoint with `timeline_history`, then use `place_media` with a source range that fits the gap. |
| `insert_media` is refused | The span is not free. Read the timeline again with `get_timeline`, then use `place_media` with a source range that fits. |
| A frame pull is refused as `not_found` | The op got a bin id like `m4`. Pass the source file's sequence URL instead. |
| The new shot looks softer than its neighbors | Compare with `metadata`, then run `video.upscale` with `dry_run` first. |
| The colors jump at the cut | Apply the same LUT with `apply_lut`, then adjust `temperature` and `exposure` with `set_grade_parameter`. |
| The generated sound clashes with the scene | Generate with the model's audio switched off, or mute the clip with `set_audio_muted`. |
| 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. |
| CLI exit 5 | A limit on credits or quota. Check the balance. |
| A call times out | Retry with the same `idempotency_key`. It finds the first generation. |
| Avoid: generating without a dry run | Instead: run `dry_run` first and tell the person the price. |
| Avoid: calling `generate` again to check on a shot | Instead: `read` the thread URL or call `status`. |

## Prompts to try

Each prompt is a real variation of the job.

```text
The shot at 00:48 ends too early. Extend it with
seedance_2_5.extend, keep the same camera move, and trim it to fit.
```

```text
I need a close-up of the letter on the table between 02:10 and 02:13.
Use frames from the wide shot as references and match the grade.
```

```text
Give me a slow push and a locked-off take of the bridge shot at 01:12,
each on its own branch. Show me frames of both.
```

## When to use something else

A generated shot is a new picture, not a recording. For a shot that must show a real place, a real product or a real person exactly, shoot a pickup with a camera and bring it in with `add_media`.

For frame-accurate VFX work, like a matte painting or rotoscoping, use a compositing tool such as Nuke or After Effects, then bring the render into the gap.

## Related

- [Make an AI short film with a character that stays the same](/how-to/film/short-film-consistent-character)
- [Color grade log footage and match shots](/how-to/film/color-grade-log-footage)
- [When to use a video effect instead of generating a new shot](/guides/when-should-i-use-a-video-effect-instead-of-generating-a-new-shot)
- [Know the cost before you spend](/how-to/cost-before-you-spend)
- [Check your video before you say it is done](/how-to/check-your-video)