Spaces:
Running
Running
| from __future__ import annotations | |
| import re | |
| import wave | |
| from functools import lru_cache | |
| from typing import Iterator | |
| import numpy as np | |
| from piper.config import SynthesisConfig | |
| from piper.voice import PiperVoice | |
| from config import CONFIG | |
| _SENTENCE_END = re.compile(r"(?<=[.!?…])\s+") | |
| # Markdown and list symbols Piper would otherwise read aloud. | |
| _SPEAK_STRIP = re.compile(r"[*_`#>]+|^\s*[-•]\s+", re.MULTILINE) | |
| def clean_text(text: str) -> str: | |
| """Strip markdown so the voice speaks words, not symbols.""" | |
| return re.sub(r"\s+", " ", _SPEAK_STRIP.sub("", text)).strip() | |
| def _voice(): | |
| """Load the Piper voice once and cache it.""" | |
| return PiperVoice.load(CONFIG.piper_path()) | |
| def _syn_config(): | |
| """Prosody knobs: slower and slightly varied reads warmer, less robotic.""" | |
| return SynthesisConfig( | |
| length_scale=CONFIG.tts_length_scale, | |
| noise_scale=CONFIG.tts_noise_scale, | |
| noise_w_scale=CONFIG.tts_noise_w, | |
| ) | |
| def synth(text: str) -> tuple[int, np.ndarray]: | |
| """Synthesize text to (sample_rate, int16 mono samples) for gr.Audio.""" | |
| voice = _voice() | |
| chunks = [ | |
| np.frombuffer(audio.audio_int16_bytes, dtype=np.int16) | |
| for audio in voice.synthesize(text, syn_config=_syn_config()) | |
| ] | |
| samples = ( | |
| np.concatenate(chunks) if chunks else np.zeros(0, dtype=np.int16) | |
| ) | |
| return voice.config.sample_rate, samples | |
| def split_sentences(text: str) -> list[str]: | |
| """Split text into trimmed, non-empty sentences.""" | |
| return [s.strip() for s in _SENTENCE_END.split(text) if s.strip()] | |
| def synth_stream(text_chunks: Iterator[str]) -> Iterator[tuple[int, np.ndarray]]: | |
| """Buffer streamed text and yield audio for each completed sentence.""" | |
| buffer = "" | |
| for chunk in text_chunks: | |
| buffer += chunk | |
| sentences = split_sentences(buffer) | |
| if len(sentences) > 1: | |
| for sentence in sentences[:-1]: | |
| yield synth(sentence) | |
| buffer = sentences[-1] | |
| if buffer.strip(): | |
| yield synth(buffer) | |
| def save_wav(path: str, sample_rate: int, samples: np.ndarray) -> None: | |
| """Write int16 mono samples to a WAV file.""" | |
| with wave.open(path, "wb") as wf: | |
| wf.setnchannels(1) | |
| wf.setsampwidth(2) | |
| wf.setframerate(sample_rate) | |
| wf.writeframes(samples.tobytes()) | |