#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SrtEntry {
pub index: u32,
pub start_ms: u64,
pub end_ms: u64,
pub text: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct SrtDocument {
pub entries: Vec<SrtEntry>,
}
impl SrtDocument {
pub fn add_entry(&mut self, start_ms: u64, end_ms: u64, text: impl Into<String>) {
let index = self.entries.len() as u32 + 1;
self.entries.push(SrtEntry {
index,
start_ms,
end_ms,
text: vec![text.into()],
});
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
}
pub fn ms_to_srt_time(ms: u64) -> String {
let h = ms / 3_600_000;
let m = (ms % 3_600_000) / 60_000;
let s = (ms % 60_000) / 1_000;
let ms = ms % 1_000;
format!("{h:02}:{m:02}:{s:02},{ms:03}")
}
pub fn render_srt(doc: &SrtDocument) -> String {
let mut out = String::new();
for entry in &doc.entries {
out.push_str(&format!("{}\n", entry.index));
out.push_str(&format!(
"{} --> {}\n",
ms_to_srt_time(entry.start_ms),
ms_to_srt_time(entry.end_ms)
));
for line in &entry.text {
out.push_str(line);
out.push('\n');
}
out.push('\n');
}
out
}
pub fn validate_srt(doc: &SrtDocument) -> bool {
doc.entries
.iter()
.all(|e| e.start_ms < e.end_ms && !e.text.is_empty())
}
pub fn total_duration_ms(doc: &SrtDocument) -> u64 {
doc.entries.iter().map(|e| e.end_ms).max().unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_doc() -> SrtDocument {
let mut doc = SrtDocument::default();
doc.add_entry(0, 2000, "Hello world");
doc.add_entry(3000, 5000, "Second line");
doc
}
#[test]
fn entry_count_correct() {
assert_eq!(sample_doc().entry_count(), 2);
}
#[test]
fn indices_start_at_one() {
assert_eq!(sample_doc().entries[0].index, 1);
}
#[test]
fn ms_to_srt_time_format() {
assert_eq!(ms_to_srt_time(3_723_456), "01:02:03,456");
}
#[test]
fn ms_to_srt_zero() {
assert_eq!(ms_to_srt_time(0), "00:00:00,000");
}
#[test]
fn render_contains_arrow() {
let s = render_srt(&sample_doc());
assert!(s.contains("-->"));
}
#[test]
fn render_contains_text() {
let s = render_srt(&sample_doc());
assert!(s.contains("Hello world"));
}
#[test]
fn validate_ok() {
assert!(validate_srt(&sample_doc()));
}
#[test]
fn validate_bad_timestamps() {
let mut doc = SrtDocument::default();
doc.entries.push(SrtEntry {
index: 1,
start_ms: 5000,
end_ms: 3000,
text: vec!["bad".to_string()],
});
assert!(!validate_srt(&doc));
}
#[test]
fn total_duration_correct() {
assert_eq!(total_duration_ms(&sample_doc()), 5000);
}
#[test]
fn empty_doc_duration_zero() {
assert_eq!(total_duration_ms(&SrtDocument::default()), 0);
}
}