e19831f7d5
Windows-Desktop-Script: Energy VAD + Whisper Wake Detection, faster-whisper STT, mss Screen Capture, MC2 Vision/Chat API, Edge TTS Ausgabe, pystray System Tray. Kein Account nötig — Wake Word frei konfigurierbar via WAKE_WORDS in config.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
130 lines
3.9 KiB
Python
130 lines
3.9 KiB
Python
"""
|
|
Hermes Voice Client
|
|
Wake Word → STT → Screenshot → Hermes (MC2) → TTS
|
|
|
|
Läuft als System-Tray-App im Hintergrund.
|
|
Aktivierung: "Hey Jarvis" (konfigurierbar in config.py)
|
|
"""
|
|
|
|
import threading
|
|
import sys
|
|
from PIL import Image, ImageDraw
|
|
import pystray
|
|
|
|
from audio_input import wait_for_wake_word, record_speech, transcribe
|
|
from audio_output import speak, play_ding, stop_speaking
|
|
from screen_capture import capture_screen
|
|
from ai_client import ask
|
|
from config import SCREENSHOT_ON_QUERY
|
|
|
|
# ── Status ───────────────────────────────────────────────────────────────
|
|
class State:
|
|
IDLE = "idle" # wartet auf Wake Word
|
|
LISTENING = "listen" # nimmt Sprache auf
|
|
THINKING = "think" # wartet auf MC2-Antwort
|
|
SPEAKING = "speak" # gibt Antwort aus
|
|
|
|
_state = State.IDLE
|
|
_stop_event = threading.Event()
|
|
_tray_icon: pystray.Icon | None = None
|
|
|
|
|
|
# ── Tray Icon ────────────────────────────────────────────────────────────
|
|
COLORS = {
|
|
State.IDLE: "#22c55e", # grün
|
|
State.LISTENING: "#eab308", # gelb
|
|
State.THINKING: "#3b82f6", # blau
|
|
State.SPEAKING: "#a855f7", # lila
|
|
}
|
|
|
|
def _make_icon(color: str) -> Image.Image:
|
|
img = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
|
|
draw = ImageDraw.Draw(img)
|
|
draw.ellipse([4, 4, 60, 60], fill=color)
|
|
return img
|
|
|
|
def _set_state(state: str):
|
|
global _state
|
|
_state = state
|
|
if _tray_icon:
|
|
_tray_icon.icon = _make_icon(COLORS[state])
|
|
labels = {
|
|
State.IDLE: "Hermes — bereit (Hey Jarvis)",
|
|
State.LISTENING: "Hermes — hört zu…",
|
|
State.THINKING: "Hermes — denkt…",
|
|
State.SPEAKING: "Hermes — spricht",
|
|
}
|
|
_tray_icon.title = labels[state]
|
|
|
|
|
|
# ── Hauptloop ────────────────────────────────────────────────────────────
|
|
def _voice_loop():
|
|
print("[Hermes] Bereit. Sage 'Hey Jarvis' zum Aktivieren.")
|
|
while not _stop_event.is_set():
|
|
_set_state(State.IDLE)
|
|
|
|
# 1. Auf Wake Word warten
|
|
detected = wait_for_wake_word(_stop_event)
|
|
if not detected:
|
|
break
|
|
|
|
# 2. Aktivierungs-Ding + Aufnahme starten
|
|
play_ding()
|
|
_set_state(State.LISTENING)
|
|
audio = record_speech()
|
|
|
|
# 3. Transkribieren
|
|
text = transcribe(audio)
|
|
if not text:
|
|
continue
|
|
print(f"[STT] {text!r}")
|
|
|
|
# 4. Screenshot machen
|
|
screenshot = capture_screen() if SCREENSHOT_ON_QUERY else None
|
|
|
|
# 5. MC2 fragen
|
|
_set_state(State.THINKING)
|
|
response = ask(text, screenshot)
|
|
print(f"[Hermes] {response!r}")
|
|
|
|
# 6. Antwort sprechen
|
|
_set_state(State.SPEAKING)
|
|
speak(response)
|
|
|
|
print("[Hermes] Beendet.")
|
|
|
|
|
|
# ── System Tray ──────────────────────────────────────────────────────────
|
|
def _on_quit(icon, item):
|
|
_stop_event.set()
|
|
stop_speaking()
|
|
icon.stop()
|
|
|
|
def _on_mute(icon, item):
|
|
stop_speaking()
|
|
|
|
def main():
|
|
global _tray_icon
|
|
|
|
loop_thread = threading.Thread(target=_voice_loop, daemon=True)
|
|
loop_thread.start()
|
|
|
|
menu = pystray.Menu(
|
|
pystray.MenuItem("Hermes Voice", None, enabled=False),
|
|
pystray.Menu.SEPARATOR,
|
|
pystray.MenuItem("Sprechen stoppen", _on_mute),
|
|
pystray.MenuItem("Beenden", _on_quit),
|
|
)
|
|
|
|
_tray_icon = pystray.Icon(
|
|
"hermes-voice",
|
|
icon=_make_icon(COLORS[State.IDLE]),
|
|
title="Hermes — bereit (Hey Jarvis)",
|
|
menu=menu,
|
|
)
|
|
_tray_icon.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|