dublab / gemini.py
khantbhko's picture
DubLab on Hugging Face (Dockerfile + Flask app, Nara Mini voices)
475d141 verified
Raw History Blame Contribute Delete
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