cut op removes stretches of a clip’s source — the dead air, the false start,
the “um” — from a project. Send it like any other op, through
POST /v1/projects/{id}/ops or the MCP edit_project tool:
Say what goes
Use exactly one of these three:ids lists the video or audio elements to cut: an element, or pieces an earlier cut
made. Each target is cut only inside its own source window (in_point to out_point);
a range outside every target is listed once, in the first target’s skipped_ranges, and
does nothing. To cut a
talking head whose sound is a separate audio element, list both: they are cut in step.
Leave one out and the report warns, because it plays on out of step after the first join.
Cutting by words
Cut with the transcript’s own word timings rather than times you work out yourself:transcript_task_idis a completedtranscribetask in your workspace, made from the samesource_urlthe targets play. Transcribe withdisfluencies: trueif you want to cut filler words: without it they are left out of the transcript and have no index.remove(orkeep) takes word-index ranges[i, j], both ends included, listed in order with each word once. Indices are integers;"14"is refused.- A cut takes about 90% of the pause on each side of the removed words. It leaves 10% of each pause next to the words that stay, and never less than 20 ms, so the cut never lands inside a word you kept.
- The server converts the transcript’s millisecond timings itself, and first clamps any words the recogniser overlapped, so every pause is zero or more.
- If the transcript’s mean word confidence is under 0.8, the report says so: preview the joins before you render.
Silences
silences shortens long pauses between the words that stay, including the pause left
at a join where words were removed. Send {} for the defaults:
A sentence ends at
., !, ?, 。, ! or ?, and the last word always ends one, so
an unpunctuated final sentence is treated as finished speech. A pause counts only for
the part that still plays, so running the same silence pass twice changes nothing.
Pieces
The first piece keeps the element’s id. Each later piece is named after the element and the second its source starts at, in milliseconds: cuttingtalk at 12.4–13.1 s makes
talk and talk~13100. The names come from the source, so the same cut always makes the
same names, and sending it again makes none. A piece keeps its name for good, though: if a
later cut takes the head of talk~13100, that piece starts later than 13.1 s under the
same name, so read where a piece starts from its in_point. Splitting one cut over
several ops can therefore name the pieces differently from a single op; what plays where
is the same.
Every piece is a full copy of the element with its own in_point, out_point and
time. Its motion goes where it plays: an entrance on the first piece, an exit on the
last (still anchored to the end), a mid-clip accent on the piece it lands in, and loops
and slow drifts such as Ken Burns on every piece, in phase.
Ripple
ripple decides what happens to the rest of the timeline:
all(the default) closes every gap, as if the removed moments never played:- Later elements move up, with their animations and keyframes.
- An overlay that plays across a cut gets shorter: video and audio through their
out_point, everything else throughduration. A music bed under the speech simply ends sooner. - Captions (
word_animation) are re-timed. A word with less than a quarter of it left is dropped, from the caption’s text too when the text’s words line up with the timings. - The request
duration, when it is set, shrinks by what was removed.
noneleaves each piece where it played, gaps and all, and moves nothing else.
"contained": "remove" to delete it with the cut. A caption with none of its words
left is removed either way.
Dry run
Add"dry_run": true to the ops body to see exactly what the batch would do — the
diff, the changed ids, the lint and the cut report — without saving a version. It works
for any batch, not only cuts, and checks if_version like a real apply. Read the
report, then send the same body without dry_run.
The result’s cut_report has one entry per target:
Joins and fades
Each new join gets a short linear audio fade on both sides, 15 ms by default, so a cut through a waveform does not click. Setjoin_fade (0–0.5 s; 0 for hard joins). A fade
the element already had stays on its outer ends: the fade-in on the first piece, the
fade-out on the last. These fades shape the sound only; the picture cuts hard.
Limits
- Cut boundaries snap inward to the output’s frame grid (
frame_rate, 30 by default), where the clip plays them at itsspeed, so every join lands on a frame and a removal never grows past what you asked for. Ranges that touch or overlap merge first; a removal shorter than one frame is then skipped. - A piece shorter than two frames is removed with its neighbours and reported: the renderer drops video and audio items under 10 ms.
- One cut op makes at most 300 pieces. Each piece re-opens and seeks its source when it
renders, so many short pieces render slowly; the
MANY_SHORT_PIECESwarning says when a source has 20 or more pieces with a median under half a second, andSLIVER_PIECEflags any video or audio element under two frames. - Speed-ramped elements,
loop: trueelements and track-matte sources cannot be cut. A speed-ramped overlay cannot play across a cut either. - A target without an
out_pointtakes its length from the stored upload. If the file was never uploaded, setout_pointfirst. - Targets that overlap on the timeline must remove the same moments: a video and its own audio track, cut with the same ranges, are fine.
- Removing everything a cut targets is refused. Delete an element with
remove_element.

