diff options
Diffstat (limited to 'server/internal')
| -rw-r--r-- | server/internal/api/api.go | 4 | ||||
| -rw-r--r-- | server/internal/store/store.go | 27 | ||||
| -rw-r--r-- | server/internal/store/store_test.go | 38 |
3 files changed, 67 insertions, 2 deletions
diff --git a/server/internal/api/api.go b/server/internal/api/api.go index 367a1c6..2db0f91 100644 --- a/server/internal/api/api.go +++ b/server/internal/api/api.go @@ -117,6 +117,9 @@ type breakRange struct { // when a break is tapped: it is the only evidence a listener can weigh before deciding // whether the detector got this one right. Partners int `json:"partners,omitempty"` + // Origin is which detector found it: "audio", "words", or both. Same reasoning as + // Partners -- a listener judging a skip wants to know what decided it. + Origin string `json:"origin,omitempty"` } // Handler returns the routed, authenticated handler. @@ -215,6 +218,7 @@ func (s *Server) episode(w http.ResponseWriter, r *http.Request) { EndMs: br.EndMs, Kind: br.Kind, Partners: br.Partners, + Origin: br.Origin, }) } writeJSON(w, http.StatusOK, episodeResponse{ diff --git a/server/internal/store/store.go b/server/internal/store/store.go index b86474d..931cdd3 100644 --- a/server/internal/store/store.go +++ b/server/internal/store/store.go @@ -174,8 +174,20 @@ type Break struct { // Kind is "ad" for inserted advertising, or "intro", "cue" or "outro" for the show's own // audio. The phone skips advertising always and furniture by preference. Kind string `json:"kind,omitempty"` + // Origin is which detector found it: [FromAudio] or [FromWords]. The listener judging a + // skip wants to know why it happened, and the two are not equally sure of themselves -- + // one compares waveforms across a batch, the other reads what was said. + Origin string `json:"origin,omitempty"` } +// Where a break came from. +const ( + // FromAudio is the cross-correlation scan over several episodes' waveforms. + FromAudio = "audio" + // FromWords is subtraction or recurrence over the episodes' transcripts. + FromWords = "words" +) + // AllBreaks is every stretch to skip, from both detectors, as one ordered list. // // Overlapping and touching ranges are merged, because playback seeks to a break's end: two @@ -187,8 +199,14 @@ type Break struct { // other way means sitting through an advertisement the server had already found. func (e *Episode) AllBreaks() []Break { all := make([]Break, 0, len(e.Breaks)+len(e.WordBreaks)) - all = append(all, e.Breaks...) - all = append(all, e.WordBreaks...) + for _, br := range e.Breaks { + br.Origin = FromAudio + all = append(all, br) + } + for _, br := range e.WordBreaks { + br.Origin = FromWords + all = append(all, br) + } sort.Slice(all, func(i, j int) bool { return all[i].StartMs < all[j].StartMs }) var merged []Break @@ -203,6 +221,11 @@ func (e *Episode) AllBreaks() []Break { if br.Partners > merged[last].Partners { merged[last].Partners = br.Partners } + // Both detectors found it, which is worth saying: they agree by entirely + // different means. + if br.Origin != merged[last].Origin { + merged[last].Origin = FromAudio + "+" + FromWords + } continue } merged = append(merged, br) diff --git a/server/internal/store/store_test.go b/server/internal/store/store_test.go index c4eafdd..f0c111b 100644 --- a/server/internal/store/store_test.go +++ b/server/internal/store/store_test.go @@ -602,3 +602,41 @@ func TestAnEpisodeOfUnknownAgeIsNotRefetched(t *testing.T) { t.Error("would refetch an episode of unknown age") } } + +/* +A break says which detector found it. + +The listener judging a skip wants to know why it happened, and the two detectors are not equally +sure of themselves: one compares waveforms across a batch of episodes, the other reads what was +said. A break both found is worth marking as such -- they agree by entirely different means. +*/ + +func TestABreakSaysWhereItCameFrom(t *testing.T) { + e := Episode{ + Breaks: []Break{{StartMs: 60_000, EndMs: 90_000, Kind: "ad"}}, + WordBreaks: []Break{{StartMs: 600_000, EndMs: 660_000, Kind: "ad"}}, + } + + got := e.AllBreaks() + if got[0].Origin != FromAudio { + t.Errorf("first break says %q, want %q", got[0].Origin, FromAudio) + } + if got[1].Origin != FromWords { + t.Errorf("second break says %q, want %q", got[1].Origin, FromWords) + } +} + +func TestABreakBothDetectorsFoundSaysSo(t *testing.T) { + e := Episode{ + Breaks: []Break{{StartMs: 60_000, EndMs: 90_000, Kind: "ad"}}, + WordBreaks: []Break{{StartMs: 80_000, EndMs: 120_000, Kind: "ad"}}, + } + + got := e.AllBreaks() + if len(got) != 1 { + t.Fatalf("got %+v, want one merged break", got) + } + if got[0].Origin != FromAudio+"+"+FromWords { + t.Errorf("origin = %q, want both named", got[0].Origin) + } +} |
