Skip to content

Commit 4fad8b8

Browse files
committed
[backends] Allow concatenating multiple inputs
Also fix a few normalization issues for videos that don't start at time 0.
1 parent fac764d commit 4fad8b8

16 files changed

Lines changed: 906 additions & 38 deletions

docs/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ The `scenedetect` API is easy to integrate with most application workflows, whil
3535

3636
* MoviePy: :class:`VideoStreamMoviePy <scenedetect.backends.moviepy.VideoStreamMoviePy>`
3737

38+
* Multiple videos can be treated as a single continuous stream using :class:`VideoStreamConcat <scenedetect.backends.concat.VideoStreamConcat>` (e.g. ``open_video(["part1.mp4", "part2.mp4"])``)
39+
3840
* :ref:`scenedetect.common ⏱️ <scenedetect-common>`: common functionality such as :class:`FrameTimecode <scenedetect.common.FrameTimecode>` for timecode handling
3941

4042
* :ref:`scenedetect.scene_manager 🎞️ <scenedetect-scene_manager>`: the :class:`SceneManager <scenedetect.scene_manager.SceneManager>` coordinates performing scene detection on a video with one or more detectors

docs/api/backends.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ Video Backends
1616

1717
.. automodule:: scenedetect.backends.moviepy
1818
:members:
19+
20+
.. automodule:: scenedetect.backends.concat
21+
:members:

scenedetect/__init__.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
VideoStreamAv,
7070
VideoStreamMoviePy,
7171
VideoCaptureAdapter,
72+
VideoStreamConcat,
73+
SourceSpan,
7274
)
7375
from scenedetect.stats_manager import StatsManager, StatsFileCorrupt
7476
from scenedetect.scene_manager import SceneManager
@@ -82,7 +84,7 @@
8284

8385

8486
def open_video(
85-
path: StrPath,
87+
path: "StrPath | list[StrPath] | tuple[StrPath, ...]",
8688
frame_rate: FrameRate | None = None,
8789
backend: str = "opencv",
8890
framerate: float | None = None,
@@ -92,7 +94,9 @@ def open_video(
9294
system, OpenCV (`VideoStreamCv2`) will be used as a fallback.
9395
9496
Arguments:
95-
path: Path to video file to open.
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>`).
96100
frame_rate: Overrides detected frame rate if set. Takes precedence over `framerate`.
97101
backend: Name of specific backend to use, if possible. See
98102
:data:`scenedetect.backends.AVAILABLE_BACKENDS` for backends available on the current
@@ -113,6 +117,10 @@ def open_video(
113117
# used, once internal callers and downstream users have had a release to migrate.
114118
if frame_rate is None:
115119
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)
116124
last_error: Exception | None = None
117125
# If `backend` is available, try to open the video at `path` using it.
118126
if backend in AVAILABLE_BACKENDS:
@@ -142,7 +150,7 @@ def open_video(
142150

143151

144152
def detect(
145-
video_path: StrPath,
153+
video_path: "StrPath | list[StrPath] | tuple[StrPath, ...]",
146154
detector: SceneDetector,
147155
stats_file_path: StrPath | None = None,
148156
show_progress: bool = False,
@@ -154,7 +162,9 @@ def detect(
154162
"""Perform scene detection on a given video `path` using the specified `detector`.
155163
156164
Arguments:
157-
video_path: Path to input video (absolute or relative to working directory).
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.
158168
detector: A `SceneDetector` instance (see :mod:`scenedetect.detectors` for a full list
159169
of detectors).
160170
stats_file_path: Path to save per-frame metrics to for statistical analysis or to

scenedetect/backends/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@
5050
In both examples above, the resulting ``video`` can be used with
5151
:meth:`SceneManager.detect_scenes() <scenedetect.scene_manager.SceneManager.detect_scenes>`.
5252
53+
Multiple videos can be opened as one continuous stream by passing a list of paths to
54+
:func:`open_video`, which returns a
55+
:class:`VideoStreamConcat <scenedetect.backends.concat.VideoStreamConcat>`:
56+
57+
.. code:: python
58+
59+
video = open_video(["part1.mp4", "part2.mp4"])
60+
5361
===============================================================
5462
Devices / Cameras / Pipes
5563
===============================================================
@@ -84,6 +92,7 @@
8492
# - Nvidia VPF: https://developer.nvidia.com/blog/vpf-hardware-accelerated-video-processing-framework-in-python/
8593

8694
# OpenCV must be available at minimum.
95+
from scenedetect.backends.concat import SourceSpan, VideoStreamConcat
8796
from scenedetect.backends.opencv import VideoCaptureAdapter, VideoStreamCv2
8897

8998
try:

0 commit comments

Comments
 (0)