181 lines
6.4 KiB
Python
181 lines
6.4 KiB
Python
import os
|
|
import sys
|
|
import warnings
|
|
import pysubs2
|
|
from deep_translator import GoogleTranslator
|
|
|
|
# Suppress warnings from google.generativeai about deprecation
|
|
warnings.filterwarnings("ignore", category=FutureWarning, module="google.generativeai")
|
|
|
|
import google.generativeai as genai
|
|
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
|
|
|
|
# ... (retry_policy and _generate_with_retry remain same)
|
|
|
|
def translate_fallback_free(source_srt_content, target_language="en"):
|
|
"""
|
|
Fallback translation using deep-translator (free Google Translate).
|
|
|
|
Args:
|
|
source_srt_content (str): Content of the source SRT file.
|
|
target_language (str): Target language code (e.g. 'en', 'fr').
|
|
|
|
Returns:
|
|
str: Translated SRT content, or None if failed.
|
|
"""
|
|
print(f" [Free Fallback] Translating via Google Translate (deep-translator)...")
|
|
try:
|
|
# Load from string
|
|
subs = pysubs2.SSAFile.from_string(source_srt_content)
|
|
translator = GoogleTranslator(source='auto', target=target_language)
|
|
|
|
# Simple line-by-line translation
|
|
for line in subs:
|
|
text = line.text.strip()
|
|
if text:
|
|
# Sanity check: Skip lines that are too long
|
|
if len(text) > 4000:
|
|
print(f" Warning: Skipping line with excessive length ({len(text)} chars).")
|
|
continue
|
|
|
|
try:
|
|
# pysubs2 text can contain \N for newlines.
|
|
original_text = text.replace(r"\N", " ")
|
|
translated_text = translator.translate(original_text)
|
|
if translated_text:
|
|
line.text = translated_text
|
|
except Exception as e:
|
|
print(f" Warning: Failed to translate line: {e}")
|
|
|
|
# Return as string
|
|
return subs.to_string(format_="srt")
|
|
except Exception as e:
|
|
print(f" [Free Fallback] Critical Error: {e}")
|
|
return None
|
|
|
|
def get_best_available_model():
|
|
# ... (rest of file)
|
|
|
|
# Define a retry decorator
|
|
# Waits 2^x * 1 seconds between retries (1s, 2s, 4s, 8s, 16s, 32s...)
|
|
# With max=60, it will cap at waiting 60s per try.
|
|
# Stop after 15 attempts (approx 15 minutes of trying before giving up)
|
|
retry_policy = retry(
|
|
stop=stop_after_attempt(15),
|
|
wait=wait_exponential(multiplier=1, min=2, max=60),
|
|
retry=retry_if_exception_type(Exception),
|
|
reraise=True
|
|
)
|
|
|
|
@retry_policy
|
|
def _generate_with_retry(model, prompt):
|
|
"""Internal function to wrap the API call with retry logic."""
|
|
try:
|
|
return model.generate_content(prompt)
|
|
except Exception as e:
|
|
if "429" in str(e) or "Resource has been exhausted" in str(e):
|
|
print(f" [Rate Limit Hit] Waiting for quota reset... ({e})")
|
|
raise e
|
|
|
|
def get_best_available_model():
|
|
"""
|
|
Queries the API to find the best available model for text generation.
|
|
Priority: gemini-1.5-flash > gemini-1.5-pro > gemini-pro > any 'generateContent' model
|
|
"""
|
|
try:
|
|
available_models = []
|
|
for m in genai.list_models():
|
|
if 'generateContent' in m.supported_generation_methods:
|
|
available_models.append(m.name)
|
|
|
|
# Priority list
|
|
priorities = [
|
|
"models/gemini-1.5-flash",
|
|
"models/gemini-1.5-pro",
|
|
"models/gemini-pro"
|
|
]
|
|
|
|
# Check for priorities first
|
|
for p in priorities:
|
|
if p in available_models:
|
|
return p
|
|
|
|
# Fallback: check for aliases without 'models/' prefix just in case
|
|
for p in priorities:
|
|
short_name = p.replace("models/", "")
|
|
# Some libraries might return short names, or custom handling
|
|
# But genai.list_models() usually returns 'models/name'
|
|
pass
|
|
|
|
# If priority not found, pick the first available gemini model
|
|
for m in available_models:
|
|
if "gemini" in m:
|
|
return m
|
|
|
|
if available_models:
|
|
return available_models[0]
|
|
|
|
except Exception as e:
|
|
print(f"Warning: Could not list models ({e}). Defaulting to 'gemini-pro'.")
|
|
|
|
return "gemini-pro"
|
|
|
|
def translate_srt(srt_content, target_language="English", api_key=None):
|
|
"""
|
|
Translates SRT subtitle content using the Gemini API, preserving timestamps.
|
|
|
|
Args:
|
|
srt_content (str): The raw text content of the SRT file.
|
|
target_language (str): The target language for translation.
|
|
api_key (str): Google Gemini API key. If None, checks env var GEMINI_API_KEY.
|
|
|
|
Returns:
|
|
str: The translated SRT content.
|
|
"""
|
|
if not srt_content:
|
|
return ""
|
|
|
|
key = api_key or os.getenv("GEMINI_API_KEY")
|
|
if not key:
|
|
print("Error: GEMINI_API_KEY not found. Please set the environment variable or pass the key.")
|
|
sys.exit(1)
|
|
|
|
genai.configure(api_key=key)
|
|
|
|
# Automatically select the best model
|
|
model_name = get_best_available_model()
|
|
print(f"Using Gemini Model: {model_name}")
|
|
|
|
model = genai.GenerativeModel(model_name)
|
|
|
|
prompt = (
|
|
"You are a professional subtitle translator. Your task is to translate the following SRT subtitle file "
|
|
f"into {target_language}.\n\n"
|
|
"RULES:\n"
|
|
"1. PRESERVE the SRT format exactly. Do not modify timestamps (e.g., 00:00:01,000 --> 00:00:04,000) or sequence numbers.\n"
|
|
"2. Only translate the dialogue text.\n"
|
|
"3. Maintain the original tone and context.\n"
|
|
"4. Output ONLY the translated SRT content, no markdown code blocks or explanations.\n\n"
|
|
"SRT Content:\n"
|
|
f"{srt_content}"
|
|
)
|
|
|
|
print(f"Translating subtitles to {target_language} (with retries)...")
|
|
try:
|
|
# Call the retried internal function
|
|
response = _generate_with_retry(model, prompt)
|
|
print("Translation complete.")
|
|
|
|
# Cleanup: sometimes models wrap output in ```srt ... ``` or ``` ... ```
|
|
cleaned_text = response.text.strip()
|
|
if cleaned_text.startswith("```"):
|
|
# Remove first line (```srt or ```) and last line (```)
|
|
lines = cleaned_text.split('\n')
|
|
if len(lines) >= 2:
|
|
cleaned_text = '\n'.join(lines[1:-1])
|
|
|
|
return cleaned_text
|
|
except Exception as e:
|
|
print(f"Error during translation after retries: {e}")
|
|
return None
|