Add native Qwen3 TTS streaming for Hermes
This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user