import asyncio import os import numpy as np import pyaudio from openwakeword.model import Model from faster_whisper import WhisperModel from edge_tts import Communicate import wave import webrtcvad # Für bessere Sprach-Erkennung from agent import Agent from util.logger import get_logger logger = get_logger("VOICE") class VoiceNora: def __init__(self): self.agent = Agent() self.running = True self.vad = webrtcvad.Vad(2) # Aggressivität 0-3 # Wake Word # Wake Word Model mit automatischer Download-Unterstützung try: self.ww_model = Model(wakeword="nora", inference_framework="tflite") except Exception as e: print("⚠️ Wakeword-Modell konnte nicht geladen werden.") print(" Führe einmalig aus: python -c \"from openwakeword.model import Model; Model(wakeword='nora')\"") raise # STT self.stt_model = WhisperModel("tiny", device="cpu", compute_type="int8") print("\n🎤 Voice N.O.R.A gestartet") print(" Wakeword: 'Nora'") print(" Weibliche Stimme aktiviert\n") async def listen_for_wakeword(self): pa = pyaudio.PyAudio() stream = pa.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=1024) print("⏳ Lausche auf 'Nora'...") while self.running: try: audio_chunk = stream.read(1024, exception_on_overflow=False) audio_np = np.frombuffer(audio_chunk, dtype=np.int16) if self.ww_model.predict(audio_np).get("nora", 0) > 0.58: print("\n✅ 'Nora' erkannt!") await self.process_voice_command() except: pass stream.stop_stream() stream.close() pa.terminate() async def process_voice_command(self): print("🎤 Ich höre zu... (sprich natürlich)") audio_data = await self.record_with_vad(max_duration=12) if len(audio_data) < 8000: # zu kurz print("❌ Zu kurz.") return # STT segments, _ = self.stt_model.transcribe( audio_data, language="de", beam_size=5, vad_filter=True ) text = " ".join(segment.text for segment in segments).strip() if not text: print("❌ Konnte nichts verstehen.") return print(f"👤 Du: {text}") response = await self.agent.run(text) print(f"🗣️ N.O.R.A: {response}") await self.speak(response) async def record_with_vad(self, max_duration=12): """Aufnahme mit Sprach-Erkennung (endet wenn du aufhörst zu sprechen)""" pa = pyaudio.PyAudio() stream = pa.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=320) frames = [] silence_count = 0 max_silence = 25 # ca. 1,5 Sekunden Stille for _ in range(int(16000 / 320 * max_duration)): chunk = stream.read(320, exception_on_overflow=False) frames.append(chunk) # VAD prüfen is_speech = self.vad.is_speech(chunk, 16000) if not is_speech: silence_count += 1 else: silence_count = 0 if silence_count > max_silence and len(frames) > 30: break # aufhören wenn lange Stille stream.stop_stream() stream.close() pa.terminate() return b''.join(frames) async def speak(self, text: str): """Edge TTS - weibliche Stimme""" try: communicate = Communicate(text, voice="de-DE-AmalaNeural") # Weiblich, natürlich await communicate.save("response.mp3") os.system("mpg123 -q response.mp3") except Exception as e: logger.error(f"TTS Fehler: {e}") async def main(): voice = VoiceNora() try: await voice.listen_for_wakeword() except KeyboardInterrupt: print("\n\n👋 N.O.R.A wird beendet.") except Exception as e: logger.error(str(e)) if __name__ == "__main__": asyncio.run(main())