subx_cli/core/formats/sub/mod.rs
1//! MicroDVD/SubViewer SUB subtitle format implementation.
2//!
3//! This module exposes the [`SubFormat`] adapter that implements the
4//! [`SubtitleFormat`] trait. The actual logic is split across:
5//!
6//! - `parser`: parsing logic and the malformed-input disposition matrix
7//! - `serializer`: serialization logic
8//! - `time`: frame ↔ time helpers and shared constants
9//!
10//! # Examples
11//!
12//! ```rust
13//! use subx_cli::core::formats::{SubtitleFormat, sub::SubFormat};
14//! let sub = SubFormat;
15//! let content = "{0}{25}Hello\n";
16//! let subtitle = sub.parse(content).unwrap();
17//! ```
18
19use crate::Result;
20use crate::core::formats::{Subtitle, SubtitleFormat};
21
22mod parser;
23mod serializer;
24mod time;
25
26#[cfg(test)]
27mod tests;
28
29/// Subtitle format implementation for MicroDVD/SubViewer SUB.
30///
31/// The `SubFormat` struct implements parsing, serialization, and
32/// detection for SUB files using frame-based timing. See the
33/// `parser` module documentation for the malformed-input disposition
34/// matrix observed by [`SubFormat::parse`].
35pub struct SubFormat;
36
37impl SubtitleFormat for SubFormat {
38 /// Parse SUB content.
39 ///
40 /// # Malformed-input dispositions
41 ///
42 /// - Empty input → returns [`crate::error::SubXError::SubtitleFormat`].
43 /// - A single cue whose body exceeds the per-cue cap (1 MiB)
44 /// → returns [`crate::error::SubXError::SubtitleFormat`].
45 /// - Non-numeric frame range (line that does not match
46 /// `{\d+}{\d+}`) → skipped with a `debug!` log; parsing continues.
47 /// - Frame number that decodes to > 24 h → skipped with a `debug!`
48 /// log; parsing continues.
49 fn parse(&self, content: &str) -> Result<Subtitle> {
50 parser::parse(content)
51 }
52
53 fn serialize(&self, subtitle: &Subtitle) -> Result<String> {
54 serializer::serialize(subtitle)
55 }
56
57 fn detect(&self, content: &str) -> bool {
58 parser::detect(content)
59 }
60
61 fn format_name(&self) -> &'static str {
62 "SUB"
63 }
64
65 fn file_extensions(&self) -> &'static [&'static str] {
66 &["sub"]
67 }
68}