"""NepaliConformer demo — transcribe Nepali speech, tuned for real telephone audio.""" import os import gradio as gr import torch # Preload at startup: the checkpoint is 485 MB — downloading it lazily made the first # user's request hang for minutes and look broken. from huggingface_hub import hf_hub_download from nemo.collections.asr.models import EncDecHybridRNNTCTCBPEModel _path = hf_hub_download("ampixa/nepali-conformer-offline", "nepali_conformer_offline.nemo") torch.set_num_threads(2) MODEL = EncDecHybridRNNTCTCBPEModel.restore_from(_path, map_location="cpu") MODEL.eval() print("model preloaded", flush=True) DESCRIPTION = ( "121M Conformer trained on ~1,655 h of conversational Nepali. " "**33.8% WER on real call-center audio** (NepTel benchmark) where Whisper-large-v3 " "zero-shot scores ~99%. Honest limitations and the full benchmark: " "[github.com/Ampixa/nepaliconformer](https://github.com/Ampixa/nepaliconformer). " "CPU demo — a 30 s clip takes roughly 10-20 s. Example clips are real call-center " "audio (CC-BY-4.0, © InfoBayAI)." ) ARTICLE = ( "Recording transcribes itself as soon as you press ⏹ stop. " "Mic blocked? [Open the demo full-screen](https://voidash-nepaliconformer.hf.space) · " "Model downloads and usage: " "[github.com/Ampixa/nepaliconformer](https://github.com/Ampixa/nepaliconformer#download--run)" ) def get_model(): return MODEL def _prepare(path): """Normalize any input to mono 16 kHz PCM and return (path, duration_seconds). Browser microphone recordings arrive as stereo (and sometimes as webm/mp4 that libsndfile cannot open at all). The model takes a mono signal only: feeding it a two-channel file raises "Output shape expected = (batch, time)" inside NeMo. """ import subprocess import tempfile import soundfile as sf try: info = sf.info(path) needs_convert = info.channels != 1 or info.samplerate != 16000 duration = info.duration except Exception: needs_convert = True duration = None if not needs_convert: return path, duration out = tempfile.NamedTemporaryFile(suffix=".wav", delete=False).name proc = subprocess.run( ["ffmpeg", "-y", "-loglevel", "error", "-i", path, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", out], capture_output=True, text=True, ) if proc.returncode != 0: raise gr.Error( "Could not read that audio (ffmpeg: %s). Try a WAV or MP3 file." % (proc.stderr.strip().splitlines() or ["unknown error"])[-1] ) return out, sf.info(out).duration def transcribe(audio_path): if not audio_path: # Most often: the user pressed Transcribe while the mic was still recording, # so no file exists yet. return "Press ⏹ stop to finish the recording — it transcribes automatically." audio_path, duration = _prepare(audio_path) if duration is not None and duration > 60: return "Please keep clips under 60 seconds for this CPU demo." model = get_model() out = model.transcribe([audio_path], batch_size=1, verbose=False)[0] text = out.text if hasattr(out, "text") else str(out) return " ".join(t for t in text.split() if t != "") or "(no speech detected)" example_files = [[f"examples/{f}"] for f in sorted(os.listdir("examples"))] \ if os.path.isdir("examples") else [] with gr.Blocks(title="NepaliConformer — Nepali ASR for real telephone calls") as demo: gr.Markdown("# NepaliConformer — Nepali ASR for real telephone calls") gr.Markdown(DESCRIPTION) with gr.Row(): with gr.Column(): audio_in = gr.Audio( sources=["microphone", "upload"], type="filepath", label="Nepali speech (mic or file, ≤60 s)", ) with gr.Row(): clear_btn = gr.Button("Clear") submit_btn = gr.Button("Transcribe", variant="primary") with gr.Column(): text_out = gr.Textbox(label="Transcript (Devanagari)", lines=6) # Transcribe as soon as the recording stops or a file lands: waiting for an explicit # Transcribe click made the demo look broken for anyone who never pressed stop. audio_in.stop_recording(transcribe, audio_in, text_out) audio_in.upload(transcribe, audio_in, text_out) submit_btn.click(transcribe, audio_in, text_out) clear_btn.click(lambda: (None, ""), None, [audio_in, text_out]) if example_files: gr.Examples( examples=example_files, inputs=audio_in, outputs=text_out, fn=transcribe, cache_examples=False, run_on_click=True, ) gr.Markdown(ARTICLE) if __name__ == "__main__": demo.launch(ssr_mode=False, show_error=True)