Added a module to coalesce adjacent same-speaker segments
This commit is contained in:
@@ -90,6 +90,7 @@ func TestMergeWritesMergedOutputAndReport(t *testing.T) {
|
||||
"placeholder-merger",
|
||||
"detect-overlaps",
|
||||
"resolve-overlaps",
|
||||
"coalesce",
|
||||
"detect-overlaps",
|
||||
"autocorrect",
|
||||
"assign-ids",
|
||||
@@ -195,7 +196,7 @@ func TestMergeDetectsOverlapGroups(t *testing.T) {
|
||||
if group.Start != 1 || group.End != 6 {
|
||||
t.Fatalf("group bounds = %f-%f, want 1-6", group.Start, group.End)
|
||||
}
|
||||
wantRefs := []string{inputA + "#0", inputA + "#1", inputB + "#0"}
|
||||
wantRefs := []string{"coalesce:1", inputB + "#0"}
|
||||
if !equalStrings(group.Segments, wantRefs) {
|
||||
t.Fatalf("group refs = %v, want %v", group.Segments, wantRefs)
|
||||
}
|
||||
@@ -210,6 +211,15 @@ func TestMergeDetectsOverlapGroups(t *testing.T) {
|
||||
t.Fatalf("segment %d overlap group ID = %d, want 1", index, segment.OverlapGroupID)
|
||||
}
|
||||
}
|
||||
if len(transcript.Segments) != 2 {
|
||||
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
||||
}
|
||||
if transcript.Segments[0].SourceRef != "coalesce:1" {
|
||||
t.Fatalf("coalesced source_ref = %q, want coalesce:1", transcript.Segments[0].SourceRef)
|
||||
}
|
||||
if !equalStrings(transcript.Segments[0].DerivedFrom, []string{inputA + "#0", inputA + "#1"}) {
|
||||
t.Fatalf("coalesced derived_from = %v", transcript.Segments[0].DerivedFrom)
|
||||
}
|
||||
|
||||
var rpt report.Report
|
||||
readJSON(t, reportPath, &rpt)
|
||||
@@ -410,6 +420,171 @@ func TestMergeDetectsResidualOverlapsAfterResolution(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeOrdersNearStartWordRunsShorterFirst(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
inputA := writeJSONFile(t, dir, "a.json", `{
|
||||
"segments": [
|
||||
{
|
||||
"start": 1,
|
||||
"end": 4,
|
||||
"text": "alice long",
|
||||
"words": [
|
||||
{"word": "alice-long", "start": 1.0, "end": 2.0}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
inputB := writeJSONFile(t, dir, "b.json", `{
|
||||
"segments": [
|
||||
{
|
||||
"start": 1.1,
|
||||
"end": 3,
|
||||
"text": "bob short",
|
||||
"words": [
|
||||
{"word": "bob-short", "start": 1.2, "end": 1.3}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
speakers := writeYAMLFile(t, dir, "speakers.yml", `match:
|
||||
- speaker: Alice
|
||||
match: ["a.json"]
|
||||
- speaker: Bob
|
||||
match: ["b.json"]
|
||||
`)
|
||||
output := filepath.Join(dir, "merged.json")
|
||||
|
||||
err := executeMerge(
|
||||
"--input-file", inputA,
|
||||
"--input-file", inputB,
|
||||
"--speakers", speakers,
|
||||
"--output-file", output,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("merge failed: %v", err)
|
||||
}
|
||||
|
||||
var transcript model.FinalTranscript
|
||||
readJSON(t, output, &transcript)
|
||||
if len(transcript.Segments) != 2 {
|
||||
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
||||
}
|
||||
if transcript.Segments[0].Text != "bob-short" || transcript.Segments[0].ID != 1 {
|
||||
t.Fatalf("first segment = %#v, want bob-short with ID 1", transcript.Segments[0])
|
||||
}
|
||||
if transcript.Segments[1].Text != "alice-long" || transcript.Segments[1].ID != 2 {
|
||||
t.Fatalf("second segment = %#v, want alice-long with ID 2", transcript.Segments[1])
|
||||
}
|
||||
if len(transcript.OverlapGroups) != 1 {
|
||||
t.Fatalf("overlap group count = %d, want 1", len(transcript.OverlapGroups))
|
||||
}
|
||||
if transcript.Segments[0].OverlapGroupID != 1 || transcript.Segments[1].OverlapGroupID != 1 {
|
||||
t.Fatalf("segments should retain residual overlap annotation: %#v", transcript.Segments)
|
||||
}
|
||||
wantRefs := []string{"word-run:1:1:1", "word-run:1:2:1"}
|
||||
if !equalStrings(transcript.OverlapGroups[0].Segments, wantRefs) {
|
||||
t.Fatalf("overlap refs = %v, want %v", transcript.OverlapGroups[0].Segments, wantRefs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCoalescesSameSpeakerSegmentsBeforeFinalOverlapDetection(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
inputA := writeJSONFile(t, dir, "a.json", `{
|
||||
"segments": [
|
||||
{"start": 1, "end": 2, "text": "first"},
|
||||
{"start": 4, "end": 5, "text": "second"}
|
||||
]
|
||||
}`)
|
||||
inputB := writeJSONFile(t, dir, "b.json", `{
|
||||
"segments": [
|
||||
{"start": 4.5, "end": 6, "text": "bob"}
|
||||
]
|
||||
}`)
|
||||
speakers := writeYAMLFile(t, dir, "speakers.yml", `match:
|
||||
- speaker: Alice
|
||||
match: ["a.json"]
|
||||
- speaker: Bob
|
||||
match: ["b.json"]
|
||||
`)
|
||||
output := filepath.Join(dir, "merged.json")
|
||||
reportPath := filepath.Join(dir, "report.json")
|
||||
|
||||
err := executeMerge(
|
||||
"--input-file", inputA,
|
||||
"--input-file", inputB,
|
||||
"--speakers", speakers,
|
||||
"--output-file", output,
|
||||
"--report-file", reportPath,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("merge failed: %v", err)
|
||||
}
|
||||
|
||||
var transcript model.FinalTranscript
|
||||
readJSON(t, output, &transcript)
|
||||
if len(transcript.Segments) != 2 {
|
||||
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
||||
}
|
||||
alice := transcript.Segments[0]
|
||||
if alice.ID != 1 || alice.Text != "first second" || alice.SourceRef != "coalesce:1" {
|
||||
t.Fatalf("unexpected coalesced Alice segment: %#v", alice)
|
||||
}
|
||||
if alice.SourceSegmentIndex != nil {
|
||||
t.Fatalf("coalesced segment source_segment_index = %d, want nil", *alice.SourceSegmentIndex)
|
||||
}
|
||||
if !equalStrings(alice.DerivedFrom, []string{inputA + "#0", inputA + "#1"}) {
|
||||
t.Fatalf("derived_from = %v", alice.DerivedFrom)
|
||||
}
|
||||
if transcript.Segments[1].ID != 2 || transcript.Segments[1].Text != "bob" {
|
||||
t.Fatalf("unexpected Bob segment: %#v", transcript.Segments[1])
|
||||
}
|
||||
if len(transcript.OverlapGroups) != 1 {
|
||||
t.Fatalf("overlap group count = %d, want 1", len(transcript.OverlapGroups))
|
||||
}
|
||||
group := transcript.OverlapGroups[0]
|
||||
if !equalStrings(group.Segments, []string{"coalesce:1", inputB + "#0"}) {
|
||||
t.Fatalf("group refs = %v", group.Segments)
|
||||
}
|
||||
if alice.OverlapGroupID != 1 || transcript.Segments[1].OverlapGroupID != 1 {
|
||||
t.Fatalf("expected final overlap annotation after coalesce: %#v", transcript.Segments)
|
||||
}
|
||||
|
||||
var rpt report.Report
|
||||
readJSON(t, reportPath, &rpt)
|
||||
if !hasReportEvent(rpt, "postprocessing", "coalesce", "merged 2 original segment(s) into 1 coalesced segment(s)") {
|
||||
t.Fatal("expected coalesce report event")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeCoalesceGapOverridePreventsMerge(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
input := writeJSONFile(t, dir, "a.json", `{
|
||||
"segments": [
|
||||
{"start": 1, "end": 2, "text": "first"},
|
||||
{"start": 4, "end": 5, "text": "second"}
|
||||
]
|
||||
}`)
|
||||
output := filepath.Join(dir, "merged.json")
|
||||
|
||||
err := executeMerge(
|
||||
"--input-file", input,
|
||||
"--output-file", output,
|
||||
"--coalesce-gap", "1",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("merge failed: %v", err)
|
||||
}
|
||||
|
||||
var transcript model.FinalTranscript
|
||||
readJSON(t, output, &transcript)
|
||||
if len(transcript.Segments) != 2 {
|
||||
t.Fatalf("segment count = %d, want 2", len(transcript.Segments))
|
||||
}
|
||||
if transcript.Segments[0].Text != "first" || transcript.Segments[1].Text != "second" {
|
||||
t.Fatalf("segments were unexpectedly coalesced: %#v", transcript.Segments)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpeakerMatchingUsesFirstMatchingRuleCaseInsensitive(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
input := writeJSONFile(t, dir, "2026-04-19-Adam_Rakestraw.json", `{
|
||||
|
||||
Reference in New Issue
Block a user