""" Utility functions for video processing. Provides FFmpeg wrappers and common operations used by all strategies. """ import json import os import subprocess from pathlib import Path def get_ffmpeg_cmd(cmd_name: str = "ffmpeg") -> str: """Get the full path to ffmpeg/ffprobe, checking common locations if not in PATH""" # 1. Try default from PATH try: subprocess.run([cmd_name, "-version"], capture_output=True, check=True) return cmd_name except (subprocess.CalledProcessError, FileNotFoundError): pass # 2. Try the specific Winget path found in the user environment winget_path = Path(r"C:\Users\91368\AppData\Local\Microsoft\WinGet\Packages\Gyan.FFmpeg_Microsoft.Winget.Source_8wekyb3d8bbwe\ffmpeg-8.0.1-full_build\bin") if winget_path.exists(): target = winget_path / (f"{cmd_name}.exe" if os.name == 'nt' else cmd_name) if target.exists(): return str(target) return cmd_name def get_video_duration(video_path: str) -> float: """ Get video duration in seconds using ffprobe. """ cmd = [ get_ffmpeg_cmd("ffprobe"), "-v", "error", "-show_entries", "format=duration", "-of", "json", video_path ] try: result = subprocess.run( cmd, capture_output=True, text=True, check=True ) data = json.loads(result.stdout) return float(data["format"]["duration"]) except subprocess.CalledProcessError as e: raise RuntimeError(f"Failed to get video duration: {e.stderr}") except (KeyError, ValueError) as e: raise RuntimeError(f"Invalid ffprobe output: {e}") def extract_audio( video_path: str, output_path: str | None = None, sample_rate: int = 16000, codec: str = "pcm_s16le" ) -> str: """ Extract audio from video file using FFmpeg. Args: video_path: Path to input video output_path: Path for output audio (default: same name with .wav) sample_rate: Audio sample rate in Hz (16000 recommended for Whisper) codec: Audio codec (pcm_s16le for WAV) Returns: Path to extracted audio file Raises: RuntimeError: If FFmpeg fails """ if output_path is None: video_name = Path(video_path).stem output_path = str(Path(video_path).parent / f"{video_name}_audio.wav") cmd = [ get_ffmpeg_cmd("ffmpeg"), "-i", video_path, "-vn", # No video "-acodec", codec, "-ar", str(sample_rate), "-ac", "1", # Mono "-y", # Overwrite output file output_path ] try: subprocess.run( cmd, capture_output=True, text=True, check=True ) return output_path except subprocess.CalledProcessError as e: raise RuntimeError(f"Failed to extract audio: {e.stderr}") def slice_video( video_path: str, start_time: float, end_time: float, output_path: str | None = None ) -> str: """ Slice a video segment using FFmpeg stream copy (fast, no re-encoding). Args: video_path: Path to input video start_time: Start time in seconds end_time: End time in seconds output_path: Path for output segment Returns: Path to output segment Raises: RuntimeError: If FFmpeg fails """ if output_path is None: video_name = Path(video_path).stem output_path = str( Path(video_path).parent / f"{video_name}_{start_time:.1f}-{end_time:.1f}.mp4" ) duration = end_time - start_time cmd = [ get_ffmpeg_cmd("ffmpeg"), "-ss", str(start_time), "-i", video_path, "-t", str(duration), "-c", "copy", # Stream copy (no re-encoding) "-y", output_path ] try: subprocess.run( cmd, capture_output=True, text=True, check=True ) return output_path except subprocess.CalledProcessError as e: raise RuntimeError(f"Failed to slice video: {e.stderr}") def check_ffmpeg_installed() -> bool: """Check if FFmpeg and FFprobe are available.""" try: subprocess.run( [get_ffmpeg_cmd("ffmpeg"), "-version"], capture_output=True, check=True ) subprocess.run( [get_ffmpeg_cmd("ffprobe"), "-version"], capture_output=True, check=True ) return True except (subprocess.CalledProcessError, FileNotFoundError): return False