Skip to content

Python API

from vaak import get_backend, load_audio
audio = load_audio("clip.wav") # float32 numpy, 16 kHz mono
backend = get_backend("faster-whisper:small") # lazy: weights load on first use
result = backend.transcribe(audio, language="hi") # language=None -> auto-detect
result.text # str
result.language # "hi"
result.segments # list[Segment(start: float, end: float, text: str)]
result.duration # audio seconds
result.rtf # real-time factor = processing_time / duration (lower is faster)
result.backend # "faster-whisper"
result.model # "small"
from vaak import load_audio
audio = load_audio(src, sample_rate=16000)
  • src: a path, a file-like object, or raw bytes. Anything ffmpeg can decode.
  • Returns a mono np.ndarray of dtype float32 in the range [-1, 1] at sample_rate.

All backends expect 16 kHz. If you already have a numpy array at another rate, pass sample_rate to transcribe() and the backend resamples.

from vaak import get_backend
backend = get_backend("faster-whisper:small", device="auto")
  • spec: <backend>:<model>. See model specs.
  • device: "auto", "cpu" or "cuda". auto picks CUDA when available.
  • **kwargs are passed to the backend constructor. For example compute_type="int8" for faster-whisper.

Construction is cheap. Weights load on the first transcribe() call, or when you call backend.load() explicitly, which is useful to pay the cost at startup.

Every backend implements this ABC from vaak/backends/base.py:

class Backend(ABC):
name: str # registry key, e.g. "faster-whisper"
model_id: str # part after the colon
languages: set[str] | None # ISO 639-1 codes supported; None = multilingual/any
device: str # "auto" | "cpu" | "cuda"
def load(self) -> None # idempotent
def transcribe(self, audio: np.ndarray, sample_rate: int = 16000, language: str | None = None) -> Transcript

Check backend.languages before sending audio in a language the model cannot handle. None means the model is multilingual, for example Whisper. indic-conformer:hi reports {"hi"}.

@dataclass
class Segment:
start: float # seconds
end: float # seconds
text: str
@dataclass
class Transcript:
text: str
language: str
segments: list[Segment]
duration: float
rtf: float
backend: str
model: str

Both are plain dataclasses. dataclasses.asdict(result) gives the same JSON the HTTP API returns.

from vaak.backends import list_backends
for info in list_backends():
print(info.name, info.available, info.example)

list_backends() returns a BackendInfo per registered backend without importing heavy runtimes. available is whether the runtime imports cleanly.

from vaak.metrics import wer, cer, normalize_text
ref = normalize_text("यह एक परीक्षण है।", "hi")
hyp = normalize_text(result.text, "hi")
print(wer(ref, hyp), cer(ref, hyp))

normalize_text applies NFC normalisation, lowercases Latin script, strips punctuation including । and ॥, and collapses whitespace. Always normalise both sides. See the benchmarks methodology.

from pathlib import Path
from vaak import get_backend, load_audio
backend = get_backend("faster-whisper:large-v3-turbo", device="cuda")
backend.load()
for path in sorted(Path("calls").glob("*.wav")):
result = backend.transcribe(load_audio(path), language="hi")
print(path.name, f"rtf={result.rtf:.3f}", result.text[:80])

Subclass Backend, implement load() and transcribe(), and register it in vaak/backends/__init__.py under a new prefix. Keep the heavy import inside load() so vaak models and the registry stay fast. See contributing.