vocal_helper.lid module
vocal_helper.lid
Spoken-language diarization — partition an audio into mono-language regions so a code-switching recording can be transcribed one language at a time.
Why a posterior curve, not a boundary detector
Running whisper in naive "auto" mode locks onto the first language it
hears and transcribes (often translates) the whole file in it — so a
text-level detector only ever sees one language. Language must be resolved
before transcription. But a language switch, unlike a speaker change,
has no sharp acoustic cue (same speaker, same channel): it cannot be
detected as a boundary, only classified per unit of time and smoothed.
Following ASR-posterior language diarization (Wang et al., Interspeech 2019)
with Gaussian-smoothed change points, using whisper’s own language head as
the posterior source, detect_language_regions():
samples a language-posterior curve over overlapping windows (
language_posterior_curve()),Gaussian-smooths it over time,
takes the per-frame argmax,
places change points where the argmax flips,
absorbs sub-
min_region_sregions into a neighbour,locally refines each change point (fine re-scan + interpolated crossing),
snaps it to the nearest silence.
detect_language() remains the single-window primitive. All codes are
plain ISO-639-1 ("en", "fr", …); callers may restrict the candidate
set so whisper never ranks an un-routable close relative (Galician gl /
Catalan ca over Spanish es) on a short window.
Independent verification
cross_check_regions() corroborates each region with a second, fully
independent audio classifier — an ECAPA-TDNN trained on VoxLingua107
(SpeechBrain) — which shares nothing with whisper but the signal. It is an
optional dependency (pip install vocal-helper[lid]), imported lazily.
- class vocal_helper.lid.LangRegion(lang, t0, t1)[source]
Bases:
objectA contiguous mono-language span of audio (seconds), ISO-639-1
lang.
- class vocal_helper.lid.RegionVerdict(t0, t1, primary, speechbrain, sb_prob, agree)[source]
Bases:
objectOne region cross-checked against the independent SpeechBrain LID.
- vocal_helper.lid.cross_check_regions(pcm, regions, sample_rate=16000, *, supported=None)[source]
Corroborate each region’s language with the independent SpeechBrain LID.
An
agree=Falseverdict is a genuine disagreement between two models that share only the audio — a signal to inspect, not a code bug. Regions shorter than 1 s are too short to judge and pass through as agreeing.- Parameters:
- Return type:
- vocal_helper.lid.detect_language(pcm, *, model='large-v3-turbo-q5_0', threads=6, offset_ms=0, supported=None)[source]
Discover the actual language of
pcm(single window).Runs whisper.cpp’s
auto_detect_languageand, by default, returns whisper’s true argmax over its full language head — the language the input actually is, with no default and no restriction.supportedis an optional, opt-in routing guard: pass the ISO-639-1 codes you can actually route and detection is re-ranked strictly within that set (the “mind the codes” guard — on a short window whisper may rank a close relative such as Galicianglabove the routable Spanishes). Leave itNoneto let the input speak for itself.- Parameters:
pcm (NDArray[np.float32]) – Mono waveform window at the model’s sample rate.
model (str, optional) – whisper.cpp model name (default
DEFAULT_MODEL).threads (int, optional) – Inference threads (default
DEFAULT_THREADS).offset_ms (int, optional) – Offset into
pcmhanded to whisper’s detector (default0).supported (tuple[str, ...] or None, optional) – Opt-in routable-language whitelist.
None(default) means discover freely — return whisper’s own top language. A tuple restricts the answer to those codes (e.g.DEFAULT_SUPPORTED_LANGS).
- Returns:
(iso_639_1_code, probability)— the discovered language and its posterior. Whensupportedis given, the top-ranked code within it.- Return type:
Examples
>>> code, prob = detect_language(pcm) >>> code 'fr'
- vocal_helper.lid.detect_language_regions(pcm, sample_rate=16000, *, model='large-v3-turbo-q5_0', threads=6, window_s=10.0, hop_s=3.0, smooth_s=6.0, min_region_s=8.0, refine_s=4.0, snap_s=1.0, supported=None)[source]
Partition
pcminto mono-language regions (posterior-curve method).A language switch has no sharp acoustic cue, so instead of hunting for a boundary we (1) sample a posterior curve over overlapping windows, (2) Gaussian-smooth it, (3) take the per-frame argmax, (4) place change points where it flips, (5) absorb sub-
min_region_sregions, (6) locally refine each change point, and (7) snap it to the nearest silence. Each region’s language is discovered from the audio, never defaulted. The regions must be transcribed before committing to a language, so each is transcribed in its own.supportedis an opt-in routing whitelist (None= discover freely, seedetect_language()).Empty or too-short-to-identify audio has no language to discover, so this returns an empty list (no region invented) rather than guessing one.
- vocal_helper.lid.detect_language_regions_fast(pcm, sample_rate=16000, *, conf_gate=0.5, model='large-v3-turbo-q5_0', threads=6, supported=None)[source]
Partition
pcminto language regions, fast path for monolingual audio.Most real recordings are one language end to end, yet
detect_language_regions()pays for a full overlapping-window posterior scan on every file. This wrapper first runs a single cheap globaldetect_language()over the whole signal: when that detection clearsconf_gateon a routable language the file is taken to be monolingual and returned as one region (a single whisper call instead of dozens). Only when the global detection is uncertain — a genuinely code-switched or noisy file — does it fall back to the accurate posterior-curve segmentation ofdetect_language_regions().On a corpus of support-call recordings this cut per-file language identification from ~73 s to ~1 s with identical region output on the monolingual majority, while the low-confidence fallback still recovers the switches. It is a drop-in replacement for
detect_language_regions()wherever code-switching is the exception rather than the rule.- Parameters:
pcm (NDArray[np.float32]) – Mono waveform at
sample_rate.sample_rate (int, optional) – Sample rate of
pcmin Hz (defaultDEFAULT_SR).conf_gate (float, optional) – Minimum whole-file detection probability required to accept the single-region fast path (default
DEFAULT_FAST_CONF_GATE). Below it, the robustdetect_language_regions()scan runs.model (str, optional) – whisper.cpp model name (default
DEFAULT_MODEL).threads (int, optional) – Inference threads (default
DEFAULT_THREADS).supported (tuple[str, ...] or None, optional) – Opt-in routable-language whitelist.
None(default) discovers the language freely (seedetect_language()).
- Returns:
A single region spanning the file on the fast path, or the full multi-region partition on the fallback path. Always ≥ 1 region.
- Return type:
Examples
>>> regions = detect_language_regions_fast(pcm, 16_000) >>> regions[0].lang 'en'
- vocal_helper.lid.detect_language_speechbrain(pcm, *, supported=None)[source]
Identify the language of
pcmwith SpeechBrain VoxLingua107.An ECAPA-TDNN spoken-language classifier that shares nothing with whisper but the audio — the independent second opinion. VoxLingua107 labels are
"<iso>: <English name>"(e.g."fr: French"); the ISO-639-1 prefix is returned. By default the classifier’s true label is returned so the second opinion stays genuinely independent — it is never silently remapped to a preferred language. Passsupportedonly if you want an out-of-whitelist label dropped to the closest routable one.- Parameters:
- Returns:
(iso_639_1_code, probability).- Return type:
- vocal_helper.lid.language_posterior_curve(pcm, sample_rate=16000, *, model='large-v3-turbo-q5_0', threads=6, window_s=10.0, hop_s=3.0, supported=None)[source]
Sample a per-window language posterior over time.
Returns
(centers[T], langs[L], P[T, L])wherecentersare window centre times (s),langsthe candidate codes, and each row ofPa posterior overlangsfrom whisper’sauto_detect_languageon awindow_swindow centred there. Overlapping windows (hop_s < window_s) give a finely sampled curve.supportedis opt-in:None(default) discovers freely — the posterior axis is whisper’s full language head (adopted from the first usable window) so any language can surface. A tuple restricts (and renormalises over) that routable set instead.