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()