use crate::GlobalOptions;
use std::path::{Path, PathBuf};
use voirs_sdk::config::AppConfig;
use voirs_sdk::{AudioFormat, QualityLevel, Result};
pub mod files;
pub mod parallel;
pub mod resume;
pub mod templates;
#[derive(Debug, Clone)]
pub struct BatchConfig {
pub input_path: PathBuf,
pub output_dir: PathBuf,
pub workers: usize,
pub quality: QualityLevel,
pub speaking_rate: f32,
pub pitch: f32,
pub volume: f32,
pub format: AudioFormat,
pub enable_resume: bool,
pub max_retries: u32,
}
impl Default for BatchConfig {
fn default() -> Self {
Self {
input_path: PathBuf::new(),
output_dir: PathBuf::new(),
workers: num_cpus::get(),
quality: QualityLevel::High,
speaking_rate: 1.0,
pitch: 0.0,
volume: 0.0,
format: AudioFormat::Wav,
enable_resume: true,
max_retries: 3,
}
}
}
pub struct BatchProcessArgs<'a> {
pub input: &'a Path,
pub output_dir: Option<&'a Path>,
pub workers: Option<usize>,
pub quality: QualityLevel,
pub rate: f32,
pub pitch: f32,
pub volume: f32,
pub resume: bool,
}
pub async fn run_batch_process(
args: BatchProcessArgs<'_>,
config: &AppConfig,
global: &GlobalOptions,
) -> Result<()> {
let mut batch_config = BatchConfig {
input_path: args.input.to_path_buf(),
output_dir: args.output_dir.map(|p| p.to_path_buf()).unwrap_or_else(|| {
args.input
.parent()
.unwrap_or(std::path::Path::new("."))
.to_path_buf()
}),
workers: args.workers.unwrap_or_else(num_cpus::get),
quality: args.quality,
speaking_rate: args.rate,
pitch: args.pitch,
volume: args.volume,
format: AudioFormat::Wav, enable_resume: args.resume,
max_retries: 3,
};
std::fs::create_dir_all(&batch_config.output_dir)?;
if !global.quiet {
println!("Batch Processing Configuration:");
println!("==============================");
println!("Input: {}", batch_config.input_path.display());
println!("Output: {}", batch_config.output_dir.display());
println!("Workers: {}", batch_config.workers);
println!("Quality: {:?}", batch_config.quality);
println!("Resume: {}", batch_config.enable_resume);
println!();
}
if batch_config.input_path.is_file() {
files::process_file(&batch_config, config, global).await
} else if batch_config.input_path.is_dir() {
files::process_directory(&batch_config, config, global).await
} else {
Err(voirs_sdk::VoirsError::config_error(format!(
"Input path does not exist: {}",
batch_config.input_path.display()
)))
}
}
pub fn get_supported_extensions() -> Vec<&'static str> {
vec!["txt", "csv", "json", "jsonl"]
}
pub fn is_supported_extension(path: &Path) -> bool {
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
get_supported_extensions().contains(&ext.to_lowercase().as_str())
} else {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_config_default() {
let config = BatchConfig::default();
assert!(config.workers > 0);
assert_eq!(config.quality, QualityLevel::High);
assert!(config.enable_resume);
}
#[test]
fn test_get_supported_extensions() {
let extensions = get_supported_extensions();
assert!(extensions.contains(&"txt"));
assert!(extensions.contains(&"csv"));
assert!(extensions.contains(&"json"));
}
#[test]
fn test_is_supported_extension() {
assert!(is_supported_extension(&PathBuf::from("test.txt")));
assert!(is_supported_extension(&PathBuf::from("data.csv")));
assert!(is_supported_extension(&PathBuf::from("config.json")));
assert!(!is_supported_extension(&PathBuf::from("image.png")));
assert!(!is_supported_extension(&PathBuf::from("noextension")));
}
}