summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--server/internal/transcript/trim.go23
-rw-r--r--server/internal/transcript/trim_test.go31
2 files changed, 45 insertions, 9 deletions
diff --git a/server/internal/transcript/trim.go b/server/internal/transcript/trim.go
index 995bc85..f2ded4c 100644
--- a/server/internal/transcript/trim.go
+++ b/server/internal/transcript/trim.go
@@ -1,6 +1,9 @@
package transcript
-import "strings"
+import (
+ "regexp"
+ "strings"
+)
/*
Pulling a candidate's edges back off the programme.
@@ -59,10 +62,10 @@ func trimmed(found []Candidate, master, served []Cue) []Candidate {
for _, candidate := range found {
inside := cuesWithin(served, candidate)
first, last := 0, len(inside)-1
- for first <= last && inMaster(inside[first], runs) {
+ for first <= last && trimmable(inside[first], runs) {
first++
}
- for last > first && inMaster(inside[last], runs) {
+ for last > first && trimmable(inside[last], runs) {
last--
}
if first > last {
@@ -82,7 +85,21 @@ func trimmed(found []Candidate, master, served []Cue) []Candidate {
return out
}
+// annotation matches a line that is entirely one of whisper's non-speech notes -- "[music]",
+// "(upbeat music)", "(audience laughing)". 312 of 56,306 recognised lines are one of these.
+//
+// They are not speech, so a publisher's written transcript never contains them and they can
+// never anchor -- which leaves them sitting at the front of a break, widening it. Unlike a
+// title card, which this deliberately leaves alone, an annotation says what it is by its shape,
+// so it can be told from an advertisement without guessing.
+var annotation = regexp.MustCompile(`^\s*[\[(][^\])]{0,40}[\])]\s*$`)
+
// inMaster reports whether a line's words appear in the master.
+// trimmable reports whether a line at the edge of a break belongs to the break.
+func trimmable(cue Cue, runs map[string]bool) bool {
+ return annotation.MatchString(cue.Text) || inMaster(cue, runs)
+}
+
func inMaster(cue Cue, runs map[string]bool) bool {
words := strings.FieldsFunc(strings.ToLower(cue.Text), notWord)
if len(words) < trimMinWords {
diff --git a/server/internal/transcript/trim_test.go b/server/internal/transcript/trim_test.go
index 51f3965..b9cdaec 100644
--- a/server/internal/transcript/trim_test.go
+++ b/server/internal/transcript/trim_test.go
@@ -109,13 +109,15 @@ func TestTheAdvertisementItselfIsLeftWhole(t *testing.T) {
}
/*
-Non-speech markers and title cards are left alone.
+A non-speech note at the edge of a break is not part of it.
-"[MUSIC PLAYING]" and "Today's episode, Tamara." are genuinely absent from a publisher's written
-transcript, so nothing here can tell them from an advertisement. They sit against the break
-anyway, and a rule invented to catch them would be a guess.
+"[MUSIC PLAYING]", "(upbeat music)", "(audience laughing)" -- 312 of 56,306 recognised lines.
+They are not speech, so no publisher's transcript contains them and they can never anchor,
+which leaves them sitting at the front of a break and widening it. They say what they are by
+their shape, which is what separates them from a title card like "Today's episode, Tamara."
+-- that one is left alone, because nothing here can tell it from an advertisement.
*/
-func TestNonSpeechMarkersAreNotTrimmed(t *testing.T) {
+func TestANonSpeechNoteAtTheEdgeIsTrimmed(t *testing.T) {
master := words(0, programme[0], programme[1], programme[2])
served := words(0, lines(
[]string{programme[0], "[MUSIC PLAYING]"}, advert, []string{programme[1], programme[2]},
@@ -125,8 +127,25 @@ func TestNonSpeechMarkersAreNotTrimmed(t *testing.T) {
if len(got) != 1 {
t.Fatalf("got %+v, want one candidate", got)
}
+ if got[0].StartMs != 2*cueMs {
+ t.Errorf("start = %d, want the advertisement's own first line", got[0].StartMs)
+ }
+}
+
+// A title card is not identifiable, so it stays.
+func TestATitleCardIsLeftAlone(t *testing.T) {
+ master := words(0, programme[0], programme[1], programme[2])
+ served := words(0, lines(
+ []string{programme[0], "Today's episode, Tamara."}, advert,
+ []string{programme[1], programme[2]},
+ )...)
+
+ got := Inserted(master, served)
+ if len(got) != 1 {
+ t.Fatalf("got %+v, want one candidate", got)
+ }
if got[0].StartMs != cueMs {
- t.Errorf("start = %d, want the music marker kept with the break", got[0].StartMs)
+ t.Errorf("start = %d, want the title card kept with the break", got[0].StartMs)
}
}