Download gemini.py from khantbhko/dublab: direct link, hf CLI and curl.
- Browser
- Download file 4 kB
-
https://huggingface.co/khantbhko/dublab/resolve/main/gemini.py
- Command line
-
hf download hf://khantbhko/dublab/gemini.py
-
curl -L -o gemini.py https://huggingface.co/khantbhko/dublab/resolve/main/gemini.py
4 kB
| """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 | |