feat: PyAV decode path for AV1 rips (AvFrameReader + scenedetect pyav backend); cv2 kept as fallback

This commit is contained in:
2026-11-01 18:21:32 -05:00
parent c8c9aae171
commit 39a8e60242
+65 -2
View File
@@ -252,6 +252,61 @@ def face_qualifies(box, frame_shape, min_width, min_height,
return True return True
class AvFrameReader:
"""Forward-only frame reader with a cv2.VideoCapture-compatible
surface (read/release). PyAV ships its own ffmpeg build with libdav1d:
the ffmpeg bundled inside opencv-python has no software AV1 decoder,
and Alder Lake-N has no AV1 hw decode, so BD AV1 rips (e.g. SavI0r)
decode 0 frames through cv2. scenedetect likewise prefers its pyav
backend once av is installed, fixing scene detection on the same files.
"""
def __init__(self, path):
import av
self._c = av.open(path)
self._vs = self._c.streams.video[0]
self._vs.thread_type = "AUTO"
self._iter = self._c.decode(self._vs)
@property
def frame_count(self):
vs = self._vs
if vs.frames:
return int(vs.frames)
# MKV headers often leave stream.frames unset; estimate from the
# container duration (it is this file's total decode work anyway).
dur = self._c.duration # microseconds, or None
if dur and vs.average_rate:
return int(float(dur) / 1e6 * float(vs.average_rate))
return 0
def read(self):
try:
frame = next(self._iter)
except StopIteration:
return False, None
return True, frame.to_ndarray(format="bgr24")
def release(self):
try:
self._c.close()
except Exception:
pass
def open_reader(video_path):
"""PyAV when available (AV1-capable), else plain cv2."""
import cv2
try:
return AvFrameReader(video_path)
except ImportError:
pass
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return None
return cap
def process_video(video_path, output_dir, args, model, device, draw, def process_video(video_path, output_dir, args, model, device, draw,
output_start=0): output_start=0):
from scenedetect import open_video, SceneManager, ContentDetector from scenedetect import open_video, SceneManager, ContentDetector
@@ -260,6 +315,12 @@ def process_video(video_path, output_dir, args, model, device, draw,
try: try:
import cv2 import cv2
try:
import av # noqa: F401
# scenedetect defaults to backend="opencv", which cannot decode
# AV1 in this env; pyav ships its own ffmpeg with libdav1d.
video = open_video(video_path, backend="pyav")
except ImportError:
video = open_video(video_path) video = open_video(video_path)
scene_manager = SceneManager() scene_manager = SceneManager()
scene_manager.add_detector( scene_manager.add_detector(
@@ -269,11 +330,13 @@ def process_video(video_path, output_dir, args, model, device, draw,
f"{video_path}: {e}") f"{video_path}: {e}")
return output_start return output_start
cap = cv2.VideoCapture(video_path) cap = open_reader(video_path)
if not cap.isOpened(): if cap is None:
print(f"Error opening video file {video_path}") print(f"Error opening video file {video_path}")
return output_start return output_start
frame_count = getattr(cap, "frame_count", 0)
if not frame_count and hasattr(cap, "get"):
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
try: try: