wakeword.py aktualisiert

This commit is contained in:
2026-05-27 23:27:31 +00:00
parent 091a553452
commit 095d3edc03

View File

@@ -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()