submatch-backend / modules /transcriber.py
Janesh's picture
feat: SubMatch backend v2.0 β€” faster-whisper, Tesseract OCR, FastAPI
b165696
Raw
History Blame Contribute Delete
3.11 kB
"""
Audio transcription using faster-whisper (CTranslate2 backend).
faster-whisper is 4-8x faster than openai-whisper on CPU and produces
identical output. It also supports int8 quantization which halves memory
usage with negligible accuracy loss.
No API key required β€” model runs 100% locally on your machine.
"""
from __future__ import annotations
import logging
from typing import Callable
logger = logging.getLogger(__name__)
# Map our model size names to faster-whisper equivalents
_MODEL_MAP = {
"tiny": "tiny",
"base": "base",
"small": "small",
"medium": "medium",
"large": "large-v3",
"large-v2": "large-v2",
"large-v3": "large-v3",
}
class AudioTranscriber:
def __init__(
self,
model_size: str = "base",
progress_hook: Callable | None = None,
):
self.model_size = _MODEL_MAP.get(model_size, model_size)
self._progress_hook = progress_hook
self._model = None # lazy-loaded on first use
def transcribe(
self,
video_path: str,
language: str | None = None,
) -> list[dict]:
"""
Transcribe the audio track of `video_path`.
Parameters
----------
video_path : str
Path to any video or audio file (ffmpeg handles extraction).
language : str | None
ISO 639-1 code ('hi', 'kn', 'en', …). None = auto-detect.
Returns
-------
list[dict] β€” [{ "start": float, "end": float, "text": str }, ...]
"""
from faster_whisper import WhisperModel
if self._model is None:
logger.info("Loading faster-whisper model: %s (int8, cpu)", self.model_size)
# int8 compute type: 2x less memory, ~2x faster, negligible accuracy loss
self._model = WhisperModel(
self.model_size,
device="cpu",
compute_type="int8",
)
logger.info("Transcribing: %s language=%s", video_path, language or "auto")
segments_iter, info = self._model.transcribe(
video_path,
language=language,
task="transcribe",
beam_size=1, # greedy decoding β€” 3x faster, near-identical accuracy
vad_filter=True, # skip silent parts β€” skips music/silence automatically
vad_parameters={"min_silence_duration_ms": 500},
word_timestamps=False,
condition_on_previous_text=False, # prevents hallucination loops
)
logger.info(
"Detected language: %s (prob=%.2f)",
info.language, info.language_probability,
)
segments: list[dict] = []
for seg in segments_iter:
text = seg.text.strip()
if text:
segments.append({
"start": round(seg.start, 3),
"end": round(seg.end, 3),
"text": text,
})
logger.info("Transcription complete: %d segments", len(segments))
return segments