# SpeechToText Lokaler, schneller Spracherkennungs-Dienst (Sprache → Text) auf Basis von [faster-whisper](https://github.com/SYSTRAN/faster-whisper) — das STT-Gegenstück zu [chatterbox-tts-cli](https://kitux.de/forgejo/dschlueter/chatterbox-tts-cli). Gedacht als Baustein für Sprachassistenten und Smarthome: Home Assistant (Wyoming), Mikrocontroller wie ESP32 (HTTP/WebSocket), Skripte (CLI) und OpenAI-kompatible Tools. - **Schnell:** Modell bleibt geladen. Gemessen auf RTX 3090 mit `large-v3-turbo`: 5,8 s Audio → 0,2 s, 35 s Audio → 0,7 s Rechenzeit; ~2,4 GB VRAM. - **Nicht blockierend:** Inferenz in einem Worker-Thread, Warteschlange begrenzt (503 statt Stau). - **Äußerungsende per VAD** (Silero) statt Stoppwort; Transkript ca. 1 s nach Sprechende. - **Robust:** GPU-Fehler → automatischer CPU-Fallback; beliebige Audioformate; Halluzinationsfilter („Untertitel im Auftrag des ZDF“ …). ## Installation ```bash python3 -m venv --system-site-packages .venv # nutzt vorhandenes faster-whisper/torch-Umfeld .venv/bin/pip install -r requirements.txt ``` ## Dienst starten ```bash # Manuell (GPU per UUID pinnen: nvidia-smi -L) CUDA_VISIBLE_DEVICES=GPU- .venv/bin/uvicorn stt_service:app --host 0.0.0.0 --port 9998 # Als systemd-User-Service (Autostart) cp deploy/stt-service.service ~/.config/systemd/user/ systemctl --user daemon-reload && systemctl --user enable --now stt-service journalctl --user -u stt-service -f curl http://127.0.0.1:9998/health ``` Beim ersten Start wird das Modell heruntergeladen (`large-v3-turbo` ≈ 1,6 GB). Der Dienst ist sofort erreichbar; `/health` meldet `loading`, bis das Modell bereit ist. ### Konfiguration (Umgebungsvariablen) | Variable | Standard | Bedeutung | |---|---|---| | `STT_MODEL` | `large-v3-turbo` | Whisper-Modell (`tiny` … `large-v3`, `large-v3-turbo`, Pfad) | | `STT_DEVICE` | `auto` | `auto` / `cuda` / `cpu` | | `STT_COMPUTE_TYPE` | `auto` | `float16` (GPU) / `int8` (CPU) automatisch | | `STT_CPU_MODEL` | `small` | Modell beim Ausweichen auf CPU | | `STT_LANGUAGE` | `de` | Default-Sprache, `auto` = Erkennung | | `STT_BEAM_SIZE` | `5` | Beam-Search-Breite (1 = schneller) | | `STT_INITIAL_PROMPT` | – | Kontext/Eigennamen für Whisper | | `STT_MAX_QUEUE` | `8` | max. wartende Aufträge, darüber HTTP 503 | | `STT_MAX_UPLOAD_MB` | `50` | max. Uploadgröße | | `STT_PRELOAD` | `1` | Modell beim Start laden + aufwärmen | | `STT_VAD_SILENCE_MS` | `700` | Stille bis Äußerungsende (Streaming) | | `STT_VAD_THRESHOLD` | `0.5` | Silero-Sprachschwelle | | `STT_VAD_MAX_UTTERANCE_S` | `30` | max. Äußerungslänge | | `STT_WYOMING_HOST` / `STT_WYOMING_PORT` | `0.0.0.0` / `10300` | Wyoming-Server, Port leer = aus | | `STT_LOG_LEVEL` | `INFO` | Log-Level | ## Schnittstellen ### HTTP ```bash # OpenAI-kompatibel (multipart) — response_format: json | text | verbose_json curl -F file=@aufnahme.mp3 -F language=de http://127.0.0.1:9998/v1/audio/transcriptions # Rohdaten im Body — ideal für Mikrocontroller (kein multipart nötig) curl --data-binary @aufnahme.wav http://127.0.0.1:9998/transcribe curl --data-binary @roh.pcm "http://127.0.0.1:9998/transcribe?format=pcm&sample_rate=16000&channels=1" ``` `/transcribe` liefert `text`, `language`, `duration`, `processing_s`, `segments`. ### WebSocket `/ws/stream` (Live-Streaming) Client schickt s16le-PCM in beliebig großen Stücken; der Dienst erkennt Äußerungen selbst. ```text → {"type":"start","sample_rate":16000,"channels":1,"channel":null,"language":"de","vad":true,"silence_ms":700} → … ← {"type":"speech_start","t":1.31} ← {"type":"transcript","text":"Schalte das Licht ein.","language":"de","duration":2.4,"processing_s":0.21,"reason":"silence","t":3.9} → {"type":"end"} # laufende Äußerung abschließen → danach {"type":"done"} → {"type":"close"} # ausstehende Transkripte senden, dann schließen ``` Ohne VAD (`"vad":false`) wird alles bis `end` gesammelt und als Ganzes transkribiert (Push-to-Talk). Für ESP32: I²S-Mikrofon mit 16 kHz mono lesen und die Puffer direkt als Binär-Frames senden. ### Home Assistant (Wyoming) Einstellungen → Geräte & Dienste → Integration hinzufügen → **Wyoming Protocol** → Host = dieser Rechner, Port `10300`. Danach in der Sprachassistenten-Pipeline als Spracherkennung auswählen. ESPHome-Satelliten nutzen den Dienst dann automatisch. ## CLI Nur Transkripte auf stdout, Status auf stderr → skriptfähig. ```bash .venv/bin/python stt_cli.py aufnahme.wav # Datei über den Dienst .venv/bin/python stt_cli.py *.mp3 --json # JSON-Zeilen .venv/bin/python stt_cli.py # Mikrofon (System-Default), fortlaufend text=$(.venv/bin/python stt_cli.py --once) # eine Äußerung → Variable .venv/bin/python stt_cli.py -d respeaker -v # bestimmtes Gerät (ReSpeaker: ASR-Kanal automatisch) .venv/bin/python stt_cli.py --local --model small aufnahme.wav # ohne Dienst .venv/bin/python stt_cli.py --list-devices ``` Exit-Codes: `0` ok, `1` Fehler, `2` keine Sprache erkannt. ## Tests ```bash .venv/bin/python -m pytest # schnell, ohne Modell (Fake-Engine) .venv/bin/python -m pytest -m integration # echtes Whisper (small, CPU) + Silero mit espeak-ng-Sprache .venv/bin/python -m pytest tests/test_core.py::test_segmenter_detects_two_utterances ``` ## Bekannte Grenzen - Wörtlich wiederholte Sätze innerhalb eines 30-s-Fensters fasst Whisper teils zusammen (bei natürlicher Sprache selten). - Keine wortweisen Zwischenergebnisse während des Sprechens — Transkript kommt pro Äußerung. - `stt.py` und `stt_respeaker.py` sind die alten Einzelskripte (historisch).