import asyncio import os import tempfile import threading import time import pygame import edge_tts pygame.mixer.init() _stop_event = threading.Event() _speak_lock = threading.Lock() def stop_speaking(): _stop_event.set() pygame.mixer.music.stop() def speak(text: str, voice: str = "de-DE-SeraphinaMultilingualNeural", rate: str = "+0%"): """Text → Edge TTS → Lautsprecher. Blockiert bis fertig oder unterbrochen.""" with _speak_lock: _stop_event.clear() tmp_path = None try: with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f: tmp_path = f.name # Edge TTS in eigenem Event-Loop (Thread-sicher) loop = asyncio.new_event_loop() try: communicate = edge_tts.Communicate(text, voice, rate=rate) loop.run_until_complete(communicate.save(tmp_path)) finally: loop.close() if _stop_event.is_set(): return pygame.mixer.music.load(tmp_path) pygame.mixer.music.play() # Polling ohne pygame.time.wait (blockiert Event-Loop nicht) while pygame.mixer.music.get_busy(): if _stop_event.is_set(): pygame.mixer.music.stop() break time.sleep(0.05) except Exception: pass finally: if tmp_path and os.path.exists(tmp_path): try: os.unlink(tmp_path) except OSError: pass def play_ding(): """Kurzer Aktivierungs-Ton (440 Hz, 120ms).""" import numpy as np sample_rate = 44100 duration = 0.12 t = np.linspace(0, duration, int(sample_rate * duration), False) wave = (np.sin(2 * np.pi * 440 * t) * 0.3 * 32767).astype(np.int16) stereo = np.column_stack([wave, wave]) sound = pygame.sndarray.make_sound(stereo) sound.play() time.sleep(duration + 0.02)