76 lines
2.1 KiB
Python
76 lines
2.1 KiB
Python
|
|
import io
|
||
|
|
import wave
|
||
|
|
import requests
|
||
|
|
import soundfile as sf
|
||
|
|
import numpy as np
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
|
||
|
|
GATEWAY_URL = "http://localhost:8003"
|
||
|
|
CHAT_ENDPOINT = f"{GATEWAY_URL}/api/chat"
|
||
|
|
|
||
|
|
# feste Annahmen für Gemini 3.1 Flash TTS über OpenRouter
|
||
|
|
SAMPLE_RATE = 24000
|
||
|
|
CHANNELS = 1
|
||
|
|
SAMPLE_WIDTH = 2 # 16-bit PCM
|
||
|
|
|
||
|
|
|
||
|
|
def pcm_to_wav(pcm_bytes: bytes, wav_path: str) -> None:
|
||
|
|
"""Rohes s16le-PCM in eine WAV-Datei schreiben."""
|
||
|
|
with wave.open(wav_path, "wb") as wf:
|
||
|
|
wf.setnchannels(CHANNELS)
|
||
|
|
wf.setsampwidth(SAMPLE_WIDTH)
|
||
|
|
wf.setframerate(SAMPLE_RATE)
|
||
|
|
wf.writeframes(pcm_bytes)
|
||
|
|
|
||
|
|
|
||
|
|
def play_wav(wav_path: str) -> None:
|
||
|
|
"""WAV-Datei abspielen (ffplay oder aplay/mpv, je nach System)."""
|
||
|
|
for cmd in (
|
||
|
|
["ffplay", "-nodisp", "-autoexit", wav_path],
|
||
|
|
["aplay", wav_path],
|
||
|
|
["mpv", wav_path],
|
||
|
|
):
|
||
|
|
try:
|
||
|
|
subprocess.run(cmd, check=True)
|
||
|
|
return
|
||
|
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
||
|
|
continue
|
||
|
|
print(f"Konnte keine geeignete Player-CLI finden für {wav_path}", file=sys.stderr)
|
||
|
|
|
||
|
|
|
||
|
|
def chat_and_play(text: str, language: str = "de") -> None:
|
||
|
|
payload = {"text": text, "language": language}
|
||
|
|
|
||
|
|
resp = requests.post(
|
||
|
|
CHAT_ENDPOINT,
|
||
|
|
json=payload,
|
||
|
|
stream=True,
|
||
|
|
)
|
||
|
|
|
||
|
|
if not resp.ok:
|
||
|
|
print("HTTP", resp.status_code)
|
||
|
|
print(resp.text)
|
||
|
|
return
|
||
|
|
|
||
|
|
pcm_bytes = b"".join(resp.iter_content(chunk_size=8192))
|
||
|
|
|
||
|
|
# Hinweis: Den Text-Trace (Transkript/Antwort) liefert /api/chat nur im JSON,
|
||
|
|
# wenn man ?debug=true anhängt - nicht als Header im Audio-Stream.
|
||
|
|
print("Audio-Format:", resp.headers.get("X-Audio-Format"))
|
||
|
|
print("Sample-Rate:", resp.headers.get("X-Audio-Sample-Rate"))
|
||
|
|
|
||
|
|
wav_path = "chat_reply.wav"
|
||
|
|
pcm_to_wav(pcm_bytes, wav_path)
|
||
|
|
print(f"WAV gespeichert unter {wav_path}")
|
||
|
|
play_wav(wav_path)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
if len(sys.argv) > 1:
|
||
|
|
user_text = " ".join(sys.argv[1:])
|
||
|
|
else:
|
||
|
|
user_text = "Wie wird das Wetter morgen in Bünde?"
|
||
|
|
|
||
|
|
chat_and_play(user_text, language="de")
|
||
|
|
|