"""Gemini REST client for dubbing-script translation (no SDK needed).""" import json import re import time import urllib.request MODEL = "gemini-3.5-flash-lite" MAX_RETRIES = 4 CHUNK = 25 PROMPT_EN2MM = """You are a professional dubbing script translator for short videos. Translate each spoken line into natural SPOKEN Burmese (စကားပြောဘာသာ) — the way real people talk, never stiff written Burmese (စာရေးဘာသာ). Rules: - Translate meaning, tone and emotion — NEVER word-for-word. A natural Burmese line that captures the feeling beats a literal one. - Keep it concise: each line will be SPOKEN in about the same time as the original, so avoid long-winded phrasing. - TIMING IS CRITICAL: Burmese speech runs longer than English, so compress hard — use the shortest natural phrasing, drop filler words and repetition, keep only the core meaning and tone. A slightly shorter line is always better than a longer one. - Match the speaker: casual stays casual, jokes stay funny in Burmese, adapt idioms to Burmese equivalents. - Names and places stay as-is (transliterated if needed). - Output format: exactly one line per input as "N|||translation" (N = the number I give you). Nothing else — no explanations.""" PROMPT_MM2EN = """You are a professional dubbing script translator for short videos. Translate each spoken Burmese line into natural SPOKEN English — the way real people talk in videos, never stiff or literal. Rules: - Translate meaning, tone and emotion — NEVER word-for-word. A natural English line that captures the feeling beats a literal one. - Keep it concise: each line will be SPOKEN in about the same time as the original, so avoid long-winded phrasing. - TIMING IS CRITICAL: compress hard — use the shortest natural phrasing, drop filler words and repetition, keep only the core meaning and tone. A slightly shorter line is always better than a longer one. - Match the speaker: casual stays casual, jokes stay funny in English, adapt Burmese idioms to English equivalents. - Names and places stay as-is. - Output format: exactly one line per input as "N|||translation" (N = the number I give you). Nothing else — no explanations.""" def _call(prompt, api_key): url = (f"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}" f":generateContent?key={api_key}") body = {"contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"maxOutputTokens": 8192, "temperature": 0.7}} for attempt in range(MAX_RETRIES): try: req = urllib.request.Request(url, data=json.dumps(body).encode(), headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=180) as r: data = json.loads(r.read().decode()) parts = data["candidates"][0].get("content", {}).get("parts", []) text = "".join(p.get("text", "") for p in parts) if text.strip(): return text raise RuntimeError("empty response") except Exception: if attempt == MAX_RETRIES - 1: raise time.sleep(2 ** attempt * 2) raise RuntimeError("gemini unreachable") def translate_lines(lines, direction, api_key): """lines: [str] -> [translated str], same order.""" prompt = PROMPT_EN2MM if direction == "en2mm" else PROMPT_MM2EN out = [] for start in range(0, len(lines), CHUNK): chunk = lines[start:start + CHUNK] numbered = "\n".join(f"{i + 1}|||{t}" for i, t in enumerate(chunk)) raw = _call(prompt + "\n\nTranslate these lines:\n" + numbered, api_key) results = {} for line in raw.strip().split("\n"): m = re.match(r"(\d+)\|\|\|(.+)", line.strip()) if m: results[int(m.group(1))] = m.group(2).strip() for i, orig in enumerate(chunk, 1): out.append(results.get(i, orig)) return out