- 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>
5.6 KiB
SpeechToText
Lokaler, schneller Spracherkennungs-Dienst (Sprache → Text) auf Basis von faster-whisper — das STT-Gegenstück zu 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
python3 -m venv --system-site-packages .venv # nutzt vorhandenes faster-whisper/torch-Umfeld
.venv/bin/pip install -r requirements.txt
Dienst starten
# 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
# 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.
→ {"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.
.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
.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.pyundstt_respeaker.pysind die alten Einzelskripte (historisch).