Spaces:
Running on Zero
Running on Zero
| import os | |
| import time | |
| import spaces # MUST be before torch (ZeroGPU) | |
| import gradio as gr | |
| import torch | |
| import numpy as np | |
| from transformers import WhisperForConditionalGeneration, WhisperProcessor | |
| # ================= CONFIG ================= | |
| DEVICE = "cuda" if torch.cuda.is_available() else "cpu" | |
| DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32 | |
| SR = 16000 | |
| # ================= MODELS ================= | |
| MODELS = { | |
| "Sabtain (Stable)": "Sabtain-Dev/STT-Whisper-Pashto", | |
| "ihanif (Experimental)": "ihanif/exp_009_small_cv24_vanilla" | |
| } | |
| DEFAULT_MODEL = "Sabtain (Stable)" | |
| # model cache | |
| _cache = {} | |
| # ================= LOAD MODEL ================= | |
| def load_model(model_id): | |
| if model_id not in _cache: | |
| print(f"[Loading] {model_id}") | |
| model = WhisperForConditionalGeneration.from_pretrained( | |
| model_id, | |
| torch_dtype=DTYPE, | |
| low_cpu_mem_usage=True | |
| ).to(DEVICE) | |
| model.eval() | |
| processor = WhisperProcessor.from_pretrained(model_id) | |
| _cache[model_id] = (model, processor) | |
| return _cache[model_id] | |
| # ================= AUDIO ================= | |
| def load_audio(path): | |
| import librosa | |
| audio, _ = librosa.load(path, sr=SR, mono=True) | |
| return audio | |
| # ================= DECODER FIX ================= | |
| def get_decoder_ids(model, processor): | |
| # use model config if available (important for ihanif model) | |
| if hasattr(model.config, "forced_decoder_ids") and model.config.forced_decoder_ids: | |
| return model.config.forced_decoder_ids | |
| # fallback for Sabtain model | |
| return processor.get_decoder_prompt_ids( | |
| language="pashto", | |
| task="transcribe" | |
| ) | |
| # ================= INFERENCE ================= | |
| def transcribe(audio_path, model_name): | |
| if not audio_path: | |
| return "", "⚠️ Upload audio first" | |
| model_id = MODELS[model_name] | |
| model, processor = load_model(model_id) | |
| audio = load_audio(audio_path) | |
| inputs = processor(audio, sampling_rate=SR, return_tensors="pt") | |
| input_features = inputs.input_features.to(DEVICE, dtype=DTYPE) | |
| forced_ids = get_decoder_ids(model, processor) | |
| start = time.time() | |
| with torch.no_grad(): | |
| pred_ids = model.generate( | |
| input_features, | |
| forced_decoder_ids=forced_ids, | |
| max_length=256 | |
| ) | |
| text = processor.batch_decode( | |
| pred_ids, | |
| skip_special_tokens=True | |
| )[0] | |
| elapsed = round(time.time() - start, 2) | |
| return text.strip(), f"⏱️ {elapsed}s | {model_name}" | |
| # ================= DOWNLOAD ================= | |
| def download_txt(text): | |
| if not text: | |
| return None | |
| path = "/tmp/transcription.txt" | |
| with open(path, "w", encoding="utf-8") as f: | |
| f.write(text) | |
| return path | |
| # ================= UI ================= | |
| def build_ui(): | |
| with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("## 🎙️ Pashto Speech-to-Text") | |
| model_select = gr.Dropdown( | |
| choices=list(MODELS.keys()), | |
| value=DEFAULT_MODEL, | |
| label="Model" | |
| ) | |
| audio = gr.Audio( | |
| sources=["upload", "microphone"], | |
| type="filepath", | |
| label="Audio" | |
| ) | |
| btn = gr.Button("Transcribe", variant="primary") | |
| output = gr.Textbox( | |
| label="Transcription", | |
| lines=5 | |
| ) | |
| status = gr.Markdown() | |
| download = gr.DownloadButton("Download .txt") | |
| btn.click( | |
| transcribe, | |
| inputs=[audio, model_select], | |
| outputs=[output, status] | |
| ) | |
| download.click( | |
| download_txt, | |
| inputs=output, | |
| outputs=download | |
| ) | |
| return demo | |
| # ================= RUN ================= | |
| if __name__ == "__main__": | |
| demo = build_ui() | |
| demo.queue().launch( | |
| server_name="0.0.0.0", | |
| server_port=int(os.environ.get("PORT", 7860)), | |
| show_error=True | |
| ) |