whisper-stt-cli/tests/conftest.py

92 lines
2.8 KiB
Python
Raw Normal View History

import io
import os
import shutil
import subprocess
import sys
import wave
from pathlib import Path
import numpy as np
import pytest
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
os.environ.setdefault("STT_WYOMING_PORT", "") # Modul-App in Tests ohne TCP-Server
import stt_core as core # noqa: E402
SR = core.SAMPLE_RATE
def make_wav(audio: np.ndarray, rate: int = SR, channels: int = 1, width: int = 2) -> bytes:
"""float32 (mono oder [n, channels]) → WAV-Bytes."""
buf = io.BytesIO()
with wave.open(buf, "wb") as w:
w.setnchannels(channels)
w.setsampwidth(width)
w.setframerate(rate)
if width == 2:
w.writeframes((np.clip(audio, -1, 1) * 32767).astype("<i2").tobytes())
elif width == 4:
w.writeframes((np.clip(audio, -1, 1) * 2147483647).astype("<i4").tobytes())
return buf.getvalue()
def tone(seconds: float, freq: float = 220.0, amp: float = 0.5, rate: int = SR) -> np.ndarray:
t = np.arange(int(seconds * rate)) / rate
return (amp * np.sin(2 * np.pi * freq * t)).astype(np.float32)
def silence(seconds: float, rate: int = SR) -> np.ndarray:
return np.zeros(int(seconds * rate), dtype=np.float32)
class EnergyProb:
"""Deterministische Ersatz-VAD für Tests: Fenster mit Energie = Sprache."""
def __call__(self, windows: np.ndarray) -> np.ndarray:
frames = windows.reshape(-1, core.VAD_WINDOW)
rms = np.sqrt((frames ** 2).mean(axis=1))
return (rms > 0.05).astype(np.float32)
class FakeEngine(core.SttEngine):
"""Engine ohne Modell: 'transkribiert' die Audiodauer."""
def __init__(self, delay: float = 0.0):
super().__init__(core.EngineConfig(model="fake"))
self.delay = delay
self.calls: list[dict] = []
self.status = "ready"
self.active_model, self.active_device = "fake", "cpu"
def load(self):
return None
def warmup(self):
return None
def transcribe(self, audio, **kwargs):
import time
if self.delay:
time.sleep(self.delay)
self.calls.append({"samples": int(audio.size), **kwargs})
duration = round(audio.size / SR, 2)
return core.Transcript(
text=f"audio {duration:.1f}s", language=self.resolve_language(kwargs.get("language")) or "de",
language_probability=1.0, duration=duration, processing_s=0.0,
segments=[core.Segment(0.0, duration, f"audio {duration:.1f}s")],
)
def espeak_wav(text: str, voice: str = "de") -> bytes | None:
if not shutil.which("espeak-ng"):
return None
return subprocess.run(["espeak-ng", "-v", voice, "-s", "140", "--stdout", text],
check=True, capture_output=True).stdout
@pytest.fixture
def fake_engine():
return FakeEngine()