Skip to main content

ass_editor/formats/ass/
format.rs

1//! ASS format handler implementation.
2//!
3//! Provides the [`AssFormat`] type, which reuses ass-core's parsing and
4//! serialization to import and export Advanced SubStation Alpha files.
5
6use crate::core::{EditorDocument, EditorError};
7use crate::formats::{
8    Format, FormatExporter, FormatImporter, FormatInfo, FormatOptions, FormatResult,
9};
10use ass_core::parser::Script;
11use std::io::{Read, Write};
12
13/// ASS format handler that reuses ass-core functionality
14#[derive(Debug)]
15pub struct AssFormat {
16    info: FormatInfo,
17}
18
19impl AssFormat {
20    /// Create a new ASS format handler
21    pub fn new() -> Self {
22        Self {
23            info: FormatInfo {
24                name: "ASS".to_string(),
25                extensions: vec!["ass".to_string()],
26                mime_type: "text/x-ass".to_string(),
27                description: "Advanced SubStation Alpha subtitle format".to_string(),
28                supports_styling: true,
29                supports_positioning: true,
30            },
31        }
32    }
33}
34
35impl Default for AssFormat {
36    fn default() -> Self {
37        Self::new()
38    }
39}
40
41impl FormatImporter for AssFormat {
42    fn format_info(&self) -> &FormatInfo {
43        &self.info
44    }
45
46    fn import_from_reader(
47        &self,
48        reader: &mut dyn Read,
49        _options: &FormatOptions,
50    ) -> Result<(EditorDocument, FormatResult), EditorError> {
51        // Read the entire content
52        let mut content = String::new();
53        reader
54            .read_to_string(&mut content)
55            .map_err(|e| EditorError::IoError(format!("Failed to read content: {e}")))?;
56
57        // Validate that it's parseable by ass-core
58        let script = Script::parse(&content)?;
59
60        // Count lines for result metadata
61        let line_count = content.lines().count();
62
63        // Create EditorDocument from the content
64        let document = EditorDocument::from_content(&content)?;
65
66        // Gather metadata from the parsed script
67        let mut result = FormatResult::success(line_count);
68
69        // Add script info as metadata
70        if let Some(ass_core::parser::ast::Section::ScriptInfo(script_info)) =
71            script.find_section(ass_core::parser::ast::SectionType::ScriptInfo)
72        {
73            if let Some(title) = script_info.get_field("Title") {
74                result = result.with_metadata("title".to_string(), title.to_string());
75            }
76            if let Some(script_type) = script_info.get_field("ScriptType") {
77                result = result.with_metadata("script_type".to_string(), script_type.to_string());
78            }
79        }
80
81        // Count sections for additional metadata
82        let section_count = script.sections().len();
83
84        result = result.with_metadata("sections".to_string(), section_count.to_string());
85
86        Ok((document, result))
87    }
88}
89
90impl FormatExporter for AssFormat {
91    fn format_info(&self) -> &FormatInfo {
92        &self.info
93    }
94
95    fn export_to_writer(
96        &self,
97        document: &EditorDocument,
98        writer: &mut dyn Write,
99        options: &FormatOptions,
100    ) -> Result<FormatResult, EditorError> {
101        let content = if options.preserve_formatting {
102            // Use the raw content to preserve exact formatting
103            document.text()
104        } else {
105            // Use ass-core's serialization for normalized output
106            document.parse_script_with(|script| script.to_ass_string())?
107        };
108
109        // Count lines before using content
110        let line_count = content.lines().count();
111
112        // Write content with proper encoding
113        let bytes = if options.encoding.eq_ignore_ascii_case("UTF-8") {
114            content.into_bytes()
115        } else {
116            // For non-UTF-8 encodings, we'd need additional encoding support
117            // For now, default to UTF-8 with a warning
118            let mut warnings = Vec::new();
119            if !options.encoding.eq_ignore_ascii_case("UTF-8") {
120                warnings.push(format!(
121                    "Encoding '{}' not supported, using UTF-8 instead",
122                    options.encoding
123                ));
124            }
125
126            let result = FormatResult::success(line_count).with_warnings(warnings);
127
128            writer
129                .write_all(&content.into_bytes())
130                .map_err(|e| EditorError::IoError(format!("Failed to write content: {e}")))?;
131
132            return Ok(result);
133        };
134
135        writer
136            .write_all(&bytes)
137            .map_err(|e| EditorError::IoError(format!("Failed to write content: {e}")))?;
138
139        Ok(FormatResult::success(line_count))
140    }
141}
142
143impl Format for AssFormat {
144    fn as_importer(&self) -> &dyn FormatImporter {
145        self
146    }
147
148    fn as_exporter(&self) -> &dyn FormatExporter {
149        self
150    }
151}