From 095d3edc0364569079194731fba5103c8ceb7c35 Mon Sep 17 00:00:00 2001 From: "info@pi-farm.de" Date: Wed, 27 May 2026 23:27:31 +0000 Subject: [PATCH] wakeword.py aktualisiert --- wakeword.py | 91 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/wakeword.py b/wakeword.py index 948ab47..c9d35a1 100644 --- a/wakeword.py +++ b/wakeword.py @@ -5,11 +5,21 @@ import json import queue import time import subprocess -import colorama +import wave import sounddevice as sd import numpy as np from vosk import Model, KaldiRecognizer from pathlib import Path +from dotenv import load_dotenv +from openai import OpenAI + +# ==================================================== +# PFADE & ENV SETUP (Aus config/.env lesen) +# ==================================================== +BASE_DIR = Path(__file__).resolve().parent +CONFIG_DIR = BASE_DIR / "config" +ENV_FILE = CONFIG_DIR / ".env" +load_dotenv(ENV_FILE) MODEL_PATH = "model" AUDIO_RATE = 48000 @@ -19,6 +29,11 @@ if not os.path.exists(MODEL_PATH): print(f"❌ Modell-Ordner '{MODEL_PATH}' wurde nicht gefunden!") sys.exit(1) +# OpenAI Client initialisieren +if not os.getenv("OPENAI_API_KEY"): + print("⚠️ Warnung: Kein OPENAI_API_KEY in der .env gefunden!") +openai_client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) + audio_queue = queue.Queue() def audio_callback(indata, frames, time, status): @@ -26,14 +41,13 @@ def audio_callback(indata, frames, time, status): print(status, file=sys.stderr) audio_queue.put(bytes(indata)) -print("🧠 J.A.R.V.I.S. lädt das Sprachmodell...") +print("🧠 J.A.R.V.I.S. lädt das Sprachmodell für das Wake-Word...") model = Model(MODEL_PATH) -# Zwei Recognizer: Einer für das Wake-Word, einer für den eigentlichen Befehl (offen) +# Nur noch EIN Recognizer: Ausschließlich für das Wake-Word ("jarvis") wake_recognizer = KaldiRecognizer(model, AUDIO_RATE, '["jarvis", "[unk]"]') -command_recognizer = KaldiRecognizer(model, AUDIO_RATE) # Sucht nach JEDEM deutschen Wort -print("🎙️ J.A.R.V.I.S. ist online und lauscht... (Sag 'Jarvis')") +print("🎙️ J.A.R.V.I.S. läuft im Hybrid-Modus (Vosk + Whisper) und lauscht... (Sag 'Jarvis')") with sd.RawInputStream(samplerate=AUDIO_RATE, blocksize=8000, dtype='int16', channels=1, callback=audio_callback): @@ -41,53 +55,75 @@ with sd.RawInputStream(samplerate=AUDIO_RATE, blocksize=8000, dtype='int16', while True: data = audio_queue.get() - # NEU: Wenn J.A.R.V.I.S. gerade spricht, leere die Queue und ignoriere das Audio + # Wenn J.A.R.V.I.S. gerade spricht, leere die Queue und ignoriere das Audio if LOCK_FILE.exists(): while not audio_queue.empty(): audio_queue.get() - wake_recognizer.Reset() # Verhindert, dass Bruchstücke von vorhin gespeichert bleiben + wake_recognizer.Reset() # Verhindert alte Bruchstücke continue - # Phase 1: Auf Wake-Word warten + # Phase 1: Auf Wake-Word warten (Lokal via Vosk) if wake_recognizer.AcceptWaveform(data): result = json.loads(wake_recognizer.Result()) if "jarvis" in result.get("text", ""): print("\n⚡ [WAKEWORD DETECTED] Ja, Sir?") - # Bestätigungston abspielen - # Kurzer, smarter Beep-Ton (800 Hz, 0.1 Sekunden) + # Bestätigungston abspielen (800 Hz, 0.1 Sekunden) duration = 0.1 frequency = 800.0 t = np.linspace(0, duration, int(AUDIO_RATE * duration), endpoint=False) - beep = np.sin(2 * np.pi * frequency * t) * 0.3 # 0.3 für angenehme Lautstärke + beep = np.sin(2 * np.pi * frequency * t) * 0.3 sd.play(beep, samplerate=AUDIO_RATE) sd.wait() - # Warteschlange leeren, um alten Ton nicht als Befehl zu interpretieren + + # Warteschlange leeren, um den Beep nicht selbst aufzunehmen while not audio_queue.empty(): audio_queue.get() - print("👂 Höre zu...") - command_text = "" + print("👂 Höre zu (Befehlsaufnahme)...") + collected_chunks = [] start_time = time.time() - # Phase 2: Für 4 Sekunden den darauffolgenden Befehl aufnehmen + # Phase 2: Für 4 Sekunden die Rohdaten aus dem Stream sammeln while time.time() - start_time < 4.0: - cmd_data = audio_queue.get() - if command_recognizer.AcceptWaveform(cmd_data): - res = json.loads(command_recognizer.Result()) - command_text += " " + res.get("text", "") + try: + # Kurzer Timeout, damit die Schleife agil bleibt + cmd_data = audio_queue.get(timeout=0.2) + collected_chunks.append(cmd_data) + except queue.Empty: + continue - # Letzten Rest auslesen - final_res = json.loads(command_recognizer.FinalResult()) - command_text += " " + final_res.get("text", "") - command_text = command_text.strip() + print("🧠 Sende Audio an OpenAI Whisper API...") + # Rohe Audio-Bytes zusammenfügen und als WAV speichern + wav_path = "/tmp/jarvis_cmd.wav" + all_bytes = b"".join(collected_chunks) + + try: + with wave.open(wav_path, "wb") as wf: + wf.setnchannels(1) + wf.setsampwidth(2) # int16 entspricht 2 Bytes + wf.setframerate(AUDIO_RATE) + wf.writeframes(all_bytes) + + # Whisper API aufrufen + with open(wav_path, "rb") as audio_file: + transcription = openai_client.audio.transcriptions.create( + model="whisper-1", + file=audio_file, + language="de" # Erzwingt deutsche Texterkennung + ) + command_text = transcription.text.strip() + + except Exception as e: + print(f"❌ Fehler bei der Spracherkennung: {e}") + command_text = "" + + # Phase 3: Befehl verarbeiten, falls Whisper etwas verstanden hat if command_text: - print(f"🗣️ Erkannter Befehl: '{command_text}'") + print(f"🗣️ Erkannt (Whisper): '{command_text}'") print("🧠 Übermittle an J.A.R.V.I.S. Gehirn...") - # Rufe jarvis.py im virtuellen Environment auf und übergib den Befehl - # (Wir nutzen hier Google Gemini oder was auch immer in deiner .env aktiv ist!) subprocess.run([ "venv/bin/python3", "jarvis.py", @@ -98,5 +134,4 @@ with sd.RawInputStream(samplerate=AUDIO_RATE, blocksize=8000, dtype='int16', print("🔇 Kein Befehl verstanden.") print("\n🎙️ Zurück im Standby. Lausche auf 'Jarvis'...") - wake_recognizer.Reset() - command_recognizer.Reset() + wake_recognizer.Reset() \ No newline at end of file