diff --git a/docs/conf.py b/docs/conf.py index 4b81d89f..60ad6188 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # diff --git a/docs/generate_cli_docs.py b/docs/generate_cli_docs.py index f8334f02..f2c85c5d 100644 --- a/docs/generate_cli_docs.py +++ b/docs/generate_cli_docs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generate formatted CLI documentation for PySceneDetect. # # Inspired by sphinx-click: https://github.com/click-contrib/sphinx-click @@ -10,22 +9,22 @@ Run from main repo folder as working directory.""" +import inspect import os +import re import sys -import inspect import typing as ty -import re from dataclasses import dataclass # Add parent folder to path so we can resolve `scenedetect` imports. currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) parentdir = os.path.dirname(currentdir) sys.path.insert(0, parentdir) -from scenedetect._cli import scenedetect - # Third-party imports import click +from scenedetect._cli import scenedetect + StrGenerator = ty.Generator[str, None, None] INDENT = " " * 4 @@ -79,7 +78,7 @@ def patch_help(s: str, commands: ty.List[str]) -> str: assert pos > 0 s = s[: pos + 1] + "scenedetect " + s[pos + 1 :] - for command in [command for command in commands if not command in INFO_COMMANDS]: + for command in [command for command in commands if command not in INFO_COMMANDS]: def add_link(_match: re.Match) -> str: return ":ref:`%s `" % (command, command) @@ -136,7 +135,7 @@ def extract_default_value(s: str) -> ty.Tuple[str, ty.Optional[str]]: assert span[1] == len(s) s, default = s[: span[0]].strip(), s[span[0] : span[1]][len("[default: ") : -1] # Double-quote any default values that contain spaces. - if " " in default and not '"' in default and not "," in default: + if " " in default and '"' not in default and "," not in default: default = '"%s"' % default return (s, default) @@ -240,7 +239,7 @@ def generate_subcommands(ctx: click.Context, commands: ty.List[str]) -> StrGener output_commands = [ command for command in commands - if (not command.startswith("detect-") and not command in INFO_COMMANDS) + if (not command.startswith("detect-") and command not in INFO_COMMANDS) ] for command in output_commands: yield from generate_command_help(ctx, ctx.command.get_command(ctx, command), ctx.info_name) diff --git a/pyproject.toml b/pyproject.toml index 4f5594ee..182167ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,9 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" [tool.ruff] +exclude = [ + "docs" +] line-length = 100 indent-width = 4 @@ -21,3 +24,30 @@ quote-style = "double" indent-style = "space" skip-magic-trailing-comma = false docstring-code-format = true + +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # TODO - Rule sets to enable: + # pyupgrade + #"UP", + # flake8-bugbear + #"B", + # flake8-simplify + #"SIM", + # isort + #"I", +] +ignore = [ + # TODO: Determine if we should use __all__, a reudndant alias, or keep this suppressed. + "F401", + # Line too long + "E501", + # Do not assign a `lambda` expression, use a `def` + "E731", +] +fixable = ["ALL"] +unfixable = [] diff --git a/scenedetect/__init__.py b/scenedetect/__init__.py index 0b6b29e0..e3db7906 100644 --- a/scenedetect/__init__.py +++ b/scenedetect/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -15,6 +14,7 @@ can be used to open a video for a :class:`SceneManager `. """ +# ruff: noqa: I001 from logging import getLogger from typing import List, Optional, Tuple, Union @@ -169,6 +169,6 @@ def detect( show_progress=show_progress, end_time=end_time, ) - if not scene_manager.stats_manager is None: + if scene_manager.stats_manager is not None: scene_manager.stats_manager.save_to_csv(csv_file=stats_file_path) return scene_manager.get_scene_list(start_in_scene=start_in_scene) diff --git a/scenedetect/__main__.py b/scenedetect/__main__.py index 0d0428d6..0328a343 100755 --- a/scenedetect/__main__.py +++ b/scenedetect/__main__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -12,14 +11,13 @@ # """Entry point for PySceneDetect's command-line interface.""" -from logging import getLogger import sys +from logging import getLogger from scenedetect._cli import scenedetect from scenedetect._cli.context import CliContext from scenedetect._cli.controller import run_scenedetect - -from scenedetect.platform import logging_redirect_tqdm, FakeTqdmLoggingRedirect +from scenedetect.platform import FakeTqdmLoggingRedirect, logging_redirect_tqdm def main(): diff --git a/scenedetect/_cli/__init__.py b/scenedetect/_cli/__init__.py index 3bb8c035..2af76bc4 100644 --- a/scenedetect/_cli/__init__.py +++ b/scenedetect/_cli/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -27,6 +26,9 @@ import click import scenedetect +from scenedetect._cli.config import CHOICE_MAP, CONFIG_FILE_PATH, CONFIG_MAP +from scenedetect._cli.context import USER_CONFIG, CliContext +from scenedetect.backends import AVAILABLE_BACKENDS from scenedetect.detectors import ( AdaptiveDetector, ContentDetector, @@ -34,10 +36,7 @@ HistogramDetector, ThresholdDetector, ) -from scenedetect.backends import AVAILABLE_BACKENDS from scenedetect.platform import get_system_version_info -from scenedetect._cli.config import CHOICE_MAP, CONFIG_FILE_PATH, CONFIG_MAP -from scenedetect._cli.context import CliContext, USER_CONFIG _PROGRAM_VERSION = scenedetect.__version__ """Used to avoid name conflict with named `scenedetect` command below.""" @@ -345,7 +344,7 @@ def help_command(ctx: click.Context, command_name: str): parent_command = ctx.parent.command all_commands = set(parent_command.list_commands(ctx)) if command_name is not None: - if not command_name in all_commands: + if command_name not in all_commands: error_strs = [ "unknown command. List of valid commands:", " %s" % ", ".join(sorted(all_commands)), diff --git a/scenedetect/_cli/config.py b/scenedetect/_cli/config.py index da3762cb..037cf1b1 100644 --- a/scenedetect/_cli/config.py +++ b/scenedetect/_cli/config.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -15,12 +14,12 @@ possible and re-used by the CLI so that there is one source of truth. """ -from abc import ABC, abstractmethod -from configparser import ConfigParser, ParsingError -from enum import Enum import logging import os import os.path +from abc import ABC, abstractmethod +from configparser import ConfigParser, ParsingError +from enum import Enum from typing import Any, AnyStr, Dict, List, Optional, Tuple, Union from platformdirs import user_config_dir @@ -408,11 +407,11 @@ def _validate_structure(config: ConfigParser) -> List[str]: """ errors: List[str] = [] for section in config.sections(): - if not section in CONFIG_MAP.keys(): + if section not in CONFIG_MAP.keys(): errors.append("Unsupported config section: [%s]" % (section)) continue for option_name, _ in config.items(section): - if not option_name in CONFIG_MAP[section].keys(): + if option_name not in CONFIG_MAP[section].keys(): errors.append("Unsupported config option in [%s]: %s" % (section, option_name)) return errors @@ -555,7 +554,7 @@ def _load_from_disk(self, path=None): # Try to load and parse the config file at `path`. config = ConfigParser() try: - with open(path, "r") as config_file: + with open(path) as config_file: config_file_contents = config_file.read() config.read_string(config_file_contents, source=path) except ParsingError as ex: diff --git a/scenedetect/_cli/context.py b/scenedetect/_cli/context.py index ecad83f8..ad88890d 100644 --- a/scenedetect/_cli/context.py +++ b/scenedetect/_cli/context.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -19,29 +18,29 @@ import click import scenedetect # Required to access __version__ -from scenedetect import open_video, AVAILABLE_BACKENDS +from scenedetect import AVAILABLE_BACKENDS, open_video +from scenedetect._cli.config import ( + CHOICE_MAP, + DEFAULT_JPG_QUALITY, + DEFAULT_WEBP_QUALITY, + ConfigLoadFailure, + ConfigRegistry, + TimecodeFormat, +) from scenedetect.detectors import ( AdaptiveDetector, ContentDetector, - ThresholdDetector, HashDetector, HistogramDetector, + ThresholdDetector, ) -from scenedetect.frame_timecode import FrameTimecode, MAX_FPS_DELTA -from scenedetect.platform import get_and_create_path, get_cv2_imwrite_params, init_logger -from scenedetect.scene_detector import SceneDetector, FlashFilter -from scenedetect.scene_manager import SceneManager, Interpolation +from scenedetect.frame_timecode import MAX_FPS_DELTA, FrameTimecode +from scenedetect.platform import get_cv2_imwrite_params, init_logger +from scenedetect.scene_detector import FlashFilter, SceneDetector +from scenedetect.scene_manager import Interpolation, SceneManager from scenedetect.stats_manager import StatsManager -from scenedetect.video_splitter import is_mkvmerge_available, is_ffmpeg_available -from scenedetect.video_stream import VideoStream, VideoOpenFailure, FrameRateUnavailable -from scenedetect._cli.config import ( - ConfigRegistry, - ConfigLoadFailure, - TimecodeFormat, - CHOICE_MAP, - DEFAULT_JPG_QUALITY, - DEFAULT_WEBP_QUALITY, -) +from scenedetect.video_splitter import is_ffmpeg_available, is_mkvmerge_available +from scenedetect.video_stream import FrameRateUnavailable, VideoOpenFailure, VideoStream logger = logging.getLogger("pyscenedetect") @@ -742,7 +741,7 @@ def handle_save_images( self.image_extension = "jpg" if jpeg else "png" if png else "webp" valid_params = get_cv2_imwrite_params() - if not self.image_extension in valid_params or valid_params[self.image_extension] is None: + if self.image_extension not in valid_params or valid_params[self.image_extension] is None: error_strs = [ "Image encoder type `%s` not supported." % self.image_extension.upper(), "The specified encoder type could not be found in the current OpenCV module.", @@ -860,7 +859,7 @@ def _open_video_stream( if backend is None: backend = self.config.get_value("global", "backend") else: - if not backend in AVAILABLE_BACKENDS: + if backend not in AVAILABLE_BACKENDS: raise click.BadParameter( "Specified backend %s is not available on this system!" % backend, param_hint="-b/--backend", diff --git a/scenedetect/_cli/controller.py b/scenedetect/_cli/controller.py index f9332d52..eae039d4 100644 --- a/scenedetect/_cli/controller.py +++ b/scenedetect/_cli/controller.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -15,10 +14,11 @@ import csv import logging import os -from string import Template import time import typing as ty +from string import Template +from scenedetect._cli.context import CliContext, check_split_video_requirements from scenedetect.frame_timecode import FrameTimecode from scenedetect.platform import get_and_create_path from scenedetect.scene_manager import ( @@ -27,9 +27,8 @@ write_scene_list, write_scene_list_html, ) -from scenedetect.video_splitter import split_video_mkvmerge, split_video_ffmpeg +from scenedetect.video_splitter import split_video_ffmpeg, split_video_mkvmerge from scenedetect.video_stream import SeekError -from scenedetect._cli.context import CliContext, check_split_video_requirements logger = logging.getLogger("pyscenedetect") @@ -176,7 +175,7 @@ def _list_scenes(context: CliContext, scene_list: SceneList, cut_list: CutList) context.scene_list_dir if context.scene_list_dir is not None else context.output_dir, ) logger.info("Writing scene list to CSV file:\n %s", scene_list_path) - with open(scene_list_path, "wt") as scene_list_file: + with open(scene_list_path, "w") as scene_list_file: write_scene_list( output_csv_file=scene_list_file, scene_list=scene_list, @@ -316,10 +315,10 @@ def _load_scenes(context: CliContext) -> ty.Tuple[SceneList, CutList]: assert context.load_scenes_input assert os.path.exists(context.load_scenes_input) - with open(context.load_scenes_input, "r") as input_file: + with open(context.load_scenes_input) as input_file: file_reader = csv.reader(input_file) csv_headers = next(file_reader) - if not context.load_scenes_column_name in csv_headers: + if context.load_scenes_column_name not in csv_headers: csv_headers = next(file_reader) # Check to make sure column headers are present if context.load_scenes_column_name not in csv_headers: diff --git a/scenedetect/_thirdparty/__init__.py b/scenedetect/_thirdparty/__init__.py index 83987ca8..5442893f 100644 --- a/scenedetect/_thirdparty/__init__.py +++ b/scenedetect/_thirdparty/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- diff --git a/scenedetect/_thirdparty/simpletable.py b/scenedetect/_thirdparty/simpletable.py index 4ae31505..df01519d 100644 --- a/scenedetect/_thirdparty/simpletable.py +++ b/scenedetect/_thirdparty/simpletable.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- # The MIT License (MIT) # @@ -64,7 +63,7 @@ def quote(string): return pathname2url(string) -class SimpleTableCell(object): +class SimpleTableCell: """A table class to create table cells. Example: @@ -89,7 +88,7 @@ def __str__(self): return "%s" % (self.text) -class SimpleTableImage(object): +class SimpleTableImage: """A table class to create table cells with an image. Example: @@ -128,7 +127,7 @@ def __str__(self): return output -class SimpleTableRow(object): +class SimpleTableRow: """A table class to create table rows, populated by table cells. Example: @@ -187,7 +186,7 @@ def add_cells(self, cells): self.cells.append(cell) -class SimpleTable(object): +class SimpleTable: """A table class to create HTML tables, populated by HTML table rows. Example: @@ -263,7 +262,7 @@ def add_rows(self, rows): self.rows.append(row) -class HTMLPage(object): +class HTMLPage: """A class to create HTML pages containing CSS and tables.""" def __init__(self, tables=None, css=None, encoding="utf-8"): diff --git a/scenedetect/backends/__init__.py b/scenedetect/backends/__init__.py index 9d60bb34..a8bd763a 100644 --- a/scenedetect/backends/__init__.py +++ b/scenedetect/backends/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -87,7 +86,7 @@ from typing import Dict, Type # OpenCV must be available at minimum. -from scenedetect.backends.opencv import VideoStreamCv2, VideoCaptureAdapter +from scenedetect.backends.opencv import VideoCaptureAdapter, VideoStreamCv2 try: from scenedetect.backends.pyav import VideoStreamAv diff --git a/scenedetect/backends/moviepy.py b/scenedetect/backends/moviepy.py index b3fb8ab8..e85f37c4 100644 --- a/scenedetect/backends/moviepy.py +++ b/scenedetect/backends/moviepy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -18,16 +17,16 @@ """ from logging import getLogger -from typing import AnyStr, Tuple, Union, Optional +from typing import AnyStr, Optional, Tuple, Union import cv2 -from moviepy.video.io.ffmpeg_reader import FFMPEG_VideoReader import numpy as np +from moviepy.video.io.ffmpeg_reader import FFMPEG_VideoReader +from scenedetect.backends.opencv import VideoStreamCv2 from scenedetect.frame_timecode import FrameTimecode from scenedetect.platform import get_file_name -from scenedetect.video_stream import VideoStream, SeekError, VideoOpenFailure -from scenedetect.backends.opencv import VideoStreamCv2 +from scenedetect.video_stream import SeekError, VideoOpenFailure, VideoStream logger = getLogger("pyscenedetect") @@ -179,7 +178,7 @@ def seek(self, target: Union[FrameTimecode, float, int]): target = FrameTimecode(target, self.frame_rate) try: self._reader.get_frame(target.get_seconds()) - except IOError as ex: + except OSError as ex: # Leave the object in a valid state. self.reset() # TODO(#380): Other backends do not currently throw an exception if attempting to seek diff --git a/scenedetect/backends/opencv.py b/scenedetect/backends/opencv.py index d9f7c9dc..862a19e2 100644 --- a/scenedetect/backends/opencv.py +++ b/scenedetect/backends/opencv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -18,17 +17,17 @@ which do not support seeking. """ -from logging import getLogger import math -from typing import AnyStr, Tuple, Union, Optional import os.path +from logging import getLogger +from typing import AnyStr, Optional, Tuple, Union import cv2 import numpy as np -from scenedetect.frame_timecode import FrameTimecode, MAX_FPS_DELTA +from scenedetect.frame_timecode import MAX_FPS_DELTA, FrameTimecode from scenedetect.platform import get_file_name -from scenedetect.video_stream import VideoStream, SeekError, VideoOpenFailure, FrameRateUnavailable +from scenedetect.video_stream import FrameRateUnavailable, SeekError, VideoOpenFailure, VideoStream logger = getLogger("pyscenedetect") @@ -44,7 +43,7 @@ def _get_aspect_ratio(cap: cv2.VideoCapture, epsilon: float = 0.0001) -> float: """Display/pixel aspect ratio of the VideoCapture as a float (1.0 represents square pixels).""" # Versions of OpenCV < 3.4.1 do not support this, so we fall back to 1.0. - if not "CAP_PROP_SAR_NUM" in dir(cv2): + if "CAP_PROP_SAR_NUM" not in dir(cv2): return 1.0 num: float = cap.get(cv2.CAP_PROP_SAR_NUM) den: float = cap.get(cv2.CAP_PROP_SAR_DEN) @@ -318,9 +317,8 @@ def _open_capture(self, framerate: Optional[float] = None): ) # We don't have a way of querying why opening a video fails (errors are logged at least), # so provide a better error message if we try to open a file that doesn't exist. - if input_is_video_file: - if not os.path.exists(self._path_or_device): - raise OSError("Video file not found.") + if input_is_video_file and not os.path.exists(self._path_or_device): + raise OSError("Video file not found.") cap = cv2.VideoCapture(self._path_or_device) if not cap.isOpened(): diff --git a/scenedetect/backends/pyav.py b/scenedetect/backends/pyav.py index f59f8ad1..3ca58151 100644 --- a/scenedetect/backends/pyav.py +++ b/scenedetect/backends/pyav.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -19,9 +18,9 @@ import av import numpy as np -from scenedetect.frame_timecode import FrameTimecode, MAX_FPS_DELTA +from scenedetect.frame_timecode import MAX_FPS_DELTA, FrameTimecode from scenedetect.platform import get_file_name -from scenedetect.video_stream import VideoStream, VideoOpenFailure, FrameRateUnavailable +from scenedetect.video_stream import FrameRateUnavailable, VideoOpenFailure, VideoStream logger = getLogger("pyscenedetect") @@ -91,7 +90,7 @@ def __init__( if threading_mode: threading_mode = threading_mode.upper() - if not threading_mode in VALID_THREAD_MODES: + if threading_mode not in VALID_THREAD_MODES: raise ValueError("Invalid threading mode! Must be one of: %s" % VALID_THREAD_MODES) if not suppress_output: @@ -348,7 +347,7 @@ def _handle_eof(self): return False self._reopened = True # Don't re-open the video if we can't seek or aren't in AUTO/FRAME thread_type mode. - if not self.is_seekable or not self._video_stream.thread_type in ("AUTO", "FRAME"): + if not self.is_seekable or self._video_stream.thread_type not in ("AUTO", "FRAME"): return False last_frame = self.frame_number orig_pos = self._io.tell() diff --git a/scenedetect/detectors/__init__.py b/scenedetect/detectors/__init__.py index c7a0833c..ff8bdd69 100644 --- a/scenedetect/detectors/__init__.py +++ b/scenedetect/detectors/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -36,6 +35,8 @@ processing videos, however they can also be used to process frames directly. """ +# ruff: noqa: I001 + from scenedetect.detectors.content_detector import ContentDetector from scenedetect.detectors.threshold_detector import ThresholdDetector from scenedetect.detectors.adaptive_detector import AdaptiveDetector diff --git a/scenedetect/detectors/adaptive_detector.py b/scenedetect/detectors/adaptive_detector.py index 580ad518..0cbb4895 100644 --- a/scenedetect/detectors/adaptive_detector.py +++ b/scenedetect/detectors/adaptive_detector.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- diff --git a/scenedetect/detectors/content_detector.py b/scenedetect/detectors/content_detector.py index fd384065..bfa99ac4 100644 --- a/scenedetect/detectors/content_detector.py +++ b/scenedetect/detectors/content_detector.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -16,14 +15,14 @@ This detector is available from the command-line as the `detect-content` command. """ -from dataclasses import dataclass import math +from dataclasses import dataclass from typing import List, NamedTuple, Optional -import numpy import cv2 +import numpy -from scenedetect.scene_detector import SceneDetector, FlashFilter +from scenedetect.scene_detector import FlashFilter, SceneDetector def _mean_pixel_distance(left: numpy.ndarray, right: numpy.ndarray) -> float: diff --git a/scenedetect/detectors/hash_detector.py b/scenedetect/detectors/hash_detector.py index c1c65a8e..36f7e1b5 100644 --- a/scenedetect/detectors/hash_detector.py +++ b/scenedetect/detectors/hash_detector.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # --------------------------------------------------------------- @@ -35,8 +34,8 @@ """ # Third-Party Library Imports -import numpy import cv2 +import numpy # PySceneDetect Library Imports from scenedetect.scene_detector import SceneDetector diff --git a/scenedetect/detectors/histogram_detector.py b/scenedetect/detectors/histogram_detector.py index baa704d9..9e37df09 100644 --- a/scenedetect/detectors/histogram_detector.py +++ b/scenedetect/detectors/histogram_detector.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # --------------------------------------------------------------- diff --git a/scenedetect/detectors/threshold_detector.py b/scenedetect/detectors/threshold_detector.py index 56c3de6c..f14d1882 100644 --- a/scenedetect/detectors/threshold_detector.py +++ b/scenedetect/detectors/threshold_detector.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -16,9 +15,9 @@ This detector is available from the command-line as the `detect-threshold` command. """ +import typing as ty from enum import Enum from logging import getLogger -import typing as ty import numpy diff --git a/scenedetect/frame_timecode.py b/scenedetect/frame_timecode.py index 55a25501..ffb836b4 100644 --- a/scenedetect/frame_timecode.py +++ b/scenedetect/frame_timecode.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -276,7 +275,7 @@ def _parse_timecode_string(self, input: str) -> int: Raises: ValueError: Value could not be parsed correctly. """ - assert not self.framerate is None + assert self.framerate is not None input = input.strip() # Exact number of frames N if input.isdigit(): diff --git a/scenedetect/platform.py b/scenedetect/platform.py index 34192c42..54a325c5 100644 --- a/scenedetect/platform.py +++ b/scenedetect/platform.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- diff --git a/scenedetect/scene_detector.py b/scenedetect/scene_detector.py index e1604a79..316437e6 100644 --- a/scenedetect/scene_detector.py +++ b/scenedetect/scene_detector.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -25,8 +24,8 @@ event (in, out, cut, etc...). """ -from enum import Enum import typing as ty +from enum import Enum import numpy diff --git a/scenedetect/scene_manager.py b/scenedetect/scene_manager.py index f4ed5dd1..efca1725 100644 --- a/scenedetect/scene_manager.py +++ b/scenedetect/scene_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -81,29 +80,29 @@ def on_new_scene(frame_img: numpy.ndarray, frame_num: int): """ import csv -from enum import Enum -from typing import Iterable, List, Tuple, Optional, Dict, Callable, Union, TextIO -import threading -import queue import logging import math +import queue import sys +import threading +from enum import Enum +from typing import Callable, Dict, Iterable, List, Optional, TextIO, Tuple, Union import cv2 import numpy as np + from scenedetect._thirdparty.simpletable import ( + HTMLPage, + SimpleTable, SimpleTableCell, SimpleTableImage, SimpleTableRow, - SimpleTable, - HTMLPage, ) - -from scenedetect.platform import tqdm, get_and_create_path, get_cv2_imwrite_params, Template from scenedetect.frame_timecode import FrameTimecode -from scenedetect.video_stream import VideoStream +from scenedetect.platform import Template, get_and_create_path, get_cv2_imwrite_params, tqdm from scenedetect.scene_detector import SceneDetector, SparseSceneDetector from scenedetect.stats_manager import StatsManager +from scenedetect.video_stream import VideoStream logger = logging.getLogger("pyscenedetect") @@ -943,7 +942,7 @@ def detect_scenes( next_frame, position = frame_queue.get() if next_frame is None and position is None: break - if not next_frame is None: + if next_frame is not None: frame_im = next_frame new_cuts = self._process_frame(position.frame_num, frame_im, callback) if progress_bar is not None: @@ -1011,7 +1010,7 @@ def _decode_thread( ) if self._frame_size_errors == MAX_FRAME_SIZE_ERRORS: logger.warn( - f"WARNING: Too many errors emitted, skipping future messages." + "WARNING: Too many errors emitted, skipping future messages." ) # Skip processing frames that have an incorrect size. continue diff --git a/scenedetect/stats_manager.py b/scenedetect/stats_manager.py index 02fdcd9d..b028e244 100644 --- a/scenedetect/stats_manager.py +++ b/scenedetect/stats_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -23,12 +22,12 @@ """ import csv -from logging import getLogger +import os.path import typing as ty +from logging import getLogger # TODO: Replace below imports with `ty.` prefix. from typing import Any, Dict, Iterable, List, Optional, Set, TextIO, Union -import os.path from scenedetect.frame_timecode import FrameTimecode @@ -245,7 +244,7 @@ def load_from_csv(self, csv_file: Union[str, bytes, TextIO]) -> Optional[int]: # recursively call ourselves again but with file set instead of path. if isinstance(csv_file, (str, bytes)): if os.path.exists(csv_file): - with open(csv_file, "r") as file: + with open(csv_file) as file: return self.load_from_csv(csv_file=file) # Path doesn't exist. return None @@ -305,7 +304,7 @@ def _get_metric(self, frame_number: int, metric_key: str) -> Optional[Any]: def _set_metric(self, frame_number: int, metric_key: str, metric_value: Any) -> None: self._metrics_updated = True - if not frame_number in self._frame_metrics: + if frame_number not in self._frame_metrics: self._frame_metrics[frame_number] = dict() self._frame_metrics[frame_number][metric_key] = metric_value diff --git a/scenedetect/video_manager.py b/scenedetect/video_manager.py index 4b5b4d54..5111ade9 100644 --- a/scenedetect/video_manager.py +++ b/scenedetect/video_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -19,18 +18,18 @@ in a future release. """ -import os import math +import os from logging import getLogger - from typing import Iterable, List, Optional, Tuple, Union -import numpy as np + import cv2 +import numpy as np -from scenedetect.platform import get_file_name -from scenedetect.frame_timecode import FrameTimecode, MAX_FPS_DELTA -from scenedetect.video_stream import VideoStream, VideoOpenFailure, FrameRateUnavailable from scenedetect.backends.opencv import _get_aspect_ratio +from scenedetect.frame_timecode import MAX_FPS_DELTA, FrameTimecode +from scenedetect.platform import get_file_name +from scenedetect.video_stream import FrameRateUnavailable, VideoOpenFailure, VideoStream ## ## VideoManager Exceptions @@ -146,7 +145,7 @@ def open_captures( if not ("%" in video_file or "://" in video_file) ] ): - raise IOError("Video file(s) not found.") + raise OSError("Video file(s) not found.") cap_list = [] try: diff --git a/scenedetect/video_splitter.py b/scenedetect/video_splitter.py index 34027a6a..8b41834d 100644 --- a/scenedetect/video_splitter.py +++ b/scenedetect/video_splitter.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -33,16 +32,16 @@ available on the computer, depending on the specified command-line options. """ -from dataclasses import dataclass import logging import math -from pathlib import Path import subprocess import time import typing as ty +from dataclasses import dataclass +from pathlib import Path -from scenedetect.platform import tqdm, invoke_command, CommandTooLong, get_ffmpeg_path, Template from scenedetect.frame_timecode import FrameTimecode +from scenedetect.platform import CommandTooLong, Template, get_ffmpeg_path, invoke_command, tqdm logger = logging.getLogger("pyscenedetect") diff --git a/scenedetect/video_stream.py b/scenedetect/video_stream.py index 03f565da..96642243 100644 --- a/scenedetect/video_stream.py +++ b/scenedetect/video_stream.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -33,7 +32,7 @@ """ from abc import ABC, abstractmethod -from typing import Tuple, Optional, Union +from typing import Optional, Tuple, Union import numpy as np diff --git a/setup.py b/setup.py index 498f577c..ec281380 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # --------------------------------------------------------------- diff --git a/tests/__init__.py b/tests/__init__.py index 6ff20946..981ec4b7 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- diff --git a/tests/conftest.py b/tests/conftest.py index 3a5a3813..6034456c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -27,9 +26,9 @@ # TODO: Properly cleanup temporary files. -from typing import AnyStr import logging import os +from typing import AnyStr import pytest @@ -93,7 +92,7 @@ def no_logs_gte_error(caplog): errors = [ record for record in caplog.get_records("call") - if record.levelno >= logging.ERROR and not record.module in EXCLUDED_MODULES + if record.levelno >= logging.ERROR and record.module not in EXCLUDED_MODULES ] assert not errors, "Test failed due to presence of one or more logs with ERROR severity." diff --git a/tests/test_api.py b/tests/test_api.py index ba4c0f60..69ede73b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -23,7 +22,7 @@ def test_api_detect(test_video_file: str): """Demonstrate usage of the `detect()` function to process a complete video.""" - from scenedetect import detect, ContentDetector + from scenedetect import ContentDetector, detect scene_list = detect(test_video_file, ContentDetector()) for i, scene in enumerate(scene_list): @@ -32,7 +31,7 @@ def test_api_detect(test_video_file: str): def test_api_detect_start_end_time(test_video_file: str): """Demonstrate usage of the `detect()` function to process a subset of a video.""" - from scenedetect import detect, ContentDetector + from scenedetect import ContentDetector, detect # Times can be seconds (float), frames (int), or timecode 'HH:MM:SSS.nnn' (str). # See test_api_timecode_types() for examples of each format. @@ -43,14 +42,14 @@ def test_api_detect_start_end_time(test_video_file: str): def test_api_detect_stats(test_video_file: str): """Demonstrate usage of the `detect()` function to generate a statsfile.""" - from scenedetect import detect, ContentDetector + from scenedetect import ContentDetector, detect detect(test_video_file, ContentDetector(), stats_file_path="frame_metrics.csv") def test_api_scene_manager(test_video_file: str): """Demonstrate how to use a SceneManager to implement a function similar to `detect()`.""" - from scenedetect import SceneManager, ContentDetector, open_video + from scenedetect import ContentDetector, SceneManager, open_video video = open_video(test_video_file) scene_manager = SceneManager() @@ -63,7 +62,7 @@ def test_api_scene_manager(test_video_file: str): def test_api_scene_manager_start_end_time(test_video_file: str): """Demonstrate how to use a SceneManager to process a subset of the input video.""" - from scenedetect import SceneManager, ContentDetector, open_video + from scenedetect import ContentDetector, SceneManager, open_video video = open_video(test_video_file) scene_manager = SceneManager() @@ -100,7 +99,7 @@ def test_api_timecode_types(): def test_api_stats_manager(test_video_file: str): """Demonstrate using a StatsManager to save per-frame statistics to disk.""" - from scenedetect import SceneManager, StatsManager, ContentDetector, open_video + from scenedetect import ContentDetector, SceneManager, StatsManager, open_video video = open_video(test_video_file) scene_manager = SceneManager(stats_manager=StatsManager()) @@ -114,7 +113,8 @@ def test_api_stats_manager(test_video_file: str): def test_api_scene_manager_callback(test_video_file: str): """Demonstrate how to use a callback with the SceneManager detect_scenes method.""" import numpy - from scenedetect import SceneManager, ContentDetector, open_video + + from scenedetect import ContentDetector, SceneManager, open_video # Callback to invoke on the first frame of every new scene detection. def on_new_scene(frame_img: numpy.ndarray, frame_num: int): @@ -132,7 +132,8 @@ def test_api_device_callback(test_video_file: str): wrapping it with a `VideoCaptureAdapter.`""" import cv2 import numpy - from scenedetect import SceneManager, ContentDetector, VideoCaptureAdapter + + from scenedetect import ContentDetector, SceneManager, VideoCaptureAdapter # Callback to invoke on the first frame of every new scene detection. def on_new_scene(frame_img: numpy.ndarray, frame_num: int): diff --git a/tests/test_backend_opencv.py b/tests/test_backend_opencv.py index 4d66b192..4a77f2cb 100644 --- a/tests/test_backend_opencv.py +++ b/tests/test_backend_opencv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -21,7 +20,7 @@ import cv2 from scenedetect import ContentDetector, SceneManager -from scenedetect.backends.opencv import VideoStreamCv2, VideoCaptureAdapter +from scenedetect.backends.opencv import VideoCaptureAdapter, VideoStreamCv2 GROUND_TRUTH_CAPTURE_ADAPTER_TEST = [1, 90, 210] GROUND_TRUTH_CAPTURE_ADAPTER_CALLBACK_TEST = [30, 180, 394] diff --git a/tests/test_backend_pyav.py b/tests/test_backend_pyav.py index 11e317a3..8e27a495 100644 --- a/tests/test_backend_pyav.py +++ b/tests/test_backend_pyav.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- diff --git a/tests/test_backwards_compat.py b/tests/test_backwards_compat.py index 43d8c2cf..b4111aba 100644 --- a/tests/test_backwards_compat.py +++ b/tests/test_backwards_compat.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -21,7 +20,7 @@ import logging import os -from scenedetect import SceneManager, StatsManager, VideoManager, ContentDetector +from scenedetect import ContentDetector, SceneManager, StatsManager, VideoManager from scenedetect.platform import init_logger @@ -46,7 +45,7 @@ def validate_backwards_compatibility(test_video_file: str, stats_file_path: str) end_time = base_timecode + 10.0 # 00:00:10.000 if os.path.exists(stats_file_path): - with open(stats_file_path, "r") as stats_file: + with open(stats_file_path) as stats_file: stats_manager.load_from_csv(stats_file) # ContentDetector requires at least 1 frame before it can calculate any metrics. assert stats_manager.metrics_exist( diff --git a/tests/test_cli.py b/tests/test_cli.py index b5ba8d3b..dbd9ef90 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -13,12 +12,12 @@ import glob import os -import typing as ty import subprocess -import pytest +import typing as ty from pathlib import Path import cv2 +import pytest from scenedetect.video_splitter import is_ffmpeg_available, is_mkvmerge_available diff --git a/tests/test_detectors.py b/tests/test_detectors.py index f670ea42..0df1f95a 100644 --- a/tests/test_detectors.py +++ b/tests/test_detectors.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -17,15 +16,21 @@ test case material. """ -from dataclasses import dataclass import os import typing as ty +from dataclasses import dataclass import pytest -from scenedetect import detect, SceneManager, FrameTimecode, StatsManager, SceneDetector -from scenedetect.detectors import * +from scenedetect import FrameTimecode, SceneDetector, SceneManager, StatsManager, detect from scenedetect.backends.opencv import VideoStreamCv2 +from scenedetect.detectors import ( + AdaptiveDetector, + ContentDetector, + HashDetector, + HistogramDetector, + ThresholdDetector, +) FAST_CUT_DETECTORS: ty.Tuple[ty.Type[SceneDetector]] = ( AdaptiveDetector, diff --git a/tests/test_frame_timecode.py b/tests/test_frame_timecode.py index b1b85d15..61b997cc 100644 --- a/tests/test_frame_timecode.py +++ b/tests/test_frame_timecode.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -21,14 +20,14 @@ or string HH:MM:SS[.nnn]. timecode format. """ +# ruff: noqa: B015 # pylint: disable=invalid-name, expression-not-assigned, unneeded-not, pointless-statement # Third-Party Library Imports import pytest # Standard Library Imports -from scenedetect.frame_timecode import FrameTimecode -from scenedetect.frame_timecode import MAX_FPS_DELTA +from scenedetect.frame_timecode import MAX_FPS_DELTA, FrameTimecode def test_framerate(): @@ -193,9 +192,9 @@ def test_equality(): x = FrameTimecode(timecode=1.0, fps=10.0) assert x == x assert x == FrameTimecode(timecode=1.0, fps=10.0) - assert not x != FrameTimecode(timecode=1.0, fps=10.0) + assert x == FrameTimecode(timecode=1.0, fps=10.0) + assert x != FrameTimecode(timecode=10.0, fps=10.0) assert x != FrameTimecode(timecode=10.0, fps=10.0) - assert not x == FrameTimecode(timecode=10.0, fps=10.0) # Comparing FrameTimecodes with different framerates raises a TypeError. with pytest.raises(TypeError): x == FrameTimecode(timecode=1.0, fps=100.0) diff --git a/tests/test_platform.py b/tests/test_platform.py index 767aeecb..319a54ea 100644 --- a/tests/test_platform.py +++ b/tests/test_platform.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -17,6 +16,7 @@ """ import platform + import pytest from scenedetect.platform import CommandTooLong, invoke_command diff --git a/tests/test_scene_manager.py b/tests/test_scene_manager.py index 7cd90105..8a1f7f9e 100644 --- a/tests/test_scene_manager.py +++ b/tests/test_scene_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -165,7 +164,7 @@ def test_save_images_zero_width_scene(test_video_file): # TODO: This would be more readable if the callbacks were defined within the test case, e.g. # split up the callback function and callback lambda test cases. # pylint: disable=unused-argument, unnecessary-lambda -class FakeCallback(object): +class FakeCallback: """Fake callback used for testing. Tracks the frame numbers the callback was invoked with.""" def __init__(self): diff --git a/tests/test_stats_manager.py b/tests/test_stats_manager.py index 0f272301..3d8361d2 100644 --- a/tests/test_stats_manager.py +++ b/tests/test_stats_manager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -35,16 +34,16 @@ import pytest -from scenedetect.scene_manager import SceneManager -from scenedetect.frame_timecode import FrameTimecode from scenedetect.backends.opencv import VideoStreamCv2 from scenedetect.detectors import ContentDetector - -from scenedetect.stats_manager import StatsManager -from scenedetect.stats_manager import StatsFileCorrupt - -from scenedetect.stats_manager import COLUMN_NAME_FRAME_NUMBER -from scenedetect.stats_manager import COLUMN_NAME_TIMECODE +from scenedetect.frame_timecode import FrameTimecode +from scenedetect.scene_manager import SceneManager +from scenedetect.stats_manager import ( + COLUMN_NAME_FRAME_NUMBER, + COLUMN_NAME_TIMECODE, + StatsFileCorrupt, + StatsManager, +) # TODO(v1.0): use https://docs.pytest.org/en/6.2.x/tmpdir.html TEST_STATS_FILES = ["TEST_STATS_FILE"] * 4 @@ -186,7 +185,7 @@ def test_load_corrupt_stats(): stats_manager = StatsManager() - with open(TEST_STATS_FILES[0], "wt") as stats_file: + with open(TEST_STATS_FILES[0], "w") as stats_file: stats_writer = csv.writer(stats_file, lineterminator="\n") some_metric_key = "some_metric" diff --git a/tests/test_video_splitter.py b/tests/test_video_splitter.py index 4e83d982..a79e3e0c 100644 --- a/tests/test_video_splitter.py +++ b/tests/test_video_splitter.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -15,14 +14,15 @@ # pylint: disable=no-self-use,missing-function-docstring from pathlib import Path + import pytest from scenedetect import open_video from scenedetect.video_splitter import ( - split_video_ffmpeg, - is_ffmpeg_available, SceneMetadata, VideoMetadata, + is_ffmpeg_available, + split_video_ffmpeg, ) diff --git a/tests/test_video_stream.py b/tests/test_video_stream.py index fbf3e10a..ccf0b537 100644 --- a/tests/test_video_stream.py +++ b/tests/test_video_stream.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # PySceneDetect: Python-Based Video Scene Detector # ------------------------------------------------------------------- @@ -17,20 +16,20 @@ all supported backends, and verify that they are functionally equivalent where possible. """ +# ruff: noqa: B011 # pylint: disable=no-self-use,missing-function-docstring +import os.path from dataclasses import dataclass from typing import List, Type -import os.path import numpy import pytest -from scenedetect.video_stream import VideoStream, SeekError +from scenedetect.backends import VideoStreamAv, VideoStreamMoviePy from scenedetect.backends.opencv import VideoStreamCv2 -from scenedetect.backends import VideoStreamAv -from scenedetect.backends import VideoStreamMoviePy from scenedetect.video_manager import VideoManager +from scenedetect.video_stream import SeekError, VideoStream # Accuracy a framerate is checked to for testing purposes. FRAMERATE_TOLERANCE = 0.001