voirs_cli/commands/batch/
mod.rs1use crate::GlobalOptions;
7use std::path::{Path, PathBuf};
8use voirs_sdk::config::AppConfig;
9use voirs_sdk::{AudioFormat, QualityLevel, Result};
10
11pub mod files;
12pub mod parallel;
13pub mod resume;
14pub mod templates;
15
16#[derive(Debug, Clone)]
18pub struct BatchConfig {
19 pub input_path: PathBuf,
21 pub output_dir: PathBuf,
23 pub workers: usize,
25 pub quality: QualityLevel,
27 pub speaking_rate: f32,
29 pub pitch: f32,
31 pub volume: f32,
33 pub format: AudioFormat,
35 pub enable_resume: bool,
37 pub max_retries: u32,
39}
40
41impl Default for BatchConfig {
42 fn default() -> Self {
43 Self {
44 input_path: PathBuf::new(),
45 output_dir: PathBuf::new(),
46 workers: num_cpus::get(),
47 quality: QualityLevel::High,
48 speaking_rate: 1.0,
49 pitch: 0.0,
50 volume: 0.0,
51 format: AudioFormat::Wav,
52 enable_resume: true,
53 max_retries: 3,
54 }
55 }
56}
57
58pub struct BatchProcessArgs<'a> {
60 pub input: &'a Path,
61 pub output_dir: Option<&'a Path>,
62 pub workers: Option<usize>,
63 pub quality: QualityLevel,
64 pub rate: f32,
65 pub pitch: f32,
66 pub volume: f32,
67 pub resume: bool,
68}
69
70pub async fn run_batch_process(
72 args: BatchProcessArgs<'_>,
73 config: &AppConfig,
74 global: &GlobalOptions,
75) -> Result<()> {
76 let mut batch_config = BatchConfig {
78 input_path: args.input.to_path_buf(),
79 output_dir: args.output_dir.map(|p| p.to_path_buf()).unwrap_or_else(|| {
80 args.input
81 .parent()
82 .unwrap_or(std::path::Path::new("."))
83 .to_path_buf()
84 }),
85 workers: args.workers.unwrap_or_else(num_cpus::get),
86 quality: args.quality,
87 speaking_rate: args.rate,
88 pitch: args.pitch,
89 volume: args.volume,
90 format: AudioFormat::Wav, enable_resume: args.resume,
92 max_retries: 3,
93 };
94
95 std::fs::create_dir_all(&batch_config.output_dir)?;
97
98 if !global.quiet {
99 println!("Batch Processing Configuration:");
100 println!("==============================");
101 println!("Input: {}", batch_config.input_path.display());
102 println!("Output: {}", batch_config.output_dir.display());
103 println!("Workers: {}", batch_config.workers);
104 println!("Quality: {:?}", batch_config.quality);
105 println!("Resume: {}", batch_config.enable_resume);
106 println!();
107 }
108
109 if batch_config.input_path.is_file() {
111 files::process_file(&batch_config, config, global).await
112 } else if batch_config.input_path.is_dir() {
113 files::process_directory(&batch_config, config, global).await
114 } else {
115 Err(voirs_sdk::VoirsError::config_error(format!(
116 "Input path does not exist: {}",
117 batch_config.input_path.display()
118 )))
119 }
120}
121
122pub fn get_supported_extensions() -> Vec<&'static str> {
124 vec!["txt", "csv", "json", "jsonl"]
125}
126
127pub fn is_supported_extension(path: &Path) -> bool {
129 if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
130 get_supported_extensions().contains(&ext.to_lowercase().as_str())
131 } else {
132 false
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn test_batch_config_default() {
142 let config = BatchConfig::default();
143 assert!(config.workers > 0);
144 assert_eq!(config.quality, QualityLevel::High);
145 assert!(config.enable_resume);
146 }
147
148 #[test]
149 fn test_get_supported_extensions() {
150 let extensions = get_supported_extensions();
151 assert!(extensions.contains(&"txt"));
152 assert!(extensions.contains(&"csv"));
153 assert!(extensions.contains(&"json"));
154 }
155
156 #[test]
157 fn test_is_supported_extension() {
158 assert!(is_supported_extension(&PathBuf::from("test.txt")));
159 assert!(is_supported_extension(&PathBuf::from("data.csv")));
160 assert!(is_supported_extension(&PathBuf::from("config.json")));
161 assert!(!is_supported_extension(&PathBuf::from("image.png")));
162 assert!(!is_supported_extension(&PathBuf::from("noextension")));
163 }
164}