subtle-gui 26.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
subtle/__init__.py ADDED
@@ -0,0 +1,184 @@
1
+ """
2
+ Subtle – Init File
3
+ ==================
4
+
5
+ This file is a part of Subtle
6
+ Copyright (C) Veronica Berglyd Olsen
7
+
8
+ This program is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation, either version 3 of the License, or
11
+ (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful, but
14
+ WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import getopt
24
+ import logging
25
+ import os
26
+ import sys
27
+
28
+ from typing import TYPE_CHECKING
29
+
30
+ from subtle.config import Config
31
+ from subtle.shared import SharedData
32
+
33
+ from PyQt6.QtWidgets import QApplication
34
+
35
+ if TYPE_CHECKING: # pragma: no cover
36
+ from subtle.guimain import GuiMain
37
+
38
+ # Package Meta
39
+ # ============
40
+
41
+ __package__ = "subtle-gui"
42
+ __copyright__ = "Copyright (C) Veronica Berglyd Olsen"
43
+ __license__ = "GPLv3"
44
+ __author__ = "Veronica Berglyd Olsen"
45
+ __maintainer__ = "Veronica Berglyd Olsen"
46
+ __email__ = "code@vkbo.net"
47
+ __version__ = "26.1.0"
48
+ __date__ = "2026-06-24"
49
+
50
+ logger = logging.getLogger(__name__)
51
+
52
+
53
+ ##
54
+ # Main Program
55
+ ##
56
+
57
+ CONFIG = Config()
58
+ SHARED = SharedData()
59
+
60
+ # ANSI Colours
61
+ RED = "\033[91m"
62
+ GREEN = "\033[92m"
63
+ YELLOW = "\033[93m"
64
+ BLUE = "\033[94m"
65
+ WHITE = "\033[97m"
66
+ END = "\033[0m"
67
+
68
+ # Log Format Components
69
+ TIME = "[{asctime:}]"
70
+ FILE = "{filename:>18}"
71
+ LINE = "{lineno:<4d}"
72
+ LVLP = "{levelname:8}"
73
+ LVLC = "{levelname:17}"
74
+ TEXT = "{message:}"
75
+
76
+ # Read Environment
77
+ FORCE_COLOR = bool(os.environ.get("FORCE_COLOR"))
78
+ NO_COLOR = bool(os.environ.get("NO_COLOR"))
79
+
80
+
81
+ def main(sysArgs: list | None = None) -> GuiMain | None:
82
+ """Parse command line, set up logging, and launch main GUI."""
83
+ if sysArgs is None:
84
+ sysArgs = sys.argv[1:]
85
+
86
+ # Valid Input Options
87
+ shortOpt = "hvicd"
88
+ longOpt = [
89
+ "help",
90
+ "version",
91
+ "info",
92
+ "debug",
93
+ "color",
94
+ ]
95
+
96
+ helpMsg = (
97
+ f"Subtle {__version__} ({__date__})\n"
98
+ f"{__copyright__}\n"
99
+ "\n"
100
+ "This program is distributed in the hope that it will be useful,\n"
101
+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
102
+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
103
+ "GNU General Public Licence for more details.\n"
104
+ "\n"
105
+ "Usage:\n"
106
+ " -h, --help Print this message.\n"
107
+ " -v, --version Print program version and exit.\n"
108
+ " -i, --info Print additional runtime information.\n"
109
+ " -d, --debug Print debug output. Includes --info.\n"
110
+ " -c, --color Add ANSI colors to log output.\n"
111
+ )
112
+
113
+ # Defaults
114
+ logLevel = logging.WARN
115
+ fmtColor = FORCE_COLOR
116
+ fmtLong = False
117
+
118
+ # Parse Options
119
+ try:
120
+ inOpts, _ = getopt.getopt(sysArgs, shortOpt, longOpt)
121
+ except getopt.GetoptError as exc:
122
+ print(helpMsg)
123
+ print(f"ERROR: {exc!s}")
124
+ sys.exit(2)
125
+
126
+ for inOpt, _ in inOpts:
127
+ if inOpt in ("-h", "--help"):
128
+ print(helpMsg)
129
+ sys.exit(0)
130
+ elif inOpt in ("-v", "--version"):
131
+ print(f"Subtle Version {__version__} [{__date__}]")
132
+ sys.exit(0)
133
+ elif inOpt in ("-i", "--info"):
134
+ logLevel = logging.INFO
135
+ elif inOpt in ("-d", "--debug"):
136
+ fmtLong = True
137
+ logLevel = logging.DEBUG
138
+ elif inOpt in ("-c", "--color"):
139
+ fmtColor = not NO_COLOR
140
+
141
+ if fmtColor:
142
+ # This will overwrite the default level names, and also ensure that
143
+ # they can be converted back to integer levels
144
+ logging.addLevelName(logging.DEBUG, f"{BLUE}DEBUG{END}")
145
+ logging.addLevelName(logging.INFO, f"{GREEN}INFO{END}")
146
+ logging.addLevelName(logging.WARNING, f"{YELLOW}WARNING{END}")
147
+ logging.addLevelName(logging.ERROR, f"{RED}ERROR{END}")
148
+ logging.addLevelName(logging.CRITICAL, f"{RED}CRITICAL{END}")
149
+
150
+ logTxt = f"{LVLC} {TEXT}" if fmtColor else f"{LVLP} {TEXT}"
151
+ logPos = f"{BLUE}{FILE}{END}:{WHITE}{LINE}{END}" if fmtColor else f"{FILE}:{LINE}"
152
+ logFmt = f"{TIME} {logPos} {logTxt}" if fmtLong else logTxt
153
+
154
+ # Setup Logging
155
+ pkgLogger = logging.getLogger(__package__)
156
+ pkgLogger.setLevel(logLevel)
157
+ if len(pkgLogger.handlers) == 0:
158
+ # Make sure we only create one logger (mostly an issue with tests)
159
+ cHandle = logging.StreamHandler()
160
+ cHandle.setFormatter(logging.Formatter(fmt=logFmt, style="{"))
161
+ pkgLogger.addHandler(cHandle)
162
+
163
+ logger.info("Starting Subtle %s (%s)", __version__, __date__)
164
+
165
+ # Finish initialising config
166
+ CONFIG.initialise()
167
+
168
+ from subtle.guimain import GuiMain
169
+
170
+ app = QApplication([CONFIG.appName])
171
+ app.setApplicationName(CONFIG.appName)
172
+ app.setApplicationVersion(__version__)
173
+ app.setDesktopFileName(CONFIG.appName)
174
+
175
+ # Run Config steps that require the QApplication
176
+ CONFIG.load()
177
+ CONFIG.fonts(app)
178
+ CONFIG.localisation(app)
179
+
180
+ # Launch main GUI
181
+ gui = GuiMain()
182
+ gui.show()
183
+
184
+ sys.exit(app.exec())
subtle/common.py ADDED
@@ -0,0 +1,178 @@
1
+ """
2
+ Subtle – Common Functions
3
+ =========================
4
+
5
+ This file is a part of Subtle
6
+ Copyright (C) Veronica Berglyd Olsen
7
+
8
+ This program is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation, either version 3 of the License, or
11
+ (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful, but
14
+ WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import logging
25
+
26
+ from typing import TYPE_CHECKING, Any, Literal
27
+
28
+ if TYPE_CHECKING:
29
+ import re
30
+
31
+ logger = logging.getLogger(__name__)
32
+
33
+ T_Subs = Literal["SRT"] | Literal["SSA"]
34
+
35
+
36
+ def simplified(text: str) -> str:
37
+ """Take a string and strip leading and trailing whitespaces, and
38
+ replace all occurrences of (multiple) whitespaces with a 0x20 space.
39
+ """
40
+ return " ".join(str(text).strip().split())
41
+
42
+
43
+ def checkInt(value: Any, default: int) -> int:
44
+ """Check if a variable is an integer."""
45
+ try:
46
+ return int(value)
47
+ except Exception:
48
+ return default
49
+
50
+
51
+ def formatInt(value: int) -> str:
52
+ """Formats an integer with k, M, G etc."""
53
+ if not isinstance(value, int):
54
+ return "ERR"
55
+
56
+ fVal = float(value)
57
+ if fVal > 1000.0:
58
+ for pF in ["k", "M", "G", "T", "P", "E"]:
59
+ fVal /= 1000.0
60
+ if fVal < 1000.0:
61
+ if fVal < 10.0:
62
+ return f"{fVal:4.2f}\u202f{pF}"
63
+ elif fVal < 100.0:
64
+ return f"{fVal:4.1f}\u202f{pF}"
65
+ else:
66
+ return f"{fVal:3.0f}\u202f{pF}"
67
+
68
+ return str(value) + "\u202f"
69
+
70
+
71
+ def textCleanup(text: str) -> str:
72
+ """Do some common cleanup on text strings."""
73
+ return text.replace("--", "\u2014").replace("....", "...")
74
+
75
+
76
+ def regexCleanup(text: str, patterns: list[tuple[re.Pattern, str]]) -> str:
77
+ """Replaces all occurrences of match group 1 in patterns."""
78
+ for regEx, value in patterns:
79
+ matches = [
80
+ (s, e, value) for match in regEx.finditer(text)
81
+ if (s := match.start(1)) >= 0 and (e := match.end(1)) >= 0
82
+ ]
83
+ for s, e, value in reversed(matches):
84
+ text = text[:s] + value + text[e:]
85
+ return text
86
+
87
+
88
+ def closeItalics(text: list[str]) -> list[str]:
89
+ """Make sure italics doesn't span multiple lines."""
90
+ italics = False
91
+ result = []
92
+ for line in text:
93
+ if italics:
94
+ line = f"<i>{line}"
95
+ if line.count("<i>") - line.count("</i>") > 0:
96
+ italics = True
97
+ line = f"{line}</i>"
98
+ result.append(line)
99
+ return result
100
+
101
+
102
+ def jsonEncode(data: dict | list | tuple, n: int = 0, nmax: int = 0) -> str:
103
+ """Encode a dictionary, list or tuple as a json object or array, and
104
+ indent from level n up to a max level nmax if nmax is larger than 0.
105
+ """
106
+ if not isinstance(data, (dict, list, tuple)):
107
+ return "[]"
108
+
109
+ buffer = []
110
+ indent = ""
111
+
112
+ for chunk in json.JSONEncoder().iterencode(data):
113
+ if chunk == "": # pragma: no cover
114
+ # Just a precaution
115
+ continue
116
+
117
+ first = chunk[0]
118
+ if chunk in ("{}", "[]"):
119
+ buffer.append(chunk)
120
+
121
+ elif first in ("{", "["):
122
+ n += 1
123
+ indent = "\n"+" "*n
124
+ if n > nmax and nmax > 0:
125
+ buffer.append(chunk)
126
+ else:
127
+ buffer.append(chunk[0] + indent + chunk[1:])
128
+
129
+ elif first in ("}", "]"):
130
+ n -= 1
131
+ indent = "\n"+" "*n
132
+ if n >= nmax and nmax > 0:
133
+ buffer.append(chunk)
134
+ else:
135
+ buffer.append(indent + chunk)
136
+
137
+ elif first == ",":
138
+ if n > nmax and nmax > 0:
139
+ buffer.append(chunk)
140
+ else:
141
+ buffer.append(chunk[0] + indent + chunk[1:].lstrip())
142
+
143
+ else:
144
+ buffer.append(chunk)
145
+
146
+ return "".join(buffer)
147
+
148
+
149
+ def formatTS(value: int) -> str:
150
+ """Format millisecond integer as HH:MM:SS,uuu timestamp."""
151
+ i, f = value//1000, value%1000
152
+ return f"{i//3600:02d}:{i%3600//60:02d}:{i%60:02d},{f:03d}"
153
+
154
+
155
+ def decodeTS(value: str | None, default: int = 0, fmt: T_Subs = "SRT") -> int:
156
+ """Decode a SRT time stamp to milliseconds."""
157
+ if isinstance(value, str):
158
+ if fmt == "SRT" and len(value) >= 12:
159
+ if value[2] == ":" and value[5] == ":" and value[8] in ".,":
160
+ try:
161
+ return (
162
+ 3600000*int(value[0:2])
163
+ + 60000*int(value[3:5])
164
+ + int(value[6:8] + value[9:12])
165
+ )
166
+ except Exception:
167
+ pass
168
+ elif fmt == "SSA" and len(value) == 10:
169
+ if value[1] == ":" and value[4] == ":" and value[7] in ":.,":
170
+ try:
171
+ return (
172
+ 3600000*int(value[0])
173
+ + 60000*int(value[2:4])
174
+ + 10*int(value[5:7] + value[8:10])
175
+ )
176
+ except Exception:
177
+ pass
178
+ return default
subtle/config.py ADDED
@@ -0,0 +1,291 @@
1
+ """
2
+ Subtle – Main Config
3
+ ====================
4
+
5
+ This file is a part of Subtle
6
+ Copyright (C) Veronica Berglyd Olsen
7
+
8
+ This program is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation, either version 3 of the License, or
11
+ (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful, but
14
+ WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import logging
25
+ import shutil
26
+ import sys
27
+
28
+ from copy import deepcopy
29
+ from pathlib import Path
30
+ from typing import Literal
31
+
32
+ from subtle.common import jsonEncode
33
+
34
+ from PyQt6.QtCore import (
35
+ PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QSize,
36
+ QStandardPaths, QSysInfo
37
+ )
38
+ from PyQt6.QtGui import QFont
39
+ from PyQt6.QtWidgets import QApplication
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+
44
+ DEFAULTS: dict = {
45
+ "Sizes": {
46
+ "mainWindow": [900, 600],
47
+ "mainSplit": [300, 300, 300],
48
+ "contentSplit": [300, 300],
49
+ "viewSplit": [200, 200, 200],
50
+ "fileTreeColumns": [],
51
+ "mediaViewColumns": [],
52
+ "subsViewColumns": [],
53
+ },
54
+ "Settings": {
55
+ "tessData": "",
56
+ },
57
+ "Fonts": {
58
+ "guiFont": "",
59
+ "fixedFont": "",
60
+ "subsFont": "",
61
+ }
62
+ }
63
+
64
+ T_Fonts = Literal["gui"] | Literal["fixed"] | Literal["subs"]
65
+
66
+
67
+ class Config:
68
+
69
+ def __init__(self) -> None:
70
+
71
+ self._data: dict[str, dict] = deepcopy(DEFAULTS)
72
+
73
+ self.appName = "Subtle"
74
+ self.appHandle = "subtle"
75
+
76
+ # Set Paths
77
+ confRoot = Path(QStandardPaths.writableLocation(
78
+ QStandardPaths.StandardLocation.ConfigLocation)
79
+ )
80
+ cacheRoot = Path(QStandardPaths.writableLocation(
81
+ QStandardPaths.StandardLocation.CacheLocation)
82
+ )
83
+ self._confPath = confRoot.absolute() / self.appHandle # The user config location
84
+ self._cachePath = cacheRoot.absolute() / self.appHandle # The user cache location
85
+ self._homePath = Path.home().absolute() # The user's home directory
86
+
87
+ self._appPath = Path(__file__).parent.absolute()
88
+ self._appRoot = self._appPath.parent
89
+
90
+ self._confFile = self._confPath / "subtle.json"
91
+
92
+ # Fonts
93
+ self.guiFont = QFont()
94
+ self.fixedFont = QFont()
95
+ self.subsFont = QFont()
96
+
97
+ # Check Qt6 Versions
98
+ self.verQtString = QT_VERSION_STR
99
+ self.verQtValue = QT_VERSION
100
+ self.verPyQtString = PYQT_VERSION_STR
101
+ self.verPyQtValue = PYQT_VERSION
102
+
103
+ # Check Python Version
104
+ self.verPyString = sys.version.split()[0]
105
+
106
+ # Check OS Type
107
+ self.osType = sys.platform
108
+ self.osLinux = False
109
+ self.osWindows = False
110
+ self.osDarwin = False
111
+ self.osUnknown = False
112
+ if self.osType.startswith("linux"):
113
+ self.osLinux = True
114
+ elif self.osType.startswith("darwin"):
115
+ self.osDarwin = True
116
+ elif self.osType.startswith(("win32", "cygwin")):
117
+ self.osWindows = True
118
+ else:
119
+ self.osUnknown = True
120
+
121
+ # Other System Info
122
+ self.hostName = QSysInfo.machineHostName()
123
+ self.kernelVer = QSysInfo.kernelVersion()
124
+
125
+ return
126
+
127
+ ##
128
+ # Properties
129
+ ##
130
+
131
+ @property
132
+ def confPath(self) -> Path:
133
+ """Return the location for config files."""
134
+ self._confPath.mkdir(exist_ok=True)
135
+ return self._confPath
136
+
137
+ @property
138
+ def dumpPath(self) -> Path:
139
+ """Return a location for dumping files during a session."""
140
+ path = self._cachePath / "dump"
141
+ path.mkdir(exist_ok=True)
142
+ return path
143
+
144
+ ##
145
+ # Getters
146
+ ##
147
+
148
+ def getSize(self, key: str) -> QSize:
149
+ """Get a size from config."""
150
+ try:
151
+ size = QSize(*self._data["Sizes"][key])
152
+ except Exception:
153
+ size = QSize()
154
+ return size
155
+
156
+ def getSizes(self, key: str) -> list[int]:
157
+ """Get a list of sizes from config."""
158
+ try:
159
+ size = list(self._data["Sizes"][key])
160
+ except Exception:
161
+ size = []
162
+ return size
163
+
164
+ def getSetting(self, key: str) -> str:
165
+ """Get a generic string setting."""
166
+ return str(self._data["Settings"].get(key, ""))
167
+
168
+ def assetPath(self, resource: str, kind: str | None = None) -> Path:
169
+ """Return the path to an asset."""
170
+ path = self._appPath / "assets"
171
+ if kind:
172
+ path /= kind
173
+ return path / resource
174
+
175
+
176
+ ##
177
+ # Setters
178
+ ##
179
+
180
+ def setSize(self, key: str, value: QSize) -> None:
181
+ """Set a size in config."""
182
+ if isinstance(value, QSize):
183
+ self._data["Sizes"][key] = [value.width(), value.height()]
184
+ return
185
+
186
+ def setSizes(self, key: str, value: list[int]) -> None:
187
+ """Set a size in config."""
188
+ if isinstance(value, list):
189
+ try:
190
+ self._data["Sizes"][key] = [int(x) for x in value]
191
+ except Exception as e:
192
+ logger.error("Problem when saving sizes list", exc_info=e)
193
+ return
194
+
195
+ def setFontSpec(self, target: T_Fonts, font: QFont | str) -> None:
196
+ """Set the font """
197
+ if isinstance(font, str):
198
+ temp = QFont()
199
+ temp.fromString(font)
200
+ font = temp
201
+
202
+ if target == "gui":
203
+ self.guiFont = font
204
+ self._data["Fonts"]["guiFont"] = font.toString()
205
+ QApplication.setFont(self.guiFont)
206
+ elif target == "fixed":
207
+ self.fixedFont = font
208
+ self._data["Fonts"]["fixedFont"] = font.toString()
209
+ elif target == "subs":
210
+ self.subsFont = font
211
+ self._data["Fonts"]["subsFont"] = font.toString()
212
+
213
+ return
214
+
215
+ ##
216
+ # Methods
217
+ ##
218
+
219
+ def initialise(self) -> None:
220
+ """Initialise the config."""
221
+ self._confPath.mkdir(exist_ok=True)
222
+ self._cachePath.mkdir(exist_ok=True)
223
+ return
224
+
225
+ def cleanup(self) -> None:
226
+ """Called before exit to clean up cache."""
227
+ path = self._cachePath / "dump"
228
+ if path.exists():
229
+ logger.debug("Clearing session cache")
230
+ shutil.rmtree(path)
231
+ return
232
+
233
+ def localisation(self, app: QApplication) -> None:
234
+ return
235
+
236
+ def fonts(self, app: QApplication) -> None:
237
+ """Set up fonts."""
238
+ if font := self._data["Fonts"].get("guiFont"):
239
+ self.setFontSpec("gui", font)
240
+ else:
241
+ self.setFontSpec("gui", app.font())
242
+
243
+ if font := self._data["Fonts"].get("fixedFont"):
244
+ self.setFontSpec("fixed", font)
245
+ else:
246
+ self.setFontSpec("fixed", QFont("monospace", app.font().pointSize()))
247
+
248
+ if font := self._data["Fonts"].get("subsFont"):
249
+ self.setFontSpec("subs", font)
250
+ else:
251
+ temp = app.font()
252
+ temp.setPointSizeF(3.0*temp.pointSizeF())
253
+ self.setFontSpec("subs", temp)
254
+ return
255
+
256
+ def load(self) -> None:
257
+ """Load the app config."""
258
+ if self._confFile.is_file():
259
+ try:
260
+ logger.debug("Loading config")
261
+ with open(self._confFile, mode="r", encoding="utf-8") as fo:
262
+ data = json.load(fo)
263
+ self._storeConfigGroup(data, "Sizes")
264
+ self._storeConfigGroup(data, "Settings")
265
+ self._storeConfigGroup(data, "Fonts")
266
+ except Exception as e:
267
+ logger.error("Could not load config", exc_info=e)
268
+ return
269
+
270
+ def save(self) -> None:
271
+ """Save the app config."""
272
+ try:
273
+ logger.debug("Saving config")
274
+ with open(self._confFile, mode="w+", encoding="utf-8") as fo:
275
+ fo.write(jsonEncode(self._data, nmax=2))
276
+ except Exception:
277
+ logger.error("Could not save config")
278
+ return
279
+
280
+ ##
281
+ # Internal Functions
282
+ ##
283
+
284
+ def _storeConfigGroup(self, data: dict, group: str) -> None:
285
+ """Process a group from config and save the data."""
286
+ if isinstance(data, dict) and group in self._data:
287
+ loaded = data.get(group, {})
288
+ default = DEFAULTS.get(group, {})
289
+ values = {k: v for k, v in loaded.items() if k in default}
290
+ self._data[group].update(values)
291
+ return
subtle/constants.py ADDED
@@ -0,0 +1,49 @@
1
+ """
2
+ Subtle – Constants
3
+ ==================
4
+
5
+ This file is a part of Subtle
6
+ Copyright (C) Veronica Berglyd Olsen
7
+
8
+ This program is free software: you can redistribute it and/or modify
9
+ it under the terms of the GNU General Public License as published by
10
+ the Free Software Foundation, either version 3 of the License, or
11
+ (at your option) any later version.
12
+
13
+ This program is distributed in the hope that it will be useful, but
14
+ WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
+ General Public License for more details.
17
+
18
+ You should have received a copy of the GNU General Public License
19
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from enum import Enum
24
+ from typing import Final
25
+
26
+ from PyQt6.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
27
+
28
+
29
+ def trConst(text: str) -> str:
30
+ """Wrapper function for locally translating constants."""
31
+ return QCoreApplication.translate("Constant", text)
32
+
33
+
34
+ class MediaType(Enum):
35
+
36
+ VIDEO = 0
37
+ AUDIO = 1
38
+ SUBS = 2
39
+ OTHER = 4
40
+
41
+
42
+ class GuiLabels:
43
+
44
+ MEDIA_TYPES: Final[dict[MediaType, str]] = {
45
+ MediaType.VIDEO: QT_TRANSLATE_NOOP("Constant", "Video"),
46
+ MediaType.AUDIO: QT_TRANSLATE_NOOP("Constant", "Audio"),
47
+ MediaType.SUBS: QT_TRANSLATE_NOOP("Constant", "Subtitles"),
48
+ MediaType.OTHER: QT_TRANSLATE_NOOP("Constant", "Other"),
49
+ }