From 248f856af3fab26912e5784131c5a866fe719499 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 24 Jun 2026 22:26:16 +0200
Subject: [PATCH 1/4] Update pyproject and lock file
---
pyproject.toml | 18 ++----------------
uv.lock | 13 -------------
2 files changed, 2 insertions(+), 29 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 51c9fc3..34b9343 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,7 +32,7 @@ dev = [
]
build = ["build", "setuptools>=77.0.3"]
test = ["coverage>=7.2.0", "pytest-qt", "pytest-timeout", "pytest>=6.0.0"]
-lint = ["isort", "pyright", "ruff"]
+lint = ["pyright", "ruff"]
[project.urls]
Homepage = "https://github.com/vkbo/subtle"
@@ -51,17 +51,8 @@ include = ["subtle_gui*"]
[tool.setuptools.package-data]
subtle_gui = ["assets/*"]
-[tool.isort]
-py_version = "311"
-line_length = 99
-wrap_length = 79
-multi_line_output = 5
-force_grid_wrap = 0
-lines_between_types = 1
-forced_separate = ["tests.*", "PyQt6.*"]
-
[tool.ruff]
-line-length = 99
+line-length = 120
[tool.ruff.lint]
preview = false
@@ -88,11 +79,6 @@ select = [
]
ignore = [
"ANN401", # any-type
- "E221", # multiple-spaces-before-operator
- "E226", # missing-whitespace-around-arithmetic-operator
- "E228", # missing-whitespace-around-modulo-operator
- "E241", # multiple-spaces-after-comma
- "E272", # multiple-spaces-before-keyword
"PLC0415", # import-outside-top-level
"PLC1901", # compare-to-empty-string
"PLW0108", # unnecessary-lambda
diff --git a/uv.lock b/uv.lock
index 2e1a9b7..1194cd3 100644
--- a/uv.lock
+++ b/uv.lock
@@ -118,15 +118,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
-[[package]]
-name = "isort"
-version = "8.0.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" },
-]
-
[[package]]
name = "nodeenv"
version = "1.10.0"
@@ -346,7 +337,6 @@ build = [
dev = [
{ name = "build" },
{ name = "coverage" },
- { name = "isort" },
{ name = "pyright" },
{ name = "pytest" },
{ name = "pytest-qt" },
@@ -355,7 +345,6 @@ dev = [
{ name = "setuptools" },
]
lint = [
- { name = "isort" },
{ name = "pyright" },
{ name = "ruff" },
]
@@ -380,7 +369,6 @@ build = [
dev = [
{ name = "build" },
{ name = "coverage", specifier = ">=7.2.0" },
- { name = "isort" },
{ name = "pyright" },
{ name = "pytest", specifier = ">=6.0.0" },
{ name = "pytest-qt" },
@@ -389,7 +377,6 @@ dev = [
{ name = "setuptools", specifier = ">=77.0.3" },
]
lint = [
- { name = "isort" },
{ name = "pyright" },
{ name = "ruff" },
]
From e901ae0b90ca0a990463495176cdfda14defa6c8 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 24 Jun 2026 22:29:55 +0200
Subject: [PATCH 2/4] Apply formatting
---
subtle.py | 2 +
subtle_gui/__init__.py | 37 +++++++++--------
subtle_gui/common.py | 24 ++++-------
subtle_gui/config.py | 38 +++++++----------
subtle_gui/constants.py | 7 ++--
subtle_gui/core/icons.py | 40 +++++++-----------
subtle_gui/core/media.py | 3 +-
subtle_gui/core/mediafile.py | 20 ++++-----
subtle_gui/core/mkvextract.py | 2 +-
subtle_gui/core/spellcheck.py | 3 +-
subtle_gui/formats/base.py | 14 +++----
subtle_gui/formats/pgssubs.py | 77 +++++++++++++++++------------------
subtle_gui/formats/srtsubs.py | 5 +--
subtle_gui/formats/ssasubs.py | 25 +++++-------
subtle_gui/gui/filetree.py | 6 +--
subtle_gui/gui/highlighter.py | 15 ++-----
subtle_gui/gui/imageviewer.py | 2 +-
subtle_gui/gui/mediaview.py | 53 +++++++++++++++---------
subtle_gui/gui/subsview.py | 25 +++++-------
subtle_gui/gui/texteditor.py | 14 ++-----
subtle_gui/gui/toolspanel.py | 21 ++++++----
subtle_gui/guimain.py | 2 +-
subtle_gui/ocr/base.py | 2 +-
subtle_gui/ocr/tesseract.py | 9 +---
subtle_gui/shared.py | 10 +----
tests/test_common.py | 14 ++++---
26 files changed, 215 insertions(+), 255 deletions(-)
diff --git a/subtle.py b/subtle.py
index 5f15143..e551d50 100755
--- a/subtle.py
+++ b/subtle.py
@@ -3,6 +3,7 @@
Subtle – Start Script
=====================
"""
+
import os
import sys
@@ -10,4 +11,5 @@
if __name__ == "__main__":
import subtle_gui
+
subtle_gui.main(sys.argv[1:])
diff --git a/subtle_gui/__init__.py b/subtle_gui/__init__.py
index 6208718..0f5842f 100644
--- a/subtle_gui/__init__.py
+++ b/subtle_gui/__init__.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import getopt
@@ -38,14 +39,14 @@
# Package Meta
# ============
-__package__ = "subtle_gui"
-__copyright__ = "Copyright (C) Veronica Berglyd Olsen"
-__license__ = "GPLv3"
-__author__ = "Veronica Berglyd Olsen"
+__package__ = "subtle_gui"
+__copyright__ = "Copyright (C) Veronica Berglyd Olsen"
+__license__ = "GPLv3"
+__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
-__email__ = "code@vkbo.net"
-__version__ = "26.1.1"
-__date__ = "2026-06-24"
+__email__ = "code@vkbo.net"
+__version__ = "26.1.1"
+__date__ = "2026-06-24"
logger = logging.getLogger(__name__)
@@ -58,12 +59,12 @@
SHARED = SharedData()
# ANSI Colours
-RED = "\033[91m"
-GREEN = "\033[92m"
+RED = "\033[91m"
+GREEN = "\033[92m"
YELLOW = "\033[93m"
-BLUE = "\033[94m"
-WHITE = "\033[97m"
-END = "\033[0m"
+BLUE = "\033[94m"
+WHITE = "\033[97m"
+END = "\033[0m"
# Log Format Components
TIME = "[{asctime:}]"
@@ -75,7 +76,7 @@
# Read Environment
FORCE_COLOR = bool(os.environ.get("FORCE_COLOR"))
-NO_COLOR = bool(os.environ.get("NO_COLOR"))
+NO_COLOR = bool(os.environ.get("NO_COLOR"))
def main(sysArgs: list | None = None) -> GuiMain | None:
@@ -113,7 +114,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Defaults
logLevel = logging.WARN
fmtColor = FORCE_COLOR
- fmtLong = False
+ fmtLong = False
# Parse Options
try:
@@ -141,10 +142,10 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
if fmtColor:
# This will overwrite the default level names, and also ensure that
# they can be converted back to integer levels
- logging.addLevelName(logging.DEBUG, f"{BLUE}DEBUG{END}")
- logging.addLevelName(logging.INFO, f"{GREEN}INFO{END}")
- logging.addLevelName(logging.WARNING, f"{YELLOW}WARNING{END}")
- logging.addLevelName(logging.ERROR, f"{RED}ERROR{END}")
+ logging.addLevelName(logging.DEBUG, f"{BLUE}DEBUG{END}")
+ logging.addLevelName(logging.INFO, f"{GREEN}INFO{END}")
+ logging.addLevelName(logging.WARNING, f"{YELLOW}WARNING{END}")
+ logging.addLevelName(logging.ERROR, f"{RED}ERROR{END}")
logging.addLevelName(logging.CRITICAL, f"{RED}CRITICAL{END}")
logTxt = f"{LVLC} {TEXT}" if fmtColor else f"{LVLP} {TEXT}"
diff --git a/subtle_gui/common.py b/subtle_gui/common.py
index ed6a594..62b4ee8 100644
--- a/subtle_gui/common.py
+++ b/subtle_gui/common.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import json
@@ -77,8 +78,7 @@ def regexCleanup(text: str, patterns: list[tuple[re.Pattern, str]]) -> str:
"""Replaces all occurrences of match group 1 in patterns."""
for regEx, value in patterns:
matches = [
- (s, e, value) for match in regEx.finditer(text)
- if (s := match.start(1)) >= 0 and (e := match.end(1)) >= 0
+ (s, e, value) for match in regEx.finditer(text) if (s := match.start(1)) >= 0 and (e := match.end(1)) >= 0
]
for s, e, value in reversed(matches):
text = text[:s] + value + text[e:]
@@ -120,7 +120,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
elif first in ("{", "["):
n += 1
- indent = "\n"+" "*n
+ indent = "\n" + " " * n
if n > nmax and nmax > 0:
buffer.append(chunk)
else:
@@ -128,7 +128,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
elif first in ("}", "]"):
n -= 1
- indent = "\n"+" "*n
+ indent = "\n" + " " * n
if n >= nmax and nmax > 0:
buffer.append(chunk)
else:
@@ -148,8 +148,8 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
def formatTS(value: int) -> str:
"""Format millisecond integer as HH:MM:SS,uuu timestamp."""
- i, f = value//1000, value%1000
- return f"{i//3600:02d}:{i%3600//60:02d}:{i%60:02d},{f:03d}"
+ i, f = value // 1000, value % 1000
+ return f"{i // 3600:02d}:{i % 3600 // 60:02d}:{i % 60:02d},{f:03d}"
def decodeTS(value: str | None, default: int = 0, fmt: T_Subs = "SRT") -> int:
@@ -158,21 +158,13 @@ def decodeTS(value: str | None, default: int = 0, fmt: T_Subs = "SRT") -> int:
if fmt == "SRT" and len(value) >= 12:
if value[2] == ":" and value[5] == ":" and value[8] in ".,":
try:
- return (
- 3600000*int(value[0:2])
- + 60000*int(value[3:5])
- + int(value[6:8] + value[9:12])
- )
+ return 3600000 * int(value[0:2]) + 60000 * int(value[3:5]) + int(value[6:8] + value[9:12])
except Exception:
pass
elif fmt == "SSA" and len(value) == 10:
if value[1] == ":" and value[4] == ":" and value[7] in ":.,":
try:
- return (
- 3600000*int(value[0])
- + 60000*int(value[2:4])
- + 10*int(value[5:7] + value[8:10])
- )
+ return 3600000 * int(value[0]) + 60000 * int(value[2:4]) + 10 * int(value[5:7] + value[8:10])
except Exception:
pass
return default
diff --git a/subtle_gui/config.py b/subtle_gui/config.py
index bbfe439..bf3c06f 100644
--- a/subtle_gui/config.py
+++ b/subtle_gui/config.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import json
@@ -31,10 +32,7 @@
from subtle_gui.common import jsonEncode
-from PyQt6.QtCore import (
- PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QSize,
- QStandardPaths, QSysInfo
-)
+from PyQt6.QtCore import PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QSize, QStandardPaths, QSysInfo
from PyQt6.QtGui import QFont
from PyQt6.QtWidgets import QApplication
@@ -58,28 +56,23 @@
"guiFont": "",
"fixedFont": "",
"subsFont": "",
- }
+ },
}
T_Fonts = Literal["gui"] | Literal["fixed"] | Literal["subs"]
class Config:
-
def __init__(self) -> None:
self._data: dict[str, dict] = deepcopy(DEFAULTS)
- self.appName = "Subtle"
+ self.appName = "Subtle"
self.appHandle = "subtle"
# Set Paths
- confRoot = Path(QStandardPaths.writableLocation(
- QStandardPaths.StandardLocation.ConfigLocation)
- )
- cacheRoot = Path(QStandardPaths.writableLocation(
- QStandardPaths.StandardLocation.CacheLocation)
- )
+ confRoot = Path(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.ConfigLocation))
+ cacheRoot = Path(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.CacheLocation))
self._confPath = confRoot.absolute() / self.appHandle # The user config location
self._cachePath = cacheRoot.absolute() / self.appHandle # The user cache location
self._homePath = Path.home().absolute() # The user's home directory
@@ -95,19 +88,19 @@ def __init__(self) -> None:
self.subsFont = QFont()
# Check Qt6 Versions
- self.verQtString = QT_VERSION_STR
- self.verQtValue = QT_VERSION
+ self.verQtString = QT_VERSION_STR
+ self.verQtValue = QT_VERSION
self.verPyQtString = PYQT_VERSION_STR
- self.verPyQtValue = PYQT_VERSION
+ self.verPyQtValue = PYQT_VERSION
# Check Python Version
self.verPyString = sys.version.split()[0]
# Check OS Type
- self.osType = sys.platform
- self.osLinux = False
+ self.osType = sys.platform
+ self.osLinux = False
self.osWindows = False
- self.osDarwin = False
+ self.osDarwin = False
self.osUnknown = False
if self.osType.startswith("linux"):
self.osLinux = True
@@ -119,7 +112,7 @@ def __init__(self) -> None:
self.osUnknown = True
# Other System Info
- self.hostName = QSysInfo.machineHostName()
+ self.hostName = QSysInfo.machineHostName()
self.kernelVer = QSysInfo.kernelVersion()
return
@@ -172,7 +165,6 @@ def assetPath(self, resource: str, kind: str | None = None) -> Path:
path /= kind
return path / resource
-
##
# Setters
##
@@ -193,7 +185,7 @@ def setSizes(self, key: str, value: list[int]) -> None:
return
def setFontSpec(self, target: T_Fonts, font: QFont | str) -> None:
- """Set the font """
+ """Set the font"""
if isinstance(font, str):
temp = QFont()
temp.fromString(font)
@@ -249,7 +241,7 @@ def fonts(self, app: QApplication) -> None:
self.setFontSpec("subs", font)
else:
temp = app.font()
- temp.setPointSizeF(3.0*temp.pointSizeF())
+ temp.setPointSizeF(3.0 * temp.pointSizeF())
self.setFontSpec("subs", temp)
return
diff --git a/subtle_gui/constants.py b/subtle_gui/constants.py
index 5baa2e3..b4f85ae 100644
--- a/subtle_gui/constants.py
+++ b/subtle_gui/constants.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
from enum import Enum
@@ -32,18 +33,16 @@ def trConst(text: str) -> str:
class MediaType(Enum):
-
VIDEO = 0
AUDIO = 1
- SUBS = 2
+ SUBS = 2
OTHER = 4
class GuiLabels:
-
MEDIA_TYPES: Final[dict[MediaType, str]] = {
MediaType.VIDEO: QT_TRANSLATE_NOOP("Constant", "Video"),
MediaType.AUDIO: QT_TRANSLATE_NOOP("Constant", "Audio"),
- MediaType.SUBS: QT_TRANSLATE_NOOP("Constant", "Subtitles"),
+ MediaType.SUBS: QT_TRANSLATE_NOOP("Constant", "Subtitles"),
MediaType.OTHER: QT_TRANSLATE_NOOP("Constant", "Other"),
}
diff --git a/subtle_gui/core/icons.py b/subtle_gui/core/icons.py
index 1eb9131..e548a65 100644
--- a/subtle_gui/core/icons.py
+++ b/subtle_gui/core/icons.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -32,29 +33,26 @@
RAW_SVG = {
"up": (
b''
),
"down": (
b''
),
- "italic": (
- b''
- ),
+ "italic": (b''),
"note": (
b''
),
@@ -67,7 +65,6 @@
class GuiIcons:
-
def __init__(self) -> None:
palette = QApplication.palette()
self._cache: dict[str, QIcon] = {}
@@ -78,17 +75,12 @@ def icon(self, key: str) -> QIcon:
"""Return an icon, either from the cache or generate it."""
if key not in self._cache:
self._cache[key] = QIcon(
- _IconEngine(
- BASE
- .replace(b"{content}", RAW_SVG.get(key, b""))
- .replace(b"{foreground}", self._color)
- )
+ _IconEngine(BASE.replace(b"{content}", RAW_SVG.get(key, b"")).replace(b"{foreground}", self._color))
)
return self._cache[key]
class _IconEngine(QIconEngine):
-
def __init__(self, data: bytes) -> None:
super().__init__()
self._data = data
@@ -107,9 +99,7 @@ def pixmap(self, size: QSize, mode: QIcon.Mode, state: QIcon.State) -> QPixmap:
self.paint(painter, QRect(QPoint(0, 0), size), mode, state)
return pix
- def paint(
- self, painter: QPainter | None, rect: QRect, mode: QIcon.Mode, state: QIcon.State
- ) -> None:
+ def paint(self, painter: QPainter | None, rect: QRect, mode: QIcon.Mode, state: QIcon.State) -> None:
"""SVG icon painter."""
renderer = QSvgRenderer(self._data)
renderer.render(painter, QRectF(rect))
diff --git a/subtle_gui/core/media.py b/subtle_gui/core/media.py
index 89a3ac6..e0e9625 100644
--- a/subtle_gui/core/media.py
+++ b/subtle_gui/core/media.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -44,7 +45,6 @@
class MediaData(QObject):
-
mediaDataCleared = pyqtSignal()
newMediaLoaded = pyqtSignal()
newTrackSelected = pyqtSignal()
@@ -110,7 +110,6 @@ def getTrack(self, trackID: str) -> MediaTrack | None:
class MediaTrack:
-
def __init__(self, media: MediaData, info: dict) -> None:
self._media = media
self._info = {}
diff --git a/subtle_gui/core/mediafile.py b/subtle_gui/core/mediafile.py
index f11ed7b..27775ad 100644
--- a/subtle_gui/core/mediafile.py
+++ b/subtle_gui/core/mediafile.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import json
@@ -38,17 +39,16 @@
class ContainerType(IntEnum):
-
- UNKNOWN = 0
- AVI = 5
- MATROSKA = 17
+ UNKNOWN = 0
+ AVI = 5
+ MATROSKA = 17
MPEG_STREAM = 21
- OGM = 23
- PGSSUP = 24
- QUICKTIME = 25
- SRT = 27
- SSA_ASS = 28
- VOBSUB = 34
+ OGM = 23
+ PGSSUP = 24
+ QUICKTIME = 25
+ SRT = 27
+ SSA_ASS = 28
+ VOBSUB = 34
EXTRACTABLE = (
diff --git a/subtle_gui/core/mkvextract.py b/subtle_gui/core/mkvextract.py
index 6b05a3f..48c0b61 100644
--- a/subtle_gui/core/mkvextract.py
+++ b/subtle_gui/core/mkvextract.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -35,7 +36,6 @@
class MkvExtract(QObject):
-
processProgress = pyqtSignal(int)
processDone = pyqtSignal(int)
diff --git a/subtle_gui/core/spellcheck.py b/subtle_gui/core/spellcheck.py
index 6375162..05de462 100644
--- a/subtle_gui/core/spellcheck.py
+++ b/subtle_gui/core/spellcheck.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import json
@@ -129,6 +130,7 @@ def listDictionaries(self) -> list[tuple[str, str]]:
lang = []
try:
import enchant
+
tags = [x for x, _ in enchant.list_dicts()]
lang = [(x, f"{QLocale(x).nativeLanguageName().title()} [{x}]") for x in set(tags)]
except Exception:
@@ -171,7 +173,6 @@ def add_to_session(self, word: str) -> None:
class UserDictionary:
-
def __init__(self) -> None:
self._words = set()
return
diff --git a/subtle_gui/formats/base.py b/subtle_gui/formats/base.py
index 9a1e1b2..d57b1d1 100644
--- a/subtle_gui/formats/base.py
+++ b/subtle_gui/formats/base.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -37,7 +38,6 @@
class SubtitlesBase(ABC):
-
__slots__ = ("_frames", "_path")
def __init__(self) -> None:
@@ -65,7 +65,7 @@ def iterFrames(self) -> Iterable[FrameBase]:
def checkFrames(self) -> None:
"""Check all frames to ensure that time stamps make sense."""
for i in range(len(self._frames) - 1):
- cf, nf = self._frames[i:i+2]
+ cf, nf = self._frames[i : i + 2]
if cf.end < cf.start:
te = cf.end
ts = cf.start
@@ -73,7 +73,10 @@ def checkFrames(self) -> None:
cf.setRange(end=max(ts + 90, tn))
logger.warning(
"Correcting end time for frame %d to %s (%+.3fs from %+.3fs)",
- i+1, formatTS(tn), (tn-ts)/1000.0, (te-ts)/1000.0
+ i + 1,
+ formatTS(tn),
+ (tn - ts) / 1000.0,
+ (te - ts) / 1000.0,
)
return
@@ -114,14 +117,11 @@ def _copyFrames(self, frameType: type[FrameBase], other: SubtitlesBase) -> None:
subclasses by passing its own FrameBase implementation as
frameType.
"""
- self._frames = [
- frameType.fromFrame(n, frame) for n, frame in enumerate(other.iterFrames())
- ]
+ self._frames = [frameType.fromFrame(n, frame) for n, frame in enumerate(other.iterFrames())]
return
class FrameBase(ABC):
-
__slots__ = ("_end", "_index", "_start", "_text")
def __init__(self, index: int) -> None:
diff --git a/subtle_gui/formats/pgssubs.py b/subtle_gui/formats/pgssubs.py
index 8d20036..85efcbe 100644
--- a/subtle_gui/formats/pgssubs.py
+++ b/subtle_gui/formats/pgssubs.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -38,10 +39,10 @@
logger = logging.getLogger(__name__)
COMP_NORMAL = 0x00
-COMP_ACQ = 0x40
-COMP_EPOCH = 0x80
+COMP_ACQ = 0x40
+COMP_EPOCH = 0x80
-IMAGE_FILL = 0xff242424
+IMAGE_FILL = 0xFF242424
CROP_MARGINS = QMargins(20, 20, 20, 20)
@@ -146,7 +147,6 @@ def _readData(self, path: Path) -> None:
class PGSFrame(FrameBase):
-
def __init__(self, index: int, ds: DisplaySet) -> None:
super().__init__(index=index)
self._ds = ds
@@ -174,7 +174,6 @@ def getImage(self) -> QImage:
class DisplaySet:
-
__slots__ = ("_image", "_ods", "_pcs", "_pds", "_wds")
def __init__(self, pcs: PresentationSegment) -> None:
@@ -281,23 +280,21 @@ def render(self, crop: bool = True) -> QImage:
if (b1 := data[p]) > 0x00:
raw += palette[b1]
p += 1
- elif (b2 := data[p+1]) <= 0x3f:
+ elif (b2 := data[p + 1]) <= 0x3F:
raw += palette[0] * b2
p += 2
- elif b2 <= 0x7f:
- raw += palette[0] * ((b2 & 0x3f)*256 + data[p+2])
+ elif b2 <= 0x7F:
+ raw += palette[0] * ((b2 & 0x3F) * 256 + data[p + 2])
p += 3
- elif b2 <= 0xbf:
- raw += palette[data[p+2]] * (b2 & 0x3f)
+ elif b2 <= 0xBF:
+ raw += palette[data[p + 2]] * (b2 & 0x3F)
p += 3
else:
- raw += palette[data[p+3]] * ((b2 & 0x3f)*256 + data[p+2])
+ raw += palette[data[p + 3]] * ((b2 & 0x3F) * 256 + data[p + 2])
p += 4
frame = frame.united(QRect(offset, box))
- painter.drawImage(offset, QImage(
- bytes(raw), box.width(), box.height(), QImage.Format.Format_ARGB32
- ))
+ painter.drawImage(offset, QImage(bytes(raw), box.width(), box.height(), QImage.Format.Format_ARGB32))
painter.end()
if crop:
@@ -309,7 +306,6 @@ def render(self, crop: bool = True) -> QImage:
class BaseSegment(ABC):
-
__slots__ = ("_data", "_ts", "_valid")
def __init__(self, ts: int, data: bytes) -> None:
@@ -320,10 +316,7 @@ def __init__(self, ts: int, data: bytes) -> None:
return
def __repr__(self) -> str:
- return (
- f"<{self.__class__.__name__}: t={self._ts/90000:.3f} "
- f"size={len(self._data)} valid={self._valid}>"
- )
+ return f"<{self.__class__.__name__}: t={self._ts / 90000:.3f} size={len(self._data)} valid={self._valid}>"
@property
def valid(self) -> bool:
@@ -348,7 +341,7 @@ class PresentationSegment(BaseSegment):
def validate(self) -> None:
"""Length is 11 + n*8"""
size = len(self._data)
- self._valid = (size >= 11 and size % 8 == 3)
+ self._valid = size >= 11 and size % 8 == 3
return
@property
@@ -410,12 +403,12 @@ def compObjects(self) -> Iterable[tuple[int, int, QPoint]]:
pos = 11
size = len(self._data)
while pos < size:
- o = int.from_bytes(self._data[pos:pos+2]) # Object ID
- w = int.from_bytes(self._data[pos+2:pos+3]) # Window ID
- f = int.from_bytes(self._data[pos+3:pos+4]) # Crop flag, only used for offset
- x = int.from_bytes(self._data[pos+4:pos+6]) # Horizontal position
- y = int.from_bytes(self._data[pos+6:pos+8]) # Vertical position
- pos += (16 if f == 0x40 else 8)
+ o = int.from_bytes(self._data[pos : pos + 2]) # Object ID
+ w = int.from_bytes(self._data[pos + 2 : pos + 3]) # Window ID
+ f = int.from_bytes(self._data[pos + 3 : pos + 4]) # Crop flag, only used for offset
+ x = int.from_bytes(self._data[pos + 4 : pos + 6]) # Horizontal position
+ y = int.from_bytes(self._data[pos + 6 : pos + 8]) # Vertical position
+ pos += 16 if f == 0x40 else 8
yield o, w, QPoint(x, y)
return
@@ -432,7 +425,7 @@ class WindowSegment(BaseSegment):
def validate(self) -> None:
"""Length is 1 + n*9"""
- self._valid = (len(self._data) % 9 == 1)
+ self._valid = len(self._data) % 9 == 1
return
@property
@@ -442,11 +435,14 @@ def count(self) -> int:
def windows(self) -> Iterable[tuple[int, QRect]]:
"""Iterate over windows defined in the segment."""
for pos in range(1, len(self._data), 9):
- yield int.from_bytes(self._data[pos:pos+1]), QRect(
- int.from_bytes(self._data[pos+1:pos+3]),
- int.from_bytes(self._data[pos+3:pos+5]),
- int.from_bytes(self._data[pos+5:pos+7]),
- int.from_bytes(self._data[pos+7:pos+9]),
+ yield (
+ int.from_bytes(self._data[pos : pos + 1]),
+ QRect(
+ int.from_bytes(self._data[pos + 1 : pos + 3]),
+ int.from_bytes(self._data[pos + 3 : pos + 5]),
+ int.from_bytes(self._data[pos + 5 : pos + 7]),
+ int.from_bytes(self._data[pos + 7 : pos + 9]),
+ ),
)
return
@@ -456,12 +452,13 @@ class PaletteSegment(BaseSegment):
This segment is used to define a palette for color conversion.
"""
+
__slots__ = ("_col",)
def validate(self) -> None:
"""Length is 2 + n*5"""
size = len(self._data)
- self._valid = (size >= 7 and size % 5 == 2)
+ self._valid = size >= 7 and size % 5 == 2
self._col: dict[int, QColor] = {}
return
@@ -482,13 +479,13 @@ def palette(self) -> list[bytes]:
"""
palette = [IMAGE_FILL.to_bytes(4, "little")] * 256
for pos in range(2, len(self._data), 5):
- if a := self._data[pos+4]: # We ignore transparent colours
- y = self._data[pos+1] - 16
- cr = self._data[pos+2] - 128
- cb = self._data[pos+3] - 128
- r = round(1.164*y + 1.793*cr)
- g = round(1.164*y - 0.213*cb - 0.533*cr)
- b = round(1.164*y + 2.112*cb)
+ if a := self._data[pos + 4]: # We ignore transparent colours
+ y = self._data[pos + 1] - 16
+ cr = self._data[pos + 2] - 128
+ cb = self._data[pos + 3] - 128
+ r = round(1.164 * y + 1.793 * cr)
+ g = round(1.164 * y - 0.213 * cb - 0.533 * cr)
+ b = round(1.164 * y + 2.112 * cb)
palette[self._data[pos]] = qRgba(r, g, b, a).to_bytes(4, "little")
return palette
diff --git a/subtle_gui/formats/srtsubs.py b/subtle_gui/formats/srtsubs.py
index 7da84c3..4672615 100644
--- a/subtle_gui/formats/srtsubs.py
+++ b/subtle_gui/formats/srtsubs.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -36,7 +37,6 @@
class SRTSubs(SubtitlesBase):
-
def __init__(self) -> None:
super().__init__()
return
@@ -56,7 +56,7 @@ def write(self, path: Path | None = None, offset: float = 0.0) -> None:
if path is None:
path = self._path
if path:
- self._writeData(path, round(offset*1000.0))
+ self._writeData(path, round(offset * 1000.0))
except Exception as exc:
logger.error("Could not write SRT file: %s", self._path, exc_info=exc)
return
@@ -123,7 +123,6 @@ def _parseFrame(self, block: list[str]) -> None:
class SRTFrame(FrameBase):
-
def __init__(self, index: int, start: int, end: int, text: list[str]) -> None:
super().__init__(index)
self._start = start
diff --git a/subtle_gui/formats/ssasubs.py b/subtle_gui/formats/ssasubs.py
index 6aa0d16..73f2cf3 100644
--- a/subtle_gui/formats/ssasubs.py
+++ b/subtle_gui/formats/ssasubs.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -43,7 +44,6 @@
class EventFormat(NamedTuple):
-
length: int
start: int
end: int
@@ -51,7 +51,6 @@ class EventFormat(NamedTuple):
class SSASubs(SubtitlesBase):
-
def __init__(self) -> None:
super().__init__()
self._format: EventFormat | None = None
@@ -98,12 +97,7 @@ def _parseFormat(self, line: str) -> None:
"""Parse dialogue format."""
parts = [f.strip() for f in line.split(",")]
try:
- self._format = EventFormat(
- len(parts),
- parts.index("Start"),
- parts.index("End"),
- parts.index("Text")
- )
+ self._format = EventFormat(len(parts), parts.index("Start"), parts.index("End"), parts.index("Text"))
except ValueError:
logger.error("Invalid events format string")
return
@@ -116,12 +110,14 @@ def _parseDialogue(self, line: str) -> None:
fmt = self._format
bits = line.split(",", fmt.length - 1)
if len(bits) == fmt.length:
- self._frames.append(SSAFrame(
- len(self._frames),
- decodeTS(bits[fmt.start], fmt="SSA"),
- decodeTS(bits[fmt.end], fmt="SSA"),
- self._processText(bits[fmt.text]),
- ))
+ self._frames.append(
+ SSAFrame(
+ len(self._frames),
+ decodeTS(bits[fmt.start], fmt="SSA"),
+ decodeTS(bits[fmt.end], fmt="SSA"),
+ self._processText(bits[fmt.text]),
+ )
+ )
else:
logger.error("Dialogue entry is malformed on line %d", self._line)
return
@@ -143,7 +139,6 @@ def _processText(self, text: str) -> list[str]:
class SSAFrame(FrameBase):
-
def __init__(self, index: int, start: int, end: int, text: list[str]) -> None:
super().__init__(index=index)
self._start = start
diff --git a/subtle_gui/gui/filetree.py b/subtle_gui/gui/filetree.py
index 508ca75..19f6114 100644
--- a/subtle_gui/gui/filetree.py
+++ b/subtle_gui/gui/filetree.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -34,7 +35,6 @@
class GuiFileTree(QTreeView):
-
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
@@ -62,9 +62,7 @@ def __init__(self, parent: QWidget) -> None:
def saveSettings(self) -> None:
"""Save widget settings."""
- CONFIG.setSizes("fileTreeColumns", [
- self.columnWidth(i) for i in range(self._model.columnCount())
- ])
+ CONFIG.setSizes("fileTreeColumns", [self.columnWidth(i) for i in range(self._model.columnCount())])
return
##
diff --git a/subtle_gui/gui/highlighter.py b/subtle_gui/gui/highlighter.py
index 41bc04b..33ac2c7 100644
--- a/subtle_gui/gui/highlighter.py
+++ b/subtle_gui/gui/highlighter.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -25,10 +26,7 @@
from subtle_gui import SHARED
-from PyQt6.QtGui import (
- QColor, QSyntaxHighlighter, QTextBlockUserData, QTextCharFormat,
- QTextDocument
-)
+from PyQt6.QtGui import QColor, QSyntaxHighlighter, QTextBlockUserData, QTextCharFormat, QTextDocument
from PyQt6.QtWidgets import QApplication
logger = logging.getLogger(__name__)
@@ -40,7 +38,6 @@
class GuiDocHighlighter(QSyntaxHighlighter):
-
def __init__(self, document: QTextDocument) -> None:
super().__init__(document)
@@ -70,7 +67,6 @@ def highlightBlock(self, text: str) -> None:
class TextBlockData(QTextBlockUserData):
-
__slots__ = ("_spellErrors",)
def __init__(self) -> None:
@@ -93,12 +89,9 @@ def spellCheck(self, text: str) -> list[tuple[int, int]]:
for rX in IGNORE_PATTERNS:
for match in rX.finditer(text):
if (s := match.start(0)) >= 0 and (e := match.end(0)) >= 0:
- text = text[:s] + " "*(e - s) + text[e:]
+ text = text[:s] + " " * (e - s) + text[e:]
for match in re.finditer(SPELL_RX, text.replace("_", " ")):
- if (
- (word := match.group(0))
- and not (word.isnumeric() or word.isupper() or checker.checkWord(word))
- ):
+ if (word := match.group(0)) and not (word.isnumeric() or word.isupper() or checker.checkWord(word)):
self._spellErrors.append((match.start(0), match.end(0)))
return self._spellErrors
diff --git a/subtle_gui/gui/imageviewer.py b/subtle_gui/gui/imageviewer.py
index 2a55218..09e3f4a 100644
--- a/subtle_gui/gui/imageviewer.py
+++ b/subtle_gui/gui/imageviewer.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -32,7 +33,6 @@
class GuiImageViewer(QWidget):
-
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
diff --git a/subtle_gui/gui/mediaview.py b/subtle_gui/gui/mediaview.py
index 9d0eed7..2e78a12 100644
--- a/subtle_gui/gui/mediaview.py
+++ b/subtle_gui/gui/mediaview.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -33,8 +34,14 @@
from PyQt6.QtCore import QModelIndex, pyqtSlot
from PyQt6.QtWidgets import (
- QHBoxLayout, QLabel, QProgressBar, QPushButton, QTreeWidget,
- QTreeWidgetItem, QVBoxLayout, QWidget
+ QHBoxLayout,
+ QLabel,
+ QProgressBar,
+ QPushButton,
+ QTreeWidget,
+ QTreeWidgetItem,
+ QVBoxLayout,
+ QWidget,
)
if TYPE_CHECKING:
@@ -46,17 +53,16 @@
class GuiMediaView(QWidget):
-
- C_TRACK = 0
- C_TYPE = 1
- C_CODEC = 2
- C_LANG = 3
- C_LENGTH = 4
- C_FRAMES = 5
- C_LABEL = 6
+ C_TRACK = 0
+ C_TYPE = 1
+ C_CODEC = 2
+ C_LANG = 3
+ C_LENGTH = 4
+ C_FRAMES = 5
+ C_LABEL = 6
C_ENABLED = 7
C_DEFAULT = 8
- C_FORCED = 9
+ C_FORCED = 9
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
@@ -71,11 +77,20 @@ def __init__(self, parent: QWidget) -> None:
self.tracksView = QTreeWidget(self)
self.tracksView.setIndentation(0)
- self.tracksView.setHeaderLabels([
- "#", self.tr("Type"), self.tr("Codec"), self.tr("Lang"), self.tr("Length"),
- self.tr("Frames"), self.tr("Label"), self.tr("Enabled"), self.tr("Default"),
- self.tr("Forced"),
- ])
+ self.tracksView.setHeaderLabels(
+ [
+ "#",
+ self.tr("Type"),
+ self.tr("Codec"),
+ self.tr("Lang"),
+ self.tr("Length"),
+ self.tr("Frames"),
+ self.tr("Label"),
+ self.tr("Enabled"),
+ self.tr("Default"),
+ self.tr("Forced"),
+ ]
+ )
self.tracksView.doubleClicked.connect(self._itemDoubleClicked)
columns = self.tracksView.columnCount()
@@ -114,9 +129,9 @@ def __init__(self, parent: QWidget) -> None:
def saveSettings(self) -> None:
"""Save widget settings."""
- CONFIG.setSizes("mediaViewColumns", [
- self.tracksView.columnWidth(i) for i in range(self.tracksView.columnCount())
- ])
+ CONFIG.setSizes(
+ "mediaViewColumns", [self.tracksView.columnWidth(i) for i in range(self.tracksView.columnCount())]
+ )
return
##
diff --git a/subtle_gui/gui/subsview.py b/subtle_gui/gui/subsview.py
index be0f365..6f7ee33 100644
--- a/subtle_gui/gui/subsview.py
+++ b/subtle_gui/gui/subsview.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -37,12 +38,11 @@
class GuiSubtitleView(QWidget):
-
- C_DATA = 0
- C_ID = 0
- C_TIME = 1
+ C_DATA = 0
+ C_ID = 0
+ C_TIME = 1
C_LENGTH = 2
- C_TEXT = 3
+ C_TEXT = 3
D_INDEX = Qt.ItemDataRole.UserRole
@@ -56,9 +56,7 @@ def __init__(self, parent: QWidget) -> None:
# Entries View
self.subEntries = QTreeWidget(self)
self.subEntries.setIndentation(0)
- self.subEntries.setHeaderLabels([
- "#", self.tr("Time Stamp"), self.tr("Length"), self.tr("Text")
- ])
+ self.subEntries.setHeaderLabels(["#", self.tr("Time Stamp"), self.tr("Length"), self.tr("Text")])
self.subEntries.clicked.connect(self._itemClicked)
columns = self.subEntries.columnCount()
@@ -80,9 +78,9 @@ def __init__(self, parent: QWidget) -> None:
def saveSettings(self) -> None:
"""Save widget settings."""
- CONFIG.setSizes("subsViewColumns", [
- self.subEntries.columnWidth(i) for i in range(self.subEntries.columnCount() - 1)
- ])
+ CONFIG.setSizes(
+ "subsViewColumns", [self.subEntries.columnWidth(i) for i in range(self.subEntries.columnCount() - 1)]
+ )
return
##
@@ -107,7 +105,7 @@ def processNewTrackLoaded(self) -> None:
item = QTreeWidgetItem()
item.setText(self.C_ID, str(self.subEntries.topLevelItemCount()))
item.setText(self.C_TIME, formatTS(frame.start))
- item.setText(self.C_LENGTH, f"{frame.length/1000.0:.3f} s")
+ item.setText(self.C_LENGTH, f"{frame.length / 1000.0:.3f} s")
item.setData(self.C_DATA, self.D_INDEX, frame.index)
item.setFont(self.C_ID, font)
item.setFont(self.C_TIME, font)
@@ -153,8 +151,7 @@ def selectNearby(self, step: int) -> None:
if item := self.subEntries.topLevelItem(index):
self.subEntries.clearSelection()
self.subEntries.scrollTo(
- self.subEntries.indexFromItem(item, 0),
- QAbstractItemView.ScrollHint.PositionAtCenter
+ self.subEntries.indexFromItem(item, 0), QAbstractItemView.ScrollHint.PositionAtCenter
)
item.setSelected(True)
self._itemClicked(self.subEntries.indexFromItem(item))
diff --git a/subtle_gui/gui/texteditor.py b/subtle_gui/gui/texteditor.py
index a91330f..3c35953 100644
--- a/subtle_gui/gui/texteditor.py
+++ b/subtle_gui/gui/texteditor.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -28,15 +29,12 @@
from PyQt6.QtCore import QPoint, Qt, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QShortcut, QTextBlock, QTextBlockFormat, QTextCharFormat, QTextCursor
-from PyQt6.QtWidgets import (
- QComboBox, QMenu, QTextEdit, QToolBar, QToolButton, QVBoxLayout, QWidget
-)
+from PyQt6.QtWidgets import QComboBox, QMenu, QTextEdit, QToolBar, QToolButton, QVBoxLayout, QWidget
logger = logging.getLogger(__name__)
class GuiTextEditor(QWidget):
-
newTextForFrame = pyqtSignal(FrameBase)
requestNewFrame = pyqtSignal(int)
@@ -244,17 +242,13 @@ def _openContextMenu(self, pos: QPoint) -> None:
block = pCursor.block()
sCursor = self.textEdit.textCursor()
sCursor.setPosition(block.position() + cPos)
- sCursor.movePosition(
- QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, cLen
- )
+ sCursor.movePosition(QTextCursor.MoveOperation.Right, QTextCursor.MoveMode.KeepAnchor, cLen)
if suggest:
ctxMenu.addSeparator()
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
for option in suggest[:15]:
if action := ctxMenu.addAction(option):
- action.triggered.connect(
- lambda _, option=option: self._correctWord(sCursor, option)
- )
+ action.triggered.connect(lambda _, option=option: self._correctWord(sCursor, option))
else:
ctxMenu.addAction(self.tr("No Suggestions"))
diff --git a/subtle_gui/gui/toolspanel.py b/subtle_gui/gui/toolspanel.py
index af0d63d..1487343 100644
--- a/subtle_gui/gui/toolspanel.py
+++ b/subtle_gui/gui/toolspanel.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -29,15 +30,23 @@
from PyQt6.QtCore import Qt, pyqtSignal, pyqtSlot
from PyQt6.QtWidgets import (
- QCheckBox, QDoubleSpinBox, QFormLayout, QGroupBox, QHBoxLayout, QLineEdit,
- QListWidget, QListWidgetItem, QPushButton, QVBoxLayout, QWidget
+ QCheckBox,
+ QDoubleSpinBox,
+ QFormLayout,
+ QGroupBox,
+ QHBoxLayout,
+ QLineEdit,
+ QListWidget,
+ QListWidgetItem,
+ QPushButton,
+ QVBoxLayout,
+ QWidget,
)
logger = logging.getLogger(__name__)
class GuiToolsPanel(QWidget):
-
D_SUBS_PATH = Qt.ItemDataRole.UserRole
requestSrtSave = pyqtSignal(Path, float)
@@ -242,11 +251,7 @@ def _scanForSubs(self, path: Path) -> None:
for folder in folders:
try:
for entry in folder.iterdir():
- if (
- entry.is_file()
- and entry.suffix == ".srt"
- and entry.stem.lower().startswith(prefix)
- ):
+ if entry.is_file() and entry.suffix == ".srt" and entry.stem.lower().startswith(prefix):
item = QListWidgetItem()
item.setText(str(entry.relative_to(root)))
item.setData(self.D_SUBS_PATH, entry)
diff --git a/subtle_gui/guimain.py b/subtle_gui/guimain.py
index cef62b1..bfab160 100644
--- a/subtle_gui/guimain.py
+++ b/subtle_gui/guimain.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -48,7 +49,6 @@
class GuiMain(QMainWindow):
-
def __init__(self) -> None:
super().__init__()
diff --git a/subtle_gui/ocr/base.py b/subtle_gui/ocr/base.py
index 79375f7..a43036f 100644
--- a/subtle_gui/ocr/base.py
+++ b/subtle_gui/ocr/base.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -32,7 +33,6 @@
class OCRBase(ABC):
-
def __init__(self) -> None:
return
diff --git a/subtle_gui/ocr/tesseract.py b/subtle_gui/ocr/tesseract.py
index 6ab2e5e..52021fd 100644
--- a/subtle_gui/ocr/tesseract.py
+++ b/subtle_gui/ocr/tesseract.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -53,14 +54,11 @@
(re.compile(r"^(-\s)[\w]", re.UNICODE), "-"),
(re.compile(r"^(\.{2})[\s\w]", re.UNICODE), "..."),
(re.compile(r"^(\.{3}\s)\w", re.UNICODE), "..."),
-
# Wrong capitalisation in the middle of words
(re.compile(r"[a-z]+(S)", re.UNICODE), "s"),
(re.compile(r"[a-z]+(O)", re.UNICODE), "o"),
-
# Slash for I in italics
(re.compile(r"(?:^|\s)(\/)\s", re.UNICODE), "I"),
-
# Notes
(re.compile(r"(?:^|\s|\[|\()(J|f)(?:$|\s|\]|\))", re.UNICODE), "\u266a"),
],
@@ -68,23 +66,20 @@
# Misinterpreted words
(re.compile(r"\b(tt)\b", re.UNICODE), "it"),
(re.compile(r"\b(fo)\b", re.UNICODE), "to"),
-
# Wrong capitalisation at the start of words
(re.compile(r"(? None:
super().__init__()
return
diff --git a/subtle_gui/shared.py b/subtle_gui/shared.py
index ea42f7b..760e480 100644
--- a/subtle_gui/shared.py
+++ b/subtle_gui/shared.py
@@ -18,6 +18,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
import logging
@@ -36,7 +37,6 @@
class SharedData(QObject):
-
spellLanguageChanged = pyqtSignal(str)
def __init__(self) -> None:
@@ -75,13 +75,7 @@ def icons(self) -> GuiIcons:
return self._icons
raise RuntimeError("Shared data object not yet initialised.")
- def initSharedData(
- self,
- media: MediaData,
- ocr: OCRBase,
- spell: SpellEnchant,
- icons: GuiIcons
- ) -> None:
+ def initSharedData(self, media: MediaData, ocr: OCRBase, spell: SpellEnchant, icons: GuiIcons) -> None:
"""Init the shared data object. This must be called right after
the GUI is created.
"""
diff --git a/tests/test_common.py b/tests/test_common.py
index c3122e6..48dcae8 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -18,20 +18,22 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
"""
+
from __future__ import annotations
-from subtle.common import closeItalics
+from subtle_gui.common import closeItalics
def testCommon_closeItalics():
"""Test the closeItalics function."""
- assert closeItalics(["Foo", "Bar"]) == [
- "Foo", "Bar"
- ]
+ assert closeItalics(["Foo", "Bar"]) == ["Foo", "Bar"]
assert closeItalics(["Foo", "Bar", "Baz"]) == [
- "Foo", "Bar", "Baz",
+ "Foo",
+ "Bar",
+ "Baz",
]
assert closeItalics(["Foo foo foo", "Bar"]) == [
- "Foo foo foo", "Bar",
+ "Foo foo foo",
+ "Bar",
]
return
From 7fc5fd4aaa5ff8cb67c4bd86c8ab8349306c6c28 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 24 Jun 2026 22:51:24 +0200
Subject: [PATCH 3/4] Add docstrings
---
pyproject.toml | 48 +++++++++++--------
subtle.py | 2 +-
subtle_gui/__init__.py | 18 ++++----
subtle_gui/common.py | 12 ++---
subtle_gui/config.py | 21 +++------
subtle_gui/constants.py | 8 +++-
subtle_gui/core/icons.py | 7 ++-
subtle_gui/core/media.py | 16 ++-----
subtle_gui/core/mediafile.py | 6 +--
subtle_gui/core/mkvextract.py | 9 ++--
subtle_gui/core/spellcheck.py | 24 +++++-----
subtle_gui/formats/base.py | 18 ++++----
subtle_gui/formats/pgssubs.py | 86 ++++++++++++++++++++---------------
subtle_gui/formats/srtsubs.py | 14 +++---
subtle_gui/formats/ssasubs.py | 14 +++---
subtle_gui/gui/filetree.py | 9 ++--
subtle_gui/gui/highlighter.py | 11 ++---
subtle_gui/gui/imageviewer.py | 9 ++--
subtle_gui/gui/mediaview.py | 15 ++----
subtle_gui/gui/subsview.py | 15 ++----
subtle_gui/gui/texteditor.py | 21 ++-------
subtle_gui/gui/toolspanel.py | 14 ++----
subtle_gui/guimain.py | 7 ++-
subtle_gui/ocr/base.py | 5 +-
subtle_gui/ocr/tesseract.py | 5 +-
subtle_gui/shared.py | 7 ++-
tests/test_common.py | 3 +-
27 files changed, 194 insertions(+), 230 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 34b9343..c80a9c2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -59,39 +59,49 @@ preview = false
# Rules: https://docs.astral.sh/ruff/rules
select = [
- "A", # flake8-builtins (A)
- "ANN", # flake8-annotations (ANN)
- "B", # flake8-bugbear (B)
- "E", # pycodestyle (E)
- "F", # Pyflakes (F)
- "FA", # flake8-future-annotations (FA)
- "PERF", # Perflint (PERF)
- "PLC", # Pylint Convention (PLC)
- "PLE", # Pylint Error (PLE)
- "PLW", # Pylint Warning (PLW)
- "Q", # flake8-quotes (Q)
- "RUF", # Ruff-specific rules (RUF)
- "SLF", # flake8-self (SLF)
- "SLOT", # flake8-slots (SLOT)
- "TC", # flake8-type-checking (TC)
- "UP", # pyupgrade (UP)
- "W", # pycodestyle (W)
+ "A", # flake8-builtins (A)
+ "ANN", # flake8-annotations (ANN)
+ "B", # flake8-bugbear (B)
+ "D", # pydocstyle (D)
+ "E", # pycodestyle (E)
+ "F", # Pyflakes (F)
+ "FA", # flake8-future-annotations (FA)
+ "PERF", # Perflint (PERF)
+ "PLC", # Pylint Convention (PLC)
+ "PLE", # Pylint Error (PLE)
+ "PLR17", # Refactor (PLR) - Only PLR17xx
+ "PLW", # Pylint Warning (PLW)
+ "Q", # flake8-quotes (Q)
+ "RET", # flake8-return (RET)
+ "RUF", # Ruff-specific rules (RUF)
+ "SLF", # flake8-self (SLF)
+ "SLOT", # flake8-slots (SLOT)
+ "TC", # flake8-type-checking (TC)
+ "UP", # pyupgrade (UP)
+ "W", # pycodestyle (W)
]
ignore = [
"ANN401", # any-type
+ "D107", # undocumented-public-init
+ "D203", # incorrect-blank-line-before-class
+ "D205", # missing-blank-line-after-summary
+ "D213", # multi-line-summary-second-line
"PLC0415", # import-outside-top-level
"PLC1901", # compare-to-empty-string
"PLW0108", # unnecessary-lambda
+ "PLW0717", # too-many-statements-in-try-clause
"PLW2901", # redefined-loop-name
+ "RET505", # superfluous-else-return
"RUF001", # ambiguous-unicode-character-string
- "RUF002", # ambiguous-unicode-character-docstring
"RUF015", # unnecessary-iterable-allocation-for-first-element
+ "RUF067", # non-empty-init-module
+ "RUF076", # pytest-fixture-autouse
"UP015", # redundant-open-modes
"UP030", # format-literals
]
[tool.ruff.lint.per-file-ignores]
-"tests/*" = ["ANN", "SLF", "TC", "PLC2701"]
+"tests/*" = ["ANN", "D101", "D102", "D105", "PLC2701", "RUF069", "SLF", "TC"]
[tool.ruff.format]
quote-style = "double"
diff --git a/subtle.py b/subtle.py
index e551d50..dd5b69c 100755
--- a/subtle.py
+++ b/subtle.py
@@ -2,7 +2,7 @@
"""
Subtle – Start Script
=====================
-"""
+""" # noqa
import os
import sys
diff --git a/subtle_gui/__init__.py b/subtle_gui/__init__.py
index 0f5842f..16ab784 100644
--- a/subtle_gui/__init__.py
+++ b/subtle_gui/__init__.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -39,14 +39,16 @@
# Package Meta
# ============
-__package__ = "subtle_gui"
-__copyright__ = "Copyright (C) Veronica Berglyd Olsen"
-__license__ = "GPLv3"
-__author__ = "Veronica Berglyd Olsen"
+# fmt: off
+__package__ = "subtle_gui"
+__copyright__ = "Copyright (C) Veronica Berglyd Olsen"
+__license__ = "GPLv3"
+__author__ = "Veronica Berglyd Olsen"
__maintainer__ = "Veronica Berglyd Olsen"
-__email__ = "code@vkbo.net"
-__version__ = "26.1.1"
-__date__ = "2026-06-24"
+__email__ = "code@vkbo.net"
+__version__ = "26.1.1"
+__date__ = "2026-06-24"
+# fmt: on
logger = logging.getLogger(__name__)
diff --git a/subtle_gui/common.py b/subtle_gui/common.py
index 62b4ee8..af106d9 100644
--- a/subtle_gui/common.py
+++ b/subtle_gui/common.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -50,7 +50,7 @@ def checkInt(value: Any, default: int) -> int:
def formatInt(value: int) -> str:
- """Formats an integer with k, M, G etc."""
+ """Format an integer with k, M, G etc."""
if not isinstance(value, int):
return "ERR"
@@ -75,7 +75,7 @@ def textCleanup(text: str) -> str:
def regexCleanup(text: str, patterns: list[tuple[re.Pattern, str]]) -> str:
- """Replaces all occurrences of match group 1 in patterns."""
+ """Replace all occurrences of match group 1 in patterns."""
for regEx, value in patterns:
matches = [
(s, e, value) for match in regEx.finditer(text) if (s := match.start(1)) >= 0 and (e := match.end(1)) >= 0
@@ -121,7 +121,7 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
elif first in ("{", "["):
n += 1
indent = "\n" + " " * n
- if n > nmax and nmax > 0:
+ if n > nmax > 0:
buffer.append(chunk)
else:
buffer.append(chunk[0] + indent + chunk[1:])
@@ -129,13 +129,13 @@ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
elif first in ("}", "]"):
n -= 1
indent = "\n" + " " * n
- if n >= nmax and nmax > 0:
+ if n >= nmax > 0:
buffer.append(chunk)
else:
buffer.append(indent + chunk)
elif first == ",":
- if n > nmax and nmax > 0:
+ if n > nmax > 0:
buffer.append(chunk)
else:
buffer.append(chunk[0] + indent + chunk[1:].lstrip())
diff --git a/subtle_gui/config.py b/subtle_gui/config.py
index bf3c06f..c4c2144 100644
--- a/subtle_gui/config.py
+++ b/subtle_gui/config.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -63,6 +63,8 @@
class Config:
+ """Main Config Class."""
+
def __init__(self) -> None:
self._data: dict[str, dict] = deepcopy(DEFAULTS)
@@ -115,8 +117,6 @@ def __init__(self) -> None:
self.hostName = QSysInfo.machineHostName()
self.kernelVer = QSysInfo.kernelVersion()
- return
-
##
# Properties
##
@@ -173,7 +173,6 @@ def setSize(self, key: str, value: QSize) -> None:
"""Set a size in config."""
if isinstance(value, QSize):
self._data["Sizes"][key] = [value.width(), value.height()]
- return
def setSizes(self, key: str, value: list[int]) -> None:
"""Set a size in config."""
@@ -182,10 +181,9 @@ def setSizes(self, key: str, value: list[int]) -> None:
self._data["Sizes"][key] = [int(x) for x in value]
except Exception as e:
logger.error("Problem when saving sizes list", exc_info=e)
- return
def setFontSpec(self, target: T_Fonts, font: QFont | str) -> None:
- """Set the font"""
+ """Set a font in config."""
if isinstance(font, str):
temp = QFont()
temp.fromString(font)
@@ -202,8 +200,6 @@ def setFontSpec(self, target: T_Fonts, font: QFont | str) -> None:
self.subsFont = font
self._data["Fonts"]["subsFont"] = font.toString()
- return
-
##
# Methods
##
@@ -212,17 +208,16 @@ def initialise(self) -> None:
"""Initialise the config."""
self._confPath.mkdir(exist_ok=True)
self._cachePath.mkdir(exist_ok=True)
- return
def cleanup(self) -> None:
- """Called before exit to clean up cache."""
+ """Clean up cache. Called before exit."""
path = self._cachePath / "dump"
if path.exists():
logger.debug("Clearing session cache")
shutil.rmtree(path)
- return
def localisation(self, app: QApplication) -> None:
+ """Set up localisation."""
return
def fonts(self, app: QApplication) -> None:
@@ -243,7 +238,6 @@ def fonts(self, app: QApplication) -> None:
temp = app.font()
temp.setPointSizeF(3.0 * temp.pointSizeF())
self.setFontSpec("subs", temp)
- return
def load(self) -> None:
"""Load the app config."""
@@ -257,7 +251,6 @@ def load(self) -> None:
self._storeConfigGroup(data, "Fonts")
except Exception as e:
logger.error("Could not load config", exc_info=e)
- return
def save(self) -> None:
"""Save the app config."""
@@ -267,7 +260,6 @@ def save(self) -> None:
fo.write(jsonEncode(self._data, nmax=2))
except Exception:
logger.error("Could not save config")
- return
##
# Internal Functions
@@ -280,4 +272,3 @@ def _storeConfigGroup(self, data: dict, group: str) -> None:
default = DEFAULTS.get(group, {})
values = {k: v for k, v in loaded.items() if k in default}
self._data[group].update(values)
- return
diff --git a/subtle_gui/constants.py b/subtle_gui/constants.py
index b4f85ae..271c00a 100644
--- a/subtle_gui/constants.py
+++ b/subtle_gui/constants.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -28,11 +28,13 @@
def trConst(text: str) -> str:
- """Wrapper function for locally translating constants."""
+ """Wrap locally translating constants."""
return QCoreApplication.translate("Constant", text)
class MediaType(Enum):
+ """Media types."""
+
VIDEO = 0
AUDIO = 1
SUBS = 2
@@ -40,6 +42,8 @@ class MediaType(Enum):
class GuiLabels:
+ """GUI labels."""
+
MEDIA_TYPES: Final[dict[MediaType, str]] = {
MediaType.VIDEO: QT_TRANSLATE_NOOP("Constant", "Video"),
MediaType.AUDIO: QT_TRANSLATE_NOOP("Constant", "Audio"),
diff --git a/subtle_gui/core/icons.py b/subtle_gui/core/icons.py
index e548a65..5069acc 100644
--- a/subtle_gui/core/icons.py
+++ b/subtle_gui/core/icons.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -65,11 +65,12 @@
class GuiIcons:
+ """GUI Icons."""
+
def __init__(self) -> None:
palette = QApplication.palette()
self._cache: dict[str, QIcon] = {}
self._color = palette.buttonText().color().name(QColor.NameFormat.HexRgb).encode()
- return
def icon(self, key: str) -> QIcon:
"""Return an icon, either from the cache or generate it."""
@@ -84,7 +85,6 @@ class _IconEngine(QIconEngine):
def __init__(self, data: bytes) -> None:
super().__init__()
self._data = data
- return
def clone(self) -> QIconEngine | None:
"""Clone the icon engine."""
@@ -103,4 +103,3 @@ def paint(self, painter: QPainter | None, rect: QRect, mode: QIcon.Mode, state:
"""SVG icon painter."""
renderer = QSvgRenderer(self._data)
renderer.render(painter, QRectF(rect))
- return
diff --git a/subtle_gui/core/media.py b/subtle_gui/core/media.py
index e0e9625..fa5bb05 100644
--- a/subtle_gui/core/media.py
+++ b/subtle_gui/core/media.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -45,6 +45,8 @@
class MediaData(QObject):
+ """Shared data object for media files and tracks."""
+
mediaDataCleared = pyqtSignal()
newMediaLoaded = pyqtSignal()
newTrackSelected = pyqtSignal()
@@ -54,7 +56,6 @@ def __init__(self) -> None:
self._tracks: dict[str, MediaTrack] = {}
self._track: MediaTrack | None = None
self._file: MediaFile | None = None
- return
@property
def hasMedia(self) -> bool:
@@ -77,7 +78,6 @@ def clear(self) -> None:
self._track = None
self._file = None
self.mediaDataCleared.emit()
- return
def loadMediaFile(self, path: Path) -> None:
"""Load a media file into the data store."""
@@ -90,7 +90,6 @@ def loadMediaFile(self, path: Path) -> None:
if idx := track.trackID:
self._tracks[idx] = track
self.newMediaLoaded.emit()
- return
def setCurrentTrack(self, trackID: str) -> None:
"""Set the current active track."""
@@ -98,7 +97,6 @@ def setCurrentTrack(self, trackID: str) -> None:
self._track = track
SHARED.setSpellLanguage(track)
self.newTrackSelected.emit()
- return
def iterTracks(self) -> Iterable[MediaTrack]:
"""Iterate through all tracks."""
@@ -110,6 +108,8 @@ def getTrack(self, trackID: str) -> MediaTrack | None:
class MediaTrack:
+ """Represents a media track within a media file."""
+
def __init__(self, media: MediaData, info: dict) -> None:
self._media = media
self._info = {}
@@ -144,8 +144,6 @@ def __init__(self, media: MediaData, info: dict) -> None:
else:
logger.info("Unsupported subtitle format: %s", codec_id or codec_nm)
- return
-
##
# Properties
##
@@ -219,14 +217,12 @@ def duration(self) -> int:
def setTrackFile(self, path: Path) -> None:
"""Set the extraction location of the track file."""
self._path = path
- return
def readTrackFile(self) -> None:
"""Read the content of the track file."""
if self._path and self._wrapper:
self._wrapper.read(self._path)
self._wrapper.checkFrames()
- return
def getFrame(self, index: int) -> FrameBase | None:
"""Return a subtitles frame."""
@@ -244,10 +240,8 @@ def copyFrames(self, target: SubtitlesBase) -> None:
"""Copy all frames from current track to target."""
if self._wrapper:
target.copyFrames(self._wrapper)
- return
def copyText(self, source: SubtitlesBase) -> None:
"""Copy all text from source to current track."""
if self._wrapper:
self._wrapper.copyText(source)
- return
diff --git a/subtle_gui/core/mediafile.py b/subtle_gui/core/mediafile.py
index 27775ad..b4011cb 100644
--- a/subtle_gui/core/mediafile.py
+++ b/subtle_gui/core/mediafile.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -39,6 +39,8 @@
class ContainerType(IntEnum):
+ """Container Type Enum."""
+
UNKNOWN = 0
AVI = 5
MATROSKA = 17
@@ -77,7 +79,6 @@ def __init__(self, file: Path) -> None:
self._id = sha1(bytes(file), usedforsecurity=False).hexdigest()
self._info = {}
self._process()
- return
@property
def filePath(self) -> Path:
@@ -145,4 +146,3 @@ def _process(self) -> None:
except Exception as e:
logger.error("Failed to load media file info", exc_info=e)
self._info = {}
- return
diff --git a/subtle_gui/core/mkvextract.py b/subtle_gui/core/mkvextract.py
index 48c0b61..c94f6c4 100644
--- a/subtle_gui/core/mkvextract.py
+++ b/subtle_gui/core/mkvextract.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -36,6 +36,8 @@
class MkvExtract(QObject):
+ """Wrapper for mkvextract process."""
+
processProgress = pyqtSignal(int)
processDone = pyqtSignal(int)
@@ -43,7 +45,6 @@ def __init__(self, parent: QObject) -> None:
super().__init__(parent=parent)
self._process = None
self._pid = 0
- return
def extract(self, file: Path, tracks: list[tuple[str, Path]]) -> None:
"""Start a subprocess running mkvextract."""
@@ -56,14 +57,12 @@ def extract(self, file: Path, tracks: list[tuple[str, Path]]) -> None:
self._process.start("mkvextract", args)
self._pid = self._process.processId()
logger.debug("Starting process %d", self._pid)
- return
def cancel(self) -> None:
"""Cancel the process."""
if isinstance(self._process, QProcess):
logger.debug("Killing process %d", self._pid)
self._process.kill()
- return
##
# Private Slots
@@ -76,7 +75,6 @@ def _processStdOut(self) -> None:
text = self._process.readAllStandardOutput().data().decode("utf-8").strip()
if text.startswith("#GUI#progress"):
self.processProgress.emit(checkInt(text[13:].strip().removesuffix("%"), 0))
- return
@pyqtSlot()
def _processFinished(self) -> None:
@@ -86,4 +84,3 @@ def _processFinished(self) -> None:
logger.debug("Process %d exited with return code %d", self._pid, code)
self.processDone.emit(code)
self._process = None
- return
diff --git a/subtle_gui/core/spellcheck.py b/subtle_gui/core/spellcheck.py
index 05de462..658a56d 100644
--- a/subtle_gui/core/spellcheck.py
+++ b/subtle_gui/core/spellcheck.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -38,7 +38,7 @@
class SpellEnchant:
- """Core: Enchant Spell Checking Wrapper
+ """Core: Enchant Spell Checking Wrapper.
This is a wrapper class for Enchant to keep the API consistent
between spell check tools.
@@ -49,7 +49,6 @@ def __init__(self) -> None:
self._userDict = UserDictionary()
self._language = None
self._broker = None
- return
##
# Properties
@@ -57,6 +56,7 @@ def __init__(self) -> None:
@property
def spellLanguage(self) -> str | None:
+ """Get the currently loaded spell checking language."""
return self._language
##
@@ -94,21 +94,19 @@ def setLanguage(self, language: str | None) -> None:
for word in self._userDict:
self._enchant.add_to_session(word)
- return
-
##
# Methods
##
def checkWord(self, word: str) -> bool:
- """Wrapper function for pyenchant."""
+ """Wrap pyenchant check."""
try:
return bool(self._enchant.check(word))
except Exception:
return True
def suggestWords(self, word: str) -> list[str]:
- """Wrapper function for pyenchant."""
+ """Wrap pyenchant suggestions."""
try:
return self._enchant.suggest(word)
except Exception:
@@ -160,27 +158,31 @@ class FakeProvider:
self.tag = ""
self.provider = FakeProvider()
- return
-
def check(self, word: str) -> bool:
+ """Wrap pyenchant check."""
return True
def suggest(self, word: str) -> list[str]:
+ """Wrap pyenchant suggestions."""
return []
def add_to_session(self, word: str) -> None:
+ """Wrap pyenchant add_to_session."""
return
class UserDictionary:
+ """A simple user dictionary that is saved to a JSON file."""
+
def __init__(self) -> None:
self._words = set()
- return
def __contains__(self, word: str) -> bool:
+ """Check if a word is in the dictionary."""
return word in self._words
def __iter__(self) -> Iterator[str]:
+ """Iterate over the words in the dictionary."""
return iter(self._words)
def add(self, word: str) -> bool:
@@ -205,7 +207,6 @@ def load(self) -> None:
logger.info("Loaded user dictionary")
except Exception as exc:
logger.error("Failed to load user dictionary", exc_info=exc)
- return
def save(self) -> None:
"""Save the user's dictionary."""
@@ -217,4 +218,3 @@ def save(self) -> None:
json.dump(data, fObj, indent=2)
except Exception as exc:
logger.error("Failed to save user dictionary", exc_info=exc)
- return
diff --git a/subtle_gui/formats/base.py b/subtle_gui/formats/base.py
index d57b1d1..4816aec 100644
--- a/subtle_gui/formats/base.py
+++ b/subtle_gui/formats/base.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -38,14 +38,16 @@
class SubtitlesBase(ABC):
+ """Base class for subtitle formats."""
+
__slots__ = ("_frames", "_path")
def __init__(self) -> None:
self._path: Path | None = None
self._frames: list[FrameBase] = []
- return
def __repr__(self) -> str:
+ """Return a string representation of the subtitle object."""
return f"<{self.__class__.__name__}: id={id(self)}>"
def frameCount(self) -> int:
@@ -78,18 +80,20 @@ def checkFrames(self) -> None:
(tn - ts) / 1000.0,
(te - ts) / 1000.0,
)
- return
@abstractmethod
def read(self, path: Path) -> None:
+ """Read subtitles from a file."""
raise NotImplementedError
@abstractmethod
def write(self, path: Path | None = None) -> None:
+ """Write subtitles to a file."""
raise NotImplementedError
@abstractmethod
def copyFrames(self, other: SubtitlesBase) -> None:
+ """Copy frames from another subtitle object."""
raise NotImplementedError
def copyText(self, other: SubtitlesBase) -> None:
@@ -110,18 +114,17 @@ def copyText(self, other: SubtitlesBase) -> None:
if (pos := frame.start + offset) in frames:
frames[pos].setText(frame.text)
- return
-
def _copyFrames(self, frameType: type[FrameBase], other: SubtitlesBase) -> None:
"""Copy frame content from other class. Must be implemented in
subclasses by passing its own FrameBase implementation as
frameType.
"""
self._frames = [frameType.fromFrame(n, frame) for n, frame in enumerate(other.iterFrames())]
- return
class FrameBase(ABC):
+ """Base class for subtitle frames."""
+
__slots__ = ("_end", "_index", "_start", "_text")
def __init__(self, index: int) -> None:
@@ -129,7 +132,6 @@ def __init__(self, index: int) -> None:
self._start: int = -1
self._end: int = -1
self._text: list[str] = []
- return
@classmethod
@abstractmethod
@@ -171,7 +173,6 @@ def imageBased(self) -> bool:
def setText(self, text: list[str]) -> None:
"""Set the frame's text."""
self._text = [t.strip() for t in text if t.strip()]
- return
def setRange(self, *, start: int | None = None, end: int | None = None) -> None:
"""Update the start and end times."""
@@ -179,7 +180,6 @@ def setRange(self, *, start: int | None = None, end: int | None = None) -> None:
self._start = start
if end:
self._end = end
- return
@abstractmethod
def getImage(self) -> QImage:
diff --git a/subtle_gui/formats/pgssubs.py b/subtle_gui/formats/pgssubs.py
index 85efcbe..f7c6832 100644
--- a/subtle_gui/formats/pgssubs.py
+++ b/subtle_gui/formats/pgssubs.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -55,7 +55,6 @@ class PGSSubs(SubtitlesBase):
def __init__(self) -> None:
super().__init__()
- return
def read(self, path: Path) -> None:
"""Read a PGS file."""
@@ -64,13 +63,13 @@ def read(self, path: Path) -> None:
self._path = path
except Exception as e:
logger.error("Failed to read file data", exc_info=e)
- return
def write(self, path: Path | None = None) -> None:
"""Write a PGS file."""
raise NotImplementedError("Cannot write PGS files.")
def copyFrames(self, other: SubtitlesBase) -> None:
+ """Copy frames from another subtitle object."""
return super()._copyFrames(PGSFrame, other)
##
@@ -143,15 +142,14 @@ def _readData(self, path: Path) -> None:
self._frames = frames
- return
-
class PGSFrame(FrameBase):
+ """PGS Subtitle Frame Class."""
+
def __init__(self, index: int, ds: DisplaySet) -> None:
super().__init__(index=index)
self._ds = ds
self._start = int(ds.timestamp / 90.0)
- return
@classmethod
def fromFrame(cls, index: int, other: FrameBase) -> FrameBase:
@@ -160,13 +158,13 @@ def fromFrame(cls, index: int, other: FrameBase) -> FrameBase:
@property
def imageBased(self) -> bool:
- """This class is image based."""
+ """Check if the frame is image based."""
+ # A PGS frame is always image-based, as it is a bitmap subtitle format
return True
def closeFrame(self, ds: DisplaySet) -> None:
"""Extract timestamp from display set used to close frame."""
self._end = int(ds.timestamp / 90.0)
- return
def getImage(self) -> QImage:
"""Return the rendered image."""
@@ -174,6 +172,8 @@ def getImage(self) -> QImage:
class DisplaySet:
+ """PGS Display Set Class."""
+
__slots__ = ("_image", "_ods", "_pcs", "_pds", "_wds")
def __init__(self, pcs: PresentationSegment) -> None:
@@ -182,9 +182,9 @@ def __init__(self, pcs: PresentationSegment) -> None:
self._pds: dict[int, PaletteSegment] = {}
self._ods: dict[int, list[ObjectSegment]] = {}
self._image: QImage | None = None
- return
def __repr__(self) -> str:
+ """Return a string representation of the display set."""
return (
f"<{self.__class__.__name__}: "
f"composition={self._pcs.compNumber} "
@@ -197,10 +197,12 @@ def __repr__(self) -> str:
@property
def pcs(self) -> PresentationSegment:
+ """Return the presentation segment."""
return self._pcs
@property
def timestamp(self) -> float:
+ """Return the timestamp of the display set."""
return self._pcs.timestamp
def addWDS(self, wds: WindowSegment, pos: int) -> None:
@@ -210,7 +212,6 @@ def addWDS(self, wds: WindowSegment, pos: int) -> None:
self._wds[idx] = rect
else:
logger.warning("Skipping invalid WindowSegment at pos %d", pos)
- return
def addPDS(self, pds: PaletteSegment, pos: int) -> None:
"""Save the segment mapped to its id."""
@@ -218,7 +219,6 @@ def addPDS(self, pds: PaletteSegment, pos: int) -> None:
self._pds[pds.id] = pds
else:
logger.warning("Skipping invalid PaletteSegment at pos %d", pos)
- return
def addODS(self, ods: ObjectSegment, pos: int) -> None:
"""Save the segment mapped to its id."""
@@ -229,12 +229,13 @@ def addODS(self, ods: ObjectSegment, pos: int) -> None:
self._ods[oid].append(ods)
else:
logger.warning("Skipping invalid ObjectSegment at pos %d", pos)
- return
def isValid(self) -> bool:
+ """Check if the display set is valid."""
return self._pcs is not None and self._pcs.valid
def isClearFrame(self) -> bool:
+ """Check if the display set is a clear frame."""
return self._pcs.compState == COMP_NORMAL and self._pcs.compObjectCount == 0
def render(self, crop: bool = True) -> QImage:
@@ -305,7 +306,9 @@ def render(self, crop: bool = True) -> QImage:
return self._image
-class BaseSegment(ABC):
+class SegmentBase(ABC):
+ """Base class for PGS segments."""
+
__slots__ = ("_data", "_ts", "_valid")
def __init__(self, ts: int, data: bytes) -> None:
@@ -313,36 +316,39 @@ def __init__(self, ts: int, data: bytes) -> None:
self._data = data
self._valid = False
self.validate()
- return
def __repr__(self) -> str:
+ """Return a string representation of the segment."""
return f"<{self.__class__.__name__}: t={self._ts / 90000:.3f} size={len(self._data)} valid={self._valid}>"
@property
def valid(self) -> bool:
+ """Check if the segment is valid."""
return self._valid
@property
def timestamp(self) -> float:
+ """Return the timestamp of the segment."""
return self._ts
@abstractmethod
def validate(self) -> None:
+ """Validate the segment data."""
raise NotImplementedError
-class PresentationSegment(BaseSegment):
- """Presentation Composition Segment
+class PresentationSegment(SegmentBase):
+ """Presentation Composition Segment.
The Presentation Composition Segment is used for composing a sub
picture.
"""
def validate(self) -> None:
- """Length is 11 + n*8"""
+ """Validate the segment data."""
+ # Length is 11 + n*8
size = len(self._data)
self._valid = size >= 11 and size % 8 == 3
- return
@property
def size(self) -> QSize:
@@ -361,7 +367,9 @@ def compNumber(self) -> int:
@property
def compState(self) -> int:
- """Type of this composition. Allowed values are:
+ """Return the type of this composition.
+
+ Allowed values are:
0x00: Normal
0x40: Acquisition Point
0x80: Epoch Start
@@ -370,8 +378,9 @@ def compState(self) -> int:
@property
def paletteUpdate(self) -> bool:
- """Indicates if this PCS describes a Palette only Display
- Update. Allowed values are:
+ """Returns True if this composition is a Palette only Display Update.
+
+ Allowed values are:
0x00: False
0x80: True
"""
@@ -390,7 +399,9 @@ def compObjectCount(self) -> int:
return int.from_bytes(self._data[10:11])
def compObjects(self) -> Iterable[tuple[int, int, QPoint]]:
- """The composition objects, also known as window information
+ """Return an iterator over the composition objects defined in this segment.
+
+ The composition objects, also known as window information
objects, define the position on the screen of every image that
will be shown.
@@ -413,8 +424,8 @@ def compObjects(self) -> Iterable[tuple[int, int, QPoint]]:
return
-class WindowSegment(BaseSegment):
- """Window Definition Segment
+class WindowSegment(SegmentBase):
+ """Window Definition Segment.
This segment is used to define the rectangular area on the screen
where the sub picture will be shown. This rectangular area is called
@@ -424,12 +435,13 @@ class WindowSegment(BaseSegment):
"""
def validate(self) -> None:
- """Length is 1 + n*9"""
+ """Validate the segment data."""
+ # Length is 1 + n*9
self._valid = len(self._data) % 9 == 1
- return
@property
def count(self) -> int:
+ """Return the number of windows defined in the segment."""
return int.from_bytes(self._data[0:1])
def windows(self) -> Iterable[tuple[int, QRect]]:
@@ -447,8 +459,8 @@ def windows(self) -> Iterable[tuple[int, QRect]]:
return
-class PaletteSegment(BaseSegment):
- """Palette Definition Segment
+class PaletteSegment(SegmentBase):
+ """Palette Definition Segment.
This segment is used to define a palette for color conversion.
"""
@@ -456,11 +468,11 @@ class PaletteSegment(BaseSegment):
__slots__ = ("_col",)
def validate(self) -> None:
- """Length is 2 + n*5"""
+ """Validate the segment data."""
+ # Length is 2 + n*5
size = len(self._data)
self._valid = size >= 7 and size % 5 == 2
self._col: dict[int, QColor] = {}
- return
@property
def id(self) -> int:
@@ -490,17 +502,17 @@ def palette(self) -> list[bytes]:
return palette
-class ObjectSegment(BaseSegment):
- """Object Definition Segment
+class ObjectSegment(SegmentBase):
+ """Object Definition Segment.
This segment defines the graphics object. These are images with
rendered text on a transparent background.
"""
def validate(self) -> None:
- """The header is 11 bytes, followed by the raw image data."""
+ """Validate the segment data."""
+ # The header is 11 bytes, followed by the raw image data.
self._valid = len(self._data) >= 11
- return
@property
def id(self) -> int:
@@ -514,7 +526,9 @@ def version(self) -> int:
@property
def sequence(self) -> int:
- """If the image is split into a series of consecutive fragments,
+ """Return the sequence flag of this object.
+
+ If the image is split into a series of consecutive fragments,
the last fragment has this flag set. Possible values:
0x40: Last in sequence
0x80: First in sequence
@@ -541,7 +555,7 @@ def size(self) -> QSize:
@property
def rle(self) -> bytes:
- """This is the image data compressed using Run-length Encoding (RLE).
+ """Return the image data compressed using Run-length Encoding (RLE).
The size of the data is defined in the Object Data Length field.
"""
pos = 4 if self.sequence == 0x40 else 11
diff --git a/subtle_gui/formats/srtsubs.py b/subtle_gui/formats/srtsubs.py
index 4672615..bedcf84 100644
--- a/subtle_gui/formats/srtsubs.py
+++ b/subtle_gui/formats/srtsubs.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -37,9 +37,10 @@
class SRTSubs(SubtitlesBase):
+ """SRT Subtitles."""
+
def __init__(self) -> None:
super().__init__()
- return
def read(self, path: Path) -> None:
"""Read SRT info from file."""
@@ -48,7 +49,6 @@ def read(self, path: Path) -> None:
self._path = path
except Exception as exc:
logger.error("Could not read SRT file: %s", self._path, exc_info=exc)
- return
def write(self, path: Path | None = None, offset: float = 0.0) -> None:
"""Writer SRT data to file."""
@@ -59,9 +59,9 @@ def write(self, path: Path | None = None, offset: float = 0.0) -> None:
self._writeData(path, round(offset * 1000.0))
except Exception as exc:
logger.error("Could not write SRT file: %s", self._path, exc_info=exc)
- return
def copyFrames(self, other: SubtitlesBase) -> None:
+ """Copy frames from another subtitle object."""
return super()._copyFrames(SRTFrame, other)
##
@@ -81,7 +81,6 @@ def _readData(self, path: Path) -> None:
block = []
if block:
self._parseFrame(block)
- return
def _writeData(self, path: Path, offset: int = 0) -> None:
"""Write SRT text to file."""
@@ -105,7 +104,6 @@ def _writeData(self, path: Path, offset: int = 0) -> None:
if skipped:
logger.warning("Skipping %d entries with no text", skipped)
logger.info("Saved %d subtitle entries to SRT file with %d ms offset.", index, offset)
- return
def _parseFrame(self, block: list[str]) -> None:
"""Add a new frame to internal data."""
@@ -119,16 +117,16 @@ def _parseFrame(self, block: list[str]) -> None:
closeItalics([textCleanup(t) for t in block[2:]]),
)
)
- return
class SRTFrame(FrameBase):
+ """SRT Subtitle Frame."""
+
def __init__(self, index: int, start: int, end: int, text: list[str]) -> None:
super().__init__(index)
self._start = start
self._end = end
self._text = text
- return
@classmethod
def fromFrame(cls, index: int, other: FrameBase) -> FrameBase:
diff --git a/subtle_gui/formats/ssasubs.py b/subtle_gui/formats/ssasubs.py
index 73f2cf3..d072c59 100644
--- a/subtle_gui/formats/ssasubs.py
+++ b/subtle_gui/formats/ssasubs.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -44,6 +44,8 @@
class EventFormat(NamedTuple):
+ """Format of an SSA event."""
+
length: int
start: int
end: int
@@ -51,11 +53,12 @@ class EventFormat(NamedTuple):
class SSASubs(SubtitlesBase):
+ """SubStation Alpha Subtitles."""
+
def __init__(self) -> None:
super().__init__()
self._format: EventFormat | None = None
self._line = -1
- return
def read(self, path: Path) -> None:
"""Read a PGS file."""
@@ -64,13 +67,13 @@ def read(self, path: Path) -> None:
self._path = path
except Exception as e:
logger.error("Failed to read file data", exc_info=e)
- return
def write(self, path: Path | None = None) -> None:
"""Write a PGS file."""
raise NotImplementedError("Cannot write PGS files.")
def copyFrames(self, other: SubtitlesBase) -> None:
+ """Copy frames from another subtitle object."""
return super()._copyFrames(SSAFrame, other)
##
@@ -91,7 +94,6 @@ def _readData(self, path: Path) -> None:
self._parseDialogue(line[9:])
elif line.startswith("Format:"):
self._parseFormat(line[7:])
- return
def _parseFormat(self, line: str) -> None:
"""Parse dialogue format."""
@@ -100,7 +102,6 @@ def _parseFormat(self, line: str) -> None:
self._format = EventFormat(len(parts), parts.index("Start"), parts.index("End"), parts.index("Text"))
except ValueError:
logger.error("Invalid events format string")
- return
def _parseDialogue(self, line: str) -> None:
"""Parse a dialogue entry."""
@@ -139,12 +140,13 @@ def _processText(self, text: str) -> list[str]:
class SSAFrame(FrameBase):
+ """SubStation Alpha Subtitle Frame."""
+
def __init__(self, index: int, start: int, end: int, text: list[str]) -> None:
super().__init__(index=index)
self._start = start
self._end = end
self._text = text
- return
@classmethod
def fromFrame(cls, index: int, other: FrameBase) -> FrameBase:
diff --git a/subtle_gui/gui/filetree.py b/subtle_gui/gui/filetree.py
index 19f6114..bbaaab2 100644
--- a/subtle_gui/gui/filetree.py
+++ b/subtle_gui/gui/filetree.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -35,6 +35,8 @@
class GuiFileTree(QTreeView):
+ """GUI File Tree."""
+
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
@@ -54,8 +56,6 @@ def __init__(self, parent: QWidget) -> None:
self.doubleClicked.connect(self._itemDoubleClicked)
- return
-
##
# Methods
##
@@ -63,7 +63,6 @@ def __init__(self, parent: QWidget) -> None:
def saveSettings(self) -> None:
"""Save widget settings."""
CONFIG.setSizes("fileTreeColumns", [self.columnWidth(i) for i in range(self._model.columnCount())])
- return
##
# Private Slots
@@ -76,11 +75,9 @@ def _itemDoubleClicked(self, index: QModelIndex) -> None:
if path != self._current:
SHARED.media.loadMediaFile(path)
self._current = path
- return
@pyqtSlot(str)
def _directoryLoaded(self, path: str) -> None:
"""Process model finished loading directory."""
if path == "/":
self.expand(self._model.index(path))
- return
diff --git a/subtle_gui/gui/highlighter.py b/subtle_gui/gui/highlighter.py
index 33ac2c7..abb214a 100644
--- a/subtle_gui/gui/highlighter.py
+++ b/subtle_gui/gui/highlighter.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -38,6 +38,8 @@
class GuiDocHighlighter(QSyntaxHighlighter):
+ """GUI Document Syntax Highlighter."""
+
def __init__(self, document: QTextDocument) -> None:
super().__init__(document)
@@ -48,8 +50,6 @@ def __init__(self, document: QTextDocument) -> None:
self._syntaxCol = QTextCharFormat()
self._syntaxCol.setForeground(QApplication.palette().highlight().color())
- return
-
def highlightBlock(self, text: str) -> None:
"""Highlight a single block."""
data = self.currentBlockUserData()
@@ -63,16 +63,15 @@ def highlightBlock(self, text: str) -> None:
cFmt.merge(self._spellErr)
self.setFormat(x, 1, cFmt)
- return
-
class TextBlockData(QTextBlockUserData):
+ """Text Block User Data."""
+
__slots__ = ("_spellErrors",)
def __init__(self) -> None:
super().__init__()
self._spellErrors: list[tuple[int, int]] = []
- return
@property
def spellErrors(self) -> list[tuple[int, int]]:
diff --git a/subtle_gui/gui/imageviewer.py b/subtle_gui/gui/imageviewer.py
index 09e3f4a..cad9b84 100644
--- a/subtle_gui/gui/imageviewer.py
+++ b/subtle_gui/gui/imageviewer.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -33,6 +33,8 @@
class GuiImageViewer(QWidget):
+ """GUI Image Viewer."""
+
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
@@ -49,8 +51,6 @@ def __init__(self, parent: QWidget) -> None:
self.setLayout(self.outerBox)
- return
-
##
# Public Slots
##
@@ -66,7 +66,6 @@ def processFrameUpdate(self, frame: FrameBase) -> None:
scene.setSceneRect(QRectF(self._imageSize))
self.imageView.setScene(scene)
self._updateSizes()
- return
##
# Event
@@ -76,7 +75,6 @@ def resizeEvent(self, event: QResizeEvent) -> None:
"""Capture resize to update image scaling."""
super().resizeEvent(event)
self._updateSizes()
- return
##
# Internal Functions
@@ -86,4 +84,3 @@ def _updateSizes(self) -> None:
"""Scale down the image if it does not fit."""
if not self.imageView.rect().contains(self._imageSize):
self.imageView.fitInView(QRectF(self._imageSize), Qt.AspectRatioMode.KeepAspectRatio)
- return
diff --git a/subtle_gui/gui/mediaview.py b/subtle_gui/gui/mediaview.py
index 2e78a12..3d8b322 100644
--- a/subtle_gui/gui/mediaview.py
+++ b/subtle_gui/gui/mediaview.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -53,6 +53,8 @@
class GuiMediaView(QWidget):
+ """GUI Media View."""
+
C_TRACK = 0
C_TYPE = 1
C_CODEC = 2
@@ -121,8 +123,6 @@ def __init__(self, parent: QWidget) -> None:
self.setLayout(self.outerBox)
- return
-
##
# Methods
##
@@ -132,7 +132,6 @@ def saveSettings(self) -> None:
CONFIG.setSizes(
"mediaViewColumns", [self.tracksView.columnWidth(i) for i in range(self.tracksView.columnCount())]
)
- return
##
# Public Slots
@@ -152,7 +151,6 @@ def processNewMediaLoaded(self) -> None:
self._setTrackInfo(track, item)
self.tracksView.addTopLevelItem(item)
self._map[track.trackID] = item
- return
##
# Private Slots
@@ -170,7 +168,6 @@ def _extractTracks(self) -> None:
tracks.append((idx, path))
track.setTrackFile(path)
self._runTrackExtraction(file.filePath, tracks)
- return
@pyqtSlot()
def _cancelExtract(self) -> None:
@@ -180,7 +177,6 @@ def _cancelExtract(self) -> None:
self.progressBar.setValue(0)
self._extracted.clear()
self._extractWorker = None
- return
@pyqtSlot(QModelIndex)
def _itemDoubleClicked(self, index: QModelIndex) -> None:
@@ -195,13 +191,11 @@ def _itemDoubleClicked(self, index: QModelIndex) -> None:
self._emitTrack = idx
if idx in self._extracted and not self._emitTrack:
SHARED.media.setCurrentTrack(idx)
- return
@pyqtSlot(int)
def _extractProgress(self, value: int) -> None:
"""Process track extraction progress count."""
self.progressBar.setValue(value)
- return
@pyqtSlot()
def _extractFinished(self) -> None:
@@ -223,8 +217,6 @@ def _extractFinished(self) -> None:
SHARED.media.setCurrentTrack(self._emitTrack)
self._emitTrack = None
- return
-
##
# Internal Functions
##
@@ -244,7 +236,6 @@ def _setTrackInfo(self, track: MediaTrack, item: QTreeWidgetItem | None = None)
item.setText(self.C_ENABLED, self._trYes if track.enabled else "")
item.setText(self.C_DEFAULT, self._trYes if track.default else "")
item.setText(self.C_FORCED, self._trYes if track.forced else "")
- return
def _runTrackExtraction(self, path: Path, tracks: list[tuple[str, Path]]) -> None:
"""Call for extraction for a set of tracks."""
diff --git a/subtle_gui/gui/subsview.py b/subtle_gui/gui/subsview.py
index 6f7ee33..c749f09 100644
--- a/subtle_gui/gui/subsview.py
+++ b/subtle_gui/gui/subsview.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -38,6 +38,8 @@
class GuiSubtitleView(QWidget):
+ """GUI Subtitle View."""
+
C_DATA = 0
C_ID = 0
C_TIME = 1
@@ -70,8 +72,6 @@ def __init__(self, parent: QWidget) -> None:
self.setLayout(self.outerBox)
- return
-
##
# Methods
##
@@ -81,7 +81,6 @@ def saveSettings(self) -> None:
CONFIG.setSizes(
"subsViewColumns", [self.subEntries.columnWidth(i) for i in range(self.subEntries.columnCount() - 1)]
)
- return
##
# Public Slots
@@ -92,7 +91,6 @@ def processNewMediaLoaded(self) -> None:
"""Clear previous content."""
self._map = {}
self.subEntries.clear()
- return
@pyqtSlot()
def processNewTrackLoaded(self) -> None:
@@ -113,14 +111,12 @@ def processNewTrackLoaded(self) -> None:
self._map[frame.index] = item
self._updateItemText(item, frame.text)
self.subEntries.addTopLevelItem(item)
- return
@pyqtSlot(FrameBase)
def updateText(self, frame: FrameBase) -> None:
"""Update text for a specific frame."""
if item := self._map.get(frame.index):
self._updateItemText(item, frame.text)
- return
@pyqtSlot(Path, float)
def writeSrtFile(self, path: Path, offset: float = 0.0) -> None:
@@ -129,7 +125,6 @@ def writeSrtFile(self, path: Path, offset: float = 0.0) -> None:
writer = SRTSubs()
SHARED.media.currentTrack.copyFrames(writer)
writer.write(path, offset)
- return
@pyqtSlot(Path)
def readSubsFile(self, path: Path) -> None:
@@ -141,7 +136,6 @@ def readSubsFile(self, path: Path) -> None:
for frame in track.iterFrames():
if item := self._map.get(frame.index):
self._updateItemText(item, frame.text)
- return
@pyqtSlot(int)
def selectNearby(self, step: int) -> None:
@@ -155,7 +149,6 @@ def selectNearby(self, step: int) -> None:
)
item.setSelected(True)
self._itemClicked(self.subEntries.indexFromItem(item))
- return
##
# Private Slots
@@ -173,7 +166,6 @@ def _itemClicked(self, index: QModelIndex) -> None:
frame.setText(text)
self.updateText(frame)
self.subsFrameUpdated.emit(frame)
- return
##
# Internal Functions
@@ -182,4 +174,3 @@ def _itemClicked(self, index: QModelIndex) -> None:
def _updateItemText(self, item: QTreeWidgetItem, text: list[str]) -> None:
"""Update the subtitle text for a given item."""
item.setText(self.C_TEXT, "\u21b2".join(text))
- return
diff --git a/subtle_gui/gui/texteditor.py b/subtle_gui/gui/texteditor.py
index 3c35953..78249c6 100644
--- a/subtle_gui/gui/texteditor.py
+++ b/subtle_gui/gui/texteditor.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -35,6 +35,8 @@
class GuiTextEditor(QWidget):
+ """GUI Text Editor."""
+
newTextForFrame = pyqtSignal(FrameBase)
requestNewFrame = pyqtSignal(int)
@@ -110,8 +112,6 @@ def __init__(self, parent: QWidget) -> None:
self.keyContext.setContext(Qt.ShortcutContext.WidgetShortcut)
self.keyContext.activated.connect(self._openContextFromCursor)
- return
-
##
# Public Slots
##
@@ -139,8 +139,6 @@ def setEditorText(self, frame: FrameBase) -> None:
self._block = False
- return
-
@pyqtSlot(str)
def updateSpellLanguage(self, language: str) -> None:
"""Update the spell check language box."""
@@ -150,7 +148,6 @@ def updateSpellLanguage(self, language: str) -> None:
else:
self.spellLang.setCurrentIndex(0)
self.spellLang.blockSignals(False)
- return
##
# Private Slots
@@ -162,26 +159,22 @@ def _textChanged(self) -> None:
if self._frame and not self._block:
self._frame.setText(self._getText())
self.newTextForFrame.emit(self._frame)
- return
@pyqtSlot(int)
def _spellLangChanged(self, index: int) -> None:
"""Process change in spell check language."""
SHARED.spelling.setLanguage(self.spellLang.currentData())
self.highlight.rehighlight()
- return
@pyqtSlot()
def _requestPrevious(self) -> None:
"""Process Page Up key press."""
self.requestNewFrame.emit(-1)
- return
@pyqtSlot()
def _requestNext(self) -> None:
"""Process Page Down key press."""
self.requestNewFrame.emit(1)
- return
@pyqtSlot()
def _formatItalic(self) -> None:
@@ -212,20 +205,16 @@ def _formatItalic(self) -> None:
self.textEdit.setTextCursor(cursor)
- return
-
@pyqtSlot()
def _insertNoteSymbol(self) -> None:
"""Process Ctrl+J key press."""
cursor = self.textEdit.textCursor()
cursor.insertText("\u266a")
- return
@pyqtSlot()
def _openContextFromCursor(self) -> None:
"""Open the spell check context menu at the cursor."""
self._openContextMenu(self.textEdit.cursorRect().center())
- return
@pyqtSlot("QPoint")
def _openContextMenu(self, pos: QPoint) -> None:
@@ -263,8 +252,6 @@ def _openContextMenu(self, pos: QPoint) -> None:
ctxMenu.exec(viewport.mapToGlobal(pos))
ctxMenu.deleteLater()
- return
-
##
# Internal Functions
##
@@ -301,7 +288,6 @@ def _correctWord(self, cursor: QTextCursor, word: str) -> None:
cursor.endEditBlock()
cursor.setPosition(pos)
self.textEdit.setTextCursor(cursor)
- return
def _addWord(self, word: str, block: QTextBlock, save: bool) -> None:
"""Slot for the spell check context menu triggered when the user
@@ -311,7 +297,6 @@ def _addWord(self, word: str, block: QTextBlock, save: bool) -> None:
logger.debug("Added '%s' to session dictionary, saved = %s", word, str(save))
SHARED.spelling.addWord(word, save=save)
self.highlight.rehighlightBlock(block)
- return
def _spellErrorAtPos(self, pos: int) -> tuple[str, int, int, list[str]]:
"""Check if there is a misspelled word at a given position in
diff --git a/subtle_gui/gui/toolspanel.py b/subtle_gui/gui/toolspanel.py
index 1487343..5462b1d 100644
--- a/subtle_gui/gui/toolspanel.py
+++ b/subtle_gui/gui/toolspanel.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -47,6 +47,8 @@
class GuiToolsPanel(QWidget):
+ """GUI Tools Panel."""
+
D_SUBS_PATH = Qt.ItemDataRole.UserRole
requestSrtSave = pyqtSignal(Path, float)
@@ -156,8 +158,6 @@ def __init__(self, parent: QWidget) -> None:
self.setLayout(self.outerBox)
- return
-
##
# Public Slots
##
@@ -171,7 +171,6 @@ def processNewMediaLoaded(self) -> None:
self.mediaFile.setText(path.name)
self._scanForSubs(path)
self._updateTrackInfo()
- return
@pyqtSlot()
def processNewTrackLoaded(self) -> None:
@@ -180,7 +179,6 @@ def processNewTrackLoaded(self) -> None:
self.srtForced.setChecked(track.forced)
self.srtSDH.setChecked(track.sdh)
self._updateTrackInfo()
- return
##
# Private Slots
@@ -196,7 +194,6 @@ def _clickedSaveSrt(self) -> None:
self.requestSrtSave.emit(folder / name, self.srtOffset.value())
except Exception as exc:
logger.error("Failed to prepare subtitles folder", exc_info=exc)
- return
@pyqtSlot()
def _clickedLoadSubs(self) -> None:
@@ -204,7 +201,6 @@ def _clickedLoadSubs(self) -> None:
if items := self.subsList.selectedItems():
if (path := Path(items[0].data(self.D_SUBS_PATH))).is_file():
self.requestSubsLoad.emit(path)
- return
@pyqtSlot()
def _updateTrackInfo(self) -> None:
@@ -228,8 +224,6 @@ def _updateTrackInfo(self) -> None:
self.srtSaveDir.setText(str(folder))
self.srtFileName.setText(".".join(bits))
- return
-
##
# Internal Functions
##
@@ -261,5 +255,3 @@ def _scanForSubs(self, path: Path) -> None:
except Exception as exc:
logger.error("Could not scan path: %s", root, exc_info=exc)
-
- return
diff --git a/subtle_gui/guimain.py b/subtle_gui/guimain.py
index bfab160..3493e1c 100644
--- a/subtle_gui/guimain.py
+++ b/subtle_gui/guimain.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -49,6 +49,8 @@
class GuiMain(QMainWindow):
+ """Main GUI Window."""
+
def __init__(self) -> None:
super().__init__()
@@ -129,8 +131,6 @@ def __init__(self) -> None:
self.setCentralWidget(self.splitMain)
self.resize(CONFIG.getSize("mainWindow"))
- return
-
##
# Events
##
@@ -150,4 +150,3 @@ def closeEvent(self, event: QCloseEvent) -> None:
CONFIG.save()
CONFIG.cleanup()
event.accept()
- return
diff --git a/subtle_gui/ocr/base.py b/subtle_gui/ocr/base.py
index a43036f..f9e6562 100644
--- a/subtle_gui/ocr/base.py
+++ b/subtle_gui/ocr/base.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -33,9 +33,12 @@
class OCRBase(ABC):
+ """Base class for OCR implementations."""
+
def __init__(self) -> None:
return
@abstractmethod
def processImage(self, index: int, image: QImage, lang: list[str]) -> list[str]:
+ """Process an image and return the recognized text."""
raise NotImplementedError
diff --git a/subtle_gui/ocr/tesseract.py b/subtle_gui/ocr/tesseract.py
index 52021fd..17bb20f 100644
--- a/subtle_gui/ocr/tesseract.py
+++ b/subtle_gui/ocr/tesseract.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -80,9 +80,10 @@
class TesseractOCR(OCRBase):
+ """Tesseract OCR implementation."""
+
def __init__(self) -> None:
super().__init__()
- return
def processImage(self, index: int, image: QImage, lang: list[str]) -> list[str]:
"""Perform OCR on a QImage."""
diff --git a/subtle_gui/shared.py b/subtle_gui/shared.py
index 760e480..49ef281 100644
--- a/subtle_gui/shared.py
+++ b/subtle_gui/shared.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -37,6 +37,8 @@
class SharedData(QObject):
+ """Shared Data Object."""
+
spellLanguageChanged = pyqtSignal(str)
def __init__(self) -> None:
@@ -45,7 +47,6 @@ def __init__(self) -> None:
self._ocr: OCRBase | None = None
self._spell: SpellEnchant | None = None
self._icons: GuiIcons | None = None
- return
@property
def media(self) -> MediaData:
@@ -83,7 +84,6 @@ def initSharedData(self, media: MediaData, ocr: OCRBase, spell: SpellEnchant, ic
self._ocr = ocr
self._spell = spell
self._icons = icons
- return
def setSpellLanguage(self, track: MediaTrack | None) -> None:
"""Set the current spell checking language."""
@@ -94,4 +94,3 @@ def setSpellLanguage(self, track: MediaTrack | None) -> None:
else:
self.spelling.setLanguage(None)
self.spellLanguageChanged.emit("None")
- return
diff --git a/tests/test_common.py b/tests/test_common.py
index 48dcae8..e21e6e8 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
-"""
+""" # noqa
from __future__ import annotations
@@ -36,4 +36,3 @@ def testCommon_closeItalics():
"Foo foo foo",
"Bar",
]
- return
From 8dc0024ac4045516e2753160cff733448f7fa508 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Wed, 24 Jun 2026 22:52:03 +0200
Subject: [PATCH 4/4] Update workflow
---
.github/workflows/syntax.yml | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml
index 6cc30cb..7bd91d0 100644
--- a/.github/workflows/syntax.yml
+++ b/.github/workflows/syntax.yml
@@ -28,14 +28,10 @@ jobs:
- name: Ruff Check
run: |
uv run ruff --version
+ uv run ruff format --check
uv run ruff check
- name: Pyright Check
run: |
uv run pyright --version
uv run pyright
-
- - name: Isort Check
- run: |
- uv run isort --version
- uv run isort --check .