diff --git a/.github/workflows/lint-and-build.yml b/.github/workflows/lint-and-build.yml
index 62d39ad2..0fe853ba 100644
--- a/.github/workflows/lint-and-build.yml
+++ b/.github/workflows/lint-and-build.yml
@@ -45,7 +45,7 @@ jobs:
- run: scripts/install.ps1
shell: pwsh
- name: Analysing the code with Pyright
- run: pyright --warnings
+ run: pyright src/ --warnings
Pylint:
runs-on: windows-latest
strategy:
@@ -64,7 +64,7 @@ jobs:
- run: scripts/install.ps1
shell: pwsh
- name: Analysing the code with Pylint
- run: pylint --reports=y --output-format=colorized src/
+ run: pylint src/ --reports=y --output-format=colorized
Flake8:
runs-on: windows-latest
strategy:
@@ -83,7 +83,7 @@ jobs:
- run: scripts/install.ps1
shell: pwsh
- name: Analysing the code with Flake8
- run: flake8
+ run: flake8 src/ typings/
Bandit:
runs-on: windows-latest
steps:
@@ -98,7 +98,7 @@ jobs:
- run: scripts/install.ps1
shell: pwsh
- name: Analysing the code with Bandit
- run: bandit -n 1 --severity-level medium --recursive src
+ run: bandit src/ -n 1 --severity-level medium --recursive
Build:
runs-on: windows-latest
strategy:
diff --git a/.sonarcloud.properties b/.sonarcloud.properties
new file mode 100644
index 00000000..79132012
--- /dev/null
+++ b/.sonarcloud.properties
@@ -0,0 +1 @@
+sonar.python.version=3.9, 3.10
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index e6b47305..33859d80 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -9,9 +9,10 @@
"ms-python.vscode-pylance",
"ms-vscode.powershell",
"pkief.material-icon-theme",
+ "redhat.vscode-xml",
"redhat.vscode-yaml",
"shardulm94.trailing-spaces",
- "sonarsource.sonarlint-vscode"
+ "sonarsource.sonarlint-vscode",
],
"unwantedRecommendations": [
// Must disable in this workspace //
@@ -36,5 +37,5 @@
"johnstoncode.svn-scm",
// Prefer using VSCode itself as a text editor
"vscodevim.vim",
- ]
+ ],
}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 601ce64c..ab483d74 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -106,4 +106,6 @@
"powershell.codeFormatting.useCorrectCasing": true,
"powershell.codeFormatting.whitespaceBetweenParameters": true,
"powershell.integratedConsole.showOnStartup": false,
+ "xml.codeLens.enabled": true,
+ "xml.format.spaceBeforeEmptyCloseTag": false,
}
diff --git a/res/design.ui b/res/design.ui
index a34632df..03aa78f7 100644
--- a/res/design.ui
+++ b/res/design.ui
@@ -10,12 +10,6 @@
424
-
-
- 0
- 0
-
-
777
diff --git a/res/settings.ui b/res/settings.ui
index 4f5cc6eb..ecc0a9d7 100644
--- a/res/settings.ui
+++ b/res/settings.ui
@@ -1,7 +1,7 @@
- DialogSettings
-
+ SettingsWidget
+
0
@@ -10,12 +10,6 @@
661
-
-
- 0
- 0
-
-
291
@@ -40,12 +34,6 @@
:/resources/icon.ico:/resources/icon.ico
-
- false
-
-
- false
-
@@ -352,7 +340,7 @@ It is highly recommended to NOT use pHash if you use masked images. It is very i
6
- 193
+ 190
261
61
@@ -364,7 +352,10 @@ It is highly recommended to NOT use pHash if you use masked images. It is very i
- <html><head/><body><p>Custom image settings and flags are set in the <br></br> image file name. These will override the default <br></br> values. View the <a href="https://github.com/{GITHUB_REPOSITORY}#readme"><span style=" text-decoration: underline; color:#0000ff;">README</span></a> for full details on all <br></br> available custom image settings.</p></body></html>
+ <html><head/><body><p>Image settings and flags can be set per image through the image file name. These will override the default values. View the <a href="https://github.com/{GITHUB_REPOSITORY}#readme"><span style="text-decoration: underline; color:#0000ff;">README</span></a> for full details on all available custom image settings.</p></body></html>
+
+
+ true
@@ -399,6 +390,34 @@ It is highly recommended to NOT use pHash if you use masked images. It is very i
999999999
+
+
+
+ 140
+ 210
+ 71
+ 31
+
+
+
+
+ 8
+ true
+
+
+
+ README
+
+
+
+ 0
+ 0
+
+
+
+ This is a workaround because custom_image_settings_info_label simply will not open links with a left click no matter what we tried.
+
+
@@ -714,11 +733,11 @@ reset image
- set_split_hotkey_button
- set_reset_hotkey_button
- set_undo_split_hotkey_button
- set_skip_split_hotkey_button
- set_pause_hotkey_button
+ split_input
+ reset_input
+ undo_split_input
+ skip_split_input
+ pause_input
fps_limit_spinbox
live_capture_region_checkbox
capture_method_combobox
diff --git a/res/update_checker.ui b/res/update_checker.ui
index 53567fc1..75fbc852 100644
--- a/res/update_checker.ui
+++ b/res/update_checker.ui
@@ -13,12 +13,6 @@
133
-
-
- 0
- 0
-
-
313
diff --git a/scripts/lint.ps1 b/scripts/lint.ps1
index f5931602..8466ea2e 100644
--- a/scripts/lint.ps1
+++ b/scripts/lint.ps1
@@ -2,9 +2,13 @@ $originalDirectory = $pwd
Set-Location "$PSScriptRoot/.."
$exitCodes = 0
+Write-Host "`nRunning autofixes..."
+isort src/ typings/
+autopep8 src/ typings/ --in-place
+
Write-Host "`nRunning Pyright..."
$Env:PYRIGHT_PYTHON_FORCE_VERSION = 'latest'
-pyright --warnings
+pyright src/ --warnings
$exitCodes += $LastExitCode
if ($LastExitCode -gt 0) {
Write-Host "`Pyright failed ($LastExitCode)" -ForegroundColor Red
@@ -14,7 +18,7 @@ else {
}
Write-Host "`nRunning Pylint..."
-pylint --output-format=colorized src/
+pylint src/ --output-format=colorized
$exitCodes += $LastExitCode
if ($LastExitCode -gt 0) {
Write-Host "`Pylint failed ($LastExitCode)" -ForegroundColor Red
@@ -24,7 +28,7 @@ else {
}
Write-Host "`nRunning Flake8..."
-flake8
+flake8 src/ typings/
$exitCodes += $LastExitCode
if ($LastExitCode -gt 0) {
Write-Host "`Flake8 failed ($LastExitCode)" -ForegroundColor Red
@@ -34,7 +38,7 @@ else {
}
Write-Host "`nRunning Bandit..."
-bandit -f custom --silent --recursive src
+bandit src/ -f custom --silent --recursive
# $exitCodes += $LastExitCode # Returns 1 on low
if ($LastExitCode -gt 0) {
Write-Host "`Bandit warning ($LastExitCode)" -ForegroundColor Yellow
diff --git a/scripts/requirements-dev.txt b/scripts/requirements-dev.txt
index 99f7134e..bcb5e46d 100644
--- a/scripts/requirements-dev.txt
+++ b/scripts/requirements-dev.txt
@@ -19,7 +19,7 @@ flake8-pyi>=22.8.1 # flake8 5 support
flake8-quotes
flake8-simplify
pep8-naming
-pylint>=2.13.9 # Respect ignore configuration options with --recursive=y
+pylint>=2.13.9,<3.0.0 # Respect ignore configuration options with --recursive=y # 3.0 still in pre-release
pyright
#
# Run `./scripts/designer.ps1` to quickly open the bundled PyQt Designer.
diff --git a/scripts/requirements.txt b/scripts/requirements.txt
index 00d56180..ef80a6a1 100644
--- a/scripts/requirements.txt
+++ b/scripts/requirements.txt
@@ -13,7 +13,7 @@
numpy>=1.23 # Updated types
opencv-python-headless>=4.5.4,<4.6 # https://github.com/pyinstaller/pyinstaller/issues/6889
PyQt6>=6.2.1 # Python 3.10 support
-git+https://github.com/Avasam/imagehash.git@patch-2#egg=ImageHash # Contains type information + setup as package not module
+git+https://github.com/JohannesBuchner/imagehash.git#egg=ImageHash # Contains type information + setup as package not module
keyboard
packaging
Pillow
diff --git a/src/AutoSplit.py b/src/AutoSplit.py
index 0a98c6f8..dc5af4cd 100644
--- a/src/AutoSplit.py
+++ b/src/AutoSplit.py
@@ -6,7 +6,6 @@
import os
import signal
import sys
-from collections.abc import Callable
from time import time
from types import FunctionType
@@ -21,7 +20,7 @@
import user_profile
from AutoControlledWorker import AutoControlledWorker
from AutoSplitImage import COMPARISON_RESIZE, START_KEYWORD, AutoSplitImage, ImageType
-from capture_method import CaptureMethodEnum, CaptureMethodInterface
+from capture_method import CaptureMethodBase, CaptureMethodEnum
from gen import about, design, settings, update_checker
from hotkeys import HOTKEYS, after_setting_hotkey, send_command
from menu_bar import (about_qt, about_qt_for_python, check_for_updates, get_default_settings_from_ui, open_about,
@@ -64,15 +63,7 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow):
AboutWidget: about.Ui_AboutAutoSplitWidget | None = None
UpdateCheckerWidget: update_checker.Ui_UpdateChecker | None = None
CheckForUpdatesThread: QtCore.QThread | None = None
- SettingsWidget: settings.Ui_DialogSettings | None = None
-
- # hotkeys need to be initialized to be passed as thread arguments in hotkeys.py
- # and for type safety in both hotkeys.py and settings_file.py
- split_hotkey: Callable[[], None] | None = None
- reset_hotkey: Callable[[], None] | None = None
- skip_split_hotkey: Callable[[], None] | None = None
- undo_split_hotkey: Callable[[], None] | None = None
- pause_hotkey: Callable[[], None] | None = None
+ SettingsWidget: settings.Ui_SettingsWidget | None = None
# Initialize a few attributes
hwnd = 0
@@ -82,7 +73,7 @@ class AutoSplit(QMainWindow, design.Ui_MainWindow):
split_image_number = 0
split_images_and_loop_number: list[tuple[AutoSplitImage, int]] = []
split_groups: list[list[int]] = []
- capture_method = CaptureMethodInterface()
+ capture_method = CaptureMethodBase()
# Last loaded settings empty and last successful loaded settings file path to None until we try to load them
last_loaded_settings = DEFAULT_PROFILE
@@ -123,6 +114,10 @@ def __init__(self, parent: QWidget | None = None): # pylint: disable=too-many-s
self.width_spinbox.setFrame(False)
self.height_spinbox.setFrame(False)
+ # Hotkeys need to be initialized to be passed as thread arguments in hotkeys.py
+ for hotkey in HOTKEYS:
+ setattr(self, f"{hotkey}_hotkey", None)
+
# Get default values defined in SettingsDialog
self.settings_dict = get_default_settings_from_ui(self)
user_profile.load_check_for_updates_on_open(self)
@@ -450,7 +445,7 @@ def skip_split(self, navigate_image_only: bool = False):
# or Splitting/skipping when there are no images left
if self.start_auto_splitter_button.text() == START_AUTO_SPLITTER_TEXT \
or "Delayed Split" in self.current_split_image.text() \
- or (not self.skip_split_button.isEnabled() and not self.is_auto_controlled) \
+ or not (self.skip_split_button.isEnabled() or self.is_auto_controlled or navigate_image_only) \
or self.__is_current_split_out_of_range():
return
@@ -795,6 +790,10 @@ def __reset_if_should(self, capture: cv2.Mat | None):
self.reset()
else:
self.table_reset_image_live_label.setText("disabled")
+ else:
+ self.table_reset_image_live_label.setText("N/A")
+ self.table_reset_image_threshold_label.setText("N/A")
+ self.table_reset_image_highest_label.setText("N/A")
return self.__check_for_reset_state_update_ui()
diff --git a/src/capture_method/BitBltCaptureMethod.py b/src/capture_method/BitBltCaptureMethod.py
index 23d2a30f..e7ddabdc 100644
--- a/src/capture_method/BitBltCaptureMethod.py
+++ b/src/capture_method/BitBltCaptureMethod.py
@@ -11,7 +11,7 @@
import win32ui
from win32 import win32gui
-from capture_method.interface import CaptureMethodInterface
+from capture_method.CaptureMethodBase import CaptureMethodBase
from utils import get_window_bounds, is_valid_hwnd
if TYPE_CHECKING:
@@ -21,7 +21,7 @@
PW_RENDERFULLCONTENT = 0x00000002
-class BitBltCaptureMethod(CaptureMethodInterface):
+class BitBltCaptureMethod(CaptureMethodBase):
_render_full_content = False
def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.Mat | None, bool]:
@@ -57,15 +57,11 @@ def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.Mat | None, bool]:
image.shape = (selection["height"], selection["width"], 4)
except (win32ui.error, pywintypes.error):
return None, False
- # We already obtained the image, so we can ignore errors during cleanup
- try:
- dc_object.DeleteDC()
- dc_object.DeleteDC()
- compatible_dc.DeleteDC()
- win32gui.ReleaseDC(hwnd, window_dc)
- win32gui.DeleteObject(bitmap.GetHandle())
- except win32ui.error:
- pass
+ # Cleanup DC and handle
+ dc_object.DeleteDC()
+ compatible_dc.DeleteDC()
+ win32gui.ReleaseDC(hwnd, window_dc)
+ win32gui.DeleteObject(bitmap.GetHandle())
return image, False
def recover_window(self, captured_window_title: str, autosplit: AutoSplit):
diff --git a/src/capture_method/interface.py b/src/capture_method/CaptureMethodBase.py
similarity index 97%
rename from src/capture_method/interface.py
rename to src/capture_method/CaptureMethodBase.py
index 253eb6cc..3f6d1212 100644
--- a/src/capture_method/interface.py
+++ b/src/capture_method/CaptureMethodBase.py
@@ -12,7 +12,7 @@
# pylint: disable=no-self-use,unnecessary-dunder-call
-class CaptureMethodInterface():
+class CaptureMethodBase():
def __init__(self, autosplit: AutoSplit | None = None):
pass
diff --git a/src/capture_method/VideoCaptureDeviceCaptureMethod.py b/src/capture_method/VideoCaptureDeviceCaptureMethod.py
index 612697ff..20e2fb18 100644
--- a/src/capture_method/VideoCaptureDeviceCaptureMethod.py
+++ b/src/capture_method/VideoCaptureDeviceCaptureMethod.py
@@ -5,7 +5,7 @@
import cv2
-from capture_method.interface import CaptureMethodInterface
+from capture_method.CaptureMethodBase import CaptureMethodBase
from error_messages import CREATE_NEW_ISSUE_MESSAGE, exception_traceback
from utils import is_valid_image
@@ -13,7 +13,7 @@
from AutoSplit import AutoSplit
-class VideoCaptureDeviceCaptureMethod(CaptureMethodInterface):
+class VideoCaptureDeviceCaptureMethod(CaptureMethodBase):
capture_device: cv2.VideoCapture
capture_thread: Thread | None
last_captured_frame: cv2.Mat | None = None
diff --git a/src/capture_method/WindowsGraphicsCaptureMethod.py b/src/capture_method/WindowsGraphicsCaptureMethod.py
index 8a687078..7fca63d8 100644
--- a/src/capture_method/WindowsGraphicsCaptureMethod.py
+++ b/src/capture_method/WindowsGraphicsCaptureMethod.py
@@ -13,7 +13,7 @@
from winsdk.windows.graphics.imaging import BitmapBufferAccessMode, SoftwareBitmap
from winsdk.windows.media.capture import MediaCapture
-from capture_method.interface import CaptureMethodInterface
+from capture_method.CaptureMethodBase import CaptureMethodBase
from utils import WINDOWS_BUILD_NUMBER, is_valid_hwnd
if TYPE_CHECKING:
@@ -22,7 +22,7 @@
WGC_NO_BORDER_MIN_BUILD = 20348
-class WindowsGraphicsCaptureMethod(CaptureMethodInterface):
+class WindowsGraphicsCaptureMethod(CaptureMethodBase):
size: SizeInt32
frame_pool: Direct3D11CaptureFramePool | None = None
session: GraphicsCaptureSession | None = None
diff --git a/src/capture_method/__init__.py b/src/capture_method/__init__.py
index 8a6cd28f..2957bef2 100644
--- a/src/capture_method/__init__.py
+++ b/src/capture_method/__init__.py
@@ -6,14 +6,13 @@
from enum import Enum, EnumMeta, unique
from typing import TYPE_CHECKING, TypedDict
-import cv2
from pygrabber import dshow_graph
from winsdk.windows.media.capture import MediaCapture
from capture_method.BitBltCaptureMethod import BitBltCaptureMethod
+from capture_method.CaptureMethodBase import CaptureMethodBase
from capture_method.DesktopDuplicationCaptureMethod import DesktopDuplicationCaptureMethod
from capture_method.ForceFullContentRenderingCaptureMethod import ForceFullContentRenderingCaptureMethod
-from capture_method.interface import CaptureMethodInterface
from capture_method.VideoCaptureDeviceCaptureMethod import VideoCaptureDeviceCaptureMethod
from capture_method.WindowsGraphicsCaptureMethod import WindowsGraphicsCaptureMethod
from utils import WINDOWS_BUILD_NUMBER
@@ -37,7 +36,7 @@ class CaptureMethodInfo():
name: str
short_description: str
description: str
- implementation: type[CaptureMethodInterface]
+ implementation: type[CaptureMethodBase]
class CaptureMethodMeta(EnumMeta):
@@ -102,7 +101,7 @@ def __getitem__(self, key: CaptureMethodEnum):
name="None",
short_description="",
description="",
- implementation=CaptureMethodInterface
+ implementation=CaptureMethodBase
)
CAPTURE_METHODS = CaptureMethodDict({
@@ -209,19 +208,23 @@ async def get_all_video_capture_devices() -> list[CameraInfo]:
named_video_inputs = dshow_graph.FilterGraph().get_input_devices()
async def get_camera_info(index: int, device_name: str):
- video_capture = cv2.VideoCapture(index)
- video_capture.setExceptionMode(True)
backend = ""
- try:
- # https://docs.opencv.org/3.4/d4/d15/group__videoio__flags__base.html#ga023786be1ee68a9105bf2e48c700294d
- backend = video_capture.getBackendName() # STS_ASSERT
- video_capture.grab() # STS_ERROR
- except cv2.error as error:
- return CameraInfo(index, device_name, True, backend) \
- if error.code in (cv2.Error.STS_ERROR, cv2.Error.STS_ASSERT) \
- else None
- finally:
- video_capture.release()
+ # Probing freezes some devices (like GV-USB2 and AverMedia) if already in use
+ # https://github.com/Toufool/Auto-Split/issues/169
+ # FIXME: Maybe offer the option to the user to obtain more info about their devies?
+ # Off by default. With a tooltip to explain the risk.
+ # video_capture = cv2.VideoCapture(index)
+ # video_capture.setExceptionMode(True)
+ # try:
+ # # https://docs.opencv.org/3.4/d4/d15/group__videoio__flags__base.html#ga023786be1ee68a9105bf2e48c700294d
+ # backend = video_capture.getBackendName() # STS_ASSERT
+ # video_capture.grab() # STS_ERROR
+ # except cv2.error as error:
+ # return CameraInfo(index, device_name, True, backend) \
+ # if error.code in (cv2.Error.STS_ERROR, cv2.Error.STS_ASSERT) \
+ # else None
+ # finally:
+ # video_capture.release()
return CameraInfo(index, device_name, False, backend)
future = asyncio.gather(*[
diff --git a/src/hotkeys.py b/src/hotkeys.py
index a7aff538..fb23600a 100644
--- a/src/hotkeys.py
+++ b/src/hotkeys.py
@@ -225,7 +225,7 @@ def is_valid_hotkey_name(hotkey_name: str):
def set_hotkey(autosplit: AutoSplit, hotkey: Hotkey, preselected_hotkey_name: str = ""):
if autosplit.SettingsWidget:
# Unfocus all fields
- cast(QtWidgets.QDialog, autosplit.SettingsWidget).setFocus()
+ cast(QtWidgets.QWidget, autosplit.SettingsWidget).setFocus()
getattr(autosplit.SettingsWidget, f"set_{hotkey}_hotkey_button").setText(PRESS_A_KEY_TEXT)
# Disable some buttons
diff --git a/src/menu_bar.py b/src/menu_bar.py
index 37dd5602..8cbcb36c 100644
--- a/src/menu_bar.py
+++ b/src/menu_bar.py
@@ -118,7 +118,7 @@ def get_capture_method_index(capture_method: str | CaptureMethodEnum):
return 0
-class __SettingsWidget(QtWidgets.QDialog, settings_ui.Ui_DialogSettings):
+class __SettingsWidget(QtWidgets.QWidget, settings_ui.Ui_SettingsWidget):
__video_capture_devices: list[CameraInfo] = []
"""
Used to temporarily store the existing cameras,
@@ -180,10 +180,7 @@ def __set_all_capture_devices(self):
else:
self.capture_device_combobox.setPlaceholderText("No device found.")
- def __init__(self, autosplit: AutoSplit):
- super().__init__()
- self.setupUi(self)
- self.autosplit = autosplit
+ def __apply_os_specific_ui_fixes(self):
# Spinbox frame disappears and reappears on Windows 11. It's much cleaner to just disable them.
# Most likely related: https://bugreports.qt.io/browse/QTBUG-95215?jql=labels%20%3D%20Windows11
# Arrow buttons tend to move a lot as well
@@ -192,13 +189,27 @@ def __init__(self, autosplit: AutoSplit):
self.default_similarity_threshold_spinbox.setFrame(False)
self.default_delay_time_spinbox.setFrame(False)
self.default_pause_time_spinbox.setFrame(False)
- # Don't autofocus any particular field
- self.setFocus()
+ def __set_readme_link(self):
self.custom_image_settings_info_label.setText(
self.custom_image_settings_info_label
.text()
.format(GITHUB_REPOSITORY=GITHUB_REPOSITORY))
+ # HACK: This is a workaround because custom_image_settings_info_label
+ # simply will not open links with a left click no matter what we tried.
+ self.readme_link_button.clicked.connect(
+ lambda: webbrowser.open(f"https://github.com/{GITHUB_REPOSITORY}#readme"))
+ self.readme_link_button.setStyleSheet("border: 0px; background-color:rgba(0,0,0,0%);")
+
+ def __init__(self, autosplit: AutoSplit):
+ super().__init__()
+ self.setupUi(self)
+ self.autosplit = autosplit
+ self.__apply_os_specific_ui_fixes()
+ self.__set_readme_link()
+ # Don't autofocus any particular field
+ self.setFocus()
+
# region Build the Capture method combobox
capture_method_values = CAPTURE_METHODS.values()
@@ -292,13 +303,13 @@ def hotkey_connect(hotkey: Hotkey):
def open_settings(autosplit: AutoSplit):
- if not autosplit.SettingsWidget or cast(QtWidgets.QDialog, autosplit.SettingsWidget).isHidden():
+ if not autosplit.SettingsWidget or cast(QtWidgets.QWidget, autosplit.SettingsWidget).isHidden():
autosplit.SettingsWidget = __SettingsWidget(autosplit)
def get_default_settings_from_ui(autosplit: AutoSplit):
- temp_dialog = QtWidgets.QDialog()
- default_settings_dialog = settings_ui.Ui_DialogSettings()
+ temp_dialog = QtWidgets.QWidget()
+ default_settings_dialog = settings_ui.Ui_SettingsWidget()
default_settings_dialog.setupUi(temp_dialog)
default_settings: user_profile.UserProfileDict = {
"split_hotkey": default_settings_dialog.split_input.text(),
diff --git a/src/utils.py b/src/utils.py
index b4147944..7f6547c7 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -102,9 +102,8 @@ def wrapped(*args: Any, **kwargs: Any):
"""The directory of either AutoSplit.exe or AutoSplit.py"""
# Shared strings
-# DIRTY_VERSION_EXTENSION = ""
-DIRTY_VERSION_EXTENSION = "-" + AUTOSPLIT_BUILD_NUMBER
-"""Set DIRTY_VERSION_EXTENSION to an empty string to generate a clean version number"""
-AUTOSPLIT_VERSION = "2.0.0-alpha.4" + DIRTY_VERSION_EXTENSION
+# Set AUTOSPLIT_BUILD_NUMBER to an empty string to generate a clean version number
+# AUTOSPLIT_BUILD_NUMBER = "" # pyright: ignore[reportConstantRedefinition] # noqa: F811
+AUTOSPLIT_VERSION = "2.0.0-alpha.5" + (f"-{AUTOSPLIT_BUILD_NUMBER}" if AUTOSPLIT_BUILD_NUMBER else "")
START_AUTO_SPLITTER_TEXT = "Start Auto Splitter"
GITHUB_REPOSITORY = AUTOSPLIT_GITHUB_REPOSITORY