import asyncio import os import subprocess import tempfile import time import wave from wyoming.audio import AudioChunk, AudioStart, AudioStop from wyoming.client import AsyncTcpClient from wyoming.tts import Synthesize, SynthesizeVoice PIPER_HOST = os.getenv("PIPER_HOST", "127.0.0.1") PIPER_PORT = int(os.getenv("PIPER_PORT", "10200")) AUDIO_DEVICE = os.getenv("AUDIO_OUTPUT_DEVICE", "plughw:1,0") async def synthesize(text, wav_path): client = AsyncTcpClient(PIPER_HOST, PIPER_PORT) await client.connect() try: await client.write_event( Synthesize( text=text, voice=SynthesizeVoice( name=os.getenv("PIPER_VOICE", "fr_FR-tom-medium") )).event() ) wav_file = None try: while True: event = await asyncio.wait_for( client.read_event(), timeout=30 ) if event is None: raise RuntimeError("Connexion Piper interrompue") if AudioStart.is_type(event.type): start = AudioStart.from_event(event) wav_file = wave.open(wav_path, "wb") wav_file.setnchannels(start.channels) wav_file.setsampwidth(start.width) wav_file.setframerate(start.rate) elif AudioChunk.is_type(event.type): chunk = AudioChunk.from_event(event) if wav_file is None: wav_file = wave.open(wav_path, "wb") wav_file.setnchannels(chunk.channels) wav_file.setsampwidth(chunk.width) wav_file.setframerate(chunk.rate) wav_file.writeframes(chunk.audio) elif AudioStop.is_type(event.type): break finally: if wav_file is not None: wav_file.close() finally: await client.disconnect() def speak(text): if not text or not text.strip(): return fd, wav_path = tempfile.mkstemp( prefix="alfred_piper_", suffix=".wav" ) os.close(fd) start = time.monotonic() try: print("[ALFRED PIPER] Synthèse...", flush=True) asyncio.run(synthesize(text, wav_path)) print("[ALFRED PIPER] Lecture...", flush=True) subprocess.run( ["aplay", "-q", "-D", AUDIO_DEVICE, wav_path], check=True, timeout=120 ) print( f"[ALFRED PIPER] Terminé en {time.monotonic() - start:.2f}s", flush=True ) except Exception as e: print(f"[ALFRED PIPER] Erreur : {e}", flush=True) raise finally: if os.path.exists(wav_path): os.remove(wav_path)