---
title: Build an assembly cut from a script by matching lines to takes
description: An assembly cut, or string-out, of your takes in script order: your agent matches each script line to its take and lays them out on a branch.
---
You get an assembly cut, also called a rough cut, string-out or selects reel, from your script and your takes. Your agent finds where each line of the script was said in your footage, picks the take to use, and lays the takes out in script order. You then watch it and choose the takes you like better.

Use invideo for this when you have many takes and no time to log them: your agent reads every take's transcript, matches it to the script, and puts the result on a branch you can change clip by clip.

## Quickest path

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

```text
Make an assembly cut of my short film. The script is ./script.pdf and
the takes are in ./footage. Match each line to its takes, use the last
full take of each line, put them in script order with a marker per
scene, and work on a branch. Give me a list of the takes you picked.
```

In a chat app, you drag the script and the takes into the project's Media panel in invideo. In a terminal, you name the folder and the agent uploads it.

## What you need

The agent needs the words of the script and the sound of every take.

- **The script.** A PDF, a Word file, or plain text, with scene headings and the speaker of each line.
- **The takes.** MP4 or MOV from the camera. Each take needs sound, from the camera or a separate recorder.
- **Separate sound, if you have it.** WAV files from the recorder. The agent lines them up with the camera sound.
- **Your rule for the take.** The last full take, the take the slate marks as good, or the take you name.
- **The shape.** The shape of your camera files. The assembly keeps it.

## Steps

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

<Steps>
<Step title="Bring in the takes and the script">
For each big file, the agent calls `start_upload`, sends the bytes, then calls `add_media` with the `upload_id` and the timeline URL. For a PDF script, it runs `media` with `document.pages` if it needs to look at the pages. Expected result: a sequence URL for each take, and the script in the project's files.
</Step>
<Step title="Get a transcript of every take">
The agent prices `audio.transcript` on each take with `dry_run` and tells you the total price. It starts each transcript, and calls again after `retry_after_s` for the words. Expected result: every word of every take, with times and speakers.
</Step>
<Step title="Match the lines and pick the takes">
The agent reads the script and each transcript and finds where each line was said, in which take and at what time. It picks one take per line by your rule: the last full take, by default. It writes the match into a brief made with `new_brief`, as a table with `write`: line, take, start, end. Expected result: a brief URL with one take per line, the other takes as options, and the lines with no match at the top.
</Step>
<Step title="Line up separate sound">
For sound recorded apart from the camera, the agent finds the same words in the camera sound and the recorder file with `audio.transcript`. It checks the line-up with `audio.waveform`. It places the recorder file at the same time and mutes the camera sound with `set_audio_muted`. Expected result: the clean sound plays with each picture, in sync.
</Step>
<Step title="Lay out the string-out on a branch">
The agent makes a timeline with `new_timeline` and a branch with `timeline_history`. It places each picked take in script order with `place_media`, from the line's start to its end, with its sound. For a take used whole, it uses `insert_media`. It adds a marker per scene with `add_marker`, named after the scene heading. Expected result: the full script in order, scene by scene.
</Step>
<Step title="Voice a scratch line for a line with no take">
The agent runs `find_models` with "scratch voice line", picks one, and reads its inputs with `get_model`. A good pick is `elevenlabs_text_to_speech.elevenlabs_tts_v4`. Your agent can search for others with `find_models`. It picks a `voice_id` with `search_library`, 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`, and names the clip "TEMP" with `rename`. Expected result: a sequence URL for the scratch line, which the agent reads with `read`.
</Step>
<Step title="Check, then hand it to you">
The agent runs `capture` at the first frame of each scene and `check_timeline`. It tells you the timeline URL, the brief URL, and the lines with no take. You watch the cut and name the takes to swap. Expected result: a string-out you can review in the editor.
</Step>
</Steps>

The same job from a terminal:

```bash
invideo project new --title "Short film assembly"
invideo agent register <project-url> --name <name>
export INVIDEO_AS=<name>
invideo edit new <project-url> --name "Assembly v1" --fps 24
invideo asset upload <edit-url> ./footage/*
invideo asset upload <project-url> ./script.pdf
invideo media audio.transcript <sequence-url> --dry-run
invideo media audio.transcript <sequence-url> --max-credits <price>
invideo context new-brief <project-url> --name "Line to take" \
  --description "Each script line, its take, start and end"
invideo edit show <edit-url> --json
invideo branch new <edit-url> --name assembly --token <state_token>
invideo edit show <branch-url> --json
invideo edit change <branch-url> --timeline T1 \
  --token <state_token> --operations @stringout.json
invideo edit capture <branch-url> --at 0,48,102 --out ./frames
invideo library search <project-url> \
  --library elevenlabs_text_to_speech_voices --query "calm male"
invideo gen models <project-url> --query "scratch voice line"
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="We should have left an hour ago." -i voice_id=<voice-id>
invideo gen run <page-url> \
  --model elevenlabs_text_to_speech.elevenlabs_tts_v4 \
  --max-credits <price> --idempotency-key scratch-line-14 \
  -i prompt="We should have left an hour ago." -i voice_id=<voice-id>
invideo status <thread-url> --wait
invideo edit check <branch-url>
```

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`.
- `<sequence-url>` is the `url` that `invideo asset upload <edit-url>` returns for each take, a `…/notebook/seq-N.M` sequence URL. A transcript refuses a bin `--asset`.
- `<price>` is the dry run's `.estimated_credits` (or `credits` on a `media` op), for each paid line.
- `<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>`. The main token is stale on the branch.
- `<thread-url>` is the `url` the paid `gen run` returns.
- `@stringout.json` reads a bare JSON list of ops, the `operations` list of the call below.
- `<voice-id>` comes from the `elevenlabs_text_to_speech_voices` search, and `<page-url>` is a notebook page listed by `invideo notebook show <project-url>`.

The key MCP call, in full, lays out two lines from two takes and marks the scene. `<branch-url>` is the branch URL that `timeline_history` returns, and `<state_token>` comes from `get_timeline` on it. `<take-3-url>` and `<take-7-url>` are the sequence URLs that `add_media` returns for takes 3 and 7. At 24 fps, a line that runs 3.5 seconds is 84 frames.

```json
{
  "url": "<branch-url>",
  "timeline_id": "T1",
  "expected_state_token": "<state_token>",
  "operations": [
    { "op": "add_marker", "at_f": 0, "name": "Scene 1. Kitchen, night",
      "note": "Lines 1 to 2" },
    { "op": "place_media", "asset": "<take-3-url>", "track": "V1",
      "at_f": 0, "source_start_seconds": 12.4,
      "source_end_seconds": 15.9, "mode": "overwrite",
      "linked_audio": true },
    { "op": "place_media", "asset": "<take-7-url>", "track": "V1",
      "at_f": 84, "source_start_seconds": 31.0,
      "source_end_seconds": 35.2, "mode": "overwrite",
      "linked_audio": true }
  ]
}
```

To swap a take, the agent uses `place_media` with mode `replace` and the clip to replace in `replace_clip`.

## 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:** one transcript per take with `audio.transcript`. Only the first call spends credits. A scratch line made with `elevenlabs_text_to_speech.elevenlabs_tts_v4` costs credits too.
- **Tool ops:** `audio.waveform`, `document.pages`. The dry run shows its price before anything starts.
- **Free:** uploads, the match, the string-out, the markers, the checks and the export.

The transcripts take a few minutes. The match and the string-out take the agent a few minutes more. Your review of the takes takes the longest. See [Credits and what they cost](/account/credits) for prices.

## If something goes wrong

Most problems come from takes with no clear sound, or lines that the actors changed.

| 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. |
| `audio.transcript` refused with `code: "not_found"`, or CLI exit 2 | The call named a media bin asset. Pass the take's sequence URL from `add_media` or `invideo asset upload`. |
| CLI exit 3 right after `invideo branch new` | The main token is stale on the branch. Run `invideo edit show <branch-url>` and use its `state_token`. |
| A line has no match | The actor changed the words. Tell the agent the take, or ask it to match by meaning. |
| The wrong take was picked | Name the take. The agent swaps it with `place_media` mode `replace`. |
| A take has no sound | It cannot be matched by words. Name the line it belongs to, or match it by the slate in `video.frames`. |
| Separate sound drifts out of sync | Check with `audio.waveform` at the start and end, and place the sound again from the matched word. |
| The transcript call returns status running | Call `audio.transcript` again with the same file after `retry_after_s`. |
| CLI exit 3 | The timeline changed since your read. Run `invideo edit show` and rebuild. |
| Avoid: laying the string-out on main | Instead: a branch from `timeline_history`, so the person accepts it. |
| Avoid: one transcript call for each word search | Instead: transcribe once, then read the words as many times as you need. |

## Prompts to try

Each prompt is a real variation of the job.

```text
Use the takes the script supervisor marked as good in ./notes.csv, not
the last take. Put the other takes for each line on a track below.
```

```text
Make a selects reel of every take of scene 4, in order, with a marker
on each take that says which lines it covers.
```

```text
My interview has no script. Use this outline instead, ./outline.md,
and lay out the answers that match each point, best answer first.
```

## When to use something else

invideo has no sync by timecode. If your camera and recorder share timecode, sync them in DaVinci Resolve or Premiere Pro first, then bring the synced files in.

For the fine cut after the assembly, keep editing on the timeline, or export a project for your editor with `export` and `format` set to `premiere`, `final_cut` or `davinci`.

## Related

- [Export to Premiere Pro, DaVinci Resolve or Final Cut Pro](/how-to/export-to-premiere-resolve-final-cut)
- [Cut ums, pauses and retakes, as reversible timeline edits](/how-to/creators/cut-ums-and-retakes)
- [Edit a long YouTube video](/how-to/creators/edit-a-long-youtube-video)
- [Edit a podcast episode and pull clips from it](/how-to/creators/podcast-edit-and-clips)
- [Check your video before you say it is done](/how-to/check-your-video)