← back to clippr  ·  this page is planning/clippr-plan.html; the .md twin next to it says the same

clippr — what it is, its parts, where we are

updated 2026-09-03 · MVP, runs on your own computer · page: http://127.0.0.1:8000 (uvicorn web.app:app)
framing rule: designed = an idea · built = code exists · verified = we watched it work. nothing built is called "working" without a run, a frame, or a number behind it.

what it does, in one breath

give clippr an episode or a film; it reads the subtitles, finds the moments that work on their own, and cuts each one into a vertical clip under 20 seconds, with or without music, ranked for you to review and download.

the parts (one box each on the board)

boxwhat it doeswhere it lives
the pagechoose the video (and a .srt only if needed), press "find the clips", watch the four stages, review the ranked cards, open the local folder, download; this documentation is embedded at the bottom and served at /docsweb/app.py, web/static/index.html
ingestreads the .srt — or pulls the English text track out of the video itself — cleans it, gives every line a [mm:ss-mm:ss] stamp; picks the English audio track tooclippr/ingest.py, clippr/ff.py
story finderfour model passes over the whole transcript (below) → a ranked list of stories, each = 1–6 segments with exact line boundariesclippr/story.py, prompts in clippr/prompts/
cuttercuts each segment, crops to 9:16, joins segments with a crossfade, levels the dialogue, keeps every file ≤ 20.0s → NN_dry.mp4 + thumbnailclippr/cut.py
musicpicks a free Creative Commons track for the mood and ducks it under the dialogue → NN_music.mp4, attribution keptclippr/music.py, music/<mood>/
the runone call that chains the parts and writes clips.json after every clip, so a crash keeps what is doneclippr/pipeline.py, run.py
the modelClaude Haiku (owner's pick) reads and judges; DeepSeek and NVIDIA NIM are wired in as fallbacksclippr/config.py
flowchart LR
  A[video on this computer
+ .srt or embedded subs] --> B[ingest
clean lines with times] B --> C[story finder
4 model passes] C --> D[cutter
9:16 · ≤20s · levelled] D --> E[music
CC track, ducked] D --> F[(output/<episode>/
dry + music + clips.json)] E --> F F --> G[the page
ranked cards · play · download · open folder] A -.-> G

how the AI decides (the four passes)

  1. read the episode — characters and their relationships, scenes with what is physically happening, and "threads": setups that might pay off later (a lie, a threat, a running joke).
  2. propose the moments — every candidate that passes the stranger test ("cover the rest of the episode, read only these lines: is it clear who wants what, and does it end on something?"), with a score 1–10, a hook (the first two seconds), and a line on why it works alone. Rules: 1–6 segments, ≤20s total, cut on subtitle lines only, a segment is a few lines, never a whole scene.
  3. tighten — anything over 20s once measured on real timings is handed back with the exact length of every line and a running total; up to two rounds.
  4. the judge — a fresh conversation that sees each candidate ONLY as its own lines, as a scrolling stranger would, and scores understand and interest 1–10. Under 6 on either → dropped, with the reason kept. The card's score is this cold-read score; the author's score is kept alongside.
sequenceDiagram
  participant P as clippr
  participant M as claude-haiku-4-5
  P->>M: full transcript → "who is who, scenes, threads"
  M-->>P: characters + scenes
  P->>M: "every clip-worthy moment; stranger test; score, hook, why-alone"
  M-->>P: candidate stories
  P->>P: snap to real lines · merge touching segments · measure
  P->>M: "these are over 20s — here is every line's length; tighten or drop" (×≤2)
  M-->>P: tightened stories
  P->>M: fresh chat: "you never saw this show; here are the words of each clip — understand? interest?"
  M-->>P: verdicts → drop < 6, rank by cold-read score
  

how one clip gets made

  1. each segment: start 0.3s early, end 0.3s late (the padding shrinks automatically when the story is already near 20s)
  2. crop the middle 9:16 of the frame → 1080×1920 at 30 fps (subtitles are burned in only if you tick the box)
  3. segments joined with a 0.2s crossfade (picture and sound)
  4. dialogue levelled to −16 LUFS — streaming masters downmixed from 5.1 arrive near-silent otherwise
  5. a thumbnail at 0.5s, then the music version: track for the mood, looped/trimmed, faded, ducked under the speech, mixed

where the files live

output/<episode>/
  01_dry.mp4  01_music.mp4  01_thumb.jpg   ← ranked, 01 = best
  02_…
  clips.json                                ← everything the page shows (title, scores, judge, segments, files, music credit)
  <episode>.eng.srt                         ← the subtitles that were used (extracted if they were embedded)
  story/  pass1.json pass2.json pass3*.json pass4.json stories.json provider.json prompt_*.md
          ↑ every model answer, for prompt tuning: what was proposed, what was dropped and why
uploads/<episode>/   ← only for uploaded files; deleted after the run unless "keep the uploaded copy"
music/<mood>/        ← the free tracks (tense funny sad heartwarming epic chill neutral) + tracks.json credits

how to run

pip install -r requirements.txt
cp .env.example .env                 # ANTHROPIC_API_KEY
python tools/fetch_music.py --per-mood 3
start_clippr.cmd                     # Windows: double-click → starts the page + opens the browser
uvicorn web.app:app                  # or by hand → http://127.0.0.1:8000
python run.py --video "C:\path\episode.mkv"          # same thing from a terminal; add --srt if there is no embedded track
python tools/make_sample.py          # a synthetic 3-min demo episode if you have no real one at hand

where we are (2026-09-03, end of day 1)

verified (we watched it work)

measured

whatnumber
story stage on a 45-min episode (4 passes, haiku)≈ 55s, ≈ $0.05
cutting + music≈ 15–20s per clip (HEVC 1080p source)
whole run, real episode2.5–4 min
deepseek direct402 — account empty (owner: keep haiku)
NIM deepseek-v4 (free)464s for two passes — last resort only
hosted free tier (Render, 0.1 CPU / 512 MB)sample episode 142s end to end at 540x960; 1080x1920 = OOM

known limits / not built

decisions to talk about