Add native Qwen3 TTS streaming for Hermes

This commit is contained in:
Mikei386
2026-09-05 16:06:27 +02:00
parent 5f3d064bb4
commit 61aa20cb52
7 changed files with 382 additions and 4 deletions
+75
View File
@@ -1519,6 +1519,13 @@ class Handler(BaseHTTPRequestHandler):
else:
self._send_error(503, "Sprachausgabe ist nicht installiert",
"server_error", "feature_disabled")
elif (path == "/v1/audio/speech/pcm-stream"
and self.command == "POST"):
if ENABLE_TTS:
self._speech_pcm_stream()
else:
self._send_error(503, "Sprachausgabe ist nicht installiert",
"server_error", "feature_disabled")
elif path == "/v1/audio/transcriptions" and self.command == "POST":
if ENABLE_STT:
self._transcribe()
@@ -2062,6 +2069,74 @@ class Handler(BaseHTTPRequestHandler):
self.end_headers()
self.wfile.write(audio)
def _speech_pcm_stream(self) -> None:
"""Pass through Qwen's native 24 kHz PCM stream without buffering."""
try:
body = self._read_body()
data = json.loads(body)
except ValueError as exc:
self._send_error(400, str(exc) or "ungültiges JSON",
"invalid_request_error", "invalid_body")
return
if not isinstance(data, dict):
self._send_error(400, "Request muss ein JSON-Objekt sein",
"invalid_request_error", "invalid_request")
return
text = data.get("input", data.get("text"))
if not isinstance(text, str) or not text.strip() or len(text) > 8000:
self._send_error(400, "'input' fehlt, ist leer oder zu lang",
"invalid_request_error", "invalid_input")
return
voice = data.get("voice", TTS_DEFAULT_VOICE)
if voice not in TTS_VOICES:
self._send_error(400, f"ungültige Stimme: {voice!r}",
"invalid_request_error", "invalid_voice")
return
hostport = TTS_WORKER_URL.split("://", 1)[-1]
host, _, port = hostport.partition(":")
connection = None
headers_sent = False
try:
connection = http.client.HTTPConnection(
host, int(port) if port else 80, timeout=TTS_CONNECT_TIMEOUT)
connection.request(
"POST", "/tts/pcm-stream", body=body,
headers={"Content-Type": "application/json",
"Accept": "application/octet-stream"})
connection.sock.settimeout(TTS_TIMEOUT)
response = connection.getresponse()
if response.status != 200:
message = response.read(512).decode(errors="replace")
self._send_error(503,
f"TTS-Stream fehlgeschlagen: {message}",
"server_error", "tts_failed")
return
self._last_code = 200
self.send_response(200)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Cache-Control", "no-store")
self.send_header("Connection", "close")
self.end_headers()
headers_sent = True
while True:
chunk = response.read1(16384)
if not chunk:
break
self.wfile.write(chunk)
self.wfile.flush()
except (OSError, http.client.HTTPException) as exc:
if not headers_sent:
try:
self._send_error(502, f"TTS-Worker nicht erreichbar: {exc}",
"server_error", "tts_unavailable")
except (OSError, BrokenPipeError):
pass
finally:
if connection is not None:
connection.close()
self.close_connection = True
# ---------- Audio-Discovery ----------
def _audio_models_payload(self) -> dict: