Spaces:
Running on Zero
Running on Zero
| """Text-to-speech (Piper). Returns the path to a .wav. Bilingual PT/EN. | |
| Piper runs on onnxruntime (no torch). Voices come from rhasspy/piper-voices. | |
| """ | |
| import os | |
| import tempfile | |
| import wave | |
| _REPO = "rhasspy/piper-voices" | |
| _VOICES = { | |
| "pt": os.environ.get("IRIS_TTS_VOICE_PT", "pt/pt_BR/faber/medium/pt_BR-faber-medium"), | |
| "en": os.environ.get("IRIS_TTS_VOICE_EN", "en/en_US/amy/medium/en_US-amy-medium"), | |
| } | |
| _cache = {} | |
| def _load(lang: str): | |
| if lang not in _cache: | |
| from huggingface_hub import hf_hub_download | |
| from piper import PiperVoice | |
| name = _VOICES.get(lang, _VOICES["pt"]) | |
| onnx = hf_hub_download(_REPO, f"{name}.onnx") | |
| conf = hf_hub_download(_REPO, f"{name}.onnx.json") | |
| _cache[lang] = PiperVoice.load(onnx, config_path=conf) | |
| return _cache[lang] | |
| def synthesize(text: str, lang: str = "pt") -> str | None: | |
| if not text or not text.strip(): | |
| return None | |
| voice = _load("en" if lang == "en" else "pt") | |
| chunks = list(voice.synthesize(text)) | |
| if not chunks: | |
| print(f"[tts] no audio for text: {text!r}", flush=True) | |
| return None | |
| path = tempfile.mktemp(suffix=".wav") | |
| with wave.open(path, "wb") as wf: | |
| wf.setnchannels(chunks[0].sample_channels) | |
| wf.setsampwidth(chunks[0].sample_width) | |
| wf.setframerate(chunks[0].sample_rate) | |
| for ch in chunks: | |
| wf.writeframes(ch.audio_int16_bytes) | |
| return path | |