CoolFace
Modelpublic

aoiandroid/IndexTTS-Rust

sourceHugging Facemitupdated 4mo agoView on Hugging Face
0likes1downloads
main.rs389 linesDownload Raw Back to src
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