Skip to content

Latest commit

 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cutroom

Point it at your footage. Get a film.

You have hours of video you will never watch. Cutroom watches it once, writes down what is in every shot, and then cuts films out of it — a new one whenever you ask, for about the price of a coffee.

Filtering the archive graph

Every shot in an archive, what it contains, and which films spend it. Filter by material, mood, time of day, or by what no cut has used yet — then click a node and it tells you what is in that clip and why a cut spent it. Each panel above is a real state of the page, reachable by URL.

On the archive it was built against: 453 shots logged once for $3.37, then two complete 14-minute films for under $2 each — sharing only 41% of their shots, because the second one was told nothing about what the film should be.

The one idea

Analysis is expensive; story is cheap. So they are separated completely.

   EXPENSIVE, BUILT ONCE                CHEAP, RE-RUN FOREVER
   ┌───────────────────────┐            ┌───────────────────────┐
   │   vault/shots/*.md    │◀───────────│   vault/cuts/*.md     │
   │   one atom per clip   │  wikilinks │   story assemblies    │
   │   IMMUTABLE           │            │   ONE PER VERSION     │
   └───────────────────────┘            └───────────────────────┘

A vision model looks at sampled frames and transcripts once and writes one note per shot. The assembly stage then never looks at a frame — it reads only those notes. That is the whole trick: a new film is a new note in cuts/, not a new analysis. Cuts are additive and never overwrite each other, so alternate versions sit side by side and stay comparable.

What you get

vault/shots/*.md one note per clip: what's in it, mood, light, camera, the usable sub-range, who is in it and how that was proven
vault/cuts/*.md an ordered edit with in/out points and a reason for every shot
derived/cuts/*.json the same cut as a machine-readable EDL
rendered video master + preview, plus every trimmed shot as its own file for import into an NLE
*.html a self-contained interactive graph of the whole archive

Requirements

  • Python 3.10+ for the model stages; ffmpeg and ffprobe on PATH
  • An Anthropic API key; an OpenAI key if you want transcripts
  • pip install anthropic (plus pillow for the WebP transcoder and diagram)

Setup

git clone <this repo> && cd <this repo>
python3 -m venv venv && ./venv/bin/pip install anthropic pillow
cp .env.example .env                      # add your keys
export ARCHIVE_SOURCE_DIR="/path/to/your/footage"   # read-only, never written

Paths resolve from the repo directory by default; set FILM_PROJECT_ROOT to put outputs elsewhere. Nothing is hardcoded to the machine this was built on.

Running it

Every stage is idempotent — rerun and it skips finished work.

# Stage 1 — mechanical, no model tokens
python3 scripts/transcode_webp_*.py      # only if your archive has animated WebP
python3 scripts/build_inventory_*.py     # ffprobe everything → inventory.tsv
python3 scripts/extract_frames_v2_*.py   # tiered frame budget → stills
python3 scripts/transcribe_*.py          # Whisper → transcripts

# Stage 2 — the expensive one, once
./venv/bin/python scripts/log_shots_*.py --dry-run    # measure real cost first
./venv/bin/python scripts/log_shots_*.py --limit 10   # pilot, read the notes
./venv/bin/python scripts/log_shots_*.py              # everything

# Stage 3 — cheap and repeatable
./venv/bin/python scripts/assemble_cut_*.py
./venv/bin/python scripts/assemble_cut_*.py --open    # withhold your premise
./venv/bin/python scripts/assemble_cut_*.py --brief "..."

# Stage 4 — render
./venv/bin/python scripts/render_cut_*.py --edl derived/cuts/cut-01-*.json --height 720

# Stage 5 — interactive graph
python3 scripts/build_graph_html_*.py

Use --dry-run. On stages 2–4 it resolves every file and counts real tokens without generating or encoding, so you know the bill before you pay it.

Design rules worth keeping

These are the parts that make the output trustworthy rather than merely plausible. They are enforced in code, not requested in a prompt.

  • The controlled vocabulary is a JSON schema. An out-of-vocabulary value is a 400 from the API, not a value that quietly rots your queries later.
  • No claim without a source. A person is named in a shot note only when the transcript or the clip's own filename says the name, and the validator re-checks the quote against the actual text. A name that fails is dropped and logged. Frames cannot tell you who someone is, so the honest output is a count of visible people and cast_evidence: none.
  • Ranges are picked, not invented. The usable sub-range of a clip must be chosen from real frame or transcript timestamps that were handed to the model, then clamped to the clip's true duration.
  • Confidence is forced down when evidence is thin. A clip with no transcript and few frames cannot yield a confident range, and says so.
  • The script does the arithmetic. Models are unreliable at summing forty durations to hit a target length, so the code sums the real numbers, compares to target, and makes exactly one bounded repair call. Never a loop.
  • Every number is measured. Per-call response.usage is written to logs/*.jsonl; cost reports read from there rather than estimating.

Adapting it to your archive

The pipeline is generic; the vocabulary and prompts are the example configuration and are where your edits go:

what where why you'd change it
material classes (how clips are bucketed) build_inventory_*.py yours are not "camping" and "zombie"
frame budget per class extract_frames_v2_*.py a 6-second insert needs fewer frames than a 27-minute reference
logged fields + vocabulary log_shots_*.pyVOCAB, SCHEMA mood/light/movement suit narrative footage; a lecture archive wants different axes
the second scoring axis log_shots_*.py one axis is craft quality; the other should be whatever your project selects for
structure of a cut assemble_cut_*.py this example uses songs as acts; yours might use chapters, days, or speakers
the premise assemble_cut_*.py or pass --open and let the model derive the subject from the material

--open is worth knowing about: it withholds your premise entirely and asks the model what the archive actually is, from the distributions. In the case study below, the premise-given and premise-withheld cuts shared only 41% of their shots.

Things that will bite you

Each of these cost real debugging time, and each is now handled in code:

  • ffmpeg cannot demux animated WebP at all. Not slowly — at all. Stage 1a exists solely to route around it via Pillow. Rendering from an EDL's original source_path then fails on every such clip; the renderer re-joins to the proxy.
  • An archive is never one format. NTSC DV, anamorphic, 16 fps, 120 fps — concatenating those without conforming them produces either a hard failure or silent geometry corruption. Square the pixels first (scale=trunc(iw*sar/2)*2:ih) or 4:3 material stretches.
  • -ss before -i is not frame-accurate on AVCHD, which shows up as frozen picture at the head of shots. --seek accurate fixes it.
  • A caught error must still delete its partial output. One failed transcode was correctly logged as failed and left on disk truncated, so every later stage planned against footage that did not exist.
  • Thousands of ffmpeg spawns can wedge a machine into unkillable D state. Frame extraction uses one spawn per clip, not one per frame.
  • Structured-output schemas reject minItems above 1. Constraints like "3 or 4 sections" belong in the prompt plus a post-response check.
  • Byte-level checks lie about video. Counting unique frame hashes finds nothing, because re-encoded near-identical frames are not byte-identical. Use mean pixel difference, and validate any detector on a known-bad case first.

Using it with a coding agent

Operating rules live in AGENTS.md — the cross-tool convention read by Cursor, Aider, Codex, Continue, Cline and others. CLAUDE.md is a symlink to it, so Claude Code picks up the same file and there is only one copy to maintain. If your tool reads neither, paste it in as context.

It is worth pointing an agent at, because the rules encode failures that are expensive to rediscover: which verification commands silently lie about video, why the shot vault must never be edited to fix a downstream bug, and which "defects" are actually creative choices to be asked about rather than fixed.

Case study

docs/case-study.md — 5.6 hours of unorganized camping trip movies and several theme songs, logged into 453 shot atoms for $3.37, then cut into two 14-minute films for under $2 each.

Licence and citation

CC BY-NC 4.0 — Copyright (c) 2026 Kourosh Salehi-Ashtiani.

Free to use, modify and build on for non-commercial purposes. Two conditions, and they apply to modified versions exactly as they apply to unmodified ones:

  • Cite this repository. Any use — as-is, adapted, or as a component of something larger — must credit it and link back.
  • No commercial use without a separate licence. Contact the copyright holder.
Salehi-Ashtiani, K. (2026). Cutroom: log a video archive once,
assemble films from it forever. https://github.com/kouroshSA/cutroom
Licensed under CC BY-NC 4.0.

CITATION.cff provides this in machine-readable form, so GitHub offers a Cite this repository button and reference managers can import it directly.

No warranty, no liability. This software is provided "as is", without warranty of any kind, and the copyright holder is not liable for any damages arising from its use — see Section 5 of the licence, restated plainly in NOTICE. It drives ffmpeg over your files and spends money against your API keys; back up your footage and watch your own bill.

Note that the non-commercial clause means this is source-available, not OSI-approved open source. Full terms in LICENSE; the copyright notice, warranty disclaimer and required citation are in NOTICE.

Notes

vault/ and derived/ are gitignored: they are output, and they contain descriptions of whatever footage you pointed this at.

About

Point it at your footage. Get a film. Logs a video archive once with a vision model, then cuts films out of it for under $2 each.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages