""" Video processing utilities for combining video, audio, and subtitles. """ import os import shutil import subprocess from pathlib import Path import tempfile from src.utils.logger import get_logger from config import OUTPUT_DIR, SUBTITLE_FONT_SIZE logger = get_logger(__name__) def combine_video_audio_subtitles(video_path, audio_path, srt_path, output_path=None): try: video_path = Path(video_path) audio_path = Path(audio_path) srt_path = Path(srt_path) if output_path is None: lang_code = srt_path.stem.split('_')[-1] output_path = OUTPUT_DIR / f"{video_path.stem}_translated_{lang_code}.mp4" else: output_path = Path(output_path) logger.info(f"Combining video, audio, and subtitles") # FIXED FFMPEG COMMAND: cmd = [ 'ffmpeg', '-y', # Overwrite output '-i', str(video_path), # Original video '-i', str(audio_path), # Translated audio # Video processing (re-encode instead of copy to allow subtitles) '-vf', f"subtitles={str(srt_path)}:force_style='FontSize={SUBTITLE_FONT_SIZE}'", '-c:v', 'libx264', # Re-encode video '-preset', 'fast', # Faster encoding '-crf', '23', # Good quality # Audio processing '-c:a', 'aac', '-b:a', '192k', # Stream selection '-map', '0:v:0', # Video from first input '-map', '1:a:0', # Audio from second input # Metadata '-metadata:s:a:0', 'language=' + lang_code[:3], # Set audio language str(output_path) ] logger.debug(f"Running command: {' '.join(cmd)}") process = subprocess.run(cmd, capture_output=True, text=True) if process.returncode != 0: error_msg = f"FFmpeg failed: {process.stderr}" if "Filtergraph" in error_msg and "streamcopy" in error_msg: error_msg += "\nFIX: Cannot use both video filters and stream copy. Must re-encode video." raise Exception(error_msg) return output_path except Exception as e: logger.error(f"Combining failed: {str(e)}") raise Exception(f"Failed to combine video/audio/subtitles: {str(e)}") def combine_method_subtitles_filter(video_path, audio_path, srt_path, output_path): """ Combine video, audio, and subtitles using ffmpeg with subtitle filter. Args: video_path (Path): Path to the video file audio_path (Path): Path to the translated audio file srt_path (Path): Path to the subtitle file output_path (Path): Path for the output video Returns: Path: Path to the output video """ logger.info(f"Using subtitles filter method") cmd = [ 'ffmpeg', '-i', str(video_path), # Video input '-i', str(audio_path), # Audio input '-vf', f"subtitles={str(srt_path)}:force_style='FontSize={SUBTITLE_FONT_SIZE}'", # Subtitle filter '-map', '0:v', # Map video from first input '-map', '1:a', # Map audio from second input '-c:v', 'libx264', # Video codec '-c:a', 'aac', # Audio codec '-strict', 'experimental', '-b:a', '192k', # Audio bitrate '-y', # Overwrite output str(output_path) ] logger.debug(f"Running command: {' '.join(cmd)}") process = subprocess.run(cmd, capture_output=True, text=True) if process.returncode != 0: error_message = f"FFmpeg subtitles filter method failed: {process.stderr}" logger.error(error_message) raise Exception(error_message) return output_path def combine_method_with_temp(video_path, audio_path, srt_path, output_path): """ Combine video, audio, and subtitles using temporary files. Args: video_path (Path): Path to the video file audio_path (Path): Path to the translated audio file srt_path (Path): Path to the subtitle file output_path (Path): Path for the output video Returns: Path: Path to the output video """ logger.info(f"Using temporary file method") # Create temporary directory temp_dir = Path(tempfile.mkdtemp(prefix="video_combine_", dir=OUTPUT_DIR / "temp")) try: # Step 1: Combine video with audio temp_video_audio = temp_dir / "video_with_audio.mp4" cmd1 = [ 'ffmpeg', '-i', str(video_path), '-i', str(audio_path), '-c:v', 'copy', '-c:a', 'aac', '-strict', 'experimental', '-map', '0:v', '-map', '1:a', '-y', str(temp_video_audio) ] logger.debug(f"Running command (step 1): {' '.join(cmd1)}") process1 = subprocess.run(cmd1, capture_output=True, text=True) if process1.returncode != 0: error_message = f"Step 1 failed: {process1.stderr}" logger.error(error_message) raise Exception(error_message) # Step 2: Add subtitles to the combined video cmd2 = [ 'ffmpeg', '-i', str(temp_video_audio), '-vf', f"subtitles={str(srt_path)}:force_style='FontSize={SUBTITLE_FONT_SIZE}'", '-c:a', 'copy', '-y', str(output_path) ] logger.debug(f"Running command (step 2): {' '.join(cmd2)}") process2 = subprocess.run(cmd2, capture_output=True, text=True) if process2.returncode != 0: error_message = f"Step 2 failed: {process2.stderr}" logger.error(error_message) raise Exception(error_message) return output_path finally: # Clean up temporary directory try: shutil.rmtree(temp_dir) logger.debug(f"Cleaned up temporary directory: {temp_dir}") except Exception as e: logger.warning(f"Failed to clean up temp directory: {str(e)}") def combine_method_no_subtitles(video_path, audio_path, srt_path, output_path): """ Fallback method: Combine only video and audio without subtitles. Args: video_path (Path): Path to the video file audio_path (Path): Path to the translated audio file srt_path (Path): Path to the subtitle file (unused in this method) output_path (Path): Path for the output video Returns: Path: Path to the output video """ logger.info(f"Using fallback method (no subtitles)") # Just combine video and audio as fallback cmd = [ 'ffmpeg', '-i', str(video_path), '-i', str(audio_path), '-c:v', 'copy', '-c:a', 'aac', '-strict', 'experimental', '-map', '0:v', '-map', '1:a', '-y', str(output_path) ] logger.debug(f"Running command: {' '.join(cmd)}") process = subprocess.run(cmd, capture_output=True, text=True) if process.returncode != 0: error_message = f"Fallback method failed: {process.stderr}" logger.error(error_message) raise Exception(error_message) logger.warning("Video was combined without subtitles") return output_path