aoiandroid/IndexTTS-Rust
01
1//! IndexTTS CLI - High-performance Text-to-Speech in Rust2//!3//! Command-line interface for IndexTTS synthesizer4 5use clap::{Parser, Subcommand};6use indextts::{7 pipeline::{IndexTTS, SynthesisOptions},8 Config, Result,9};10use std::path::PathBuf;11 12#[derive(Parser)]13#[command(14 name = "indextts",15 about = "High-performance Text-to-Speech engine in Rust",16 version,17 author18)]19struct Cli {20 #[command(subcommand)]21 command: Commands,22}23 24#[derive(Subcommand)]25enum Commands {26 /// Synthesize speech from text27 Synthesize {28 /// Text to synthesize29 #[arg(short, long)]30 text: String,31 32 /// Speaker reference audio file33 #[arg(short = 'v', long)]34 voice: PathBuf,35 36 /// Output audio file path37 #[arg(short, long, default_value = "output.wav")]38 output: PathBuf,39 40 /// Configuration file path41 #[arg(short, long)]42 config: Option<PathBuf>,43 44 /// Model directory45 #[arg(short, long, default_value = "models")]46 model_dir: PathBuf,47 48 /// Emotion vector (comma-separated, 8 values 0-1)49 #[arg(long)]50 emotion: Option<String>,51 52 /// Emotion strength (0-1)53 #[arg(long, default_value = "1.0")]54 emotion_alpha: f32,55 56 /// Top-k sampling parameter57 #[arg(long, default_value = "50")]58 top_k: usize,59 60 /// Top-p sampling parameter61 #[arg(long, default_value = "0.95")]62 top_p: f32,63 64 /// Repetition penalty65 #[arg(long, default_value = "1.1")]66 repetition_penalty: f32,67 68 /// Use FP16 inference69 #[arg(long)]70 fp16: bool,71 72 /// Device (cpu, cuda:0, etc.)73 #[arg(short, long, default_value = "cpu")]74 device: String,75 },76 77 /// Synthesize from a text file78 SynthesizeFile {79 /// Input text file80 #[arg(short, long)]81 input: PathBuf,82 83 /// Speaker reference audio file84 #[arg(short = 'v', long)]85 voice: PathBuf,86 87 /// Output audio file path88 #[arg(short, long, default_value = "output.wav")]89 output: PathBuf,90 91 /// Configuration file path92 #[arg(short, long)]93 config: Option<PathBuf>,94 95 /// Model directory96 #[arg(short, long, default_value = "models")]97 model_dir: PathBuf,98 99 /// Silence between segments (milliseconds)100 #[arg(long, default_value = "200")]101 silence_ms: u32,102 },103 104 /// Generate default configuration file105 InitConfig {106 /// Output path for config file107 #[arg(short, long, default_value = "config.yaml")]108 output: PathBuf,109 },110 111 /// Show information about the system112 Info,113 114 /// Run benchmarks115 Benchmark {116 /// Number of iterations117 #[arg(short, long, default_value = "10")]118 iterations: usize,119 },120}121 122fn main() -> Result<()> {123 // Initialize logger124 env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();125 126 let cli = Cli::parse();127 128 match cli.command {129 Commands::Synthesize {130 text,131 voice,132 output,133 config,134 model_dir,135 emotion,136 emotion_alpha,137 top_k,138 top_p,139 repetition_penalty,140 fp16: _,141 device: _,142 } => {143 log::info!("IndexTTS Synthesizer");144 log::info!("====================");145 146 // Load or create config147 let cfg = if let Some(config_path) = config {148 Config::load(config_path)?149 } else {150 let mut cfg = Config::default();151 cfg.model_dir = model_dir;152 cfg153 };154 155 // Create TTS instance156 let tts = IndexTTS::new(cfg)?;157 158 // Parse emotion vector159 let emotion_vec = emotion.map(|s| {160 s.split(',')161 .filter_map(|v| v.trim().parse::<f32>().ok())162 .collect::<Vec<f32>>()163 });164 165 // Create synthesis options166 let options = SynthesisOptions {167 emotion_vector: emotion_vec,168 emotion_alpha,169 sampling: indextts::model::SamplingStrategy::TopKP { k: top_k, p: top_p },170 repetition_penalty,171 ..Default::default()172 };173 174 // Synthesize175 log::info!("Text: {}", &text[..text.len().min(100)]);176 log::info!("Voice: {}", voice.display());177 log::info!("Output: {}", output.display());178 179 let result = tts.synthesize_to_file(180 &text,181 voice.to_str().unwrap(),182 output.to_str().unwrap(),183 &options,184 )?;185 186 log::info!("Duration: {}", result.duration_formatted());187 log::info!("Processing time: {:.2}s", result.processing_time);188 log::info!("Real-time factor: {:.3}x", result.rtf);189 190 println!("✓ Synthesis complete: {}", output.display());191 }192 193 Commands::SynthesizeFile {194 input,195 voice,196 output,197 config,198 model_dir,199 silence_ms,200 } => {201 log::info!("IndexTTS File Synthesizer");202 log::info!("==========================");203 204 // Read text file205 let text = std::fs::read_to_string(&input)?;206 207 // Load or create config208 let cfg = if let Some(config_path) = config {209 Config::load(config_path)?210 } else {211 let mut cfg = Config::default();212 cfg.model_dir = model_dir;213 cfg214 };215 216 // Create TTS instance217 let tts = IndexTTS::new(cfg)?;218 219 // Create synthesis options220 let options = SynthesisOptions {221 segment_silence_ms: silence_ms,222 ..Default::default()223 };224 225 // Synthesize226 log::info!("Input file: {}", input.display());227 log::info!("Text length: {} characters", text.len());228 229 let result = tts.synthesize_long(230 &text,231 voice.to_str().unwrap(),232 &options,233 )?;234 235 result.save(&output)?;236 237 log::info!("Duration: {}", result.duration_formatted());238 log::info!("Processing time: {:.2}s", result.processing_time);239 log::info!("Real-time factor: {:.3}x", result.rtf);240 241 println!("✓ Synthesis complete: {}", output.display());242 }243 244 Commands::InitConfig { output } => {245 log::info!("Creating default configuration...");246 247 let config = Config::default();248 config.save(&output)?;249 250 println!("✓ Configuration saved to: {}", output.display());251 }252 253 Commands::Info => {254 println!("IndexTTS - High-performance Text-to-Speech Engine");255 println!("==================================================");256 println!("Version: {}", indextts::VERSION);257 println!("Platform: {}", std::env::consts::OS);258 println!("Architecture: {}", std::env::consts::ARCH);259 println!();260 println!("Features:");261 println!(" - Multi-language support (Chinese, English, mixed)");262 println!(" - Zero-shot voice cloning");263 println!(" - 8-dimensional emotion control");264 println!(" - High-quality neural vocoding (BigVGAN)");265 println!(" - SIMD-optimized audio processing");266 println!(" - Parallel processing with Rayon");267 println!();268 println!("Sample Rate: {} Hz", indextts::SAMPLE_RATE);269 println!("Mel Bands: {}", indextts::N_MELS);270 println!("FFT Size: {}", indextts::N_FFT);271 println!("Hop Length: {}", indextts::HOP_LENGTH);272 println!();273 println!("CPU Cores: {}", num_cpus::get());274 println!("Physical Cores: {}", num_cpus::get_physical());275 }276 277 Commands::Benchmark { iterations } => {278 log::info!("Running benchmarks ({} iterations)...", iterations);279 280 // Benchmark mel-spectrogram computation281 benchmark_mel_spectrogram(iterations);282 283 // Benchmark tokenization284 benchmark_tokenization(iterations);285 286 // Benchmark vocoder287 benchmark_vocoder(iterations);288 289 println!("✓ Benchmarks complete");290 }291 }292 293 Ok(())294}295 296fn benchmark_mel_spectrogram(iterations: usize) {297 use indextts::audio::{mel_spectrogram, AudioConfig};298 use std::time::Instant;299 300 println!("\nMel-Spectrogram Benchmark");301 println!("-------------------------");302 303 let config = AudioConfig::default();304 let num_samples = config.sample_rate as usize; // 1 second of audio305 let signal: Vec<f32> = (0..num_samples)306 .map(|i| (i as f32 * 0.01).sin())307 .collect();308 309 let start = Instant::now();310 for _ in 0..iterations {311 let _ = mel_spectrogram(&signal, &config);312 }313 let elapsed = start.elapsed();314 315 let per_iter = elapsed.as_secs_f32() / iterations as f32;316 println!(" Signal length: {} samples ({:.2}s)", num_samples, num_samples as f32 / config.sample_rate as f32);317 println!(" Iterations: {}", iterations);318 println!(" Total time: {:.3}s", elapsed.as_secs_f32());319 println!(" Per iteration: {:.3}ms", per_iter * 1000.0);320 println!(" Throughput: {:.1}x real-time", 1.0 / per_iter);321}322 323fn benchmark_tokenization(iterations: usize) {324 use indextts::text::{TextNormalizer, TextTokenizer, TokenizerConfig};325 use std::time::Instant;326 327 println!("\nTokenization Benchmark");328 println!("----------------------");329 330 let normalizer = TextNormalizer::new();331 let tokenizer = TextTokenizer::new(TokenizerConfig::default()).unwrap();332 333 let test_texts = vec![334 "Hello world, this is a test of the text-to-speech system.",335 "The quick brown fox jumps over the lazy dog.",336 "你好世界,这是一个测试。",337 "Mixed language: Hello 世界 and 你好 world.",338 ];339 340 let start = Instant::now();341 for _ in 0..iterations {342 for text in &test_texts {343 let normalized = normalizer.normalize(text).unwrap();344 let _tokens = tokenizer.encode(&normalized).unwrap();345 }346 }347 let elapsed = start.elapsed();348 349 let total_chars: usize = test_texts.iter().map(|t| t.len()).sum();350 let per_iter = elapsed.as_secs_f32() / iterations as f32;351 println!(" Texts: {}", test_texts.len());352 println!(" Total characters: {}", total_chars);353 println!(" Iterations: {}", iterations);354 println!(" Total time: {:.3}s", elapsed.as_secs_f32());355 println!(" Per iteration: {:.3}ms", per_iter * 1000.0);356 println!(357 " Throughput: {:.0} chars/sec",358 (total_chars * iterations) as f32 / elapsed.as_secs_f32()359 );360}361 362fn benchmark_vocoder(iterations: usize) {363 use indextts::vocoder::{create_bigvgan_22k, Vocoder};364 use ndarray::Array2;365 use std::time::Instant;366 367 println!("\nVocoder Benchmark");368 println!("-----------------");369 370 let vocoder = create_bigvgan_22k();371 let num_frames = 100; // ~2.5 seconds of audio372 let mel = Array2::zeros((80, num_frames));373 374 let start = Instant::now();375 for _ in 0..iterations {376 let _ = vocoder.synthesize(&mel);377 }378 let elapsed = start.elapsed();379 380 let audio_duration = num_frames as f32 * vocoder.hop_length() as f32 / vocoder.sample_rate() as f32;381 let per_iter = elapsed.as_secs_f32() / iterations as f32;382 println!(" Mel frames: {}", num_frames);383 println!(" Audio duration: {:.2}s", audio_duration);384 println!(" Iterations: {}", iterations);385 println!(" Total time: {:.3}s", elapsed.as_secs_f32());386 println!(" Per iteration: {:.3}ms", per_iter * 1000.0);387 println!(" RTF: {:.3}x", per_iter / audio_duration);388}389 