Files
audio-summary-with-local-LLM/src/audio_summary/pipeline/transcription.py
Damien 701f5dd56e Update Voxtral STT model to non-realtime batch variant
Use Voxtral-Mini-3B-2507-bf16 across all tiers and set appropriate
max_tokens for full-file transcription
2026-06-17 11:25:04 +02:00

77 lines
2.8 KiB
Python

"""Speech-to-Text on Apple Silicon.
Two engines are supported, selected through ``config.STTModel.engine``:
* ``voxtral`` -> Mistral Voxtral (non-realtime batch model) via ``mlx-audio``.
Outperforms Whisper large-v3 on most benchmarks. The *realtime* Voxtral
variant is deliberately not used here: it ignores the requested language and
truncates when run one-shot on a full file.
* ``whisper`` -> ``mlx-whisper`` fallback for memory-constrained Macs.
"""
from ..models import STTModel
# Upper bound on generated tokens for a full-file transcription. The non-realtime
# Voxtral ``generate`` defaults to only 128 tokens, which would truncate any real
# recording, so we raise it well above the length of a long-form transcript.
_VOXTRAL_MAX_TOKENS = 32768
def _transcribe_voxtral(file_path: str, model_repo: str, language: str | None) -> str:
"""Transcribe using Mistral Voxtral through mlx-audio."""
from mlx_audio.stt.utils import load
model = load(model_repo)
# The non-realtime Voxtral model transcribes the whole file in one pass and
# honors ``language``. ``generate`` returns an object with a ``.text``
# attribute. ``language`` is optional (auto-detected when None).
kwargs: dict = {"max_tokens": _VOXTRAL_MAX_TOKENS}
if language:
kwargs["language"] = language
result = model.generate(file_path, **kwargs)
return result.text.strip()
def _transcribe_whisper(file_path: str, model_repo: str, language: str | None) -> str:
"""Transcribe using the mlx-whisper fallback engine."""
import mlx_whisper
result = mlx_whisper.transcribe(
file_path,
path_or_hf_repo=model_repo,
language=language, # None triggers auto-detection
)
return result["text"].strip()
def transcribe_file(
file_path: str,
output_file: str,
stt_model: STTModel,
language: str | None = None,
) -> str:
"""Transcribe ``file_path`` and persist the transcript to ``output_file``.
``language`` may be a language code (e.g. ``"en"``, ``"fr"``) or ``None`` for
automatic detection. Returns the full transcript text.
"""
print(f"Transcribing with {stt_model.engine} model: {stt_model.repo}")
if language:
print(f"Transcription language: {language}")
else:
print("Using automatic language detection")
print("Starting transcription (this may take a while for longer files)...")
if stt_model.engine == "voxtral":
text = _transcribe_voxtral(file_path, stt_model.repo, language)
elif stt_model.engine == "whisper":
text = _transcribe_whisper(file_path, stt_model.repo, language)
else:
raise ValueError(f"Unknown STT engine: {stt_model.engine!r}")
with open(output_file, "w") as tmp_file:
tmp_file.write(text)
print(f"Transcription saved to file: {output_file}")
return text