Dubberkuro commited on
Commit
abfb253
·
verified ·
1 Parent(s): ea7c7c7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +287 -0
app.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import subprocess
4
+ import tempfile
5
+ from functools import lru_cache
6
+ from pathlib import Path
7
+
8
+ import gradio as gr
9
+ import torch
10
+ from transformers import pipeline
11
+
12
+ # =========================
13
+ # Settings
14
+ # =========================
15
+ WHISPER_MODEL = os.getenv("WHISPER_MODEL", "openai/whisper-small")
16
+ NLLB_MODEL = os.getenv("NLLB_MODEL", "facebook/nllb-200-distilled-600M")
17
+ TARGET_LANG = "mya_Mymr" # Burmese (Myanmar)
18
+
19
+ # Common NLLB language codes
20
+ LANGS = {
21
+ "English": "eng_Latn",
22
+ "Myanmar": "mya_Mymr",
23
+ "Thai": "tha_Thai",
24
+ "Japanese": "jpn_Jpan",
25
+ "Korean": "kor_Hang",
26
+ "Chinese (Simplified)": "zho_Hans",
27
+ "Hindi": "hin_Deva",
28
+ "French": "fra_Latn",
29
+ "Spanish": "spa_Latn",
30
+ "German": "deu_Latn",
31
+ "Russian": "rus_Cyrl",
32
+ "Arabic": "arb_Arab",
33
+ "Indonesian": "ind_Latn",
34
+ "Vietnamese": "vie_Latn",
35
+ }
36
+
37
+ VIDEO_EXTS = {".mp4", ".mkv", ".mov", ".webm", ".avi", ".flv", ".m4v"}
38
+ AUDIO_EXTS = {".mp3", ".wav", ".m4a", ".aac", ".flac", ".ogg", ".opus"}
39
+
40
+
41
+ # =========================
42
+ # Helpers
43
+ # =========================
44
+ def get_device():
45
+ return 0 if torch.cuda.is_available() else -1
46
+
47
+
48
+ @lru_cache(maxsize=1)
49
+ def get_asr():
50
+ device = get_device()
51
+ kwargs = {
52
+ "task": "automatic-speech-recognition",
53
+ "model": WHISPER_MODEL,
54
+ "device": device,
55
+ }
56
+ return pipeline(**kwargs)
57
+
58
+
59
+ @lru_cache(maxsize=32)
60
+ def get_translator(src_lang_code: str):
61
+ device = get_device()
62
+ kwargs = {
63
+ "task": "translation",
64
+ "model": NLLB_MODEL,
65
+ "src_lang": src_lang_code,
66
+ "tgt_lang": TARGET_LANG,
67
+ "device": device,
68
+ }
69
+ return pipeline(**kwargs)
70
+
71
+
72
+ def ffmpeg_to_wav(input_path: str) -> str:
73
+ """
74
+ Convert any audio/video file to 16kHz mono WAV for stable ASR.
75
+ Requires ffmpeg installed.
76
+ """
77
+ input_path = str(input_path)
78
+ out_dir = tempfile.mkdtemp(prefix="audio_")
79
+ out_wav = str(Path(out_dir) / "audio.wav")
80
+
81
+ cmd = [
82
+ "ffmpeg",
83
+ "-y",
84
+ "-i",
85
+ input_path,
86
+ "-vn",
87
+ "-ac",
88
+ "1",
89
+ "-ar",
90
+ "16000",
91
+ out_wav,
92
+ ]
93
+
94
+ try:
95
+ subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
96
+ except FileNotFoundError as e:
97
+ raise RuntimeError("ffmpeg မတွေ့ပါ။ apt.txt ထဲမှာ ffmpeg ထည့်ပါ။") from e
98
+ except subprocess.CalledProcessError as e:
99
+ raise RuntimeError(
100
+ "ဖိုင်ကို audio အဖြစ်ပြောင်းမရပါ။ video/audio file ကို ပြန်စစ်ပါ။"
101
+ ) from e
102
+
103
+ return out_wav
104
+
105
+
106
+ def format_timestamp(seconds: float) -> str:
107
+ if seconds is None:
108
+ seconds = 0.0
109
+ ms = int(round((seconds - int(seconds)) * 1000))
110
+ total = int(seconds)
111
+ hh = total // 3600
112
+ mm = (total % 3600) // 60
113
+ ss = total % 60
114
+ return f"{hh:02d}:{mm:02d}:{ss:02d},{ms:03d}"
115
+
116
+
117
+ def smart_two_line(text: str, width: int = 44) -> str:
118
+ text = re.sub(r"\s+", " ", text).strip()
119
+ if len(text) <= width:
120
+ return text
121
+
122
+ # Try to split near the middle on a space
123
+ words = text.split(" ")
124
+ if len(words) == 1:
125
+ mid = len(text) // 2
126
+ return text[:mid].rstrip() + "\n" + text[mid:].lstrip()
127
+
128
+ total_len = len(text)
129
+ best_idx = 1
130
+ best_diff = float("inf")
131
+ current = 0
132
+
133
+ for i, w in enumerate(words[:-1], start=1):
134
+ current += len(w) + 1
135
+ diff = abs(current - total_len / 2)
136
+ if diff < best_diff:
137
+ best_diff = diff
138
+ best_idx = i
139
+
140
+ line1 = " ".join(words[:best_idx]).strip()
141
+ line2 = " ".join(words[best_idx:]).strip()
142
+
143
+ if len(line1) > width and len(line2) > width:
144
+ mid = len(text) // 2
145
+ return text[:mid].rstrip() + "\n" + text[mid:].lstrip()
146
+
147
+ return line1 + "\n" + line2
148
+
149
+
150
+ def translate_text(src_text: str, src_lang_code: str) -> str:
151
+ src_text = src_text.strip()
152
+ if not src_text:
153
+ return ""
154
+
155
+ # If already Burmese, keep as-is
156
+ if src_lang_code == TARGET_LANG:
157
+ return src_text
158
+
159
+ translator = get_translator(src_lang_code)
160
+ result = translator(src_text, max_new_tokens=256)
161
+
162
+ if isinstance(result, list) and result:
163
+ return result[0].get("translation_text", "").strip()
164
+ if isinstance(result, dict):
165
+ return result.get("translation_text", "").strip()
166
+ return str(result).strip()
167
+
168
+
169
+ def normalize_chunks(asr_result):
170
+ chunks = asr_result.get("chunks")
171
+ if chunks and isinstance(chunks, list):
172
+ return chunks
173
+
174
+ # Fallback: single subtitle
175
+ text = asr_result.get("text", "").strip()
176
+ if not text:
177
+ return []
178
+ return [{"timestamp": (0.0, None), "text": text}]
179
+
180
+
181
+ def build_srt(input_path: str, source_language_name: str):
182
+ if not input_path:
183
+ raise gr.Error("ဖိုင်တင်ပါ။")
184
+
185
+ if source_language_name not in LANGS:
186
+ raise gr.Error("Source language မမှန်ပါ။")
187
+
188
+ src_lang_code = LANGS[source_language_name]
189
+ audio_path = ffmpeg_to_wav(input_path)
190
+
191
+ asr = get_asr()
192
+ # Whisper pipeline supports timestamps for ASR. We keep them for SRT timing.
193
+ asr_result = asr(
194
+ audio_path,
195
+ return_timestamps=True,
196
+ generate_kwargs={
197
+ "task": "transcribe",
198
+ "language": src_lang_code,
199
+ },
200
+ )
201
+
202
+ chunks = normalize_chunks(asr_result)
203
+
204
+ subtitles = []
205
+ index = 1
206
+
207
+ for chunk in chunks:
208
+ text = (chunk.get("text") or "").strip()
209
+ if not text:
210
+ continue
211
+
212
+ ts = chunk.get("timestamp")
213
+ start = 0.0
214
+ end = None
215
+
216
+ if isinstance(ts, (tuple, list)) and len(ts) >= 2:
217
+ start = ts[0] if ts[0] is not None else 0.0
218
+ end = ts[1]
219
+
220
+ translated = translate_text(text, src_lang_code)
221
+ if not translated:
222
+ continue
223
+
224
+ if end is None:
225
+ # Safe fallback duration if the model gives only a start timestamp
226
+ end = start + max(2.0, min(6.0, len(translated) / 10.0))
227
+
228
+ subtitles.append(
229
+ f"{index}\n"
230
+ f"{format_timestamp(float(start))} --> {format_timestamp(float(end))}\n"
231
+ f"{smart_two_line(translated)}\n"
232
+ )
233
+ index += 1
234
+
235
+ if not subtitles:
236
+ raise gr.Error("Subtitle ထုတ်မရပါ။ အသံမရှင်းတာ သို့မဟုတ် ဖိုင်ပြဿနာရှိနိုင်တယ်။")
237
+
238
+ srt_text = "\n".join(subtitles).strip() + "\n"
239
+
240
+ out_dir = tempfile.mkdtemp(prefix="srt_")
241
+ out_path = str(Path(out_dir) / f"{Path(input_path).stem}_burmese.srt")
242
+ with open(out_path, "w", encoding="utf-8") as f:
243
+ f.write(srt_text)
244
+
245
+ preview = srt_text[:8000]
246
+ return out_path, preview
247
+
248
+
249
+ # =========================
250
+ # UI
251
+ # =========================
252
+ with gr.Blocks(title="Burmese SRT Generator") as demo:
253
+ gr.Markdown(
254
+ """
255
+ # Burmese SRT Generator
256
+ Video / Audio file တင်ပြီး မြန်မာလို subtitle `.srt` ထုတ်မယ်။
257
+ """
258
+ )
259
+
260
+ with gr.Row():
261
+ input_file = gr.File(
262
+ label="Video / Audio File",
263
+ file_types=[
264
+ ".mp4", ".mkv", ".mov", ".webm", ".avi", ".flv", ".m4v",
265
+ ".mp3", ".wav", ".m4a", ".aac", ".flac", ".ogg", ".opus",
266
+ ],
267
+ type="filepath",
268
+ )
269
+ source_lang = gr.Dropdown(
270
+ choices=list(LANGS.keys()),
271
+ value="English",
272
+ label="Source Language",
273
+ )
274
+
275
+ run_btn = gr.Button("Generate Burmese SRT", variant="primary")
276
+
277
+ output_file = gr.File(label="Download .srt")
278
+ preview_box = gr.Textbox(label="Preview", lines=18)
279
+
280
+ run_btn.click(
281
+ fn=build_srt,
282
+ inputs=[input_file, source_lang],
283
+ outputs=[output_file, preview_box],
284
+ )
285
+
286
+ if __name__ == "__main__":
287
+ demo.launch()