""" Audio-Input: Aufnahme + Whisper STT. Kein Wake-Word-Loop — Aufnahme startet direkt auf Hotkey-Signal. """ import os import tempfile import threading import numpy as np import sounddevice as sd import scipy.io.wavfile as wav from faster_whisper import WhisperModel from pathlib import Path SAMPLE_RATE = 16000 CHUNK = 1024 ENERGY_THRESHOLD = 0.008 _model: WhisperModel | None = None _model_size: str = "" _recording = False _frames: list[np.ndarray] = [] _stream: sd.InputStream | None = None _lock = threading.Lock() def load_model(model_size: str): global _model, _model_size if _model is None or _model_size != model_size: model_dir = Path.home() / ".hermes-voice" / "whisper-models" model_dir.mkdir(parents=True, exist_ok=True) _model = WhisperModel( model_size, device="cpu", compute_type="int8", download_root=str(model_dir), ) _model_size = model_size def start_recording(): global _recording, _frames, _stream with _lock: _recording = True _frames = [] def callback(indata, frames, time, status): if _recording: _frames.append(indata[:, 0].copy()) _stream = sd.InputStream( samplerate=SAMPLE_RATE, channels=1, dtype="float32", blocksize=CHUNK, callback=callback, ) _stream.start() def stop_recording() -> np.ndarray: global _recording, _stream with _lock: _recording = False if _stream: _stream.stop() _stream.close() _stream = None if not _frames: return np.array([], dtype=np.float32) return np.concatenate(_frames) def transcribe(audio: np.ndarray, model_size: str, language: str) -> str: if len(audio) < SAMPLE_RATE * 0.3: return "" load_model(model_size) with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f: tmp = f.name try: wav.write(tmp, SAMPLE_RATE, (audio * 32767).astype(np.int16)) lang = language if language != "auto" else None segments, _ = _model.transcribe( tmp, language=lang, beam_size=3, vad_filter=True, ) return " ".join(s.text for s in segments).strip() finally: os.unlink(tmp)