package transcript import "fmt" /* Which of the advertising read out of an episode's words is fit to skip. Everything else in this package produces candidates to look at. This is the one function that decides what the phone will seek past, so it is deliberately narrow. Two detectors produce candidates and they are not close in quality. Subtraction -- comparing an episode's words against a second description of the same programme -- measured 92% to 104% of what the declared-versus-served length gap implies, finding every pod, with its edges pulled back off the programme. Reading the ad copy measured 0%, 0%, 15% and 29% on the same kind of episode. Only the first is acted on. **That 92-104% is the figure for a publisher's transcript**, which is a second description of the programme written once. Against a refetch -- a second download of the same dynamically-assembled file, carrying its own advertising at its own positions -- the same code recovers a median of 33% across fifteen episodes, with a range of 20% to 146%. Measured on the live corpus 2026-11-02; see docs/detection-findings.md. Nothing below distinguishes the two, which is a known gap, not a decision. The length gap is both the measure and the guard. It comes from the feed and the file rather than from any detector, which makes it the one number able to contradict one. */ // overshootLimit is how much more than the lengths imply a candidate set may claim and still be // published, as a percentage. 150 leaves room for a music sting at the edge of a pod without // leaving room for having found the wrong thing entirely. // // It was set against the publisher-transcript spread, which runs to 104%, so it sat well clear of // the distribution. Where it actually binds is the shows whose length gap is noise rather than a // measurement -- Reply All's gaps run from -0.48 to +1.02 minutes across eight episodes, so a // percentage of one of them means little either way. Raising this limit would not help and // lowering it would not either; see docs/detection-findings.md for what would. const overshootLimit = 150 // Publishable returns the candidates fit to skip, or nothing and the reason. // // A refusal is a different state from having found nothing, and the caller keeps them apart -- // the same distinction the ad scan's own refusals have needed twice before. func Publishable(found []Candidate, servedMs, declaredMs int64, master Source) ([]Candidate, string) { if len(found) == 0 { return nil, "" } measured := make([]Candidate, 0, len(found)) for _, c := range found { if len(c.Signals) == 1 && c.Signals[0] == SignalDifference { measured = append(measured, c) } } if len(measured) == 0 { return nil, "found by reading the ad copy, which has never agreed with the lengths" } // What was subtracted decides whether this is a measurement or a guess. A publisher's // transcript describes the programme once; a refetch is another copy of the same assembled // file, with its own advertising in its own places, so the difference between them is two ad // loads rather than one. Measured on the live corpus: 94-104% of the length gap from a // published master, a median of 33% from a refetch. // // An allowlist, not a "not refetch" test. Sixteen episodes carry a master whose label is // blank -- the field postdates the first masters written -- and a denylist published them on // the strength of not being the one named bad kind. if master != SourcePublished { kind := string(master) if kind == "" { kind = "a master that does not say what it is" } else { kind = "a " + kind } return nil, fmt.Sprintf("subtracted from %s rather than the publisher's transcript, "+ "which is the only master measured accurate enough to skip on", kind) } gap := servedMs - declaredMs if !HasInsertedAdvertising(servedMs, declaredMs) { return nil, "the lengths say nothing was inserted into this copy, so there is " + "nothing to check these against" } var total int64 for _, c := range measured { total += c.EndMs - c.StartMs } if total*100 > gap*overshootLimit { return nil, fmt.Sprintf( "claims %s against the %s the lengths say was inserted, so it has found "+ "something other than advertising", clock(total), clock(gap)) } return measured, "" } // recurringShareLimit is how much of an episode may recur before the result is refused, as a // percentage. Thirty-five, the same share the ad scan refuses at and for the same reason: a // re-aired episode, a two-parter or a show built around a formulaic segment would otherwise be // eaten whole. const recurringShareLimit = 35 /* PublishableRecurring returns the recurring text fit to skip as the show's own audio. Separate from [Publishable] because it publishes a different thing. Subtraction measures advertising and the phone skips advertising unconditionally; this measures only that a show says something every week, which is its sponsor reads and its furniture together, and nothing available separates them -- ad copy appears in 5% of the advertising and 4% of the furniture, and length and position overlap throughout. So it is published as the show's own audio, which the listener's own setting governs, and the stronger claim is left unmade. Guarded by share of the episode rather than by the length gap, because the episodes this is worth most on are the ones with no gap at all: a show that bakes its reads into what it publishes declares the length it serves, so the gap is blind to every advertisement in it. */ func PublishableRecurring(found []Candidate, episodeMs int64) ([]Candidate, string) { recurring := make([]Candidate, 0, len(found)) for _, c := range found { if len(c.Signals) == 1 && c.Signals[0] == SignalRecurring { recurring = append(recurring, c) } } if len(recurring) == 0 { return nil, "" } if episodeMs <= 0 { return nil, "the episode's length is unknown, so there is no share to judge this against" } var total int64 for _, c := range recurring { total += c.EndMs - c.StartMs } if total*100 > episodeMs*recurringShareLimit { return nil, fmt.Sprintf( "%s of a %s episode recurs, which is too much of it to have been understood", clock(total), clock(episodeMs)) } return recurring, "" }