Source code for fmusvid.backends.ffmpeg.subtitle

"""
Subtitle operations for FFmpeg backend.

This module provides subtitle functionality for the FFmpeg backend.
"""

import os
import tempfile
from pathlib import Path
from typing import Union, List, Tuple, Optional, Dict, Any
import logging

from PIL import Image, ImageDraw, ImageFont, ImageColor

from ...operations.subtitle import SubtitleEntry

logger = logging.getLogger(__name__)

[docs] class FFmpegSubtitle: """FFmpeg subtitle operations."""
[docs] def add_subtitles(self, video: Dict, entries: List[SubtitleEntry], font: str = "Arial", size: int = 24, color: Union[str, Tuple[int, int, int]] = "white", position: Optional[Tuple[int, int]] = None) -> Dict: """ Add subtitles from a list of entries. Args: video: Video dictionary entries: List of SubtitleEntry objects font: Font name or path size: Font size in pixels color: Text color (name or RGB tuple) position: Default position (None for bottom center) Returns: Updated video dictionary """ if not entries: return video self._ensure_temp_dir() # Convert color to hex format if isinstance(color, tuple): hex_color = "#{:02x}{:02x}{:02x}".format(*color) else: rgb = ImageColor.getrgb(color) hex_color = "#{:02x}{:02x}{:02x}".format(*rgb) # Create a temporary SRT file srt_file = Path(tempfile.mktemp(suffix=".srt", dir=self._temp_dir)) with open(srt_file, 'w', encoding='utf-8') as f: for i, entry in enumerate(entries, 1): # Format timestamps as SRT format: HH:MM:SS,mmm start_time_str = self._format_srt_time(entry.start_time) end_time_str = self._format_srt_time(entry.end_time) # Write SRT entry f.write(f"{i}\n") f.write(f"{start_time_str} --> {end_time_str}\n") f.write(f"{entry.text}\n\n") # Create subtitle filter # Escape path for Windows (replace colons) escaped_path = srt_file.as_posix().replace('\\', '\\\\').replace(':', '\\:') subtitle_filter = f"subtitles={escaped_path}:force_style=" # Add font style style_params = [ f"FontName={font}", f"FontSize={size}", f"PrimaryColour={hex_color}", f"Outline=1", f"Shadow=0", f"MarginV=10" ] # Add position if specified if position: video_info = self.get_info(video) video_width = video_info.get("width", 1280) video_height = video_info.get("height", 720) x, y = position # Convert absolute position to percentage x_percent = int((x / video_width) * 100) y_percent = int((y / video_height) * 100) style_params.append(f"MarginL={x_percent}") style_params.append(f"MarginV={y_percent}") subtitle_filter += ','.join(style_params) # Apply the subtitle filter using apply_filter return self.apply_filter(video, subtitle_filter)
[docs] def add_subtitle_text(self, video: Dict, entry: SubtitleEntry, font: str = "Arial", size: int = 24, color: Union[str, Tuple[int, int, int]] = "white") -> Dict: """ Add a single subtitle entry. Args: video: Video dictionary entry: SubtitleEntry object font: Font name or path size: Font size in pixels color: Text color (name or RGB tuple) Returns: Updated video dictionary """ # Reuse the add_subtitles method with a single entry return self.add_subtitles( video, [entry], font=font, size=size, color=color, position=entry.position )
def _format_srt_time(self, seconds: float) -> str: """ Format time in seconds to SRT format (HH:MM:SS,mmm). Args: seconds: Time in seconds Returns: Formatted time string """ hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) seconds = seconds % 60 return f"{hours:02d}:{minutes:02d}:{int(seconds):02d},{int((seconds % 1) * 1000):03d}"