GautamKishore commited on
Commit
6e7f02b
·
verified ·
1 Parent(s): ecb2aae

Gradio demo: app.py

Browse files
Files changed (1) hide show
  1. app.py +318 -0
app.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """PolyWhisper — Gradio demo for multilingual Indic ASR.
3
+
4
+ Run locally:
5
+ python app.py # local only
6
+ python app.py --share # public URL via gradio.live
7
+ python app.py --share --port 7860
8
+ """
9
+
10
+ import os
11
+ os.environ.setdefault("TRANSFORMERS_NO_ADVISORY_WARNINGS", "1")
12
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
13
+
14
+ import gradio as gr
15
+ import numpy as np
16
+ import time
17
+ import json
18
+ from pathlib import Path
19
+
20
+ # Global model cache
21
+ _model = None
22
+ _processor = None
23
+
24
+ LANGUAGES = {
25
+ "Hindi (हिन्दी)": "hi",
26
+ "Tamil (தமிழ்)": "ta",
27
+ "Telugu (తెలుగు)": "te",
28
+ "Bengali (বাংলা)": "bn",
29
+ "Marathi (मराठी)": "mr",
30
+ }
31
+
32
+ SAMPLE_AUDIO_DIR = Path(__file__).parent / "samples"
33
+
34
+
35
+ def load_model():
36
+ global _model, _processor
37
+ if _model is None:
38
+ from polywhisper.model import PolyWhisper
39
+ _model = PolyWhisper(backbone="small", device="cpu")
40
+ # Pre-load all adapters
41
+ for lang in ["hi", "ta", "te", "bn", "mr"]:
42
+ try:
43
+ _model.auto_load_adapter(lang, variant="prod")
44
+ except FileNotFoundError:
45
+ pass
46
+ _processor = _model.processor
47
+ return _model, _processor
48
+
49
+
50
+ def transcribe_audio(audio, language, max_tokens, beam_width, output_format):
51
+ """Transcribe uploaded or recorded audio."""
52
+ if audio is None:
53
+ return "Please upload or record audio first.", "", None
54
+
55
+ lang_code = LANGUAGES.get(language, "hi")
56
+ model, processor = load_model()
57
+
58
+ # Load audio
59
+ import soundfile as sf
60
+ if isinstance(audio, tuple):
61
+ # Gradio returns (sample_rate, numpy_array) for recorded audio
62
+ sr, audio_array = audio
63
+ audio_array = audio_array.astype(np.float32)
64
+ if audio_array.ndim > 1:
65
+ audio_array = audio_array.mean(axis=1)
66
+ if sr != 16000:
67
+ import resampy
68
+ audio_array = resampy.resample(audio_array, sr, 16000)
69
+ else:
70
+ # File path
71
+ audio_array, sr = sf.read(str(audio), dtype="float32")
72
+ if audio_array.ndim > 1:
73
+ audio_array = audio_array.mean(axis=1)
74
+ if sr != 16000:
75
+ import resampy
76
+ audio_array = resampy.resample(audio_array, sr, 16000)
77
+
78
+ # Transcribe
79
+ t0 = time.time()
80
+ result = _transcribe(model, processor, audio_array, lang_code, max_tokens, int(beam_width))
81
+ dt = time.time() - t0
82
+
83
+ # Format output
84
+ text = result["text"]
85
+ duration = result["duration"]
86
+
87
+ # Build output based on format
88
+ if output_format == "Plain Text":
89
+ output_text = text
90
+ srt_output = None
91
+ elif output_format == "JSON":
92
+ output_text = json.dumps(result, ensure_ascii=False, indent=2)
93
+ srt_output = None
94
+ elif output_format == "SRT Subtitles":
95
+ output_text = text
96
+ srt_lines = []
97
+ for i, seg in enumerate(result.get("segments", []), 1):
98
+ start = _fmt_srt(seg["start"])
99
+ end = _fmt_srt(seg["end"])
100
+ srt_lines.append(f"{i}\n{start} --> {end}\n{seg['text']}\n")
101
+ srt_output = "\n".join(srt_lines)
102
+ else:
103
+ output_text = text
104
+ srt_output = None
105
+
106
+ # Stats
107
+ stats = f"Language: {language} | Duration: {duration:.1f}s | Processed in: {dt:.1f}s | Speed: {duration/dt:.1f}× realtime"
108
+
109
+ return output_text, stats, srt_output
110
+
111
+
112
+ def _transcribe(model, processor, audio, lang, max_new_tokens=256, num_beams=1):
113
+ """Core transcription function."""
114
+ import torch
115
+ model.set_language(lang)
116
+
117
+ feats = processor.feature_extractor(
118
+ [audio], sampling_rate=16000, return_tensors="pt", padding=True
119
+ )["input_features"]
120
+ if feats.shape[-1] < 3000:
121
+ feats = torch.cat(
122
+ [feats, torch.zeros(1, 80, 3000 - feats.shape[-1], dtype=feats.dtype)], -1
123
+ )
124
+
125
+ out = model.generate(
126
+ feats, lang=lang, max_new_tokens=max_new_tokens,
127
+ num_beams=num_beams, use_cache=True, task="transcribe",
128
+ )
129
+ text = processor.decode(out[0], skip_special_tokens=True).strip()
130
+
131
+ return {
132
+ "text": text,
133
+ "lang": lang,
134
+ "duration": len(audio) / 16000,
135
+ "segments": [{"text": text, "start": 0.0, "end": len(audio) / 16000}],
136
+ }
137
+
138
+
139
+ def _fmt_srt(sec):
140
+ h = int(sec // 3600)
141
+ m = int((sec % 3600) // 60)
142
+ s = int(sec % 60)
143
+ ms = int((sec - int(sec)) * 1000)
144
+ return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
145
+
146
+
147
+ # ===== Custom CSS =====
148
+ CUSTOM_CSS = """
149
+ .main-title {
150
+ text-align: center;
151
+ margin-bottom: 0.5em;
152
+ }
153
+ .main-title h1 {
154
+ font-size: 2.2em;
155
+ font-weight: 700;
156
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
157
+ -webkit-background-clip: text;
158
+ -webkit-text-fill-color: transparent;
159
+ margin-bottom: 0.2em;
160
+ }
161
+ .main-title p {
162
+ font-size: 1.1em;
163
+ color: #666;
164
+ margin-top: 0;
165
+ }
166
+ .lang-badge {
167
+ display: inline-block;
168
+ padding: 2px 8px;
169
+ border-radius: 12px;
170
+ font-size: 0.85em;
171
+ font-weight: 500;
172
+ margin: 2px;
173
+ }
174
+ .stats-box {
175
+ background: #f0f7ff;
176
+ border: 1px solid #b3d4fc;
177
+ border-radius: 8px;
178
+ padding: 8px 12px;
179
+ font-family: monospace;
180
+ font-size: 0.9em;
181
+ }
182
+ footer { display: none !important; }
183
+ """
184
+
185
+
186
+ def build_app():
187
+ """Build the Gradio interface."""
188
+ with gr.Blocks(
189
+ title="PolyWhisper — Indic ASR Demo",
190
+ ) as app:
191
+ # Header
192
+ gr.HTML("""
193
+ <div class="main-title">
194
+ <h1>PolyWhisper</h1>
195
+ <p>Efficient Multilingual Indic Speech Recognition</p>
196
+ <p style="font-size:0.9em; color:#888;">
197
+ Hindi · Tamil · Telugu · Bengali · Marathi
198
+ &nbsp;|&nbsp; Powered by Whisper Small + LoRA
199
+ </p>
200
+ </div>
201
+ """)
202
+
203
+ with gr.Row():
204
+ # Left column: Input
205
+ with gr.Column(scale=1):
206
+ audio_input = gr.Audio(
207
+ label="Upload or Record Audio",
208
+ sources=["upload", "microphone"],
209
+ type="numpy",
210
+ waveform_options={"waveform_color": "#667eea"},
211
+ )
212
+
213
+ with gr.Row():
214
+ language = gr.Dropdown(
215
+ choices=list(LANGUAGES.keys()),
216
+ value="Hindi (हिन्दी)",
217
+ label="Language",
218
+ scale=2,
219
+ )
220
+ output_format = gr.Dropdown(
221
+ choices=["Plain Text", "JSON", "SRT Subtitles"],
222
+ value="Plain Text",
223
+ label="Output Format",
224
+ scale=1,
225
+ )
226
+
227
+ with gr.Row():
228
+ max_tokens = gr.Slider(
229
+ minimum=64, maximum=512, value=256, step=64,
230
+ label="Max Tokens",
231
+ )
232
+ beam_width = gr.Slider(
233
+ minimum=1, maximum=5, value=1, step=1,
234
+ label="Beam Width",
235
+ )
236
+
237
+ transcribe_btn = gr.Button(
238
+ "Transcribe",
239
+ variant="primary",
240
+ size="lg",
241
+ elem_id="transcribe-btn",
242
+ )
243
+
244
+ # Right column: Output
245
+ with gr.Column(scale=1):
246
+ output_text = gr.Textbox(
247
+ label="Transcription",
248
+ lines=8,
249
+ elem_id="output-text",
250
+ )
251
+ stats = gr.Textbox(
252
+ label="Stats",
253
+ interactive=False,
254
+ elem_classes=["stats-box"],
255
+ )
256
+ srt_output = gr.Textbox(
257
+ label="SRT Subtitles",
258
+ lines=8,
259
+ visible=False,
260
+ )
261
+
262
+ # Wire up output format visibility
263
+ output_format.change(
264
+ fn=lambda fmt: gr.update(visible=fmt == "SRT Subtitles"),
265
+ inputs=[output_format],
266
+ outputs=[srt_output],
267
+ )
268
+
269
+ # Wire up transcribe button
270
+ transcribe_btn.click(
271
+ fn=transcribe_audio,
272
+ inputs=[audio_input, language, max_tokens, beam_width, output_format],
273
+ outputs=[output_text, stats, srt_output],
274
+ )
275
+
276
+ # Examples
277
+ gr.Examples(
278
+ examples=[
279
+ ["polywhisper_output/data/audio_fleurs_hi_in/000000.wav", "Hindi (हिन्दी)"],
280
+ ["polywhisper_output/data/audio_fleurs_ta_in/000000.wav", "Tamil (தமிழ்)"],
281
+ ],
282
+ inputs=[audio_input, language],
283
+ label="Try these examples",
284
+ )
285
+
286
+ # Footer
287
+ gr.HTML("""
288
+ <div style="text-align:center; margin-top:2em; padding:1em; color:#888; font-size:0.85em;">
289
+ <p><b>PolyWhisper</b> — Frozen Whisper Small + Per-Language LoRA Adapters</p>
290
+ <p>
291
+ <a href="https://huggingface.co/eulogik/polywhisper" target="_blank">🤗 Model</a> ·
292
+ <a href="https://github.com/eulogikdeveloper/PolyWhisper" target="_blank"> GitHub</a> ·
293
+ <a href="https://huggingface.co/papers" target="_blank">📄 Paper</a>
294
+ </p>
295
+ <p>Hindi WER 37.2 | Tamil WER 60.4 on FLEURS benchmark</p>
296
+ </div>
297
+ """)
298
+
299
+ return app
300
+
301
+
302
+ if __name__ == "__main__":
303
+ import argparse
304
+ parser = argparse.ArgumentParser()
305
+ parser.add_argument("--share", action="store_true", help="Create public URL via gradio.live")
306
+ parser.add_argument("--port", type=int, default=7860)
307
+ parser.add_argument("--host", default="0.0.0.0")
308
+ args = parser.parse_args()
309
+
310
+ app = build_app()
311
+ app.launch(
312
+ server_name=args.host,
313
+ server_port=args.port,
314
+ share=args.share,
315
+ show_error=True,
316
+ css=CUSTOM_CSS,
317
+ theme=gr.themes.Soft(primary_hue="indigo", secondary_hue="purple"),
318
+ )