---
title: Update one line and re-render, keep the rest
description: Change one spoken line or one on-screen word in a finished training video, re-voice only that line, and export again with nothing else changed.
---
Update one line in a finished video: a new price, a renamed button, a changed policy. Your agent re-voices only that line, fits it into the cut, fixes the on-screen text, and exports again. Every other clip, cut and caption stays exactly as it was.

Use invideo for this when the video lives on an invideo timeline, so a one-line fix is a few edits and a free export, not a new video.

## Quickest path

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

```text
In this invideo timeline <edit-url>, the line "Click Save" is now
"Click Publish". Make a branch, re-voice only that line in the same voice,
change the on-screen text to match, keep everything else, and show me
frames around the change before you export.
```

In a chat app, you paste the timeline URL. In a terminal, the agent can find it with `invideo project list` and `invideo project show`.

## What you need

Your agent needs the old video and the new words.

- The timeline URL of the video, or the project name.
- The old line and the new line, word for word.
- The voice the video used. The agent finds it in the notebook: `read` shows the generation that made the narration and its inputs.
- Any on-screen text that should change too.

## Steps

The agent never edits main for this job. It works on a branch you can compare and throw away.

<Steps>
<Step title="Branch first">
The agent reads the timeline with `get_timeline`, then creates a branch with `timeline_history` action `create_branch`. It sees the new branch's URL. Every later edit goes to that URL.
</Step>
<Step title="Find the line">
The agent prices `audio.transcript` with `dry_run` on the narration's sequence URL and tells you the price. For a price of 1 credit or more it waits for your yes. Then it starts the transcript with `max_credits` set to that price, and calls again after `retry_after_s` for the words. When the timeline has captions, it reads the caption slides with `get_timeline` mode `collection` instead. It sees the clip id and the frames where the line sits.
</Step>
<Step title="Re-voice only that line">
1. The agent runs `read` on the thread that made the original narration, for its model id and `voice_id`, such as `elevenlabs_text_to_speech.elevenlabs_tts_v4`. It reads the model's inputs with `get_model`. If the model is gone, it runs `find_models` with the job in words and tells you the voice may change.
2. It calls `generate` with that model, that `voice_id`, the new line as `prompt` and `dry_run: true`, and tells you the price.
3. 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, when the result lands, a sequence URL. It reads that URL with `read` and never calls `generate` again to check.
</Step>
<Step title="Fit it into the cut">
The agent splits the old line out with `split_clip`, opens any extra time the new take needs on every track with `edit_time_range` mode `insert`, and removes the old line with `delete_clip` mode `leave_gap`. Then it places the new take in the gap with `place_media` mode `overwrite`, so nothing else moves. It sees a new `state_token`.
</Step>
<Step title="Fix the words on screen">
The agent changes a text clip with `set_text`, or one caption slide with `set_caption_slide_text`. Words that did not change keep their timing. It sees the clip with the new words.
</Step>
<Step title="Check the seam">
The agent calls `capture` before, on and after the change, and listens with `media` `audio.waveform` on the new take for a click or a gap. It calls `check_timeline`. It sees frames and findings.
</Step>
<Step title="Export the branch">
The agent calls `export` on the branch URL with format `video`. It sees an export card with status `completed`. You compare it with the old export, and on your yes the agent makes the branch the default with `timeline_history` action `set_default`.
</Step>
</Steps>

The terminal version:

```bash
invideo edit show <edit-url> --json
invideo branch new <edit-url> --name fix-publish-line --token <state_token>
invideo media audio.transcript <narration-sequence-url> --dry-run
invideo media audio.transcript <narration-sequence-url> \
  --max-credits <price>
invideo notebook show <thread-url>
# only when the original model is gone:
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="Click Publish." -i voice_id=<voice-id>
invideo gen run <page-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4 \
  --max-credits <price> --idempotency-key fix-publish-line \
  -i prompt="Click Publish." -i voice_id=<voice-id>
invideo edit show <branch-url> --json
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @fix.json --dry-run
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @fix.json
invideo edit show <branch-url> --json
invideo edit change <branch-url> --timeline T1 --token <state_token> \
  --operations @place.json
invideo edit capture <branch-url> --at 41,43,45 --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:

- `<edit-url>` is the video's timeline URL, from `invideo edit new` or the editor. `<state_token>` comes from `invideo edit show <edit-url>` for `branch new`.
- `<branch-url>` is the `branch_url` that `invideo branch new` returns. After that, `<state_token>` comes from `invideo edit show <branch-url>`, read again before each change, because the main token is stale on the branch.
- `<narration-sequence-url>` is the narration's sequence URL, `…/notebook/seq-N.M`, from `invideo notebook show <project-url>`.
- `<thread-url>` is the thread that made the original narration, and `<voice-id>` is the `voice_id` it shows.
- `<project-url>` is the video's project. `<page-url>` is a notebook page of the project, listed by `invideo notebook show <project-url>`.
- `<price>` is each dry run's `.estimated_credits`.
- `<card-url>` is `.sequences[0]` of the export answer. `invideo status` waits but saves nothing. `invideo notebook show` saves the video as `./out/export.mp4`, and the agent renames it.

The file behind `@fix.json` is the bare list of ops that `--operations` takes, sent in one `edit change`. It lands all or none. The agent splits at the later frame first, so `c12` still covers the earlier frame. `c12` is the narration clip and `c20` the text clip, from `invideo edit show`.

```json
[
  { "op": "split_clip", "clip": "c12", "at_f": 1290 },
  { "op": "split_clip", "clip": "c12", "at_f": 1230 },
  { "op": "edit_time_range", "timeline": "T1",
    "start_f": 1230, "end_f": 1260, "mode": "insert" },
  { "op": "set_text", "clip": "c20", "text": "Click Publish" }
]
```

After the splits, the agent reads the timeline again for the new clip ids. The file behind `@place.json` then deletes the old line's clip with `delete_clip` mode `leave_gap` and places the new take with `place_media` mode `overwrite`. Its `asset` is the take's sequence URL from the paid `gen run` thread.

## 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 one new voice take, and a transcript if the video had none.
- Free: the branch, every edit, `capture` and `export`.

The whole fix takes a few minutes. See [Credits](/account/credits) for prices.

## If something goes wrong

Most problems are a voice that does not match or a stale read.

| 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. |
| CLI exit 3 on the first change after `invideo branch new` | The main token is stale on the branch. Run `invideo edit show <branch-url>` and use its `state_token`. |
| `invideo status --wait` ends and the folder is empty | `status` does not download. Run `invideo notebook show <card-url> --out <dir>`. |
| The new line sounds like another person | Use the same model and the same `voice_id` as the original take. `read` the old sequence for its inputs. |
| The picture drifts after the change | The time was changed on one track only. Use `edit_time_range`, which changes every unlocked track. |
| `split_clip` or `trim_clip` refused as stale | Read again with `get_timeline` and use the new frames. |
| CLI exit 3 | Someone edited since your read. Run `invideo edit show` and rebuild the ops. |
| A caption still shows the old words | Change that slide with `set_caption_slide_text`. `set_text` works only on a caption with one slide. |
| The new take is shorter than the old line | Take the spare time out of every track with `edit_time_range` mode `close_gap`. |
| A click at the seam | Add a short fade with `set_audio_fades` on the new take. |
| Avoid: re-generate the whole narration | Instead: re-voice only the changed line. |
| Avoid: edit main | Instead: branch with `timeline_history`, and set it default after approval. |

## Prompts to try

Each prompt is a real variation of this job.

```text
Our support email changed. Find every place the old email is said or shown
in this timeline, fix each one on a branch, and list the timecodes.
```

```text
Replace the image on the pricing slide with new-ui.png and keep its timing.
```

```text
The intro line is too long. Re-voice it shorter and close the gap, then
show me before and after frames.
```

## When to use something else

If the video was made outside invideo and you only have the MP4, the fix needs the original project. Bring the MP4 in and cut around the line, but a replaced line in mixed audio is hard to hide. For a presenter on camera, record the line again. Lip sync with `sync_lipsync_v3.video` fixes a mouth for a short span, but a new recording looks more natural.

## Related

- [Turn slides into a narrated video, one deck or hundreds](/how-to/training/slides-to-narrated-video)
- [Voice a script or narrate a course](/how-to/training/voice-a-script-or-narrate-a-course)
- [Translate a training video and retime the cut to fit the new voice](/how-to/training/translate-and-retime)
- [Check your video before you say it is done](/how-to/check-your-video)
- [Timeline edit operations](/reference/edit-operations)