Skip to content

Commit 268e259

Browse files
committed
[lint] Use proper Path type hints rather than str | bytes
1 parent 9377cf1 commit 268e259

10 files changed

Lines changed: 110 additions & 86 deletions

File tree

scenedetect/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
TimecodeLike,
4141
Interpolation,
4242
)
43+
from scenedetect.platform import StrPath
4344
from scenedetect.video_stream import VideoStream, VideoOpenFailure
4445
from scenedetect.output import (
4546
save_images,
@@ -80,7 +81,7 @@
8081

8182

8283
def open_video(
83-
path: str,
84+
path: StrPath,
8485
framerate: float | None = None,
8586
backend: str = "opencv",
8687
**kwargs,
@@ -133,9 +134,9 @@ def open_video(
133134

134135

135136
def detect(
136-
video_path: str,
137+
video_path: StrPath,
137138
detector: SceneDetector,
138-
stats_file_path: str | None = None,
139+
stats_file_path: StrPath | None = None,
139140
show_progress: bool = False,
140141
start_time: TimecodeLike | None = None,
141142
end_time: TimecodeLike | None = None,

scenedetect/_cli/__init__.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -319,10 +319,10 @@ def print_command_help(ctx: click.Context, command: click.Command):
319319
@click.pass_context
320320
def scenedetect(
321321
ctx: click.Context,
322-
input: ty.AnyStr | None,
323-
output: ty.AnyStr | None,
324-
stats: ty.AnyStr | None,
325-
config: ty.AnyStr | None,
322+
input: str | None,
323+
output: str | None,
324+
stats: str | None,
325+
config: str | None,
326326
framerate: float | None,
327327
min_scene_len: str | None,
328328
drop_short_scenes: bool | None,
@@ -332,7 +332,7 @@ def scenedetect(
332332
downscale: int | None,
333333
frame_skip: int | None,
334334
verbosity: str | None,
335-
logfile: ty.AnyStr | None,
335+
logfile: str | None,
336336
quiet: bool,
337337
):
338338
ctx = ctx.obj
@@ -1052,7 +1052,7 @@ def load_scenes_command(ctx: click.Context, input: str | None, start_col_name: s
10521052
@click.pass_context
10531053
def save_html_command(
10541054
ctx: click.Context,
1055-
filename: ty.AnyStr | None,
1055+
filename: str | None,
10561056
no_images: bool,
10571057
image_width: int | None,
10581058
image_height: int | None,
@@ -1144,8 +1144,8 @@ def save_html_command(
11441144
@click.pass_context
11451145
def list_scenes_command(
11461146
ctx: click.Context,
1147-
output: ty.AnyStr | None,
1148-
filename: ty.AnyStr | None,
1147+
output: str | None,
1148+
filename: str | None,
11491149
no_output_file: bool | None,
11501150
quiet: bool | None,
11511151
skip_cuts: bool | None,
@@ -1280,8 +1280,8 @@ def list_scenes_command(
12801280
@click.pass_context
12811281
def split_video_command(
12821282
ctx: click.Context,
1283-
output: ty.AnyStr | None,
1284-
filename: ty.AnyStr | None,
1283+
output: str | None,
1284+
filename: str | None,
12851285
quiet: bool,
12861286
copy: bool,
12871287
high_quality: bool,
@@ -1487,8 +1487,8 @@ def split_video_command(
14871487
@click.pass_context
14881488
def save_images_command(
14891489
ctx: click.Context,
1490-
output: ty.AnyStr | None = None,
1491-
filename: ty.AnyStr | None = None,
1490+
output: str | None = None,
1491+
filename: str | None = None,
14921492
num_images: int | None = None,
14931493
jpeg: bool = False,
14941494
webp: bool = False,
@@ -1603,10 +1603,10 @@ def save_images_command(
16031603
@click.pass_context
16041604
def save_edl_command(
16051605
ctx: click.Context,
1606-
filename: ty.AnyStr | None,
1607-
title: ty.AnyStr | None,
1608-
reel: ty.AnyStr | None,
1609-
output: ty.AnyStr | None,
1606+
filename: str | None,
1607+
title: str | None,
1608+
reel: str | None,
1609+
output: str | None,
16101610
):
16111611
ctx = ctx.obj
16121612
assert isinstance(ctx, CliContext)
@@ -1657,8 +1657,8 @@ def save_edl_command(
16571657
@click.pass_context
16581658
def save_qp_command(
16591659
ctx: click.Context,
1660-
filename: ty.AnyStr | None,
1661-
output: ty.AnyStr | None,
1660+
filename: str | None,
1661+
output: str | None,
16621662
disable_shift: bool | None,
16631663
):
16641664
ctx = ctx.obj
@@ -1706,9 +1706,9 @@ def save_qp_command(
17061706
@click.pass_context
17071707
def save_fcp_command(
17081708
ctx: click.Context,
1709-
filename: ty.AnyStr | None,
1710-
format: ty.AnyStr | None,
1711-
output: ty.AnyStr | None,
1709+
filename: str | None,
1710+
format: str | None,
1711+
output: str | None,
17121712
):
17131713
ctx = ctx.obj
17141714
assert isinstance(ctx, CliContext)
@@ -1767,9 +1767,9 @@ def save_fcp_command(
17671767
@click.pass_context
17681768
def save_otio_command(
17691769
ctx: click.Context,
1770-
filename: ty.AnyStr | None,
1771-
name: ty.AnyStr | None,
1772-
output: ty.AnyStr | None,
1770+
filename: str | None,
1771+
name: str | None,
1772+
output: str | None,
17731773
audio: bool,
17741774
no_audio: bool,
17751775
):

scenedetect/_cli/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,11 @@ class FcpFormat(Enum):
318318
ConfigValue = bool | int | float | str
319319
ConfigDict = dict[str, dict[str, ConfigValue]]
320320

321-
_CONFIG_FILE_NAME: ty.AnyStr = "scenedetect.cfg"
322-
_CONFIG_FILE_DIR: ty.AnyStr = user_config_dir("PySceneDetect", False)
321+
_CONFIG_FILE_NAME: str = "scenedetect.cfg"
322+
_CONFIG_FILE_DIR: str = user_config_dir("PySceneDetect", False)
323323
_PLACEHOLDER = 0 # Placeholder for image quality default, as the value depends on output format
324324

325-
CONFIG_FILE_PATH: ty.AnyStr = os.path.join(_CONFIG_FILE_DIR, _CONFIG_FILE_NAME)
325+
CONFIG_FILE_PATH: str = os.path.join(_CONFIG_FILE_DIR, _CONFIG_FILE_NAME)
326326
DEFAULT_JPG_QUALITY = 95
327327
DEFAULT_WEBP_QUALITY = 100
328328

scenedetect/_cli/context.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,21 @@ def parse_timecode(self, value: str | None, correct_pts: bool = False) -> FrameT
159159

160160
def handle_options(
161161
self,
162-
input_path: ty.AnyStr,
163-
output: ty.AnyStr | None,
164-
framerate: float,
165-
stats_file: ty.AnyStr | None,
166-
frame_skip: int,
167-
min_scene_len: str,
162+
input_path: str | None,
163+
output: str | None,
164+
framerate: float | None,
165+
stats_file: str | None,
166+
frame_skip: int | None,
167+
min_scene_len: str | None,
168168
drop_short_scenes: bool | None,
169169
merge_last_scene: bool | None,
170170
backend: str | None,
171171
crop: tuple[int, int, int, int] | None,
172172
downscale: int | None,
173173
quiet: bool,
174-
logfile: ty.AnyStr | None,
175-
config: ty.AnyStr | None,
176-
stats: ty.AnyStr | None,
174+
logfile: str | None,
175+
config: str | None,
176+
stats: str | None,
177177
verbosity: str | None,
178178
):
179179
"""Parse all global options/arguments passed to the main scenedetect command,
@@ -496,7 +496,7 @@ def _initialize_logging(
496496
self,
497497
quiet: bool | None = None,
498498
verbosity: str | None = None,
499-
logfile: ty.AnyStr | None = None,
499+
logfile: str | None = None,
500500
):
501501
"""Setup logging based on CLI args and user configuration settings."""
502502
if quiet is not None:
@@ -527,7 +527,7 @@ def _initialize_logging(
527527

528528
def _open_video_stream(
529529
self,
530-
input_path: ty.AnyStr,
530+
input_path: str,
531531
framerate: float | None,
532532
backend: str | None,
533533
):

scenedetect/backends/moviepy.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
image sequences or AviSynth scripts are supported as inputs.
1717
"""
1818

19+
import os
1920
import time
2021
import typing as ty
2122
from fractions import Fraction
@@ -26,8 +27,13 @@
2627
from moviepy.video.io.ffmpeg_reader import FFMPEG_VideoReader
2728

2829
from scenedetect.backends.opencv import VideoStreamCv2
29-
from scenedetect.common import FrameTimecode, Timecode, TimecodeLike, framerate_to_fraction
30-
from scenedetect.platform import get_file_name
30+
from scenedetect.common import (
31+
FrameTimecode,
32+
Timecode,
33+
TimecodeLike,
34+
framerate_to_fraction,
35+
)
36+
from scenedetect.platform import StrPath, get_file_name
3137
from scenedetect.video_stream import SeekError, VideoOpenFailure, VideoStream
3238

3339
logger = getLogger("pyscenedetect")
@@ -63,7 +69,7 @@ def _retry_on_oserror(op_name: str, fn: ty.Callable):
6369
class VideoStreamMoviePy(VideoStream):
6470
"""MoviePy `FFMPEG_VideoReader` backend."""
6571

66-
def __init__(self, path: ty.AnyStr, framerate: float | None = None, print_infos: bool = False):
72+
def __init__(self, path: StrPath, framerate: float | None = None, print_infos: bool = False):
6773
"""Open a video or device.
6874
6975
Arguments:
@@ -84,13 +90,13 @@ def __init__(self, path: ty.AnyStr, framerate: float | None = None, print_infos:
8490
"VideoStreamMoviePy does not support the `framerate` argument yet."
8591
)
8692

87-
self._path = path
93+
self._path: str = os.fspath(path)
8894
# TODO: Need to map errors based on the strings, since several failure
8995
# cases return IOErrors (e.g. could not read duration/video resolution). These
9096
# should be mapped to specific errors, e.g. write a function to map MoviePy
9197
# exceptions to a new set of equivalents.
9298
self._reader = _retry_on_oserror(
93-
"open", lambda: FFMPEG_VideoReader(path, print_infos=print_infos)
99+
"open", lambda: FFMPEG_VideoReader(self._path, print_infos=print_infos)
94100
)
95101
# This will always be one behind self._reader.lastread when we finally call read()
96102
# as MoviePy caches the first frame when opening the video. Thus self._last_frame
@@ -117,7 +123,7 @@ def frame_rate(self) -> Fraction:
117123
return framerate_to_fraction(self._reader.fps)
118124

119125
@property
120-
def path(self) -> bytes | str:
126+
def path(self) -> str:
121127
"""Video path."""
122128
return self._path
123129

scenedetect/backends/opencv.py

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"""
1919

2020
import math
21+
import os
2122
import os.path
2223
import typing as ty
2324
import warnings
@@ -34,7 +35,7 @@
3435
TimecodeLike,
3536
framerate_to_fraction,
3637
)
37-
from scenedetect.platform import get_file_name
38+
from scenedetect.platform import StrPath, get_file_name
3839
from scenedetect.video_stream import (
3940
FrameRateUnavailable,
4041
SeekError,
@@ -71,10 +72,10 @@ class VideoStreamCv2(VideoStream):
7172

7273
def __init__(
7374
self,
74-
path: ty.AnyStr | None = None,
75+
path: StrPath | None = None,
7576
framerate: float | None = None,
7677
max_decode_attempts: int = 5,
77-
path_or_device: bytes | str | int | None = None,
78+
path_or_device: StrPath | int | None = None,
7879
):
7980
"""Open a video file, image sequence, or network stream.
8081
@@ -103,15 +104,19 @@ def __init__(
103104
DeprecationWarning,
104105
stacklevel=2,
105106
)
106-
path = path_or_device
107-
if path is None:
107+
resolved: str | int = (
108+
path_or_device if isinstance(path_or_device, int) else os.fspath(path_or_device)
109+
)
110+
elif path is None:
108111
raise ValueError("Path must be specified!")
112+
else:
113+
resolved = os.fspath(path)
109114
if framerate is not None and framerate < MAX_FPS_DELTA:
110115
raise ValueError(f"Specified framerate ({framerate:f}) is invalid!")
111116
if max_decode_attempts < 0:
112117
raise ValueError("Maximum decode attempts must be >= 0!")
113118

114-
self._path_or_device = path
119+
self._path_or_device: str | int = resolved
115120
self._is_device = isinstance(self._path_or_device, int)
116121

117122
# Initialized in _open_capture:
@@ -156,11 +161,11 @@ def frame_rate(self) -> Fraction:
156161
return self._frame_rate
157162

158163
@property
159-
def path(self) -> bytes | str:
164+
def path(self) -> str:
160165
if self._is_device:
161-
assert isinstance(self._path_or_device, (int))
166+
assert isinstance(self._path_or_device, int)
162167
return f"Device {self._path_or_device}"
163-
assert isinstance(self._path_or_device, (bytes, str))
168+
assert isinstance(self._path_or_device, str)
164169
return self._path_or_device
165170

166171
@property
@@ -316,15 +321,21 @@ def read(self, decode: bool = True) -> np.ndarray | bool:
316321

317322
def _open_capture(self, framerate: float | None = None):
318323
"""Opens capture referenced by this object and resets internal state."""
319-
if self._is_device and self._path_or_device < 0:
320-
raise ValueError("Invalid/negative device ID specified.")
321-
input_is_video_file = not self._is_device and not any(
322-
identifier in self._path_or_device for identifier in NON_VIDEO_FILE_INPUT_IDENTIFIERS
323-
)
324-
# We don't have a way of querying why opening a video fails (errors are logged at least),
325-
# so provide a better error message if we try to open a file that doesn't exist.
326-
if input_is_video_file and not os.path.exists(self._path_or_device):
327-
raise OSError("Video file not found.")
324+
if self._is_device:
325+
assert isinstance(self._path_or_device, int)
326+
if self._path_or_device < 0:
327+
raise ValueError("Invalid/negative device ID specified.")
328+
input_is_video_file = False
329+
else:
330+
assert isinstance(self._path_or_device, str)
331+
input_is_video_file = not any(
332+
identifier in self._path_or_device
333+
for identifier in NON_VIDEO_FILE_INPUT_IDENTIFIERS
334+
)
335+
# We don't have a way of querying why opening a video fails (errors are logged at
336+
# least), so provide a better error message if we try to open a missing file.
337+
if input_is_video_file and not os.path.exists(self._path_or_device):
338+
raise OSError("Video file not found.")
328339

329340
cap = cv2.VideoCapture(self._path_or_device)
330341
if not cap.isOpened():

0 commit comments

Comments
 (0)