Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/syntax.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Linting

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
checkSyntax:
runs-on: ubuntu-latest
steps:
- name: Python Setup
uses: actions/setup-python@v5
with:
python-version: "3"
architecture: x64
- name: Checkout Source
uses: actions/checkout@v4
- name: Install Dependencies
run: pip install -r requirements.txt -r requirements-dev.txt
- name: Ruff Check
run: |
ruff --version
ruff check
- name: Pyright Check
run: |
pyright --version
pyright
- name: Isort Check
run: |
isort --version
isort --check .
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Code
# Python
.venv/
.ruff_cache/
*.pyc

# Tests
.pytest_cache
.coverage
coverage.*
htmlcov/
73 changes: 60 additions & 13 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,17 @@ readme = {file = "README.md", content-type = "text/markdown"}
license = {text = "GNU General Public License v3"}
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Development Status :: 3 - Alpha",
"Operating System :: OS Independent",
"Intended Audience :: End Users/Desktop",
"Natural Language :: English",
"Topic :: Multimedia",
]
requires-python = ">=3.9"
requires-python = ">=3.11"
dependencies = [
"pyqt6>=6.4",
"pyenchant>=3.0.0",
Expand All @@ -46,20 +44,69 @@ version = {attr = "subtle.__version__"}
include = ["subtle*"]

[tool.isort]
py_version="310"
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.flake8]
max-line-length = 99
ignore = ["E133", "E221", "E226", "E228", "E241", "W503", "ANN101", "ANN102", "ANN401"]
per-file-ignores = ["tests/*:ANN"]
exclude = ["docs/*"]
[tool.ruff]
line-length = 99

[tool.autopep8]
max_line_length = 99
ignore = ["E133", "E221", "E226", "E228", "E241", "W503"]
[tool.ruff.lint]
preview = true

# 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)
]
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
"PLW2901", # redefined-loop-name
"RUF001", # ambiguous-unicode-character-string
"RUF002", # ambiguous-unicode-character-docstring
"RUF015", # unnecessary-iterable-allocation-for-first-element
"UP015", # redundant-open-modes
"UP030", # format-literals
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["ANN", "SLF", "TC", "PLC2701"]
"utils/*" = ["ANN", "SLF", "TC"]

[tool.ruff.format]
quote-style = "double"

[tool.pyright]
include = ["subtle"]
exclude = ["**/__pycache__"]

reportIncompatibleMethodOverride = false

pythonVersion = "3.11"
3 changes: 3 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
isort
pyright
ruff
8 changes: 4 additions & 4 deletions subtle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,18 @@ def main(sysArgs: list | None = None) -> GuiMain | None:

# Parse Options
try:
inOpts, inRemain = getopt.getopt(sysArgs, shortOpt, longOpt)
inOpts, _ = getopt.getopt(sysArgs, shortOpt, longOpt)
except getopt.GetoptError as exc:
print(helpMsg)
print(f"ERROR: {str(exc)}")
print(f"ERROR: {exc!s}")
sys.exit(2)

for inOpt, inArg in inOpts:
for inOpt, _ in inOpts:
if inOpt in ("-h", "--help"):
print(helpMsg)
sys.exit(0)
elif inOpt in ("-v", "--version"):
print("Subtle Version %s [%s]" % (__version__, __date__))
print(f"Subtle Version {__version__} [{__date__}]")
sys.exit(0)
elif inOpt in ("-i", "--info"):
logLevel = logging.INFO
Expand Down
14 changes: 8 additions & 6 deletions subtle/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@

import json
import logging
import re

from typing import Any, Literal
from typing import TYPE_CHECKING, Any, Literal

if TYPE_CHECKING:
import re

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -74,10 +76,10 @@ 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."""
for regEx, value in patterns:
matches = []
for match in regEx.finditer(text):
if (s := match.start(1)) >= 0 and (e := match.end(1)) >= 0:
matches.append((s, e, value))
matches = [
(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:]
return text
Expand Down
3 changes: 2 additions & 1 deletion subtle/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from __future__ import annotations

from enum import Enum
from typing import Final

from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication

Expand All @@ -40,7 +41,7 @@ class MediaType(Enum):

class GuiLabels:

MEDIA_TYPES = {
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"),
Expand Down
10 changes: 7 additions & 3 deletions subtle/core/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,24 @@

import logging

from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING

from subtle import SHARED
from subtle.common import decodeTS
from subtle.constants import MediaType
from subtle.core.mediafile import MediaFile
from subtle.formats.base import FrameBase, SubtitlesBase
from subtle.formats.pgssubs import PGSSubs
from subtle.formats.srtsubs import SRTSubs
from subtle.formats.ssasubs import SSASubs

from PyQt6.QtCore import QObject, pyqtSignal

if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path

from subtle.formats.base import FrameBase, SubtitlesBase

logger = logging.getLogger(__name__)


Expand Down
7 changes: 5 additions & 2 deletions subtle/core/mediafile.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@
import logging
import subprocess

from collections.abc import Iterable
from enum import IntEnum
from hashlib import sha1
from pathlib import Path
from typing import TYPE_CHECKING

from subtle import CONFIG

if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path

logger = logging.getLogger(__name__)


Expand Down
5 changes: 4 additions & 1 deletion subtle/core/mkvextract.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@

import logging

from pathlib import Path
from typing import TYPE_CHECKING

from subtle.common import checkInt

from PyQt6.QtCore import QObject, QProcess, pyqtSignal, pyqtSlot

if TYPE_CHECKING:
from pathlib import Path

logger = logging.getLogger(__name__)


Expand Down
5 changes: 4 additions & 1 deletion subtle/core/spellcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
import json
import logging

from collections.abc import Iterator
from pathlib import Path
from typing import TYPE_CHECKING

from subtle import CONFIG

from PyQt6.QtCore import QLocale

if TYPE_CHECKING:
from collections.abc import Iterator

logger = logging.getLogger(__name__)


Expand Down
13 changes: 8 additions & 5 deletions subtle/formats/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,22 @@
import logging

from abc import ABC, abstractmethod
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING

from subtle.common import formatTS

from PyQt6.QtGui import QImage
if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path

from PyQt6.QtGui import QImage

logger = logging.getLogger(__name__)


class SubtitlesBase(ABC):

__slots__ = ("_path", "_frames")
__slots__ = ("_frames", "_path")

def __init__(self) -> None:
self._path: Path | None = None
Expand Down Expand Up @@ -119,7 +122,7 @@ def _copyFrames(self, frameType: type[FrameBase], other: SubtitlesBase) -> None:

class FrameBase(ABC):

__slots__ = ("_index", "_start", "_end", "_text")
__slots__ = ("_end", "_index", "_start", "_text")

def __init__(self, index: int) -> None:
self._index: int = index
Expand Down
15 changes: 9 additions & 6 deletions subtle/formats/pgssubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@
import logging

from abc import ABC, abstractmethod
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING

from subtle.common import formatTS
from subtle.formats.base import FrameBase, SubtitlesBase

from PyQt6.QtCore import QMargins, QPoint, QRect, QSize
from PyQt6.QtGui import QColor, QImage, QPainter, qRgba

if TYPE_CHECKING:
from collections.abc import Iterable
from pathlib import Path

logger = logging.getLogger(__name__)

COMP_NORMAL = 0x00
Expand Down Expand Up @@ -172,7 +175,7 @@ def getImage(self) -> QImage:

class DisplaySet:

__slots__ = ("_pcs", "_wds", "_pds", "_ods", "_image")
__slots__ = ("_image", "_ods", "_pcs", "_pds", "_wds")

def __init__(self, pcs: PresentationSegment) -> None:
self._pcs: PresentationSegment = pcs
Expand Down Expand Up @@ -293,7 +296,7 @@ def render(self, crop: bool = True) -> QImage:

frame = frame.united(QRect(offset, box))
painter.drawImage(offset, QImage(
raw, box.width(), box.height(), QImage.Format.Format_ARGB32
bytes(raw), box.width(), box.height(), QImage.Format.Format_ARGB32
))

painter.end()
Expand All @@ -307,7 +310,7 @@ def render(self, crop: bool = True) -> QImage:

class BaseSegment(ABC):

__slots__ = ("_ts", "_data", "_valid")
__slots__ = ("_data", "_ts", "_valid")

def __init__(self, ts: int, data: bytes) -> None:
self._ts = ts
Expand Down Expand Up @@ -453,7 +456,7 @@ class PaletteSegment(BaseSegment):

This segment is used to define a palette for color conversion.
"""
__slots__ = ("_col")
__slots__ = ("_col",)

def validate(self) -> None:
"""Length is 2 + n*5"""
Expand Down
Loading