subx_cli/
lib.rs

1//! SubX: Intelligent Subtitle Processing Library
2//!
3//! SubX is a comprehensive Rust library for intelligent subtitle file processing,
4//! featuring AI-powered matching, format conversion, audio synchronization,
5//! and advanced encoding detection capabilities.
6//!
7//! # Key Features
8//!
9//! - **AI-Powered Matching**: Intelligent subtitle file matching and renaming
10//! - **Format Conversion**: Support for multiple subtitle formats (SRT, ASS, VTT, etc.)
11//! - **Audio Synchronization**: Advanced audio-subtitle timing adjustment
12//! - **Encoding Detection**: Automatic character encoding detection and conversion
13//! - **Parallel Processing**: High-performance batch operations
14//! - **Configuration Management**: Flexible multi-source configuration system
15//!
16//! # Architecture Overview
17//!
18//! The library is organized into several key modules:
19//!
20//! - [`cli`] - Command-line interface and argument parsing
21//! - [`commands`] - Implementation of all SubX commands
22//! - [`config`] - Configuration management and validation
23//! - [`core`] - Core processing engines (formats, matching, sync)
24//! - [`error`] - Comprehensive error handling system
25//! - [`services`] - External service integrations (AI, audio processing)
26//!
27//! # Quick Start
28//!
29//! ```rust,no_run
30//! use subx_cli::config::{TestConfigService, ConfigService};
31//!
32//! // Create a configuration service
33//! let config_service = TestConfigService::with_defaults();
34//! let config = config_service.config();
35//!
36//! // Use the configuration for processing...
37//! ```
38//!
39//! # Error Handling
40//!
41//! All operations return a [`Result<T>`] type that wraps [`error::SubXError`]:
42//!
43//! ```rust
44//! use subx_cli::{Result, error::SubXError};
45//!
46//! fn example_operation() -> Result<String> {
47//!     // This could fail with various error types
48//!     Err(SubXError::config("Missing configuration"))
49//! }
50//! ```
51//!
52//! # Configuration
53//!
54//! SubX supports dependency injection-based configuration:
55//!
56//! ```rust,no_run
57//! use subx_cli::config::{TestConfigService, Config};
58//!
59//! // Create configuration service with AI settings
60//! let config_service = TestConfigService::with_ai_settings("openai", "gpt-4.1");
61//! let config = config_service.config();
62//!
63//! // Access configuration values
64//! println!("AI Provider: {}", config.ai.provider);
65//! println!("AI Model: {}", config.ai.model);
66//! ```
67//!
68//! # Performance Considerations
69//!
70//! - Use [`core::parallel`] for batch operations on large file sets
71//! - Configure appropriate cache settings for repeated operations
72//! - Consider memory usage when processing large audio files
73//!
74//! # Thread Safety
75//!
76//! The library is designed to be thread-safe where appropriate:
77//! - Configuration manager uses `Arc<RwLock<T>>` for shared state
78//! - File operations include rollback capabilities for atomicity
79//! - Parallel processing uses safe concurrency patterns
80//!
81//! # Feature Flags
82//!
83//! SubX supports several optional features:
84//! ```text
85//! - ai - AI service integrations (default)
86//! - audio - Audio processing capabilities (default)  
87//! - parallel - Parallel processing support (default)
88//! ```
89#![allow(
90    clippy::new_without_default,
91    clippy::manual_clamp,
92    clippy::useless_vec,
93    clippy::items_after_test_module,
94    clippy::needless_borrow
95)]
96
97/// Library version string.
98///
99/// This constant provides the current version of the SubX library,
100/// automatically populated from `Cargo.toml` at compile time.
101///
102/// # Examples
103///
104/// ```rust
105/// use subx_cli::VERSION;
106///
107/// println!("SubX version: {}", VERSION);
108/// ```
109pub const VERSION: &str = env!("CARGO_PKG_VERSION");
110
111pub mod cli;
112pub mod commands;
113pub mod config;
114pub use config::Config;
115// Re-export new configuration service system
116pub use config::{
117    ConfigService, EnvironmentProvider, ProductionConfigService, SystemEnvironmentProvider,
118    TestConfigBuilder, TestConfigService, TestEnvironmentProvider,
119};
120pub mod core;
121pub mod error;
122/// Convenient type alias for `Result<T, SubXError>`.
123///
124/// This type alias simplifies error handling throughout the SubX library
125/// by providing a default error type for all fallible operations.
126pub type Result<T> = error::SubXResult<T>;
127
128pub mod services;
129
130/// Main application structure with dependency injection support.
131///
132/// The `App` struct provides a programmatic interface to SubX functionality,
133/// designed for embedding SubX in other Rust applications or for advanced
134/// use cases requiring fine-grained control over configuration and execution.
135///
136/// # Use Cases
137///
138/// - **Embedding**: Use SubX as a library component in larger applications
139/// - **Testing**: Programmatic testing of SubX functionality with custom configurations
140/// - **Automation**: Scripted execution of SubX operations without shell commands
141/// - **Custom Workflows**: Building complex workflows that combine multiple SubX operations
142///
143/// # vs CLI Interface
144///
145/// | Feature | CLI (`subx` command) | App (Library API) |
146/// |---------|---------------------|-------------------|
147/// | Usage | Command line tool | Embedded in Rust code |
148/// | Config | Files + Environment | Programmatic injection |
149/// | Output | Terminal/stdout | Programmatic control |
150/// | Error Handling | Exit codes | Result types |
151///
152/// # Examples
153///
154/// ## Basic Usage
155///
156/// ```rust,no_run
157/// use subx_cli::{App, config::ProductionConfigService};
158/// use std::sync::Arc;
159///
160/// # async fn example() -> subx_cli::Result<()> {
161/// let config_service = Arc::new(ProductionConfigService::new()?);
162/// let app = App::new(config_service);
163///
164/// // Execute operations programmatically
165/// app.match_files("/movies", true).await?; // dry run
166/// app.convert_files("/subs", "srt", Some("/output")).await?;
167/// # Ok(())
168/// # }
169/// ```
170///
171/// ## With Custom Configuration
172///
173/// ```rust,no_run
174/// use subx_cli::{App, config::{TestConfigService, Config}};
175/// use std::sync::Arc;
176///
177/// # async fn example() -> subx_cli::Result<()> {
178/// let mut config_service = TestConfigService::with_ai_settings("openai", "gpt-4");
179///
180/// let app = App::new(Arc::new(config_service));
181/// app.match_files("/path", false).await?;
182/// # Ok(())
183/// # }
184/// ```
185pub struct App {
186    config_service: std::sync::Arc<dyn config::ConfigService>,
187}
188
189impl App {
190    /// Create a new application instance with the provided configuration service.
191    ///
192    /// # Arguments
193    ///
194    /// * `config_service` - The configuration service to use
195    ///
196    /// # Examples
197    ///
198    /// ```rust,no_run
199    /// use subx_cli::{App, config::TestConfigService};
200    /// use std::sync::Arc;
201    ///
202    /// let config_service = Arc::new(TestConfigService::with_defaults());
203    /// let app = App::new(config_service);
204    /// ```
205    pub fn new(config_service: std::sync::Arc<dyn config::ConfigService>) -> Self {
206        Self { config_service }
207    }
208
209    /// Create a new application instance with the production configuration service.
210    ///
211    /// This is the default way to create an application instance for production use.
212    ///
213    /// # Examples
214    ///
215    /// ```rust,no_run
216    /// use subx_cli::App;
217    ///
218    /// # async fn example() -> subx_cli::Result<()> {
219    /// let app = App::new_with_production_config()?;
220    /// // Ready to use with production configuration
221    /// # Ok(())
222    /// # }
223    /// ```
224    ///
225    /// # Errors
226    ///
227    /// Returns an error if the production configuration service cannot be created.
228    pub fn new_with_production_config() -> Result<Self> {
229        let config_service = std::sync::Arc::new(config::ProductionConfigService::new()?);
230        Ok(Self::new(config_service))
231    }
232
233    /// Run the application with command-line argument parsing.
234    ///
235    /// This method provides a programmatic way to run SubX with CLI-style
236    /// arguments, useful for embedding SubX in other Rust applications.
237    ///
238    /// # Examples
239    ///
240    /// ```rust,no_run
241    /// use subx_cli::{App, config::ProductionConfigService};
242    /// use std::sync::Arc;
243    ///
244    /// # async fn example() -> subx_cli::Result<()> {
245    /// let config_service = Arc::new(ProductionConfigService::new()?);
246    /// let app = App::new(config_service);
247    ///
248    /// // This parses std::env::args() just like the CLI
249    /// app.run().await?;
250    /// # Ok(())
251    /// # }
252    /// ```
253    ///
254    /// # Errors
255    ///
256    /// Returns an error if command execution fails.
257    pub async fn run(&self) -> Result<()> {
258        let cli = <cli::Cli as clap::Parser>::parse();
259        self.handle_command(cli.command).await
260    }
261
262    /// Handle a specific command with the current configuration.
263    ///
264    /// This method allows programmatic execution of specific SubX commands
265    /// without parsing command-line arguments.
266    ///
267    /// # Examples
268    ///
269    /// ```rust,no_run
270    /// use subx_cli::{App, cli::{Commands, MatchArgs}, config::TestConfigService};
271    /// use std::sync::Arc;
272    ///
273    /// # async fn example() -> subx_cli::Result<()> {
274    /// let config_service = Arc::new(TestConfigService::with_defaults());
275    /// let app = App::new(config_service);
276    ///
277    /// let match_args = MatchArgs {
278    ///     path: Some("/path/to/files".into()),
279    ///     input_paths: vec![],
280    ///     dry_run: true,
281    ///     confidence: 80,
282    ///     recursive: false,
283    ///     backup: false,
284    ///     copy: false,
285    ///     move_files: false,
286    /// };
287    ///
288    /// app.handle_command(Commands::Match(match_args)).await?;
289    /// # Ok(())
290    /// # }
291    /// ```
292    ///
293    /// # Arguments
294    ///
295    /// * `command` - The command to execute
296    ///
297    /// # Errors
298    ///
299    /// Returns an error if command execution fails.
300    pub async fn handle_command(&self, command: cli::Commands) -> Result<()> {
301        // Use the centralized dispatcher to eliminate code duplication
302        crate::commands::dispatcher::dispatch_command(command, self.config_service.clone()).await
303    }
304
305    /// Execute a match operation programmatically.
306    ///
307    /// This is a convenience method for programmatic usage without
308    /// needing to construct the Commands enum manually.
309    ///
310    /// # Examples
311    ///
312    /// ```rust,no_run
313    /// use subx_cli::{App, config::TestConfigService};
314    /// use std::sync::Arc;
315    ///
316    /// # async fn example() -> subx_cli::Result<()> {
317    /// let config_service = Arc::new(TestConfigService::with_defaults());
318    /// let app = App::new(config_service);
319    ///
320    /// // Match files programmatically
321    /// app.match_files("/path/to/files", true).await?; // dry_run = true
322    /// # Ok(())
323    /// # }
324    /// ```
325    ///
326    /// # Arguments
327    ///
328    /// * `input_path` - Path to the directory or file to process
329    /// * `dry_run` - Whether to perform a dry run (no actual changes)
330    ///
331    /// # Errors
332    ///
333    /// Returns an error if the match operation fails.
334    pub async fn match_files(&self, input_path: &str, dry_run: bool) -> Result<()> {
335        let args = cli::MatchArgs {
336            path: Some(input_path.into()),
337            input_paths: vec![],
338            dry_run,
339            confidence: 80,
340            recursive: false,
341            backup: false,
342            copy: false,
343            move_files: false,
344        };
345        self.handle_command(cli::Commands::Match(args)).await
346    }
347
348    /// Convert subtitle files programmatically.
349    ///
350    /// # Examples
351    ///
352    /// ```rust,no_run
353    /// use subx_cli::{App, config::TestConfigService};
354    /// use std::sync::Arc;
355    ///
356    /// # async fn example() -> subx_cli::Result<()> {
357    /// let config_service = Arc::new(TestConfigService::with_defaults());
358    /// let app = App::new(config_service);
359    ///
360    /// // Convert to SRT format
361    /// app.convert_files("/path/to/subtitles", "srt", Some("/output/path")).await?;
362    /// # Ok(())
363    /// # }
364    /// ```
365    ///
366    /// # Arguments
367    ///
368    /// * `input_path` - Path to subtitle files to convert
369    /// * `output_format` - Target format ("srt", "ass", "vtt", etc.)
370    /// * `output_path` - Optional output directory path
371    ///
372    /// # Errors
373    ///
374    /// Returns an error if the conversion fails.
375    pub async fn convert_files(
376        &self,
377        input_path: &str,
378        output_format: &str,
379        output_path: Option<&str>,
380    ) -> Result<()> {
381        let format = match output_format.to_lowercase().as_str() {
382            "srt" => cli::OutputSubtitleFormat::Srt,
383            "ass" => cli::OutputSubtitleFormat::Ass,
384            "vtt" => cli::OutputSubtitleFormat::Vtt,
385            "sub" => cli::OutputSubtitleFormat::Sub,
386            _ => {
387                return Err(error::SubXError::CommandExecution(format!(
388                    "Unsupported output format: {}. Supported formats: srt, ass, vtt, sub",
389                    output_format
390                )));
391            }
392        };
393
394        let args = cli::ConvertArgs {
395            input: Some(input_path.into()),
396            input_paths: vec![],
397            recursive: false,
398            format: Some(format),
399            output: output_path.map(Into::into),
400            keep_original: false,
401            encoding: "utf-8".to_string(),
402        };
403        self.handle_command(cli::Commands::Convert(args)).await
404    }
405
406    /// Synchronize subtitle files programmatically.
407    ///
408    /// # Examples
409    ///
410    /// ```rust,no_run
411    /// use subx_cli::{App, config::TestConfigService};
412    /// use std::sync::Arc;
413    ///
414    /// # async fn example() -> subx_cli::Result<()> {
415    /// let config_service = Arc::new(TestConfigService::with_defaults());
416    /// let app = App::new(config_service);
417    ///
418    /// // Synchronize using VAD method
419    /// app.sync_files("/path/to/video.mp4", "/path/to/subtitle.srt", "vad").await?;
420    /// # Ok(())
421    /// # }
422    /// ```
423    ///
424    /// # Arguments
425    ///
426    /// * `video_path` - Path to video file for audio analysis
427    /// * `subtitle_path` - Path to subtitle file to synchronize
428    /// * `method` - Synchronization method ("vad", "manual")
429    ///
430    /// # Errors
431    ///
432    /// Returns an error if synchronization fails.
433    pub async fn sync_files(
434        &self,
435        video_path: &str,
436        subtitle_path: &str,
437        method: &str,
438    ) -> Result<()> {
439        let sync_method = match method.to_lowercase().as_str() {
440            "vad" => Some(cli::SyncMethodArg::Vad),
441            "manual" => Some(cli::SyncMethodArg::Manual),
442            _ => {
443                return Err(error::SubXError::CommandExecution(format!(
444                    "Unsupported sync method: {}. Supported methods: vad, manual",
445                    method
446                )));
447            }
448        };
449
450        let args = cli::SyncArgs {
451            video: Some(video_path.into()),
452            subtitle: Some(subtitle_path.into()),
453            input_paths: vec![],
454            recursive: false,
455            offset: None,
456            method: sync_method,
457            window: 30,
458            vad_sensitivity: None,
459            output: None,
460            verbose: false,
461            dry_run: false,
462            force: false,
463            batch: false,
464            #[allow(deprecated)]
465            range: None,
466            #[allow(deprecated)]
467            threshold: None,
468        };
469        self.handle_command(cli::Commands::Sync(args)).await
470    }
471
472    /// Synchronize subtitle files with manual offset.
473    ///
474    /// # Examples
475    ///
476    /// ```rust,no_run
477    /// use subx_cli::{App, config::TestConfigService};
478    /// use std::sync::Arc;
479    ///
480    /// # async fn example() -> subx_cli::Result<()> {
481    /// let config_service = Arc::new(TestConfigService::with_defaults());
482    /// let app = App::new(config_service);
483    ///
484    /// // Apply +2.5 second offset to subtitles
485    /// app.sync_files_with_offset("/path/to/subtitle.srt", 2.5).await?;
486    /// # Ok(())
487    /// # }
488    /// ```
489    ///
490    /// # Arguments
491    ///
492    /// * `subtitle_path` - Path to subtitle file to synchronize
493    /// * `offset` - Time offset in seconds (positive delays, negative advances)
494    ///
495    /// # Errors
496    ///
497    /// Returns an error if synchronization fails.
498    pub async fn sync_files_with_offset(&self, subtitle_path: &str, offset: f32) -> Result<()> {
499        let args = cli::SyncArgs {
500            video: None,
501            subtitle: Some(subtitle_path.into()),
502            input_paths: vec![],
503            recursive: false,
504            offset: Some(offset),
505            method: None,
506            window: 30,
507            vad_sensitivity: None,
508            output: None,
509            verbose: false,
510            dry_run: false,
511            force: false,
512            batch: false,
513            #[allow(deprecated)]
514            range: None,
515            #[allow(deprecated)]
516            threshold: None,
517        };
518        self.handle_command(cli::Commands::Sync(args)).await
519    }
520
521    /// Get a reference to the configuration service.
522    ///
523    /// This allows access to the configuration service for testing or
524    /// advanced use cases.
525    pub fn config_service(&self) -> &std::sync::Arc<dyn config::ConfigService> {
526        &self.config_service
527    }
528
529    /// Get the current configuration.
530    ///
531    /// This is a convenience method that retrieves the configuration
532    /// from the configured service.
533    ///
534    /// # Errors
535    ///
536    /// Returns an error if configuration loading fails.
537    pub fn get_config(&self) -> Result<config::Config> {
538        self.config_service.get_config()
539    }
540}