mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 10:03:51 +00:00
craft-captions: an SCC pop-on load may run across the previous caption's erase
Ported from storytold/filmcraft#1837 (filmcraft#1791). A caption that closely follows another popped on about a second late, because its load had to be one unbroken run of free frames. Doubled codes stay adjacent; pauses are allowed between them.
This commit is contained in:
committed by
Dylan M. Taylor
parent
71fd9a791a
commit
c55287c57a
@@ -446,14 +446,32 @@ pub fn schedule(doc: &Document) -> BTreeMap<i64, (u8, u8)> {
|
||||
}
|
||||
let load = load_pairs(&c.text);
|
||||
let n = load.len() as i64;
|
||||
// Doubled codes must stay adjacent, so the load is one contiguous run of free frames:
|
||||
// the latest run ending by `s`, else the first run after `cursor` (delaying the caption).
|
||||
let run_free = |occ: &BTreeMap<i64, (u8, u8)>, b: i64| (b..b + n).all(|f| !occ.contains_key(&f));
|
||||
let b = (cursor..=s - n).rev().find(|&b| run_free(&occ, b)).unwrap_or_else(|| (cursor..).find(|&b| run_free(&occ, b)).unwrap_or(cursor));
|
||||
for (k, p) in load.into_iter().enumerate() {
|
||||
occ.insert(b + k as i64, p);
|
||||
// Erasing displayed memory can interrupt loading non-displayed memory. Keep each
|
||||
// doubled control/character code adjacent, but allow pauses between those groups.
|
||||
let plan_load = |mut f: i64| {
|
||||
let mut plan = Vec::with_capacity(load.len());
|
||||
let mut k = 0;
|
||||
while let Some(&p) = load.get(k) {
|
||||
let doubled = (0x10..=0x1f).contains(&p.0) && load.get(k + 1) == Some(&p);
|
||||
let width = if doubled { 2 } else { 1 };
|
||||
while (f..f + width).any(|at| occ.contains_key(&at)) {
|
||||
f += 1;
|
||||
}
|
||||
for offset in 0..width {
|
||||
plan.push((f + offset, p));
|
||||
}
|
||||
f += width;
|
||||
k += width as usize;
|
||||
}
|
||||
(plan, f)
|
||||
};
|
||||
// Prefer the latest load that finishes by the requested start; if it cannot fit,
|
||||
// use the earliest available frames rather than waiting until the prior cue ends.
|
||||
let (plan, end) = (cursor..=s - n).rev().map(plan_load).find(|(_, end)| *end <= s).unwrap_or_else(|| plan_load(cursor));
|
||||
for (f, p) in plan {
|
||||
occ.insert(f, p);
|
||||
}
|
||||
let mut d = s.max(b + n);
|
||||
let mut d = s.max(end);
|
||||
while occ.contains_key(&d) {
|
||||
d += 1;
|
||||
}
|
||||
|
||||
@@ -260,3 +260,49 @@ fn srt_to_vtt_to_scc_keeps_text_and_frames() {
|
||||
assert_eq!(scc::frame_of(doc3.cues[0].start), 60);
|
||||
assert_eq!(scc::frame_of(doc3.cues[1].end), 210);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tight_pop_on_load_can_cross_the_previous_caption_end() {
|
||||
let text = "A second caption that needs more time to load";
|
||||
let doc = Document {
|
||||
cues: vec![
|
||||
Cue { start: scc::RATE.tick_of(30), end: scc::RATE.tick_of(60), text: "First".into(), ..Default::default() },
|
||||
Cue { start: scc::RATE.tick_of(67), end: scc::RATE.tick_of(150), text: text.into(), ..Default::default() },
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
for format in [Format::Scc, Format::Mcc] {
|
||||
let back = parse(&write(&doc, format, WriteOptions::default()), format).unwrap();
|
||||
assert_eq!(back.cues.len(), 2);
|
||||
assert_eq!(scc::frame_of(back.cues[0].end), 60);
|
||||
assert_eq!(scc::frame_of(back.cues[1].start), 67, "{format:?}");
|
||||
assert_eq!(scc::frame_of(back.cues[1].end), 150);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interrupted_load_keeps_extended_characters_and_repeated_basic_pairs() {
|
||||
for text in ["A second caption that needs more time to load", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "ÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀ\nÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀÀ"]
|
||||
{
|
||||
let doc = Document {
|
||||
cues: vec![
|
||||
Cue { start: scc::RATE.tick_of(30), end: scc::RATE.tick_of(60), text: "First".into(), ..Default::default() },
|
||||
Cue { start: scc::RATE.tick_of(67), end: scc::RATE.tick_of(180), text: text.into(), ..Default::default() },
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
for format in [Format::Scc, Format::Mcc] {
|
||||
let back = parse(&write(&doc, format, WriteOptions::default()), format).unwrap();
|
||||
assert_eq!(back.cues.len(), 2);
|
||||
assert_eq!(back.cues[1].text.replace(['\n', ' '], ""), text.replace(['\n', ' '], ""), "{format:?}");
|
||||
assert_eq!(scc::frame_of(back.cues[0].end), 60);
|
||||
assert_eq!(scc::frame_of(back.cues[1].end), 180);
|
||||
}
|
||||
let pairs = scc::schedule(&doc);
|
||||
for (&frame, &pair) in &pairs {
|
||||
if (0x10..=0x1f).contains(&pair.0) {
|
||||
assert!(pairs.get(&(frame - 1)) == Some(&pair) || pairs.get(&(frame + 1)) == Some(&pair), "split doubled code at {frame}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user