71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
|
|
"""Keyframe engine + preserved scene/uniform fallbacks."""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import frames
|
||
|
|
|
||
|
|
|
||
|
|
def test_keyframe_engine_on_cut_clip(cut_clip: Path, tmp_path: Path):
|
||
|
|
out, meta = frames.extract_keyframes(str(cut_clip), tmp_path / "f", max_frames=50)
|
||
|
|
assert meta["engine"] == "keyframe"
|
||
|
|
assert meta["fallback"] is False
|
||
|
|
assert len(out) >= frames.KEYFRAME_MIN
|
||
|
|
assert all(fr["reason"] == "keyframe" for fr in out)
|
||
|
|
assert len(out) == len(list((tmp_path / "f").glob("frame_*.jpg")))
|
||
|
|
|
||
|
|
|
||
|
|
def test_keyframe_even_sampling_caps_and_spans(cut_clip: Path, tmp_path: Path):
|
||
|
|
out, meta = frames.extract_keyframes(str(cut_clip), tmp_path / "f", max_frames=5)
|
||
|
|
assert meta["engine"] == "keyframe"
|
||
|
|
assert len(out) == 5
|
||
|
|
assert meta["selected_count"] == 5
|
||
|
|
assert meta["candidate_count"] > 5
|
||
|
|
ts = [fr["timestamp_seconds"] for fr in out]
|
||
|
|
assert ts == sorted(ts)
|
||
|
|
assert ts[0] < ts[-1] # spans first → last keyframe
|
||
|
|
assert [fr["index"] for fr in out] == [0, 1, 2, 3, 4]
|
||
|
|
|
||
|
|
|
||
|
|
def test_keyframe_fallback_on_static_clip(static_clip: Path, tmp_path: Path):
|
||
|
|
out, meta = frames.extract_keyframes(str(static_clip), tmp_path / "f", max_frames=50)
|
||
|
|
assert meta["engine"] == "uniform"
|
||
|
|
assert meta["fallback"] is True
|
||
|
|
assert len(out) > 0
|
||
|
|
assert all(fr["reason"] == "uniform" for fr in out)
|
||
|
|
|
||
|
|
|
||
|
|
def test_scene_engine_on_cut_clip(cut_clip: Path, tmp_path: Path):
|
||
|
|
out, meta = frames.extract_scene_or_uniform(
|
||
|
|
str(cut_clip), tmp_path / "f", fps=2.0, target_frames=50, max_frames=100,
|
||
|
|
)
|
||
|
|
assert meta["engine"] == "scene"
|
||
|
|
assert meta["fallback"] is False
|
||
|
|
assert len(out) >= frames.SCENE_MIN_FRAMES
|
||
|
|
|
||
|
|
|
||
|
|
def test_scene_even_sampling_caps_and_spans(cut_clip: Path, tmp_path: Path):
|
||
|
|
"""Over-cap scene detection must even-sample across the whole clip, not keep
|
||
|
|
the first N cuts and drop the tail (the long-video coverage bug)."""
|
||
|
|
out, meta = frames.extract_scene_or_uniform(
|
||
|
|
str(cut_clip), tmp_path / "f", fps=2.0, target_frames=50, max_frames=5,
|
||
|
|
)
|
||
|
|
assert meta["engine"] == "scene"
|
||
|
|
assert meta["fallback"] is False
|
||
|
|
assert len(out) == 5
|
||
|
|
assert meta["selected_count"] == 5
|
||
|
|
assert meta["candidate_count"] > 5 # all cuts detected, then sampled down
|
||
|
|
ts = [fr["timestamp_seconds"] for fr in out]
|
||
|
|
assert ts == sorted(ts)
|
||
|
|
assert ts[-1] > 4.0 # spans the full ~5.6s clip, not just the first ~1.6s
|
||
|
|
assert len(out) == len(list((tmp_path / "f").glob("frame_*.jpg")))
|
||
|
|
assert [fr["index"] for fr in out] == [0, 1, 2, 3, 4]
|
||
|
|
|
||
|
|
|
||
|
|
def test_scene_fallback_on_static_clip(static_clip: Path, tmp_path: Path):
|
||
|
|
out, meta = frames.extract_scene_or_uniform(
|
||
|
|
str(static_clip), tmp_path / "f", fps=2.0, target_frames=12, max_frames=100,
|
||
|
|
)
|
||
|
|
assert meta["engine"] == "uniform"
|
||
|
|
assert meta["fallback"] is True
|