Files

172 lines
7.4 KiB
Python

import os
import sys
import argparse
import time
import shutil
import config
from translator import extract_audio, transcribe_audio, translate_segments, write_srt, burn_subtitles
def check_dependencies():
"""Verify that FFmpeg is installed and accessible in system path"""
if not shutil.which("ffmpeg"):
print("[Error] FFmpeg is not installed or not in your system PATH.")
print("Please install FFmpeg to run this tool. On macOS: 'brew install ffmpeg'")
sys.exit(1)
def main():
check_dependencies()
parser = argparse.ArgumentParser(description="Video Captioning & Translation Tool using OpenAI API + Local FFmpeg")
parser.add_argument("-v", "--video", type=str, required=True, help="Path to the input video file")
parser.add_argument("-s", "--source-lang", type=str, default=config.DEFAULT_SOURCE_LANG,
help=f"Source language of the video audio (default: {config.DEFAULT_SOURCE_LANG})")
parser.add_argument("-t", "--target-lang", type=str, default=config.DEFAULT_TARGET_LANG,
help=f"Target language for the captions (default: {config.DEFAULT_TARGET_LANG})")
parser.add_argument("-o", "--output", type=str, default=None,
help="Path for the output captioned video (default: <video_name>_<target>_captioned.mp4)")
parser.add_argument("--whisper-model", type=str, default=config.DEFAULT_WHISPER_MODEL,
help=f"OpenAI Whisper model name (default: {config.DEFAULT_WHISPER_MODEL})")
parser.add_argument("--model", type=str, default=config.DEFAULT_OPENAI_MODEL,
help=f"OpenAI GPT model name for translation (default: {config.DEFAULT_OPENAI_MODEL})")
parser.add_argument("--keep-temp", action="store_true", default=False,
help="Keep intermediate audio (.wav) and subtitle (.srt) files")
parser.add_argument("--style", type=str, choices=list(config.SUBTITLE_PRESETS.keys()), default=config.DEFAULT_STYLE,
help=f"Subtitle style preset: {', '.join(config.SUBTITLE_PRESETS.keys())} (default: {config.DEFAULT_STYLE})")
parser.add_argument("--custom-style", type=str, default=None,
help="Raw ASS style override (e.g. 'FontName=Arial,FontSize=24,PrimaryColour=&H0000FFFF')")
parser.add_argument("--max-chars", type=int, default=0,
help="Force subtitle line length limit by splitting segments (default: 0 for no splitting)")
args = parser.parse_args()
video_path = os.path.abspath(args.video)
if not os.path.exists(video_path):
print(f"[Error] Input video file does not exist: {args.video}")
sys.exit(1)
# Define output file path if not specified
video_dir = os.path.dirname(video_path)
video_name, video_ext = os.path.splitext(os.path.basename(video_path))
if args.output:
output_path = os.path.abspath(args.output)
else:
output_path = os.path.join(video_dir, f"{video_name}_{args.target_lang}_captioned{video_ext}")
# Use space-free temporary names to prevent FFmpeg subtitle filter parsing errors
temp_audio_path = os.path.join(video_dir, f"temp_audio_{int(time.time())}.wav")
temp_srt_path = os.path.join(video_dir, f"temp_subtitles_{int(time.time())}.srt")
# User-friendly SRT path for when keep-temp is enabled
final_srt_path = os.path.join(video_dir, f"{video_name}_{args.target_lang}.srt")
print("\n" + "="*50)
print(" VIDEO CAPTIONING & TRANSLATION TOOL (OPENAI)")
print("="*50)
print(f"Input Video: {video_path}")
print(f"Source Language: {args.source_lang}")
print(f"Target Language: {args.target_lang}")
print(f"Output Video: {output_path}")
print(f"Whisper Model: {args.whisper_model}")
print(f"Translation Model:{args.model}")
print("="*50 + "\n")
pipeline_start = time.time()
# Step 1: Extract Audio
step_start = time.time()
if not extract_audio(video_path, temp_audio_path):
print("[Error] Failed to extract audio. Exiting.")
sys.exit(1)
audio_time = time.time() - step_start
# Step 2: Transcribe Audio via OpenAI API
step_start = time.time()
try:
segments = transcribe_audio(temp_audio_path, args.whisper_model)
except Exception as e:
print(f"[Error] ASR failed: {e}")
if not args.keep_temp and os.path.exists(temp_audio_path):
os.remove(temp_audio_path)
sys.exit(1)
transcribe_time = time.time() - step_start
if not segments:
print("[Warning] No speech detected in video audio.")
shutil.copy2(video_path, output_path)
print(f"[Finished] Output copied to {output_path}")
if not args.keep_temp and os.path.exists(temp_audio_path):
os.remove(temp_audio_path)
sys.exit(0)
# Step 3: Translate segments via OpenAI API
step_start = time.time()
try:
translated_segments = translate_segments(
segments=segments,
source_lang=args.source_lang,
target_lang=args.target_lang,
model=args.model
)
except Exception as e:
print(f"[Error] Translation failed: {e}")
if not args.keep_temp and os.path.exists(temp_audio_path):
os.remove(temp_audio_path)
sys.exit(1)
translation_time = time.time() - step_start
# Optional: Adjust subtitle cadence by splitting long segments
if args.max_chars > 0:
from translator import split_long_segments
translated_segments = split_long_segments(translated_segments, args.max_chars)
# Step 4: Write Subtitles
write_srt(translated_segments, temp_srt_path)
# Step 5: Burn Subtitles locally using FFmpeg
step_start = time.time()
burn_success = burn_subtitles(
video_path=video_path,
srt_path=temp_srt_path,
output_path=output_path,
style_preset=args.style,
custom_style=args.custom_style
)
burn_time = time.time() - step_start
# Clean up temp files
if args.keep_temp:
friendly_audio_path = os.path.join(video_dir, f"{video_name}_temp_audio.wav")
if os.path.exists(temp_audio_path):
shutil.move(temp_audio_path, friendly_audio_path)
print(f"[Cleanup] Saved audio track copy at: {friendly_audio_path}")
if os.path.exists(temp_srt_path):
shutil.move(temp_srt_path, final_srt_path)
print(f"[Cleanup] Saved subtitle file copy at: {final_srt_path}")
else:
print("[Cleanup] Removing temporary files...")
if os.path.exists(temp_audio_path):
os.remove(temp_audio_path)
if os.path.exists(temp_srt_path):
os.remove(temp_srt_path)
if burn_success:
print("\n" + "="*50)
print(f"SUCCESS: Captioned video generated!")
print(f"Saved at: {output_path}")
print("-"*50)
print(f"Timing Breakdown:")
print(f" - Audio Extraction: {audio_time:.1f}s")
print(f" - OpenAI ASR: {transcribe_time:.1f}s")
print(f" - OpenAI Translate: {translation_time:.1f}s")
print(f" - FFmpeg Burning: {burn_time:.1f}s")
print(f" - Total Elapsed: {time.time() - pipeline_start:.1f}s")
print("="*50 + "\n")
else:
print("\n[Error] Failed to generate captioned video during subtitle burning phase.")
sys.exit(1)
if __name__ == "__main__":
main()