From 13b4d43e62fb6efea52ef50cef36f445c3c6e47c Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 29 Nov 2021 14:04:56 -0500 Subject: [PATCH 1/3] Moved most settings to pyproject.toml and ran autopep8 --- .flake8 | 1 + .mypy.ini | 33 ------------------ .vscode/extensions.json | 9 ++--- .vscode/settings.json | 13 ++++--- README.md | 2 +- pyproject.toml | 67 +++++++++++++++++++++++++++++++++++++ pyrightconfig.json | 19 ----------- src/AutoControlledWorker.py | 4 +-- src/AutoSplit.py | 6 +++- src/compare.py | 7 +++- src/gen/about.pyi | 2 +- src/gen/design.pyi | 2 +- src/gen/update_checker.pyi | 2 +- 13 files changed, 96 insertions(+), 71 deletions(-) delete mode 100644 .mypy.ini create mode 100644 pyproject.toml delete mode 100644 pyrightconfig.json diff --git a/.flake8 b/.flake8 index a5dfe806..a97455b0 100644 --- a/.flake8 +++ b/.flake8 @@ -12,3 +12,4 @@ per-file-ignores = ; line too long ; mixed case __init__.pyi:F401,E501,N816 +inline-quotes = " diff --git a/.mypy.ini b/.mypy.ini deleted file mode 100644 index 6b23dbbd..00000000 --- a/.mypy.ini +++ /dev/null @@ -1,33 +0,0 @@ -; https://mypy.readthedocs.io/en/stable/command_line.html#disallow-dynamic-typing -[mypy] -show_column_numbers=True -show_error_codes=True -no_color_output=False -color_output=True - -ignore_missing_imports=True -follow_imports=silent - -; Note: exclude is ignored when linting file by file so VSCode will still show errors in typings -; typings are incomplete as we only add types for what we need, cannot disable specific rules per file -; Auto generated -exclude=(typings/|src/gen/) - -; Redundant -disallow_untyped_defs=False -; Doesn't see our cv2 type stubs -warn_return_any=False -; False positives when it's needed for other linting libraries -warn_unused_ignores=False -; Doesn't see imports from src/gen/ -disallow_any_unimported=False -disallow_subclassing_any=False -; False positives with ndarray -disable_error_code=no-untyped-def - -strict=True -; Doesn't see types from some imports -disallow_any_expr=False -disallow_any_decorated=True -disallow_any_explicit=True -warn_unreachable=True diff --git a/.vscode/extensions.json b/.vscode/extensions.json index ca9b77ca..d4464eda 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,11 +1,12 @@ { "recommendations": [ - "ms-python.vscode-pylance", - "ms-python.python", - "sonarsource.sonarlint-vscode", + "bungcip.better-toml", "davidanson.vscode-markdownlint", + "eamodio.gitlens", + "ms-python.python", + "ms-python.vscode-pylance", "shardulm94.trailing-spaces", - "eamodio.gitlens" + "sonarsource.sonarlint-vscode" ], "unwantedRecommendations": [ "ms-pyright.pyright", diff --git a/.vscode/settings.json b/.vscode/settings.json index 2f06bd99..e06aef8d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,9 +13,11 @@ "[python]": { "editor.tabSize": 4, "editor.rulers": [ - 79, - 100, - 120, + 72, // PEP8-17 docstrings + // 79, // PEP8-17 default max + // 88, // Black default + 99, // PEP8-17 acceptable max + 120, // Our hard rule ] }, "editor.formatOnSave": true, @@ -23,9 +25,6 @@ "source.fixAll": true, "source.fixAll.markdownlint": true, }, - // Set to trace when sending error reports to Pylance - // "python.analysis.logLevel": "Trace", - // https://code.visualstudio.com/docs/python/linting#_specific-linters "python.linting.enabled": true, "python.linting.pylintEnabled": true, "python.linting.pylintCategorySeverity.convention": "Warning", @@ -66,5 +65,5 @@ "**/bower_components": true, "**/*.code-search": true, "typings": true, - } + }, } diff --git a/README.md b/README.md index f11ae3e3..9ce40c48 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# LiveSplit AutoSplit +# LiveSplit AutoSplit [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) Easy to use image comparison based auto splitter for speedrunning on console or PC. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e20af376 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,67 @@ +# https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file +[tool.black] +line-length = 120 +# Auto generated +force-exclude = "src/gen/.*\\.py$" + +# https://github.com/hhatto/autopep8#usage +# https://github.com/hhatto/autopep8#more-advanced-usage +[tool.autopep8] +max_line_length = 120 +recursive = true +aggressive = 3 + +# https://github.com/microsoft/pyright/blob/main/docs/configuration.md#sample-pyprojecttoml-file +[tool.pyright] +pythonPlatform = "Windows" +typeCheckingMode = "strict" +ignore = [ + # Auto generated + "src/gen/", + "typings/", +] +reportMissingTypeStubs = "information" +# False positives with TYPE_CHECKING +reportImportCycles = "information" +# PyQt .connect +reportFunctionMemberAccess = "information" +# Extra runtime safety +reportUnnecessaryComparison = "warning" +# Flake8 does a better job +reportUnusedImport = "none" +# numpy has way too many complex types that triggers this +reportUnknownMemberType = "none" + +# https://mypy.readthedocs.io/en/stable/command_line.html#disallow-dynamic-typing +[tool.mypy] +show_column_numbers = true +show_error_codes = true +no_color_output = false +color_output = true + +ignore_missing_imports = true +follow_imports = "silent" + +# Note: exclude is ignored when linting file by file so VSCode will still show errors in typings +# typings are incomplete as we only add types for what we need, cannot disable specific rules per file +# Auto generated +exclude = "(typings/|src/gen/)" + +# Redundant +disallow_untyped_defs = false +# Doesn't see our cv2 type stubs +warn_return_any = false +# False positives when it's needed for other linting libraries +warn_unused_ignores = false +# Doesn't see imports from src/gen/ +disallow_any_unimported = false +disallow_subclassing_any = false +# False positives with ndarray +disable_error_code = "no-untyped-def" + +strict = true +# Doesn't see types from some imports +disallow_any_expr = false +disallow_any_decorated = true +disallow_any_explicit = true +warn_unreachable = true diff --git a/pyrightconfig.json b/pyrightconfig.json deleted file mode 100644 index 5aaa8948..00000000 --- a/pyrightconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "typeCheckingMode": "strict", - // Auto generated - "ignore": [ - // "src/gen/", - "typings/", - ], - "reportMissingTypeStubs": "information", - // False positives with TYPE_CHECKING - "reportImportCycles": "information", - // PyQt .connect - "reportFunctionMemberAccess": "information", - // Extra runtime safety - "reportUnnecessaryComparison": "warning", - // Flake8 does a better job - "reportUnusedImport": "none", - // numpy has way too many complex types that triggers this - "reportUnknownMemberType": "none", -} diff --git a/src/AutoControlledWorker.py b/src/AutoControlledWorker.py index fef30676..a29c0910 100644 --- a/src/AutoControlledWorker.py +++ b/src/AutoControlledWorker.py @@ -25,9 +25,9 @@ def run(self): if line == 'kill': self.autosplit.closeEvent() break - elif line == 'start': + if line == 'start': self.autosplit.startAutoSplitter() - elif line == 'split' or line == 'skip': + elif line in ('split', 'skip'): self.autosplit.startSkipSplit() elif line == 'undo': self.autosplit.startUndoSplit() diff --git a/src/AutoSplit.py b/src/AutoSplit.py index b35b5c2a..b8f5e01c 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -305,7 +305,11 @@ def loadStartImage(self, started_by_button: bool = False, wait_for_delay: bool = error_messages.noKeywordImageError('start_auto_splitter') return - if self.start_image_name is not None and (not self.splitLineEdit.text() or not self.resetLineEdit.text() or not self.pausehotkeyLineEdit.text()) and not self.is_auto_controlled: + if self.start_image_name is not None \ + and not self.is_auto_controlled \ + and (not self.splitLineEdit.text() + or not self.resetLineEdit.text() + or not self.pausehotkeyLineEdit.text()): error_messages.loadStartImageError() return diff --git a/src/compare.py b/src/compare.py index c2cd9243..b1c89fd8 100644 --- a/src/compare.py +++ b/src/compare.py @@ -12,7 +12,12 @@ ranges = [0, MAXRANGE, 0, MAXRANGE, 0, MAXRANGE] -def compareImage(comparisonMethod: int, image: Optional[cv2.ndarray], capture: Optional[cv2.ndarray], mask: Optional[cv2.ndarray] = None): +def compareImage( + comparisonMethod: int, + image: Optional[cv2.ndarray], + capture: Optional[cv2.ndarray], + mask: Optional[cv2.ndarray] = None +): if image is None or capture is None: return 0.0 if comparisonMethod == 0: diff --git a/src/gen/about.pyi b/src/gen/about.pyi index 786170bc..c686e0ea 100644 --- a/src/gen/about.pyi +++ b/src/gen/about.pyi @@ -1,6 +1,6 @@ from PyQt6.QtWidgets import QWidget -class Ui_aboutAutoSplitWidget(object): +class Ui_aboutAutoSplitWidget(): def setupUi(self, aboutAutoSplitWidget: QWidget) -> None: ... diff --git a/src/gen/design.pyi b/src/gen/design.pyi index d999d224..6d951853 100644 --- a/src/gen/design.pyi +++ b/src/gen/design.pyi @@ -1,6 +1,6 @@ from PyQt6.QtWidgets import QMainWindow -class Ui_MainWindow(object): +class Ui_MainWindow(): def setupUi(self, MainWindow: QMainWindow) -> None: ... diff --git a/src/gen/update_checker.pyi b/src/gen/update_checker.pyi index c3680370..027afe46 100644 --- a/src/gen/update_checker.pyi +++ b/src/gen/update_checker.pyi @@ -1,6 +1,6 @@ from PyQt6.QtWidgets import QWidget -class Ui_UpdateChecker(object): +class Ui_UpdateChecker(): def setupUi(self, UpdateChecker: QWidget) -> None: ... From 9840fd9878fd528ae17a39303a69d9e1808ad405 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 29 Nov 2021 16:11:03 -0500 Subject: [PATCH 2/3] Removed mypy and Fixed import-outside-toplevel --- .flake8 | 4 +- .pylintrc | 23 ------------ .vscode/settings.json | 3 +- pyproject.toml | 79 +++++++++++++++++++++++++--------------- scripts/install.bat | 2 - scripts/lint.ps1 | 9 ++++- scripts/requirements.txt | 7 ++-- src/AutoSplit.py | 67 ++++++++++++++++------------------ src/error_messages.py | 3 +- src/hotkeys.py | 10 ++--- src/settings_file.py | 8 ++-- 11 files changed, 106 insertions(+), 109 deletions(-) delete mode 100644 .pylintrc diff --git a/.flake8 b/.flake8 index a97455b0..e6aff2b6 100644 --- a/.flake8 +++ b/.flake8 @@ -1,8 +1,6 @@ [flake8] color=always max-line-length=120 -; TODO: Bring WAY down -max-complexity=55 ; Auto generated exclude=src/gen/ ; TODO: We want to configure this @@ -12,4 +10,6 @@ per-file-ignores = ; line too long ; mixed case __init__.pyi:F401,E501,N816 +; TODO: Bring WAY down +max-complexity=55 inline-quotes = " diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 8bd9be7a..00000000 --- a/.pylintrc +++ /dev/null @@ -1,23 +0,0 @@ -; http://pylint-messages.wikidot.com/all-codes -[MASTER] -max-line-length=120 -ignore-paths= - ; Haven't looked into disabling specific rules per file - ^typings/.*$, - ; Auto generated - ^src/gen/.*$ -disable= - missing-docstring, - ; TODO: We want to configure this - ; https://pylint.pycqa.org/en/latest/user_guide/options.html#naming-styles - invalid-name, - ; We group imports - wrong-import-position, - ; Already taken care of and grayed out. Also conflicts with Pylance reportIncompatibleMethodOverride - unused-argument, - ; Already taken care of by Flake8 - unused-import, -extension-pkg-allow-list=PyQt6,win32ui - -[TYPECHECK] -generated-members=cv2 diff --git a/.vscode/settings.json b/.vscode/settings.json index e06aef8d..5336311a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,7 +31,8 @@ "python.linting.pylintCategorySeverity.refactor": "Warning", "python.linting.flake8Enabled": true, "python.linting.flake8CategorySeverity.E": "Warning", - "python.linting.mypyEnabled": true, + // PyRight obsoletes mypy + "python.linting.mypyEnabled": false, // Is already wrapped by Flake8, prospector and pylama "python.linting.pycodestyleEnabled": false, // Just another wrapper, use Flake8 OR this diff --git a/pyproject.toml b/pyproject.toml index e20af376..2bd88316 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,36 +32,55 @@ reportUnusedImport = "none" # numpy has way too many complex types that triggers this reportUnknownMemberType = "none" -# https://mypy.readthedocs.io/en/stable/command_line.html#disallow-dynamic-typing -[tool.mypy] -show_column_numbers = true -show_error_codes = true -no_color_output = false -color_output = true - -ignore_missing_imports = true -follow_imports = "silent" +# https://github.com/PyCQA/pylint/blob/main/examples/pylintrc +# https://pylint.pycqa.org/en/latest/technical_reference/features.html +[tool.pylint.MASTER] +# https://pylint.pycqa.org/en/latest/technical_reference/extensions.html +load-plugins = [ + "pylint.extensions.broad_try_clause", + "pylint.extensions.code_style", + "pylint.extensions.emptystring", + "pylint.extensions.comparetozero", + "pylint.extensions.comparison_placement", + "pylint.extensions.confusing_elif", + "pylint.extensions.for_any_all", + "pylint.extensions.consider_ternary_expression", + "pylint.extensions.bad_builtin", + "pylint.extensions.mccabe", + "pylint.extensions.docstyle", + "pylint.extensions.check_elif", + "pylint.extensions.redefined_variable_type", + "pylint.extensions.overlapping_exceptions", + "pylint.extensions.docparams", + "pylint.extensions.empty_comment", + "pylint.extensions.set_membership", + "pylint.extensions.typing", + "pylint.extensions.while_used", +] +ignore-paths = [ + # Haven't looked into disabling specific rules per file + "^typings/.*$", + # Auto generated + "^src/gen/.*$", +] +extension-pkg-allow-list = ["PyQt6", "win32ui"] -# Note: exclude is ignored when linting file by file so VSCode will still show errors in typings -# typings are incomplete as we only add types for what we need, cannot disable specific rules per file -# Auto generated -exclude = "(typings/|src/gen/)" +[tool.pylint.FORMAT] +max-line-length = 120 -# Redundant -disallow_untyped_defs = false -# Doesn't see our cv2 type stubs -warn_return_any = false -# False positives when it's needed for other linting libraries -warn_unused_ignores = false -# Doesn't see imports from src/gen/ -disallow_any_unimported = false -disallow_subclassing_any = false -# False positives with ndarray -disable_error_code = "no-untyped-def" +[tool.pylint.'MESSAGES CONTROL'] +disable = [ + "missing-docstring", + # TODO: We want to configure this + # https://pylint.pycqa.org/en/latest/user_guide/options.html#naming-styles + "invalid-name", + # We group imports + "wrong-import-position", + # Already taken care of and grayed out. Also conflicts with Pylance reportIncompatibleMethodOverride + "unused-argument", + # Already taken care of by Flake8 + "unused-import", +] -strict = true -# Doesn't see types from some imports -disallow_any_expr = false -disallow_any_decorated = true -disallow_any_explicit = true -warn_unreachable = true +[tool.pylint.TYPECHECK] +generated-members = "cv2" diff --git a/scripts/install.bat b/scripts/install.bat index a824c7b4..b32ff99e 100644 --- a/scripts/install.bat +++ b/scripts/install.bat @@ -1,6 +1,4 @@ py -3.9 -m pip install wheel py -3.9 -m pip install -r "%~p0requirements.txt" -@REM https://github.com/python/mypy/issues/10600 --non-interactive may still have issues -mypy --install-types --non-interactive npm install -g pyright CALL "%~p0compile_resources.bat" diff --git a/scripts/lint.ps1 b/scripts/lint.ps1 index 298956ce..be2eaa1c 100644 --- a/scripts/lint.ps1 +++ b/scripts/lint.ps1 @@ -1,8 +1,13 @@ +echo "`nRunning Pylint..." pylint --score=n --output-format=text,colorized $(git ls-files '**/*.py*') # pylint --reports=y --output-format=text,colorized $(git ls-files '**/*.py*') -mypy . -# mypy --pretty src + +echo "`nRunning Pyright..." pyright + +echo "`nRunning Bandit..." bandit -f custom --silent --severity-level medium -r . # bandit -n 1 --severity-level medium -r src + +echo "`nRunning Flake8..." flake8 diff --git a/scripts/requirements.txt b/scripts/requirements.txt index 71cf1231..e68458c6 100644 --- a/scripts/requirements.txt +++ b/scripts/requirements.txt @@ -11,7 +11,7 @@ # # Dependencies: numpy>=1.22.0rc1 -opencv-python<=4.5.3.56 # https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/110 +opencv-python<=4.5.3.56 # https://github.com/pyinstaller/pyinstaller-hooks-contrib/issues/110 PyQt6 Pillow ImageHash @@ -21,10 +21,11 @@ keyboard packaging pyautogui PySide6 +requests +# Linting flake8 -mypy +type-requests pylint -requests # # Comment this out if you don't want to build AutoSplit.exe: PyInstaller diff --git a/src/AutoSplit.py b/src/AutoSplit.py index b8f5e01c..09ca69a9 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -26,12 +26,15 @@ from win32con import MAXBYTE import error_messages +import settings_file as settings import split_parser from AutoControlledWorker import AutoControlledWorker +from capture_windows import capture_region, Rect from gen import design +from hotkeys import send_command, afterSettingHotkey, setSplitHotkey, setResetHotkey, setSkipSplitHotkey, \ + setUndoSplitHotkey, setPauseHotkey from menu_bar import AboutWidget, VERSION, UpdateCheckerWidget, about, viewHelp, checkForUpdates -from capture_windows import capture_region, Rect -from settings_file import auto_split_directory +from screen_region import selectRegion, selectWindow, alignRegion, validateBeforeComparison from split_parser import BELOW_FLAG, DUMMY_FLAG, PAUSE_FLAG from compare import checkIfImageHasTransparency, compareImage @@ -49,14 +52,6 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): - # pylint: disable=import-outside-toplevel - from hotkeys import send_command - from settings_file import saveSettings, saveSettingsAs, loadSettings, haveSettingsChanged, getSaveSettingsValues, \ - loadPyQtSettings - from screen_region import selectRegion, selectWindow, alignRegion, validateBeforeComparison - from hotkeys import afterSettingHotkey, beforeSettingHotkey, setSplitHotkey, setResetHotkey, setSkipSplitHotkey, \ - setUndoSplitHotkey, setPauseHotkey - myappid = f'Toufool.AutoSplit.v{VERSION}' ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) @@ -137,15 +132,15 @@ def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.setupUi(self) - self.loadPyQtSettings() + settings.loadPyQtSettings(self) # close all processes when closing window self.actionView_Help.triggered.connect(viewHelp) self.actionAbout.triggered.connect(lambda: about(self)) self.actionCheck_for_Updates.triggered.connect(lambda: checkForUpdates(self)) - self.actionSave_Settings.triggered.connect(self.saveSettings) - self.actionSave_Settings_As.triggered.connect(self.saveSettingsAs) - self.actionLoad_Settings.triggered.connect(self.loadSettings) + self.actionSave_Settings.triggered.connect(lambda: settings.saveSettings) + self.actionSave_Settings_As.triggered.connect(lambda: settings.saveSettingsAs) + self.actionLoad_Settings.triggered.connect(lambda: settings.loadSettings) # disable buttons upon open self.undosplitButton.setEnabled(False) @@ -181,20 +176,20 @@ def __init__(self, parent: Optional[QWidget] = None): # Connecting button clicks to functions self.browseButton.clicked.connect(self.browse) - self.selectregionButton.clicked.connect(self.selectRegion) + self.selectregionButton.clicked.connect(lambda: selectRegion(self)) self.takescreenshotButton.clicked.connect(self.takeScreenshot) self.startautosplitterButton.clicked.connect(self.autoSplitter) self.checkfpsButton.clicked.connect(self.checkFPS) self.resetButton.clicked.connect(self.reset) self.skipsplitButton.clicked.connect(self.skipSplit) self.undosplitButton.clicked.connect(self.undoSplit) - self.setsplithotkeyButton.clicked.connect(self.setSplitHotkey) - self.setresethotkeyButton.clicked.connect(self.setResetHotkey) - self.setskipsplithotkeyButton.clicked.connect(self.setSkipSplitHotkey) - self.setundosplithotkeyButton.clicked.connect(self.setUndoSplitHotkey) - self.setpausehotkeyButton.clicked.connect(self.setPauseHotkey) - self.alignregionButton.clicked.connect(self.alignRegion) - self.selectwindowButton.clicked.connect(self.selectWindow) + self.setsplithotkeyButton.clicked.connect(lambda: setSplitHotkey(self)) + self.setresethotkeyButton.clicked.connect(lambda: setResetHotkey(self)) + self.setskipsplithotkeyButton.clicked.connect(lambda: setSkipSplitHotkey(self)) + self.setundosplithotkeyButton.clicked.connect(lambda: setUndoSplitHotkey(self)) + self.setpausehotkeyButton.clicked.connect(lambda: setPauseHotkey(self)) + self.alignregionButton.clicked.connect(lambda: alignRegion(self)) + self.selectwindowButton.clicked.connect(lambda: selectWindow(self)) self.startImageReloadButton.clicked.connect(lambda: self.loadStartImage(True, True)) # update x, y, width, and height when changing the value of these spinbox's are changed @@ -205,7 +200,7 @@ def __init__(self, parent: Optional[QWidget] = None): # connect signals to functions self.updateCurrentSplitImage.connect(self.updateSplitImageGUI) - self.afterSettingHotkeySignal.connect(self.afterSettingHotkey) + self.afterSettingHotkeySignal.connect(lambda: afterSettingHotkey(self)) self.startAutoSplitterSignal.connect(self.autoSplitter) self.resetSignal.connect(self.reset) self.skipSplitSignal.connect(self.skipSplit) @@ -237,7 +232,7 @@ def browse(self): new_split_image_directory = QFileDialog.getExistingDirectory( self, 'Select Split Image Directory', - os.path.join(self.split_image_directory or auto_split_directory, "..")) + os.path.join(self.split_image_directory or settings.auto_split_directory, "..")) # If the user doesn't select a folder, it defaults to "". if new_split_image_directory: @@ -287,7 +282,7 @@ def loadStartImage(self, started_by_button: bool = False, wait_for_delay: bool = self.startImageLabel.setText("Start image: not found") QApplication.processEvents() - if not self.validateBeforeComparison(started_by_button): + if not validateBeforeComparison(self, started_by_button): return self.start_image_name = None @@ -410,7 +405,7 @@ def startImageFunction(self): and start_image_similarity < start_image_threshold) \ or (start_image_similarity >= start_image_threshold and not start_image_flags & BELOW_FLAG): def split(): - self.send_command("start") + send_command(self, "start") # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(1 / self.fpslimitSpinBox.value()) # type: ignore self.startAutoSplitter() @@ -462,7 +457,7 @@ def updateSplitImageGUI(self, qImage: QtGui.QImage): self.currentSplitImage.setPixmap(pix) def takeScreenshot(self): - if not self.validateBeforeComparison(check_empty_directory=False): + if not validateBeforeComparison(self, check_empty_directory=False): return take_screenshot_filename = '001_SplitImage' @@ -486,7 +481,7 @@ def takeScreenshot(self): # check max FPS button connects here. # TODO: Average on all images and check for transparency (cv2.COLOR_BGRA2RGB and cv2.IMREAD_UNCHANGED) def checkFPS(self): - if not self.validateBeforeComparison(): + if not validateBeforeComparison(self): return split_image_filenames = os.listdir(self.split_image_directory) @@ -494,9 +489,9 @@ def checkFPS(self): cv2.imread(os.path.join(self.split_image_directory, image), cv2.IMREAD_COLOR) for image in split_image_filenames] - for image in split_images: + for i, image in enumerate(split_images): if image is None: - error_messages.imageTypeError(image) + error_messages.imageTypeError(split_image_filenames[i]) return # grab first image in the split image folder @@ -609,7 +604,7 @@ def checkForReset(self): return False def autoSplitter(self): - if not self.validateBeforeComparison(): + if not validateBeforeComparison(self): self.guiChangesOnReset() return @@ -728,7 +723,7 @@ def autoSplitter(self): capture, self.reset_mask) if reset_similarity >= self.reset_image_threshold: - self.send_command("reset") + send_command(self, "reset") self.reset() if self.checkForReset(): @@ -829,7 +824,7 @@ def autoSplitter(self): capture, self.reset_mask) if reset_similarity >= self.reset_image_threshold: - self.send_command("reset") + send_command(self, "reset") self.reset() continue # Email sent to pyqt@riverbankcomputing.com @@ -838,7 +833,7 @@ def autoSplitter(self): self.waiting_for_split_delay = False # if {p} flag hit pause key, otherwise hit split hotkey - self.send_command("pause" if self.flags & PAUSE_FLAG == PAUSE_FLAG else "split") + send_command(self, "pause" if self.flags & PAUSE_FLAG == PAUSE_FLAG else "split") # if loop check box is checked and its the last split, go to first split. # else go to the next split image. @@ -895,7 +890,7 @@ def autoSplitter(self): capture, self.reset_mask) if reset_similarity >= self.reset_image_threshold: - self.send_command("reset") + send_command(self, "reset") self.reset() continue # Email sent to pyqt@riverbankcomputing.com @@ -1112,7 +1107,7 @@ def exitProgram(): if a0 is None: exitProgram() - if self.haveSettingsChanged(): + if settings.haveSettingsChanged(self): # Give a different warning if there was never a settings file that was loaded successfully, # and "save as" instead of "save". settings_file_name = "Untitled" \ diff --git a/src/error_messages.py b/src/error_messages.py index 740ee14f..f0c105c5 100644 --- a/src/error_messages.py +++ b/src/error_messages.py @@ -95,7 +95,8 @@ def checkForUpdatesError(): def loadStartImageError(): - setTextMessage("Start Image found, but cannot be loaded unless Start, Reset, and Pause hotkeys are set. Please set these hotkeys, and then click the Reload Start Image button.") + setTextMessage("Start Image found, but cannot be loaded unless Start, Reset, and Pause hotkeys are set. " + "Please set these hotkeys, and then click the Reload Start Image button.") def stdinLostError(): diff --git a/src/hotkeys.py b/src/hotkeys.py index 56d773b3..a332c37b 100644 --- a/src/hotkeys.py +++ b/src/hotkeys.py @@ -132,7 +132,7 @@ def setSplitHotkey(autosplit: AutoSplit): autosplit.setsplithotkeyButton.setText('Press a key...') # disable some buttons - autosplit.beforeSettingHotkey() + beforeSettingHotkey(autosplit) # new thread points to callback. this thread is needed or GUI will freeze # while the program waits for user input on the hotkey @@ -190,7 +190,7 @@ def callback(hotkey: Callable[[], None]): def setResetHotkey(autosplit: AutoSplit): autosplit.setresethotkeyButton.setText('Press a key...') - autosplit.beforeSettingHotkey() + beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): try: @@ -221,7 +221,7 @@ def callback(hotkey: Callable[[], None]): def setSkipSplitHotkey(autosplit: AutoSplit): autosplit.setskipsplithotkeyButton.setText('Press a key...') - autosplit.beforeSettingHotkey() + beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): try: @@ -252,7 +252,7 @@ def callback(hotkey: Callable[[], None]): def setUndoSplitHotkey(autosplit: AutoSplit): autosplit.setundosplithotkeyButton.setText('Press a key...') - autosplit.beforeSettingHotkey() + beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): try: @@ -283,7 +283,7 @@ def callback(hotkey: Callable[[], None]): def setPauseHotkey(autosplit: AutoSplit): autosplit.setpausehotkeyButton.setText('Press a key...') - autosplit.beforeSettingHotkey() + beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): try: diff --git a/src/settings_file.py b/src/settings_file.py index 0391405e..a2c6c087 100644 --- a/src/settings_file.py +++ b/src/settings_file.py @@ -54,7 +54,7 @@ def getSaveSettingsValues(autosplit: AutoSplit): def haveSettingsChanged(autosplit: AutoSplit): - autosplit.getSaveSettingsValues() + getSaveSettingsValues(autosplit) current_save_settings = [ autosplit.split_image_directory, autosplit.similarity_threshold, @@ -85,9 +85,9 @@ def haveSettingsChanged(autosplit: AutoSplit): def saveSettings(autosplit: AutoSplit): if not autosplit.last_successfully_loaded_settings_file_path: - autosplit.saveSettingsAs() + saveSettingsAs(autosplit) else: - autosplit.getSaveSettingsValues() + getSaveSettingsValues(autosplit) autosplit.last_saved_settings = [ autosplit.split_image_directory, autosplit.similarity_threshold, @@ -126,7 +126,7 @@ def saveSettingsAs(autosplit: AutoSplit): if not autosplit.save_settings_file_path: return - autosplit.getSaveSettingsValues() + getSaveSettingsValues(autosplit) autosplit.last_saved_settings = [ autosplit.split_image_directory, autosplit.similarity_threshold, From feda2ad66972d3f20e288c8e007ee74913e9d4b2 Mon Sep 17 00:00:00 2001 From: Avasam Date: Mon, 29 Nov 2021 18:54:25 -0500 Subject: [PATCH 3/3] Double quotes and pylint complete linting --- pyproject.toml | 21 ++++-- scripts/lint.ps1 | 8 +- src/AutoControlledWorker.py | 15 ++-- src/AutoSplit.py | 146 +++++++++++++++++++++--------------- src/capture_windows.py | 6 +- src/compare.py | 2 +- src/error_messages.py | 10 +-- src/hotkeys.py | 61 ++++++++------- src/menu_bar.py | 6 +- src/screen_region.py | 27 ++++--- src/settings_file.py | 16 ++-- src/split_parser.py | 54 ++++++------- 12 files changed, 205 insertions(+), 167 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2bd88316..1b9a5ab6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,25 +37,28 @@ reportUnknownMemberType = "none" [tool.pylint.MASTER] # https://pylint.pycqa.org/en/latest/technical_reference/extensions.html load-plugins = [ - "pylint.extensions.broad_try_clause", - "pylint.extensions.code_style", "pylint.extensions.emptystring", - "pylint.extensions.comparetozero", - "pylint.extensions.comparison_placement", "pylint.extensions.confusing_elif", - "pylint.extensions.for_any_all", "pylint.extensions.consider_ternary_expression", "pylint.extensions.bad_builtin", "pylint.extensions.mccabe", - "pylint.extensions.docstyle", "pylint.extensions.check_elif", "pylint.extensions.redefined_variable_type", "pylint.extensions.overlapping_exceptions", - "pylint.extensions.docparams", "pylint.extensions.empty_comment", "pylint.extensions.set_membership", "pylint.extensions.typing", - "pylint.extensions.while_used", + # TODO: Maybe later + # "pylint.extensions.docparams", + # Not wanted/needed + # "pylint.extensions.broad_try_clause", + # "pylint.extensions.code_style", + # "pylint.extensions.comparetozero", + # "pylint.extensions.docstyle", + # "pylint.extensions.while_used", + # Didn't work + # "pylint.extensions.comparison_placement", + # "pylint.extensions.for_any_all", ] ignore-paths = [ # Haven't looked into disabling specific rules per file @@ -63,6 +66,8 @@ ignore-paths = [ # Auto generated "^src/gen/.*$", ] +# No need to mention the fixmes +disable=["fixme"] extension-pkg-allow-list = ["PyQt6", "win32ui"] [tool.pylint.FORMAT] diff --git a/scripts/lint.ps1 b/scripts/lint.ps1 index be2eaa1c..5d720fbe 100644 --- a/scripts/lint.ps1 +++ b/scripts/lint.ps1 @@ -1,10 +1,10 @@ -echo "`nRunning Pylint..." -pylint --score=n --output-format=text,colorized $(git ls-files '**/*.py*') -# pylint --reports=y --output-format=text,colorized $(git ls-files '**/*.py*') - echo "`nRunning Pyright..." pyright +echo "`nRunning Pylint..." +pylint --score=n --output-format=colorized $(git ls-files '**/*.py*') +# pylint --reports=y --output-format=colorized $(git ls-files '**/*.py*') + echo "`nRunning Bandit..." bandit -f custom --silent --severity-level medium -r . # bandit -n 1 --severity-level medium -r src diff --git a/src/AutoControlledWorker.py b/src/AutoControlledWorker.py index a29c0910..7ef33a0c 100644 --- a/src/AutoControlledWorker.py +++ b/src/AutoControlledWorker.py @@ -5,6 +5,7 @@ from PyQt6 import QtCore import error_messages +import settings_file as settings class AutoControlledWorker(QtCore.QObject): @@ -22,21 +23,21 @@ def run(self): # TODO: "AutoSplit Integration" needs to call this and wait instead of outright killing the app. # TODO: See if we can also get LiveSplit to wait on Exit in "AutoSplit Integration" # For now this can only used in a Development environment - if line == 'kill': + if line == "kill": self.autosplit.closeEvent() break - if line == 'start': + if line == "start": self.autosplit.startAutoSplitter() - elif line in ('split', 'skip'): + elif line in {"split", "skip"}: self.autosplit.startSkipSplit() - elif line == 'undo': + elif line == "undo": self.autosplit.startUndoSplit() - elif line == 'reset': + elif line == "reset": self.autosplit.startReset() - elif line.startswith('settings'): + elif line.startswith("settings"): # Allow for any split character between "settings" and the path self.autosplit.load_settings_file_path = line[9:] - self.autosplit.loadSettings(load_settings_from_livesplit=True) + settings.loadSettings(self.autosplit, load_settings_from_livesplit=True) # TODO: Not yet implemented in AutoSplit Integration # elif line == 'pause': # self.startPause() diff --git a/src/AutoSplit.py b/src/AutoSplit.py index 09ca69a9..100ee5da 100644 --- a/src/AutoSplit.py +++ b/src/AutoSplit.py @@ -7,8 +7,9 @@ # - Externals # - Internals from __future__ import annotations +from collections.abc import Callable from types import FunctionType, TracebackType -from typing import Callable, List, Literal, Optional, Type, Union, cast +from typing import Literal, Optional, Union, cast import sys import os @@ -52,11 +53,11 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): - myappid = f'Toufool.AutoSplit.v{VERSION}' + myappid = f"Toufool.AutoSplit.v{VERSION}" ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid) # Parse command line args - is_auto_controlled = '--auto-controlled' in sys.argv + is_auto_controlled = "--auto-controlled" in sys.argv # Signals updateCurrentSplitImage = QtCore.pyqtSignal(QtGui.QImage) @@ -92,7 +93,7 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): y: int width: int height: int - hwnd_title = '' + hwnd_title = "" group_dummy_splits_undo_skip_setting: Literal[0, 1] loop_setting: Literal[0, 1] auto_start_on_reset_setting: Literal[0, 1] @@ -110,16 +111,16 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): pause_hotkey: Optional[Callable[[], None]] = None # Initialize a few attributes - last_saved_settings: Optional[List[Union[str, float, int]]] = None + last_saved_settings: Optional[list[Union[str, float, int]]] = None save_settings_file_path = "" load_settings_file_path = "" live_image_function_on_open = True - split_image_loop_amount: List[int] = [] + split_image_loop_amount: list[int] = [] split_image_number = 0 loop_number = 1 # Last loaded settings and last successful loaded settings file path to None until we try to load them - last_loaded_settings: Optional[List[Union[str, float, int]]] = None + last_loaded_settings: Optional[list[Union[str, float, int]]] = None last_successfully_loaded_settings_file_path: Optional[str] = None # Automatic timer start @@ -128,6 +129,28 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow): highest_similarity = 0.0 check_start_image_timestamp = 0.0 + # Define all other attributes + setting_check_for_updates_on_open: QtCore.QSettings + imageHasTransparency: bool + start_image_split_below_threshold: bool + waiting_for_split_delay: bool + split_below_threshold: bool + split_image_path: str + split_image_filenames: list[str] + split_image_filenames_including_loops: list[str] + split_image_filenames_and_loop_number: list[tuple[str, int, int]] + split_groups: list[list[int]] + run_start_time: float + similarity: float + reset_image_threshold: float + reset_image_pause_time: float + split_delay: int + flags: int + reset_image: Optional[cv2.ndarray] + reset_mask: Optional[cv2.ndarray] + split_image: cv2.ndarray + image_mask: cv2.ndarray + def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.setupUi(self) @@ -172,7 +195,7 @@ def __init__(self, parent: Optional[QWidget] = None): self.update_auto_control.start() # split image folder line edit text - self.splitimagefolderLineEdit.setText('No Folder Selected') + self.splitimagefolderLineEdit.setText("No Folder Selected") # Connecting button clicks to functions self.browseButton.clicked.connect(self.browse) @@ -219,19 +242,19 @@ def __init__(self, parent: Optional[QWidget] = None): self.last_successfully_loaded_settings_file_path = None if not self.is_auto_controlled: - self.loadSettings(load_settings_on_open=True) + settings.loadSettings(self, load_settings_on_open=True) # FUNCTIONS def getGlobalSettingsValues(self): - self.setting_check_for_updates_on_open = QtCore.QSettings('AutoSplit', 'Check For Updates On Open') + self.setting_check_for_updates_on_open = QtCore.QSettings("AutoSplit", "Check For Updates On Open") # TODO add checkbox for going back to image 1 when resetting. def browse(self): # User selects the file with the split images in it. new_split_image_directory = QFileDialog.getExistingDirectory( self, - 'Select Split Image Directory', + "Select Split Image Directory", os.path.join(self.split_image_directory or settings.auto_split_directory, "..")) # If the user doesn't select a folder, it defaults to "". @@ -278,7 +301,7 @@ def liveImageFunction(self): def loadStartImage(self, started_by_button: bool = False, wait_for_delay: bool = True): self.timerStartImage.stop() - self.currentsplitimagefileLabel.setText(' ') + self.currentsplitimagefileLabel.setText(" ") self.startImageLabel.setText("Start image: not found") QApplication.processEvents() @@ -287,17 +310,17 @@ def loadStartImage(self, started_by_button: bool = False, wait_for_delay: bool = self.start_image_name = None for image in os.listdir(self.split_image_directory): - if 'start_auto_splitter' in image.lower(): + if "start_auto_splitter" in image.lower(): if self.start_image_name is None: self.start_image_name = image else: if started_by_button: - error_messages.multipleKeywordImagesError('start_auto_splitter') + error_messages.multipleKeywordImagesError("start_auto_splitter") return if self.start_image_name is None: if started_by_button: - error_messages.noKeywordImageError('start_auto_splitter') + error_messages.noKeywordImageError("start_auto_splitter") return if self.start_image_name is not None \ @@ -338,8 +361,8 @@ def loadStartImage(self, started_by_button: bool = False, wait_for_delay: bool = if not wait_for_delay and start_image_pause is not None and start_image_pause > 0: self.check_start_image_timestamp = time() + start_image_pause self.startImageLabel.setText("Start image: paused") - self.highestsimilarityLabel.setText(' ') - self.currentsimilaritythresholdnumberLabel.setText(' ') + self.highestsimilarityLabel.setText(" ") + self.currentsimilaritythresholdnumberLabel.setText(" ") else: self.check_start_image_timestamp = 0.0 self.startImageLabel.setText("Start image: ready") @@ -359,7 +382,7 @@ def startImageFunction(self): or (not self.splitLineEdit.text() and not self.is_auto_controlled): pause_time_left = f"{self.check_start_image_timestamp - time():.1f}" self.currentSplitImage.setText( - f'None\n (Paused before loading Start Image).\n {pause_time_left} sec remaining') + f"None\n (Paused before loading Start Image).\n {pause_time_left} sec remaining") return if self.check_start_image_timestamp > 0: @@ -382,7 +405,7 @@ def startImageFunction(self): # Show live similarity if the checkbox is checked self.livesimilarityLabel.setText(str(start_image_similarity)[:4] if self.showlivesimilarityCheckBox.isChecked() - else ' ') + else " ") # If the similarity becomes higher than highest similarity, set it as such. if start_image_similarity > self.highest_similarity: @@ -391,7 +414,7 @@ def startImageFunction(self): # Show live highest similarity if the checkbox is checked self.highestsimilarityLabel.setText(str(self.highest_similarity)[:4] if self.showlivesimilarityCheckBox.isChecked() - else ' ') + else " ") # If the {b} flag is set, let similarity go above threshold first, then split on similarity below threshold # Otherwise just split when similarity goes above threshold @@ -419,7 +442,7 @@ def split(): delay_start_time = time() while time() - delay_start_time < (start_image_delay / 1000): delay_time_left = round((start_image_delay / 1000) - (time() - delay_start_time), 1) - self.currentSplitImage.setText(f'Delayed Before Starting:\n {delay_time_left} sec remaining') + self.currentSplitImage.setText(f"Delayed Before Starting:\n {delay_time_left} sec remaining") # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(1) # type: ignore @@ -459,7 +482,7 @@ def updateSplitImageGUI(self, qImage: QtGui.QImage): def takeScreenshot(self): if not validateBeforeComparison(self, check_empty_directory=False): return - take_screenshot_filename = '001_SplitImage' + take_screenshot_filename = "001_SplitImage" # check if file exists and rename it if it does # Below starts the FileNameNumber at #001 up to #999. After that it will go to 1000, @@ -522,7 +545,7 @@ def is_current_split_out_of_range(self): def undoSplit(self): # Can't undo until timer is started # or Undoing past the first image - if self.startautosplitterButton.text() == 'Start Auto Splitter' \ + if self.startautosplitterButton.text() == "Start Auto Splitter" \ or "Delayed Split" in self.currentSplitImage.text() \ or (not self.undosplitButton.isEnabled() and not self.is_auto_controlled) \ or self.is_current_split_out_of_range(): @@ -544,7 +567,7 @@ def undoSplit(self): def skipSplit(self): # Can't skip or split until timer is started # or Splitting/skipping when there are no images left - if self.startautosplitterButton.text() == 'Start Auto Splitter' \ + if self.startautosplitterButton.text() == "Start Auto Splitter" \ or "Delayed Split" in self.currentSplitImage.text() \ or (not self.skipsplitButton.isEnabled() and not self.is_auto_controlled) \ or self.is_current_split_out_of_range(): @@ -568,12 +591,12 @@ def skipSplit(self): def reset(self): # When the reset button or hotkey is pressed, it will change this text, # which will trigger in the autoSplitter function, if running, to abort and change GUI. - self.startautosplitterButton.setText('Start Auto Splitter') + self.startautosplitterButton.setText("Start Auto Splitter") # Functions for the hotkeys to return to the main thread from signals and start their corresponding functions def startAutoSplitter(self): # If the auto splitter is already running or the button is disabled, don't emit the signal to start it. - if self.startautosplitterButton.text() == 'Running...' \ + if self.startautosplitterButton.text() == "Running..." \ or (not self.startautosplitterButton.isEnabled() and not self.is_auto_controlled): return @@ -595,7 +618,7 @@ def startPause(self): self.pauseSignal.emit() def checkForReset(self): - if self.startautosplitterButton.text() == 'Start Auto Splitter': + if self.startautosplitterButton.text() == "Start Auto Splitter": if self.autostartonresetCheckBox.isChecked(): self.startAutoSplitterSignal.emit() else: @@ -633,7 +656,7 @@ def autoSplitter(self): in self.split_image_filenames] # construct a list of filenames, each filename copied with # of loops it has. - self.split_image_filenames_including_loops: List[str] = [] + self.split_image_filenames_including_loops: list[str] = [] for i, filename in enumerate(self.split_image_filenames): current_loop = 1 while split_image_loop_amount[i] >= current_loop: @@ -641,7 +664,7 @@ def autoSplitter(self): current_loop = current_loop + 1 # construct a list of corresponding loop number to the filenames - loop_numbers: List[int] = [] + loop_numbers: list[int] = [] loop_count = 1 for i, filename in enumerate(self.split_image_filenames_including_loops): if i == 0: @@ -660,9 +683,9 @@ def autoSplitter(self): ] # construct groups of splits if needed - self.split_groups: List[List[int]] = [] + self.split_groups: list[list[int]] = [] if self.groupDummySplitsCheckBox.isChecked(): - current_group: List[int] = [] + current_group: list[int] = [] self.split_groups.append(current_group) for i, image in enumerate(self.split_image_filenames_including_loops): @@ -745,7 +768,7 @@ def autoSplitter(self): if self.showlivesimilarityCheckBox.isChecked(): self.livesimilarityLabel.setText(str(self.similarity)[:4]) else: - self.livesimilarityLabel.setText(' ') + self.livesimilarityLabel.setText(" ") # if the similarity becomes higher than highest similarity, set it as such. if self.similarity > self.highest_similarity: @@ -755,7 +778,7 @@ def autoSplitter(self): if self.showhighestsimilarityCheckBox.isChecked(): self.highestsimilarityLabel.setText(str(self.highest_similarity)[:4]) else: - self.highestsimilarityLabel.setText(' ') + self.highestsimilarityLabel.setText(" ") if not self.is_auto_controlled: # if its the last split image or can't skip due to grouped dummy splits, disable skip split button @@ -771,14 +794,14 @@ def autoSplitter(self): # then split on similarity below threshold. # if no b flag, just split when similarity goes above threshold. if not self.waiting_for_split_delay: - if self.flags & BELOW_FLAG == BELOW_FLAG and not self.split_below_threshold: - if self.similarity >= self.similarity_threshold: + if self.flags & BELOW_FLAG == BELOW_FLAG: + if self.split_below_threshold: + if self.similarity < self.similarity_threshold: + self.split_below_threshold = False + break + elif self.similarity >= self.similarity_threshold: self.split_below_threshold = True continue - elif self.flags & BELOW_FLAG == BELOW_FLAG and self.split_below_threshold: - if self.similarity < self.similarity_threshold: - self.split_below_threshold = False - break elif self.similarity >= self.similarity_threshold: break @@ -800,14 +823,14 @@ def autoSplitter(self): self.waiting_for_split_delay = True self.undosplitButton.setEnabled(False) self.skipsplitButton.setEnabled(False) - self.currentsplitimagefileLabel.setText(' ') + self.currentsplitimagefileLabel.setText(" ") # check for reset while delayed and display a counter of the remaining split delay time delay_start_time = time() split_delay = self.split_delay / 1000 while time() - delay_start_time < split_delay: delay_time_left = round(split_delay - (time() - delay_start_time), 1) - self.currentSplitImage.setText(f'Delayed Split: {delay_time_left} sec remaining') + self.currentSplitImage.setText(f"Delayed Split: {delay_time_left} sec remaining") # check for reset if not windowText: self.reset() @@ -863,12 +886,12 @@ def autoSplitter(self): # A pause loop to check if the user presses skip split, undo split, or reset here. # Also updates the current split image text, counting down the time until the next split image if self.pause > 0: - self.currentsplitimagefileLabel.setText(' ') - self.imageloopLabel.setText('Image Loop: -') + self.currentsplitimagefileLabel.setText(" ") + self.imageloopLabel.setText("Image Loop: -") pause_start_time = time() while time() - pause_start_time < self.pause: pause_time_left = round(self.pause - (time() - pause_start_time), 1) - self.currentSplitImage.setText(f'None (Paused). {pause_time_left} sec remaining') + self.currentSplitImage.setText(f"None (Paused). {pause_time_left} sec remaining") # check for reset if not windowText: @@ -901,7 +924,7 @@ def autoSplitter(self): def guiChangesOnStart(self): self.timerStartImage.stop() - self.startautosplitterButton.setText('Running...') + self.startautosplitterButton.setText("Running...") self.browseButton.setEnabled(False) self.groupDummySplitsCheckBox.setEnabled(False) self.startImageReloadButton.setEnabled(False) @@ -920,13 +943,13 @@ def guiChangesOnStart(self): QApplication.processEvents() def guiChangesOnReset(self): - self.startautosplitterButton.setText('Start Auto Splitter') - self.imageloopLabel.setText('Image Loop: -') - self.currentSplitImage.setText(' ') - self.currentsplitimagefileLabel.setText(' ') - self.livesimilarityLabel.setText(' ') - self.highestsimilarityLabel.setText(' ') - self.currentsimilaritythresholdnumberLabel.setText(' ') + self.startautosplitterButton.setText("Start Auto Splitter") + self.imageloopLabel.setText("Image Loop: -") + self.currentSplitImage.setText(" ") + self.currentsplitimagefileLabel.setText(" ") + self.livesimilarityLabel.setText(" ") + self.highestsimilarityLabel.setText(" ") + self.currentsimilaritythresholdnumberLabel.setText(" ") self.browseButton.setEnabled(True) self.groupDummySplitsCheckBox.setEnabled(True) self.startImageReloadButton.setEnabled(True) @@ -956,8 +979,8 @@ def shouldCheckResetImage(self): return self.reset_image is not None and time() - self.run_start_time > self.reset_image_pause_time def findResetImage(self): - self.reset_image: Optional[cv2.ndarray] = None - self.reset_mask: Optional[cv2.ndarray] = None + self.reset_image = None + self.reset_mask = None reset_image_file = None for image in self.split_image_filenames: @@ -1003,7 +1026,7 @@ def findResetImage(self): self.reset_image = cv2.imread(path, cv2.IMREAD_COLOR) self.reset_image = cv2.resize(self.reset_image, COMPARISON_RESIZE, interpolation=cv2.INTER_NEAREST) - def updateSplitImage(self, custom_image_file: str = '', from_start_image: bool = False): + def updateSplitImage(self, custom_image_file: str = "", from_start_image: bool = False): # Splitting/skipping when there are no images left or Undoing past the first image # Start image is expected to be out of range (index 0 of 0-length array) if "START_AUTO_SPLITTER" not in custom_image_file.upper() and self.is_current_split_out_of_range(): @@ -1027,7 +1050,6 @@ def updateSplitImage(self, custom_image_file: str = '', from_start_image: bool = split_image_display = copy(self.split_image) # Transform transparency into UI's gray BG color transparent_mask = split_image_display[:, :, 3] == 0 - split_image_display[:, :, 3] == 0 split_image_display[transparent_mask] = [240, 240, 240, MAXBYTE] split_image_display = cv2.cvtColor(split_image_display, cv2.COLOR_BGRA2RGB) @@ -1088,7 +1110,7 @@ def updateSplitImage(self, custom_image_file: str = '', from_start_image: bool = def closeEvent(self, a0: Optional[QtGui.QCloseEvent] = None): # save global setting values here - self.setting_check_for_updates_on_open.setValue('check_for_updates_on_open', + self.setting_check_for_updates_on_open.setValue("check_for_updates_on_open", self.actionCheck_for_Updates_on_Open.isChecked()) def exitProgram(): @@ -1136,7 +1158,7 @@ def exitProgram(): def main(): app = QApplication(sys.argv) try: - app.setWindowIcon(QtGui.QIcon(':/resources/icon.ico')) + app.setWindowIcon(QtGui.QIcon(":/resources/icon.ico")) main_window = AutoSplit() main_window.show() # Needs to be after main_window.show() to be shown over @@ -1149,9 +1171,9 @@ def main(): timer.start(500) exit_code = app.exec() - except Exception as exception: + except Exception as exception: # pylint: disable=broad-except # Print error to console if not running in executable - if getattr(sys, 'frozen', False): + if getattr(sys, "frozen", False): error_messages.exceptionTraceback( f"AutoSplit encountered an unrecoverable exception and will now close. {CREATE_NEW_ISSUE_MESSAGE}", exception) @@ -1165,7 +1187,7 @@ def main(): sys.exit(exit_code) -def excepthook(exceptionType: Type[BaseException], exception: BaseException, _traceback: Optional[TracebackType]): +def excepthook(exceptionType: type[BaseException], exception: BaseException, _traceback: Optional[TracebackType]): # Catch Keyboard Interrupts for a clean close if exceptionType is KeyboardInterrupt: sys.exit(0) @@ -1175,6 +1197,6 @@ def excepthook(exceptionType: Type[BaseException], exception: BaseException, _tr exception) -if __name__ == '__main__': +if __name__ == "__main__": sys.excepthook = excepthook main() diff --git a/src/capture_windows.py b/src/capture_windows.py index cbcda017..b0d064f0 100644 --- a/src/capture_windows.py +++ b/src/capture_windows.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Dict, cast +from typing import cast import ctypes import ctypes.wintypes @@ -17,7 +17,7 @@ # This is an undocumented nFlag value for PrintWindow PW_RENDERFULLCONTENT = 0x00000002 -accelerated_windows: Dict[int, bool] = {} +accelerated_windows: dict[int, bool] = {} is_windows_11 = version.parse(platform.version()) >= version.parse("10.0.22000") @@ -79,7 +79,7 @@ def __get_image(hwnd: int, selection: Rect, print_window: bool = False): except (win32ui.error, pywintypes.error): # type: ignore return np.array([0, 0, 0, 1], dtype="uint8") - image: cv2.ndarray = np.frombuffer(cast(bytes, bitmap.GetBitmapBits(True)), dtype='uint8') + image: cv2.ndarray = np.frombuffer(cast(bytes, bitmap.GetBitmapBits(True)), dtype="uint8") image.shape = (height, width, 4) try: diff --git a/src/compare.py b/src/compare.py index b1c89fd8..6d58c63e 100644 --- a/src/compare.py +++ b/src/compare.py @@ -123,7 +123,7 @@ def compare_phash(source: cv2.ndarray, capture: cv2.ndarray, mask: Optional[cv2. return 1 - (hash_diff / 64.0) -def checkIfImageHasTransparency(image: cv2.ndarray): +def checkIfImageHasTransparency(image: cv2.ndarray) -> bool: # Check if there's a transparency channel (4th channel) and if at least one pixel is transparent (< 255) if image.shape[2] != 4: return False diff --git a/src/error_messages.py b/src/error_messages.py index f0c105c5..bad19bd9 100644 --- a/src/error_messages.py +++ b/src/error_messages.py @@ -3,9 +3,9 @@ from PyQt6 import QtCore, QtWidgets -def setTextMessage(message: str, details: str = ''): +def setTextMessage(message: str, details: str = ""): messageBox = QtWidgets.QMessageBox() - messageBox.setWindowTitle('Error') + messageBox.setWindowTitle("Error") messageBox.setTextFormat(QtCore.Qt.TextFormat.RichText) messageBox.setText(message) if details: @@ -30,7 +30,7 @@ def splitImageDirectoryEmpty(): def imageTypeError(image: str): - setTextMessage(f"\"{image}\" is not a valid image file, does not exist, " + setTextMessage(f'"{image}" is not a valid image file, does not exist, ' "or the full image file path contains a special character.") @@ -57,11 +57,11 @@ def alignmentNotMatchedError(): def noKeywordImageError(keyword: str): - setTextMessage(f"Your split image folder does not contain an image with the keyword \"{keyword}\".") + setTextMessage(f'Your split image folder does not contain an image with the keyword "{keyword}".') def multipleKeywordImagesError(keyword: str): - setTextMessage(f"Only one image with the keyword \"{keyword}\" is allowed.") + setTextMessage(f'Only one image with the keyword "{keyword}" is allowed.') def resetHotkeyError(): diff --git a/src/hotkeys.py b/src/hotkeys.py index a332c37b..b586272b 100644 --- a/src/hotkeys.py +++ b/src/hotkeys.py @@ -1,5 +1,6 @@ from __future__ import annotations -from typing import Optional, Callable, TYPE_CHECKING, Union +from typing import Optional, TYPE_CHECKING, Union +from collections.abc import Callable if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -24,11 +25,11 @@ def beforeSettingHotkey(autosplit: AutoSplit): # do all of these things after you set a hotkey. a signal connects to this because # changing GUI stuff in the hotkey thread was causing problems def afterSettingHotkey(autosplit: AutoSplit): - autosplit.setsplithotkeyButton.setText('Set Hotkey') - autosplit.setresethotkeyButton.setText('Set Hotkey') - autosplit.setskipsplithotkeyButton.setText('Set Hotkey') - autosplit.setundosplithotkeyButton.setText('Set Hotkey') - autosplit.setpausehotkeyButton.setText('Set Hotkey') + autosplit.setsplithotkeyButton.setText("Set Hotkey") + autosplit.setresethotkeyButton.setText("Set Hotkey") + autosplit.setskipsplithotkeyButton.setText("Set Hotkey") + autosplit.setundosplithotkeyButton.setText("Set Hotkey") + autosplit.setpausehotkeyButton.setText("Set Hotkey") autosplit.startautosplitterButton.setEnabled(True) autosplit.setsplithotkeyButton.setEnabled(True) autosplit.setresethotkeyButton.setEnabled(True) @@ -49,15 +50,14 @@ def is_digit(key: Optional[str]): def send_command(autosplit: AutoSplit, command: str): if autosplit.is_auto_controlled: print(command, flush=True) + elif command in {"split", "start"}: + _send_hotkey(autosplit.splitLineEdit.text()) + elif command == "pause": + _send_hotkey(autosplit.pausehotkeyLineEdit.text()) + elif command == "reset": + _send_hotkey(autosplit.resetLineEdit.text()) else: - if command in ("split", "start"): - _send_hotkey(autosplit.splitLineEdit.text()) - elif command == "pause": - _send_hotkey(autosplit.pausehotkeyLineEdit.text()) - elif command == "reset": - _send_hotkey(autosplit.resetLineEdit.text()) - else: - raise KeyError(f"'{command}' is not a valid LiveSplit.AutoSplitIntegration command") + raise KeyError(f"'{command}' is not a valid LiveSplit.AutoSplitIntegration command") # Supports sending the appropriate scan code for all the special cases @@ -67,20 +67,19 @@ def _send_hotkey(key_or_scan_code: Union[int, str]): # Deal with regular inputs if isinstance(key_or_scan_code, int) \ - or not (key_or_scan_code.startswith('num ') or key_or_scan_code == 'decimal'): + or not (key_or_scan_code.startswith("num ") or key_or_scan_code == "decimal"): keyboard.send(key_or_scan_code) - keyboard.key_to_scan_codes return - # Deal with problematic keys. Even by sending specific scan code 'keyboard' still sends the default (wrong) key + # Deal with problematic keys. Even by sending specific scan code "keyboard" still sends the default (wrong) key # keyboard.send(keyboard.key_to_scan_codes(key_or_scan_code)[1]) - pyautogui.hotkey(key_or_scan_code.replace(' ', '')) + pyautogui.hotkey(key_or_scan_code.replace(" ", "")) def __validate_keypad(expected_key: str, keyboard_event: KeyboardEvent) -> bool: # Prevent "(keypad)delete", "(keypad)./decimal" and "del" from triggering each other # as well as "." and "(keypad)./decimal" - if keyboard_event.scan_code in (83, 52): + if keyboard_event.scan_code in {83, 52}: # TODO: "del" won't work with "(keypad)delete" if localized in non-english (ie: "suppr" in french) return expected_key == keyboard_event.name # Prevent "action keys" from triggering "keypad keys" @@ -99,11 +98,11 @@ def __validate_keypad(expected_key: str, keyboard_event: KeyboardEvent) -> bool: # Windows reports different physical keys with the same scan code. # For example, "Home", "Num Home" and "Num 7" are all "71". # See: https://github.com/boppreh/keyboard/issues/171#issuecomment-390437684 -# + # We're doing the check here instead of saving the key code because it'll # cause issues with save files and the non-keypad shared keys are localized # while the keypad ones aren't. -# + # Since we reuse the key string we set to send to LiveSplit, we can't use fake names like "num home". # We're also trying to achieve the same hotkey behaviour as LiveSplit has. def _hotkey_action(keyboard_event: KeyboardEvent, key_name: str, action: Callable[[], None]): @@ -129,7 +128,7 @@ def __is_key_already_set(autosplit: AutoSplit, key_name: str): # TODO: Refactor to de-duplicate all this code, including settings_file.py # Going to comment on one func, and others will be similar. def setSplitHotkey(autosplit: AutoSplit): - autosplit.setsplithotkeyButton.setText('Press a key...') + autosplit.setsplithotkeyButton.setText("Press a key...") # disable some buttons beforeSettingHotkey(autosplit) @@ -163,7 +162,7 @@ def callback(hotkey: Callable[[], None]): # hotkey. A try and except is needed if a hotkey hasn't been set yet. I'm not # allowing for these multiple-key hotkeys because it can cause crashes, and # not many people are going to really use or need this. - if __is_key_already_set(autosplit, key_name) or (key_name != '+' and '+' in key_name): + if __is_key_already_set(autosplit, key_name) or (key_name != "+" and "+" in key_name): autosplit.afterSettingHotkeySignal.emit() return except AttributeError: @@ -189,7 +188,7 @@ def callback(hotkey: Callable[[], None]): def setResetHotkey(autosplit: AutoSplit): - autosplit.setresethotkeyButton.setText('Press a key...') + autosplit.setresethotkeyButton.setText("Press a key...") beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): @@ -201,7 +200,7 @@ def callback(hotkey: Callable[[], None]): key_name = __get_key_name(keyboard.read_event(True)) try: - if __is_key_already_set(autosplit, key_name) or (key_name != '+' and '+' in key_name): + if __is_key_already_set(autosplit, key_name) or (key_name != "+" and "+" in key_name): autosplit.afterSettingHotkeySignal.emit() return except AttributeError: @@ -220,7 +219,7 @@ def callback(hotkey: Callable[[], None]): def setSkipSplitHotkey(autosplit: AutoSplit): - autosplit.setskipsplithotkeyButton.setText('Press a key...') + autosplit.setskipsplithotkeyButton.setText("Press a key...") beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): @@ -232,7 +231,7 @@ def callback(hotkey: Callable[[], None]): key_name = __get_key_name(keyboard.read_event(True)) try: - if __is_key_already_set(autosplit, key_name) or (key_name != '+' and '+' in key_name): + if __is_key_already_set(autosplit, key_name) or (key_name != "+" and "+" in key_name): autosplit.afterSettingHotkeySignal.emit() return except AttributeError: @@ -251,7 +250,7 @@ def callback(hotkey: Callable[[], None]): def setUndoSplitHotkey(autosplit: AutoSplit): - autosplit.setundosplithotkeyButton.setText('Press a key...') + autosplit.setundosplithotkeyButton.setText("Press a key...") beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): @@ -263,7 +262,7 @@ def callback(hotkey: Callable[[], None]): key_name = __get_key_name(keyboard.read_event(True)) try: - if __is_key_already_set(autosplit, key_name) or (key_name != '+' and '+' in key_name): + if __is_key_already_set(autosplit, key_name) or (key_name != "+" and "+" in key_name): autosplit.afterSettingHotkeySignal.emit() return except AttributeError: @@ -282,7 +281,7 @@ def callback(hotkey: Callable[[], None]): def setPauseHotkey(autosplit: AutoSplit): - autosplit.setpausehotkeyButton.setText('Press a key...') + autosplit.setpausehotkeyButton.setText("Press a key...") beforeSettingHotkey(autosplit) def callback(hotkey: Callable[[], None]): @@ -294,7 +293,7 @@ def callback(hotkey: Callable[[], None]): key_name = __get_key_name(keyboard.read_event(True)) try: - if __is_key_already_set(autosplit, key_name) or (key_name != '+' and '+' in key_name): + if __is_key_already_set(autosplit, key_name) or (key_name != "+" and "+" in key_name): autosplit.afterSettingHotkeySignal.emit() return except AttributeError: diff --git a/src/menu_bar.py b/src/menu_bar.py index 7042e290..cf67f09d 100644 --- a/src/menu_bar.py +++ b/src/menu_bar.py @@ -53,7 +53,7 @@ def __init__(self, latest_version: str, autosplit: AutoSplit, check_for_updates_ def openUpdate(self): if self.checkBoxDoNotAskMeAgain.isChecked(): self.autosplit.actionCheck_for_Updates_on_Open.setChecked(False) - os.system("start \"\" https://github.com/Toufool/Auto-Split/releases/latest") + os.system('start "" https://github.com/Toufool/Auto-Split/releases/latest') self.close() def closeWindow(self): @@ -63,7 +63,7 @@ def closeWindow(self): def viewHelp(): - os.system("start \"\" https://github.com/Toufool/Auto-Split#tutorial") + os.system('start "" https://github.com/Toufool/Auto-Split#tutorial') def about(autosplit: AutoSplit): @@ -74,7 +74,7 @@ def checkForUpdates(autosplit: AutoSplit, check_for_updates_on_open: bool = Fals try: response = requests.get("https://api.github.com/repos/Toufool/Auto-Split/releases/latest") latest_version = response.json()["name"].split("v")[1] - except Exception: + except requests.exceptions.RequestException: if not check_for_updates_on_open: error_messages.checkForUpdatesError() else: diff --git a/src/screen_region.py b/src/screen_region.py index 05fceb50..29d8817b 100644 --- a/src/screen_region.py +++ b/src/screen_region.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Tuple, cast, TYPE_CHECKING +from typing import cast, TYPE_CHECKING if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -84,12 +84,12 @@ def selectWindow(autosplit: AutoSplit): # Need to wait until the user has selected a region using the widget before moving on with # selecting the window settings - while selector.x == -1 and selector.y == -1: + while selector.x() == -1 and selector.y() == -1: # Email sent to pyqt@riverbankcomputing.com QtTest.QTest.qWait(1) # type: ignore # Grab the window handle from the coordinates selected by the widget - autosplit.hwnd = cast(int, win32gui.WindowFromPoint((selector.x, selector.y))) + autosplit.hwnd = cast(int, win32gui.WindowFromPoint((selector.x(), selector.y()))) del selector @@ -111,7 +111,7 @@ def selectWindow(autosplit: AutoSplit): # also the top bar with the window name is not accounted for # I hardcoded the x and y coordinates to fix this # This is not an ideal solution because it assumes every window will have a top bar - selection: Tuple[int, int, int, int] = win32gui.GetClientRect(autosplit.hwnd) + selection: tuple[int, int, int, int] = win32gui.GetClientRect(autosplit.hwnd) autosplit.selection.left = 8 autosplit.selection.top = 31 autosplit.selection.right = 8 + selection[2] @@ -235,7 +235,7 @@ def __init__(self): user32.GetSystemMetrics(SM_YVIRTUALSCREEN), user32.GetSystemMetrics(SM_CXVIRTUALSCREEN), user32.GetSystemMetrics(SM_CYVIRTUALSCREEN)) - self.setWindowTitle(' ') + self.setWindowTitle(" ") self.setWindowOpacity(0.5) self.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint) self.show() @@ -247,9 +247,18 @@ def keyPressEvent(self, a0: QtGui.QKeyEvent): # Widget to select a window and obtain its bounds class SelectWindowWidget(BaseSelectWidget): + __x = -1 + __y = -1 + + def x(self): + return self.__x + + def y(self): + return self.__y + def mouseReleaseEvent(self, a0: QtGui.QMouseEvent): - self.x = lambda: int(a0.position().x()) + self.geometry().x() - self.y = lambda: int(a0.position().y()) + self.geometry().y() + self.__x = int(a0.position().x()) + self.geometry().x() + self.__y = int(a0.position().y()) + self.geometry().y() self.close() @@ -276,8 +285,8 @@ def width(self): def paintEvent(self, a0: QtGui.QPaintEvent): if self.__begin != self.__end: qPainter = QtGui.QPainter(self) - qPainter.setPen(QtGui.QPen(QtGui.QColor('red'), 2)) - qPainter.setBrush(QtGui.QColor('opaque')) + qPainter.setPen(QtGui.QPen(QtGui.QColor("red"), 2)) + qPainter.setBrush(QtGui.QColor("opaque")) qPainter.drawRect(QtCore.QRect(self.__begin, self.__end)) def mousePressEvent(self, a0: QtGui.QMouseEvent): diff --git a/src/settings_file.py b/src/settings_file.py index a2c6c087..3948c596 100644 --- a/src/settings_file.py +++ b/src/settings_file.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, List, Literal, cast +from typing import TYPE_CHECKING, Any, Literal, cast if TYPE_CHECKING: from AutoSplit import AutoSplit @@ -14,14 +14,14 @@ from hotkeys import _hotkey_action # Get the directory of either AutoSplit.exe or AutoSplit.py -auto_split_directory = os.path.dirname(sys.executable if getattr(sys, 'frozen', False) else os.path.abspath(__file__)) +auto_split_directory = os.path.dirname(sys.executable if getattr(sys, "frozen", False) else os.path.abspath(__file__)) def loadPyQtSettings(autosplit: AutoSplit): # These are only global settings values. They are not *pkl settings values. autosplit.getGlobalSettingsValues() check_for_updates_on_open = autosplit.setting_check_for_updates_on_open.value( - 'check_for_updates_on_open', + "check_for_updates_on_open", True, type=bool) autosplit.actionCheck_for_Updates_on_Open.setChecked(check_for_updates_on_open) @@ -110,7 +110,7 @@ def saveSettings(autosplit: AutoSplit): autosplit.loop_setting, autosplit.auto_start_on_reset_setting] # save settings to a .pkl file - with open(autosplit.last_successfully_loaded_settings_file_path, 'wb') as f: + with open(autosplit.last_successfully_loaded_settings_file_path, "wb") as f: pickle.dump(autosplit.last_saved_settings, f) @@ -150,7 +150,7 @@ def saveSettingsAs(autosplit: AutoSplit): autosplit.auto_start_on_reset_setting] # save settings to a .pkl file - with open(autosplit.save_settings_file_path, 'wb') as f: + with open(autosplit.save_settings_file_path, "wb") as f: pickle.dump(autosplit.last_saved_settings, f) # Wording is kinda off here but this needs to be here for an edge case: @@ -188,15 +188,15 @@ def loadSettings(autosplit: AutoSplit, load_settings_on_open: bool = False, load return try: - with open(autosplit.load_settings_file_path, 'rb') as f: - settings: List[Any] = pickle.load(f) + with open(autosplit.load_settings_file_path, "rb") as f: + settings: list[Any] = pickle.load(f) settings_count = len(settings) if settings_count < 18: autosplit.showErrorSignal.emit(error_messages.oldVersionSettingsFileError) return # v1.3-1.4 settings. Add default pause_key and auto_start_on_reset_setting if settings_count == 18: - settings.insert(9, '') + settings.insert(9, "") settings.insert(20, 0) # v1.5 settings elif settings_count != 20: diff --git a/src/split_parser.py b/src/split_parser.py index 1932878c..5e24c78a 100644 --- a/src/split_parser.py +++ b/src/split_parser.py @@ -1,9 +1,11 @@ -import os -from typing import List +from __future__ import annotations +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from AutoSplit import AutoSplit +import os import cv2 -from AutoSplit import AutoSplit import error_messages @@ -27,8 +29,8 @@ def threshold_from_filename(filename: str): # Check to make sure there is a valid floating point number between # parentheses of the filename try: - threshold = float(filename.split('(', 1)[1].split(')')[0]) - except Exception: + threshold = float(filename.split("(", 1)[1].split(")")[0]) + except (IndexError, ValueError): return None # Check to make sure if it is a valid threshold @@ -47,8 +49,8 @@ def pause_from_filename(filename: str): # Check to make sure there is a valid pause time between brackets # of the filename try: - pause = float(filename.split('[', 1)[1].split(']')[0]) - except Exception: + pause = float(filename.split("[", 1)[1].split("]")[0]) + except (IndexError, ValueError): return None # Pause times should always be positive or zero @@ -67,8 +69,8 @@ def delay_from_filename(filename: str): # Check to make sure there is a valid delay time between brackets # of the filename try: - delay = float(filename.split('#', 1)[1].split('#')[0]) - except Exception: + delay = float(filename.split("#", 1)[1].split("#")[0]) + except (IndexError, ValueError): return 0.0 # Delay times should always be positive or zero @@ -87,8 +89,8 @@ def loop_from_filename(filename: str): # Check to make sure there is a valid delay time between brackets # of the filename try: - loop = int(filename.split('@', 1)[1].split('@')[0]) - except Exception: + loop = int(filename.split("@", 1)[1].split("@")[0]) + except (IndexError, ValueError): return 1 # Loop should always be positive @@ -102,29 +104,29 @@ def flags_from_filename(filename: str): @param filename: String containing the file's name @return: The flags as an integer, if invalid flags are found it returns 0 - List of flags: - 'd' = dummy, do nothing when this split is found - 'b' = below threshold, after threshold is met, split when it goes below the threhsold. - 'p' = pause, hit pause key when this split is found + list of flags: + "d" = dummy, do nothing when this split is found + "b" = below threshold, after threshold is met, split when it goes below the threhsold. + "p" = pause, hit pause key when this split is found """ # Check to make sure there are flags between curly braces # of the filename try: - flags_str = filename.split('{', 1)[1].split('}')[0] - except Exception: + flags_str = filename.split("{", 1)[1].split("}")[0] + except (IndexError, ValueError): return 0 flags = 0x00 for c in flags_str: - if c.upper() == 'D': + if c.upper() == "D": flags |= DUMMY_FLAG - elif c.upper() == 'M': + elif c.upper() == "M": flags |= MASK_FLAG - elif c.upper() == 'B': + elif c.upper() == "B": flags |= BELOW_FLAG - elif c.upper() == 'P': + elif c.upper() == "P": flags |= PAUSE_FLAG else: # An invalid flag was caught, this filename was written incorrectly @@ -146,7 +148,7 @@ def is_reset_image(filename: str): @param filename: String containing the file's name @return: True if its a reset image """ - return 'RESET' in filename.upper() + return "RESET" in filename.upper() def is_start_auto_splitter_image(filename: str): @@ -156,10 +158,10 @@ def is_start_auto_splitter_image(filename: str): @param filename: String containing the file's name @return: True if its a reset image """ - return 'START_AUTO_SPLITTER' in filename.upper() + return "START_AUTO_SPLITTER" in filename.upper() -def removeStartAutoSplitterImage(split_image_filenames: List[str]): +def removeStartAutoSplitterImage(split_image_filenames: list[str]): start_auto_splitter_image_file = None for image in split_image_filenames: if is_start_auto_splitter_image(image): @@ -207,7 +209,7 @@ def validate_images_before_parsing(autosplit: AutoSplit): return if already_found_reset_image: autosplit.guiChangesOnReset() - error_messages.multipleKeywordImagesError('reset') + error_messages.multipleKeywordImagesError("reset") return already_found_reset_image = True @@ -215,6 +217,6 @@ def validate_images_before_parsing(autosplit: AutoSplit): if is_start_auto_splitter_image(image): if already_found_start_image: autosplit.guiChangesOnReset() - error_messages.multipleKeywordImagesError('start_auto_splitter') + error_messages.multipleKeywordImagesError("start_auto_splitter") return already_found_start_image = True