whisper-stt-cli/README.md
dschlueter a986cbe42a STT-Dienst: Kern, HTTP/WebSocket/Wyoming-Service, CLI, Tests, systemd-Unit
- stt_core.py: Whisper-Engine (einmal laden, CPU-Fallback), Audio-Dekodierung
  jedes Formats, Halluzinationsfilter, Äußerungserkennung per Silero-VAD
- stt_service.py: /health, /v1/audio/transcriptions (OpenAI), /transcribe (Rohdaten),
  WS /ws/stream; Inferenz im Worker-Thread, begrenzte Warteschlange (503)
- stt_wyoming.py: Home-Assistant-Anbindung (TCP 10300)
- stt_cli.py: Datei/Mikrofon über den Dienst oder lokal
- Tests (Unit + Integration), README, requirements.txt, deploy/stt-service.service

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 21:34:03 +02:00

127 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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-<uuid> .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}
→ <binär: PCM> …
← {"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).