Documentation

vcut proposes cuts as data and renders only what a human approved. These pages cover the commands, the transcript requirement, and the edit decision list you are approving.

Getting started

vcut turns a raw recording into a clean master in three steps, and stops at every point where a human should look.

npm install -g @crafter/vcut

Or run it without installing:

npx @crafter/vcut recording.mp4

Requirements

vcut shells out to ffmpeg and ffprobe. Both must be on your PATH.

brew install ffmpeg     # macOS
vcut doctor             # confirms both are visible

vcut doctor exits non-zero when something is missing, so it works as a precondition check in a script.

The three steps

# 1. Find what is worth cutting. Writes candidates, decides nothing.
vcut detect recording.mp4 --preset clean > detect.json

# 2. Draft an edit decision list. Every segment is proposed.
vcut edl build --detect detect.json --output master.mp4 --campaign my-video

# 3. Preview it, watch it, and only then render a master.
vcut render --edl edl.json --mode preview

The middle step is not optional plumbing. The EDL is the artifact you read and disagree with before anything gets rendered.

Reading the summary

In a terminal, vcut detect prints a summary rather than the raw candidate list:

recording.mp4  6m 22s
  detected dead air       ###.................  16.5%  (1m 03s)
  net after margins       ##..................  10.3%  (~39s once 100ms is kept on each side)
  silences                119 spans, 1m 03s
  longest silence         1s at 6m 20s
  fillers                 4 hits, 1s
  review candidates       1 (never cut automatically)
                          clipping: peak level -0.24 dB exceeds -1 dBFS

Two numbers appear because they answer different questions. Detected dead air is how much silence exists. Net after margins is how much actually gets removed once the padding around each cut is given back, and that second number is what vcut edl build should land near. A large gap between them means the margin is eating the cuts.

Choosing a preset

The preset sets the loudness floor below which audio counts as silence.

PresetThresholdUse
noisy (default)-20 dBEvents, rooms with ambient noise
clean-30 dBStudio, talking head
podcast-35 dBDeliberate pauses you want to keep

A preset that is too aggressive cuts into breath and delivery. One that is too conservative leaves the pauses in. Start with the one that matches the room, then tune --min-silence if the result is close but not right.

What comes next

Nothing here approves anything. Segments are written as proposed and the EDL as draft, and vcut render --mode master refuses to run until a human changes that. See Commands for the full surface and The EDL for what you are approving.

Commands

vcut <input>                       Shorthand for: vcut detect <input>
vcut detect <input> [flags]        Find silences, fillers, and review candidates
vcut edl build [flags]             Turn a detect report into a draft EDL
vcut render --edl <path> [flags]   Render an EDL to video
vcut schema [name]                 Print the JSON contract for a command
vcut skills list|get [name]        Read the bundled agent manual
vcut doctor                        Check external dependencies
vcut version                       Print the version

Global flags: --json forces machine output, --human forces the summary, --help works on any command.

vcut detect

Runs the deterministic pass. Never edits media, never writes an EDL.

vcut detect recording.mp4 --preset clean --lang es --transcript words.srt
FlagDefaultWhat it does
--input <path>Source recording. Also accepted positionally.
--preset <name>noisynoisy (-20 dB), clean (-30 dB), podcast (-35 dB)
--min-silence <sec>0.3Shortest silence worth cutting
--margin <sec>0.10Padding kept on each side of speech
--lang <code>eses, en, or pt; selects the filler list
--transcript <path>SRT used for filler detection; must be word-level
--skip-video-scanoffSkip black and frozen frame detection

It reports four kinds of finding: silences measured from audio energy, fillers matched against a word list, review candidates (clipping, black frames, frozen frames), and warnings for conditions worth reading before trusting the run.

Review candidates are never cut automatically. They exist so a human looks.

vcut edl build

Turns a detect report into a draft edit decision list.

vcut edl build --detect detect.json --output master.mp4 --campaign my-video
FlagDefaultWhat it does
--detect <path>requiredReport produced by vcut detect
--output <path>requiredWhere the rendered master will go
--campaign <id>requiredCampaign identifier, stored in the EDL
--edl <path>./edl.jsonWhere to write the EDL
--width, --height, --fpssource valuesOutput geometry
--no-fillersoffCut silences only, ignore filler candidates

The command inverts the cut intervals into the spans worth keeping, so the EDL always describes surviving material rather than deleted material.

It also reports a removal percentage. Compare it against the content type:

ContentExpected removal
Event or interview30-45%
Tutorial or screencast15-25%
Scripted talking head10-20%

A number far below target usually means the source was already edited.

vcut render

vcut render --edl edl.json --mode preview --dry-run
vcut render --edl edl.json --mode preview
FlagDefaultWhat it does
--edl <path>requiredThe EDL to render
--output <path>from EDLOverride the output path
--mode <name>previewpreview or master
--dry-runoffPrint the ffmpeg command without running it

preview accepts proposed segments. master refuses unless the EDL is approved, every segment is approved, every source hash still matches, and the output path is free. It will not overwrite.

After rendering, vcut probes the file it produced and validates it against the EDL. A mismatch fails the run instead of shipping a bad file.

vcut schema

vcut schema            # lists the commands with a contract
vcut schema detect     # the field-by-field contract for detect

Versioned, so an agent can introspect the output shape at runtime instead of parsing help text or reading source.

vcut skills

Install the skill into Claude Code, Cursor, or any agent that reads them:

npx skills add Railly/vcut

What gets installed is a thin stub. It carries the description an agent matches against and then points at the CLI:

vcut skills list
vcut skills get core     # the usage guide, raw markdown on stdout
vcut skills path

The guide ships inside the npm package and is served by the CLI itself, so it always matches the installed version. A copy pasted into an agent’s config would go stale the moment you upgrade; a stub that points at skills get cannot.

vcut doctor

Checks that ffmpeg and ffprobe are reachable and reports their versions. Exits non-zero when something is missing.

Exit codes

CodeMeaning
0Success
1The run failed
2The invocation was wrong

Data always goes to stdout, diagnostics always to stderr.

Filler words

Cutting a single spoken word requires knowing where that word starts and ends. A normal SRT does not carry that: it has one cue per sentence, spanning several seconds and a dozen words.

vcut detects this and reports zero fillers with a warning naming the fix, rather than interpolating a position inside a cue. A confident wrong timestamp cuts into speech; an honest zero costs one re-run of the transcriber.

Getting a word-level transcript

One cue per word. Either of these produces it:

# with trx, which wraps whisper and handles extraction
trx transcribe recording.mp4 --words --language es

# or with whisper-cli directly
whisper-cli -m model.bin -f audio.wav --max-len 1 --output-srt

Then point vcut at the result:

vcut detect recording.mp4 --transcript words.srt --lang es

The fillers line in the summary changes from not checked to a count.

The lists

LanguageWords
esaaa, eee, este, pues, o sea, tipo
enuh, um, like, basically, you know, i mean
ptaaa, eee, tipo, assim, entendeu, sabe

Matching ignores case, accents, and punctuation, and handles multi-word entries like o sea across consecutive cues.

A match is not a verdict

The list matches tokens, not intent. Spanish este is a filler in “y este, entonces” and an ordinary demonstrative in “en este caso”. English like is a filler in “it was like, hard” and a verb in “I like this”. The detector cannot tell them apart.

On a real six-minute recording, vcut found four filler hits and one of them was este inside “en este caso” — cutting it would have mutilated the sentence.

This is one reason every hit lands in the EDL as proposed. Read them before approving, or pass --no-fillers to vcut edl build and keep only the silence cuts.

A better transcript can mean fewer cuts

Worth knowing, because it looks like a bug the first time: whisper --max-len 1 stretches each cue to the start of the next word, so a word’s range routinely swallows the pause that follows it.

On that same recording, 118 of 119 detected silences overlapped some word’s range. Clamping every cut strictly inside word boundaries erased 57 of them outright, and removal collapsed from 10.5% to 2.7% purely because a better transcript was supplied.

vcut treats silence measured from audio energy as stronger evidence than a boundary inferred by a model. When clamping would shred a cut into a remainder shorter than your own --min-silence, that remainder is overlap residue rather than a real pause, and the measured span wins.

The EDL

The edit decision list is the artifact between detection and rendering. It exists so there is something a human can read and disagree with before any file gets written.

It describes the material that survives, not the material that gets deleted.

Shape

{
  "version": 1,
  "campaignId": "my-video",
  "createdAt": "2026-01-15T18:00:00.000Z",
  "timebase": "milliseconds",
  "sources": [
    {
      "id": "recording-mp4",
      "path": "/absolute/path/recording.mp4",
      "sha256": "",
      "durationMs": 381760,
      "hasVideo": true,
      "hasAudio": true
    }
  ],
  "segments": [
    {
      "id": "segment-001",
      "sourceId": "recording-mp4",
      "inMs": 1100,
      "outMs": 4633,
      "reason": "approved-line",
      "handlesMs": { "before": 100, "after": 100 },
      "approval": "proposed",
      "semanticRisk": "none",
      "crop": null
    }
  ],
  "audio": {
    "speechTargetLufs": -16,
    "truePeakMaxDbtp": -1,
    "noiseReduction": "off",
    "externalAudioSourceId": null,
    "syncOffsetMs": 0
  },
  "output": {
    "path": "/absolute/path/master.mp4",
    "width": 1920,
    "height": 1080,
    "fps": 60,
    "videoCodec": "h264",
    "pixelFormat": "yuv420p",
    "colorSpace": "bt709",
    "audioTrackPolicy": "required",
    "overwrite": false
  },
  "approval": {
    "status": "draft",
    "approvedAt": null,
    "approvedBy": null
  }
}

The full JSON Schema ships in the package under schemas/edl.schema.json.

Approval

Two levels, and both start closed.

  • segment.approval is proposed, approved, or rejected.
  • approval.status on the EDL is draft, approved, or rejected.

vcut render --mode preview accepts proposed segments, so you can watch the cut before committing to it. --mode master requires the EDL approved, every segment approved, and an approval identity recorded.

vcut never writes approved. There is no flag for it and no --yes. Changing it is a human act, whether by hand or through a tool a human drives.

What the renderer refuses

A master render aborts on any of these:

  • the EDL or any segment is not approved
  • a source file is missing
  • a source hash no longer matches what the EDL recorded
  • the output path already exists
  • a segment references an unknown source or an interval outside the source duration
  • a crop falls outside the frame
  • audioTrackPolicy is required but a source has no audio

Hashing sources and refusing on a mismatch is what makes an approval mean something: you approved that footage, not whatever now sits at that path.

Fields that are rejected, not ignored

The schema has room for externalAudioSourceId, syncOffsetMs, and noiseReduction. The renderer does not implement them, so it rejects an EDL that sets them rather than rendering something that quietly ignores half the instruction.

A tool that silently drops a field you set is worse than one that refuses to run.

Self-validation

After rendering, vcut probes the file it just produced and compares it against the EDL: dimensions, pixel format, colour metadata, decoded frame count within one frame, sample rate, channel count, and the audio track contract.

A render that quietly produced two extra frames is a bug, and without this check it ships as a working file.

Reproducibility

Renders pin the thread count, fix the creation timestamp, and avoid anything nondeterministic, so the same EDL produces a byte-identical file. The sha256 in the render result exists so you can verify that yourself rather than take it on faith.

Frame boundaries

Cut points are milliseconds, but frames are not whole milliseconds: at 60fps a frame is 16.666…ms. ffmpeg rounds each trim to the nearest frame on its own, and near a frame edge that rounding can go either way.

vcut places every boundary in the middle of its frame, as far as possible from the point where the rounding flips, so the same frame is chosen regardless of how many segments the EDL has. Aiming at the frame edge instead held for eight segments and drifted past tolerance at ten.

The end of the source is clamped to the real duration rather than snapped: a segment must never claim material past the end of the file.