1use crate::core::formats::converter::FormatConverter;
16use crate::core::formats::{Subtitle, SubtitleFormatType};
17
18impl FormatConverter {
19 pub(crate) fn transform_subtitle(
30 &self,
31 subtitle: Subtitle,
32 target_format: &str,
33 ) -> crate::Result<Subtitle> {
34 match (subtitle.format.as_str(), target_format) {
35 ("srt", "ass") => self.srt_to_ass(subtitle),
36 ("ass", "srt") => self.ass_to_srt(subtitle),
37 ("srt", "vtt") => self.srt_to_vtt(subtitle),
38 ("vtt", "srt") => self.vtt_to_srt(subtitle),
39 ("ass", "vtt") => self.ass_to_vtt(subtitle),
40 ("vtt", "ass") => self.vtt_to_ass(subtitle),
41 (source, target) if source == target => Ok(subtitle),
42 _ => Err(crate::error::SubXError::subtitle_format(
43 subtitle.format.to_string(),
44 format!(
45 "Unsupported conversion: {} -> {}",
46 subtitle.format, target_format
47 ),
48 )),
49 }
50 }
51
52 pub(crate) fn srt_to_ass(&self, mut subtitle: Subtitle) -> crate::Result<Subtitle> {
54 let _default_style = crate::core::formats::ass::AssStyle {
55 name: "Default".to_string(),
56 font_name: "Arial".to_string(),
57 font_size: 16,
58 primary_color: crate::core::formats::ass::Color::white(),
59 secondary_color: crate::core::formats::ass::Color::red(),
60 outline_color: crate::core::formats::ass::Color::black(),
61 shadow_color: crate::core::formats::ass::Color::black(),
62 bold: false,
63 italic: false,
64 underline: false,
65 alignment: 2,
66 };
67 for entry in &mut subtitle.entries {
68 if self.config.preserve_styling {
69 entry.styling = Some(self.extract_srt_styling(&entry.text)?);
70 }
71 entry.text = self.convert_srt_tags_to_ass(&entry.text);
72 }
73 subtitle.format = SubtitleFormatType::Ass;
74 subtitle.metadata.original_format = SubtitleFormatType::Srt;
75 Ok(subtitle)
76 }
77
78 pub(crate) fn ass_to_srt(&self, mut subtitle: Subtitle) -> crate::Result<Subtitle> {
80 for entry in &mut subtitle.entries {
81 entry.text = self.strip_ass_tags(&entry.text);
82 if self.config.preserve_styling {
83 entry.text = self.convert_ass_tags_to_srt(&entry.text);
84 }
85 entry.styling = None;
86 }
87 subtitle.format = SubtitleFormatType::Srt;
88 Ok(subtitle)
89 }
90
91 pub(crate) fn srt_to_vtt(&self, mut subtitle: Subtitle) -> crate::Result<Subtitle> {
93 subtitle.metadata.title = Some("WEBVTT".to_string());
94 for entry in &mut subtitle.entries {
95 entry.text = self.convert_srt_tags_to_vtt(&entry.text);
96 }
97 subtitle.format = SubtitleFormatType::Vtt;
98 Ok(subtitle)
99 }
100
101 pub(crate) fn ass_to_vtt(&self, subtitle: Subtitle) -> crate::Result<Subtitle> {
103 let subtitle = self.ass_to_srt(subtitle)?;
105 self.srt_to_vtt(subtitle)
106 }
107
108 pub(crate) fn vtt_to_srt(&self, mut subtitle: Subtitle) -> crate::Result<Subtitle> {
110 for entry in &mut subtitle.entries {
112 if self.config.preserve_styling {
113 entry.text = self.convert_vtt_tags_to_srt(&entry.text);
114 } else {
115 entry.text = self.strip_vtt_tags(&entry.text);
116 }
117 entry.styling = None;
118 }
119 subtitle.format = SubtitleFormatType::Srt;
120 Ok(subtitle)
121 }
122
123 pub(crate) fn vtt_to_ass(&self, subtitle: Subtitle) -> crate::Result<Subtitle> {
125 let subtitle = self.vtt_to_srt(subtitle)?;
127 self.srt_to_ass(subtitle)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use std::time::Duration;
134
135 use crate::core::formats::converter::{ConversionConfig, FormatConverter};
136 use crate::core::formats::{Subtitle, SubtitleEntry, SubtitleFormatType, SubtitleMetadata};
137
138 fn make_config(preserve_styling: bool) -> ConversionConfig {
139 ConversionConfig {
140 preserve_styling,
141 target_encoding: "UTF-8".to_string(),
142 keep_original: false,
143 validate_output: false,
144 }
145 }
146
147 fn make_converter(preserve_styling: bool) -> FormatConverter {
148 FormatConverter::new(make_config(preserve_styling))
149 }
150
151 fn make_subtitle(format: SubtitleFormatType, entries: Vec<SubtitleEntry>) -> Subtitle {
152 Subtitle {
153 entries,
154 metadata: SubtitleMetadata::new(format.clone()),
155 format,
156 }
157 }
158
159 fn make_entry(index: usize, text: &str) -> SubtitleEntry {
160 SubtitleEntry::new(
161 index,
162 Duration::from_secs((index as u64) * 5),
163 Duration::from_secs((index as u64) * 5 + 3),
164 text.to_string(),
165 )
166 }
167
168 #[test]
171 fn transform_srt_to_ass() {
172 let conv = make_converter(false);
173 let sub = make_subtitle(SubtitleFormatType::Srt, vec![make_entry(1, "Hello")]);
174 let result = conv.transform_subtitle(sub, "ass").unwrap();
175 assert_eq!(result.format, SubtitleFormatType::Ass);
176 }
177
178 #[test]
179 fn transform_ass_to_srt() {
180 let conv = make_converter(false);
181 let sub = make_subtitle(SubtitleFormatType::Ass, vec![make_entry(1, "Hello")]);
182 let result = conv.transform_subtitle(sub, "srt").unwrap();
183 assert_eq!(result.format, SubtitleFormatType::Srt);
184 }
185
186 #[test]
187 fn transform_srt_to_vtt() {
188 let conv = make_converter(false);
189 let sub = make_subtitle(SubtitleFormatType::Srt, vec![make_entry(1, "Hello")]);
190 let result = conv.transform_subtitle(sub, "vtt").unwrap();
191 assert_eq!(result.format, SubtitleFormatType::Vtt);
192 }
193
194 #[test]
195 fn transform_vtt_to_srt() {
196 let conv = make_converter(false);
197 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![make_entry(1, "Hello")]);
198 let result = conv.transform_subtitle(sub, "srt").unwrap();
199 assert_eq!(result.format, SubtitleFormatType::Srt);
200 }
201
202 #[test]
203 fn transform_ass_to_vtt() {
204 let conv = make_converter(false);
205 let sub = make_subtitle(SubtitleFormatType::Ass, vec![make_entry(1, "Hello")]);
206 let result = conv.transform_subtitle(sub, "vtt").unwrap();
207 assert_eq!(result.format, SubtitleFormatType::Vtt);
208 }
209
210 #[test]
211 fn transform_vtt_to_ass() {
212 let conv = make_converter(false);
213 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![make_entry(1, "Hello")]);
214 let result = conv.transform_subtitle(sub, "ass").unwrap();
215 assert_eq!(result.format, SubtitleFormatType::Ass);
216 }
217
218 #[test]
219 fn transform_same_format_returns_unchanged() {
220 let conv = make_converter(false);
221 let sub = make_subtitle(SubtitleFormatType::Srt, vec![make_entry(1, "Same")]);
222 let result = conv.transform_subtitle(sub, "srt").unwrap();
223 assert_eq!(result.format, SubtitleFormatType::Srt);
224 assert_eq!(result.entries[0].text, "Same");
225 }
226
227 #[test]
228 fn transform_same_format_ass() {
229 let conv = make_converter(false);
230 let sub = make_subtitle(SubtitleFormatType::Ass, vec![make_entry(1, "Same")]);
231 let result = conv.transform_subtitle(sub, "ass").unwrap();
232 assert_eq!(result.format, SubtitleFormatType::Ass);
233 }
234
235 #[test]
236 fn transform_unsupported_format_returns_error() {
237 let conv = make_converter(false);
238 let sub = make_subtitle(SubtitleFormatType::Srt, vec![make_entry(1, "Hello")]);
239 let result = conv.transform_subtitle(sub, "sub");
240 assert!(result.is_err());
241 }
242
243 #[test]
246 fn srt_to_ass_sets_format() {
247 let conv = make_converter(false);
248 let sub = make_subtitle(SubtitleFormatType::Srt, vec![make_entry(1, "Hello")]);
249 let result = conv.srt_to_ass(sub).unwrap();
250 assert_eq!(result.format, SubtitleFormatType::Ass);
251 assert_eq!(result.metadata.original_format, SubtitleFormatType::Srt);
252 }
253
254 #[test]
255 fn srt_to_ass_converts_bold_tags() {
256 let conv = make_converter(false);
257 let entry = make_entry(1, "<b>bold text</b>");
258 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
259 let result = conv.srt_to_ass(sub).unwrap();
260 assert!(result.entries[0].text.contains("{\\b1}"));
261 assert!(result.entries[0].text.contains("{\\b0}"));
262 }
263
264 #[test]
265 fn srt_to_ass_converts_italic_tags() {
266 let conv = make_converter(false);
267 let entry = make_entry(1, "<i>italic</i>");
268 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
269 let result = conv.srt_to_ass(sub).unwrap();
270 assert!(result.entries[0].text.contains("{\\i1}"));
271 assert!(result.entries[0].text.contains("{\\i0}"));
272 }
273
274 #[test]
275 fn srt_to_ass_converts_underline_tags() {
276 let conv = make_converter(false);
277 let entry = make_entry(1, "<u>underline</u>");
278 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
279 let result = conv.srt_to_ass(sub).unwrap();
280 assert!(result.entries[0].text.contains("{\\u1}"));
281 assert!(result.entries[0].text.contains("{\\u0}"));
282 }
283
284 #[test]
285 fn srt_to_ass_converts_font_color_tag() {
286 let conv = make_converter(false);
287 let entry = make_entry(1, r##"<font color="#FF0000">red</font>"##);
288 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
289 let result = conv.srt_to_ass(sub).unwrap();
290 assert!(result.entries[0].text.contains("{\\c"));
291 assert!(result.entries[0].text.contains("{\\c}"));
292 }
293
294 #[test]
295 fn srt_to_ass_with_preserve_styling_sets_styling() {
296 let conv = make_converter(true);
297 let entry = make_entry(1, "<b>bold</b>");
298 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
299 let result = conv.srt_to_ass(sub).unwrap();
300 assert!(result.entries[0].styling.is_some());
301 let styling = result.entries[0].styling.as_ref().unwrap();
302 assert!(styling.bold);
303 }
304
305 #[test]
306 fn srt_to_ass_with_preserve_styling_italic() {
307 let conv = make_converter(true);
308 let entry = make_entry(1, "<i>italic</i>");
309 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
310 let result = conv.srt_to_ass(sub).unwrap();
311 let styling = result.entries[0].styling.as_ref().unwrap();
312 assert!(styling.italic);
313 }
314
315 #[test]
316 fn srt_to_ass_with_preserve_styling_underline() {
317 let conv = make_converter(true);
318 let entry = make_entry(1, "<u>underlined</u>");
319 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
320 let result = conv.srt_to_ass(sub).unwrap();
321 let styling = result.entries[0].styling.as_ref().unwrap();
322 assert!(styling.underline);
323 }
324
325 #[test]
326 fn srt_to_ass_empty_entries() {
327 let conv = make_converter(false);
328 let sub = make_subtitle(SubtitleFormatType::Srt, vec![]);
329 let result = conv.srt_to_ass(sub).unwrap();
330 assert_eq!(result.format, SubtitleFormatType::Ass);
331 assert!(result.entries.is_empty());
332 }
333
334 #[test]
337 fn ass_to_srt_sets_format() {
338 let conv = make_converter(false);
339 let sub = make_subtitle(SubtitleFormatType::Ass, vec![make_entry(1, "Hello")]);
340 let result = conv.ass_to_srt(sub).unwrap();
341 assert_eq!(result.format, SubtitleFormatType::Srt);
342 }
343
344 #[test]
345 fn ass_to_srt_strips_ass_tags() {
346 let conv = make_converter(false);
347 let entry = make_entry(1, "{\\an8}Dialogue text");
348 let sub = make_subtitle(SubtitleFormatType::Ass, vec![entry]);
349 let result = conv.ass_to_srt(sub).unwrap();
350 assert_eq!(result.entries[0].text, "Dialogue text");
351 }
352
353 #[test]
354 fn ass_to_srt_clears_styling() {
355 let conv = make_converter(false);
356 let mut entry = make_entry(1, "{\\b1}bold{\\b0}");
357 entry.styling = Some(crate::core::formats::StylingInfo {
358 bold: true,
359 ..Default::default()
360 });
361 let sub = make_subtitle(SubtitleFormatType::Ass, vec![entry]);
362 let result = conv.ass_to_srt(sub).unwrap();
363 assert!(result.entries[0].styling.is_none());
364 }
365
366 #[test]
367 fn ass_to_srt_with_preserve_styling_converts_bold() {
368 let conv = make_converter(true);
369 let entry = make_entry(1, "{\\b1}bold text{\\b0}");
370 let sub = make_subtitle(SubtitleFormatType::Ass, vec![entry]);
371 let result = conv.ass_to_srt(sub).unwrap();
372 assert!(!result.entries[0].text.contains("{\\b1}"));
374 assert_eq!(result.entries[0].text, "bold text");
375 }
376
377 #[test]
378 fn ass_to_srt_with_preserve_styling_converts_italic() {
379 let conv = make_converter(true);
380 let entry = make_entry(1, "{\\i1}italic{\\i0}");
381 let sub = make_subtitle(SubtitleFormatType::Ass, vec![entry]);
382 let result = conv.ass_to_srt(sub).unwrap();
383 assert!(!result.entries[0].text.contains("{\\i1}"));
384 assert_eq!(result.entries[0].text, "italic");
385 }
386
387 #[test]
388 fn ass_to_srt_with_preserve_styling_converts_underline() {
389 let conv = make_converter(true);
390 let entry = make_entry(1, "{\\u1}underline{\\u0}");
391 let sub = make_subtitle(SubtitleFormatType::Ass, vec![entry]);
392 let result = conv.ass_to_srt(sub).unwrap();
393 assert!(!result.entries[0].text.contains("{\\u1}"));
394 assert_eq!(result.entries[0].text, "underline");
395 }
396
397 #[test]
398 fn ass_to_srt_empty_entries() {
399 let conv = make_converter(false);
400 let sub = make_subtitle(SubtitleFormatType::Ass, vec![]);
401 let result = conv.ass_to_srt(sub).unwrap();
402 assert_eq!(result.format, SubtitleFormatType::Srt);
403 assert!(result.entries.is_empty());
404 }
405
406 #[test]
409 fn srt_to_vtt_sets_format_and_title() {
410 let conv = make_converter(false);
411 let sub = make_subtitle(SubtitleFormatType::Srt, vec![make_entry(1, "Hello")]);
412 let result = conv.srt_to_vtt(sub).unwrap();
413 assert_eq!(result.format, SubtitleFormatType::Vtt);
414 assert_eq!(result.metadata.title.as_deref(), Some("WEBVTT"));
415 }
416
417 #[test]
418 fn srt_to_vtt_preserves_text() {
419 let conv = make_converter(false);
420 let entry = make_entry(1, "Plain text");
421 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
422 let result = conv.srt_to_vtt(sub).unwrap();
423 assert_eq!(result.entries[0].text, "Plain text");
424 }
425
426 #[test]
427 fn srt_to_vtt_empty_entries() {
428 let conv = make_converter(false);
429 let sub = make_subtitle(SubtitleFormatType::Srt, vec![]);
430 let result = conv.srt_to_vtt(sub).unwrap();
431 assert_eq!(result.format, SubtitleFormatType::Vtt);
432 }
433
434 #[test]
437 fn ass_to_vtt_sets_format() {
438 let conv = make_converter(false);
439 let sub = make_subtitle(SubtitleFormatType::Ass, vec![make_entry(1, "{\\an8}Text")]);
440 let result = conv.ass_to_vtt(sub).unwrap();
441 assert_eq!(result.format, SubtitleFormatType::Vtt);
442 }
443
444 #[test]
445 fn ass_to_vtt_strips_ass_tags() {
446 let conv = make_converter(false);
447 let entry = make_entry(1, "{\\b1}bold{\\b0}");
448 let sub = make_subtitle(SubtitleFormatType::Ass, vec![entry]);
449 let result = conv.ass_to_vtt(sub).unwrap();
450 assert!(!result.entries[0].text.contains("{\\"));
451 }
452
453 #[test]
456 fn vtt_to_srt_sets_format() {
457 let conv = make_converter(false);
458 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![make_entry(1, "Hello")]);
459 let result = conv.vtt_to_srt(sub).unwrap();
460 assert_eq!(result.format, SubtitleFormatType::Srt);
461 }
462
463 #[test]
464 fn vtt_to_srt_strips_html_tags_without_preserve_styling() {
465 let conv = make_converter(false);
466 let entry = make_entry(1, "<b>bold</b> text");
467 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![entry]);
468 let result = conv.vtt_to_srt(sub).unwrap();
469 assert!(!result.entries[0].text.contains("<b>"));
470 assert_eq!(result.entries[0].text, "bold text");
471 }
472
473 #[test]
474 fn vtt_to_srt_preserves_tags_with_preserve_styling() {
475 let conv = make_converter(true);
476 let entry = make_entry(1, "<i>italic</i>");
477 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![entry]);
478 let result = conv.vtt_to_srt(sub).unwrap();
479 assert_eq!(result.entries[0].text, "<i>italic</i>");
480 }
481
482 #[test]
483 fn vtt_to_srt_clears_styling() {
484 let conv = make_converter(false);
485 let mut entry = make_entry(1, "text");
486 entry.styling = Some(crate::core::formats::StylingInfo {
487 italic: true,
488 ..Default::default()
489 });
490 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![entry]);
491 let result = conv.vtt_to_srt(sub).unwrap();
492 assert!(result.entries[0].styling.is_none());
493 }
494
495 #[test]
496 fn vtt_to_srt_empty_entries() {
497 let conv = make_converter(false);
498 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![]);
499 let result = conv.vtt_to_srt(sub).unwrap();
500 assert_eq!(result.format, SubtitleFormatType::Srt);
501 }
502
503 #[test]
506 fn vtt_to_ass_sets_format() {
507 let conv = make_converter(false);
508 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![make_entry(1, "Hello")]);
509 let result = conv.vtt_to_ass(sub).unwrap();
510 assert_eq!(result.format, SubtitleFormatType::Ass);
511 }
512
513 #[test]
514 fn vtt_to_ass_multiple_entries() {
515 let conv = make_converter(false);
516 let sub = make_subtitle(
517 SubtitleFormatType::Vtt,
518 vec![make_entry(1, "First"), make_entry(2, "Second")],
519 );
520 let result = conv.vtt_to_ass(sub).unwrap();
521 assert_eq!(result.entries.len(), 2);
522 }
523
524 #[test]
527 fn extract_srt_styling_detects_bold() {
528 let conv = make_converter(false);
529 let styling = conv.extract_srt_styling("<b>text</b>").unwrap();
530 assert!(styling.bold);
531 }
532
533 #[test]
534 fn extract_srt_styling_detects_bold_uppercase() {
535 let conv = make_converter(false);
536 let styling = conv.extract_srt_styling("<B>text</B>").unwrap();
537 assert!(styling.bold);
538 }
539
540 #[test]
541 fn extract_srt_styling_detects_italic() {
542 let conv = make_converter(false);
543 let styling = conv.extract_srt_styling("<i>text</i>").unwrap();
544 assert!(styling.italic);
545 }
546
547 #[test]
548 fn extract_srt_styling_detects_italic_uppercase() {
549 let conv = make_converter(false);
550 let styling = conv.extract_srt_styling("<I>text</I>").unwrap();
551 assert!(styling.italic);
552 }
553
554 #[test]
555 fn extract_srt_styling_detects_underline() {
556 let conv = make_converter(false);
557 let styling = conv.extract_srt_styling("<u>text</u>").unwrap();
558 assert!(styling.underline);
559 }
560
561 #[test]
562 fn extract_srt_styling_detects_underline_uppercase() {
563 let conv = make_converter(false);
564 let styling = conv.extract_srt_styling("<U>text</U>").unwrap();
565 assert!(styling.underline);
566 }
567
568 #[test]
569 fn extract_srt_styling_plain_text_no_flags() {
570 let conv = make_converter(false);
571 let styling = conv.extract_srt_styling("plain text").unwrap();
572 assert!(!styling.bold);
573 assert!(!styling.italic);
574 assert!(!styling.underline);
575 assert!(styling.color.is_none());
576 }
577
578 #[test]
579 fn extract_srt_styling_all_flags() {
580 let conv = make_converter(false);
581 let styling = conv
582 .extract_srt_styling("<b><i><u>all</u></i></b>")
583 .unwrap();
584 assert!(styling.bold);
585 assert!(styling.italic);
586 assert!(styling.underline);
587 }
588
589 #[test]
592 fn convert_srt_tags_to_ass_bold() {
593 let conv = make_converter(false);
594 let result = conv.convert_srt_tags_to_ass("<b>text</b>");
595 assert_eq!(result, "{\\b1}text{\\b0}");
596 }
597
598 #[test]
599 fn convert_srt_tags_to_ass_italic() {
600 let conv = make_converter(false);
601 let result = conv.convert_srt_tags_to_ass("<i>text</i>");
602 assert_eq!(result, "{\\i1}text{\\i0}");
603 }
604
605 #[test]
606 fn convert_srt_tags_to_ass_underline() {
607 let conv = make_converter(false);
608 let result = conv.convert_srt_tags_to_ass("<u>text</u>");
609 assert_eq!(result, "{\\u1}text{\\u0}");
610 }
611
612 #[test]
613 fn convert_srt_tags_to_ass_font_color_hex() {
614 let conv = make_converter(false);
615 let result = conv.convert_srt_tags_to_ass(r##"<font color="#FF0000">red</font>"##);
616 assert!(result.contains("{\\c&H"));
617 assert!(result.contains("{\\c}"));
618 }
619
620 #[test]
621 fn convert_srt_tags_to_ass_no_tags() {
622 let conv = make_converter(false);
623 let result = conv.convert_srt_tags_to_ass("plain text");
624 assert_eq!(result, "plain text");
625 }
626
627 #[test]
630 fn strip_ass_tags_removes_override_blocks() {
631 let conv = make_converter(false);
632 let result = conv.strip_ass_tags("{\\an8}{\\b1}text{\\b0}");
633 assert_eq!(result, "text");
634 }
635
636 #[test]
637 fn strip_ass_tags_plain_text_unchanged() {
638 let conv = make_converter(false);
639 let result = conv.strip_ass_tags("plain text");
640 assert_eq!(result, "plain text");
641 }
642
643 #[test]
644 fn strip_ass_tags_empty_string() {
645 let conv = make_converter(false);
646 let result = conv.strip_ass_tags("");
647 assert_eq!(result, "");
648 }
649
650 #[test]
653 fn convert_ass_tags_to_srt_bold() {
654 let conv = make_converter(false);
655 let result = conv.convert_ass_tags_to_srt("{\\b1}text{\\b0}");
656 assert_eq!(result, "<b>text</b>");
657 }
658
659 #[test]
660 fn convert_ass_tags_to_srt_italic() {
661 let conv = make_converter(false);
662 let result = conv.convert_ass_tags_to_srt("{\\i1}text{\\i0}");
663 assert_eq!(result, "<i>text</i>");
664 }
665
666 #[test]
667 fn convert_ass_tags_to_srt_underline() {
668 let conv = make_converter(false);
669 let result = conv.convert_ass_tags_to_srt("{\\u1}text{\\u0}");
670 assert_eq!(result, "<u>text</u>");
671 }
672
673 #[test]
674 fn convert_ass_tags_to_srt_no_tags() {
675 let conv = make_converter(false);
676 let result = conv.convert_ass_tags_to_srt("no tags here");
677 assert_eq!(result, "no tags here");
678 }
679
680 #[test]
683 fn extract_color_from_tags_returns_none() {
684 let conv = make_converter(false);
685 let result = conv.extract_color_from_tags("<font color=\"red\">text</font>");
686 assert!(result.is_none());
687 }
688
689 #[test]
692 fn convert_color_to_ass_strips_hash() {
693 let conv = make_converter(false);
694 let result = conv.convert_color_to_ass("#FF0000");
695 assert_eq!(result, "FF0000");
696 }
697
698 #[test]
699 fn convert_color_to_ass_no_hash() {
700 let conv = make_converter(false);
701 let result = conv.convert_color_to_ass("00FF00");
702 assert_eq!(result, "00FF00");
703 }
704
705 #[test]
708 fn convert_srt_tags_to_vtt_passthrough() {
709 let conv = make_converter(false);
710 let result = conv.convert_srt_tags_to_vtt("<b>text</b>");
711 assert_eq!(result, "<b>text</b>");
712 }
713
714 #[test]
717 fn convert_vtt_tags_to_srt_passthrough() {
718 let conv = make_converter(false);
719 let result = conv.convert_vtt_tags_to_srt("<i>text</i>");
720 assert_eq!(result, "<i>text</i>");
721 }
722
723 #[test]
726 fn strip_vtt_tags_removes_html_tags() {
727 let conv = make_converter(false);
728 let result = conv.strip_vtt_tags("<b>bold</b> and <i>italic</i>");
729 assert_eq!(result, "bold and italic");
730 }
731
732 #[test]
733 fn strip_vtt_tags_plain_text_unchanged() {
734 let conv = make_converter(false);
735 let result = conv.strip_vtt_tags("plain text");
736 assert_eq!(result, "plain text");
737 }
738
739 #[test]
740 fn strip_vtt_tags_empty_string() {
741 let conv = make_converter(false);
742 let result = conv.strip_vtt_tags("");
743 assert_eq!(result, "");
744 }
745
746 #[test]
747 fn strip_vtt_tags_self_closing() {
748 let conv = make_converter(false);
749 let result = conv.strip_vtt_tags("line1<br/>line2");
750 assert_eq!(result, "line1line2");
751 }
752
753 #[test]
756 fn srt_to_ass_special_characters() {
757 let conv = make_converter(false);
758 let entry = make_entry(1, "Special: <>&\"'");
759 let sub = make_subtitle(SubtitleFormatType::Srt, vec![entry]);
760 let result = conv.srt_to_ass(sub).unwrap();
761 assert!(result.entries[0].text.contains("Special:"));
762 }
763
764 #[test]
765 fn srt_to_ass_multiple_entries_converted() {
766 let conv = make_converter(false);
767 let sub = make_subtitle(
768 SubtitleFormatType::Srt,
769 vec![
770 make_entry(1, "<b>First</b>"),
771 make_entry(2, "<i>Second</i>"),
772 make_entry(3, "Third"),
773 ],
774 );
775 let result = conv.srt_to_ass(sub).unwrap();
776 assert_eq!(result.entries.len(), 3);
777 assert!(result.entries[0].text.contains("{\\b1}"));
778 assert!(result.entries[1].text.contains("{\\i1}"));
779 assert_eq!(result.entries[2].text, "Third");
780 }
781
782 #[test]
783 fn ass_to_srt_multiple_entries() {
784 let conv = make_converter(true);
785 let sub = make_subtitle(
786 SubtitleFormatType::Ass,
787 vec![
788 make_entry(1, "{\\b1}one{\\b0}"),
789 make_entry(2, "{\\i1}two{\\i0}"),
790 ],
791 );
792 let result = conv.ass_to_srt(sub).unwrap();
793 assert_eq!(result.entries.len(), 2);
794 assert_eq!(result.entries[0].text, "one");
796 assert_eq!(result.entries[1].text, "two");
797 }
798
799 #[test]
800 fn vtt_to_srt_strips_multiple_tags() {
801 let conv = make_converter(false);
802 let entry = make_entry(1, "<v Speaker>text</v>");
803 let sub = make_subtitle(SubtitleFormatType::Vtt, vec![entry]);
804 let result = conv.vtt_to_srt(sub).unwrap();
805 assert!(!result.entries[0].text.contains('<'));
806 }
807}