rb · reference
rafabenedetti.com
the shelf · 2026
Reference · User guide

roughcut: user guide

A command-line tool for assistant editing in Final Cut Pro: it turns FCPXML exports, paper cuts and commented scripts into selects and rough-cut projects. What it does, how to install it, and every command.

roughcut is a command-line for my assistant editor that works with FCP. It reads projects or events (p.e. containing footage from a shoot) exported from FCP as an FCPXML, a paper cut, or a script with shoot comments, and builds FCP projects you import back as FCPXML and finish by hand in the app. It never touches the library or the media: it reads the exports and writes the resulting FCPXML files next to them.

It does two jobs:

JobYou give itYou get back
roughcut papercut: selects from a paper cuta stringout (an FCP project with captions, exported as XML) and a paper cut (the lines to keep, with timecodes)one FCP project per paper-cut section, each a string of selects cut from the stringout, with a marker per select and a report of anything doubtful
roughcut script: rough cuts from a scriptthe camera files, the script .docx with the client’s comments, and the event exported from FCPone FCP project per script, each paragraph cut from the take the comments point to, with every other take of it stacked above (disabled) to audition, and a marker per comment

Everything it writes is checked against Apple’s FCPXML 1.14 rules before it’s saved; a file that doesn’t pass is never written as final. It never overwrites a file: a second run writes _2, _3, and so on, or asks you for a suffix.


1. Requirements

What gets installed, and what for:

DependencyNeeded forInstalled with
Homebrewinstalling ffmpegthe installer script from brew.sh
uvinstalling roughcut, Python 3.14 and its librariesthe installer script from astral.sh
Python 3.14running roughcutuv, automatically
lxml, PyYAMLreading and writing XML and YAMLuv, automatically
xmllintchecking every file against the FCPXML 1.14 rulesalready part of macOS
ffmpeg, ffprobereading audio from camera files (--refine, script transcribe)Homebrew
mlx-whisperword-level transcription (--refine, script transcribe)uv, as its own tool
the Whisper model whisper-large-v3-turbothe samethe Hugging Face hf command

2. Installing

Open Terminal and run each step in order. Steps 1 to 4 are needed by everyone; steps 5 and 6 only if you’ll use --refine or roughcut script, which is most people.

Step 1: Homebrew

Check whether you already have it:

brew --version

If that prints a version, skip to step 2. If it says command not found, install it:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

At the end the installer prints two or three commands under Next steps that add Homebrew to your shell. Copy and run them, then close Terminal and open a new window.

Step 2: uv

curl -LsSf https://astral.sh/uv/install.sh | sh

Close Terminal and open a new window, then check:

uv --version

You don’t need to install Python yourself: uv downloads Python 3.14 the first time roughcut needs it.

Step 3: ffmpeg

brew install ffmpeg

Check it (both should print a version):

ffmpeg -version
ffprobe -version

xmllint is already on every Mac. Check it with:

xmllint --version

Step 4: roughcut

Get a copy of the code. This puts it in a Developer folder in your home folder; any folder works, but keep it, because updating means pulling into it:

mkdir -p ~/Developer
git clone https://github.com/rafabenedetti/roughcut.git ~/Developer/roughcut

If Git asks you to install the Command Line Developer Tools, accept, wait for it to finish, and run the clone again. If it asks you to sign in, sign in to the GitHub account that has access to the repository.

Install it as a command:

uv tool install ~/Developer/roughcut

If uv then warns that its tools folder isn’t on your PATH, run the command it suggests, which is usually:

uv tool update-shell

and open a new Terminal window. Check the install:

roughcut --version

It should print roughcut 0.1.0 (FCPXML 1.14 DTD ok). “DTD ok” means the copy of Apple’s FCPXML rules inside roughcut is intact; if it ever says otherwise, reinstall.

Step 5: mlx-whisper (for --refine and roughcut script transcribe)

mlx-whisper is installed as its own tool, separate from roughcut. roughcut finds it by itself.

uv tool install mlx-whisper

Step 6: the Whisper model (for --refine and roughcut script transcribe)

roughcut runs Whisper offline, so the model has to be downloaded once, beforehand. Install the Hugging Face command-line tool, then use it to fetch the model (about 1.6 GB):

uv tool install huggingface-hub
hf download mlx-community/whisper-large-v3-turbo

The model lands in ~/.cache/huggingface/hub/. You don’t need to do anything else with it.

Updating roughcut

When there’s a new version, pull it and reinstall:

git -C ~/Developer/roughcut pull
uv tool install --reinstall ~/Developer/roughcut

uv tool install installs a snapshot of the code, so changes only reach the roughcut command after --reinstall.

Uninstalling

uv tool uninstall roughcut

3. The commands

CommandWhat it does
roughcut --versionprints the version and checks the built-in FCPXML rules
roughcut --print-configprints every setting with its default and what it does
roughcut papercut STRINGOUT PAPERCUTpaper cut + stringout → selects projects
roughcut papercut STRINGOUT PAPERCUT --refinethe same, with cuts placed on the words and the pauses between them
roughcut papercut --manifest JOBS.yamlseveral paper cuts in one run
roughcut papercut --to-canonical PAPERCUTconverts an older paper cut to the current format
roughcut script transcribe PROJECTcamera files → word-level transcripts
roughcut script map PROJECTscript + comments + transcripts → a take map for you to check
roughcut script cut PROJECTthe checked take map → one FCPXML of rough-cut projects

Add --help to any of them for its options, e.g. roughcut papercut --help.

Every command ends with an exit code, which also tells you how the run went:

ExitMeaning
0done, nothing to look at
2done, with flags: things worth checking, listed in the output and the report
1failed: nothing (or not everything) was written; the message says why

Paths with spaces need quotes, or drag the file from Finder into Terminal, which quotes it for you.


4. roughcut papercut: selects from a paper cut

What you need

  1. The stringout. A project in FCP holding the interview (all the takes you’ll select from, in any order), with captions: roughcut finds each paper-cut line by matching it against the captions, so they have to be there (FCP’s Transcribe to Captions is fine). Multicam clips and plain clips both work. Select the project in the browser, choose File › Export XML…, and keep the default format (FCPXML 1.14). You get a .fcpxmld bundle; zipping it is fine too.

  2. The paper cut. The lines you want, grouped into selects, with the stringout timecode of each line. The format:

    # Jane Doe profile selects
    
    ## PROJECT_v1_JaneDoe-ProfileSelects
    
    0:07:17 – 0:07:19 Design thinking is an approach that allows you to deeply
    0:07:19 – 0:07:22 understand people in their context, synthesize
    //
    0:01:38 – 0:01:41 [Jane] I'm Jane Doe, and I am a design strategist,
    
    ## Another project from the same interview
    
    0:10:33 – 0:10:37 My wife will be embarrassed that
    • # is the document’s title; it’s ignored.
    • Each ## heading starts a project, named exactly as written. One file can hold one project or many.
    • Each line is start – end text, with the timecode of the stringout’s timeline.
    • // on its own line ends a select. A project’s selects play in the order written.
    • [Name] at the start of a line is a speaker label, not spoken text.

    The easiest way to get these lines is from a transcript made by itt-convert, which writes this format: copy the lines you want, add ## and //. Older paper cuts (a Word document with the timecode on its own line, or the August markdown tables) still work; --to-canonical converts them to this format (see below).

Running it

roughcut papercut "Jane_stringout.fcpxmld" "Jane_papercut.md"

What it writes, next to the paper cut (or in the folder given with -o):

FileWhat it is
<project>.fcpxmldone per ## project: import these into FCP
<paper cut>.report.mda readable report: each select, where it was found, and any flags
<paper cut>.report.jsonthe same, for other tools
<paper cut>.selects.jsonthe cut decisions (for troubleshooting)
failed/only if a project couldn’t be made valid: the bundle and its errors

Import in FCP with File › Import › XML… and pick the .fcpxmld. Each bundle holds one project cut from the stringout’s own clips, so it links to the media your library already has.

Options:

OptionWhat it does
-o DIRwrite the outputs in DIR instead of next to the paper cut
--suffix _v3add _v3 to the project, bundle and report names; use it to run again without overwriting
--zipwrite <project>.fcpxmld.zip instead of the bundle folder
--refineplace each cut on the words themselves (see below)
--config FILEuse settings from FILE (see Settings)

A run never overwrites. If an output already exists it stops before writing; run again with --suffix.

--refine: cuts on the words

Without it, cuts land where the captions start and end, which is close but can clip a word or leave a breath. With --refine, roughcut reads the audio around each cut from the camera files, runs Whisper on it, and puts the in just before the first word and the out in the quiet after the last one, keeping a breath when the speaker carries on. It follows a few rules learned from real listens: when in doubt it keeps more material rather than cutting a word, it never makes a jump cut inside a continuous take, and it reproduces the editor’s own camera switches exactly. Anything it can’t place with confidence falls back to the caption-based cut and is flagged.

It needs steps 5 and 6 of the install, and the camera files mounted where they were when the stringout was exported (roughcut reads the media paths inside the export). It takes a few seconds per select.

roughcut papercut "Jane_stringout.fcpxmld" "Jane_papercut.md" --refine --suffix _refined

Several paper cuts at once: --manifest

Write a YAML file listing the jobs. Paths are relative to the manifest file:

- stringout: Jane_stringout.fcpxmld.zip
  papercut: Jane_papercut.md
- stringout: Sam_stringout.fcpxmld.zip
  papercut: Sam_papercut.md
  output: selects            # optional: output folder
  suffix: _v2                # optional
  settings: {speakers: [Tom]}  # optional: settings for this job only
roughcut papercut --manifest jobs.yaml

It runs every job even if one fails, and prints a line per job plus a total.

Converting an older paper cut: --to-canonical

roughcut papercut --to-canonical "Jane_papercut.docx"

writes Jane_papercut_canonical.md next to it and checks that it reads back to the same selects. For the August style, with speaker names inline in the text, list the names in speakers: first (see Settings) so they’re stripped from the text; the output says how many were, and you can add [Name] labels back by hand where you want them.

Reading the flags

The report lists flags per select; the terminal shows how many. The ones you’ll meet:

In the reportMeaningWhat to do
tight-tail: … out clamped to the next cuethe out had to stop where the next caption startscheck the end sounds clean
tail-extra: …a few words after your last line were kept, rather than cutting mid-captiontrim in FCP if you want
refine-fallback: …--refine couldn’t place this cut on the words, so it used the captionslisten to it
block split into N segments (… at spine cuts)the select crosses a cut in the stringout, so it became several clipsusually nothing: the stringout’s own cuts are kept
kept inside the take (…s not in the paper cut): "Right."a short interjection between your lines was kept, so the take plays without a jump cuttrim it in FCP if you want
excluded unquoted/partial cues inside anchored spancaptions your paper cut skipped were cut out of the selectcheck the joins
possible duplicate take …; both kepttwo parts of the select seem to be the same words said twiceusually one take split by an FCP cut; check
ignored-child: … <analysis-marker> not carriedsomething in the stringout isn’t carried into the selectsnothing

Paper-cut problems (a line without a timecode, a timecode without text) are flagged too, with the line they’re on; the format section above says what a clean paper cut looks like.

Settings

Every setting has a sensible default. To see them all, with a comment on each:

roughcut --print-config

To change one, put it in a file called roughcut.yaml. roughcut reads, in order, each one overriding the one before:

  1. its built-in defaults
  2. ~/.config/roughcut/roughcut.yaml (your own defaults)
  3. roughcut.yaml in the paper cut’s folder (a good place for a project’s speakers:)
  4. the file given with --config
  5. a manifest job’s settings:

For example, a roughcut.yaml next to an August-style paper cut:

speakers: [Ana, Ben, Kai]

5. roughcut script: rough cuts from a script and the client’s comments

This is for scripted shoots: the talent reads numbered scripts to camera, several takes each, and the client leaves comments on the script (“2nd close-up take was good”, “use the take with the new sentence”). roughcut transcribes every take, works out which takes read which paragraph, reads the comments, proposes a take per paragraph, lets you correct it, and cuts.

It works in three commands, run in order, with a check by you in the middle:

transcribe  →  map  →  (you check and edit the take map)  →  cut  →  import into FCP

What you need

The project config

Everything about a shoot lives in one small file, roughcut-project.yaml, in the project folder or in its tools/ folder. Paths are relative to the project folder, so the file keeps working if the drive mounts under a different name.

project:
  version: v1
  script: exports/v1/docs/Scripts - Jane Doe.docx
  footage: assets/raw_footage/20260101_PROJECT_v1_Studio_JaneDoe/camera_a
  event_xml: exports/v1/xml/20260101_PROJECT_v1_Studio_JaneDoe.fcpxmld
transcribe:
  speaker: Jane Doe           # helps Whisper spell the name
  vocabulary: [wayfinding]     # words Whisper might miss; names in the script are found automatically
cut:
  event: PROJECT rough cuts v1    # the event the rough cuts appear in, in FCP

Only version, script and footage are required (event_xml is needed for cut). What roughcut writes, and where, unless the config’s paths: says otherwise ({shoot} is the name of the footage folder’s parent):

WhatWhere
audio for transcriptionexports/{version}/audio_for_transcripts/
transcriptsexports/{version}/transcripts/{shoot}_transcripts/
take mapsexports/{version}/take_maps/{shoot}_take_map.yaml and .md
rough cutsexports/{version}/xml/{shoot}_roughcuts_{version}.fcpxml

Other settings you can add:

SectionSettingDefaultMeaning
transcribemixallwhich audio to transcribe: all channels mixed, or a list like [1]
transcribespellingnoneextra fixes, e.g. {'\bline shot\b': wide shot}
transcribemodel, languageturbo, enthe Whisper model and language
cuthandles6frames kept each side of a paragraph
cutmerge_gap_seconds2.5consecutive paragraphs from one take closer than this stay one clip
pathsaudio, transcripts, take_map, roughcutsas aboveput any of them elsewhere, e.g. where earlier transcripts already are

In the commands below, PROJECT is the project folder (or the config file itself).

Step 1: roughcut script transcribe

roughcut script transcribe "/Volumes/media/MyProject" --dry-run

--dry-run shows what it will do and writes nothing: how many takes are done and how many are left, and the vocabulary it found in the script. Then run it for real:

roughcut script transcribe "/Volumes/media/MyProject"

It extracts each camera file’s audio and transcribes it with Whisper, word by word. It’s safe to stop and run again: takes that already have a transcript are skipped. Allow roughly a few seconds per minute of footage on a recent Mac.

Step 2: roughcut script map

roughcut script map "/Volumes/media/MyProject"

It writes the take map, a pair of files in take_maps/:

Framing comes from what the camera operator says on the recording (“starting on the wide”, “let’s go to the close-up”); when a file has no cue, it carries on from the file before. The output lists flags for everything worth checking: takes whose framing is unknown or carried over, comments that point at a take that doesn’t exist, paragraphs no take covers, takes that read no script.

Step 3: check and edit the take map

Open the newest …_take_map.md to read, and edit the newest …_take_map.yaml in any text editor. Two kinds of edit:

Decisions, at the top of the YAML, fix what roughcut can’t know:

decisions:
  false_start_seconds: 20      # a read from the top shorter than this is a false start
  framing:                     # when the operator's cues are missing or wrong
    t01: wide
    t05: {'0:00': close, '6:43': wide}   # close-up, switching to wide at 6:43
  false_starts: [t10]          # takes (or single passes, like t12.3) never to count or pick
  dropped:
    10: [5]                    # script 10, paragraph 5 was cut on set
  drop_sentences:              # a sentence rewritten on set: drop the version that wasn't said
    16: ['The old version of the sentence, exactly as it is in the script.']
  skip_scripts: [1]            # scripts to leave out

Takes are named by the part of the file name that differs (t05 for …_ssd22_t05_422.MOV). Times are minutes:seconds from the head of the file, in quotes.

Picks: under each script, each paragraph has a line like

- {para: 3, pass: t03.4, proposed: t03.4, why: Alex}

To choose a different take for that paragraph, change pass: to another pass’s id (they’re listed under the script’s passes:, and in the .md). Leave proposed: as it is: that’s how roughcut knows you changed it.

Then run map again. It reads your decisions and picks from the newest take map and writes the next file (_2, _3, …), so you can compare. Repeat until the flags left are ones you’re happy with.

Step 4: roughcut script cut

roughcut script cut "/Volumes/media/MyProject" --dry-run

builds and checks everything and writes nothing; without --dry-run it writes xml/…_roughcuts_v1.fcpxml (and a .selects.json beside it). It always uses the newest take map; --take-map FILE picks another. It won’t run until a take map exists, so a proposal is never cut unchecked.

Import it with File › Import › XML…. You get a new event with one project per script, named by section and script (1.1.3 S02 Getting Started). In each project:


6. Troubleshooting

ProblemFix
roughcut: command not foundopen a new Terminal window; if it persists, run uv tool update-shell and open another
no Whisper Python at …install mlx-whisper (install step 5)
Whisper fails with a message about the model or the networkdownload the model (install step 6); roughcut never downloads it itself
--refine flags every select refine-fallbackthe camera files aren’t where the stringout says: mount the drive under the same name as when you exported
would overwrite …run again with --suffix _v2, or move the old outputs away
a project lands in failed/the errors are in failed/<project>.errors.txt; send them, with the inputs, to Rafa
no roughcut-project.yaml at …give the project folder that holds the config (or its tools/ folder), or the config file itself
project root … isn't a folder (is the drive mounted?)mount the drive
… isn't in the event; its passes are left outthe take wasn’t in the exported event: add it to the event, export again, and cut again

Every file roughcut writes is new; if something looks wrong, delete (or move to the Trash) the outputs of that run and try again. The stringouts, paper cuts, scripts, media and FCP library are only ever read and never modified.