View full analysis
float:
"""Return media duration in seconds. Raises RuntimeError on ffprobe failure."""
res = subprocess.run(
[
"ffprobe", "-v", "error",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
str(path),
],
check=False, capture_output=True, text=True,
)
if res.returncode != 0:
# Surface only the last meaningful line so the user gets a clean message.
msg = (res.stderr or "").strip().splitlines()
last = msg[-1] if msg else "ffprobe failed"
raise RuntimeError(f"ffprobe failed: {last}")
try:
return float(res.stdout.strip())
except ValueError:
raise RuntimeError("ffprobe returned no duration")
def detect_scene_changes(path: Path, threshold: float = 0.3) -> List[float]:
res = subprocess.run(
[
"ffmpeg", "-hide_banner", "-i", str(path),
"-filter:v", f"select='gt(scene,{threshold})',showinfo",
"-f", "null", "-",
],
check=False, capture_output=True, text=True,
)
timestamps: List[float] = []
for line in res.stderr.splitlines():
m = re.search(r"pts_time:([0-9.]+)", line)
if m:
timestamps.append(float(m.group(1)))
return timestamps
def extract_frame_at(src: Path, timestamp: float, out_path: Path) -> bool:
res = subprocess.run(
[
"ffmpeg", "-hide_banner", "-y",
"-ss", f"{timestamp}",
"-i", str(src),
"-frames:v", "1",
"-q:v", "2",
str(out_path),
],
check=False, capture_output=True, text=True,
)
return res.returnco
...[truncated 2497 chars]