1717test case material.
1818"""
1919
20- import time
20+ from dataclasses import dataclass
21+ import os
22+ import typing as ty
2123
22- from scenedetect import detect , SceneManager , FrameTimecode , StatsManager
24+ import pytest
25+
26+ from scenedetect import detect , SceneManager , FrameTimecode , StatsManager , SceneDetector
2327from scenedetect .detectors import AdaptiveDetector , ContentDetector , ThresholdDetector
2428from scenedetect .backends .opencv import VideoStreamCv2
2529
26- # TODO: Test more parameters and add more videos. Parameterize the tests below such that
27- # a detector instance is combined with the other parameters like ground truth that go along
28- # with a specific video and detector values. E.g. Use Video-000, Video-001, etc..., and map
29- # that to a particular filename.
30-
31- TEST_MOVIE_CLIP_START_FRAMES_ACTUAL = [1199 , 1226 , 1260 , 1281 , 1334 , 1365 , 1590 , 1697 , 1871 ]
32- """Ground truth of start frame for each fast cut in `test_movie_clip`."""
33-
34- TEST_VIDEO_FILE_START_FRAMES_ACTUAL = [0 , 15 , 198 , 376 ]
35- """Results for `test_video_file` with default ThresholdDetector values."""
36-
37- FADES_FLOOR_START_FRAMES = [0 , 84 , 167 , 245 ]
38- """Results for `test_fades_clip` with default ThresholdDetector values."""
39-
40- FADES_CEILING_START_FRAMES = [0 , 42 , 125 , 209 ]
41- """Results for `test_fades_clip` with ThresholdDetector fade to light with threshold 243."""
42-
43-
44- def test_detect (test_video_file ):
45- """ Test scenedetect.detect and ThresholdDetector. """
46- scene_list = detect (video_path = test_video_file , detector = ThresholdDetector ())
47- assert len (scene_list ) == len (TEST_VIDEO_FILE_START_FRAMES_ACTUAL )
48- detected_start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
49- assert all (x == y for (x , y ) in zip (TEST_VIDEO_FILE_START_FRAMES_ACTUAL , detected_start_frames ))
50-
51-
52- def test_content_detector (test_movie_clip ):
53- """ Test SceneManager with VideoStreamCv2 and ContentDetector. """
54- video = VideoStreamCv2 (test_movie_clip )
55- scene_manager = SceneManager ()
56- scene_manager .add_detector (ContentDetector ())
57-
58- video_fps = video .frame_rate
59- start_time = FrameTimecode ('00:00:50' , video_fps )
60- end_time = FrameTimecode ('00:01:19' , video_fps )
6130
62- video .seek (start_time )
63- scene_manager .auto_downscale = True
64-
65- scene_manager .detect_scenes (video = video , end_time = end_time )
66- scene_list = scene_manager .get_scene_list ()
67- assert len (scene_list ) == len (TEST_MOVIE_CLIP_START_FRAMES_ACTUAL )
68- detected_start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
69- assert TEST_MOVIE_CLIP_START_FRAMES_ACTUAL == detected_start_frames
70- # Ensure last scene's end timecode matches the end time we set.
71- assert scene_list [- 1 ][1 ] == end_time
72-
73-
74- def test_adaptive_detector (test_movie_clip ):
75- """ Test SceneManager with VideoStreamCv2 and AdaptiveDetector. """
76- video = VideoStreamCv2 (test_movie_clip )
77- scene_manager = SceneManager ()
78- scene_manager .add_detector (AdaptiveDetector ())
79- scene_manager .auto_downscale = True
80-
81- video_fps = video .frame_rate
82- start_time = FrameTimecode ('00:00:50' , video_fps )
83- end_time = FrameTimecode ('00:01:19' , video_fps )
84-
85- video .seek (start_time )
86- scene_manager .detect_scenes (video = video , end_time = end_time )
87-
88- scene_list = scene_manager .get_scene_list ()
89- assert len (scene_list ) == len (TEST_MOVIE_CLIP_START_FRAMES_ACTUAL )
90- detected_start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
91- assert TEST_MOVIE_CLIP_START_FRAMES_ACTUAL == detected_start_frames
92- # Ensure last scene's end timecode matches the end time we set.
93- assert scene_list [- 1 ][1 ] == end_time
94-
95-
96- def test_threshold_detector (test_video_file ):
97- """ Test SceneManager with VideoStreamCv2 and ThresholdDetector. """
98- video = VideoStreamCv2 (test_video_file )
99- scene_manager = SceneManager ()
100- scene_manager .add_detector (ThresholdDetector ())
101- scene_manager .auto_downscale = True
102- scene_manager .detect_scenes (video )
103- scene_list = scene_manager .get_scene_list ()
104- assert len (scene_list ) == len (TEST_VIDEO_FILE_START_FRAMES_ACTUAL )
105- detected_start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
106- assert all (x == y for (x , y ) in zip (TEST_VIDEO_FILE_START_FRAMES_ACTUAL , detected_start_frames ))
31+ # TODO: Reduce code duplication here and in `conftest.py`
32+ def get_absolute_path (relative_path : str ) -> str :
33+ """ Returns the absolute path to a (relative) path of a file that
34+ should exist within the tests/ directory.
35+
36+ Throws FileNotFoundError if the file could not be found.
37+ """
38+ abs_path = os .path .join (os .path .abspath (os .path .dirname (__file__ )), relative_path )
39+ if not os .path .exists (abs_path ):
40+ raise FileNotFoundError ("""
41+ Test video file (%s) must be present to run test case. This file can be obtained by running the following commands from the root of the repository:
42+
43+ git fetch --depth=1 https://github.com/Breakthrough/PySceneDetect.git refs/heads/resources:refs/remotes/origin/resources
44+ git checkout refs/remotes/origin/resources -- tests/resources/
45+ git reset
46+ """ % relative_path )
47+ return abs_path
48+
49+
50+ @dataclass
51+ class TestCase :
52+ """Properties for detector test cases."""
53+ path : str
54+ """Path to video for test case."""
55+ detector : SceneDetector
56+ """Detector instance to use."""
57+ start_time : int
58+ """Start time as frames."""
59+ end_time : int
60+ """End time as frames."""
61+ scene_boundaries : ty .List [int ]
62+ """Scene boundaries."""
63+
64+ def detect (self ):
65+ """Run scene detection for test case. Should only be called once."""
66+ return detect (
67+ video_path = self .path ,
68+ detector = self .detector ,
69+ start_time = self .start_time ,
70+ end_time = self .end_time )
71+
72+
73+ def get_fast_cut_test_cases ():
74+ """Fixture for parameterized test cases that detect fast cuts."""
75+ return [
76+ pytest .param (
77+ TestCase (
78+ path = get_absolute_path ("resources/goldeneye.mp4" ),
79+ detector = ContentDetector (),
80+ start_time = 1199 ,
81+ end_time = 1450 ,
82+ scene_boundaries = [1199 , 1226 , 1260 , 1281 , 1334 , 1365 ]),
83+ id = "content_default" ),
84+ pytest .param (
85+ TestCase (
86+ path = get_absolute_path ("resources/goldeneye.mp4" ),
87+ detector = AdaptiveDetector (),
88+ start_time = 1199 ,
89+ end_time = 1450 ,
90+ scene_boundaries = [1199 , 1226 , 1260 , 1281 , 1334 , 1365 ]),
91+ id = "adaptive_default" ),
92+ pytest .param (
93+ TestCase (
94+ path = get_absolute_path ("resources/goldeneye.mp4" ),
95+ detector = ContentDetector (min_scene_len = 30 ),
96+ start_time = 1199 ,
97+ end_time = 1450 ,
98+ scene_boundaries = [1199 , 1260 , 1334 , 1365 ]),
99+ id = "content_min_scene_len" ),
100+ pytest .param (
101+ TestCase (
102+ path = get_absolute_path ("resources/goldeneye.mp4" ),
103+ detector = AdaptiveDetector (min_scene_len = 30 ),
104+ start_time = 1199 ,
105+ end_time = 1450 ,
106+ scene_boundaries = [1199 , 1260 , 1334 , 1365 ]),
107+ id = "adaptive_min_scene_len" ),
108+ ]
109+
110+
111+ def get_fade_in_out_test_cases ():
112+ """Fixture for parameterized test cases that detect fades."""
113+ # TODO: min_scene_len doesn't seem to be working as intended for ThresholdDetector.
114+ # Possibly related to #278: https://github.com/Breakthrough/PySceneDetect/issues/278
115+ return [
116+ pytest .param (
117+ TestCase (
118+ path = get_absolute_path ("resources/testvideo.mp4" ),
119+ detector = ThresholdDetector (),
120+ start_time = 0 ,
121+ end_time = 500 ,
122+ scene_boundaries = [0 , 15 , 198 , 376 ]),
123+ id = "threshold_testvideo_default" ),
124+ pytest .param (
125+ TestCase (
126+ path = get_absolute_path ("resources/fades.mp4" ),
127+ detector = ThresholdDetector (),
128+ start_time = 0 ,
129+ end_time = 250 ,
130+ scene_boundaries = [0 , 84 , 167 ]),
131+ id = "threshold_fades_default" ),
132+ pytest .param (
133+ TestCase (
134+ path = get_absolute_path ("resources/fades.mp4" ),
135+ detector = ThresholdDetector (
136+ threshold = 12.0 ,
137+ method = ThresholdDetector .Method .FLOOR ,
138+ add_final_scene = True ,
139+ ),
140+ start_time = 0 ,
141+ end_time = 250 ,
142+ scene_boundaries = [0 , 84 , 167 , 245 ]),
143+ id = "threshold_fades_floor" ),
144+ pytest .param (
145+ TestCase (
146+ path = get_absolute_path ("resources/fades.mp4" ),
147+ detector = ThresholdDetector (
148+ threshold = 243.0 ,
149+ method = ThresholdDetector .Method .CEILING ,
150+ add_final_scene = True ,
151+ ),
152+ start_time = 0 ,
153+ end_time = 250 ,
154+ scene_boundaries = [0 , 42 , 125 , 209 ]),
155+ id = "threshold_fades_ceil" ),
156+ ]
157+
158+
159+ @pytest .mark .parametrize ("test_case" , get_fast_cut_test_cases ())
160+ def test_detect_fast_cuts (test_case : TestCase ):
161+ scene_list = test_case .detect ()
162+ start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
163+ assert test_case .scene_boundaries == start_frames
164+ assert scene_list [0 ][0 ] == test_case .start_time
165+ assert scene_list [- 1 ][1 ] == test_case .end_time
166+
167+
168+ @pytest .mark .parametrize ("test_case" , get_fade_in_out_test_cases ())
169+ def test_detect_fades (test_case : TestCase ):
170+ scene_list = test_case .detect ()
171+ start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
172+ assert test_case .scene_boundaries == start_frames
173+ assert scene_list [0 ][0 ] == test_case .start_time
174+ assert scene_list [- 1 ][1 ] == test_case .end_time
107175
108176
109177def test_detectors_with_stats (test_video_file ):
@@ -116,10 +184,7 @@ def test_detectors_with_stats(test_video_file):
116184 scene_manager .add_detector (detector ())
117185 scene_manager .auto_downscale = True
118186 end_time = FrameTimecode ('00:00:08' , video .frame_rate )
119- benchmark_start = time .time ()
120187 scene_manager .detect_scenes (video = video , end_time = end_time )
121- benchmark_end = time .time ()
122- time_no_stats = benchmark_end - benchmark_start
123188 initial_scene_len = len (scene_manager .get_scene_list ())
124189 assert initial_scene_len > 0 # test case must have at least one scene!
125190 # Re-analyze using existing stats manager.
@@ -129,44 +194,6 @@ def test_detectors_with_stats(test_video_file):
129194 video .reset ()
130195 scene_manager .auto_downscale = True
131196
132- benchmark_start = time .time ()
133197 scene_manager .detect_scenes (video = video , end_time = end_time )
134- benchmark_end = time .time ()
135- time_with_stats = benchmark_end - benchmark_start
136198 scene_list = scene_manager .get_scene_list ()
137199 assert len (scene_list ) == initial_scene_len
138-
139- print ("--------------------------------------------------------------------" )
140- print ("StatsManager Benchmark For %s" % (detector .__name__ ))
141- print ("--------------------------------------------------------------------" )
142- print ("No Stats:\t %2.1fs" % time_no_stats )
143- print ("With Stats:\t %2.1fs" % time_with_stats )
144- print ("--------------------------------------------------------------------" )
145-
146-
147- def test_threshold_detector_fade_out (test_fades_clip ):
148- """Test ThresholdDetector handles fading out to black."""
149- video = VideoStreamCv2 (test_fades_clip )
150- scene_manager = SceneManager ()
151- scene_manager .add_detector (ThresholdDetector (add_final_scene = True ))
152- scene_manager .auto_downscale = True
153- scene_manager .detect_scenes (video )
154- scene_list = scene_manager .get_scene_list ()
155- assert len (scene_list ) == len (FADES_FLOOR_START_FRAMES )
156- detected_start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
157- assert all (x == y for (x , y ) in zip (FADES_FLOOR_START_FRAMES , detected_start_frames ))
158-
159-
160- def test_threshold_detector_fade_in (test_fades_clip ):
161- """Test ThresholdDetector handles fading in from white."""
162- video = VideoStreamCv2 (test_fades_clip )
163- scene_manager = SceneManager ()
164- scene_manager .add_detector (
165- ThresholdDetector (
166- threshold = 243 , method = ThresholdDetector .Method .CEILING , add_final_scene = True ))
167- scene_manager .auto_downscale = True
168- scene_manager .detect_scenes (video )
169- scene_list = scene_manager .get_scene_list ()
170- assert len (scene_list ) == len (FADES_CEILING_START_FRAMES )
171- detected_start_frames = [timecode .get_frames () for timecode , _ in scene_list ]
172- assert all (x == y for (x , y ) in zip (FADES_CEILING_START_FRAMES , detected_start_frames ))
0 commit comments