RegaLabs-TTS / infer.py
shahryyar's picture
Publish Sorani LLM adapter, fix Space default prompt, scrub blocklist terms from docs
5da3b02
Raw
History Blame Contribute Delete
4.61 kB
#!/usr/bin/env python3
"""RegaLabs-TTS: Direct Hugging Face Sorani Text-to-Speech Inference Script."""
import argparse
import sys
import os
from pathlib import Path
import torch
import soundfile as sf
def parse_args():
parser = argparse.ArgumentParser(description="RegaLabs-TTS Sorani Speech Synthesis")
parser.add_argument("--text", required=True, help="Sorani text to synthesize")
parser.add_argument("--prompt-wav", required=True, help="Path to reference audio WAV")
parser.add_argument("--prompt-text", required=True, help="Transcript of reference audio prompt")
parser.add_argument("--flow-checkpoint", default=str(Path(__file__).parent / "cosyvoice3_sorani_flow_best_step2300.pt"), help="Path to flow checkpoint")
parser.add_argument("--adapter", default=str(Path(__file__).parent / "cosyvoice3_sorani_lora_refined_best.pt"), help="Path to the Sorani LLM LoRA adapter (.pt)")
parser.add_argument("--base-model", default="FunAudioLLM/Fun-CosyVoice3-0.5B-2512", help="Hugging Face base model ID")
parser.add_argument("--out", default="output_sorani.wav", help="Output WAV path")
parser.add_argument("--cosyvoice-repo", default=os.environ.get("COSYVOICE_REPO", "./CosyVoice"), help="Path to cloned CosyVoice repo")
return parser.parse_args()
def main():
args = parse_args()
repo = Path(args.cosyvoice_repo).resolve()
if not repo.exists():
print(f"Error: CosyVoice engine directory not found at {repo}.", file=sys.stderr)
print("Please clone CosyVoice: git clone --recursive https://github.com/FunAudioLLM/CosyVoice.git", file=sys.stderr)
sys.exit(1)
sys.path.insert(0, str(repo))
matcha_path = repo / "third_party" / "Matcha-TTS"
if matcha_path.exists():
sys.path.insert(0, str(matcha_path))
sys.path.insert(0, str(Path(__file__).parent))
from sorani.frontend import normalize_sorani_text
from sorani.censor import verify_checkpoint
from cosyvoice.cli.cosyvoice import CosyVoice3
print(f"Loading base model: {args.base_model}...")
cosyvoice = CosyVoice3(args.base_model, fp16=torch.cuda.is_available())
adapter = Path(args.adapter)
if adapter.exists():
from cosyvoice.utils.lora import inject_lora, load_lora_state_dict
llm = cosyvoice.model.llm
target_count = inject_lora(llm, rank=16, alpha=32.0, dropout=0.05)
load_lora_state_dict(
llm, torch.load(adapter, map_location="cpu", weights_only=False)
)
llm.to(cosyvoice.model.device).eval()
print(f"Loaded RegaLabs-TTS Sorani LLM adapter ({target_count} projections).")
elif str(adapter) != str(Path(__file__).parent / "cosyvoice3_sorani_lora_refined_best.pt"):
print(f"Warning: adapter not found at {adapter}; continuing without LLM adapter.", file=sys.stderr)
verify_checkpoint(args.flow_checkpoint)
print(f"Loading RegaLabs-TTS Sorani flow checkpoint: {args.flow_checkpoint}...")
flow_state = torch.load(args.flow_checkpoint, map_location="cpu", weights_only=False)
if isinstance(flow_state, dict):
for key in ("model", "state_dict", "flow"):
nested = flow_state.get(key)
if isinstance(nested, dict):
flow_state = nested
break
flow_state = {k: v for k, v in flow_state.items() if isinstance(k, str) and isinstance(v, torch.Tensor)}
cosyvoice.model.flow.load_state_dict(flow_state, strict=False)
cosyvoice.model.flow.to(cosyvoice.model.device).eval()
print(f"Synthesizing Sorani text: '{args.text}'...")
clean_text = normalize_sorani_text(args.text)
clean_prompt_text = normalize_sorani_text(args.prompt_text)
if "<|endofprompt|>" not in clean_prompt_text:
clean_prompt_text = f"You are a helpful assistant.<|endofprompt|> {clean_prompt_text}"
pieces = []
with torch.inference_mode():
for output in cosyvoice.inference_zero_shot(
clean_text,
clean_prompt_text,
args.prompt_wav,
stream=False,
text_frontend=False,
):
pieces.append(output["tts_speech"].detach().cpu())
if not pieces:
raise RuntimeError("No audio output returned.")
speech = torch.cat(pieces, dim=1).squeeze(0).numpy()
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
sf.write(out_path, speech, cosyvoice.sample_rate, subtype="PCM_16")
print(f"✅ Audio generated successfully: {out_path}")
if __name__ == "__main__":
main()