diff --git a/scripts/diag_record.py b/scripts/diag_record.py new file mode 100644 index 0000000..cc1d418 --- /dev/null +++ b/scripts/diag_record.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +"""Diagnose: Countdown + grünes Signal -> Aufnahme -> sofort transkribieren. + +Ein Durchlauf, kein Timing-Problem, keine Verwechslung der Datei. Zeigt die +WAV-Dauer und die faster-whisper-Segmente (Standard und mit VAD-Filter), damit man +sieht, ob Sätze nach Pausen verloren gehen. + + .venv/bin/python scripts/diag_record.py + .venv/bin/python scripts/diag_record.py --device plughw:5,0 --seconds 20 --model base +""" + +import argparse +import shutil +import subprocess +import sys +import tempfile +import time +import wave + +GREEN = "\033[1;32m" +RED = "\033[1;31m" +RESET = "\033[0m" + + +def main() -> None: + ap = argparse.ArgumentParser() + ap.add_argument("--device", default="plughw:5,0", help="Aufnahmegerät (arecord -L)") + ap.add_argument("--seconds", type=int, default=20) + ap.add_argument("--rate", type=int, default=16000) + ap.add_argument("--model", default="base", help="tiny|base|small|medium|large-v3") + args = ap.parse_args() + + if not shutil.which("arecord"): + sys.exit("Fehlt: arecord") + + wav = tempfile.NamedTemporaryFile(suffix=".wav", delete=False) + wav.close() + + print() + for n in (3, 2, 1): + print(f" Aufnahme startet in {n} …", end="\r", flush=True) + time.sleep(1) + print(f"\n\n{GREEN}🟢🟢🟢🟢🟢 S P R I C H J E T Z T ({args.seconds}s) 🟢🟢🟢🟢🟢{RESET}\n") + + result = subprocess.run( + ["arecord", "-q", "-f", "S16_LE", "-r", str(args.rate), "-c", "1", + "-D", args.device, "-d", str(args.seconds), wav.name] + ) + print(f"{RED}🔴 STOP — Aufnahme fertig.{RESET}\n") + if result.returncode != 0: + sys.exit(f"arecord-Fehler (Gerät {args.device}?). Geräte: arecord -L") + + with wave.open(wav.name, "rb") as w: + dur = w.getnframes() / w.getframerate() + print(f"WAV-Dauer: {dur:.1f}s (Datei: {wav.name})\n") + + print("Lade faster-whisper …") + from faster_whisper import WhisperModel + + model = WhisperModel(args.model, device="cpu", compute_type="int8") + for title, kw in [("Standard (wie im Provider)", {}), ("mit vad_filter=True", {"vad_filter": True})]: + print(f"--- {title} ---") + segments, _ = model.transcribe(wav.name, language="de", **kw) + count = 0 + for seg in segments: + count += 1 + print(f" [{seg.start:5.1f}-{seg.end:5.1f}] {seg.text.strip()!r}") + if count == 0: + print(" (nichts erkannt)") + + +if __name__ == "__main__": + main() diff --git a/scripts/voice_loop.py b/scripts/voice_loop.py index 9e53f0f..610f0c6 100644 --- a/scripts/voice_loop.py +++ b/scripts/voice_loop.py @@ -109,6 +109,19 @@ def record_utterance(recorder: str, device: str | None, rate: int) -> bytes: with open(tmp.name, "rb") as fh: data = fh.read() + # Aufnahmedauer anzeigen (hilft, zu fruehes Stoppen sofort zu erkennen). + # Hinweis: arecord schreibt beim Stoppen per Signal eine falsche Laenge in den + # WAV-Header -> Dauer aus der TATSAECHLICHEN Datenmenge berechnen, nicht aus dem Header. + try: + with wave.open(io.BytesIO(data), "rb") as wav_in: + rate = wav_in.getframerate() or 16000 + channels = wav_in.getnchannels() or 1 + width = wav_in.getsampwidth() or 2 + raw = wav_in.readframes(10 ** 9) # liest alles Vorhandene (Header-Laenge unzuverlaessig) + seconds = len(raw) / (rate * channels * width) + print(f" Aufnahme: {seconds:.1f}s") + except (wave.Error, EOFError): + pass # Recorder-Fehlertext nur zeigen, wenn nichts/zu wenig aufgenommen wurde. if len(data) < 1000: err.seek(0)