Skip to main content

subx_cli/cli/
convert_args.rs

1//! Subtitle format conversion command-line arguments and options.
2//!
3//! This module defines the command-line interface for the `convert` subcommand,
4//! which handles subtitle format conversion between different standards like SRT,
5//! ASS, VTT, and SUB. It provides comprehensive format conversion capabilities
6//! with encoding support and optional file preservation.
7//!
8//! # Supported Formats
9//!
10//! - **SRT (SubRip)**: Most widely used subtitle format
11//! - **ASS (Advanced SubStation Alpha)**: Rich formatting and styling support
12//! - **VTT (WebVTT)**: Web-optimized subtitle format for HTML5 video
13//! - **SUB (MicroDVD/SubViewer)**: Frame-based subtitle format
14//!
15//! # Examples
16//!
17//! ```bash
18//! # Convert SRT to ASS format
19//! subx convert input.srt --format ass --output output.ass
20//!
21//! # Batch convert all SRT files in a directory to VTT
22//! subx convert ./subtitles/ --format vtt
23//!
24//! # Convert with specific encoding
25//! subx convert input.srt --format ass --encoding utf-8 --keep-original
26//! ```
27
28#![allow(clippy::needless_borrows_for_generic_args)]
29// src/cli/convert_args.rs
30use crate::cli::InputPathHandler;
31use crate::error::SubXError;
32use clap::{Args, ValueEnum};
33use std::path::PathBuf;
34
35/// Command-line arguments for subtitle format conversion.
36#[derive(Args, Debug)]
37pub struct ConvertArgs {
38    /// Input file or directory path containing subtitle files
39    pub input: Option<PathBuf>,
40
41    /// Specify file or directory paths to process (new parameter), can be used multiple times
42    #[arg(short = 'i', long = "input", value_name = "PATH")]
43    pub input_paths: Vec<PathBuf>,
44
45    /// Recursively process subdirectories (new parameter)
46    #[arg(short, long)]
47    pub recursive: bool,
48
49    /// Target output format for converted subtitles
50    #[arg(long, value_enum)]
51    pub format: Option<OutputSubtitleFormat>,
52
53    /// Output file path for the converted subtitle
54    #[arg(short, long)]
55    pub output: Option<PathBuf>,
56
57    /// Preserve the original files after conversion
58    #[arg(long)]
59    pub keep_original: bool,
60
61    /// Character encoding for input and output files
62    #[arg(long, default_value = "utf-8")]
63    pub encoding: String,
64
65    /// Disable automatic archive extraction for `-i` inputs
66    #[arg(long, default_value_t = false)]
67    pub no_extract: bool,
68}
69
70impl ConvertArgs {
71    /// Get all input paths
72    /// Get all input paths, combining input and input_paths parameters
73    pub fn get_input_handler(&self) -> Result<InputPathHandler, SubXError> {
74        let optional_paths = vec![self.input.clone()];
75        let merged_paths = InputPathHandler::merge_paths_from_multiple_sources(
76            &optional_paths,
77            &self.input_paths,
78            &[],
79        )?;
80
81        Ok(InputPathHandler::from_args(&merged_paths, self.recursive)?
82            .with_extensions(&["srt", "ass", "vtt", "sub", "ssa"])
83            .with_no_extract(self.no_extract))
84    }
85}
86
87/// Supported output subtitle formats for conversion operations.
88///
89/// This enum defines all subtitle formats that SubX can generate as output.
90/// Each format has specific characteristics and use cases:
91///
92/// - **SRT**: Simple, widely supported, good for basic subtitles
93/// - **ASS**: Advanced formatting, styling, and positioning capabilities
94/// - **VTT**: Web-optimized, supports HTML5 video elements
95/// - **SUB**: Frame-based timing, used in some legacy systems
96///
97/// # Format Characteristics
98///
99/// | Format | Timing | Styling | Web Support | Compatibility |
100/// |--------|--------|---------|-------------|---------------|
101/// | SRT    | Time   | Basic   | Good        | Excellent     |
102/// | ASS    | Time   | Rich    | Limited     | Good          |
103/// | VTT    | Time   | Medium  | Excellent   | Good          |
104/// | SUB    | Frame  | Basic   | Poor        | Limited       |
105///
106/// # Examples
107///
108/// ```rust
109/// use subx_cli::cli::OutputSubtitleFormat;
110///
111/// let srt_format = OutputSubtitleFormat::Srt;
112/// assert_eq!(srt_format.as_str(), "srt");
113/// assert_eq!(srt_format.file_extension(), ".srt");
114/// ```
115#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
116pub enum OutputSubtitleFormat {
117    /// SubRip (.srt) format - most widely supported subtitle format.
118    ///
119    /// Features:
120    /// - Simple time-based format
121    /// - Basic text formatting (bold, italic, underline)
122    /// - Excellent player compatibility
123    /// - Small file size
124    Srt,
125
126    /// Advanced SubStation Alpha (.ass) format - professional subtitle format.
127    ///
128    /// Features:
129    /// - Rich styling and formatting options
130    /// - Precise positioning and animation
131    /// - Multiple font and color support
132    /// - Advanced timing controls
133    Ass,
134
135    /// WebVTT (.vtt) format - web-optimized subtitle format.
136    ///
137    /// Features:
138    /// - HTML5 video element support
139    /// - CSS-like styling capabilities
140    /// - Cue positioning and alignment
141    /// - Web accessibility features
142    Vtt,
143
144    /// MicroDVD/SubViewer (.sub) format - frame-based subtitle format.
145    ///
146    /// Features:
147    /// - Frame-based timing (not time-based)
148    /// - Basic text formatting
149    /// - Legacy format support
150    /// - Compact file structure
151    Sub,
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::cli::{Cli, Commands};
158    use clap::Parser;
159    use std::path::PathBuf;
160
161    #[test]
162    fn test_convert_args_default_and_format() {
163        let cli =
164            Cli::try_parse_from(&["subx-cli", "convert", "movie.srt", "--format", "ass"]).unwrap();
165        let args = match cli.command {
166            Commands::Convert(a) => a,
167            _ => panic!("Expected Convert command"),
168        };
169        assert!(args.input_paths.is_empty());
170        assert_eq!(args.input, Some(PathBuf::from("movie.srt")));
171        assert!(!args.recursive);
172        assert_eq!(args.format, Some(OutputSubtitleFormat::Ass));
173        assert!(!args.keep_original);
174        assert_eq!(args.encoding, "utf-8");
175    }
176
177    #[test]
178    fn test_convert_args_multiple_input_recursive_and_keep_original() {
179        let cli = Cli::try_parse_from(&[
180            "subx-cli",
181            "convert",
182            "-i",
183            "d1",
184            "-i",
185            "f.srt",
186            "--recursive",
187            "--format",
188            "vtt",
189            "--encoding",
190            "utf-16",
191            "--keep-original",
192        ])
193        .unwrap();
194        let args = match cli.command {
195            Commands::Convert(a) => a,
196            _ => panic!("Expected Convert command"),
197        };
198        assert_eq!(
199            args.input_paths,
200            vec![PathBuf::from("d1"), PathBuf::from("f.srt")]
201        );
202        assert_eq!(args.input, None);
203        assert!(args.recursive);
204        assert_eq!(args.format, Some(OutputSubtitleFormat::Vtt));
205        assert!(args.keep_original);
206        assert_eq!(args.encoding, "utf-16");
207    }
208}
209
210impl OutputSubtitleFormat {
211    /// Returns the format identifier as a string.
212    ///
213    /// This method provides the lowercase string representation of the format,
214    /// which is used for command-line arguments and configuration files.
215    ///
216    /// # Examples
217    ///
218    /// ```rust
219    /// use subx_cli::cli::OutputSubtitleFormat;
220    ///
221    /// assert_eq!(OutputSubtitleFormat::Srt.as_str(), "srt");
222    /// assert_eq!(OutputSubtitleFormat::Ass.as_str(), "ass");
223    /// assert_eq!(OutputSubtitleFormat::Vtt.as_str(), "vtt");
224    /// assert_eq!(OutputSubtitleFormat::Sub.as_str(), "sub");
225    /// ```
226    pub fn as_str(&self) -> &'static str {
227        match self {
228            OutputSubtitleFormat::Srt => "srt",
229            OutputSubtitleFormat::Ass => "ass",
230            OutputSubtitleFormat::Vtt => "vtt",
231            OutputSubtitleFormat::Sub => "sub",
232        }
233    }
234
235    /// Returns the file extension for this format including the dot prefix.
236    ///
237    /// This method provides the standard file extension used for each
238    /// subtitle format, which is useful for generating output filenames.
239    ///
240    /// # Examples
241    ///
242    /// ```rust
243    /// use subx_cli::cli::OutputSubtitleFormat;
244    ///
245    /// assert_eq!(OutputSubtitleFormat::Srt.file_extension(), ".srt");
246    /// assert_eq!(OutputSubtitleFormat::Ass.file_extension(), ".ass");
247    /// assert_eq!(OutputSubtitleFormat::Vtt.file_extension(), ".vtt");
248    /// assert_eq!(OutputSubtitleFormat::Sub.file_extension(), ".sub");
249    /// ```
250    pub fn file_extension(&self) -> &'static str {
251        match self {
252            OutputSubtitleFormat::Srt => ".srt",
253            OutputSubtitleFormat::Ass => ".ass",
254            OutputSubtitleFormat::Vtt => ".vtt",
255            OutputSubtitleFormat::Sub => ".sub",
256        }
257    }
258}
259
260impl std::fmt::Display for OutputSubtitleFormat {
261    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262        write!(f, "{}", self.as_str())
263    }
264}
265// Test parameter parsing behavior
266#[cfg(test)]
267mod tests_parse {
268    use super::*;
269    use crate::cli::{Cli, Commands};
270    use clap::Parser;
271    use std::path::PathBuf;
272
273    #[test]
274    fn test_convert_args_default_values() {
275        let cli = Cli::try_parse_from(&["subx-cli", "convert", "in_path"]).unwrap();
276        let args = match cli.command {
277            Commands::Convert(c) => c,
278            _ => panic!("Expected Convert command"),
279        };
280        assert_eq!(args.input, Some(PathBuf::from("in_path")));
281        assert!(args.input_paths.is_empty());
282        assert!(!args.recursive);
283        assert_eq!(args.format, None);
284        assert_eq!(args.output, None);
285        assert!(!args.keep_original);
286        assert_eq!(args.encoding, "utf-8");
287    }
288
289    #[test]
290    fn test_convert_args_parsing() {
291        let cli = Cli::try_parse_from(&[
292            "subx-cli",
293            "convert",
294            "in",
295            "--format",
296            "vtt",
297            "--output",
298            "out",
299            "--keep-original",
300            "--encoding",
301            "gbk",
302        ])
303        .unwrap();
304        let args = match cli.command {
305            Commands::Convert(c) => c,
306            _ => panic!("Expected Convert command"),
307        };
308        assert_eq!(args.input, Some(PathBuf::from("in")));
309        assert!(args.input_paths.is_empty());
310        assert!(!args.recursive);
311        assert_eq!(args.format.unwrap(), OutputSubtitleFormat::Vtt);
312        assert_eq!(args.output, Some(PathBuf::from("out")));
313        assert!(args.keep_original);
314        assert_eq!(args.encoding, "gbk");
315    }
316}