Field notes · Commentary film
One song, two recordings
A worked account of making a ten-minute commentary film about two recordings of the same song — and then re-voicing the whole thing, and cutting a two-minute version and a one-minute short, without repeating a single piece of research.
The material: two recordings of Simon & Garfunkel's The Sound of Silence — the 1966 electric single, and the live performance from The Concert in Central Park on 19 September 1981. Both are copyrighted commercial masters, so every cut described here is published private. That is the correct final state for brief excerpts under commentary, not a staging step.
The shape of the problem
Why "make a video about this song" is the wrong unit of work.
The naive way to build a commentary film is to write a script, find some pictures, render, and ship. It works exactly once. Ask for a shorter version and you start again; ask for a different voice and you start again; notice a wrong fact and you have no idea which parts of the film depend on it.
The alternative is to treat the research as the durable thing and the film as one rendering of it. Every fact, timing, image and rights record becomes an addressable artifact on disk, linked to the others. A film is then a traversal of those artifacts. A second film is a second traversal — and costs a script, not a project.
That is the claim this piece is testing, and the honest answer is at the bottom: the second and third cuts cost about 3% of what the first one did.
Layer one: research becomes artifacts
Five acquisition steps, each writing something another step can address.
Genius — lyrics and annotations
Three entries, not one: the canonical song (9 annotations), the 1965 single version (3), and the 1982 live version (zero). Written to data/genius/*.json.
Web research, once per recording The 1966 record's story — a flop, a producer's overdub, a number one. The 1981 concert's story — a free show for a broke city, a reunion nobody expected. Two different jobs.
Measured word times ElevenLabs Scribe over both masters, then a greedy walk of those words against the known lyric lines. 34 of 34 lines timed in each recording, from measurement rather than interpolation.
70 stills with rights attached
illustration over Wikimedia and Openverse, licence-gated at fetch time, every hit carrying attribution, license, license_url, source_page_url, author.
One transcode from the source 320 kbps from the original Opus, not from the lossy MP3s sitting next to them. Provenance verified rather than assumed.
Why the zero matters
The Genius entry for the live recording carries no annotations at all. That single number is the argument for splitting the research in two. Annotation sources describe the work — what a lyric means, which line quotes Daniel 5. They say nothing about the recording: who made it, when, in front of whom. And on this material, the recording's circumstances were the entire reason the film was worth making.
Two things we checked rather than believed
The file labelled "1966" is the electric single, not the 1964 acoustic take — its sub-150 Hz energy is 2.5× the live recording's (a bass guitar the acoustic version does not have) and it ends in a clean five-second fade. That matters, because the middle act of the film is about that overdub: the file labelled 1966 is the thing Paul Simon was reportedly horrified by.
And the famous claim that the 1981 crowd applauds the line about
"ten thousand people" turns out to be measurable. Scribe tags
[crowd cheering] at 100.54–102.31 s; the lyric line ends at
100.52 s and the next begins at 102.50 s. The reaction sits exactly in
the gap. That is independent confirmation of a widely-repeated story — and it
became the film's cold open and its centrepiece.
Layer two: artifacts become a film
The render is a traversal, and it emits its own record.
Two details make this work rather than merely sound tidy.
The renderer emits its own timeline. Not a transcript, not an
estimate — the actual [start, duration) of every beat as mixed.
The picture track is cut from that, so images change where the narration changes
subject. Eyeballing panel times off a script is the classic way to get a film
that drifts out of sync by the third minute.
Clips are namespaced. A comparison piece needs to say which recording a quote comes from, and a token matcher scores the same lyric line equally well in both. So references carry a prefix and a router dispatches them:
source = NamespacedSegmentSource({
"1966": TimedLineSegmentSource(lines=studio, asset_path="studio.mp3"),
"1981": TimedLineSegmentSource(lines=live, asset_path="live.mp3"),
})
SegmentBeat("1981: and in the naked light i saw") # → the live master
An unknown prefix raises rather than falling through. Silently picking the wrong recording would ship one performance under commentary describing another — a failure the listener cannot detect and a diff does not show.
Two voices, because there are two kinds of material
The script contains both an argument and a documented record — dates, names, sourced quotes. Those get different voices. The handoff does the signposting without a word of signposting: measured across the finished episode, the presenter sits at 127–134 Hz median pitch and the record voice at 95–103 Hz, about four semitones apart. The renderer tracked them as separate kinds too — 375.6 s of commentary against 128.8 s of record.
What the pictures had to survive
Found-image film is where correctness quietly leaks. Six stills passed a contact sheet and were still wrong once we read the Commons titles: a category called Crowds returned Library of Congress portraits of jazz musicians, a Dresden apartment block sat under the words "tenement halls", a 1910 hotel was captioned as a record shop.
Three stills we kept are the more interesting case. They are genuine large concerts in the right park that are not this concert — the Beach Boys in 1971, a Judy Collins audience at the Schaefer Bandstand — plus one dense crowd that is Liverpool Street station in London. Unlabelled under this narration each is an implicit false claim. Labelled on screen, each is honest illustration. So every still carries its own caption, generated from the same manifest that recorded the fetch, and a still with no identifiable subject gets an explicit "unlabelled" marker rather than silence.
A thumbnail tells you what a picture looks like. It does not tell you what it is.
Layer three: the same artifacts, a different performance
The first cut was correct and boring. Here is the measurement that fixed it.
The ten-minute film shipped, and the verdict on the narration was one word: somniferous. The script was fine. The facts were checked. It was simply dull to listen to.
The received wisdom — encoded in our own tooling — was that expressiveness comes
from the voice model's stability setting, and that inline
[audio tags] should be used sparingly because over-tagging
reads as camp. So the first script carried ten tags in 1371 words and varied
stability across four "registers" (0.30 / 0.40 / 0.55 / 0.65).
That advice is backwards, and it is measurable. One sentence, one voice, pitch range (f0 p5–p95) as the proxy for liveliness:
| Lever | Range, before → after | Effect |
|---|---|---|
| Audio tag density plain text → densely tagged | 70.8 → 124.4 Hz | +76% |
| Voice choice a warm read → a vivid one | 124 → 199 Hz | +60% |
stabilityacross its entire range, 0 → 1 | 85 → 72 Hz | −15% |
So the four carefully-chosen registers were worth about 2 Hz
of pitch range between them, while the one lever that would have worked was being
deliberately rationed. Tag density is the engine; stability is nearly
noise.
The fix is now a gate rather than a matter of taste. audit_expressiveness
enforces a band of 2–5 tags per 100 words and complains in whichever direction you
are out — because the two failure modes are real and pull opposite ways:
under-tagged reads as somniferous, a tag on every clause reads as camp. The
original script scores 0.73, half the floor. The re-voiced script
scores 3.66, with sixteen distinct tags.
>>> braidio.audio_tag_rate(prose) # the first cut
0.73
>>> braidio.audit_expressiveness(prose)
['audio-tag rate 0.7/100 words is below 1.5 — this will read flat.
Tags are the main expressiveness lever on v3; write the performance
into the text.']
And the result is measurable on the finished mixes rather than on a test sentence. Across the whole ten minutes, pitch range went from 110.2 Hz to 158.2 Hz — a 44% widening — for the same facts, the same structure, the same six clips and the same stills.
Note what re-voicing did not require. No new research, no new Scribe pass, no new images, no new rights review. The facts, the act structure, the six clips and their measured in/out points are identical. Only the performance changed — which is exactly what "the artifacts are the durable thing" is supposed to buy you.
Layer four: three lengths from one body of work
A short version is not a truncation.
The essay
Three acts. The lyric read closely, both recordings' histories in full, the payoff, a coda. Six clips, 59 distinct stills, 57 image credits.
The argument
Same spine, no elaboration. Loses the close reading, the Boston DJ, the Dylan session, the coda. Keeps all three acts and the payoff.
One move
The crowd moment, what it means, and where to go for the rest. Rendered vertical, 1080×1920. Ends on a pointer to the long cut rather than on a conclusion.
All three are published private, each with captions built from the script rather than from ASR, a credits roll generated from the same manifest that recorded the image fetches, and an on-screen label on every identifiable still.
A two-minute version of a ten-minute essay that simply stops at two minutes has no ending. Each cut keeps the same spine — two recordings, sixteen years, a crowd hearing itself described — and sheds supporting detail at a different rate. The one-minute short deliberately does not resolve: its job is to make you want the long one, and it says so.
Because the artifacts are addressable, writing those two scripts was the whole job. Both drew on the same measured line timings, the same two masters, the same vetted still pool. Both were checked by the same two gates before a cent was spent on synthesis — and both were caught by them: the first draft of the "two-minute" cut ran 3.0 minutes, and the first draft of the short was over the tag ceiling.
What it cost
| Stage | Spend | Reused? |
|---|---|---|
| Genius acquisition + web research | free | built once |
| Scribe word timings, both masters | cents | built once |
| Image search, vetting, fetch (70 stills) | free | built once |
| 10-minute narration — first cut | $2.29 | — |
| 10-minute narration — re-voiced | $2.43 | everything but the script |
| 2-minute cut | $0.51 | everything but the script |
| 1-minute short | $0.21 | everything but the script |
The two extra cuts together cost $0.72 and about twenty minutes of wall clock. Against the first film, that is roughly 3%. None of the expensive parts — deciding what is true, checking that a claim about a recording is actually about that recording, looking at every picture before it reaches the screen — had to happen twice.
Seven things that cost a rebuild
All of them silent failures. None of them would raise an exception.
- A cache that only checks existence. A throttled first pass wrote 330 px thumbnail fallbacks; every later run found the files on disk and never retried. The whole film was built from 6× upscales before anyone checked a pixel dimension.
- Wikimedia throttles on User-Agent policy, not just rate — and it presents as HTTP 429 with
Retry-After. With a vague agent string: about two successful requests per hundred seconds. With a compliant tool/URL/email string: 200 immediately. Same URLs, measured back to back. - Credit lines that name no licence. For a minority of hits the ready-made attribution string comes back as the bare author, while the licence fields on the same record are correct. Rendering it as documented ships a name as the entire credit for a CC BY-SA image.
- A duration that lies. An encode killed at 90% still reported the correct 607.8 s, because the container takes duration from the audio stream while the video held only 540 s of frames. Counting packets against duration × fps is the check that catches it.
- Panels that fall through to a default. Spans headed by a scene break inherited no image and quietly took a generic aerial — including the twelve seconds carrying the film's first music clip. Now it raises.
- A float landing a hair below zero. The Ken Burns sampler can produce a crop edge of
-1.1e-14where0.0was meant — the rect is clamped, but the expansion that fits it to the output aspect runs afterwards, in floats.math.floor(-1.1e-14)is-1, and in numpy-1means from the last row, not one above the top. The frame is then resampled from a different region of the picture. Sometimes that raises and kills a render late; sometimes it fits, and the finished film simply contains a frame showing a different rectangle — which on screen is the motion jumping. It is intermittent because it turns on where a float lands, which is why it had been seen more than once without a cause being found. - A frame cache keyed on the wrong thing. The renderer caches prepared canvases as
<index>_<still>.jpg— a key with no frame size in it. With all three cuts sharing one working directory, the 1080×1920 vertical short silently reused 1280×720 canvases the two-minute cut had already written, and came out 404×720. Exactly the same mistake as caching a download on existence alone, two layers apart.
Note the family resemblance in items 1 and 7. Both are caches whose key omits the thing that varies, and both fail by succeeding — returning a plausible artifact rather than an error. Those are the expensive ones, because nothing downstream has any reason to look twice.
Item 6 has a second lesson, about how a bug propagates. The crash forced a
workaround — an ad-hoc zoompan filter chain in place of the
real motion engine — and that carried its own trap: the usual
pull-out idiom resets to the start zoom the instant it reaches the floor, a
hard snap partway through every second panel. So the visible defect was two
steps removed from its cause. Fixing the engine removed the reason to
hand-roll a substitute at all, and the substitute went in the bin.
Every one of these is now either a gate in code or a warning in the tooling's own documentation, which is the only form in which a lesson survives contact with the next project.