"""Clip extraction service using FFmpeg.""" import asyncio import logging import os from app.config import settings logger = logging.getLogger(__name__) async def extract_clip( video_path: str, start_time: float, end_time: float, output_path: str, ) -> str: """Extract a clip from video using FFmpeg stream copy (instant, no re-encode). Args: video_path: path to source video start_time: clip start in seconds end_time: clip end in seconds output_path: where to write the clip Returns: output_path """ os.makedirs(os.path.dirname(output_path), exist_ok=True) duration = end_time - start_time # Use stream copy for speed - seek before input for accuracy cmd = [ "ffmpeg", "-ss", str(start_time), "-i", video_path, "-t", str(duration), "-c", "copy", "-avoid_negative_ts", "make_zero", "-y", output_path, ] logger.info( f"Extracting clip: {start_time:.1f}s - {end_time:.1f}s -> {output_path}" ) proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) _, stderr = await proc.communicate() if proc.returncode != 0: raise RuntimeError(f"FFmpeg clip extraction failed: {stderr.decode()}") size_mb = os.path.getsize(output_path) / (1024 * 1024) logger.info(f"Extracted clip: {output_path} ({size_mb:.1f} MB)") return output_path async def extract_thumbnail( video_path: str, timestamp: float, output_path: str, ) -> str: """Extract a single frame as thumbnail.""" os.makedirs(os.path.dirname(output_path), exist_ok=True) cmd = [ "ffmpeg", "-ss", str(timestamp), "-i", video_path, "-vframes", "1", "-q:v", "2", "-y", output_path, ] proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) _, stderr = await proc.communicate() if proc.returncode != 0: raise RuntimeError(f"FFmpeg thumbnail extraction failed: {stderr.decode()}") return output_path async def get_video_duration(video_path: str) -> float: """Get video duration in seconds using ffprobe.""" cmd = [ "ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", video_path, ] proc = await asyncio.create_subprocess_exec( *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, ) stdout, _ = await proc.communicate() if proc.returncode != 0: return 0.0 import json data = json.loads(stdout.decode()) return float(data.get("format", {}).get("duration", 0))