''' GPT-SoVITS API - Direct call interface ''' import os import sys import random import torch import numpy as np now_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.append(now_dir) sys.path.append(os.path.dirname(now_dir)) from TTS_infer_pack.TTS import TTS, TTS_Config class GPTSoVITSAPI: def __init__(self, version="v2ProPlus", device=None, is_half=None): """ Initialize GPT-SoVITS API (same as WebUI) Args: version: Model version, options: v1, v2, v3, v4, v2Pro, v2ProPlus device: Running device, e.g. "cuda" or "cpu" is_half: Whether to use half precision """ self.version = version # Set device (same as WebUI) if device is not None: self.device = torch.device(device) else: self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # Set half precision (same as WebUI) if is_half is not None: self.is_half = is_half else: self.is_half = torch.cuda.is_available() # Initialize TTS exactly like WebUI self._init_tts() print(f"GPT-SoVITS API initialized") # print(f" Version: {self.version}") # print(f" Device: {self.device}") # print(f" Half precision: {self.is_half}") # print(f" GPT model: {self.tts.configs.t2s_weights_path}") # print(f" SoVITS model: {self.tts.configs.vits_weights_path}") def _init_tts(self): """Initialize TTS pipeline exactly like WebUI""" # Use config file (same as WebUI) self.tts_config = TTS_Config(os.path.join(now_dir, "configs/tts_infer.yaml")) self.tts_config.device = self.device self.tts_config.is_half = self.is_half self.tts_config.update_version(self.version) # Create TTS instance self.tts = TTS(self.tts_config) def generate( self, ref_wav_path, prompt_text, text, prompt_language="all_zh", text_language="all_zh", top_k=15, top_p=1, temperature=1, text_split_method="cut1", batch_size=20, speed_factor=1.0, split_bucket=True, seed=-1, keep_random=True, parallel_infer=True, repetition_penalty=1.35, sample_steps=32, super_sampling=False, output_path=None, **kwargs ): """ Generate speech from text (same parameters as WebUI) Args: ref_wav_path: Path to reference audio file prompt_text: Reference text (should match reference audio) text: Target text to synthesize prompt_language: Language of prompt text, options: all_zh, zh, ja, en text_language: Language of target text, options: all_zh, zh, ja, en top_k: Top-K sampling parameter top_p: Top-P sampling parameter temperature: Temperature for sampling text_split_method: Text split method, options: cut1, cut2, cut3 batch_size: Batch size for inference speed_factor: Speed factor (1.0 = normal) split_bucket: Whether to use bucket splitting seed: Random seed (-1 = random, or specific seed value) keep_random: Whether to keep random (True) or use fixed seed (False) parallel_infer: Whether to use parallel inference repetition_penalty: Repetition penalty sample_steps: Number of sampling steps super_sampling: Whether to use super sampling output_path: Optional output file path to save audio (e.g., "output.wav") Returns: (sample_rate, audio_array) tuple, or (None, None) on failure """ # Handle seed (same logic as WebUI) seed = -1 if keep_random else seed actual_seed = seed if seed not in [-1, "", None] else random.randint(0, 2**32 - 1) # Build inputs exactly like WebUI inputs = { "text": text, "text_lang": text_language, "ref_audio_path": ref_wav_path, "aux_ref_audio_paths": [], "prompt_text": prompt_text, "prompt_lang": prompt_language, "top_k": top_k, "top_p": top_p, "temperature": temperature, "text_split_method": text_split_method, "batch_size": batch_size, "speed_factor": float(speed_factor), "split_bucket": split_bucket, "return_fragment": False, "fragment_interval": 0.3, "seed": actual_seed, "parallel_infer": parallel_infer, "repetition_penalty": repetition_penalty, "sample_steps": sample_steps, "super_sampling": super_sampling, } # # Print parameters for debugging # print("=" * 60) # print("【API 推理参数】") # print(f" version: {self.version}") # print(f" text: {text[:50]}..." if len(text) > 50 else f" text: {text}") # print(f" text_lang: {text_language}") # print(f" ref_audio_path: {ref_wav_path}") # print(f" prompt_text: {prompt_text[:50]}..." if len(prompt_text) > 50 else f" prompt_text: {prompt_text}") # print(f" prompt_lang: {prompt_language}") # print(f" top_k: {top_k}") # print(f" top_p: {top_p}") # print(f" temperature: {temperature}") # print(f" text_split_method: {text_split_method}") # print(f" batch_size: {batch_size}") # print(f" speed_factor: {speed_factor}") # print(f" split_bucket: {split_bucket}") # print(f" seed: {actual_seed}") # print(f" keep_random: {keep_random}") # print(f" parallel_infer: {parallel_infer}") # print(f" repetition_penalty: {repetition_penalty}") # print(f" sample_steps: {sample_steps}") # print(f" super_sampling: {super_sampling}") # print("=" * 60) # Run inference (same as WebUI) result = None for item in self.tts.run(inputs): result = item if result is not None: # TTS.run() returns (sr, audio_array) tuple if isinstance(result, tuple) and len(result) >= 2: sr = result[0] audio_data = result[1] if isinstance(audio_data, np.ndarray): # Save audio if output_path is provided if output_path is not None: try: import soundfile as sf sf.write(output_path, audio_data, sr) print(f"Audio saved to: {output_path}") except Exception as e: print(f"Error saving audio: {e}") return sr, audio_data else: print(f"Warning: audio_data is {type(audio_data)}, not numpy array") return None, None else: print(f"Warning: result is {type(result)}, not a tuple") return None, None return None, None def save_audio(self, audio_array, sample_rate, output_path): """ Save audio array to file Args: audio_array: numpy array of audio data sample_rate: Sample rate output_path: Output file path Returns: output_path if successful, None otherwise """ try: import soundfile as sf sf.write(output_path, audio_array, sample_rate) print(f"Audio saved to: {output_path}") return output_path except Exception as e: print(f"Error saving audio: {e}") return None # Command line interface if __name__ == "__main__": import argparse parser = argparse.ArgumentParser(description="GPT-SoVITS API") parser.add_argument("--ref_wav", required=True, help="Path to reference audio") parser.add_argument("--prompt_text", required=True, help="Reference text") parser.add_argument("--text", required=True, help="Target text to synthesize") parser.add_argument("--prompt_lang", default="all_zh", help="Prompt language") parser.add_argument("--text_lang", default="all_zh", help="Text language") parser.add_argument("--output", default="output.wav", help="Output file path") parser.add_argument("--version", default="v2ProPlus", help="Model version") parser.add_argument("--seed", type=int, default=819407889, help="Random seed") args = parser.parse_args() # Initialize API api = GPTSoVITSAPI(version=args.version) # Generate audio sr, audio = api.generate( ref_wav_path=args.ref_wav, prompt_text=args.prompt_text, text=args.text, prompt_language=args.prompt_lang, text_language=args.text_lang, seed=args.seed ) # Save output if sr is not None and audio is not None: api.save_audio(audio, sr, args.output) print(f"\n✓ Successfully generated audio: {args.output}") else: print("\n✗ Failed to generate audio")