From dbcdfa0f5549984646221ec7c582fd81ea106a66 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Sat, 31 Oct 2026 01:00:19 +0000 Subject: Write down the transcript pipeline, and that it is verified on a device CLAUDE.md still described the server as doing audio detection and nothing else. Six days of work later it recognises speech, subtracts transcripts, and publishes the result as skips, and none of that was written down anywhere a future session would look. The device verification is the part worth recording most. Heavyweight's "Jonathan + Death of an Artist", breaks at 0-1:45 and 13:32-15:16: downloaded from the server, both rows landed in episode_midrolls, twenty seconds of playing put the position at 2:03 -- which is only reachable by jumping the pre-roll -- and the post-roll skipped to the end, marked the episode played and reset the position. Server to phone to playback, on hardware, not inferred from the fact that each link has a passing test. Also written down: the measured 10.3x realtime, the four alignment mistakes that only measurement caught, why a declined transcript is kept rather than discarded, why refetching belongs to slow-drip shows, and the shared-text signal that is prototyped but not built. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- CLAUDE.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 44189b4..4faa0f4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -347,6 +347,70 @@ by default and correct; it is simply waiting for a publisher who marks breaks. eaten). Both record the refusal, because "found nothing" and "refused what it found" are different states and conflating them is the readout mistake again. +- **Episodes have words now, and the words are the best ad detector in the project.** Two + sources, and they are not interchangeable. + + **A publisher's own ``** is exact and free. Eleven of 44 feeds carry one. + It describes the publisher's *master*, so it is only usable where the show inserts nothing: + measured across every mirrored show that publishes one, the transcript tracks the **declared** + length every time, which means it aligns precisely when served and declared agree and is + minutes out of step precisely when they do not. `transcript.Fits` makes that call from the two + lengths rather than from the words. 38 taken, 19 declined. + + **Speech recognition** (whisper.cpp, `base.en`, installed at `/opt/whisper`) describes exactly + the bytes the phone will play. Measured, not estimated: **10.3x realtime on six threads, 793MB + peak** -- a 63-minute episode in 6m11s. The earlier guess of "roughly realtime" was wrong by an + order of magnitude. + + **A declined transcript is kept as the master, not thrown away.** Declined means "this copy + carries inserted advertising the master does not describe", which is precisely the episode + subtraction works on. + +- **`transcript.Inserted` finds advertising by subtraction, and it is a measurement rather than + a guess.** The master says what the programme is, the recognised copy says what was played, + and the difference is the advertising by definition. Across the 18 episodes holding a master + it accounts for **92% to 104%** of what the declared-versus-served length gap implies, finding + every pod. Reading ad copy (`transcript.Spot`) scores 0-29% on the same episodes, and only the + first is ever published. + + Four things it took measurement to get right, all of them documented in their own commits: + anchors name the *start* of a matching run (getting that wrong skipped the programme's last + sentence); a gap is partitioned line by line rather than refused when the master drifted + (which was losing a whole pod on three episodes); whisper's `[music]` notes are trimmed from + break edges but still shown; and a short line is judged on a three-word run, because "I'm Jack + Rhysider" comes back as "Resider" and one misheard word in four hid the host's own sentence + inside a break. + +- **Transcript breaks are published, and this is verified on a device end to end** (2026-10-31). + Heavyweight's "Jonathan + Death of an Artist", breaks at 0-1:45 and 13:32-15:16: downloaded + from the server, both rows landed in `episode_midrolls`, playback started past the pre-roll + (20 seconds of playing put the position at 2:03) and the post-roll skipped to the end, marked + the episode played and reset the position. + + They live in `store.Episode.WordBreaks`, **not** beside the audio scan's: `detectAds` rewrites + `Breaks` from scratch on every pass and would wipe them. `Episode.AllBreaks()` serves the + union, merging ranges that overlap or touch, with advertising winning where two disagree. + + **`transcript.Publishable` is the only thing that turns words into a seek.** It publishes + subtraction only, refuses anything for an episode with no length gap to check against, and + refuses a candidate set claiming more than 150% of what was inserted. + +- **Refetching a second copy is for slow-drip and leisure shows, not daily news.** Peter's steer, + and the numbers agree: a second fetch minutes after the first came back 20 seconds different + and subtraction found 21% of the advertising; another came back the same length to the second + and found none. Insertion varies the *amount* per request, not the campaign. Gated to episodes + four weeks old. + +- **Text shared across a show's episodes is the strongest unexploited signal, and it is free.** + Eight episodes mirrored within a minute carry the same campaign and different programmes, so + text appearing across several of them is the advertising -- the text analogue of the audio + scan, using transcripts already on disk. Prototyped against the live library: Heavyweight 111%, + Incels 105%, Ezra Klein 92%, Tangle 83% (against refetching's 21% on the same show), covering + 21 shows. It also reaches the ~81 episodes with no length gap at all, where ads are baked into + what the publisher serves and nothing else can see them. **It catches a show's own furniture + too** -- Tangle's welcome, its credits, "we'll be right back after this quick break" -- so it + needs the same intro/cue/outro/ad classification `skips.Classify` already does. Not built. + - **Intro and outro offsets are computed, logged, and deliberately not published.** They assume the agreeing episodes were fetched independently, and mirroring eight at once means they were not. Proved, not suspected: the segment confirmed on 8 of 8 Radiolab episodes was searched for -- cgit v1.2.3