Skip to content

Commit 9377cf1

Browse files
committed
[common] Add TimecodeLike type hint
1 parent 6d4c780 commit 9377cf1

6 files changed

Lines changed: 32 additions & 16 deletions

File tree

scenedetect/__init__.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
CutList,
3838
CropRegion,
3939
TimecodePair,
40+
TimecodeLike,
4041
Interpolation,
4142
)
4243
from scenedetect.video_stream import VideoStream, VideoOpenFailure
@@ -136,8 +137,8 @@ def detect(
136137
detector: SceneDetector,
137138
stats_file_path: str | None = None,
138139
show_progress: bool = False,
139-
start_time: str | float | int | None = None,
140-
end_time: str | float | int | None = None,
140+
start_time: TimecodeLike | None = None,
141+
end_time: TimecodeLike | None = None,
141142
start_in_scene: bool = False,
142143
) -> SceneList:
143144
"""Perform scene detection on a given video `path` using the specified `detector`.
@@ -170,18 +171,16 @@ def detect(
170171
"""
171172
video = open_video(video_path)
172173
if start_time is not None:
173-
start_time = video.base_timecode + start_time
174-
video.seek(start_time)
175-
if end_time is not None:
176-
end_time = video.base_timecode + end_time
174+
video.seek(FrameTimecode(start_time, video.frame_rate))
175+
end_timecode = FrameTimecode(end_time, video.frame_rate) if end_time is not None else None
177176
# To reduce memory consumption when not required, we only add a StatsManager if we
178177
# need to save frame metrics to disk.
179178
scene_manager = SceneManager(StatsManager() if stats_file_path else None)
180179
scene_manager.add_detector(detector)
181180
scene_manager.detect_scenes(
182181
video=video,
183182
show_progress=show_progress,
184-
end_time=end_time,
183+
end_time=end_timecode,
185184
)
186185
if scene_manager.stats_manager is not None:
187186
scene_manager.stats_manager.save_to_csv(csv_file=stats_file_path)

scenedetect/backends/moviepy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from moviepy.video.io.ffmpeg_reader import FFMPEG_VideoReader
2727

2828
from scenedetect.backends.opencv import VideoStreamCv2
29-
from scenedetect.common import FrameTimecode, Timecode, framerate_to_fraction
29+
from scenedetect.common import FrameTimecode, Timecode, TimecodeLike, framerate_to_fraction
3030
from scenedetect.platform import get_file_name
3131
from scenedetect.video_stream import SeekError, VideoOpenFailure, VideoStream
3232

@@ -189,7 +189,7 @@ def frame_number(self) -> int:
189189
"""
190190
return self._frame_number
191191

192-
def seek(self, target: FrameTimecode | float | int):
192+
def seek(self, target: TimecodeLike):
193193
"""Seek to the given timecode. If given as a frame number, represents the current seek
194194
pointer (e.g. if seeking to 0, the next frame decoded will be the first frame of the video).
195195

scenedetect/backends/opencv.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@
2727
import cv2
2828
import numpy as np
2929

30-
from scenedetect.common import MAX_FPS_DELTA, FrameTimecode, Timecode, framerate_to_fraction
30+
from scenedetect.common import (
31+
MAX_FPS_DELTA,
32+
FrameTimecode,
33+
Timecode,
34+
TimecodeLike,
35+
framerate_to_fraction,
36+
)
3137
from scenedetect.platform import get_file_name
3238
from scenedetect.video_stream import (
3339
FrameRateUnavailable,
@@ -231,9 +237,11 @@ def frame_number(self) -> int:
231237
assert self._cap is not None
232238
return math.trunc(self._cap.get(cv2.CAP_PROP_POS_FRAMES))
233239

234-
def seek(self, target: FrameTimecode | float | int):
240+
def seek(self, target: TimecodeLike):
235241
if self._is_device:
236242
raise SeekError("Cannot seek if input is a device!")
243+
if not isinstance(target, FrameTimecode):
244+
target = FrameTimecode(target, self.frame_rate)
237245
if target < 0:
238246
raise ValueError("Target seek position cannot be negative!")
239247
assert self._cap is not None
@@ -484,7 +492,7 @@ def position_ms(self) -> float:
484492
def frame_number(self) -> int:
485493
return self._num_frames
486494

487-
def seek(self, target: FrameTimecode | float | int):
495+
def seek(self, target: TimecodeLike):
488496
"""The underlying VideoCapture is assumed to not support seeking."""
489497
raise NotImplementedError("Seeking is not supported.")
490498

scenedetect/backends/pyav.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import av
1919
import numpy as np
2020

21-
from scenedetect.common import MAX_FPS_DELTA, FrameTimecode, Timecode
21+
from scenedetect.common import MAX_FPS_DELTA, FrameTimecode, Timecode, TimecodeLike
2222
from scenedetect.platform import get_file_name
2323
from scenedetect.video_stream import FrameRateUnavailable, VideoOpenFailure, VideoStream
2424

@@ -234,7 +234,7 @@ def aspect_ratio(self) -> float:
234234
frame_aspect_ratio = self.frame_size[0] / self.frame_size[1]
235235
return display_aspect_ratio / frame_aspect_ratio
236236

237-
def seek(self, target: FrameTimecode | float | int) -> None:
237+
def seek(self, target: TimecodeLike) -> None:
238238
"""Seek to the given timecode. If given as a frame number, represents the current seek
239239
pointer (e.g. if seeking to 0, the next frame decoded will be the first frame of the video).
240240
@@ -252,6 +252,8 @@ def seek(self, target: FrameTimecode | float | int) -> None:
252252
Raises:
253253
ValueError: `target` is not a valid value (i.e. it is negative).
254254
"""
255+
if not isinstance(target, FrameTimecode):
256+
target = FrameTimecode(target, self.frame_rate)
255257
if target < 0:
256258
raise ValueError("Target cannot be negative!")
257259
beginning = target == 0

scenedetect/common.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,3 +715,10 @@ def _get_other_as_seconds(self, other: ty.Union[int, float, str, "FrameTimecode"
715715

716716
def _compare_as_fixed(a: FrameTimecode, b: ty.Any) -> bool:
717717
return a._rate is not None and isinstance(b, FrameTimecode) and b._rate is not None
718+
719+
720+
TimecodeLike = int | float | str | Timecode | FrameTimecode
721+
"""Type hint for values that can be converted to a :class:`FrameTimecode`. Accepts a frame number
722+
(`int`), number of seconds (`float`), timecode string (`str` of the form ``HH:MM:SS[.nnn]``), a
723+
:class:`Timecode`, or an existing :class:`FrameTimecode`.
724+
"""

scenedetect/video_stream.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
import numpy as np
3838

39-
from scenedetect.common import FrameTimecode
39+
from scenedetect.common import FrameTimecode, TimecodeLike
4040

4141

4242
class SeekError(Exception):
@@ -193,7 +193,7 @@ def reset(self) -> None:
193193
...
194194

195195
@abstractmethod
196-
def seek(self, target: FrameTimecode | float | int) -> None:
196+
def seek(self, target: TimecodeLike) -> None:
197197
"""Seek to the given timecode. If given as a frame number, represents the current seek
198198
pointer (e.g. if seeking to 0, the next frame decoded will be the first frame of the video).
199199

0 commit comments

Comments
 (0)