#!/usr/bin/env python3 import os import sys import argparse import subprocess from dotenv import load_dotenv from datetime import datetime import pysubs2 from deep_translator import GoogleTranslator # Load config script_dir = os.path.dirname(os.path.abspath(__file__)) env_path = os.path.abspath(os.path.join(script_dir, '../.env_files/.env.aitranscribe')) if os.path.exists(env_path): load_dotenv(env_path) else: load_dotenv() # Add ai_transcriber_v2 to path so we can import modules sys.path.append(os.path.join(script_dir, 'ai_transcriber_v2')) from ai_transcriber_v2.translator import translate_srt from ai_transcriber_v2.utils import validate_and_repair_srt def translate_fallback_free(source_srt_path, output_srt_path, target_lang="en"): """ Fallback translation using deep-translator (free Google Translate). Parses SRT, translates text line-by-line, and saves new SRT. """ print(f" [Free Fallback] Translating {source_srt_path}...") subs = None encodings_to_try = ['utf-8', 'shift_jis', 'euc_jp', 'latin-1', 'cp1252', 'utf-16'] for enc in encodings_to_try: try: subs = pysubs2.load(source_srt_path, encoding=enc) break except Exception: continue if subs is None: print(f" [Free Fallback] Critical Error: Could not decode file with standard encodings.") return False try: translator = GoogleTranslator(source='auto', target=target_lang) for line in subs: text = line.text.strip() if text: # Sanity check: Skip lines that are too long (likely garbage/corruption) if len(text) > 4000: print(f" Warning: Skipping line with excessive length ({len(text)} chars). Likely corrupted.") continue try: 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}") subs.save(output_srt_path) print(f" [Free Fallback] Saved to {output_srt_path}") return True except Exception as e: print(f" [Free Fallback] Critical Error: {e}") return False def re_embed_subtitles(video_path, srt_path, output_path=None): """ Re-embeds subtitles into an EXISTING video file, replacing the old tracks. Uses robust flags to handle bad metadata. """ if not os.path.exists(video_path) or not os.path.exists(srt_path): print("Error: Video or SRT file not found.") return False temp_output = video_path + ".temp.mp4" print(f"Re-embedding subtitles into: {video_path}...") sub_codec = "mov_text" if video_path.lower().endswith(".mp4") else "srt" command = [ "ffmpeg", "-ignore_editlist", "1", "-i", video_path, "-i", srt_path, "-map", "0:v", "-map", "0:a", "-map", "1:0", "-c", "copy", "-c:s", sub_codec, "-disposition:s:0", "default", "-metadata:s:s:0", "language=eng", "-metadata:s:s:0", "title=English (AI Translated)", "-max_interleave_delta", "0", "-avoid_negative_ts", "make_zero", "-y", "-v", "error", temp_output ] try: subprocess.run(command, check=True) os.replace(temp_output, video_path) print(f"✅ Fixed: {video_path}") return True except subprocess.CalledProcessError as e: print(f"Error re-embedding: {e}") if os.path.exists(temp_output): os.remove(temp_output) return False def process_recovery(folder_path, target_lang="English", prefer_deep=False): print(f"Scanning {folder_path} for incomplete translations (V2)...") if prefer_deep: print("Preference: DeepTranslate (Google Translate Free) > Gemini") else: print("Preference: Gemini (API) > DeepTranslate") recovery_log_file = os.path.join(folder_path, "recovery_status.log") print(f"Logging actions to: {recovery_log_file}") count_fixed = 0 video_extensions = ('.mp4', '.mkv', '.mov', '.avi') for root, dirs, files in os.walk(folder_path): for file in files: if file.endswith(".srt") and \ not file.endswith(f".{target_lang}.srt") and \ not file.endswith(f".{target_lang}.deep_translate.srt"): source_srt_path = os.path.join(root, file) base_name = os.path.splitext(file)[0] path_gemini = os.path.join(root, f"{base_name}.{target_lang}.srt") path_deep = os.path.join(root, f"{base_name}.{target_lang}.deep_translate.srt") if os.path.exists(path_gemini) or os.path.exists(path_deep): continue print(f"\nFound untranslated transcript: {file}") content = None # extended list to include common Japanese encodings encodings_to_try = ['utf-8', 'shift_jis', 'euc_jp', 'latin-1', 'cp1252', 'utf-16'] for enc in encodings_to_try: try: with open(source_srt_path, "r", encoding=enc) as f: content = f.read() break # Success except UnicodeDecodeError: continue if content is None: print(f"❌ Error: Could not decode {file} with any standard encoding. Skipping.") continue # Helpers for translation attempts def try_gemini(): res = translate_srt(content, target_language=target_lang) if res: with open(path_gemini, "w", encoding="utf-8") as f: f.write(res) return True, path_gemini, "Gemini" return False, None, None def try_deep(): lang_map = { "English": "en", "French": "fr", "Spanish": "es", "German": "de", "Italian": "it", "Portuguese": "pt", "Russian": "ru", "Japanese": "ja", "Chinese": "zh-CN" } target_code = lang_map.get(target_lang, "en") if translate_fallback_free(source_srt_path, path_deep, target_lang=target_code): return True, path_deep, "DeepTranslate" return False, None, None success = False method_used = "None" final_srt_path = None if prefer_deep: # 1. Try DeepTranslate success, final_srt_path, method_used = try_deep() if not success: print("❌ DeepTranslate failed. Attempting Gemini fallback...") success, final_srt_path, method_used = try_gemini() else: # 1. Try Gemini success, final_srt_path, method_used = try_gemini() if not success: print("❌ Gemini API failed. Attempting Free Fallback...") success, final_srt_path, method_used = try_deep() if success and final_srt_path: # Log result with open(recovery_log_file, "a", encoding="utf-8") as log: log.write(f"{datetime.now().isoformat()} | {method_used} | {file} -> {os.path.basename(final_srt_path)}\n") validate_and_repair_srt(final_srt_path) video_candidates = [ os.path.join(root, base_name + ".mp4"), os.path.join(root, base_name + ".mkv"), os.path.join(root, base_name + ".subbed.mp4"), ] found_video = None for v in video_candidates: if os.path.exists(v): found_video = v break if found_video: print(f"Found video to fix: {found_video}") if re_embed_subtitles(found_video, final_srt_path): count_fixed += 1 else: print("Warning: Could not find a corresponding video file to embed into.") else: print("❌ All translation methods failed. Skipping.") print(f"\nRecovery Complete. Fixed {count_fixed} files.") if __name__ == "__main__": parser = argparse.ArgumentParser(description="Recover and Fix Translations (V2)") parser.add_argument("folder", help="Path to the folder to scan") parser.add_argument("lang", nargs="?", default="English", help="Target language (default: English)") parser.add_argument("--prefer-deep", action="store_true", help="Prefer DeepTranslate (Free) over Gemini API") args = parser.parse_args() process_recovery(args.folder, args.lang, args.prefer_deep)