diora-web/tts/piper_engine.py
marwin 05216a8613
All checks were successful
Build and push Docker image / build (push) Successful in 1m3s
Test / test (push) Successful in 1m32s
Vorlesen-Funktion im Reader: Piper/Thorsten-TTS (SW v42)
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
2026-08-31 22:29:13 +02:00

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()