diff options
Diffstat (limited to 'server/cmd')
| -rw-r--r-- | server/cmd/listend/main.go | 76 |
1 files changed, 72 insertions, 4 deletions
diff --git a/server/cmd/listend/main.go b/server/cmd/listend/main.go index a4400d2..3ca2727 100644 --- a/server/cmd/listend/main.go +++ b/server/cmd/listend/main.go @@ -34,6 +34,7 @@ import ( "github.com/terst/listen/server/internal/signature" "github.com/terst/listen/server/internal/skips" "github.com/terst/listen/server/internal/store" + "github.com/terst/listen/server/internal/transcript" ) func main() { @@ -69,6 +70,8 @@ func main() { } decoder := decode.Decoder{} + transcriptDir := filepath.Join(*dataDir, "transcripts") + transcripts := transcript.Store{Dir: transcriptDir} worker := &worker{ state: state, statePath: statePath, @@ -77,13 +80,16 @@ func main() { keep: *keep, mirror: &mirror.Mirror{AudioDir: audioDir, Probe: decoder}, decoder: decoder, + transcripts: transcripts, + harvester: transcript.Harvester{Store: transcripts}, requests: make(chan request, 64), wakeups: make(chan struct{}, 1), } server := api.New(state, worker, api.Options{ - Token: token, - AudioDir: audioDir, + Token: token, + AudioDir: audioDir, + TranscriptDir: transcriptDir, // Persist the list the moment it arrives, then poll. // // Both halves were learned the hard way. The list is the one thing the server cannot @@ -156,8 +162,12 @@ type worker struct { keep int mirror *mirror.Mirror decoder decode.Decoder - requests chan request - wakeups chan struct{} + // transcripts holds each episode's words, and harvester takes the publishers' own where + // they describe the copy that was mirrored. See transcript.Fits for when they do not. + transcripts transcript.Store + harvester transcript.Harvester + requests chan request + wakeups chan struct{} } // wake asks for a poll now. Coalescing, so a burst of subscription updates is one poll. @@ -291,6 +301,14 @@ func (w *worker) pollOne(ctx context.Context, feedURL string) { if e.Mirrored() { _ = w.mirror.Prune(e) } + // The words go with the audio. A transcript of a file nobody holds any more is of no + // use to the phone, which plays the server's bytes, and the directory would otherwise + // grow for ever. + if e.TranscriptExamined() { + _ = w.transcripts.Prune(e.ID) + e.TranscriptSource, e.TranscriptCues, e.TranscriptNote = "", 0, "" + e.TranscriptAtUnix = 0 + } } fetched := 0 @@ -311,9 +329,59 @@ func (w *worker) pollOne(ctx context.Context, feedURL string) { if fetched > 0 { log.Printf("%s: mirrored %d new episode(s)", channel.Title, fetched) } + w.harvestTranscripts(ctx, show, kept) w.detect(ctx, show, kept) } +// harvestTranscripts takes the publishers' own transcripts for a show's mirrored episodes. +// +// Only where the publisher's master is what was mirrored, which transcript.Fits decides from the +// declared-versus-served length gap rather than from the words. A show using dynamic ad +// insertion publishes a transcript of the ad-free programme, and reading along with it would be +// minutes out of step with the audio the phone plays. +// +// Each episode is examined once. A refusal is a judgement about this copy, so re-fetching it on +// every poll would be work done for ever to reach the same answer -- and the refusal is itself +// the record of which episodes speech recognition would have to do. +func (w *worker) harvestTranscripts(ctx context.Context, show *store.Show, candidates []*store.Episode) { + taken, declined := 0, 0 + for _, e := range candidates { + if ctx.Err() != nil { + return + } + if !e.Mirrored() || e.TranscriptExamined() || e.TranscriptURL == "" { + continue + } + result, err := w.harvester.Harvest(ctx, transcript.Offer{ + EpisodeID: e.ID, + URL: e.TranscriptURL, + Type: e.TranscriptType, + ServedMs: e.DurationMs, + DeclaredMs: e.DeclaredDurationMs, + }) + if err != nil { + // The download failed, so nothing has been decided and the next poll tries again. + log.Printf("%s: transcript for %q: %v", show.Title, e.Title, err) + continue + } + switch { + case result.Stored: + e.TranscriptSource = string(transcript.SourcePublished) + e.TranscriptCues = result.Cues + e.TranscriptAtUnix = time.Now().Unix() + e.TranscriptNote = "" + taken++ + case result.Note != "": + e.TranscriptNote = result.Note + e.TranscriptAtUnix = time.Now().Unix() + declined++ + } + } + if taken > 0 || declined > 0 { + log.Printf("%s: transcripts -- took %d, declined %d", show.Title, taken, declined) + } +} + // detect runs the intro/outro search over a show's mirrored episodes and records the result. // // Every mirrored episode at once, which is the whole reason this is a server: the phone could |
