home-kitchen-admin / transcriber.py
Nguyen Minh Nhat
After-Shift Admin Assistant
11b9749
Raw
History Blame Contribute Delete
2.69 kB
"""Speech-to-text.
Primary path is Gemma 4's audio modality (one model for the whole pipeline). Gemma
caps audio at ~30s, so longer notes fall back to faster-whisper, which handles any
length. If the duration can't be probed, we try Gemma and fall back on any error.
"""
from __future__ import annotations
import threading
import gemma
import llama_backend
from config import (
GEMMA_AUDIO_MAX_SEC,
WHISPER_BEAM,
WHISPER_COMPUTE,
WHISPER_DEVICE,
WHISPER_LANG,
WHISPER_MODEL,
)
_whisper = None
_lock = threading.Lock()
def _duration(audio_path: str) -> float | None:
"""Best-effort audio length in seconds; None if it can't be determined."""
try:
import librosa
return float(librosa.get_duration(path=audio_path))
except Exception:
return None
def _get_whisper():
global _whisper
if _whisper is None:
with _lock:
if _whisper is None:
from faster_whisper import WhisperModel
_whisper = WhisperModel(
WHISPER_MODEL, device=WHISPER_DEVICE, compute_type=WHISPER_COMPUTE,
)
return _whisper
def _whisper_transcribe(audio_path: str) -> str:
model = _get_whisper()
segments, _info = model.transcribe(
audio_path,
language=WHISPER_LANG,
beam_size=WHISPER_BEAM,
vad_filter=True,
)
return " ".join(seg.text.strip() for seg in segments).strip()
def transcribe(audio_path: str) -> str:
"""Transcribe an audio file to plain text. Returns '' for no/empty input.
Order: llama.cpp (Gemma mtmd) -> Gemma transformers -> faster-whisper. The Gemma
paths only run for clips within the 30s audio cap; longer notes go straight to
whisper, which handles any length."""
if not audio_path:
return ""
short = (dur := _duration(audio_path)) is None or dur <= GEMMA_AUDIO_MAX_SEC
if short:
# Primary: llama.cpp mtmd (Gemma audio encoder)
try:
text = llama_backend.transcribe_audio(audio_path)
if text:
return text
except Exception:
pass
# Fallback: Gemma via transformers
try:
text = gemma.transcribe_audio(audio_path)
if text:
return text
except Exception:
pass
# Last resort (and all long audio): faster-whisper
return _whisper_transcribe(audio_path)
if __name__ == "__main__": # quick CLI: python transcriber.py path/to/audio.m4a
import sys
if len(sys.argv) < 2:
print("usage: python transcriber.py <audio-file>")
raise SystemExit(1)
print(transcribe(sys.argv[1]))