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
8 changes: 8 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ jobs:

- name: Publish Package
run: uv publish

- name: Upload Artifacts
uses: actions/upload-artifact@v7
with:
name: Subtle-PyPI
path: dist/*
if-no-files-found: error
retention-days: 14
27 changes: 14 additions & 13 deletions .github/workflows/syntax.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,32 @@ on:

jobs:
checkSyntax:
name: Linting and Type Checking
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@v7

- name: Install UV and Python
uses: astral-sh/setup-uv@v8.2.0
with:
python-version: "3.13"
enable-cache: true

- name: Install Dependencies
run: pip install -r requirements.txt -r requirements-dev.txt
run: uv sync --locked

- name: Ruff Check
run: |
ruff --version
ruff check
uv run ruff --version
uv run ruff check

- name: Pyright Check
run: |
pyright --version
pyright
uv run pyright --version
uv run pyright

- name: Isort Check
run: |
isort --version
isort --check .
uv run isort --version
uv run isort --check .
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
# Subtle Changelog

## Version 0.1.0 [2024-10-11]
## Version 26.1.1 [2026-06-24]

### Release Notes

First test version of the app with basic functionality. It can read media files, and extract SRT,
Small bugfix release that renames the root package to `subtle_gui` since a project named `subtle`
already exists.

### Detailed Changelog

* Renames the installed package to `subtle_gui`.
* Updates main readme with PyPI info.

----

## Version 26.1 [2026-06-24]

### Release Notes

Initial release of the app with basic functionality. It can read media files, and extract SRT,
PGS and SSA subtitles. PGS subtitles can be OCR scanned to extract the text, which can then be
edited and spell checked. Italics is the only supported formatting.

Expand Down
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,27 @@ The app is developed on Debian Linux, and I have no idea if it runs on other sys
browser certainly assumes that file system root is `/`, so it probably doesn't work on Windows
without modification.

## Usage
## Installation

Install the PyQt6 and enchant dependencies, either from distro repo or in a Python virtual env
using the `requirements.txt` file.

Install MkvToolNix and Tesseract. For Debian, this is:
Install with:

```bash
sudo apt install mkvtoolnix tesseract-ocr
pipx install subtle-gui
```

Launch the Subtle start script from the root of the source:
Launch with:

```bash
./subtle.py
subtle
```

Add `--info` or `--debug` for more log output.
### Prerequisites

Subtle relies on MkvToolNix and Tesseract OCR for processing the media files and performing the OCR
scanning.

For Debian Linux, they can be installed with:

```bash
sudo apt install mkvtoolnix tesseract-ocr
```
19 changes: 16 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,32 @@ requires-python = ">=3.11"
dependencies = ["pyqt6>=6.4", "pyenchant>=3.0.0"]
dynamic = ["version"]

[dependency-groups]
dev = [
{ include-group = "build" },
{ include-group = "test" },
{ include-group = "lint" },
]
build = ["build", "setuptools>=77.0.3"]
test = ["coverage>=7.2.0", "pytest-qt", "pytest-timeout", "pytest>=6.0.0"]
lint = ["isort", "pyright", "ruff"]

[project.urls]
Homepage = "https://github.com/vkbo/subtle"
Repository = "https://github.com/vkbo/subtle"
Issues = "https://github.com/vkbo/subtle/issues"

[project.gui-scripts]
subtle = "subtle:main"
subtle = "subtle_gui:main"

[tool.setuptools.dynamic]
version = { attr = "subtle.__init__.__version__" }
version = { attr = "subtle_gui.__init__.__version__" }

[tool.setuptools.packages.find]
include = ["subtle*"]
include = ["subtle_gui*"]

[tool.setuptools.package-data]
subtle_gui = ["assets/*"]

[tool.isort]
py_version = "311"
Expand Down
3 changes: 0 additions & 3 deletions requirements-dev.txt

This file was deleted.

2 changes: 0 additions & 2 deletions requirements.txt

This file was deleted.

4 changes: 2 additions & 2 deletions subtle.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
os.curdir = os.path.abspath(os.path.dirname(__file__))

if __name__ == "__main__":
import subtle
subtle.main(sys.argv[1:])
import subtle_gui
subtle_gui.main(sys.argv[1:])
16 changes: 8 additions & 8 deletions subtle/__init__.py → subtle_gui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,24 @@

from typing import TYPE_CHECKING

from subtle.config import Config
from subtle.shared import SharedData
from subtle_gui.config import Config
from subtle_gui.shared import SharedData

from PyQt6.QtWidgets import QApplication

if TYPE_CHECKING: # pragma: no cover
from subtle.guimain import GuiMain
from subtle_gui.guimain import GuiMain

# Package Meta
# ============

__package__ = "subtle-gui"
__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.0"
__version__ = "26.1.1"
__date__ = "2026-06-24"

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -100,7 +100,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public Licence for more details.\n"
"GNU General Public License for more details.\n"
"\n"
"Usage:\n"
" -h, --help Print this message.\n"
Expand Down Expand Up @@ -160,12 +160,12 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
cHandle.setFormatter(logging.Formatter(fmt=logFmt, style="{"))
pkgLogger.addHandler(cHandle)

logger.info("Starting Subtle %s (%s)", __version__, __date__)
logger.info("Starting Subtle %s [%s]", __version__, __date__)

# Finish initialising config
CONFIG.initialise()

from subtle.guimain import GuiMain
from subtle_gui.guimain import GuiMain

app = QApplication([CONFIG.appName])
app.setApplicationName(CONFIG.appName)
Expand Down
File renamed without changes
File renamed without changes.
2 changes: 1 addition & 1 deletion subtle/config.py → subtle_gui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from pathlib import Path
from typing import Literal

from subtle.common import jsonEncode
from subtle_gui.common import jsonEncode

from PyQt6.QtCore import (
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QSize,
Expand Down
File renamed without changes.
File renamed without changes.
16 changes: 8 additions & 8 deletions subtle/core/media.py → subtle_gui/core/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@

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.pgssubs import PGSSubs
from subtle.formats.srtsubs import SRTSubs
from subtle.formats.ssasubs import SSASubs
from subtle_gui import SHARED
from subtle_gui.common import decodeTS
from subtle_gui.constants import MediaType
from subtle_gui.core.mediafile import MediaFile
from subtle_gui.formats.pgssubs import PGSSubs
from subtle_gui.formats.srtsubs import SRTSubs
from subtle_gui.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
from subtle_gui.formats.base import FrameBase, SubtitlesBase

logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion subtle/core/mediafile.py → subtle_gui/core/mediafile.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from hashlib import sha1
from typing import TYPE_CHECKING

from subtle import CONFIG
from subtle_gui import CONFIG

if TYPE_CHECKING:
from collections.abc import Iterable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from typing import TYPE_CHECKING

from subtle.common import checkInt
from subtle_gui.common import checkInt

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from pathlib import Path
from typing import TYPE_CHECKING

from subtle import CONFIG
from subtle_gui import CONFIG

from PyQt6.QtCore import QLocale

Expand Down
2 changes: 1 addition & 1 deletion subtle/formats/base.py → subtle_gui/formats/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

from subtle.common import formatTS
from subtle_gui.common import formatTS

if TYPE_CHECKING:
from collections.abc import Iterable
Expand Down
4 changes: 2 additions & 2 deletions subtle/formats/pgssubs.py → subtle_gui/formats/pgssubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING

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

from PyQt6.QtCore import QMargins, QPoint, QRect, QSize
from PyQt6.QtGui import QColor, QImage, QPainter, qRgba
Expand Down
4 changes: 2 additions & 2 deletions subtle/formats/srtsubs.py → subtle_gui/formats/srtsubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

from typing import TYPE_CHECKING

from subtle.common import closeItalics, decodeTS, formatTS, textCleanup
from subtle.formats.base import FrameBase, SubtitlesBase
from subtle_gui.common import closeItalics, decodeTS, formatTS, textCleanup
from subtle_gui.formats.base import FrameBase, SubtitlesBase

if TYPE_CHECKING:
from pathlib import Path
Expand Down
4 changes: 2 additions & 2 deletions subtle/formats/ssasubs.py → subtle_gui/formats/ssasubs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@

from typing import TYPE_CHECKING, NamedTuple

from subtle.common import closeItalics, decodeTS, regexCleanup, simplified, textCleanup
from subtle.formats.base import FrameBase, SubtitlesBase
from subtle_gui.common import closeItalics, decodeTS, regexCleanup, simplified, textCleanup
from subtle_gui.formats.base import FrameBase, SubtitlesBase

if TYPE_CHECKING:
from pathlib import Path
Expand Down
2 changes: 1 addition & 1 deletion subtle/gui/filetree.py → subtle_gui/gui/filetree.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

from pathlib import Path

from subtle import CONFIG, SHARED
from subtle_gui import CONFIG, SHARED

from PyQt6.QtCore import QModelIndex, pyqtSlot
from PyQt6.QtGui import QFileSystemModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import logging
import re

from subtle import SHARED
from subtle_gui import SHARED

from PyQt6.QtGui import (
QColor, QSyntaxHighlighter, QTextBlockUserData, QTextCharFormat,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import logging

from subtle.formats.base import FrameBase
from subtle_gui.formats.base import FrameBase

from PyQt6.QtCore import QRect, QRectF, Qt, pyqtSlot
from PyQt6.QtGui import QPixmap, QResizeEvent
Expand Down
12 changes: 6 additions & 6 deletions subtle/gui/mediaview.py → subtle_gui/gui/mediaview.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@

from typing import TYPE_CHECKING

from subtle import CONFIG, SHARED
from subtle.common import formatTS
from subtle.constants import GuiLabels, MediaType, trConst
from subtle.core.mediafile import EXTRACTABLE, SUBTITLE_FILE
from subtle.core.mkvextract import MkvExtract
from subtle_gui import CONFIG, SHARED
from subtle_gui.common import formatTS
from subtle_gui.constants import GuiLabels, MediaType, trConst
from subtle_gui.core.mediafile import EXTRACTABLE, SUBTITLE_FILE
from subtle_gui.core.mkvextract import MkvExtract

from PyQt6.QtCore import QModelIndex, pyqtSlot
from PyQt6.QtWidgets import (
Expand All @@ -40,7 +40,7 @@
if TYPE_CHECKING:
from pathlib import Path

from subtle.core.media import MediaTrack
from subtle_gui.core.media import MediaTrack

logger = logging.getLogger(__name__)

Expand Down
Loading