scenedetect-core 0.7.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scenedetect/__init__.py +211 -0
- scenedetect/__main__.py +60 -0
- scenedetect/_cli/__init__.py +1866 -0
- scenedetect/_cli/commands.py +367 -0
- scenedetect/_cli/config.py +832 -0
- scenedetect/_cli/context.py +567 -0
- scenedetect/_cli/controller.py +223 -0
- scenedetect/_thirdparty/__init__.py +14 -0
- scenedetect/_thirdparty/simpletable.py +326 -0
- scenedetect/backends/__init__.py +125 -0
- scenedetect/backends/concat.py +387 -0
- scenedetect/backends/moviepy.py +295 -0
- scenedetect/backends/opencv.py +539 -0
- scenedetect/backends/pyav.py +427 -0
- scenedetect/common.py +837 -0
- scenedetect/detector.py +224 -0
- scenedetect/detectors/__init__.py +72 -0
- scenedetect/detectors/adaptive_detector.py +143 -0
- scenedetect/detectors/content_detector.py +243 -0
- scenedetect/detectors/hash_detector.py +151 -0
- scenedetect/detectors/histogram_detector.py +166 -0
- scenedetect/detectors/threshold_detector.py +191 -0
- scenedetect/detectors/transnet_v2.py +210 -0
- scenedetect/frame_timecode.py +22 -0
- scenedetect/output/__init__.py +660 -0
- scenedetect/output/image.py +535 -0
- scenedetect/output/video.py +389 -0
- scenedetect/platform.py +423 -0
- scenedetect/scene_detector.py +22 -0
- scenedetect/scene_manager.py +731 -0
- scenedetect/stats_manager.py +314 -0
- scenedetect/video_splitter.py +22 -0
- scenedetect/video_stream.py +222 -0
- scenedetect_core-0.7.1.dev0.dist-info/METADATA +106 -0
- scenedetect_core-0.7.1.dev0.dist-info/RECORD +38 -0
- scenedetect_core-0.7.1.dev0.dist-info/WHEEL +5 -0
- scenedetect_core-0.7.1.dev0.dist-info/licenses/LICENSE +28 -0
- scenedetect_core-0.7.1.dev0.dist-info/top_level.txt +1 -0
scenedetect/__init__.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#
|
|
2
|
+
# PySceneDetect: Python-Based Video Scene Detector
|
|
3
|
+
# -------------------------------------------------------------------
|
|
4
|
+
# [ Site: https://scenedetect.com ]
|
|
5
|
+
# [ Docs: https://scenedetect.com/docs/ ]
|
|
6
|
+
# [ Github: https://github.com/Breakthrough/PySceneDetect/ ]
|
|
7
|
+
#
|
|
8
|
+
# Copyright (C) 2016 Brandon Castellano <http://www.bcastell.com>.
|
|
9
|
+
# PySceneDetect is licensed under the BSD 3-Clause License; see the
|
|
10
|
+
# included LICENSE file, or visit one of the above pages for details.
|
|
11
|
+
#
|
|
12
|
+
"""The ``scenedetect`` module comes with helper functions to simplify common use cases.
|
|
13
|
+
:func:`detect` can be used to perform scene detection on a video by path. :func:`open_video`
|
|
14
|
+
can be used to open a video for a
|
|
15
|
+
:class:`SceneManager <scenedetect.scene_manager.SceneManager>`.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from logging import getLogger
|
|
19
|
+
|
|
20
|
+
# OpenCV is a required package, but we don't have it as an explicit dependency since we
|
|
21
|
+
# need to support both opencv-python and opencv-python-headless. Include some additional
|
|
22
|
+
# context with the exception if this is the case.
|
|
23
|
+
try:
|
|
24
|
+
import cv2 as _ # availability check; raise a friendlier error if missing
|
|
25
|
+
except ModuleNotFoundError as ex:
|
|
26
|
+
raise ModuleNotFoundError(
|
|
27
|
+
"OpenCV could not be found, try installing opencv-python:\n\npip install opencv-python",
|
|
28
|
+
name="cv2",
|
|
29
|
+
) from ex
|
|
30
|
+
|
|
31
|
+
# Commonly used classes/functions exported under the `scenedetect` namespace for brevity.
|
|
32
|
+
# Note that order of importants is important!
|
|
33
|
+
from scenedetect.platform import init_logger # noqa: I001
|
|
34
|
+
from scenedetect.common import (
|
|
35
|
+
FrameTimecode,
|
|
36
|
+
FrameRate,
|
|
37
|
+
SceneList,
|
|
38
|
+
CutList,
|
|
39
|
+
CropRegion,
|
|
40
|
+
TimecodePair,
|
|
41
|
+
TimecodeLike,
|
|
42
|
+
Interpolation,
|
|
43
|
+
)
|
|
44
|
+
from scenedetect.platform import StrPath
|
|
45
|
+
from scenedetect.video_stream import VideoStream, VideoOpenFailure
|
|
46
|
+
from scenedetect.output import (
|
|
47
|
+
save_images,
|
|
48
|
+
split_video_ffmpeg,
|
|
49
|
+
split_video_mkvmerge,
|
|
50
|
+
is_ffmpeg_available,
|
|
51
|
+
is_mkvmerge_available,
|
|
52
|
+
write_scene_list,
|
|
53
|
+
write_scene_list_html,
|
|
54
|
+
PathFormatter,
|
|
55
|
+
VideoMetadata,
|
|
56
|
+
SceneMetadata,
|
|
57
|
+
)
|
|
58
|
+
from scenedetect.detector import SceneDetector
|
|
59
|
+
from scenedetect.detectors import (
|
|
60
|
+
ContentDetector,
|
|
61
|
+
AdaptiveDetector,
|
|
62
|
+
ThresholdDetector,
|
|
63
|
+
HistogramDetector,
|
|
64
|
+
HashDetector,
|
|
65
|
+
)
|
|
66
|
+
from scenedetect.backends import (
|
|
67
|
+
AVAILABLE_BACKENDS,
|
|
68
|
+
VideoStreamCv2,
|
|
69
|
+
VideoStreamAv,
|
|
70
|
+
VideoStreamMoviePy,
|
|
71
|
+
VideoCaptureAdapter,
|
|
72
|
+
VideoStreamConcat,
|
|
73
|
+
SourceSpan,
|
|
74
|
+
)
|
|
75
|
+
from scenedetect.stats_manager import StatsManager, StatsFileCorrupt
|
|
76
|
+
from scenedetect.scene_manager import SceneManager
|
|
77
|
+
|
|
78
|
+
# Used for module identification and when printing version & about info
|
|
79
|
+
# (e.g. calling `scenedetect version` or `scenedetect about`).
|
|
80
|
+
__version__ = "0.7.1-dev0"
|
|
81
|
+
|
|
82
|
+
init_logger()
|
|
83
|
+
logger = getLogger("pyscenedetect")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def open_video(
|
|
87
|
+
path: "StrPath | list[StrPath] | tuple[StrPath, ...]",
|
|
88
|
+
frame_rate: FrameRate | None = None,
|
|
89
|
+
backend: str = "opencv",
|
|
90
|
+
framerate: float | None = None,
|
|
91
|
+
**kwargs,
|
|
92
|
+
) -> VideoStream:
|
|
93
|
+
"""Open a video at the given path. If `backend` is specified but not available on the current
|
|
94
|
+
system, OpenCV (`VideoStreamCv2`) will be used as a fallback.
|
|
95
|
+
|
|
96
|
+
Arguments:
|
|
97
|
+
path: Path to video file to open. May also be a list of paths, in which case the videos
|
|
98
|
+
are concatenated and treated as a single continuous stream
|
|
99
|
+
(see :class:`VideoStreamConcat <scenedetect.backends.concat.VideoStreamConcat>`).
|
|
100
|
+
frame_rate: Overrides detected frame rate if set. Takes precedence over `framerate`.
|
|
101
|
+
backend: Name of specific backend to use, if possible. See
|
|
102
|
+
:data:`scenedetect.backends.AVAILABLE_BACKENDS` for backends available on the current
|
|
103
|
+
system. If the backend fails to open the video, OpenCV will be used as a fallback.
|
|
104
|
+
framerate: [DEPRECATED] Use `frame_rate` instead. Retained as a deprecated alias for
|
|
105
|
+
backwards compatibility; ignored when `frame_rate` is provided.
|
|
106
|
+
kwargs: Optional named arguments to pass to the specified `backend` constructor for
|
|
107
|
+
overriding backend-specific options.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Backend object created with the specified video path.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
:class:`VideoOpenFailure`: Constructing the VideoStream fails. If multiple backends have
|
|
114
|
+
been attempted, the error from the first backend will be returned.
|
|
115
|
+
"""
|
|
116
|
+
# TODO(https://scenedetect.com/issue/548): emit DeprecationWarning when `framerate=` is
|
|
117
|
+
# used, once internal callers and downstream users have had a release to migrate.
|
|
118
|
+
if frame_rate is None:
|
|
119
|
+
frame_rate = framerate
|
|
120
|
+
# A list of paths is opened as a single concatenated stream. VideoStreamConcat handles
|
|
121
|
+
# backend selection/fallback internally, so this must come before the lookup below.
|
|
122
|
+
if isinstance(path, (list, tuple)):
|
|
123
|
+
return VideoStreamConcat(path, frame_rate, backend=backend, **kwargs)
|
|
124
|
+
last_error: Exception | None = None
|
|
125
|
+
# If `backend` is available, try to open the video at `path` using it.
|
|
126
|
+
if backend in AVAILABLE_BACKENDS:
|
|
127
|
+
backend_type = AVAILABLE_BACKENDS[backend]
|
|
128
|
+
try:
|
|
129
|
+
logger.debug("Opening video with %s...", backend_type.BACKEND_NAME)
|
|
130
|
+
return backend_type(path, frame_rate, **kwargs)
|
|
131
|
+
except VideoOpenFailure as ex:
|
|
132
|
+
logger.warning("Failed to open video with %s: %s", backend_type.BACKEND_NAME, str(ex))
|
|
133
|
+
if backend == VideoStreamCv2.BACKEND_NAME:
|
|
134
|
+
raise
|
|
135
|
+
last_error = ex
|
|
136
|
+
else:
|
|
137
|
+
logger.warning("Backend %s not available.", backend)
|
|
138
|
+
# Fallback to OpenCV if `backend` is unavailable, or specified backend failed to open `path`.
|
|
139
|
+
backend_type = VideoStreamCv2
|
|
140
|
+
logger.warning("Trying another backend: %s", backend_type.BACKEND_NAME)
|
|
141
|
+
try:
|
|
142
|
+
return backend_type(path, frame_rate)
|
|
143
|
+
except VideoOpenFailure as ex:
|
|
144
|
+
logger.debug("Failed to open video: %s", str(ex))
|
|
145
|
+
if last_error is None:
|
|
146
|
+
last_error = ex
|
|
147
|
+
# Propagate any exceptions raised from specified backend, instead of errors from the fallback.
|
|
148
|
+
assert last_error is not None
|
|
149
|
+
raise last_error
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def detect(
|
|
153
|
+
video_path: "StrPath | list[StrPath] | tuple[StrPath, ...]",
|
|
154
|
+
detector: SceneDetector,
|
|
155
|
+
stats_file_path: StrPath | None = None,
|
|
156
|
+
show_progress: bool = False,
|
|
157
|
+
start_time: TimecodeLike | None = None,
|
|
158
|
+
end_time: TimecodeLike | None = None,
|
|
159
|
+
start_in_scene: bool = False,
|
|
160
|
+
backend: str = "opencv",
|
|
161
|
+
) -> SceneList:
|
|
162
|
+
"""Perform scene detection on a given video `path` using the specified `detector`.
|
|
163
|
+
|
|
164
|
+
Arguments:
|
|
165
|
+
video_path: Path to input video (absolute or relative to working directory). May also
|
|
166
|
+
be a list of paths, in which case the videos are concatenated and treated as a
|
|
167
|
+
single continuous stream.
|
|
168
|
+
detector: A `SceneDetector` instance (see :mod:`scenedetect.detectors` for a full list
|
|
169
|
+
of detectors).
|
|
170
|
+
stats_file_path: Path to save per-frame metrics to for statistical analysis or to
|
|
171
|
+
determine a better threshold value.
|
|
172
|
+
show_progress: Show a progress bar with estimated time remaining. Default is False.
|
|
173
|
+
start_time: Starting point in video, in the form of a timecode ``HH:MM:SS[.nnn]`` (`str`),
|
|
174
|
+
number of seconds ``123.45`` (`float`), or number of frames ``200`` (`int`).
|
|
175
|
+
end_time: Starting point in video, in the form of a timecode ``HH:MM:SS[.nnn]`` (`str`),
|
|
176
|
+
number of seconds ``123.45`` (`float`), or number of frames ``200`` (`int`).
|
|
177
|
+
start_in_scene: Assume the video begins in a scene. This means that when detecting
|
|
178
|
+
fast cuts with `ContentDetector`, if no cuts are found, the resulting scene list
|
|
179
|
+
will contain a single scene spanning the entire video (instead of no scenes).
|
|
180
|
+
When detecting fades with `ThresholdDetector`, the beginning portion of the video
|
|
181
|
+
will always be included until the first fade-out event is detected.
|
|
182
|
+
backend: Name of the backend to use for video decoding. See
|
|
183
|
+
:data:`scenedetect.backends.AVAILABLE_BACKENDS` for backends available on the
|
|
184
|
+
current system. Defaults to OpenCV; falls back to OpenCV if the requested backend
|
|
185
|
+
is unavailable or fails to open the video.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
List of scenes as pairs of (start, end) :class:`FrameTimecode` objects.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
:class:`VideoOpenFailure`: `video_path` could not be opened.
|
|
192
|
+
:class:`StatsFileCorrupt`: `stats_file_path` is an invalid stats file
|
|
193
|
+
ValueError: `start_time` or `end_time` are incorrectly formatted.
|
|
194
|
+
TypeError: `start_time` or `end_time` are invalid types.
|
|
195
|
+
"""
|
|
196
|
+
video = open_video(video_path, backend=backend)
|
|
197
|
+
if start_time is not None:
|
|
198
|
+
video.seek(FrameTimecode(start_time, video.frame_rate))
|
|
199
|
+
end_timecode = FrameTimecode(end_time, video.frame_rate) if end_time is not None else None
|
|
200
|
+
# To reduce memory consumption when not required, we only add a StatsManager if we
|
|
201
|
+
# need to save frame metrics to disk.
|
|
202
|
+
scene_manager = SceneManager(StatsManager() if stats_file_path else None)
|
|
203
|
+
scene_manager.add_detector(detector)
|
|
204
|
+
scene_manager.detect_scenes(
|
|
205
|
+
video=video,
|
|
206
|
+
show_progress=show_progress,
|
|
207
|
+
end_time=end_timecode,
|
|
208
|
+
)
|
|
209
|
+
if scene_manager.stats_manager is not None and stats_file_path is not None:
|
|
210
|
+
scene_manager.stats_manager.save_to_csv(csv_file=stats_file_path)
|
|
211
|
+
return scene_manager.get_scene_list(start_in_scene=start_in_scene)
|
scenedetect/__main__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#
|
|
2
|
+
# PySceneDetect: Python-Based Video Scene Detector
|
|
3
|
+
# -------------------------------------------------------------------
|
|
4
|
+
# [ Site: https://scenedetect.com ]
|
|
5
|
+
# [ Docs: https://scenedetect.com/docs/ ]
|
|
6
|
+
# [ Github: https://github.com/Breakthrough/PySceneDetect/ ]
|
|
7
|
+
#
|
|
8
|
+
# Copyright (C) 2016 Brandon Castellano <http://www.bcastell.com>.
|
|
9
|
+
# PySceneDetect is licensed under the BSD 3-Clause License; see the
|
|
10
|
+
# included LICENSE file, or visit one of the above pages for details.
|
|
11
|
+
#
|
|
12
|
+
"""Entry point for PySceneDetect's command-line interface."""
|
|
13
|
+
|
|
14
|
+
import sys
|
|
15
|
+
from logging import getLogger
|
|
16
|
+
|
|
17
|
+
from scenedetect._cli import scenedetect
|
|
18
|
+
from scenedetect._cli.context import CliContext
|
|
19
|
+
from scenedetect._cli.controller import run_scenedetect
|
|
20
|
+
from scenedetect.platform import DEBUG_MODE, FakeTqdmLoggingRedirect, logging_redirect_tqdm
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main():
|
|
24
|
+
"""PySceneDetect command-line interface (CLI) entry point."""
|
|
25
|
+
context = CliContext()
|
|
26
|
+
try:
|
|
27
|
+
# Process command line arguments and subcommands to initialize the context.
|
|
28
|
+
scenedetect.main(obj=context) # Parse CLI arguments with registered callbacks.
|
|
29
|
+
except SystemExit as exit:
|
|
30
|
+
help_command = any(arg in sys.argv for arg in ["-h", "--help"])
|
|
31
|
+
if help_command or exit.code != 0:
|
|
32
|
+
raise
|
|
33
|
+
|
|
34
|
+
# If we get here, processing the command line and loading the context worked. Let's run
|
|
35
|
+
# the controller if we didn't process any help requests.
|
|
36
|
+
logger = getLogger("pyscenedetect")
|
|
37
|
+
# Ensure log messages don't conflict with any progress bars. If we're in quiet mode, where
|
|
38
|
+
# no progress bars get created, we instead create a fake context manager. This is done here
|
|
39
|
+
# to avoid needing a separate context manager at each point a progress bar is created.
|
|
40
|
+
log_redirect = (
|
|
41
|
+
FakeTqdmLoggingRedirect() if context.quiet_mode else logging_redirect_tqdm(loggers=[logger])
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
with log_redirect:
|
|
45
|
+
try:
|
|
46
|
+
run_scenedetect(context)
|
|
47
|
+
except KeyboardInterrupt:
|
|
48
|
+
logger.info("Stopped.")
|
|
49
|
+
if DEBUG_MODE:
|
|
50
|
+
raise
|
|
51
|
+
raise SystemExit(1) from None
|
|
52
|
+
except BaseException as ex:
|
|
53
|
+
if DEBUG_MODE:
|
|
54
|
+
raise
|
|
55
|
+
logger.critical("ERROR: Unhandled exception:", exc_info=ex)
|
|
56
|
+
raise SystemExit(1) from ex
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
main()
|