mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 22:04:41 +02:00
feat(voice): détection deux niveaux + service systemd
- VAD mode : Whisper small (~0.3s) pour détecter "ok hermes", large-v3 uniquement pour transcrire la commande - PTT mode : large-v3 direct (inchangé) - install-service.sh : installe hermes-voice comme service utilisateur systemd (auto-start à la session) - doc mise à jour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a3ba3089e2
commit
69ea82c0e7
2 changed files with 120 additions and 37 deletions
|
|
@ -1,12 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Hermes Voice Client
|
||||
VAD → Whisper STT → keyword "hermes" → LiteLLM → piper TTS
|
||||
VAD → Whisper STT (2 niveaux) → keyword "hermes" → hermes -z SSH → piper TTS
|
||||
|
||||
Usage:
|
||||
python hermes-voice.py # parlez naturellement, commencez par "Hermes, ..."
|
||||
python hermes-voice.py --ptt # Entrée pour commencer/terminer (plus simple)
|
||||
python hermes-voice.py --no-tts # réponses texte seulement (pour tester)
|
||||
python hermes-voice.py # dites "Ok Hermes, ..." — écoute en permanence
|
||||
python hermes-voice.py --ptt # Entrée pour commencer/terminer
|
||||
python hermes-voice.py --no-tts # réponses texte seulement (debug)
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
|
@ -181,41 +181,60 @@ def speak(text: str):
|
|||
out.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def handle(whisper_model, audio: np.ndarray, no_tts: bool, require_keyword: bool = True):
|
||||
def handle_vad(whisper_fast, whisper_full, audio: np.ndarray, no_tts: bool):
|
||||
"""VAD : détection rapide (small) puis transcription précise (large-v3)."""
|
||||
if len(audio) / SAMPLE_RATE < MIN_SPEECH:
|
||||
return
|
||||
|
||||
# Étape 1 — vérification rapide du mot-clé avec le petit modèle
|
||||
quick = transcribe(whisper_fast, audio)
|
||||
found, _ = detect_keyword(quick)
|
||||
if not found:
|
||||
print(f"(ignoré : \"{quick[:60]}\")")
|
||||
return
|
||||
|
||||
# Étape 2 — transcription précise uniquement si mot-clé trouvé
|
||||
print(" ⏳ Transcription...", end=" ", flush=True)
|
||||
text = transcribe(whisper_full, audio)
|
||||
if not text:
|
||||
print("(rien compris)")
|
||||
return
|
||||
_, command = detect_keyword(text)
|
||||
query = command or text
|
||||
|
||||
print(f"\n 🗣️ Vous : {text}")
|
||||
_respond(query, no_tts)
|
||||
|
||||
|
||||
def handle_ptt(whisper_full, audio: np.ndarray, no_tts: bool):
|
||||
"""PTT : transcription directe sans filtre mot-clé."""
|
||||
if len(audio) / SAMPLE_RATE < MIN_SPEECH:
|
||||
return
|
||||
|
||||
print(" ⏳ Transcription...", end=" ", flush=True)
|
||||
text = transcribe(whisper_model, audio)
|
||||
text = transcribe(whisper_full, audio)
|
||||
if not text:
|
||||
print("(rien compris)")
|
||||
return
|
||||
|
||||
if require_keyword:
|
||||
found, command = detect_keyword(text)
|
||||
if not found:
|
||||
print(f"(ignoré : \"{text[:60]}\")")
|
||||
return
|
||||
query = command
|
||||
else:
|
||||
query = text
|
||||
|
||||
print(f"\n 🗣️ Vous : {text}")
|
||||
_respond(text, no_tts)
|
||||
|
||||
|
||||
def _respond(query: str, no_tts: bool):
|
||||
print(" ⏳ Hermes...", end=" ", flush=True)
|
||||
try:
|
||||
response = ask_hermes(query)
|
||||
except requests.RequestException as e:
|
||||
print(f"\n ❌ Réseau : {e}")
|
||||
except (requests.RequestException, RuntimeError) as e:
|
||||
print(f"\n ❌ Erreur : {e}")
|
||||
return
|
||||
print(f"\n 🤖 Hermes : {response}\n")
|
||||
|
||||
if not no_tts:
|
||||
speak(response)
|
||||
|
||||
|
||||
def run_vad(whisper_model, no_tts: bool):
|
||||
"""Écoute continue par VAD — commencer par 'Hermes, ...' pour déclencher."""
|
||||
print(f"🎙️ En écoute — commencez par \"{KEYWORD.capitalize()}, ...\" (Ctrl+C pour quitter)\n")
|
||||
def run_vad(whisper_fast, whisper_full, no_tts: bool):
|
||||
"""Écoute continue — dites 'Ok Hermes, ...' pour déclencher."""
|
||||
print(f"🎙️ En écoute — dites \"Ok {KEYWORD.capitalize()}, ...\" (Ctrl+C pour quitter)\n")
|
||||
|
||||
audio_q: queue.Queue = queue.Queue()
|
||||
pre_roll: deque = deque(maxlen=PRE_ROLL)
|
||||
|
|
@ -236,7 +255,7 @@ def run_vad(whisper_model, no_tts: bool):
|
|||
# Voix détectée — enregistrer jusqu'au silence
|
||||
print(" 🔴 ", end="", flush=True)
|
||||
captured = list(pre_roll)
|
||||
silent_blocks = 0
|
||||
silent_blocks = 0
|
||||
required_silent = int(SILENCE_SEC * SAMPLE_RATE / BLOCK_SIZE)
|
||||
|
||||
while True:
|
||||
|
|
@ -255,10 +274,10 @@ def run_vad(whisper_model, no_tts: bool):
|
|||
|
||||
print()
|
||||
beep(440)
|
||||
handle(whisper_model, np.concatenate(captured), no_tts, require_keyword=True)
|
||||
handle_vad(whisper_fast, whisper_full, np.concatenate(captured), no_tts)
|
||||
|
||||
|
||||
def run_ptt(whisper_model, no_tts: bool):
|
||||
def run_ptt(whisper_full, no_tts: bool):
|
||||
"""Push-to-talk : Entrée pour commencer, Entrée pour terminer."""
|
||||
print("🎙️ Mode push-to-talk — Entrée pour parler, Entrée pour terminer (Ctrl+C pour quitter)\n")
|
||||
|
||||
|
|
@ -281,7 +300,7 @@ def run_ptt(whisper_model, no_tts: bool):
|
|||
if not chunks:
|
||||
continue
|
||||
beep(440)
|
||||
handle(whisper_model, np.concatenate(chunks), no_tts, require_keyword=False)
|
||||
handle_ptt(whisper_full, np.concatenate(chunks), no_tts)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
@ -292,18 +311,29 @@ def main():
|
|||
|
||||
print("Hermes Voice Client")
|
||||
print("=" * 40)
|
||||
print(f" ⏳ Chargement Whisper ({WHISPER_SIZE})...", end=" ", flush=True)
|
||||
from faster_whisper import WhisperModel
|
||||
whisper = WhisperModel(WHISPER_SIZE, device="cpu", compute_type="int8")
|
||||
print("✓\n")
|
||||
|
||||
try:
|
||||
if args.ptt:
|
||||
run_ptt(whisper, args.no_tts)
|
||||
else:
|
||||
run_vad(whisper, args.no_tts)
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Au revoir")
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
if args.ptt:
|
||||
print(f" ⏳ Chargement Whisper ({WHISPER_SIZE})...", end=" ", flush=True)
|
||||
whisper_full = WhisperModel(WHISPER_SIZE, device="cpu", compute_type="int8")
|
||||
print("✓\n")
|
||||
try:
|
||||
run_ptt(whisper_full, args.no_tts)
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Au revoir")
|
||||
else:
|
||||
# Mode VAD : deux modèles — small pour détection rapide, large pour transcription
|
||||
print(" ⏳ Chargement Whisper rapide (small)...", end=" ", flush=True)
|
||||
whisper_fast = WhisperModel("small", device="cpu", compute_type="int8")
|
||||
print("✓")
|
||||
print(f" ⏳ Chargement Whisper précis ({WHISPER_SIZE})...", end=" ", flush=True)
|
||||
whisper_full = WhisperModel(WHISPER_SIZE, device="cpu", compute_type="int8")
|
||||
print("✓\n")
|
||||
try:
|
||||
run_vad(whisper_fast, whisper_full, args.no_tts)
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Au revoir")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
53
tools/hermes-voice/install-service.sh
Executable file
53
tools/hermes-voice/install-service.sh
Executable file
|
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env bash
|
||||
# Installe hermes-voice comme service utilisateur systemd
|
||||
# Lance automatiquement au démarrage de session, écoute "Ok Hermes, ..."
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
VENV_PYTHON="$SCRIPT_DIR/.venv/bin/python"
|
||||
SERVICE_DIR="$HOME/.config/systemd/user"
|
||||
SERVICE_FILE="$SERVICE_DIR/hermes-voice.service"
|
||||
|
||||
# Vérifications
|
||||
if [ ! -f "$VENV_PYTHON" ]; then
|
||||
echo "❌ venv introuvable — lancez d'abord :"
|
||||
echo " python3 -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$SERVICE_DIR"
|
||||
|
||||
cat > "$SERVICE_FILE" << EOF
|
||||
[Unit]
|
||||
Description=Hermes Voice Client
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=$VENV_PYTHON $SCRIPT_DIR/hermes-voice.py
|
||||
WorkingDirectory=$SCRIPT_DIR
|
||||
Restart=on-failure
|
||||
RestartSec=10s
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=hermes-voice
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
EOF
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable hermes-voice
|
||||
systemctl --user start hermes-voice
|
||||
|
||||
echo ""
|
||||
echo "✓ Service installé et démarré"
|
||||
echo ""
|
||||
echo "Commandes utiles :"
|
||||
echo " systemctl --user status hermes-voice"
|
||||
echo " journalctl --user -u hermes-voice -f"
|
||||
echo " systemctl --user stop hermes-voice"
|
||||
echo " systemctl --user restart hermes-voice"
|
||||
Loading…
Add table
Add a link
Reference in a new issue