cuekit and cuenorm: three subtitle formats, one cue stream

This commit is contained in:
funkpower
2026-09-19 11:54:36 -04:00
parent 33c2e40a1a
commit c7d0767376
42 changed files with 1433 additions and 68 deletions
+66
View File
@@ -0,0 +1,66 @@
//! Collapsing the repetition automatic captioning produces.
use crate::cue::Cue;
/// Merge consecutive cues whose text is identical, keeping the earliest start
/// and the latest end.
///
/// Automatic captioning emits a rolling window: the same line appears in cue
/// after cue as the window slides, so a ten-minute stretch of speech can
/// arrive as three times as many cues as there are sentences. Indexed as they
/// come, every one of those is a separate hit for the same words at almost
/// the same timestamp, which buries everything else in a result list.
pub fn merge_repeats(cues: &[Cue]) -> Vec<Cue> {
let mut merged: Vec<Cue> = Vec::with_capacity(cues.len());
for cue in cues {
match merged.last_mut() {
Some(previous) if previous.text == cue.text => {
previous.end = previous.end.max(cue.end);
}
_ => merged.push(cue.clone()),
}
}
merged
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_repeated_line_becomes_one_cue_spanning_both() {
let cues = [
Cue::new(0.0, 2.0, "the mill stood"),
Cue::new(1.0, 3.0, "the mill stood"),
Cue::new(3.0, 4.0, "at the northern end"),
];
let merged = merge_repeats(&cues);
assert_eq!(merged.len(), 2);
assert_eq!(merged[0].start, 0.0);
assert_eq!(merged[0].end, 3.0);
assert_eq!(merged[1].text, "at the northern end");
}
#[test]
fn a_line_that_comes_back_later_is_kept_separately() {
let cues = [
Cue::new(0.0, 1.0, "yes"),
Cue::new(1.0, 2.0, "no"),
Cue::new(2.0, 3.0, "yes"),
];
assert_eq!(merge_repeats(&cues).len(), 3);
}
#[test]
fn an_end_that_goes_backwards_does_not_shorten_the_merged_cue() {
let cues = [Cue::new(0.0, 9.0, "x"), Cue::new(1.0, 2.0, "x")];
let merged = merge_repeats(&cues);
assert_eq!(merged.len(), 1);
assert_eq!(merged[0].end, 9.0);
}
#[test]
fn nothing_in_nothing_out() {
assert!(merge_repeats(&[]).is_empty());
}
}