1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
package transcript
import (
"fmt"
"github.com/terst/listen/server/internal/advertising"
)
const (
// insertionMs is how far a served file may exceed its declared length before the excess is
// read as inserted advertising rather than as the feed being approximate. One advertising
// slot, the same judgement skips.Classify makes and for the same reason.
insertionMs = advertising.UnitMs
// tailMs is how much of a file may follow the last spoken word before the silence has to be
// explained. Closing music runs to fifteen seconds in this corpus (LOW's "Part 5: Bane"
// ends 15.2s before its file does); the shortest advertisement does not fit in sixty.
tailMs = 60_000
)
// Verdict is whether a published transcript may be used for a particular mirrored file, and if
// not, why not.
//
// Why, not just no. A transcript refused because the file carries inserted advertising is a
// different thing from one refused because it describes another recording: the first is an
// episode that needs speech recognition, and the second is a publisher's mistake.
type Verdict struct {
OK bool
Why string
Reason Reason
}
// Reason says which kind of refusal this is, so a caller can act on it. Read rather than
// matched against the wording of Why, which is for people.
type Reason string
const (
// ReasonInserted: the file carries advertising the publisher's master does not describe.
// The transcript is still the master, and subtracting it from a recognised reading of the
// served file is what locates that advertising. See Inserted.
ReasonInserted Reason = "inserted"
// ReasonOtherRecording: the transcript describes something that is not this file at all,
// so it is no use for anything.
ReasonOtherRecording Reason = "other-recording"
// ReasonEmpty: there were no cues to judge.
ReasonEmpty Reason = "empty"
)
// Fits reports whether a transcript ending at lastCueEndMs describes a mirrored file of
// servedMs, given the declaredMs the feed claims.
//
// The decision is made on the lengths rather than on the words because the lengths are
// independent of the transcript: the feed declares the ad-free programme's length and the file
// measures what was served, so their difference is the advertising inserted into this copy.
// A publisher's transcript always describes the ad-free master, so it aligns exactly when that
// difference is nothing.
func Fits(lastCueEndMs, servedMs, declaredMs int64) Verdict {
if lastCueEndMs <= 0 {
return Verdict{Why: "the transcript has no cues", Reason: ReasonEmpty}
}
if lastCueEndMs > servedMs {
return Verdict{Why: fmt.Sprintf(
"the transcript runs %s past the end of the file, so it describes another recording",
clock(lastCueEndMs-servedMs)), Reason: ReasonOtherRecording}
}
if declaredMs > 0 {
if gap := servedMs - declaredMs; gap > insertionMs {
return Verdict{Why: fmt.Sprintf(
"this copy carries %s of inserted advertising the transcript does not describe",
clock(gap)), Reason: ReasonInserted}
}
return Verdict{OK: true}
}
// No declared length to compare against, so the transcript answers for its own gap.
if gap := servedMs - lastCueEndMs; gap > tailMs {
return Verdict{Why: fmt.Sprintf(
"the transcript stops %s before the file does, and the feed declares no length",
clock(gap)), Reason: ReasonInserted}
}
return Verdict{OK: true}
}
// HasInsertedAdvertising reports whether this copy is longer than the feed says the programme
// is, by more than the feed could be approximate about.
//
// The one fact about an episode that comes from neither a detector nor a transcript. An episode
// without it has nothing inserted to find, so there is no point fetching a second copy to
// compare against -- the two would be the same recording.
func HasInsertedAdvertising(servedMs, declaredMs int64) bool {
return declaredMs > 0 && servedMs-declaredMs > insertionMs
}
// LastCueEndMs is where a transcript stops describing the audio.
func LastCueEndMs(cues []Cue) int64 {
if len(cues) == 0 {
return 0
}
return cues[len(cues)-1].EndMs
}
func clock(ms int64) string {
s := ms / 1000
return fmt.Sprintf("%d:%02d", s/60, s%60)
}
|