Damonar commited on
Commit
c90d3fc
·
verified ·
1 Parent(s): 19d9ce1

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -226
app.py DELETED
@@ -1,226 +0,0 @@
1
- import os
2
- import re
3
- import time
4
-
5
- import spaces
6
- import torch
7
- import gradio as gr
8
- import pysubs2
9
- from transformers import AutoTokenizer, AutoModelForCausalLM
10
-
11
- # ── تنظیمات ──────────────────────────────────────────────
12
- MODEL_ID = "google/translategemma-4b-it"
13
- SRC_LANG = "en"
14
- TGT_LANG = "fa"
15
- BATCH_SIZE = 12
16
- MAX_NEW_TOKENS = 1536
17
-
18
- HF_TOKEN = os.environ.get("HF_TOKEN")
19
-
20
- # ── بارگذاری مدل (سطح global، طبق الزام ZeroGPU) ────────
21
- tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN)
22
- model = AutoModelForCausalLM.from_pretrained(
23
- MODEL_ID,
24
- torch_dtype=torch.bfloat16,
25
- token=HF_TOKEN,
26
- )
27
- model.to("cuda")
28
- model.eval()
29
-
30
-
31
- # ── ساخت پرامپت و پارس خروجی ─────────────────────────────
32
- def build_batch(subs, start, end):
33
- chunks = []
34
- for n, idx in enumerate(range(start, end), 1):
35
- chunks.append(f"SEGMENT {n}:\n{subs[idx].text.strip()}")
36
- return "\n\n".join(chunks)
37
-
38
-
39
- def build_prompt(batch_text, count):
40
- return f"""<start_of_turn>user
41
- شما یک مترجم حرفه‌ای زیرنویس انگلیسی به فارسی هستید.
42
-
43
- متن زیر شامل دقیقاً {count} بخش (SEGMENT) پیاپی از یک گفتگوی طبیعی است. هر بخش را با توجه به بافت کل گفتگو، به فارسی محاوره‌ای، خودمونی و روان ترجمه کن — مثل زبان صحبت روزمره، نه فارسی رسمی و کتابی.
44
-
45
- قوانین مهم:
46
- - هر SEGMENT را جداگانه و دقیقاً به همان ترتیب ترجمه کن.
47
- - هرگز دو یا چند SEGMENT را با هم ادغام نکن و هیچ SEGMENT را نصف نکن.
48
- - برچسب‌های SEGMENT فقط برای هم‌ترازی هستند؛ آن‌ها را ترجمه نکن.
49
- - نام‌های خاص، اصطلاحات فنی و کلمات انگلیسی رایج (مثل اسم نرم‌افزارها) را به همون شکل انگلیسی نگه دار.
50
- - فقط خروجی خواسته‌شده را بده، بدون توضیح اضافه.
51
-
52
- خروجی را دقیقاً به این شکل بده:
53
- SEGMENT 1: <ترجمه فارسی>
54
- SEGMENT 2: <ترجمه فارسی>
55
- ...
56
- SEGMENT {count}: <ترجمه فارسی>
57
-
58
- گفتگو:
59
- {batch_text}<end_of_turn>
60
- <start_of_turn>model
61
- """
62
-
63
-
64
- def parse_output(text, count):
65
- text = (text or "").strip()
66
- text = re.sub(r"```(?:text|plaintext|srt)?", "", text, flags=re.I).replace("```", "").strip()
67
- text = re.sub(
68
- r"(?im)^\s*\[?\s*SEGMENT\s+(\d+)\s*\]?\s*[::-]\s*",
69
- lambda m: f"\n@@SEGMENT_{m.group(1)}@@\n",
70
- text
71
- )
72
- parts = re.split(r"\n@@SEGMENT_(\d+)@@\n", "\n" + text + "\n")
73
- found = {}
74
- for i in range(1, len(parts), 2):
75
- try:
76
- n = int(parts[i])
77
- value = parts[i + 1].strip()
78
- if value:
79
- found[n] = value
80
- except Exception:
81
- pass
82
-
83
- if count == 1 and not found:
84
- return [text]
85
-
86
- if len(found) != count or any(i not in found for i in range(1, count + 1)):
87
- raise ValueError(f"Expected {count} segments, got {len(found)}")
88
- return [found[i] for i in range(1, count + 1)]
89
-
90
-
91
- # ── تابع GPU (هر batch یک فراخوانی کوتاه است) ────────────
92
- @spaces.GPU(duration=60)
93
- def translate_batch(batch_text, count):
94
- prompt = build_prompt(batch_text, count)
95
- inputs = tokenizer(prompt, return_tensors="pt")
96
- inputs = {k: v.to(model.device) for k, v in inputs.items()}
97
-
98
- with torch.inference_mode():
99
- output = model.generate(
100
- **inputs,
101
- max_new_tokens=MAX_NEW_TOKENS,
102
- do_sample=False,
103
- pad_token_id=tokenizer.eos_token_id,
104
- )
105
-
106
- generated = output[0][inputs["input_ids"].shape[-1]:]
107
- answer = tokenizer.decode(generated, skip_special_tokens=True)
108
- return parse_output(answer, count)
109
-
110
-
111
- def translate_batch_retry(subs, start, end):
112
- batch_text = build_batch(subs, start, end)
113
- count = end - start
114
- try:
115
- return translate_batch(batch_text, count)
116
- except Exception as e:
117
- print(f"⚠️ Batch alignment failed: {e}")
118
- print("🔁 Retrying segment-by-segment...")
119
- result = []
120
- for idx in range(start, end):
121
- try:
122
- single = build_batch(subs, idx, idx + 1)
123
- result.extend(translate_batch(single, 1))
124
- except Exception as e2:
125
- print(f"❌ Segment {idx + 1}: {e2}")
126
- result.append(subs[idx].text.strip())
127
- return result
128
-
129
-
130
- def fix_bidi(text):
131
- """دور کلمات/عبارات لاتین و عددی، کاراکتر Unicode Isolate می‌ذاره
132
- تا در متن راست‌چین، جهت‌شون به‌هم نریزه."""
133
- LRI = "\u2066" # Left-to-Right Isolate
134
- PDI = "\u2069" # Pop Directional Isolate
135
- pattern = re.compile(r'[A-Za-z0-9][A-Za-z0-9 _\-\.]*[A-Za-z0-9]|[A-Za-z0-9]')
136
- return pattern.sub(lambda m: f"{LRI}{m.group(0)}{PDI}", text)
137
-
138
-
139
- def translate_srt_file(input_path, progress=gr.Progress()):
140
- subs = pysubs2.load(input_path, encoding="utf-8")
141
- total = len(subs)
142
- translations = [""] * total
143
- started = time.time()
144
-
145
- for start in range(0, total, BATCH_SIZE):
146
- end = min(total, start + BATCH_SIZE)
147
- translations[start:end] = translate_batch_retry(subs, start, end)
148
-
149
- elapsed = time.time() - started
150
- print(f"✅ {end}/{total} | {end/elapsed:.2f} segments/sec", flush=True)
151
- if progress:
152
- progress(end / total, desc=f"ترجمه {end}/{total}")
153
-
154
- output = pysubs2.SSAFile()
155
- preview = []
156
-
157
- for i, sub in enumerate(subs):
158
- original = sub.text.strip()
159
- translated = fix_bidi(translations[i].strip()) if original else ""
160
-
161
- output.append(pysubs2.SSAEvent(
162
- start=sub.start,
163
- end=sub.end,
164
- text=translated
165
- ))
166
-
167
- preview.append({
168
- "number": i + 1,
169
- "english": original,
170
- "persian": translated
171
- })
172
-
173
- base = os.path.splitext(os.path.basename(input_path))[0]
174
- output_path = f"/tmp/{base}_FA.srt"
175
- output.save(output_path, encoding="utf-8")
176
-
177
- print(f"✅ Finished in {(time.time()-started)/60:.1f} minutes")
178
- return output_path, preview
179
-
180
-
181
- def ui_translate(file, progress=gr.Progress()):
182
- if file is None:
183
- raise gr.Error("لطفاً یک فایل SRT انتخاب کنید.")
184
-
185
- output_path, data = translate_srt_file(file, progress)
186
-
187
- blocks = []
188
- for x in data[:50]:
189
- blocks.append(
190
- f"### #{x['number']}\n\n"
191
- f"🇬🇧 **English**\n\n{x['english']}\n\n"
192
- f"🇮🇷 **فارسی**\n\n{x['persian']}\n\n---"
193
- )
194
- preview = "\n\n".join(blocks)
195
- if len(data) > 50:
196
- preview += f"\n\n> نمایش ۵۰ مورد اول از {len(data)} subtitle."
197
- return output_path, preview
198
-
199
-
200
- with gr.Blocks(title="TranslateGemma 4B SRT Translator") as demo:
201
- gr.Markdown("""
202
- # 🎬 English → Persian SRT Translator
203
- ### Powered by TranslateGemma 4B
204
-
205
- مدل timestamp و شماره SRT را نمی‌بیند؛ فقط متن تمیز و segmentهای context-aware را ترجمه می‌کند.
206
- """)
207
-
208
- with gr.Row():
209
- input_file = gr.File(
210
- label="📂 English SRT",
211
- file_types=[".srt"],
212
- type="filepath"
213
- )
214
- output_file = gr.File(label="📥 Persian SRT")
215
-
216
- translate_button = gr.Button("🚀 شروع ترجمه", variant="primary", size="lg")
217
- gr.Markdown("## 👁️ پیش‌نمایش")
218
- preview = gr.Markdown()
219
-
220
- translate_button.click(
221
- fn=ui_translate,
222
- inputs=input_file,
223
- outputs=[output_file, preview]
224
- )
225
-
226
- demo.queue(max_size=5).launch()