Download transcribe.py from Modularcomputing/dynamic-asr: direct link, hf CLI and curl.
- Browser
- Download file 3.36 kB
-
https://huggingface.co/Modularcomputing/dynamic-asr/resolve/main/transcribe.py
- Command line
-
hf download hf://Modularcomputing/dynamic-asr/transcribe.py
-
curl -L -o transcribe.py https://huggingface.co/Modularcomputing/dynamic-asr/resolve/main/transcribe.py
3.36 kB
| """Dynamic ASR: transcribe Nigerian English, Pidgin, Igbo, Yoruba, Hausa and code-switched speech. | |
| pip install "omnilingual-asr>=0.2.0" huggingface_hub soundfile scipy | |
| python transcribe.py audio.wav [more.wav ...] [--model real|codeswitch] [--no-latin-mask] | |
| Model: Meta omniASR_CTC_1B_v2 (Apache-2.0) fine-tuned by Modular. Greedy CTC decoding, no language hint needed. | |
| Output: lowercase text without punctuation (Igbo without tone marks; Yoruba with tone marks). | |
| """ | |
| import argparse, unicodedata | |
| import numpy as np, torch, torch.nn.functional as F | |
| import soundfile as sf | |
| from scipy.signal import resample_poly | |
| from huggingface_hub import hf_hub_download | |
| from omnilingual_asr.models.inference.pipeline import ASRInferencePipeline # noqa: F401 (registers omniASR cards) | |
| from fairseq2.models.hub import load_model | |
| from fairseq2.data.tokenizers.hub import load_tokenizer | |
| from fairseq2.nn.batch_layout import BatchLayout | |
| REPO = "Modularcomputing/dynamic-asr" | |
| FILES = {"real": "real/model.pt", "codeswitch": "codeswitch-experimental/model.pt"} | |
| BASE = "omniASR_CTC_1B_v2" | |
| def latin_ok(s): | |
| for c in s: | |
| cat = unicodedata.category(c) | |
| if cat[0] == "M": | |
| if not ("\u0300" <= c <= "\u036f"): | |
| return False | |
| elif c.isalpha() and "LATIN" not in unicodedata.name(c, ""): | |
| return False | |
| return True | |
| class DynamicASR: | |
| def __init__(self, model="real", device="cuda" if torch.cuda.is_available() else "cpu", latin_mask=True, ckpt=None): | |
| self.dev = torch.device(device) | |
| dtype = torch.bfloat16 if self.dev.type == "cuda" else torch.float32 | |
| self.model = load_model(BASE, device=self.dev, dtype=dtype).eval() # base weights, then overwritten | |
| ck = torch.load(ckpt or hf_hub_download(REPO, FILES[model]), map_location="cpu", weights_only=False) | |
| self.model.load_state_dict({k: v.to(dtype) for k, v in ck["model"].items()}) | |
| self.dtype = dtype | |
| self.dec = load_tokenizer(BASE).create_decoder(skip_special_tokens=True) | |
| V = self.model.final_proj.weight.shape[0] | |
| self.bad = torch.tensor([i for i in range(V) if not latin_ok(str(self.dec(torch.tensor([i]))))], device=self.dev) if latin_mask else None | |
| def __call__(self, wav, sr=16000): | |
| y = np.asarray(wav, dtype=np.float32) | |
| if y.ndim > 1: y = y.mean(1) | |
| if sr != 16000: y = resample_poly(y, 16000, sr).astype(np.float32) | |
| x = torch.from_numpy(y); x = F.layer_norm(x, x.shape)[None].to(self.dev, self.dtype) | |
| logits, lay = self.model(x, BatchLayout(x.shape, seq_lens=[x.shape[1]], device=self.dev)) | |
| if self.bad is not None: logits[..., self.bad] = -float("inf") | |
| s = logits[0, : lay.seq_lens[0]].argmax(-1); m = torch.ones_like(s, dtype=torch.bool); m[1:] = s[1:] != s[:-1] | |
| return str(self.dec(s[m])) | |
| if __name__ == "__main__": | |
| ap = argparse.ArgumentParser(); ap.add_argument("audio", nargs="+"); ap.add_argument("--model", default="real", choices=list(FILES)) | |
| ap.add_argument("--no-latin-mask", action="store_true"); ap.add_argument("--ckpt", default=None, help="local model.pt instead of the Hub") | |
| a = ap.parse_args() | |
| asr = DynamicASR(a.model, latin_mask=not a.no_latin_mask, ckpt=a.ckpt) | |
| for f in a.audio: | |
| w, sr = sf.read(f, dtype="float32"); print(f"{f}\t{asr(w, sr)}") | |