Skip to content

Commit aa9412f

Browse files
authored
Faster unfragment. (#57)
The old implementation was N^2 which could take a long time for subtitles with many items. Break out of the loop after we are beyond the end time for the item that is being compared. Also support unfragmenting nested items, eg: 1 -> 4 and 2 -> 5 will be combined into 1 -> 5.
1 parent 39d5008 commit aa9412f

File tree

2 files changed

+76
-5
lines changed

2 files changed

+76
-5
lines changed

subtitles.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -619,20 +619,25 @@ func (s *Subtitles) Unfragment() {
619619
return
620620
}
621621

622+
// Order
623+
s.Order()
624+
622625
// Loop through items
623626
for i := 0; i < len(s.Items)-1; i++ {
624627
for j := i + 1; j < len(s.Items); j++ {
625628
// Items are the same
626-
if s.Items[i].String() == s.Items[j].String() && s.Items[i].EndAt == s.Items[j].StartAt {
627-
s.Items[i].EndAt = s.Items[j].EndAt
629+
if s.Items[i].String() == s.Items[j].String() && s.Items[i].EndAt >= s.Items[j].StartAt {
630+
// Only override end time if longer
631+
if s.Items[i].EndAt < s.Items[j].EndAt {
632+
s.Items[i].EndAt = s.Items[j].EndAt
633+
}
628634
s.Items = append(s.Items[:j], s.Items[j+1:]...)
629635
j--
636+
} else if s.Items[i].EndAt < s.Items[j].StartAt {
637+
break
630638
}
631639
}
632640
}
633-
634-
// Order
635-
s.Order()
636641
}
637642

638643
// Write writes subtitles to a file

subtitles_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,72 @@ func TestSubtitles_Fragment(t *testing.T) {
126126
assert.Equal(t, 5*time.Second, s.Items[2].EndAt)
127127
}
128128

129+
func TestSubtitles_Unfragment(t *testing.T) {
130+
itemText := func(s string) []astisub.Line {
131+
return []astisub.Line{{Items: []astisub.LineItem{{Text: s}}}}
132+
}
133+
items := []*astisub.Item{{
134+
Lines: itemText("subtitle-1"),
135+
StartAt: 1 * time.Second,
136+
EndAt: 2 * time.Second,
137+
}, {
138+
Lines: itemText("subtitle-2"),
139+
StartAt: 2 * time.Second,
140+
EndAt: 5 * time.Second,
141+
}, {
142+
Lines: itemText("subtitle-3"),
143+
StartAt: 3 * time.Second,
144+
EndAt: 4 * time.Second,
145+
}, {
146+
// gap and nested within first subtitle-2; should not override end time
147+
Lines: itemText("subtitle-2"),
148+
StartAt: 3 * time.Second,
149+
EndAt: 4 * time.Second,
150+
}, {
151+
Lines: itemText("subtitle-3"),
152+
// gap and start time equals previous end time
153+
StartAt: 4 * time.Second,
154+
EndAt: 5 * time.Second,
155+
}, {
156+
// should not be combined
157+
Lines: itemText("subtitle-3"),
158+
StartAt: 6 * time.Second,
159+
EndAt: 7 * time.Second,
160+
}, {
161+
// test correcting for out-of-orderness
162+
Lines: itemText("subtitle-1"),
163+
StartAt: 0 * time.Second,
164+
EndAt: 3 * time.Second,
165+
}}
166+
167+
s := &astisub.Subtitles{Items: items}
168+
169+
s.Unfragment()
170+
171+
expected := []astisub.Item{{
172+
Lines: itemText("subtitle-1"),
173+
StartAt: 0 * time.Second,
174+
EndAt: 3 * time.Second,
175+
}, {
176+
Lines: itemText("subtitle-2"),
177+
StartAt: 2 * time.Second,
178+
EndAt: 5 * time.Second,
179+
}, {
180+
Lines: itemText("subtitle-3"),
181+
StartAt: 3 * time.Second,
182+
EndAt: 5 * time.Second,
183+
}, {
184+
Lines: itemText("subtitle-3"),
185+
StartAt: 6 * time.Second,
186+
EndAt: 7 * time.Second,
187+
}}
188+
189+
assert.Equal(t, len(expected), len(s.Items))
190+
for i := range expected {
191+
assert.Equal(t, expected[i], *s.Items[i])
192+
}
193+
}
194+
129195
func TestSubtitles_Merge(t *testing.T) {
130196
var s1 = &astisub.Subtitles{Items: []*astisub.Item{{EndAt: 3 * time.Second, StartAt: time.Second}, {EndAt: 8 * time.Second, StartAt: 5 * time.Second}, {EndAt: 12 * time.Second, StartAt: 10 * time.Second}}, Regions: map[string]*astisub.Region{"region_0": {ID: "region_0"}, "region_1": {ID: "region_1"}}, Styles: map[string]*astisub.Style{"style_0": {ID: "style_0"}, "style_1": {ID: "style_1"}}}
131197
var s2 = &astisub.Subtitles{Items: []*astisub.Item{{EndAt: 4 * time.Second, StartAt: 2 * time.Second}, {EndAt: 7 * time.Second, StartAt: 6 * time.Second}, {EndAt: 11 * time.Second, StartAt: 9 * time.Second}, {EndAt: 14 * time.Second, StartAt: 13 * time.Second}}, Regions: map[string]*astisub.Region{"region_1": {ID: "region_1"}, "region_2": {ID: "region_2"}}, Styles: map[string]*astisub.Style{"style_1": {ID: "style_1"}, "style_2": {ID: "style_2"}}}

0 commit comments

Comments
 (0)