Serverseitige Sprachsynthese (deutsche Thorsten-Stimme, Piper) für den EPUB-Reader: /tts/synthesize/ nimmt einen Satz (max. 500 Zeichen) entgegen, synthetisiert und streamt WAV zurück, ohne zu persistieren, zu loggen oder zu cachen — eine bewusste, eng begrenzte Ausnahme vom "Server sieht nie Klartext"-Prinzip (siehe CLAUDE.md). Der neue ▶-Button im Reader-Header liest ab der aktuellen Position satzweise vor, hebt den gerade gesprochenen Satz hervor und scrollt mit; eine kleine Leiste bietet Pause/Stop. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xb6yX2S9aTGepYA9JFba9x
36 lines
1 KiB
Python
36 lines
1 KiB
Python
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()
|