1# -*- coding: utf-8 -*- 2# 3# PySceneDetect: Python-Based Video Scene Detector 4# --------------------------------------------------------------- 5# [ Site: http://www.bcastell.com/projects/PySceneDetect/ ] 6# [ Github: https://github.com/Breakthrough/PySceneDetect/ ] 7# [ Documentation: http://pyscenedetect.readthedocs.org/ ] 8# 9# Copyright (C) 2014-2020 Brandon Castellano <http://www.bcastell.com>. 10# 11# PySceneDetect is licensed under the BSD 3-Clause License; see the included 12# LICENSE file, or visit one of the following pages for details: 13# - https://github.com/Breakthrough/PySceneDetect/ 14# - http://www.bcastell.com/projects/PySceneDetect/ 15# 16# This software uses Numpy, OpenCV, click, tqdm, simpletable, and pytest. 17# See the included LICENSE files or one of the above URLs for more information. 18# 19# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22# AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 23# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25# 26 27""" PySceneDetect scenedetect.video_manager Tests 28 29This file includes unit tests for the scenedetect.video_manager module, acting as 30a video container/decoder, allowing seeking and concatenation of multiple sources. 31 32These unit tests test the VideoManager object with respect to object construction, 33testing argument format/limits, opening videos and grabbing frames, and appending 34multiple videos together. These tests rely on testvideo.mp4, available in the 35PySceneDetect git repository "resources" branch. 36 37These tests rely on the testvideo.mp4 test video file, available by checking out the 38PySceneDetect git repository "resources" branch, or the following URL to download it 39directly: https://github.com/Breakthrough/PySceneDetect/tree/resources/tests 40""" 41 42# Standard project pylint disables for unit tests using pytest. 43# pylint: disable=no-self-use, protected-access, multiple-statements, invalid-name 44# pylint: disable=redefined-outer-name 45 46 47# Third-Party Library Imports 48import pytest 49import cv2 50 51from scenedetect.scene_manager import SceneManager 52# PySceneDetect Library Imports 53from scenedetect.video_manager import VideoManager 54from scenedetect.video_manager import VideoOpenFailure 55 56# TODO: The following exceptions still require test cases. 57# Since these are API contract violations, should they be refactored 58# into assertions instead? 59from scenedetect.video_manager import VideoDecodingInProgress 60from scenedetect.video_manager import VideoDecoderNotStarted 61 62# TODO: Need to implement a mock VideoCapture to test the exceptions below. 63# TODO: The following exceptions still require test cases: 64from scenedetect.video_manager import VideoFramerateUnavailable 65from scenedetect.video_manager import VideoParameterMismatch 66 67 68def test_video_params(test_video_file): 69 """ Test VideoManager get_framerate/get_framesize methods on test_video_file. """ 70 try: 71 cap = cv2.VideoCapture(test_video_file) 72 video_manager = VideoManager([test_video_file] * 2) 73 assert cap.isOpened() 74 assert video_manager.get_framerate() == pytest.approx(cap.get(cv2.CAP_PROP_FPS)) 75 assert video_manager.get_framesize() == ( 76 pytest.approx(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), 77 pytest.approx(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) 78 finally: 79 cap.release() 80 video_manager.release() 81 82 83def test_start_release(test_video_file): 84 """ Test VideoManager start/release methods on 3 appended videos. """ 85 video_manager = VideoManager([test_video_file] * 2) 86 # *must* call release() after start() or video manager process will be rogue. 87 # 88 # The start method is the only big usage differences between the 89 # VideoManager and cv2.VideoCapture objects from the point of view 90 # of a SceneManager (the other VideoManager methods function 91 # independently of it's job as a frame source). 92 try: 93 video_manager.start() 94 # even if exception thrown here, video manager process will stop. 95 finally: 96 video_manager.release() 97 98 99def test_get_property(test_video_file): 100 """ Test VideoManager get method on test_video_file. """ 101 video_manager = VideoManager([test_video_file] * 3) 102 video_framerate = video_manager.get_framerate() 103 assert video_manager.get(cv2.CAP_PROP_FPS) == pytest.approx(video_framerate) 104 assert video_manager.get(cv2.CAP_PROP_FPS, 1) == pytest.approx(video_framerate) 105 assert video_manager.get(cv2.CAP_PROP_FPS, 2) == pytest.approx(video_framerate) 106 video_manager.release() 107 108 109def test_wrong_video_files_type(): 110 """ Test VideoManager constructor (__init__ method) with invalid video_files 111 argument types to trigger a ValueError exception. """ 112 with pytest.raises(ValueError): VideoManager([0, 1, 2]) 113 with pytest.raises(ValueError): VideoManager([0, 'somefile']) 114 with pytest.raises(ValueError): VideoManager(['somefile', 1, 2, 'somefile']) 115 with pytest.raises(ValueError): VideoManager([-1]) 116 117 118def test_wrong_framerate_type(test_video_file): 119 """ Test VideoManager constructor (__init__ method) with an invalid framerate 120 argument types to trigger a TypeError exception. """ 121 with pytest.raises(TypeError): VideoManager([test_video_file], framerate=int(0)) 122 with pytest.raises(TypeError): VideoManager([test_video_file], framerate=int(10)) 123 with pytest.raises(TypeError): VideoManager([test_video_file], framerate='10') 124 VideoManager([test_video_file], framerate=float(10)).release() 125 126 127def test_video_open_failure(): 128 """ Test VideoManager constructor (__init__ method) with invalid filename(s) 129 and device IDs to trigger an IOError/VideoOpenFailure exception. """ 130 # Attempt to open non-existing video files should raise an IOError. 131 with pytest.raises(IOError): VideoManager(['fauxfile.mp4']) 132 with pytest.raises(IOError): VideoManager(['fauxfile.mp4', 'otherfakefile.mp4']) 133 # Attempt to open 99th video device should raise a VideoOpenFailure since 134 # the OpenCV VideoCapture open() method will likely fail (unless the test 135 # case computer has 100 webcams or more...) 136 with pytest.raises(VideoOpenFailure): VideoManager([99]) 137 # Test device IDs > 100. 138 with pytest.raises(VideoOpenFailure): VideoManager([120]) 139 with pytest.raises(VideoOpenFailure): VideoManager([255]) 140 141 142def test_grab_retrieve(test_video_file): 143 """ Test VideoManager grab/retrieve methods. """ 144 video_manager = VideoManager([test_video_file] * 2) 145 base_timecode = video_manager.get_base_timecode() 146 try: 147 video_manager.start() 148 assert video_manager.get_current_timecode() == base_timecode 149 for i in range(1, 10): 150 # VideoManager.grab() -> bool 151 ret_val = video_manager.grab() 152 assert ret_val 153 assert video_manager.get_current_timecode() == base_timecode + i 154 # VideoManager.retrieve() -> Tuple[bool, numpy.ndarray] 155 ret_val, frame_image = video_manager.retrieve() 156 assert ret_val 157 assert frame_image.shape[0] > 0 158 assert video_manager.get_current_timecode() == base_timecode + i 159 finally: 160 video_manager.release() 161 162 163def test_read(test_video_file): 164 """ Test VideoManager read method. """ 165 video_manager = VideoManager([test_video_file] * 2) 166 base_timecode = video_manager.get_base_timecode() 167 try: 168 video_manager.start() 169 assert video_manager.get_current_timecode() == base_timecode 170 for i in range(1, 10): 171 # VideoManager.read() -> Tuple[bool, numpy.ndarray] 172 ret_val, frame_image = video_manager.read() 173 assert ret_val 174 assert frame_image.shape[0] > 0 175 assert video_manager.get_current_timecode() == base_timecode + i 176 finally: 177 video_manager.release() 178 179 180def test_seek(test_video_file): 181 """ Test VideoManager seek method. """ 182 video_manager = VideoManager([test_video_file] * 2) 183 base_timecode = video_manager.get_base_timecode() 184 try: 185 video_manager.start() 186 assert video_manager.get_current_timecode() == base_timecode 187 ret_val, frame_image = video_manager.read() 188 assert ret_val 189 assert frame_image.shape[0] > 0 190 assert video_manager.get_current_timecode() == base_timecode + 1 191 192 assert video_manager.seek(base_timecode + 10) 193 assert video_manager.get_current_timecode() == base_timecode + 10 194 ret_val, frame_image = video_manager.read() 195 assert ret_val 196 assert frame_image.shape[0] > 0 197 assert video_manager.get_current_timecode() == base_timecode + 11 198 199 finally: 200 video_manager.release() 201 202 203def test_reset(test_video_file): 204 """ Test VideoManager reset method. """ 205 video_manager = VideoManager([test_video_file] * 2) 206 base_timecode = video_manager.get_base_timecode() 207 try: 208 video_manager.start() 209 assert video_manager.get_current_timecode() == base_timecode 210 ret_val, frame_image = video_manager.read() 211 assert ret_val 212 assert frame_image.shape[0] > 0 213 assert video_manager.get_current_timecode() == base_timecode + 1 214 215 video_manager.release() 216 video_manager.reset() 217 218 video_manager.start() 219 assert video_manager.get_current_timecode() == base_timecode 220 ret_val, frame_image = video_manager.read() 221 assert ret_val 222 assert frame_image.shape[0] > 0 223 assert video_manager.get_current_timecode() == base_timecode + 1 224 225 finally: 226 video_manager.release() 227 228 229def test_multiple_videos(test_video_file): 230 """ Test VideoManager handling decoding frames across video boundaries. """ 231 232 NUM_FRAMES = 10 233 NUM_VIDEOS = 3 234 # Open VideoManager and get base timecode. 235 video_manager = VideoManager([test_video_file] * NUM_VIDEOS) 236 base_timecode = video_manager.get_base_timecode() 237 238 # List of NUM_VIDEOS VideoManagers pointing to test_video_file. 239 vm_list = [ 240 VideoManager([test_video_file]), 241 VideoManager([test_video_file]), 242 VideoManager([test_video_file])] 243 244 # Set duration of all VideoManagers in vm_list to NUM_FRAMES frames. 245 for vm in vm_list: vm.set_duration(duration=base_timecode+NUM_FRAMES) 246 # (FOR TESTING PURPOSES ONLY) Manually override _cap_list with the 247 # duration-limited VideoManager objects in vm_list 248 video_manager._cap_list = vm_list 249 250 try: 251 for vm in vm_list: vm.start() 252 video_manager.start() 253 assert video_manager.get_current_timecode() == base_timecode 254 255 curr_time = video_manager.get_base_timecode() 256 while True: 257 ret_val, frame_image = video_manager.read() 258 if not ret_val: 259 break 260 assert frame_image.shape[0] > 0 261 curr_time += 1 262 assert curr_time == base_timecode + ((NUM_FRAMES+1) * NUM_VIDEOS) 263 264 finally: 265 # Will release the VideoManagers in vm_list as well. 266 video_manager.release() 267 268def test_many_videos_downscale_detect_scenes(test_video_file): 269 """ Test scene detection on multiple videos in VideoManager. """ 270 271 NUM_VIDEOS = 3 272 # Open VideoManager with NUM_VIDEOS test videos 273 video_manager = VideoManager([test_video_file] * NUM_VIDEOS) 274 video_manager.set_downscale_factor() 275 276 try: 277 video_manager.start() 278 scene_manager = SceneManager() 279 scene_manager.detect_scenes(frame_source=video_manager) 280 finally: 281 # Will release the VideoManagers in vm_list as well. 282 video_manager.release() 283