LinguaStream / src /video /processor.py
Maaz1's picture
Update src/video/processor.py
54ba66a verified
"""
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