|
20 | 20 | import pytest |
21 | 21 |
|
22 | 22 | from scenedetect import SceneManager, open_video |
23 | | -from scenedetect.common import Timecode |
| 23 | +from scenedetect.common import FrameTimecode, Timecode |
24 | 24 | from scenedetect.detectors import ContentDetector |
25 | 25 | from scenedetect.output import save_images, write_scene_list |
26 | 26 | from scenedetect.stats_manager import StatsManager |
@@ -229,18 +229,29 @@ def test_vfr_save_images_opencv_matches_pyav(test_vfr_video: str, tmp_path): |
229 | 229 | If the OpenCV seek off-by-one bug is present, scene thumbnails will show content from the |
230 | 230 | wrong scene; MSE against PyAV (ground truth) will be very high for those scenes. |
231 | 231 | """ |
232 | | - # Run save-images for both backends with 1 image per scene for simplicity. |
233 | | - scene_lists = {} |
| 232 | + # Detect scenes once and save images with both backends from the same scene list. Detection |
| 233 | + # must not run per-backend: the cut at 00:01:39.474 scores content_val=27.08 against the |
| 234 | + # default threshold of 27.0, so decoder/colorspace differences between backends (or FFmpeg |
| 235 | + # builds - e.g. av 17.1.0 on macOS arm64) can flip it, changing the scene count. |
| 236 | + video = open_video(test_vfr_video, backend="pyav") |
| 237 | + sm = SceneManager() |
| 238 | + sm.add_detector(ContentDetector()) |
| 239 | + sm.detect_scenes(video=video) |
| 240 | + scene_list = sm.get_scene_list() |
| 241 | + assert len(scene_list) > 0 |
| 242 | + |
| 243 | + # Run save-images for both backends with 1 image per scene for simplicity. The backends |
| 244 | + # report different nominal frame rates for VFR video, so rebase the scene list onto each |
| 245 | + # video's rate; the underlying PTS values are preserved (FrameTimecode copy constructor). |
234 | 246 | for backend in ("pyav", "opencv"): |
235 | 247 | out_dir = tmp_path / backend |
236 | 248 | out_dir.mkdir() |
237 | 249 | video = open_video(test_vfr_video, backend=backend) |
238 | | - sm = SceneManager() |
239 | | - sm.add_detector(ContentDetector()) |
240 | | - sm.detect_scenes(video=video) |
241 | | - scene_lists[backend] = sm.get_scene_list() |
242 | | - assert len(scene_lists[backend]) > 0 |
243 | | - save_images(scene_lists[backend], video, num_images=1, output_dir=str(out_dir)) |
| 250 | + rebased = [ |
| 251 | + (FrameTimecode(start, fps=video.frame_rate), FrameTimecode(end, fps=video.frame_rate)) |
| 252 | + for start, end in scene_list |
| 253 | + ] |
| 254 | + save_images(rebased, video, num_images=1, output_dir=str(out_dir)) |
244 | 255 |
|
245 | 256 | pyav_imgs = sorted((tmp_path / "pyav").glob("*.jpg")) |
246 | 257 | opencv_imgs = sorted((tmp_path / "opencv").glob("*.jpg")) |
|
0 commit comments