diora-web/tts/piper_engine.py

42 lines
1.4 KiB
Python
Raw Normal View History

import io
import threading
import wave
from django.conf import settings
DEFAULT_LANGUAGE = 'de'
SUPPORTED_LANGUAGES = tuple(settings.TTS_VOICES.keys())
# Lazy per-process singletons, one per language: each gunicorn worker loads a
# voice only once it's actually requested, rather than all workers loading
# every model at startup (the host runs several other containers with limited
# spare RAM). One lock guards both the lazy-load and the inference call below
# — a single self-hosted user never needs concurrent synthesis across
# languages, so there's no reason for a lock per voice.
_voices = {}
_lock = threading.Lock()
def _get_voice(lang):
if lang not in _voices:
with _lock:
if lang not in _voices:
from piper import PiperVoice
_voices[lang] = PiperVoice.load(str(settings.TTS_VOICES[lang]))
return _voices[lang]
def synthesize_wav(text, lang=DEFAULT_LANGUAGE):
"""Synthesize `text` (in `lang`) 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(lang)
buf = io.BytesIO()
with _lock:
with wave.open(buf, 'wb') as wav_file:
voice.synthesize_wav(text, wav_file)
return buf.getvalue()