brandtech-sandbox-video-sub.../Video-Translator-Rerun.py
2026-04-16 15:41:25 +05:30

215 lines
No EOL
9 KiB
Python
Executable file

import ffmpeg
import argparse
import os
import sys
import shutil
import subprocess
from pathlib import Path
# Function to get available system fonts
def get_system_fonts():
"""Get list of available fonts on the system using fc-list command"""
available_fonts = []
try:
# Try to get fonts using fc-list (works on Linux, macOS with fontconfig)
output = subprocess.check_output(['fc-list', ':', 'family']).decode('utf-8')
fonts = set()
for line in output.splitlines():
for font in line.split(','):
font = font.strip()
if font:
fonts.add(font)
available_fonts = sorted(list(fonts))
except (subprocess.SubprocessError, FileNotFoundError):
# Fallback for systems without fc-list
available_fonts = [
'Arial', 'Helvetica', 'Times New Roman', 'Courier New', 'Verdana',
'Georgia', 'Palatino', 'Garamond', 'Bookman', 'Comic Sans MS',
'Trebuchet MS', 'Arial Black', 'Impact', 'Tahoma'
]
return available_fonts
class SubtitleBurner:
def __init__(self, video_path, subtitle_path):
if not os.path.exists(video_path):
raise FileNotFoundError(f"Video file not found: {video_path}")
if not os.path.exists(subtitle_path):
raise FileNotFoundError(f"Subtitle file not found: {subtitle_path}")
self.video_path = video_path
self.subtitle_path = subtitle_path
# Get available fonts
self.available_fonts = get_system_fonts()
def burn_subtitles(self, output_path, font='Arial', fontsize=10, primary_color='white',
outline_color='black', outline_width=1, position='bottom'):
"""Burn subtitles into video using FFmpeg with customizable font options
Args:
output_path: Path for the output video
font: Font family to use (default: Arial)
fontsize: Font size (default: 24)
primary_color: Main text color (default: white)
outline_color: Text outline color (default: black)
outline_width: Width of the text outline (default: 1)
position: Subtitle position, 'bottom' or 'top' (default: bottom)
"""
print(f"Burning subtitles from {self.subtitle_path} into video with custom styling...")
try:
# Create output directory if it doesn't exist
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
# Convert position to FFmpeg subtitle style format
y_position = 'h-max(th,48)' if position == 'bottom' else '10'
# Convert color names to ASS color format (BBGGRR)
color_map = {
'white': 'FFFFFF',
'yellow': '00FFFF',
'black': '000000',
'red': '0000FF',
'blue': 'FF0000',
'green': '00FF00',
'orange': '0080FF',
'purple': '800080'
}
# Get hex color values or use defaults if not found
primary_hex = color_map.get(primary_color.lower(), 'FFFFFF')
outline_hex = color_map.get(outline_color.lower(), '000000')
# Verify font is available or use fallback
if font not in self.available_fonts:
print(f"Warning: Font '{font}' not available. Using Arial as fallback.")
font = 'Arial'
# Logging to help debug outline width issues
print(f"DEBUG: Using outline width: {outline_width} (type: {type(outline_width)})")
# Escape path for FFmpeg filter string: forward slashes + escaped colon
subtitle_path_escaped = self.subtitle_path.replace('\\', '/').replace(':', '\\:')
# Create advanced subtitles filter with styling options
subtitles_filter = (
f"subtitles='{subtitle_path_escaped}':force_style='Fontname={font},"
f"Fontsize={fontsize},PrimaryColour=&H{primary_hex},"
f"OutlineColour=&H{outline_hex},BorderStyle=1,Outline={outline_width:.1f},"
f"Alignment=2,MarginV=10,Shadow=0,MarginL=10,MarginR=10,y={y_position}'"
)
# Burn subtitles
stream = ffmpeg.input(self.video_path)
stream = ffmpeg.output(stream, output_path,
vf=subtitles_filter,
acodec='copy')
# Run FFmpeg command
print(f"Creating output video: {output_path}")
ffmpeg.run(stream, overwrite_output=True)
print(f"Successfully created video with burned subtitles: {output_path}")
except ffmpeg.Error as e:
print('FFmpeg error:', e.stderr.decode() if e.stderr else str(e))
raise
except Exception as e:
print(f"Error during subtitle burning: {str(e)}")
raise
def validate_files(video_path, subtitle_path):
"""Validate input files exist and have correct extensions"""
# Check video extension
video_ext = os.path.splitext(video_path)[1].lower()
valid_video_extensions = ['.mp4', '.avi', '.mkv', '.mov']
if video_ext not in valid_video_extensions:
raise ValueError(f"Invalid video format. Supported formats: {', '.join(valid_video_extensions)}")
# Check subtitle extension
subtitle_ext = os.path.splitext(subtitle_path)[1].lower()
if subtitle_ext != '.srt':
raise ValueError("Subtitle file must be in .srt format")
def main():
# Set up argument parser
parser = argparse.ArgumentParser(description='Burn subtitles into video')
parser.add_argument('input_video', help='Path to the input video file')
parser.add_argument('subtitle_file', help='Path to the SRT subtitle file')
parser.add_argument('--output', '-o', help='Path to the output video file',
default='output_with_subtitles.mp4')
parser.add_argument('--font', help='Font family for subtitles', default='Arial')
parser.add_argument('--fontsize', help='Font size for subtitles', type=int, default=10)
parser.add_argument('--text-color', help='Subtitle text color', default='white')
parser.add_argument('--outline-color', help='Subtitle outline color', default='black')
parser.add_argument('--outline-width', help='Subtitle outline width', type=float, default=1.0)
parser.add_argument('--position', help='Subtitle position (bottom or top)', choices=['bottom', 'top'], default='bottom')
args = parser.parse_args()
try:
# Validate input files
validate_files(args.input_video, args.subtitle_file)
# Resolve all paths to absolute to avoid Windows relative-path issues
input_video = str(Path(args.input_video).resolve())
subtitle_file = str(Path(args.subtitle_file).resolve())
output_path = str(Path(args.output).resolve())
# Generate unique names based on input video
base_filename = os.path.splitext(os.path.basename(input_video))[0]
subtitle_copy = f"{base_filename}_subtitles.srt"
# Create temporary directory for processing if needed
temp_dir = str(Path(output_path).parent / 'temp')
if not os.path.exists(temp_dir):
os.makedirs(temp_dir)
temp_subtitle_path = os.path.join(temp_dir, subtitle_copy)
try:
# Copy subtitle file to temp directory with unique name
shutil.copy2(subtitle_file, temp_subtitle_path)
# Create output directory if it doesn't exist
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
# Create burner and process using the temporary subtitle file
print("Initializing subtitle burner...")
burner = SubtitleBurner(input_video, temp_subtitle_path)
print("Starting subtitle burn process...")
burner.burn_subtitles(
output_path,
font=args.font,
fontsize=args.fontsize,
primary_color=args.text_color,
outline_color=args.outline_color,
outline_width=args.outline_width,
position=args.position
)
finally:
# Clean up temporary files
if os.path.exists(temp_subtitle_path):
os.remove(temp_subtitle_path)
if os.path.exists(temp_dir):
try:
os.rmdir(temp_dir)
except OSError:
pass # Directory might not be empty
except FileNotFoundError as e:
print(f"Error: {str(e)}")
sys.exit(1)
except ValueError as e:
print(f"Error: {str(e)}")
sys.exit(1)
except Exception as e:
print(f"Error: An unexpected error occurred - {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()