voice-agent/mac-worker/app/core/whisper_engine.py
root 5321c8602e feat: voice-agent MVP — LXC web frontend + Mac AI worker
LXC-Frontend (FastAPI + HTML/JS):
- Audio-Upload (MP3/WAV/M4A/MP4/OGG/FLAC, max. 500 MB)
- SQLite Job-Store, BackgroundTask-Pipeline
- Job-Liste mit Live-Status, Downloads (DOCX + JSON)
- Mac-Health-Indicator im UI

Mac-Worker (FastAPI):
- /api/transcribe (lightning-whisper-mlx | faster-whisper | mock)
- /api/summarize + /api/protocol via Ollama (llama3.1:8b)
- /api/export/docx via python-docx

Deploy:
- systemd-Service, Nginx Reverse-Proxy
- deploy/install.sh: idempotentes LXC-Setup

Doku: README.md, lxc-frontend/README.md, mac-worker/README.md
2026-05-13 15:33:53 +00:00

84 lines
3.2 KiB
Python

"""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: {"text": "...", "segments": [...]} oder String. Robust mappen.
if isinstance(out, str):
return {"text": out, "language": lang, "segments": []}
segments = out.get("segments") or []
normalized = [
{
"start": float(s.get("start", 0.0)),
"end": float(s.get("end", 0.0)),
"text": (s.get("text") or "").strip(),
}
for s in segments
]
return {"text": out.get("text", "").strip(), "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()