Spaces:
Sleeping
Sleeping
| """Video helpers: audio extraction, duration probing, single-frame grabbing. | |
| All heavy lifting is delegated to ffmpeg/ffprobe (already on PATH). ffmpeg's | |
| ``-ss`` before ``-i`` is both fast and frame-accurate in modern builds, which we | |
| rely on for precise frame extraction at a given timestamp. | |
| """ | |
| from __future__ import annotations | |
| import subprocess | |
| from pathlib import Path | |
| from . import config | |
| class FFmpegError(RuntimeError): | |
| """Raised when an ffmpeg/ffprobe subprocess fails.""" | |
| def _run(cmd: list[str]) -> subprocess.CompletedProcess: | |
| proc = subprocess.run(cmd, capture_output=True, text=True) | |
| if proc.returncode != 0: | |
| raise FFmpegError( | |
| f"Command failed ({proc.returncode}): {' '.join(cmd)}\n{proc.stderr.strip()}" | |
| ) | |
| return proc | |
| def get_duration(video_path: str | Path) -> float: | |
| """Return the media duration in seconds (0.0 if it cannot be determined).""" | |
| try: | |
| proc = _run( | |
| [ | |
| config.FFPROBE_BIN, | |
| "-v", "error", | |
| "-show_entries", "format=duration", | |
| "-of", "default=noprint_wrappers=1:nokey=1", | |
| str(video_path), | |
| ] | |
| ) | |
| return float(proc.stdout.strip()) | |
| except (FFmpegError, ValueError): | |
| return 0.0 | |
| def extract_audio(video_path: str | Path, out_wav: str | Path) -> Path: | |
| """Extract a 16 kHz mono WAV (the format faster-whisper expects).""" | |
| out_wav = Path(out_wav) | |
| out_wav.parent.mkdir(parents=True, exist_ok=True) | |
| _run( | |
| [ | |
| config.FFMPEG_BIN, | |
| "-y", | |
| "-i", str(video_path), | |
| "-vn", # drop video | |
| "-ac", "1", # mono | |
| "-ar", "16000", # 16 kHz | |
| "-f", "wav", | |
| str(out_wav), | |
| ] | |
| ) | |
| return out_wav | |
| def extract_frame(video_path: str | Path, timestamp: float, out_png: str | Path) -> Path: | |
| """Save a single frame at ``timestamp`` seconds as a PNG. | |
| ``-ss`` is placed before ``-i`` for fast, frame-accurate seeking. | |
| """ | |
| out_png = Path(out_png) | |
| out_png.parent.mkdir(parents=True, exist_ok=True) | |
| _run( | |
| [ | |
| config.FFMPEG_BIN, | |
| "-y", | |
| "-ss", f"{max(timestamp, 0.0):.3f}", | |
| "-i", str(video_path), | |
| "-frames:v", "1", | |
| "-q:v", "2", | |
| str(out_png), | |
| ] | |
| ) | |
| return out_png | |