Skip to content
VIDEOJAMMINGBuild walkthroughGet in touch
clipjamPython · Gemini · yt-dlp · ffmpegBuilt in one session

The model picks the moment. Nothing else trusts it.

clipjam turns a four-hour podcast into short, social-ready clips and a stitched compilation. An LLM decides which thirty seconds matter — and then every step downstream of that judgement is deterministic ffmpeg, verified by measurement rather than by looking at it. This page walks through the decisions and shows the numbers they produced.

cut accuracy
+0ms
cuts off a word edge
0of 200
pass-A token cost
8% of naive
defects caught by measurement
106 silent
tests
270no network
types + lint
cleanstrict

The shape of it

every stage is a file on disk

Six stages, each a pure function over files. A stage reads the previous artifact and writes the next under a fixed filename, which means any of them can be re-run in isolation, and any intermediate can be hand-edited and the run resumed from it. The models are loaded with unknown keys forbidden, so a typo in a hand-edited artifact fails loudly instead of being silently dropped.

The highlighted stage is the point of the whole design: nothing is cut and nothing is rendered until a person has approved it.

Six decisions, and what they cost

problem · decision · proof

Each of these is framed problem-first, because a decision only reads as judgement if you can see what it was answering. Every measurement comes from a real run against a real episode — The Rise and Fall of the Roman Empire | Lex Fridman Podcast #498, 3:51:47.

  1. 01

    Getting text you can actually cut on

    The problem

    YouTube exposes two caption tracks and they are good at different things. Human-written captions have punctuation, sentence case and speaker turns — and no word-level timing at all. Auto captions are unpunctuated ASR, but every single word carries an onset. You need both, and most tools pick one.

    The decision

    Fetch both and merge: sentence text from the manual track, word timings from the auto track, with the provenance of each recorded in the artifact. Word durations don't exist in either format, so they're estimated from word length and capped at the gap to the next onset — documented as an estimate, and deterministic, which is what makes a cut point reproducible.

    What it measured
    38,281 words · 99.9% coverage · 5,497 snap candidates. Silence model validated before being built on: 82% of manual-caption sentence ends land within 1.5s of a detected ≥250ms pause.
  2. 02

    Not spending a million tokens per iteration

    The problem

    A 3-hour video handed to a multimodal model as native video is roughly a million input tokens. At that price you cannot iterate on the prompt, which means you cannot actually tune selection quality — the thing the whole tool exists to do.

    The decision

    Two passes. A cheap wide scan reads only the transcript in overlapping 15-minute windows on a fast model, returning ~3× the candidates wanted. Then a narrow pass sends the YouTube URL bounded by video_metadata so the model pays only for that 70-second slice, and is asked only what a transcript cannot answer: does the delivery land, is there crosstalk, would this survive a vertical crop.

    What it measured
    18 calls, ~80,000 input tokens for the wide pass — about 8% of what the same episode costs as native video. The narrow pass then spends real video tokens on roughly a dozen windows instead of four hours.
  3. 03

    Deciding where the blade actually falls

    The problem

    A language model gives you a timestamp like 12:34. Cut there and you slice through the middle of a word. Ask the model to be frame-accurate and you are trusting a probabilistic system with a deterministic problem it has no way to solve — it cannot see the waveform.

    The decision

    Treat the model's in/out points as a proposal, never a decision. A separate module with no LLM in it snaps each edge, in priority order: a sentence boundary, then a word edge flanked by ≥250ms of silence, then any word edge, then a recorded no-op. Every move is capped at ±2s, and the direction preference is asymmetric on purpose — starts move back so a clip doesn't open mid-breath, ends move forward so the thought finishes.

    What it measured
    200 cut points swept across the real episode: 0 landed off a word edge, 0 cut mid-word, no drift exceeded ±2s, and 89.5% landed on a silence-flanked edge. The remaining 10.5% correctly fell back to a plain word edge in dense stretches where no pause exists inside the budget.
  4. 04

    Cutting video that isn't quietly broken

    The problem

    Stream copying is fast and can only cut on keyframes, so any in-point that isn't one produces a clip opening on frozen or black frames while the audio runs. And the ffmpeg concat demuxer performs no validation whatsoever: hand it clips whose resolution, SAR, frame rate or audio parameters disagree and it writes a file that looks fine to any inspector and plays wrong.

    The decision

    Always re-encode, never -c copy. Normalise every clip to identical geometry as it is cut rather than at concat time, so disagreement is impossible upstream. Then refuse to concatenate anything that still mismatches, naming both files — because the demuxer won't.

    What it measured
    Every cut verified with ffprobe: duration within 100ms of target, first-frame luma above the black threshold. Measured: +0ms, 1920×1080 / 30fps / SAR 1:1, −14.5 LUFS against a −14 target.
  5. 05

    Keeping a human in the loop without a save button

    The problem

    The model proposes; a person has to approve before anything renders. But a review UI with its own in-memory session immediately drifts from the artifact on disk, and then you have two sources of truth and a save button to forget.

    The decision

    There is no session. Every edit — approve, reject, nudge an edge by a quarter second, retitle, reorder — is a read-modify-write of clips.json under a lock. The file on disk is the state, so editing the JSON in a text editor at the same time works, and the external edit wins. Nudging an edge re-labels its snap rule as manual and recomputes the drift against the machine's original proposal, so the record stops claiming a rule a human has overridden.

    What it measured
    Driven in a real browser, not just unit-tested: approve, nudge, drag-reorder and the player's out-point stop each verified against the bytes on disk rather than the response body.
  6. 06

    Making it useful without an API key

    The problem

    The expensive part of this pipeline is the model call. The valuable part isn't — it's everything that happens to a timestamp after someone chooses it. Gating the whole tool behind a funded API key throws that away.

    The decision

    Skip the two model stages entirely. The tool prints a prompt carrying the same selection rubric, you paste it into any chat window along with the URL, and paste the answer back. Hand-fed timestamps are treated exactly like the model's — snapped against the transcript already cached, then cut frame-accurately and captioned from the same word onsets.

    What it measured
    Pasted 3:29 - 4:01 became 208.68 - 242.46, snapped silence/silence, cut at +0ms, compilation passing all three A/V checks. The parser takes ranges, deep links, bare seconds and JSON, mixed freely.

What comes out

real output, not a mockup
1080×1920 · captions burned from the word onsets · blurred pillarbox rather than a centre crop that would decapitate anyone off-centre.

Captions are built from the same word timings the cutter uses, grouped two to four at a time and broken at whichever comes first: the word limit, a pause long enough to be a phrase boundary, or sentence-final punctuation. That is why the cue below breaks after reasons. rather than mid-clause.

0:00:00.00 → 0:00:01.24  And those reasons have
0:00:01.24 → 0:00:02.72  created these models where
0:00:03.68 → 0:00:04.80  they were they called
0:00:04.80 → 0:00:05.36  it Empire of the
0:00:05.36 → 0:00:06.12  Greeks for a thousand
0:00:06.12 → 0:00:06.92  years and they switched
0:00:06.92 → 0:00:08.36  to Byzantine Empire for
0:00:08.36 → 0:00:09.49  very political reasons.

A/V sync across every join

3 clips, 2 boundaries

The concat demuxer validates nothing, so the compilation is checked on measurable consequences instead: total length against the sum of its parts, the audio stream drifting against the video stream, and any gap in the frame timeline wider than a frame.

checkhard cutscrossfadetolerance
total duration93.938s / 93.917 exp pass93.500s / 93.500 exp pass±150ms
audio vs video+5ms pass+0ms pass±100ms
largest frame gap46.7ms pass33.3ms pass≤67ms

What broke, and how I know

the actually useful part

Ten defects surfaced during the build. Six of them would have failed silently — producing a plausible-looking file that was wrong. None were found by reading the code; every one came from measuring a step instead of eyeballing it. This is the argument for building verification into a pipeline rather than bolting tests on afterwards.

wheredefecthow it would have failedsilent
transcriptAuto-caption rollup events counted as contentHalf of all events in the feed are scroll artefacts. Unfiltered, the entire transcript duplicates.silent
transcriptVTT split on blank linesYouTube emits single-space lines inside cues, so the first cue tore in half and silently lost its words.silent
mediaMapped both the raw and the filtered video streamffmpeg wrote two streams and left the unfiltered one first — 640×360 output that would have surfaced much later as unexplained concat corruption.silent
mediaBlack-frame threshold set below the value it detectsh264 is limited-range, so a genuinely black frame reports luma 16, not 0. At a threshold of 8 the check could never fire — a verification that cannot fail is worse than none.silent
renderCrossfade computed video offsets from container durationsxfade walks video timestamps while acrossfade walks decoded samples, so audio ended 173ms before video across three clips, drifting further with each join.silent
manualMalformed 99:99:99 fell through to bare-number parsingIts digits were re-read as seconds, silently producing a clip at 1:39 instead of refusing the line.silent
transcriptHTML entities left undecoded>> leaking into clip transcripts and burned-in captions.loud
cliLatest-run resolved by timestamp alonePointed every stage at an abandoned run that had only a sources.json in it.loud
review uiPanel shown with style.display = ""Clearing the inline style fell back to the stylesheet's display:none, so the whole stage never appeared.loud
manualTitle cleaning stripped numbers and range words unconditionally"26 emperors murdered in 50 years" lost its numbers; "Eunuchs, taxes and power" lost its "and".loud

The black-frame threshold is the instructive one. I set it from intuition, and it sat below the value it was supposed to catch — so the check could never fire. It now has a test that renders an actually-black clip and requires the check to catch it, because a verification that cannot fail is worse than no verification at all: it buys false confidence.

What this does not prove

stated up front

No live model call was ever made. Selection and refinement ran against a canned response through the same seam the dry-run path uses, deliberately labelled so it cannot be mistaken for output.

That means the quality of the model's judgement is untested. Everything downstream of that judgement — snapping, cutting, concatenation, A/V sync, captions — is verified on real footage.

Manual mode exists partly because of that gap: it makes the deterministic 80% of the tool useful today, and it is how the end-to-end runs on this page were actually produced.

How I build with models

the transferable part
  • Agents propose, gates dispose

    The LLM is scoped to the one judgement it is uniquely good at — which thirty seconds are worth watching. Timing, arithmetic, deduplication and encoding are handled by deterministic code, because they have correct answers and a model guessing at them is a liability.

  • Design for the token bill

    A cheap wide pass over text, then an expensive narrow pass over the survivors, bounded so the model pays only for the slice it needs. 8% of the naive cost is the difference between a tool you can tune and one you run once.

  • Verify consequences, not intentions

    Every stage asserts something measurable about its own output — duration within 100ms, first frame not black, audio not drifting from video. Six silent defects say that reading the diff would not have been enough.

  • Put the human where the judgement is

    Review sits between selection and render, and there is no way around it. The file on disk is the state, so the reviewer and the pipeline can never drift apart, and rejected candidates are recorded rather than deleted so any of them can be overruled.

  • Make the failure legible

    Errors carry the exact command line, the real stderr, and the fix. A ranged download whose length disagrees with the request halts the run rather than quietly cutting the wrong moment.

  • Degrade to something useful

    Without an API key the tool still does the valuable 80%: paste timestamps from any chat window and the deterministic half — snapping, cutting, captions, compilation — runs unchanged.