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
66impl ConvertArgs {
67 /// Get all input paths
68 /// Get all input paths, combining input and input_paths parameters
69 pub fn get_input_handler(&self) -> Result<InputPathHandler, SubXError> {
70 let optional_paths = vec![self.input.clone()];
71 let merged_paths = InputPathHandler::merge_paths_from_multiple_sources(
72 &optional_paths,
73 &self.input_paths,
74 &[],
75 )?;
76
77 Ok(InputPathHandler::from_args(&merged_paths, self.recursive)?
78 .with_extensions(&["srt", "ass", "vtt", "sub", "ssa"]))
79 }
80}
81
82/// Supported output subtitle formats for conversion operations.
83///
84/// This enum defines all subtitle formats that SubX can generate as output.
85/// Each format has specific characteristics and use cases:
86///
87/// - **SRT**: Simple, widely supported, good for basic subtitles
88/// - **ASS**: Advanced formatting, styling, and positioning capabilities
89/// - **VTT**: Web-optimized, supports HTML5 video elements
90/// - **SUB**: Frame-based timing, used in some legacy systems
91///
92/// # Format Characteristics
93///
94/// | Format | Timing | Styling | Web Support | Compatibility |
95/// |--------|--------|---------|-------------|---------------|
96/// | SRT | Time | Basic | Good | Excellent |
97/// | ASS | Time | Rich | Limited | Good |
98/// | VTT | Time | Medium | Excellent | Good |
99/// | SUB | Frame | Basic | Poor | Limited |
100///
101/// # Examples
102///
103/// ```rust
104/// use subx_cli::cli::OutputSubtitleFormat;
105///
106/// let srt_format = OutputSubtitleFormat::Srt;
107/// assert_eq!(srt_format.as_str(), "srt");
108/// assert_eq!(srt_format.file_extension(), ".srt");
109/// ```
110#[derive(ValueEnum, Clone, Debug, PartialEq, Eq)]
111pub enum OutputSubtitleFormat {
112 /// SubRip (.srt) format - most widely supported subtitle format.
113 ///
114 /// Features:
115 /// - Simple time-based format
116 /// - Basic text formatting (bold, italic, underline)
117 /// - Excellent player compatibility
118 /// - Small file size
119 Srt,
120
121 /// Advanced SubStation Alpha (.ass) format - professional subtitle format.
122 ///
123 /// Features:
124 /// - Rich styling and formatting options
125 /// - Precise positioning and animation
126 /// - Multiple font and color support
127 /// - Advanced timing controls
128 Ass,
129
130 /// WebVTT (.vtt) format - web-optimized subtitle format.
131 ///
132 /// Features:
133 /// - HTML5 video element support
134 /// - CSS-like styling capabilities
135 /// - Cue positioning and alignment
136 /// - Web accessibility features
137 Vtt,
138
139 /// MicroDVD/SubViewer (.sub) format - frame-based subtitle format.
140 ///
141 /// Features:
142 /// - Frame-based timing (not time-based)
143 /// - Basic text formatting
144 /// - Legacy format support
145 /// - Compact file structure
146 Sub,
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152 use crate::cli::{Cli, Commands};
153 use clap::Parser;
154 use std::path::PathBuf;
155
156 #[test]
157 fn test_convert_args_default_and_format() {
158 let cli =
159 Cli::try_parse_from(&["subx-cli", "convert", "movie.srt", "--format", "ass"]).unwrap();
160 let args = match cli.command {
161 Commands::Convert(a) => a,
162 _ => panic!("Expected Convert command"),
163 };
164 assert!(args.input_paths.is_empty());
165 assert_eq!(args.input, Some(PathBuf::from("movie.srt")));
166 assert!(!args.recursive);
167 assert_eq!(args.format, Some(OutputSubtitleFormat::Ass));
168 assert!(!args.keep_original);
169 assert_eq!(args.encoding, "utf-8");
170 }
171
172 #[test]
173 fn test_convert_args_multiple_input_recursive_and_keep_original() {
174 let cli = Cli::try_parse_from(&[
175 "subx-cli",
176 "convert",
177 "-i",
178 "d1",
179 "-i",
180 "f.srt",
181 "--recursive",
182 "--format",
183 "vtt",
184 "--encoding",
185 "utf-16",
186 "--keep-original",
187 ])
188 .unwrap();
189 let args = match cli.command {
190 Commands::Convert(a) => a,
191 _ => panic!("Expected Convert command"),
192 };
193 assert_eq!(
194 args.input_paths,
195 vec![PathBuf::from("d1"), PathBuf::from("f.srt")]
196 );
197 assert_eq!(args.input, None);
198 assert!(args.recursive);
199 assert_eq!(args.format, Some(OutputSubtitleFormat::Vtt));
200 assert!(args.keep_original);
201 assert_eq!(args.encoding, "utf-16");
202 }
203}
204
205impl OutputSubtitleFormat {
206 /// Returns the format identifier as a string.
207 ///
208 /// This method provides the lowercase string representation of the format,
209 /// which is used for command-line arguments and configuration files.
210 ///
211 /// # Examples
212 ///
213 /// ```rust
214 /// use subx_cli::cli::OutputSubtitleFormat;
215 ///
216 /// assert_eq!(OutputSubtitleFormat::Srt.as_str(), "srt");
217 /// assert_eq!(OutputSubtitleFormat::Ass.as_str(), "ass");
218 /// assert_eq!(OutputSubtitleFormat::Vtt.as_str(), "vtt");
219 /// assert_eq!(OutputSubtitleFormat::Sub.as_str(), "sub");
220 /// ```
221 pub fn as_str(&self) -> &'static str {
222 match self {
223 OutputSubtitleFormat::Srt => "srt",
224 OutputSubtitleFormat::Ass => "ass",
225 OutputSubtitleFormat::Vtt => "vtt",
226 OutputSubtitleFormat::Sub => "sub",
227 }
228 }
229
230 /// Returns the file extension for this format including the dot prefix.
231 ///
232 /// This method provides the standard file extension used for each
233 /// subtitle format, which is useful for generating output filenames.
234 ///
235 /// # Examples
236 ///
237 /// ```rust
238 /// use subx_cli::cli::OutputSubtitleFormat;
239 ///
240 /// assert_eq!(OutputSubtitleFormat::Srt.file_extension(), ".srt");
241 /// assert_eq!(OutputSubtitleFormat::Ass.file_extension(), ".ass");
242 /// assert_eq!(OutputSubtitleFormat::Vtt.file_extension(), ".vtt");
243 /// assert_eq!(OutputSubtitleFormat::Sub.file_extension(), ".sub");
244 /// ```
245 pub fn file_extension(&self) -> &'static str {
246 match self {
247 OutputSubtitleFormat::Srt => ".srt",
248 OutputSubtitleFormat::Ass => ".ass",
249 OutputSubtitleFormat::Vtt => ".vtt",
250 OutputSubtitleFormat::Sub => ".sub",
251 }
252 }
253}
254
255impl std::fmt::Display for OutputSubtitleFormat {
256 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
257 write!(f, "{}", self.as_str())
258 }
259}
260// Test parameter parsing behavior
261#[cfg(test)]
262mod tests_parse {
263 use super::*;
264 use crate::cli::{Cli, Commands};
265 use clap::Parser;
266 use std::path::PathBuf;
267
268 #[test]
269 fn test_convert_args_default_values() {
270 let cli = Cli::try_parse_from(&["subx-cli", "convert", "in_path"]).unwrap();
271 let args = match cli.command {
272 Commands::Convert(c) => c,
273 _ => panic!("Expected Convert command"),
274 };
275 assert_eq!(args.input, Some(PathBuf::from("in_path")));
276 assert!(args.input_paths.is_empty());
277 assert!(!args.recursive);
278 assert_eq!(args.format, None);
279 assert_eq!(args.output, None);
280 assert!(!args.keep_original);
281 assert_eq!(args.encoding, "utf-8");
282 }
283
284 #[test]
285 fn test_convert_args_parsing() {
286 let cli = Cli::try_parse_from(&[
287 "subx-cli",
288 "convert",
289 "in",
290 "--format",
291 "vtt",
292 "--output",
293 "out",
294 "--keep-original",
295 "--encoding",
296 "gbk",
297 ])
298 .unwrap();
299 let args = match cli.command {
300 Commands::Convert(c) => c,
301 _ => panic!("Expected Convert command"),
302 };
303 assert_eq!(args.input, Some(PathBuf::from("in")));
304 assert!(args.input_paths.is_empty());
305 assert!(!args.recursive);
306 assert_eq!(args.format.unwrap(), OutputSubtitleFormat::Vtt);
307 assert_eq!(args.output, Some(PathBuf::from("out")));
308 assert!(args.keep_original);
309 assert_eq!(args.encoding, "gbk");
310 }
311}