my_voice_assistant_v3_jamulix/chat_client.py

76 lines
2.1 KiB
Python
Raw Normal View History

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