aspasia/microdvd/
data.rs

1use std::{borrow::Cow, fmt::Display, fs::File, io::BufReader, path::Path, str::FromStr};
2
3use encoding_rs::Encoding;
4use encoding_rs_io::DecodeReaderBytesBuilder;
5
6use crate::{
7    encoding::detect_file_encoding,
8    timing::{frame_to_moment, moment_to_frame, Frame},
9    traits::TimedSubtitle,
10    AssSubtitle, Error, Moment, SsaSubtitle, SubRipSubtitle, Subtitle, TextEvent,
11    TextEventInterface, TextSubtitle, TimedEvent, TimedEventInterface, WebVttSubtitle,
12};
13
14use super::parse::parse_microdvd;
15
16type FrameRate = f32;
17
18/// Timed version of MicroDVD (.sub) subtitle, using user-supplied framerate to calculate timings
19///
20/// When initialised without framerate, the default framerate is 24.
21#[derive(Debug)]
22pub struct TimedMicroDvdSubtitle {
23    events: Vec<TimedMicroDvdEvent>,
24    framerate: FrameRate,
25}
26
27/// Timed MicroDVD event
28#[derive(Debug)]
29pub struct TimedMicroDvdEvent {
30    /// Start time of event
31    pub start: Moment,
32    /// End time of event
33    pub end: Moment,
34    /// Text to display during event
35    pub text: String,
36}
37
38/// Unmodified MicroDVD subtitle, with events timed in terms of frames.
39///
40/// This is not well supported, so things like conversion are not implemented for this type.
41/// If possible, use of [`MicroDvdSubtitle`], which represents subtitle events using actual timestamps, is better supported.
42#[derive(Debug)]
43pub struct MicroDvdSubtitle {
44    events: Vec<MicroDvdEvent>,
45}
46
47/// Unmodified MicroDVD event, timed in terms of frames
48#[derive(Debug)]
49pub struct MicroDvdEvent {
50    /// Frame at which event starts
51    pub start: Frame,
52    /// Frame at which event ends
53    pub end: Frame,
54    /// Text to display during event
55    pub text: String,
56}
57
58impl TimedMicroDvdSubtitle {
59    fn open_file_with_encoding(
60        path: impl AsRef<Path>,
61        encoding: Option<&'static Encoding>,
62    ) -> Result<Self, Error> {
63        let file = File::open(path)?;
64        let transcoded = DecodeReaderBytesBuilder::new()
65            .encoding(encoding)
66            .build(file);
67        let reader = BufReader::new(transcoded);
68
69        Ok(Self::from_raw(&parse_microdvd(reader), Some(24.0)))
70    }
71
72    fn open_file_with_encoding_and_framerate(
73        path: impl AsRef<Path>,
74        encoding: Option<&'static Encoding>,
75        framerate: FrameRate,
76    ) -> Result<Self, Error> {
77        let file = File::open(path)?;
78        let transcoded = DecodeReaderBytesBuilder::new()
79            .encoding(encoding)
80            .build(file);
81        let reader = BufReader::new(transcoded);
82
83        Ok(Self::from_raw(&parse_microdvd(reader), Some(framerate)))
84    }
85
86    /// Convert raw MicroDVD subtitle data to timed MicroDVD data, given the framerate the subtitles were created for.
87    /// If no framerate is given, the default of 24 is used.
88    #[must_use]
89    pub fn from_raw(raw: &MicroDvdSubtitle, framerate: Option<FrameRate>) -> Self {
90        let framerate = framerate.unwrap_or(24.0);
91        let events = raw
92            .events
93            .iter()
94            .map(|event| TimedMicroDvdEvent {
95                start: frame_to_moment(event.start, framerate),
96                end: frame_to_moment(event.end, framerate),
97                text: event.text.clone(),
98            })
99            .collect();
100
101        Self { events, framerate }
102    }
103
104    /// Create MicroDVD from path given and calculate its timings using the given framerate.
105    ///
106    /// # Errors
107    ///
108    /// Returns [`Error::FileIoError`] if an error occurs while opening the file
109    pub fn with_framerate(path: impl AsRef<Path>, framerate: FrameRate) -> Result<Self, Error> {
110        let mut enc = detect_file_encoding(path.as_ref(), Some(30)).ok();
111        let mut result = Self::open_file_with_encoding_and_framerate(path.as_ref(), enc, framerate);
112
113        if result.is_err() {
114            enc = detect_file_encoding(path.as_ref(), None).ok();
115            result = Self::open_file_with_encoding_and_framerate(path.as_ref(), enc, framerate);
116        }
117
118        result
119    }
120
121    /// Get framerate used to create timings
122    #[must_use]
123    pub fn framerate(&self) -> FrameRate {
124        self.framerate
125    }
126
127    /// Modify framerate associated with subtitle.
128    /// Does not modify event timings at all.
129    pub fn set_framerate(&mut self, framerate: FrameRate) {
130        self.framerate = framerate;
131    }
132
133    /// Modify framerate associated with subtitle
134    ///
135    /// This will also recalculate and update all event timings to match the new framerate.
136    pub fn update_framerate(&mut self, framerate: FrameRate) {
137        let ratio = self.framerate / framerate;
138        for event in &mut self.events {
139            event.start =
140                Moment::from(((i64::from(event.start) as FrameRate) * ratio).round() as i64);
141            event.end = Moment::from(((i64::from(event.end) as FrameRate) * ratio).round() as i64);
142        }
143        self.framerate = framerate;
144    }
145}
146
147impl Subtitle for TimedMicroDvdSubtitle {
148    type Event = TimedMicroDvdEvent;
149
150    fn from_path_with_encoding(
151        path: impl AsRef<std::path::Path>,
152        encoding: Option<&'static encoding_rs::Encoding>,
153    ) -> Result<Self, Error> {
154        let mut enc = encoding.or_else(|| detect_file_encoding(path.as_ref(), Some(30)).ok());
155        let mut result = Self::open_file_with_encoding(path.as_ref(), enc);
156
157        if encoding.is_none() && result.is_err() {
158            enc = encoding.or_else(|| detect_file_encoding(path.as_ref(), None).ok());
159            result = Self::open_file_with_encoding(path.as_ref(), enc);
160        }
161
162        result
163    }
164
165    fn events(&self) -> &[Self::Event] {
166        self.events.as_slice()
167    }
168
169    fn events_mut(&mut self) -> &mut [Self::Event] {
170        self.events.as_mut_slice()
171    }
172}
173
174impl TextSubtitle for TimedMicroDvdSubtitle {}
175
176impl TimedSubtitle for TimedMicroDvdSubtitle {}
177
178impl Display for TimedMicroDvdSubtitle {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        for event in &self.events {
181            writeln!(
182                f,
183                "{{{}}}{{{}}}{}",
184                moment_to_frame(event.start, self.framerate),
185                moment_to_frame(event.end, self.framerate),
186                event.text
187            )?;
188        }
189
190        Ok(())
191    }
192}
193
194impl FromStr for TimedMicroDvdSubtitle {
195    type Err = Error;
196
197    fn from_str(s: &str) -> Result<Self, Self::Err> {
198        let reader = BufReader::new(s.as_bytes());
199
200        Ok(Self::from_raw(&parse_microdvd(reader), None))
201    }
202}
203
204impl From<&AssSubtitle> for TimedMicroDvdSubtitle {
205    fn from(value: &AssSubtitle) -> Self {
206        Self {
207            events: value
208                .events()
209                .iter()
210                .map(|line| TimedMicroDvdEvent {
211                    text: line.as_plaintext().replace('\n', "|"),
212                    start: line.start,
213                    end: line.end,
214                })
215                .collect(),
216            framerate: 24.0,
217        }
218    }
219}
220
221impl From<&SsaSubtitle> for TimedMicroDvdSubtitle {
222    fn from(value: &SsaSubtitle) -> Self {
223        Self {
224            events: value
225                .events()
226                .iter()
227                .map(|line| TimedMicroDvdEvent {
228                    text: line.as_plaintext().replace('\n', "|"),
229                    start: line.start,
230                    end: line.end,
231                })
232                .collect(),
233            framerate: 24.0,
234        }
235    }
236}
237
238impl From<&SubRipSubtitle> for TimedMicroDvdSubtitle {
239    fn from(value: &SubRipSubtitle) -> Self {
240        Self {
241            events: value
242                .events()
243                .iter()
244                .map(|line| TimedMicroDvdEvent {
245                    text: line.as_plaintext().replace('\n', "|"),
246                    start: line.start,
247                    end: line.end,
248                })
249                .collect(),
250            framerate: 24.0,
251        }
252    }
253}
254
255impl From<&WebVttSubtitle> for TimedMicroDvdSubtitle {
256    fn from(value: &WebVttSubtitle) -> Self {
257        Self {
258            events: value
259                .events()
260                .iter()
261                .map(|line| TimedMicroDvdEvent {
262                    text: line.as_plaintext().replace('\n', "|"),
263                    start: line.start,
264                    end: line.end,
265                })
266                .collect(),
267            framerate: 24.0,
268        }
269    }
270}
271
272impl TextEvent for TimedMicroDvdEvent {
273    fn unformatted_text(&self) -> Cow<'_, String> {
274        Cow::Borrowed(&self.text)
275    }
276
277    fn as_plaintext(&self) -> Cow<'_, String> {
278        Cow::Owned(self.text.replace('|', "\n"))
279    }
280}
281
282impl TextEventInterface for TimedMicroDvdEvent {
283    fn text(&self) -> String {
284        self.text.clone()
285    }
286
287    fn set_text(&mut self, text: String) {
288        self.text = text;
289    }
290}
291
292impl TimedEvent for TimedMicroDvdEvent {}
293
294impl TimedEventInterface for TimedMicroDvdEvent {
295    fn start(&self) -> Moment {
296        self.start
297    }
298
299    fn end(&self) -> Moment {
300        self.end
301    }
302
303    fn set_start(&mut self, moment: Moment) {
304        self.start = moment;
305    }
306
307    fn set_end(&mut self, moment: Moment) {
308        self.end = moment;
309    }
310}
311
312impl From<&TimedMicroDvdSubtitle> for MicroDvdSubtitle {
313    fn from(value: &TimedMicroDvdSubtitle) -> Self {
314        Self {
315            events: value
316                .events
317                .iter()
318                .map(|event| MicroDvdEvent {
319                    start: moment_to_frame(event.start, value.framerate),
320                    end: moment_to_frame(event.end, value.framerate),
321                    text: event.text.clone(),
322                })
323                .collect(),
324        }
325    }
326}
327
328impl MicroDvdSubtitle {
329    /// Create new instance from already existing list of `MicroDvdEvent`s.
330    #[must_use]
331    pub fn from_events(events: Vec<MicroDvdEvent>) -> Self {
332        Self { events }
333    }
334
335    fn open_file_with_encoding(
336        path: impl AsRef<Path>,
337        encoding: Option<&'static Encoding>,
338    ) -> Result<Self, Error> {
339        let file = File::open(path)?;
340        let transcoded = DecodeReaderBytesBuilder::new()
341            .encoding(encoding)
342            .build(file);
343        let reader = BufReader::new(transcoded);
344
345        Ok(parse_microdvd(reader))
346    }
347}
348
349impl Subtitle for MicroDvdSubtitle {
350    type Event = MicroDvdEvent;
351
352    fn from_path_with_encoding(
353        path: impl AsRef<Path>,
354        encoding: Option<&'static Encoding>,
355    ) -> Result<Self, Error> {
356        let mut enc = encoding.or_else(|| detect_file_encoding(path.as_ref(), Some(30)).ok());
357        let mut result = Self::open_file_with_encoding(path.as_ref(), enc);
358
359        if encoding.is_none() && result.is_err() {
360            enc = encoding.or_else(|| detect_file_encoding(path.as_ref(), None).ok());
361            result = Self::open_file_with_encoding(path.as_ref(), enc);
362        }
363
364        result
365    }
366
367    fn events(&self) -> &[Self::Event] {
368        self.events.as_slice()
369    }
370
371    fn events_mut(&mut self) -> &mut [Self::Event] {
372        self.events.as_mut_slice()
373    }
374}
375
376impl TextSubtitle for MicroDvdSubtitle {}
377
378impl Display for MicroDvdSubtitle {
379    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380        for event in &self.events {
381            writeln!(f, "{{{}}}{{{}}}{}", event.start, event.end, event.text)?;
382        }
383
384        Ok(())
385    }
386}
387
388impl FromStr for MicroDvdSubtitle {
389    type Err = Error;
390
391    fn from_str(s: &str) -> Result<Self, Self::Err> {
392        let reader = BufReader::new(s.as_bytes());
393
394        Ok(parse_microdvd(reader))
395    }
396}
397
398impl TextEvent for MicroDvdEvent {
399    fn unformatted_text(&self) -> Cow<'_, String> {
400        Cow::Owned(self.text.replace('|', "\n"))
401    }
402}
403
404impl TextEventInterface for MicroDvdEvent {
405    fn text(&self) -> String {
406        self.text.clone()
407    }
408
409    fn set_text(&mut self, text: String) {
410        self.text = text;
411    }
412}