import os
import requests
import gradio as gr
import re
import random
import time
# Registry សម្រាប់ឆែកស្ថានភាព Key
key_status_registry = {}
class AIService:
@staticmethod
def get_all_keys(prefix):
keys = []
i = 1
while True:
key = os.environ.get(f"{prefix}{i}")
if key:
keys.append(key)
i += 1
else: break
if not keys and os.environ.get(prefix.replace("_KEY_", "_KEY")):
keys.append(os.environ.get(prefix.replace("_KEY_", "_KEY")))
return keys
@staticmethod
def get_status_html(engine):
prefix = "GEMINI_API_KEY_" if "Gemini" in engine else "GROQ_API_KEY_" if "Llama" in engine else "SEA_LION_API_KEY"
keys = AIService.get_all_keys(prefix)
label = engine.split(" ")[-1]
html = f"
{label} Status:"
for k in keys:
state = key_status_registry.get(k, "ready")
color = "#22c55e" if state == "ready" else "#ef4444"
html += f"
"
return html + "
"
@staticmethod
def call_api(engine, prompt):
if "Gemini" in engine:
keys = AIService.get_all_keys("GEMINI_API_KEY_")
for key in keys:
try:
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent?key={key}"
res = requests.post(url, json={
"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {"temperature": 0.6}
}, timeout=30)
if res.status_code == 200:
key_status_registry[key] = "ready"
return res.json()['candidates'][0]['content']['parts'][0]['text'].strip()
key_status_registry[key] = "dead"
except: continue
elif "Llama" in engine:
keys = AIService.get_all_keys("GROQ_API_KEY_")
for key in keys:
try:
res = requests.post("https://api.groq.com/openai/v1/chat/completions",
headers={"Authorization": f"Bearer {key}"},
json={"model": "llama-3.3-70b-versatile", "messages": [{"role": "user", "content": prompt}], "temperature": 0.5}, timeout=30)
if res.status_code == 200:
key_status_registry[key] = "ready"
return res.json()['choices'][0]['message']['content'].strip()
key_status_registry[key] = "dead"
except: continue
else: # SEA-LION
key = os.environ.get("SEA_LION_API_KEY")
try:
res = requests.post("https://api.sea-lion.ai/v1/chat/completions",
headers={"Authorization": f"Bearer {key}"},
json={"model": "aisingapore/Gemma-SEA-LION-v4-27B-IT", "messages": [{"role": "user", "content": prompt}], "temperature": 0.5}, timeout=60)
if res.status_code == 200:
key_status_registry[key] = "ready"
return res.json()['choices'][0]['message']['content'].strip()
key_status_registry[key] = "dead"
except: pass
return None
def translator_hub(text, target_lang, engine):
if not text.strip(): return "", AIService.get_status_html(engine)
is_srt = bool(re.search(r'\d+\n\d{2}:\d{2}:\d{2}', text))
chunk_size = 10 # បកប្រែម្ដង ១០ បន្ទាត់ដើម្បីការពារការបាត់ទិន្នន័យ
if is_srt:
blocks = re.split(r'\n(?=\d+\n\d{2}:\d{2}:\d{2})', text.strip())
chunks = ["\n".join(blocks[i:i + chunk_size]) for i in range(0, len(blocks), chunk_size)]
else:
lines = text.split('\n')
chunks = ["\n".join(lines[i:i + chunk_size]) for i in range(0, len(lines), chunk_size)]
final_output = []
for chunk in chunks:
prompt = (
f"You are a professional movie translator. Translate to {target_lang.split()[-1]}.\n"
f"STRICT INSTRUCTIONS:\n"
f"1. RELATIONSHIPS: Use 'បង/អូន' for couples. Use family terms like 'ម៉ាក់, ប៉ា, កូន, បង, ប្អូន, អ៊ំ, ពូ, មីង' based on context. NEVER use 'លោក/អ្នក' in family or close friends context.\n"
f"2. NAMES: DO NOT translate character names or places. Keep them as they are.\n"
f"3. COMPLETENESS: Translate every single line. Do not skip or merge lines.\n"
f"4. FORMAT: If input is SRT, keep index and timecodes exactly the same.\n"
f"5. TONE: Adapt to the scene (Historical, Modern, Angry, or Romantic).\n\n"
f"CONTENT TO TRANSLATE:\n{chunk}"
)
result = AIService.call_api(engine, prompt)
if result:
# លុប Text ឥតប្រយោជន៍ដែល AI ចូលចិត្តថែម
cleaned = re.sub(r'(?i)(here is the translation:|```srt|```)', '', result).strip()
final_output.append(cleaned)
else:
final_output.append("❌ [ការតភ្ជាប់មានបញ្ហា]")
separator = "\n\n" if is_srt else "\n"
return separator.join(final_output), AIService.get_status_html(engine)
# --- UI Setup with Custom CSS ---
css = """
body { background-color: #0d1117; color: #c9d1d9; font-family: 'Kantumruy Pro', sans-serif; }
.gradio-container { border: none !important; }
.main-box { background: #161b22; border-radius: 12px; padding: 20px; border: 1px solid #30363d; }
.btn-trans { background: linear-gradient(90deg, #1f6feb, #388bfd) !important; color: white !important; font-weight: bold !important; }
.status-area { background: #0d1117; padding: 10px; border-radius: 8px; margin-bottom: 10px; border: 1px dashed #30363d; }
"""
with gr.Blocks(css=css, title="SRT Translator Pro") as demo:
gr.Markdown("🎬 SRT SMART TRANSLATOR ULTIMATE
")
gr.Markdown("បកប្រែរឿងភាគ បែបបត់បែនតាមកាលៈទេសៈ និងរក្សាទម្រង់ដើម
")
with gr.Row(elem_classes="main-box"):
with gr.Column(scale=1):
engine_opt = gr.Radio(["💎 Gemini", "🦙 Llama", "🦁 SEA-LION"], value="💎 Gemini", label="ជ្រើសរើសម៉ូដែល AI")
lang_opt = gr.Dropdown(["🇰🇭 Khmer", "🇺🇸 English", "🇹🇭 Thai", "🇨🇳 Chinese"], value="🇰🇭 Khmer", label="បកប្រែជាភាសា")
status_ui = gr.HTML(AIService.get_status_html("💎 Gemini"), elem_classes="status-area")
input_box = gr.Textbox(label="អត្ថបទដើម (Paste SRT here)", lines=12, placeholder="បិទភ្ជាប់អត្ថបទ ឬឯកសារ SRT នៅទីនេះ...")
with gr.Row():
btn_clear = gr.Button("🗑️ សម្អាត")
btn_trans = gr.Button("⚡ ចាប់ផ្ដើមបកប្រែ", variant="primary", elem_classes="btn-trans")
with gr.Column(scale=1):
output_box = gr.Textbox(label="លទ្ធផលបកប្រែ", lines=22, interactive=False, show_copy_button=True)
btn_copy_all = gr.Button("📋 ចម្លងលទ្ធផលទាំងអស់")
# Events
engine_opt.change(AIService.get_status_html, engine_opt, status_ui)
btn_trans.click(translator_hub, [input_box, lang_opt, engine_opt], [output_box, status_ui])
btn_clear.click(lambda: ["", ""], None, [input_box, output_box])
btn_copy_all.click(None, output_box, js="(v) => { navigator.clipboard.writeText(v); alert('ចម្លងជោគជ័យ!'); }")
if __name__ == "__main__":
demo.launch()