Skip to content

Commit 7f2b25b

Browse files
committed
[lint] Fix more type hint errors
1 parent c508441 commit 7f2b25b

14 files changed

Lines changed: 89 additions & 68 deletions

File tree

scenedetect/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def open_video(
105105
:class:`VideoOpenFailure`: Constructing the VideoStream fails. If multiple backends have
106106
been attempted, the error from the first backend will be returned.
107107
"""
108-
last_error: Exception = None
108+
last_error: Exception | None = None
109109
# If `backend` is available, try to open the video at `path` using it.
110110
if backend in AVAILABLE_BACKENDS:
111111
backend_type = AVAILABLE_BACKENDS[backend]
@@ -183,6 +183,6 @@ def detect(
183183
show_progress=show_progress,
184184
end_time=end_timecode,
185185
)
186-
if scene_manager.stats_manager is not None:
186+
if scene_manager.stats_manager is not None and stats_file_path is not None:
187187
scene_manager.stats_manager.save_to_csv(csv_file=stats_file_path)
188188
return scene_manager.get_scene_list(start_in_scene=start_in_scene)

scenedetect/_cli/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def _click_range(section: str, key: str) -> "click.IntRange | click.FloatRange":
6666
assert isinstance(val, RangeValue), f"Expected RangeValue at {section}/{key}, got {type(val)}"
6767
return val.click_range
6868

69+
6970
# About & copyright message string shown for the 'about' CLI command (scenedetect about).
7071
ABOUT_STRING = """
7172
Site: http://scenedetect.com/

scenedetect/_cli/controller.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,13 @@ def calculate_timecode(value: str) -> FrameTimecode:
209209
start_time = context.start_time
210210
cut_list = [cut for cut in cut_list if cut > context.start_time]
211211

212-
end_time = context.video_stream.duration
212+
video_duration = context.video_stream.duration
213+
assert video_duration is not None
214+
end_time = video_duration
213215
if context.end_time is not None:
214-
end_time = min(context.end_time, context.video_stream.duration)
216+
end_time = min(context.end_time, video_duration)
215217
elif context.duration is not None:
216-
end_time = min(start_time + context.duration, context.video_stream.duration)
218+
end_time = min(start_time + context.duration, video_duration)
217219

218220
cut_list = [cut for cut in cut_list if cut < end_time]
219221
scene_list = get_scenes_from_cuts(cut_list=cut_list, start_pos=start_time, end_pos=end_time)

scenedetect/backends/moviepy.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,8 @@ def read(self, decode: bool = True) -> np.ndarray | bool:
267267
return False
268268
self._eof = True
269269
self._frame_number += 1
270-
if decode:
271-
last_frame_valid = self._last_frame is not None and self._last_frame is not False
272-
if last_frame_valid:
273-
self._last_frame_rgb = cv2.cvtColor(self._last_frame, cv2.COLOR_BGR2RGB)
274-
assert self._last_frame_rgb is not None
275-
return self._last_frame_rgb
270+
if decode and isinstance(self._last_frame, np.ndarray):
271+
self._last_frame_rgb = cv2.cvtColor(self._last_frame, cv2.COLOR_BGR2RGB)
272+
assert self._last_frame_rgb is not None
273+
return self._last_frame_rgb
276274
return not self._eof

scenedetect/backends/pyav.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(
7474
VideoOpenFailure: video could not be opened (may be corrupted)
7575
ValueError: specified framerate is invalid
7676
"""
77-
self._container: av.container.InputContainer | None = None
77+
self._container: av.container.InputContainer | None = None # type: ignore[name-defined]
7878

7979
# TODO(https://scenedetect.com/issues/258): See what
8080
# `self._container.discard_corrupt = True` does with corrupt videos.
@@ -93,15 +93,15 @@ def __init__(
9393

9494
if threading_mode:
9595
try:
96-
threading_mode = av.codec.context.ThreadType[threading_mode.upper()]
96+
threading_mode = av.codec.context.ThreadType[threading_mode.upper()] # type: ignore[attr-defined]
9797
except KeyError as _:
9898
raise ValueError(
9999
f"Invalid threading mode! Must be one of: {VALID_THREAD_MODES}"
100100
) from None
101101

102102
if not suppress_output:
103103
logger.debug("Restoring default ffmpeg log callbacks.")
104-
av.logging.restore_default_callback()
104+
av.logging.restore_default_callback() # type: ignore[attr-defined]
105105

106106
try:
107107
if isinstance(path_or_io, (str, os.PathLike)):
@@ -305,7 +305,7 @@ def read(self, decode: bool = True) -> np.ndarray | bool:
305305
assert self._decoder is not None
306306
self._frame = next(self._decoder)
307307
self._decode_count += 1
308-
except av.error.EOFError:
308+
except av.error.EOFError: # type: ignore[attr-defined]
309309
self._frame = last_frame
310310
if self._handle_eof():
311311
return self.read(decode)

scenedetect/detector.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,10 @@ def __init__(self, mode: Mode, length: int | float | str):
134134
self._filter_secs = FrameTimecode(timecode=length, fps=100.0).seconds
135135
else:
136136
self._filter_length = int(length)
137-
self._last_above = None # Last frame above threshold.
137+
self._last_above: FrameTimecode | None = None # Last frame above threshold.
138138
self._merge_enabled = False # Used to disable merging until at least one cut was found.
139139
self._merge_triggered = False # True when the merge filter is active.
140-
self._merge_start = None # Frame number where we started the merge filter.
140+
self._merge_start: FrameTimecode | None = None # Frame where we started merging.
141141

142142
@property
143143
def max_behind(self) -> int:
@@ -165,12 +165,16 @@ def filter(self, timecode: FrameTimecode, above_threshold: bool) -> list[FrameTi
165165
return self._filter_suppress(timecode=timecode, above_threshold=above_threshold)
166166
raise RuntimeError("Unhandled FlashFilter mode.")
167167

168-
def _filter_suppress(self, timecode: FrameTimecode, above_threshold: bool) -> list[int]:
169-
assert timecode.framerate >= 0
168+
def _filter_suppress(
169+
self, timecode: FrameTimecode, above_threshold: bool
170+
) -> list[FrameTimecode]:
171+
framerate = timecode.framerate
172+
assert framerate is not None and framerate >= 0
173+
assert self._last_above is not None
170174
# Compute the threshold in seconds once from the first frame's framerate. This avoids
171175
# using an incorrect average fps (e.g. OpenCV on VFR video) on subsequent frames.
172176
if self._filter_secs is None:
173-
self._filter_secs = self._filter_length / timecode.framerate
177+
self._filter_secs = self._filter_length / framerate
174178
min_length_met: bool = (timecode - self._last_above) >= self._filter_secs
175179
if not (above_threshold and min_length_met):
176180
return []
@@ -179,17 +183,20 @@ def _filter_suppress(self, timecode: FrameTimecode, above_threshold: bool) -> li
179183
self._last_above = timecode
180184
return [timecode]
181185

182-
def _filter_merge(self, timecode: FrameTimecode, above_threshold: bool) -> list[int]:
183-
assert timecode.framerate >= 0
186+
def _filter_merge(self, timecode: FrameTimecode, above_threshold: bool) -> list[FrameTimecode]:
187+
framerate = timecode.framerate
188+
assert framerate is not None and framerate >= 0
189+
assert self._last_above is not None
184190
# Compute the threshold in seconds once from the first frame's framerate.
185191
if self._filter_secs is None:
186-
self._filter_secs = self._filter_length / timecode.framerate
192+
self._filter_secs = self._filter_length / framerate
187193
min_length_met: bool = (timecode - self._last_above) >= self._filter_secs
188194
# Ensure last frame is always advanced to the most recent one that was above the threshold.
189195
if above_threshold:
190196
self._last_above = timecode
191197
if self._merge_triggered:
192198
# This frame was under the threshold, see if enough frames passed to disable the filter.
199+
assert self._merge_start is not None
193200
if (
194201
min_length_met
195202
and not above_threshold

scenedetect/detectors/hash_detector.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def __init__(
5757
self._size = size
5858
self._size_sq = float(size * size)
5959
self._factor = lowpass
60-
self._last_frame: numpy.ndarray = None
61-
self._last_scene_cut: FrameTimecode = None
60+
self._last_frame: numpy.ndarray | None = None
61+
self._last_scene_cut: FrameTimecode | None = None
6262
self._last_hash = numpy.array([])
6363
self._metric_key = f"hash_dist [size={self._size} lowpass={self._factor}]"
6464

@@ -136,7 +136,7 @@ def hash_frame(frame_img, hash_size, factor) -> numpy.ndarray:
136136
max_value = 1
137137

138138
# Calculate discrete cosine tranformation of the image
139-
resized_img = numpy.float32(resized_img) / max_value
139+
resized_img = numpy.asarray(numpy.float32(resized_img) / max_value)
140140
dct_complete = cv2.dct(resized_img)
141141

142142
# Only keep the low frequency information

scenedetect/detectors/threshold_detector.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
This detector is available from the command-line as the `detect-threshold` command.
1616
"""
1717

18+
import typing as ty
1819
import warnings
1920
from enum import Enum
2021
from logging import getLogger
@@ -82,12 +83,12 @@ def __init__(
8283
self.fade_bias = fade_bias
8384
self.min_scene_len = min_scene_len
8485
self.processed_frame = False
85-
self.last_scene_cut = None
86+
self.last_scene_cut: FrameTimecode | None = None
8687
# Whether to add an additional scene or not when ending on a fade out
8788
# (as cuts are only added on fade ins; see post_process() for details).
8889
self.add_final_scene = add_final_scene
8990
# Where the last fade (threshold crossing) was detected.
90-
self.last_fade = {
91+
self.last_fade: dict[str, ty.Any] = {
9192
"frame": None, # FrameTimecode where the last detected fade is
9293
"type": None, # type of fade, can be either 'in' or 'out'
9394
}
@@ -174,14 +175,12 @@ def post_process(self, timecode: FrameTimecode) -> list[FrameTimecode]:
174175
# scene break to indicate the end of the scene. This is only done for
175176
# fade-outs, as a scene cut is already added when a fade-in is found.
176177
cuts: list[FrameTimecode] = []
178+
elapsed = timecode if self.last_scene_cut is None else timecode - self.last_scene_cut
177179
if (
178180
self.last_fade["type"] == "out"
179181
and self.add_final_scene
180182
and self.last_fade["frame"] is not None
181-
and (
182-
(self.last_scene_cut is None and timecode >= self.min_scene_len)
183-
or (timecode - self.last_scene_cut) >= self.min_scene_len
184-
)
183+
and elapsed >= self.min_scene_len
185184
):
186185
cuts.append(self.last_fade["frame"])
187186
return cuts

scenedetect/output/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,12 @@ def write_scene_list_html(
244244
def _edl_timecode(timecode: FrameTimecode) -> str:
245245
"""Format `timecode` as ``HH:MM:SS:FF`` for a CMX 3600 EDL entry."""
246246
total_seconds = timecode.seconds
247+
framerate = timecode.framerate
248+
assert framerate is not None
247249
hours = int(total_seconds // 3600)
248250
minutes = int((total_seconds % 3600) // 60)
249251
seconds = int(total_seconds % 60)
250-
frames_part = int((total_seconds * timecode.framerate) % timecode.framerate)
252+
frames_part = int((total_seconds * framerate) % framerate)
251253
return f"{hours:02d}:{minutes:02d}:{seconds:02d}:{frames_part:02d}"
252254

253255

scenedetect/output/image.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def _generate_timecode_list(
4444
`frame_margin` accepts an int (frames), float (seconds), or str (e.g. ``"0.1s"``).
4545
"""
4646
framerate = scene_list[0][0].framerate
47+
assert framerate is not None
4748
margin_secs = FrameTimecode(timecode=frame_margin, fps=framerate).seconds
4849
result = []
4950
for start, end in scene_list:
@@ -71,7 +72,7 @@ def _generate_timecode_list(
7172

7273
def _scale_image(
7374
image: np.ndarray,
74-
aspect_ratio: float,
75+
aspect_ratio: float | None,
7576
height: int | None,
7677
width: int | None,
7778
scale: float | None,
@@ -90,9 +91,11 @@ def _scale_image(
9091
if height and not width:
9192
factor = height / float(image_height)
9293
width = int(factor * image_width)
93-
if width and not height:
94+
elif width and not height:
9495
factor = width / float(image_width)
9596
height = int(factor * image_height)
97+
assert height is not None
98+
assert width is not None
9699
assert height > 0 and width > 0
97100
image = cv2.resize(image, (width, height), interpolation=interpolation.value)
98101
elif scale:
@@ -106,7 +109,7 @@ def __init__(
106109
num_images: int = 3,
107110
frame_margin: int | float | str = 1,
108111
image_extension: str = "jpg",
109-
imwrite_param: dict[str, int | None] | None = None,
112+
imwrite_param: list[int] | None = None,
110113
image_name_template: str = "$VIDEO_NAME-Scene-$SCENE_NUMBER-$IMAGE_NUMBER",
111114
scale: float | None = None,
112115
height: int | None = None,
@@ -153,7 +156,7 @@ def __init__(
153156
self._height = height
154157
self._width = width
155158
self._interpolation = interpolation
156-
self._imwrite_param = imwrite_param if imwrite_param else {}
159+
self._imwrite_param: list[int] = imwrite_param if imwrite_param is not None else []
157160

158161
def run(
159162
self,
@@ -337,7 +340,7 @@ def generate_timecode_list(self, scene_list: SceneList) -> list[list[FrameTimeco
337340
def resize_image(
338341
self,
339342
image: np.ndarray,
340-
aspect_ratio: float,
343+
aspect_ratio: float | None,
341344
) -> np.ndarray:
342345
return _scale_image(
343346
image, aspect_ratio, self._height, self._width, self._scale, self._interpolation
@@ -436,7 +439,7 @@ def save_images(
436439
width,
437440
interpolation,
438441
)
439-
return extractor.run(video, scene_list, output_dir, show_progress)
442+
return extractor.run(video, scene_list, output_dir, bool(show_progress))
440443

441444
# Setup flags and init progress bar if available.
442445
completed = True
@@ -467,7 +470,7 @@ def save_images(
467470
for j, image_timecode in enumerate(scene_timecodes):
468471
video.seek(image_timecode)
469472
frame_im = video.read()
470-
if frame_im is not None and frame_im is not False:
473+
if isinstance(frame_im, np.ndarray):
471474
# TODO: Add extension to template.
472475
# TODO: Allow NUM to be a valid suffix in addition to NUMBER.
473476
file_path = "{}.{}".format(
@@ -495,9 +498,11 @@ def save_images(
495498
if height and not width:
496499
factor = height / float(frame_height)
497500
width = int(factor * frame_width)
498-
if width and not height:
501+
elif width and not height:
499502
factor = width / float(frame_width)
500503
height = int(factor * frame_height)
504+
assert height is not None
505+
assert width is not None
501506
assert height > 0 and width > 0
502507
frame_im = cv2.resize(
503508
frame_im, (width, height), interpolation=interpolation.value

0 commit comments

Comments
 (0)