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.
The shape of it
every stage is a file on diskSix 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.
sources.jsonmetadata + word-level transcriptcandidates.jsoncheap wide scan over textclips.jsonnarrow pass over real videoclips.jsona human approves — or doesn'tclips/*.mp4frame-accurate re-encodecompilation.mp4+ manifest.jsonThe 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 · proofEach 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.
- 01
Getting text you can actually cut on
The problemYouTube 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 decisionFetch 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 measured38,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. - 02
Not spending a million tokens per iteration
The problemA 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 decisionTwo 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 measured18 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. - 03
Deciding where the blade actually falls
The problemA 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 decisionTreat 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 measured200 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. - 04
Cutting video that isn't quietly broken
The problemStream 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 decisionAlways 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 measuredEvery 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. - 05
Keeping a human in the loop without a save button
The problemThe 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 decisionThere 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 measuredDriven 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. - 06
Making it useful without an API key
The problemThe 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 decisionSkip 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 measuredPasted 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 mockupCaptions 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 boundariesThe 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.
| check | hard cuts | crossfade | tolerance |
|---|---|---|---|
| total duration | 93.938s / 93.917 exp pass | 93.500s / 93.500 exp pass | ±150ms |
| audio vs video | +5ms pass | +0ms pass | ±100ms |
| largest frame gap | 46.7ms pass | 33.3ms pass | ≤67ms |
What broke, and how I know
the actually useful partTen 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.
| where | defect | how it would have failed | silent |
|---|---|---|---|
| transcript | Auto-caption rollup events counted as content | Half of all events in the feed are scroll artefacts. Unfiltered, the entire transcript duplicates. | silent |
| transcript | VTT split on blank lines | YouTube emits single-space lines inside cues, so the first cue tore in half and silently lost its words. | silent |
| media | Mapped both the raw and the filtered video stream | ffmpeg wrote two streams and left the unfiltered one first — 640×360 output that would have surfaced much later as unexplained concat corruption. | silent |
| media | Black-frame threshold set below the value it detects | h264 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 |
| render | Crossfade computed video offsets from container durations | xfade walks video timestamps while acrossfade walks decoded samples, so audio ended 173ms before video across three clips, drifting further with each join. | silent |
| manual | Malformed 99:99:99 fell through to bare-number parsing | Its digits were re-read as seconds, silently producing a clip at 1:39 instead of refusing the line. | silent |
| transcript | HTML entities left undecoded | >> leaking into clip transcripts and burned-in captions. | loud |
| cli | Latest-run resolved by timestamp alone | Pointed every stage at an abandoned run that had only a sources.json in it. | loud |
| review ui | Panel shown with style.display = "" | Clearing the inline style fell back to the stylesheet's display:none, so the whole stage never appeared. | loud |
| manual | Title 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 frontNo 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 partAgents 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.