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#[derive(Debug)]
22pub struct TimedMicroDvdSubtitle {
23 events: Vec<TimedMicroDvdEvent>,
24 framerate: FrameRate,
25}
26
27#[derive(Debug)]
29pub struct TimedMicroDvdEvent {
30 pub start: Moment,
32 pub end: Moment,
34 pub text: String,
36}
37
38#[derive(Debug)]
43pub struct MicroDvdSubtitle {
44 events: Vec<MicroDvdEvent>,
45}
46
47#[derive(Debug)]
49pub struct MicroDvdEvent {
50 pub start: Frame,
52 pub end: Frame,
54 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 #[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 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 #[must_use]
123 pub fn framerate(&self) -> FrameRate {
124 self.framerate
125 }
126
127 pub fn set_framerate(&mut self, framerate: FrameRate) {
130 self.framerate = framerate;
131 }
132
133 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 #[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}