takumi0002 commited on
Commit
364c005
Β·
verified Β·
1 Parent(s): 6b4c1c6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -155
app.py CHANGED
@@ -3,8 +3,9 @@ import requests
3
  import gradio as gr
4
  import re
5
  import random
 
6
 
7
- # Dictionary αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αžšαž€αŸ’αžŸαžΆαž‘αž»αž€αžŸαŸ’αžαžΆαž“αž—αžΆαž– Key
8
  key_status_registry = {}
9
 
10
  class AIService:
@@ -24,192 +25,128 @@ class AIService:
24
 
25
  @staticmethod
26
  def get_status_html(engine):
27
- if "Gemini" in engine: prefix = "GEMINI_API_KEY_"
28
- elif "Llama" in engine: prefix = "GROQ_API_KEY_"
29
- else: prefix = "SEA_LION_API_KEY"
30
-
31
  keys = AIService.get_all_keys(prefix)
32
  label = engine.split(" ")[-1]
33
- html = f"<div style='display: flex; gap: 10px; align-items: center; margin-bottom: 10px;'><b style='color: #94a3b8; font-size: 12px;'>{label} Status:</b>"
34
- for i, k in enumerate(keys):
35
  state = key_status_registry.get(k, "ready")
36
  color = "#22c55e" if state == "ready" else "#ef4444"
37
- ani = "animation: pulse 1.5s infinite;" if state == "ready" else ""
38
- html += f"<div style='width: 12px; height: 12px; background: {color}; border-radius: 50%; box-shadow: 0 0 8px {color}; {ani}'></div>"
39
- html += "</div>"
40
- return html
41
 
42
  @staticmethod
43
- def translate_gemini(prompt):
44
- keys = AIService.get_all_keys("GEMINI_API_KEY_")
45
- rand_val = random.randint(1, 1000)
46
- for key in keys:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  try:
48
- url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key={key}"
49
- res = requests.post(url, json={
50
- "contents": [{"parts": [{"text": f"{prompt}\nVerification_Code: {rand_val}"}]}],
51
- "generationConfig": {"temperature": 0.75}
52
- }, timeout=30)
53
- if res.status_code == 200:
54
- key_status_registry[key] = "ready"
55
- return res.json()['candidates'][0]['content']['parts'][0]['text'].strip()
56
- key_status_registry[key] = "dead"
57
- except: key_status_registry[key] = "dead"; continue
58
- return None
59
-
60
- @staticmethod
61
- def translate_llama(prompt):
62
- keys = AIService.get_all_keys("GROQ_API_KEY_")
63
- rand_val = random.randint(1, 1000)
64
- for key in keys:
65
- try:
66
- headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
67
- payload = {
68
- "model": "llama-3.3-70b-versatile",
69
- "messages": [
70
- {"role": "system", "content": "You are a professional SRT translator. Keep the exact format. Translate with high-level vocabulary."},
71
- {"role": "user", "content": f"{prompt}\nSeed: {rand_val}"}
72
- ],
73
- "temperature": 0.7
74
- }
75
- res = requests.post("https://api.groq.com/openai/v1/chat/completions", headers=headers, json=payload, timeout=30)
76
  if res.status_code == 200:
77
  key_status_registry[key] = "ready"
78
  return res.json()['choices'][0]['message']['content'].strip()
79
  key_status_registry[key] = "dead"
80
- except: key_status_registry[key] = "dead"; continue
81
- return None
82
-
83
- @staticmethod
84
- def translate_sealion(prompt):
85
- key = os.environ.get("SEA_LION_API_KEY")
86
- rand_val = random.randint(1, 1000)
87
- try:
88
- url = "https://api.sea-lion.ai/v1/chat/completions"
89
- payload = {
90
- "model": "aisingapore/Gemma-SEA-LION-v4-27B-IT",
91
- "messages": [{"role": "user", "content": f"{prompt}\nID: {rand_val}"}],
92
- "temperature": 0.7,
93
- "max_new_tokens": 2048
94
- }
95
- res = requests.post(url, headers={"Authorization": f"Bearer {key}"}, json=payload, timeout=60)
96
- if res.status_code == 200:
97
- key_status_registry[key] = "ready"
98
- return res.json()['choices'][0]['message']['content'].strip()
99
- key_status_registry[key] = "dead"
100
- except: key_status_registry[key] = "dead"
101
  return None
102
 
103
  def translator_hub(text, target_lang, engine):
104
  if not text.strip(): return "", AIService.get_status_html(engine)
105
- lang_name = target_lang.split(" ")[-1]
106
  is_srt = bool(re.search(r'\d+\n\d{2}:\d{2}:\d{2}', text))
 
107
 
108
- # αž”αž„αŸ’αžαŸ†αž±αŸ’αž™αž™αž€αž˜αŸ’αžŠαž„ ៑០ αž”αž“αŸ’αž‘αžΆαžαŸ‹ αžŠαžΎαž˜αŸ’αž”αžΈαž€αž»αŸ†αž±αŸ’αž™αž”αžΆαžαŸ‹αž‘αž·αž“αŸ’αž“αž“αŸαž™
109
- chunk_limit = 10
110
-
111
  if is_srt:
112
  blocks = re.split(r'\n(?=\d+\n\d{2}:\d{2}:\d{2})', text.strip())
113
- chunks = ["\n".join(blocks[i:i + chunk_limit]) for i in range(0, len(blocks), chunk_limit)]
114
  else:
115
  lines = text.split('\n')
116
- chunks = ["\n".join(lines[i:i + chunk_limit]) for i in range(0, len(lines), chunk_limit)]
117
 
118
- translated_parts = []
119
  for chunk in chunks:
120
- # Prompt αžαžΉαž„αžšαžΉαž„αž”αŸ†αž•αž»αžαžŸαž˜αŸ’αžšαžΆαž”αŸ‹ SRT
121
- if is_srt:
122
- prompt = (
123
- f"TASK: Translate the following SRT blocks to {lang_name} naturally.\n"
124
- f"STRICT RULES:\n"
125
- f"1. Keep the exact SRT format (Index number, Timecodes).\n"
126
- f"2. Translate ONLY the text content.\n"
127
- f"3. DO NOT skip any blocks. Translate all {chunk_limit} blocks provided.\n"
128
- f"4. Keep names of people original.\n"
129
- f"5. Use appropriate tones (Historical/Royal if Chinese Drama, Slang if modern).\n"
130
- f"6. Return ONLY the translated SRT content.\n\n"
131
- f"CONTENT:\n{chunk}"
132
- )
133
- else:
134
- prompt = (
135
- f"Translate this text to {lang_name} naturally. Maintain the tone of the context.\n"
136
- f"Rules: Return ONLY translation, No names translation, No mixing languages.\n\n"
137
- f"CONTENT:\n{chunk}"
138
- )
139
 
140
- if "Gemini" in engine: result = AIService.translate_gemini(prompt)
141
- elif "Llama" in engine: result = AIService.translate_llama(prompt)
142
- else: result = AIService.translate_sealion(prompt)
143
-
144
- if result: translated_parts.append(result)
145
- else: translated_parts.append("❌ [Section Error]")
 
146
 
147
  separator = "\n\n" if is_srt else "\n"
148
- return separator.join(translated_parts), AIService.get_status_html(engine)
149
 
150
- # ==========================================
151
- # UI & PWA SCRIPTS
152
- # ==========================================
153
- pwa_js = """
154
- function initPWA(){
155
- const s=document.createElement('script');
156
- s.src='https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js';
157
- document.head.appendChild(s);
158
- document.addEventListener('click', (e) => {
159
- if (e.target.tagName === 'BUTTON' && typeof confetti === 'function') {
160
- const rect = e.target.getBoundingClientRect();
161
- confetti({
162
- particleCount: 20,
163
- spread: 30,
164
- origin: { x: (rect.left + rect.width/2) / window.innerWidth, y: (rect.top + rect.height/2) / window.innerHeight },
165
- colors: ['#60a5fa', '#3b82f6', '#ffffff'],
166
- ticks: 100,
167
- gravity: 1.5,
168
- scalar: 0.7
169
- });
170
- }
171
- });
172
- }
173
- function copyText(v) {
174
- if(!v) return;
175
- const el = document.createElement('textarea');
176
- el.value = v;
177
- document.body.appendChild(el);
178
- el.select();
179
- document.execCommand('copy');
180
- document.body.removeChild(el);
181
- alert('αž”αžΆαž“αž…αž˜αŸ’αž›αž„αž‡αŸ„αž‚αž‡αŸαž™! πŸ“‹');
182
- }
183
- function clearNotice() {
184
- alert('αž”αžΆαž“αžŸαž˜αŸ’αž’αžΆαžαž’αžαŸ’αžαž”αž‘αžšαž½αž…αžšαžΆαž›αŸ‹! 🧹');
185
- }
186
  """
187
 
188
- css_style = "body{background-color:#0d1117!important;cursor:crosshair;} .api-box{background:#161b22;border-radius:8px;padding:10px;border:1px solid #30363d;margin-bottom:15px;} .center-btn{justify-content:center!important;gap:8px!important;} .small-btn{max-width:85px!important;min-width:70px!important;font-size:13px!important;} .translate-btn{min-width:150px!important;font-weight:bold!important;background:linear-gradient(45deg,#1e40af,#3b82f6)!important; color:white!important;}"
189
-
190
- with gr.Blocks(css=css_style, head=f"<script>{pwa_js}</script>") as demo:
191
- demo.load(None, None, None, js="() => { initPWA(); }")
192
- gr.Markdown("<h2 style='text-align: center; color: #60a5fa;'>πŸŽ† SRT SMART ULTIMATE πŸ‡°πŸ‡­</h2>")
193
 
194
- with gr.Column():
195
- with gr.Row():
196
- engine_opt = gr.Radio(choices=["πŸ’Ž Gemini", "πŸ¦™ Llama", "🦁 SEA-LION"], value="πŸ’Ž Gemini", label="αž‡αŸ’αžšαžΎαžŸαžšαžΎαžŸαž˜αŸ‰αžΌαžŠαŸ‚αž›")
197
- lang_opt = gr.Dropdown(choices=["πŸ‡°πŸ‡­ Khmer", "πŸ‡ΊπŸ‡Έ English", "πŸ‡ΉπŸ‡­ Thai", "πŸ‡¨πŸ‡³ Chinese", "πŸ‡―πŸ‡΅ Japanese", "πŸ‡°πŸ‡· Korean", "πŸ‡»πŸ‡³ Vietnamese"], value="πŸ‡°πŸ‡­ Khmer", label="αž”αž€αž”αŸ’αžšαŸ‚αž‡αžΆαž—αžΆαžŸαžΆ")
198
-
199
- api_status_ui = gr.HTML(AIService.get_status_html("πŸ’Ž Gemini"), elem_classes="api-box")
200
- input_text = gr.Textbox(label="αž’αžαŸ’αžαž”αž‘αžŠαžΎαž˜", lines=8, placeholder="αž”αž·αž‘αž—αŸ’αž‡αžΆαž”αŸ‹αž’αžαŸ’αžαž”αž‘αž’αž˜αŸ’αž˜αžαžΆ ឬ SRT αžœαŸ‚αž„αŸ—...")
201
-
202
- with gr.Row(elem_classes="center-btn"):
203
- clear_btn = gr.Button("πŸ—‘οΈ αž›αž»αž”", variant="secondary", elem_classes="small-btn")
204
- submit_btn = gr.Button("⚑ αž”αž€αž”αŸ’αžšαŸ‚αž₯ទូវ", variant="primary", elem_classes="translate-btn")
205
- copy_btn = gr.Button("πŸ“‹ αž…αž˜αŸ’αž›αž„", variant="secondary", elem_classes="small-btn")
206
 
207
- output_text = gr.Textbox(label="αž›αž‘αŸ’αž’αž•αž›", lines=10, interactive=False)
 
 
 
 
 
 
 
 
208
 
209
- engine_opt.change(fn=AIService.get_status_html, inputs=engine_opt, outputs=api_status_ui)
210
- submit_btn.click(fn=translator_hub, inputs=[input_text, lang_opt, engine_opt], outputs=[output_text, api_status_ui])
211
- clear_btn.click(lambda: ["", "", AIService.get_status_html("πŸ’Ž Gemini")], None, [input_text, output_text, api_status_ui], js="() => { clearNotice(); }")
212
- copy_btn.click(None, inputs=output_text, js="(v) => { copyText(v); }")
 
213
 
214
  if __name__ == "__main__":
215
  demo.launch()
 
3
  import gradio as gr
4
  import re
5
  import random
6
+ import time
7
 
8
+ # Registry αžŸαž˜αŸ’αžšαžΆαž”αŸ‹αž†αŸ‚αž€αžŸαŸ’αžαžΆαž“αž—αžΆαž– Key
9
  key_status_registry = {}
10
 
11
  class AIService:
 
25
 
26
  @staticmethod
27
  def get_status_html(engine):
28
+ prefix = "GEMINI_API_KEY_" if "Gemini" in engine else "GROQ_API_KEY_" if "Llama" in engine else "SEA_LION_API_KEY"
 
 
 
29
  keys = AIService.get_all_keys(prefix)
30
  label = engine.split(" ")[-1]
31
+ html = f"<div style='display: flex; gap: 8px; align-items: center; margin-bottom: 10px;'><b style='color: #94a3b8; font-size: 12px;'>{label} Status:</b>"
32
+ for k in keys:
33
  state = key_status_registry.get(k, "ready")
34
  color = "#22c55e" if state == "ready" else "#ef4444"
35
+ html += f"<div style='width: 10px; height: 10px; background: {color}; border-radius: 50%; box-shadow: 0 0 5px {color};'></div>"
36
+ return html + "</div>"
 
 
37
 
38
  @staticmethod
39
+ def call_api(engine, prompt):
40
+ if "Gemini" in engine:
41
+ keys = AIService.get_all_keys("GEMINI_API_KEY_")
42
+ for key in keys:
43
+ try:
44
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key={key}"
45
+ res = requests.post(url, json={
46
+ "contents": [{"parts": [{"text": prompt}]}],
47
+ "generationConfig": {"temperature": 0.6}
48
+ }, timeout=30)
49
+ if res.status_code == 200:
50
+ key_status_registry[key] = "ready"
51
+ return res.json()['candidates'][0]['content']['parts'][0]['text'].strip()
52
+ key_status_registry[key] = "dead"
53
+ except: continue
54
+ elif "Llama" in engine:
55
+ keys = AIService.get_all_keys("GROQ_API_KEY_")
56
+ for key in keys:
57
+ try:
58
+ res = requests.post("https://api.groq.com/openai/v1/chat/completions",
59
+ headers={"Authorization": f"Bearer {key}"},
60
+ json={"model": "llama-3.3-70b-versatile", "messages": [{"role": "user", "content": prompt}], "temperature": 0.5}, timeout=30)
61
+ if res.status_code == 200:
62
+ key_status_registry[key] = "ready"
63
+ return res.json()['choices'][0]['message']['content'].strip()
64
+ key_status_registry[key] = "dead"
65
+ except: continue
66
+ else: # SEA-LION
67
+ key = os.environ.get("SEA_LION_API_KEY")
68
  try:
69
+ res = requests.post("https://api.sea-lion.ai/v1/chat/completions",
70
+ headers={"Authorization": f"Bearer {key}"},
71
+ json={"model": "aisingapore/Gemma-SEA-LION-v4-27B-IT", "messages": [{"role": "user", "content": prompt}], "temperature": 0.5}, timeout=60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  if res.status_code == 200:
73
  key_status_registry[key] = "ready"
74
  return res.json()['choices'][0]['message']['content'].strip()
75
  key_status_registry[key] = "dead"
76
+ except: pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  return None
78
 
79
  def translator_hub(text, target_lang, engine):
80
  if not text.strip(): return "", AIService.get_status_html(engine)
81
+
82
  is_srt = bool(re.search(r'\d+\n\d{2}:\d{2}:\d{2}', text))
83
+ chunk_size = 10 # αž”αž€αž”αŸ’αžšαŸ‚αž˜αŸ’αžŠαž„ ៑០ αž”αž“αŸ’αž‘αžΆαžαŸ‹αžŠαžΎαž˜αŸ’αž”αžΈαž€αžΆαžšαž–αžΆαžšαž€αžΆαžšαž”αžΆαžαŸ‹αž‘αž·αž“αŸ’αž“αž“αŸαž™
84
 
 
 
 
85
  if is_srt:
86
  blocks = re.split(r'\n(?=\d+\n\d{2}:\d{2}:\d{2})', text.strip())
87
+ chunks = ["\n".join(blocks[i:i + chunk_size]) for i in range(0, len(blocks), chunk_size)]
88
  else:
89
  lines = text.split('\n')
90
+ chunks = ["\n".join(lines[i:i + chunk_size]) for i in range(0, len(lines), chunk_size)]
91
 
92
+ final_output = []
93
  for chunk in chunks:
94
+ prompt = (
95
+ f"You are a professional movie translator. Translate to {target_lang.split()[-1]}.\n"
96
+ f"STRICT INSTRUCTIONS:\n"
97
+ f"1. RELATIONSHIPS: Use 'αž”αž„/αž’αžΌαž“' for couples. Use family terms like 'αž˜αŸ‰αžΆαž€αŸ‹, αž”αŸ‰αžΆ, αž€αžΌαž“, αž”αž„, αž”αŸ’αž’αžΌαž“, αž’αŸŠαŸ†, αž–αžΌ, αž˜αžΈαž„' based on context. NEVER use 'αž›αŸ„αž€/αž’αŸ’αž“αž€' in family or close friends context.\n"
98
+ f"2. NAMES: DO NOT translate character names or places. Keep them as they are.\n"
99
+ f"3. COMPLETENESS: Translate every single line. Do not skip or merge lines.\n"
100
+ f"4. FORMAT: If input is SRT, keep index and timecodes exactly the same.\n"
101
+ f"5. TONE: Adapt to the scene (Historical, Modern, Angry, or Romantic).\n\n"
102
+ f"CONTENT TO TRANSLATE:\n{chunk}"
103
+ )
 
 
 
 
 
 
 
 
 
104
 
105
+ result = AIService.call_api(engine, prompt)
106
+ if result:
107
+ # αž›αž»αž” Text αž₯αžαž”αŸ’αžšαž™αŸ„αž‡αž“αŸαžŠαŸ‚αž› AI αž…αžΌαž›αž…αž·αžαŸ’αžαžαŸ‚αž˜
108
+ cleaned = re.sub(r'(?i)(here is the translation:|```srt|```)', '', result).strip()
109
+ final_output.append(cleaned)
110
+ else:
111
+ final_output.append("❌ [αž€αžΆαžšαžαž—αŸ’αž‡αžΆαž”αŸ‹αž˜αžΆαž“αž”αž‰αŸ’αž αžΆ]")
112
 
113
  separator = "\n\n" if is_srt else "\n"
114
+ return separator.join(final_output), AIService.get_status_html(engine)
115
 
116
+ # --- UI Setup with Custom CSS ---
117
+ css = """
118
+ body { background-color: #0d1117; color: #c9d1d9; font-family: 'Kantumruy Pro', sans-serif; }
119
+ .gradio-container { border: none !important; }
120
+ .main-box { background: #161b22; border-radius: 12px; padding: 20px; border: 1px solid #30363d; }
121
+ .btn-trans { background: linear-gradient(90deg, #1f6feb, #388bfd) !important; color: white !important; font-weight: bold !important; }
122
+ .status-area { background: #0d1117; padding: 10px; border-radius: 8px; margin-bottom: 10px; border: 1px dashed #30363d; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  """
124
 
125
+ with gr.Blocks(css=css, title="SRT Translator Pro") as demo:
126
+ gr.Markdown("<h1 style='text-align: center; color: #58a6ff;'>🎬 SRT SMART TRANSLATOR ULTIMATE</h1>")
127
+ gr.Markdown("<p style='text-align: center; color: #8b949e;'>αž”αž€αž”αŸ’αžšαŸ‚αžšαžΏαž„αž—αžΆαž‚ αž”αŸ‚αž”αž”αžαŸ‹αž”αŸ‚αž“αžαžΆαž˜αž€αžΆαž›αŸˆαž‘αŸαžŸαŸˆ αž“αž·αž„αžšαž€αŸ’αžŸαžΆαž‘αž˜αŸ’αžšαž„αŸ‹αžŠαžΎαž˜</p>")
 
 
128
 
129
+ with gr.Row(elem_classes="main-box"):
130
+ with gr.Column(scale=1):
131
+ engine_opt = gr.Radio(["πŸ’Ž Gemini", "πŸ¦™ Llama", "🦁 SEA-LION"], value="πŸ’Ž Gemini", label="αž‡αŸ’αžšαžΎαžŸαžšαžΎαžŸαž˜αŸ‰αžΌαžŠαŸ‚αž› AI")
132
+ lang_opt = gr.Dropdown(["πŸ‡°πŸ‡­ Khmer", "πŸ‡ΊπŸ‡Έ English", "πŸ‡ΉπŸ‡­ Thai", "πŸ‡¨πŸ‡³ Chinese"], value="πŸ‡°οΏ½οΏ½ Khmer", label="αž”αž€αž”αŸ’αžšαŸ‚αž‡αžΆαž—αžΆαžŸαžΆ")
133
+ status_ui = gr.HTML(AIService.get_status_html("πŸ’Ž Gemini"), elem_classes="status-area")
 
 
 
 
 
 
 
134
 
135
+ input_box = gr.Textbox(label="αž’αžαŸ’αžαž”αž‘αžŠαžΎαž˜ (Paste SRT here)", lines=12, placeholder="αž”αž·αž‘αž—αŸ’αž‡αžΆαž”αŸ‹αž’αžαŸ’αžαž”αž‘ αž¬αž―αž€αžŸαžΆαžš SRT αž“αŸ…αž‘αžΈαž“αŸαŸ‡...")
136
+
137
+ with gr.Row():
138
+ btn_clear = gr.Button("πŸ—‘οΈ αžŸαž˜αŸ’αž’αžΆαž")
139
+ btn_trans = gr.Button("⚑ αž…αžΆαž”αŸ‹αž•αŸ’αžŠαžΎαž˜αž”αž€αž”αŸ’αžšαŸ‚", variant="primary", elem_classes="btn-trans")
140
+
141
+ with gr.Column(scale=1):
142
+ output_box = gr.Textbox(label="αž›αž‘αŸ’αž’αž•αž›αž”αž€αž”αŸ’αžšαŸ‚", lines=22, interactive=False, show_copy_button=True)
143
+ btn_copy_all = gr.Button("πŸ“‹ αž…αž˜αŸ’αž›αž„αž›αž‘αŸ’αž’αž•αž›αž‘αžΆαŸ†αž„αž’αžŸαŸ‹")
144
 
145
+ # Events
146
+ engine_opt.change(AIService.get_status_html, engine_opt, status_ui)
147
+ btn_trans.click(translator_hub, [input_box, lang_opt, engine_opt], [output_box, status_ui])
148
+ btn_clear.click(lambda: ["", ""], None, [input_box, output_box])
149
+ btn_copy_all.click(None, output_box, js="(v) => { navigator.clipboard.writeText(v); alert('αž…αž˜αŸ’αž›αž„αž‡αŸ„αž‚αž‡αŸαž™!'); }")
150
 
151
  if __name__ == "__main__":
152
  demo.launch()