From b78b846189124ede389a25aee2a3b45c98b528ec Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Fri, 30 Oct 2026 08:25:35 +0000 Subject: Tell the phone what reading the words concluded Three numbers on the line that already says an episode has a transcript: how many stretches of advertising were found, how long they come to, and how. The method travels with them because subtraction and ad-copy reading are not close in quality -- 92-104% of the length gap against 0-29% -- and a figure on its own would invite reading the two alike. An episode that turned up nothing says nothing rather than zero, which is the same three-state care the rest of this takes. Still nothing published as a skip. This is so the evidence can be looked at from the app rather than from my logs, which is what the decision to publish should rest on. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B --- server/internal/api/api.go | 19 ++++++++++ server/internal/api/transcript_test.go | 66 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) (limited to 'server/internal') diff --git a/server/internal/api/api.go b/server/internal/api/api.go index d2ad841..0d18c0f 100644 --- a/server/internal/api/api.go +++ b/server/internal/api/api.go @@ -97,6 +97,16 @@ type transcriptNote struct { // server keys them by its own id, and handing over a URL is what keeps that from having // to change. Empty when there is nothing to fetch. URL string `json:"url,omitempty"` + + // Ads and AdMs are what reading the words concluded about the advertising in this episode: + // how many stretches and how long they come to. Candidates, not skips -- nothing in the + // playback path reads them. + Ads int `json:"ads,omitempty"` + AdMs int64 `json:"adMs,omitempty"` + // Method is how they were found: "difference" where a second description of the programme + // was available to subtract, or the kinds of ad copy that were read otherwise. The two are + // not close in quality and the screen should not present them as if they were. + Method string `json:"method,omitempty"` } type breakRange struct { @@ -233,6 +243,15 @@ func (s *Server) transcriptNoteFor(e *store.Episode) *transcriptNote { if e.HasTranscript() { note.URL = "/api/transcript?episode=" + url.QueryEscape(e.ID) + "&token=" + url.QueryEscape(s.opts.Token) + if held, err := (transcript.Store{Dir: s.opts.TranscriptDir}).Get(e.ID); err == nil { + note.Ads = len(held.Ads) + for _, ad := range held.Ads { + note.AdMs += ad.EndMs - ad.StartMs + if note.Method == "" && len(ad.Signals) > 0 { + note.Method = ad.Signals[0] + } + } + } } return note } diff --git a/server/internal/api/transcript_test.go b/server/internal/api/transcript_test.go index 651225d..6d0ad7f 100644 --- a/server/internal/api/transcript_test.go +++ b/server/internal/api/transcript_test.go @@ -235,3 +235,69 @@ func TestTheTranscriptRouteNeedsTheToken(t *testing.T) { t.Errorf("status %d, want 401", rec.Code) } } + +/* +What reading the words concluded about the advertising, on the episode response. + +The phone needs it per episode and it is three numbers, so it travels with the line that says +there is a transcript rather than waiting for somebody to open one. The method travels with it +because subtraction and ad-copy reading are not close in quality -- 92-104% of the length gap +against 0-29% -- and a screen that showed only a figure would invite reading them alike. +*/ +func TestTheResponseSaysWhatTheWordsFound(t *testing.T) { + s, st, _, audioDir := newServer(t) + e := seedTranscribedEpisode(t, s, st, audioDir) + + held, err := (transcript.Store{Dir: s.opts.TranscriptDir}).Get(e.ID) + if err != nil { + t.Fatalf("Get: %v", err) + } + held.Ads = []transcript.Candidate{ + {StartMs: 1_000, EndMs: 61_000, Signals: []string{transcript.SignalDifference}}, + {StartMs: 100_000, EndMs: 130_000, Signals: []string{transcript.SignalDifference}}, + } + if err := (transcript.Store{Dir: s.opts.TranscriptDir}).Put(held); err != nil { + t.Fatalf("Put: %v", err) + } + + rec := get(t, s, "/api/episode?feed=https://f&guid=g1") + var got struct { + Transcript *struct { + Ads int `json:"ads"` + AdMs int64 `json:"adMs"` + Method string `json:"method"` + } `json:"transcript"` + } + if err := json.NewDecoder(rec.Body).Decode(&got); err != nil { + t.Fatalf("decode: %v", err) + } + if got.Transcript == nil { + t.Fatal("no transcript line at all") + } + if got.Transcript.Ads != 2 || got.Transcript.AdMs != 90_000 { + t.Errorf("got %+v, want 2 breaks totalling 90s", *got.Transcript) + } + if got.Transcript.Method != transcript.SignalDifference { + t.Errorf("method = %q, want %q", got.Transcript.Method, transcript.SignalDifference) + } +} + +// An episode whose words turned up no advertising says nothing rather than zero. +func TestAnEpisodeWithNoAdvertisingInItsWordsSaysNothing(t *testing.T) { + s, st, _, audioDir := newServer(t) + seedTranscribedEpisode(t, s, st, audioDir) + + rec := get(t, s, "/api/episode?feed=https://f&guid=g1") + var got struct { + Transcript *struct { + Ads int `json:"ads"` + AdMs int64 `json:"adMs"` + } `json:"transcript"` + } + if err := json.NewDecoder(rec.Body).Decode(&got); err != nil { + t.Fatalf("decode: %v", err) + } + if got.Transcript.Ads != 0 || got.Transcript.AdMs != 0 { + t.Errorf("got %+v, want nothing claimed", *got.Transcript) + } +} -- cgit v1.2.3