Spaces:
Running
Running
""" | |
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 | |