nepaliconformer / app.py
voidash's picture
examples: run_on_click so clicking a sample transcribes it
ee038c5 verified
Raw
History Blame Contribute Delete
4.83 kB
"""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 != "<breath>") 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)