1use std::{io::Read, time::Duration};
5
6use crate::Result;
7
8use winnow::{
9 BStr,
10 ascii::{dec_uint, digit1, line_ending},
11 combinator::{eof, opt, seq, terminated, trace},
12 error::{ContextError, ErrMode, InputError},
13 prelude::*,
14 token::{take, take_until, take_while},
15};
16
17fn parse_digits<'s>(input: &mut &'s BStr) -> ModalResult<u64> {
18 trace("parse_digits", move |input: &mut &'s BStr| {
19 digit1
20 .parse_to()
21 .parse_next(input)
22 .map_err(|_e: ErrMode<InputError<&'s BStr>>| ErrMode::Cut(ContextError::new()))
23 })
24 .parse_next(input)
25}
26
27fn parse_duration(input: &mut &BStr) -> ModalResult<Duration> {
28 trace("parse_duration", move |input: &mut &BStr| {
29 let (hours, _, minutes, _, seconds, _, millis) = (
30 take(2usize),
31 ':',
32 take(2usize),
33 ':',
34 take(2usize),
35 ',',
36 take(3usize),
37 )
38 .parse_next(input)?;
39
40 let hours: u64 = parse_digits(&mut hours.into())?;
41 let minutes: u64 = parse_digits(&mut minutes.into())?;
42 let seconds: u64 = parse_digits(&mut seconds.into())?;
43 let millis: u64 = parse_digits(&mut millis.into())?;
44
45 let minutes = hours * 60 + minutes;
46 let secs = 60 * minutes + seconds;
47 let nanos = millis * 1_000_000;
48 Ok(Duration::new(secs, nanos.try_into().unwrap()))
49 })
50 .parse_next(input)
51}
52
53#[rustfmt::skip]
54#[derive(Debug)]
55pub struct Stanza {
56 pub(crate) _count: usize,
57 pub(crate) _start: std::time::Duration,
58 pub(crate) _stop: std::time::Duration,
59 pub(crate) lines: String,
60}
61
62impl Stanza {
63 pub fn lines(&self) -> &str {
64 &self.lines
65 }
66}
67
68fn parse_stanza(input: &mut &BStr) -> ModalResult<Stanza> {
69 trace("parse_stanza", move |input: &mut &BStr| {
70 let mut num = dec_uint::<_, usize, ContextError>;
71
72 let count_res: winnow::Result<(usize,)> = seq!(num, _: line_ending).parse_next(input);
74 let count = count_res.map_err(ErrMode::Cut)?.0;
75
76 let start_stop_res: ModalResult<(Duration, Duration)> =
78 seq!(parse_duration, _: " --> ", parse_duration, _: line_ending).parse_next(input);
79 let (start, stop) = start_stop_res?;
80
81 let till_newlines = take_until(0.., "\n\n");
83
84 let res: ModalResult<&[u8]> = match opt(till_newlines).parse_next(input)? {
85 Some(lines0) => {
86 "\n".parse_next(input)?;
88 Ok(lines0)
89 }
90 _ => {
91 terminated(take_while(0.., |_| true), eof).parse_next(input)
93 }
94 };
95 let lines0 = res?;
96 let lines =
97 String::from_utf8(lines0.to_vec()).map_err(|_e| ErrMode::Cut(ContextError::new()))?;
98
99 Ok(Stanza {
100 _count: count,
101 _start: start,
102 _stop: stop,
103 lines,
104 })
105 })
106 .parse_next(input)
107}
108
109fn parse_stanzas(input: &mut &BStr) -> Vec<Stanza> {
114 let mut result = vec![];
115 loop {
116 match opt(eof::<_, ContextError>).parse_next(input) {
117 Ok(Some(_)) | Err(_) => break,
118 Ok(None) => {}
119 }
120 match parse_stanza.parse_next(input) {
121 Ok(x) => result.push(x),
122 Err(_) => break,
123 }
124 match opt(eof::<_, ContextError>).parse_next(input) {
125 Ok(Some(_)) | Err(_) => break,
126 Ok(None) => {}
127 }
128 if line_ending::<_, ContextError>.parse_next(input).is_err() {
129 break;
130 }
131 }
132 result
133}
134
135pub struct SrtParseOutcome {
138 pub stanzas: Vec<Stanza>,
139 pub truncated_at_line: Option<usize>,
143}
144
145pub fn read_srt_file(p: &std::path::Path) -> Result<SrtParseOutcome> {
146 let mut fd = std::fs::File::open(p)?;
147 let mut buf = Vec::new();
148 fd.read_to_end(&mut buf)?;
149 let mut buf_bstr: &BStr = buf.as_slice().into();
150
151 let stanzas = parse_stanzas(&mut buf_bstr);
152 let truncated_at_line = if buf_bstr.is_empty() {
153 None
154 } else {
155 let offset = buf.len() - buf_bstr.len();
156 Some(buf[..offset].iter().filter(|&&b| b == b'\n').count() + 1)
157 };
158
159 Ok(SrtParseOutcome {
160 stanzas,
161 truncated_at_line,
162 })
163}
164
165#[cfg(test)]
166mod test {
167 use super::*;
168
169 const B0: &[u8] = b"";
170
171 const B1: &[u8] = br#"1
17200:00:00,000 --> 00:00:00,040
173{"frame_cnt":1,"timestamp":"2024-11-21T21:04:19.534412+01:00"}
174"#;
175
176 const B2A: &[u8] = br#"1
17700:00:00,000 --> 00:00:00,040
178{"frame_cnt":1,"timestamp":"2024-11-21T21:04:19.534412+01:00"}
179
1802
18100:00:00,040 --> 00:00:00,080
182{"frame_cnt":2,"timestamp":"2024-11-21T21:04:19.552417+01:00"}"#;
183
184 const B2B: &[u8] = br#"1
18500:00:00,000 --> 00:00:00,040
186{"frame_cnt":1,"timestamp":"2024-11-21T21:04:19.534412+01:00"}
187
1882
18900:00:00,040 --> 00:00:00,080
190{"frame_cnt":2,"timestamp":"2024-11-21T21:04:19.552417+01:00"}
191
192"#;
193
194 const B3A: &[u8] = br#"1
19500:00:00,000 --> 00:00:00,040
196{"frame_cnt":1,"timestamp":"2024-11-21T21:04:19.534412+01:00"}
197
1982
19900:00:00,040 --> 00:00:00,080
200{"frame_cnt":2,"timestamp":"2024-11-21T21:04:19.552417+01:00"}
201
2023
20300:00:00,080 --> 00:00:00,120
204{"frame_cnt":3,"timestamp":"2024-11-21T21:04:19.563575+01:00"}"#;
205
206 const B3B: &[u8] = br#"1
20700:00:00,000 --> 00:00:00,040
208{"frame_cnt":1,"timestamp":"2024-11-21T21:04:19.534412+01:00"}
209
2102
21100:00:00,040 --> 00:00:00,080
212{"frame_cnt":2,"timestamp":"2024-11-21T21:04:19.552417+01:00"}
213
2143
21500:00:00,080 --> 00:00:00,120
216{"frame_cnt":3,"timestamp":"2024-11-21T21:04:19.563575+01:00"}
217"#;
218
219 #[test]
220 fn test_parse() {
221 for (sz, in_b3) in [(0, B0), (1, B1), (2, B2A), (2, B2B), (3, B3A), (3, B3B)] {
222 println!(
223 "testing size {sz} with value:\n{:?}",
224 String::from_utf8_lossy(in_b3)
225 );
226 let b3 = parse_stanzas(&mut in_b3.into());
227 assert_eq!(b3.len(), sz);
228 }
229 }
230}
231
232#[cfg(test)]
233mod test_duration {
234 use super::*;
235
236 trait Srt {
237 fn srt(&self) -> String;
238 }
239
240 impl Srt for Duration {
241 fn srt(&self) -> String {
242 let total_secs = self.as_secs();
248 let hours = total_secs / (60 * 60);
249 let minutes = (total_secs % (60 * 60)) / 60;
250 let seconds = total_secs % 60;
251 debug_assert_eq!(total_secs, hours * 60 * 60 + minutes * 60 + seconds);
252 let millis = self.subsec_millis();
253 format!("{hours:02}:{minutes:02}:{seconds:02},{millis:03}")
254 }
255 }
256 #[test]
257 fn test_duration_roundtrip() {
258 for (h, m, s, ms) in [(1, 2, 3, 4), (3, 2, 1, 0), (10, 9, 8, 999)] {
259 let m = h * 60 + m;
260 let s = m * 60 + s;
261 let ms = s * 1000 + ms;
262 let dur = Duration::from_millis(ms);
263 let dur_str = dur.srt();
264 let dur_bytes: &BStr = dur_str.as_str().into();
265 let parsed = trace("parse_duration", parse_duration).parse(dur_bytes);
266 let parsed = parsed.unwrap();
267 assert_eq!(dur, parsed);
268 }
269 }
270}