import io import threading import wave from django.conf import settings # Lazy per-process singleton: each gunicorn worker loads its own copy on first # use rather than all workers loading the ONNX model at startup (the host runs # several other containers with limited spare RAM). _voice = None _lock = threading.Lock() def _get_voice(): global _voice if _voice is None: with _lock: if _voice is None: from piper import PiperVoice _voice = PiperVoice.load(str(settings.TTS_MODEL_PATH)) return _voice def synthesize_wav(text): """Synthesize `text` to WAV bytes. Never persists or logs `text` — callers must not log it either. The lock also serializes inference, since one onnxruntime session isn't meant to run concurrent calls within a process. """ voice = _get_voice() buf = io.BytesIO() with _lock: with wave.open(buf, 'wb') as wav_file: voice.synthesize_wav(text, wav_file) return buf.getvalue()