Added revisions to the translation app
This commit is contained in:
@@ -19,9 +19,9 @@ else:
|
||||
load_dotenv()
|
||||
|
||||
from extractor import extract_audio, embed_subtitles
|
||||
from transcriber import transcribe_audio, save_as_srt
|
||||
from translator import translate_srt, translate_fallback_free
|
||||
from utils import validate_and_repair_srt
|
||||
from transcriber import transcribe_audio, save_as_srt, load_whisper_model
|
||||
from translator import translate_with_auto_fallback
|
||||
from utils import validate_and_repair_srt, check_srt_duration_match, GracefulKiller, ensure_ollama_running, check_service_availability, check_path_permissions
|
||||
from diarizer import diarize_audio, merge_diarization_with_transcript
|
||||
import tracker
|
||||
from tracker import JobStatus
|
||||
@@ -51,7 +51,7 @@ def save_srt_with_speakers(segments, output_path):
|
||||
f.write(f"{text}\n\n")
|
||||
print(f"SRT saved to: {output_path}")
|
||||
|
||||
def process_file(file_path, args, source_lang=None):
|
||||
def process_file(file_path, args, source_lang=None, loaded_model=None, service_status=None):
|
||||
tracker.logger.info(f"=== Processing: {file_path} ===")
|
||||
|
||||
# Initialize Job
|
||||
@@ -81,7 +81,8 @@ def process_file(file_path, args, source_lang=None):
|
||||
with open(transcript_file, "r", encoding="utf-8") as f:
|
||||
srt_content = f.read()
|
||||
else:
|
||||
result = transcribe_audio(audio_path, model_size=args.model, language=source_lang)
|
||||
# Use loaded_model if available
|
||||
result = transcribe_audio(audio_path, model_size=args.model, language=source_lang, loaded_model=loaded_model)
|
||||
segments = result["segments"]
|
||||
|
||||
if args.diarize:
|
||||
@@ -110,66 +111,75 @@ def process_file(file_path, args, source_lang=None):
|
||||
|
||||
base_translated = os.path.splitext(file_path)[0] + f".{args.lang}.srt"
|
||||
deep_translated = os.path.splitext(file_path)[0] + f".{args.lang}.deep_translate.srt"
|
||||
local_translated = os.path.splitext(file_path)[0] + f".{args.lang}.local_llm.srt"
|
||||
|
||||
translated_file = base_translated # Default
|
||||
# Determine output path logic
|
||||
target_path_gemini = base_translated
|
||||
target_path_deep = deep_translated
|
||||
target_path_local = local_translated
|
||||
|
||||
translated_file = None
|
||||
translation_success = False
|
||||
method_used = "None"
|
||||
|
||||
if (os.path.exists(base_translated) or os.path.exists(deep_translated)) and not args.force:
|
||||
if os.path.exists(deep_translated):
|
||||
# Check existing
|
||||
if (os.path.exists(base_translated) or os.path.exists(deep_translated) or os.path.exists(local_translated)) and not args.force:
|
||||
if os.path.exists(local_translated):
|
||||
translated_file = local_translated
|
||||
method_used = "Local LLM (Existing)"
|
||||
elif os.path.exists(deep_translated):
|
||||
translated_file = deep_translated
|
||||
method_used = "DeepTranslate (Existing)"
|
||||
else:
|
||||
translated_file = base_translated
|
||||
method_used = "Gemini (Existing)"
|
||||
|
||||
tracker.logger.info(f"Translation exists: {translated_file} ({method_used}). Skipping translation.")
|
||||
final_srt_path = translated_file
|
||||
translation_success = True
|
||||
else:
|
||||
if srt_content:
|
||||
# Helper functions
|
||||
def try_gemini():
|
||||
res = translate_srt(srt_content, target_language=args.lang)
|
||||
if res:
|
||||
with open(base_translated, "w", encoding="utf-8") as f:
|
||||
f.write(res)
|
||||
return True, base_translated, "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(args.lang, "en")
|
||||
res = translate_fallback_free(srt_content, target_language=target_code)
|
||||
if res:
|
||||
with open(deep_translated, "w", encoding="utf-8") as f:
|
||||
f.write(res)
|
||||
return True, deep_translated, "DeepTranslate"
|
||||
return False, None, None
|
||||
|
||||
success = False
|
||||
res_content, method = translate_with_auto_fallback(
|
||||
srt_content,
|
||||
target_language=args.lang,
|
||||
prefer_deep=args.prefer_deep,
|
||||
prefer_local=args.prefer_local,
|
||||
available_services=service_status
|
||||
)
|
||||
|
||||
if args.prefer_deep:
|
||||
success, path, method = try_deep()
|
||||
if not success:
|
||||
tracker.logger.info("DeepTranslate failed. Attempting Gemini...")
|
||||
success, path, method = try_gemini()
|
||||
if res_content:
|
||||
# Save based on method used
|
||||
if "DeepTranslate" in method:
|
||||
save_path = target_path_deep
|
||||
elif "Local LLM" in method:
|
||||
save_path = target_path_local
|
||||
else:
|
||||
save_path = target_path_gemini
|
||||
|
||||
with open(save_path, "w", encoding="utf-8") as f:
|
||||
f.write(res_content)
|
||||
|
||||
tracker.logger.info(f"Translation saved to: {save_path} ({method})")
|
||||
validate_and_repair_srt(save_path)
|
||||
|
||||
# Duration Check
|
||||
is_valid_duration, msg = check_srt_duration_match(transcript_file, save_path)
|
||||
if is_valid_duration:
|
||||
tracker.logger.info(f"Validation: {msg}")
|
||||
final_srt_path = save_path
|
||||
translation_success = True
|
||||
method_used = method
|
||||
else:
|
||||
tracker.logger.error(f"VALIDATION FAILED: {msg}")
|
||||
tracker.logger.error("Marking translation as failed due to incomplete coverage.")
|
||||
|
||||
redo_file = os.path.join(os.path.dirname(file_path), "redo_queue.txt")
|
||||
with open(redo_file, "a", encoding="utf-8") as rf:
|
||||
rf.write(f"{file_path} | {msg}\n")
|
||||
|
||||
translation_success = False
|
||||
else:
|
||||
success, path, method = try_gemini()
|
||||
if not success:
|
||||
tracker.logger.warning("Gemini failed. Attempting DeepTranslate...")
|
||||
success, path, method = try_deep()
|
||||
|
||||
if success:
|
||||
tracker.logger.info(f"Translation saved to: {path} ({method})")
|
||||
validate_and_repair_srt(path)
|
||||
final_srt_path = path
|
||||
translation_success = True
|
||||
method_used = method
|
||||
else:
|
||||
tracker.logger.error("TRANSLATION FAILED.")
|
||||
tracker.logger.error("TRANSLATION FAILED (All methods attempted).")
|
||||
tracker.update_step(file_path, "step_translate", "failed")
|
||||
translation_success = False
|
||||
|
||||
@@ -237,6 +247,7 @@ def main():
|
||||
parser.add_argument("--delete-source", action="store_true", help="Delete original file after embedding")
|
||||
parser.add_argument("--retry-failed", action="store_true", help="Retry FAILED jobs from DB")
|
||||
parser.add_argument("--prefer-deep", action="store_true", help="Prefer DeepTranslate (Free) over Gemini")
|
||||
parser.add_argument("--prefer-local", action="store_true", help="Prefer Local LLM (Ollama) over cloud APIs")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -257,9 +268,13 @@ def main():
|
||||
user_input = input("Enter source language (e.g. 'French'). Enter for Auto: ").strip()
|
||||
source_lang = user_input if user_input else None
|
||||
|
||||
# Load model for retries too
|
||||
loaded_model = load_whisper_model(args.model)
|
||||
service_status = check_service_availability()
|
||||
|
||||
for file_path in failed_files:
|
||||
if os.path.exists(file_path):
|
||||
process_file(file_path, args, source_lang)
|
||||
process_file(file_path, args, source_lang, loaded_model=loaded_model, service_status=service_status)
|
||||
else:
|
||||
print(f"Skipping missing file: {file_path}")
|
||||
return
|
||||
@@ -274,22 +289,65 @@ def main():
|
||||
source_lang = user_input if user_input else None
|
||||
print(f"Selected: {source_lang if source_lang else 'Auto-detect'}")
|
||||
|
||||
# --- Ensure Ollama is Running ---
|
||||
ensure_ollama_running()
|
||||
# --------------------------------
|
||||
|
||||
# --- Check Service Health ---
|
||||
service_status = check_service_availability()
|
||||
# ----------------------------
|
||||
|
||||
# --- Check Path Permissions ---
|
||||
valid_inputs = []
|
||||
print("Checking Input Permissions...")
|
||||
for inp in args.inputs:
|
||||
ok, msg = check_path_permissions(inp)
|
||||
print(msg)
|
||||
if ok:
|
||||
valid_inputs.append(inp)
|
||||
|
||||
if not valid_inputs:
|
||||
print("\n❌ Error: No valid inputs with read/write permissions found. Exiting.")
|
||||
return
|
||||
# ------------------------------
|
||||
|
||||
# --- Load Model Once ---
|
||||
loaded_model = load_whisper_model(args.model)
|
||||
# -----------------------
|
||||
|
||||
# Initialize Graceful Exit Handler
|
||||
killer = GracefulKiller()
|
||||
|
||||
video_extensions = ('.mp4', '.mkv', '.mov', '.avi', '.webm', '.flv', '.wmv', '.m4v')
|
||||
|
||||
for input_path in args.inputs:
|
||||
for input_path in valid_inputs:
|
||||
if killer.kill_now:
|
||||
break
|
||||
|
||||
if os.path.isfile(input_path):
|
||||
process_file(input_path, args, source_lang)
|
||||
process_file(input_path, args, source_lang, loaded_model=loaded_model, service_status=service_status)
|
||||
elif os.path.isdir(input_path):
|
||||
found = False
|
||||
for root, dirs, files in os.walk(input_path):
|
||||
if killer.kill_now:
|
||||
break
|
||||
|
||||
for file in files:
|
||||
if killer.kill_now:
|
||||
break
|
||||
|
||||
if file.lower().endswith(video_extensions):
|
||||
found = True
|
||||
process_file(os.path.join(root, file), args, source_lang)
|
||||
process_file(os.path.join(root, file), args, source_lang, loaded_model=loaded_model, service_status=service_status)
|
||||
if not found:
|
||||
print(f"No video files found in {input_path}")
|
||||
else:
|
||||
print(f"Error: Invalid input path '{input_path}'")
|
||||
|
||||
if killer.kill_now:
|
||||
print("\n🛑 Process stopped by user. Progress saved in database.")
|
||||
else:
|
||||
print("\n✅ All jobs finished.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user