forked from Breakthrough/PySceneDetect
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_scene_manager.py
More file actions
168 lines (127 loc) · 5.67 KB
/
Copy pathtest_scene_manager.py
File metadata and controls
168 lines (127 loc) · 5.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# -*- coding: utf-8 -*-
#
# PySceneDetect: Python-Based Video Scene Detector
# ---------------------------------------------------------------
# [ Site: http://www.scenedetect.scenedetect.com/ ]
# [ Docs: http://manual.scenedetect.scenedetect.com/ ]
# [ Github: https://github.com/Breakthrough/PySceneDetect/ ]
#
# Copyright (C) 2014-2022 Brandon Castellano <http://www.bcastell.com>.
# PySceneDetect is licensed under the BSD 3-Clause License; see the
# included LICENSE file, or visit one of the above pages for details.
#
"""PySceneDetect scenedetect.scene_manager Tests
This file includes unit tests for the scenedetect.scene_manager.SceneManager class,
which applies SceneDetector algorithms on VideoStream backends.
"""
# Standard project pylint disables for unit tests using pytest.
# pylint: disable=protected-access, invalid-name, unused-argument, redefined-outer-name
import glob
import os
import os.path
from typing import Iterable, Tuple
import pytest
from scenedetect.backends.opencv import VideoStreamCv2
from scenedetect.detectors import ContentDetector
from scenedetect.frame_timecode import FrameTimecode
from scenedetect.scene_manager import SceneManager, save_images
def test_scene_list(test_video_file):
""" Test SceneManager get_scene_list method with VideoStreamCv2/ContentDetector. """
video = VideoStreamCv2(test_video_file)
sm = SceneManager()
sm.add_detector(ContentDetector())
video_fps = video.frame_rate
start_time = FrameTimecode('00:00:05', video_fps)
end_time = FrameTimecode('00:00:15', video_fps)
assert end_time.get_frames() > start_time.get_frames()
video.seek(start_time)
sm.auto_downscale = True
num_frames = sm.detect_scenes(video=video, end_time=end_time)
assert num_frames == (1 + end_time.get_frames() - start_time.get_frames())
scene_list = sm.get_scene_list()
assert scene_list
# Each scene is in the format (Start Timecode, End Timecode)
assert len(scene_list[0]) == 2
for i, _ in enumerate(scene_list):
assert scene_list[i][0].get_frames() < scene_list[i][1].get_frames()
if i > 0:
# Ensure frame list is sorted (i.e. end time frame of
# one scene is equal to the start time of the next).
assert scene_list[i - 1][1] == scene_list[i][0]
def test_save_images(test_video_file):
""" Test scenedetect.scene_manager.save_images function. """
video = VideoStreamCv2(test_video_file)
sm = SceneManager()
sm.add_detector(ContentDetector())
image_name_glob = 'scenedetect.tempfile.*.jpg'
image_name_template = 'scenedetect.tempfile.$SCENE_NUMBER.$IMAGE_NUMBER'
try:
video_fps = video.frame_rate
start_time = FrameTimecode('00:00:05', video_fps)
end_time = FrameTimecode('00:00:15', video_fps)
video.seek(start_time)
sm.auto_downscale = True
sm.detect_scenes(video=video, end_time=end_time)
scene_list = sm.get_scene_list()
assert scene_list
image_filenames = save_images(
scene_list=scene_list,
video=video,
num_images=3,
image_extension='jpg',
image_name_template=image_name_template)
# Ensure images got created, and the proper number got created.
total_images = 0
for scene_number in image_filenames:
for path in image_filenames[scene_number]:
assert os.path.exists(path)
total_images += 1
assert total_images == len(glob.glob(image_name_glob))
finally:
for path in glob.glob(image_name_glob):
os.remove(path)
class FakeCallback(object):
""" Fake callback used for testing purposes only. Currently just stores
the number of times the callback was invoked."""
def __init__(self):
self.num_invoked: int = 0
def get_callback_lambda(self):
"""Returns a callback which consumes a frame image and timecode. The `num_invoked` property
is incremented each time the callback is invoked."""
return lambda image, frame_num: self._callback(image, frame_num)
def get_callback_func(self):
"""Returns a callback which consumes a frame image and timecode. The `num_invoked` property
is incremented each time the callback is invoked."""
def callback(image, frame_num):
nonlocal self
self._callback(image, frame_num)
return callback
def _callback(self, image, frame_num):
self.num_invoked += 1
def test_detect_scenes_callback(test_video_file):
""" Test SceneManager detect_scenes method with a callback function.
Note that the API signature of the callback will undergo breaking changes in v1.0.
"""
video = VideoStreamCv2(test_video_file)
sm = SceneManager()
sm.add_detector(ContentDetector())
fake_callback = FakeCallback()
video_fps = video.frame_rate
start_time = FrameTimecode('00:00:05', video_fps)
end_time = FrameTimecode('00:00:15', video_fps)
video.seek(start_time)
sm.auto_downscale = True
_ = sm.detect_scenes(
video=video, end_time=end_time, callback=fake_callback.get_callback_lambda())
scene_list = sm.get_scene_list()
assert scene_list
assert fake_callback.num_invoked == (len(sm.get_scene_list()) - 1)
# Perform same test using callback function instead of lambda.
sm.clear()
sm.add_detector(ContentDetector())
fake_callback.num_invoked = 0
video.seek(start_time)
_ = sm.detect_scenes(video=video, end_time=end_time, callback=fake_callback.get_callback_func())
scene_list = sm.get_scene_list()
assert scene_list
assert fake_callback.num_invoked == (len(sm.get_scene_list()) - 1)