ass_editor/formats/webvtt/
importer.rs1use crate::core::{EditorDocument, EditorError};
7use crate::formats::{FormatImporter, FormatInfo, FormatOptions, FormatResult};
8use ass_core::parser::Script;
9use std::io::Read;
10
11use super::WebVttFormat;
12
13impl FormatImporter for WebVttFormat {
14 fn format_info(&self) -> &FormatInfo {
15 &self.info
16 }
17
18 fn import_from_reader(
19 &self,
20 reader: &mut dyn Read,
21 options: &FormatOptions,
22 ) -> Result<(EditorDocument, FormatResult), EditorError> {
23 let mut content = String::new();
25 reader
26 .read_to_string(&mut content)
27 .map_err(|e| EditorError::IoError(format!("Failed to read WebVTT content: {e}")))?;
28
29 let lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
30 let mut warnings = Vec::new();
31 let mut dialogues = Vec::new();
32 let mut idx = 0;
33 let mut cue_count = 0;
34
35 if lines.is_empty() || !lines[0].trim().starts_with("WEBVTT") {
37 warnings.push("Missing or invalid WebVTT header".to_string());
38 } else {
39 idx = 1; }
41
42 while idx < lines.len() {
44 match Self::parse_vtt_cue(&lines, idx) {
45 Ok((next_idx, dialogue)) => {
46 dialogues.push(dialogue);
47 idx = next_idx;
48 cue_count += 1;
49 }
50 Err(e) => {
51 if idx < lines.len() {
52 warnings.push(format!("Skipping invalid cue at line {}: {e}", idx + 1));
53 idx += 1;
54 } else {
55 break;
56 }
57 }
58 }
59 }
60
61 let mut ass_content = String::new();
63
64 ass_content.push_str("[Script Info]\n");
66 ass_content.push_str("Title: Converted from WebVTT\n");
67 ass_content.push_str("ScriptType: v4.00+\n");
68 ass_content.push_str("Collisions: Normal\n");
69 ass_content.push_str("PlayDepth: 0\n");
70 ass_content.push_str("Timer: 100.0000\n");
71 ass_content.push_str("Video Aspect Ratio: 0\n");
72 ass_content.push_str("Video Zoom: 6\n");
73 ass_content.push_str("Video Position: 0\n");
74 ass_content.push_str("PlayResX: 640\n");
75 ass_content.push_str("PlayResY: 480\n\n");
76
77 ass_content.push_str("[V4+ Styles]\n");
79 ass_content.push_str("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n");
80 ass_content.push_str("Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1\n\n");
81
82 ass_content.push_str("[Events]\n");
84 ass_content.push_str(
85 "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n",
86 );
87
88 for dialogue in dialogues {
89 ass_content.push_str(&dialogue);
90 ass_content.push('\n');
91 }
92
93 let _script = Script::parse(&ass_content)?;
95
96 let document = EditorDocument::from_content(&ass_content)?;
98
99 let mut result = FormatResult::success(cue_count)
101 .with_metadata("original_format".to_string(), "WebVTT".to_string())
102 .with_metadata("cues_count".to_string(), cue_count.to_string())
103 .with_metadata("encoding".to_string(), options.encoding.clone());
104
105 if !warnings.is_empty() {
106 result = result.with_warnings(warnings);
107 }
108
109 Ok((document, result))
110 }
111}