"""Abstraktion über mehrere Whisper-Implementierungen.""" from __future__ import annotations import logging from pathlib import Path from typing import Any from .config import settings log = logging.getLogger("mac-worker.whisper") class WhisperEngine: def __init__(self) -> None: self.engine = settings.whisper_engine.lower() self._impl: Any = None def _ensure_loaded(self) -> None: if self._impl is not None: return if self.engine == "mlx": from lightning_whisper_mlx import LightningWhisperMLX # type: ignore log.info("Loading lightning-whisper-mlx model=%s batch=%d", settings.whisper_model, settings.whisper_batch_size) self._impl = LightningWhisperMLX( model=settings.whisper_model, batch_size=settings.whisper_batch_size, quant=None, ) elif self.engine == "faster": from faster_whisper import WhisperModel # type: ignore log.info("Loading faster-whisper model=%s", settings.whisper_model) self._impl = WhisperModel(settings.whisper_model, device="auto", compute_type="auto") elif self.engine == "mock": self._impl = "mock" else: raise ValueError(f"Unknown WHISPER_ENGINE: {self.engine}") def transcribe(self, audio_path: Path, language: str | None = None) -> dict: self._ensure_loaded() lang = language or settings.whisper_language if self.engine == "mock": return { "text": "[MOCK] Dies ist eine Beispiel-Transkription für Tests ohne Whisper-Modell.", "language": lang, "segments": [ {"start": 0.0, "end": 3.0, "text": "[MOCK] Erste Aussage."}, {"start": 3.0, "end": 6.0, "text": "[MOCK] Zweite Aussage."}, ], } if self.engine == "mlx": out = self._impl.transcribe(audio_path=str(audio_path), language=lang) # MLX-Output schwankt zwischen Versionen: # - String # - {"text": "...", "segments": [...]} # - {"text": "...", "segments": [[start, end, text], ...]} # - {"text": "...", "segments": [{"start":..,"end":..,"text":..}, ...]} if isinstance(out, str): return {"text": out, "language": lang, "segments": []} raw_segments = out.get("segments") or [] normalized = [] for s in raw_segments: if isinstance(s, dict): normalized.append({ "start": float(s.get("start", 0.0)), "end": float(s.get("end", 0.0)), "text": (s.get("text") or "").strip(), }) elif isinstance(s, (list, tuple)) and len(s) >= 3: normalized.append({ "start": float(s[0] or 0.0), "end": float(s[1] or 0.0), "text": str(s[2] or "").strip(), }) else: normalized.append({"start": 0.0, "end": 0.0, "text": str(s).strip()}) text = (out.get("text") or "").strip() if not text and normalized: text = " ".join(seg["text"] for seg in normalized).strip() return {"text": text, "language": lang, "segments": normalized} if self.engine == "faster": segments_iter, info = self._impl.transcribe(str(audio_path), language=lang, vad_filter=True) segments = [] full = [] for s in segments_iter: t = s.text.strip() full.append(t) segments.append({"start": float(s.start), "end": float(s.end), "text": t}) return {"text": " ".join(full), "language": info.language or lang, "segments": segments} raise RuntimeError(f"Engine {self.engine} not implemented") engine = WhisperEngine()