Merge 29eb135752d3a071c294ba99474e2cb8c4791d70 into 0c6f4f496162211a3febd63cb400a33325a5c58a

This commit is contained in:
283375 2024-06-28 22:25:10 +08:00 committed by GitHub
commit 4a9ee7b512
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 298 additions and 354 deletions

View File

@ -4,11 +4,10 @@ repos:
hooks: hooks:
- id: end-of-file-fixer - id: end-of-file-fixer
- id: trailing-whitespace - id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 23.1.0 - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.9
hooks: hooks:
- id: black - id: ruff
- repo: https://github.com/PyCQA/isort args: ["--fix"]
rev: 5.12.0 - id: ruff-format
hooks:
- id: isort

0
core/__init__.py Normal file
View File

10
core/color.py Normal file
View File

@ -0,0 +1,10 @@
from PySide6.QtGui import QColor
def mixColor(source: QColor, mix: QColor, ratio: float = 0.5):
r = round((mix.red() - source.red()) * ratio + source.red())
g = round((mix.green() - source.green()) * ratio + source.green())
b = round((mix.blue() - source.blue()) * ratio + source.blue())
a = round((mix.alpha() - source.alpha()) * ratio + source.alpha())
return QColor(r, g, b, a)

View File

@ -0,0 +1,5 @@
from .base import Settings, settings
from .keys import SettingsKeys
from .values import SettingsValues
__all__ = ["settings", "Settings", "SettingsKeys", "SettingsValues"]

44
core/settings/base.py Normal file
View File

@ -0,0 +1,44 @@
import sys
from enum import Enum
from typing import Any
from PySide6.QtCore import QFileInfo, QSettings, Signal
from core.singleton import QSingleton
__all__ = ["Settings"]
TSettingsKey = str | Enum
class Settings(QSettings, metaclass=QSingleton):
updated = Signal(str)
def __init__(self, parent=None):
super().__init__(
QFileInfo(sys.argv[0]).dir().absoluteFilePath("arcaea_offline.ini"),
QSettings.Format.IniFormat,
parent,
)
def __settingsKey(self, key: TSettingsKey) -> str:
if isinstance(key, Enum):
return self.__settingsKey(key.value)
if isinstance(key, str):
return key
raise TypeError(f"{key!r} is not a valid key")
def setValue(self, key: TSettingsKey, value: Any) -> None:
_key = self.__settingsKey(key)
super().setValue(_key, value)
self.updated.emit(_key)
def stringValue(self, key: TSettingsKey) -> str | None:
_key = self.__settingsKey(key)
return self.value(_key, None, type=str)
settings = Settings()

26
core/settings/keys.py Normal file
View File

@ -0,0 +1,26 @@
from dataclasses import dataclass
from enum import StrEnum
class _General(StrEnum):
Language = "Language"
DatabaseUrl = "DatabaseUrl"
class _Ocr(StrEnum):
KnnModelFile = "Ocr/KnnModelFile"
B30KnnModelFile = "Ocr/B30KnnModelFile"
PhashDatabaseFile = "Ocr/PHashDatabaseFile"
DateSource = "Ocr/DateSource"
class _Andreal(StrEnum):
Folder = "Andreal/AndrealFolder"
Executable = "Andreal/AndrealExecutable"
@dataclass(frozen=True)
class SettingsKeys:
General = _General
Ocr = _Ocr
Andreal = _Andreal

17
core/settings/values.py Normal file
View File

@ -0,0 +1,17 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class _Ocr_ScoreDateSource:
FileCreated: str = "FileCreated"
FileLastModified: str = "FileLastModified"
@dataclass(frozen=True)
class _Ocr:
DateSource = _Ocr_ScoreDateSource()
@dataclass(frozen=True)
class SettingsValues:
Ocr = _Ocr()

View File

@ -14,5 +14,5 @@ class Singleton(type, Generic[T]):
return cls._instance return cls._instance
class QObjectSingleton(type(QObject), Singleton): class QSingleton(type(QObject), Singleton):
pass pass

View File

@ -9,25 +9,21 @@ from PySide6.QtCore import QCoreApplication, QLocale
from PySide6.QtGui import QFontDatabase, QIcon from PySide6.QtGui import QFontDatabase, QIcon
from PySide6.QtWidgets import QApplication, QDialog, QMessageBox from PySide6.QtWidgets import QApplication, QDialog, QMessageBox
import ui.resources.resources_rc import ui.resources.resources_rc # noqa: F401
from core.settings import SettingsKeys, settings
from ui.extends.shared.language import changeAppLanguage from ui.extends.shared.language import changeAppLanguage
from ui.extends.shared.settings import Settings
from ui.implements.mainwindow import MainWindow from ui.implements.mainwindow import MainWindow
from ui.startup.databaseChecker import DatabaseChecker, DatabaseCheckerResult from ui.startup.databaseChecker import DatabaseChecker, DatabaseCheckerResult
rootLogger = logging.getLogger("root") rootLogger = logging.getLogger("root")
rootLogger.setLevel(logging.DEBUG) rootLogger.setLevel(logging.DEBUG)
rootLoggerFormatter = logging.Formatter(
"[{levelname}]{asctime}|{name}: {msg}", "%m-%d %H:%M:%S", "{"
)
def handle_exception(exc_type, exc_value, exc_traceback): def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt): sys.__excepthook__(exc_type, exc_value, exc_traceback)
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
if issubclass(exc_type, KeyboardInterrupt):
return
rootLogger.critical( rootLogger.critical(
"Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback) "Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)
) )
@ -46,6 +42,11 @@ if __name__ == "__main__":
ymd = now.strftime("%Y%m%d") ymd = now.strftime("%Y%m%d")
hms = now.strftime("%H%M%S") hms = now.strftime("%H%M%S")
rootLoggerFormatter = logging.Formatter(
"[%(asctime)s/%(levelname)s] %(name)s (%(tag)s): %(message)s",
"%m-%d %H:%M:%S",
defaults={"tag": "/"},
)
rootLoggerFileHandler = logging.FileHandler( rootLoggerFileHandler = logging.FileHandler(
str(logFolder / f"arcaea-offline-pyside-ui-{ymd}-{hms}_debug.log"), str(logFolder / f"arcaea-offline-pyside-ui-{ymd}-{hms}_debug.log"),
encoding="utf-8", encoding="utf-8",
@ -59,16 +60,19 @@ if __name__ == "__main__":
rootLogger.addHandler(rootLoggerStdOutHandler) rootLogger.addHandler(rootLoggerStdOutHandler)
app = QApplication(sys.argv) app = QApplication(sys.argv)
locale = ( settingsLanguage = settings.stringValue(SettingsKeys.General.Language)
QLocale(Settings().language()) if Settings().language() else QLocale.system() locale = QLocale(settingsLanguage) if settingsLanguage else QLocale.system()
)
changeAppLanguage(locale) changeAppLanguage(locale)
QFontDatabase.addApplicationFont(":/fonts/GeosansLight.ttf") QFontDatabase.addApplicationFont(":/fonts/GeosansLight.ttf")
databaseChecker = DatabaseChecker() databaseChecker = DatabaseChecker()
databaseChecker.setWindowIcon(QIcon(":/images/icon.png")) databaseChecker.setWindowIcon(QIcon(":/images/icon.png"))
databaseCheckResult = databaseChecker.confirmDb() if Settings().databaseUrl() else 0 databaseCheckResult = (
databaseChecker.confirmDb()
if settings.stringValue(SettingsKeys.General.DatabaseUrl)
else 0
)
if not databaseCheckResult & DatabaseCheckerResult.Initted: if not databaseCheckResult & DatabaseCheckerResult.Initted:
result = databaseChecker.exec() result = databaseChecker.exec()

View File

@ -24,19 +24,34 @@ classifiers = [
"Homepage" = "https://github.com/ArcaeaOffline/client-pyside6" "Homepage" = "https://github.com/ArcaeaOffline/client-pyside6"
"Bug Tracker" = "https://github.com/ArcaeaOffline/client-pyside6/issues" "Bug Tracker" = "https://github.com/ArcaeaOffline/client-pyside6/issues"
[tool.black] [tool.ruff]
force-exclude = ''' exclude = ["*_ui.py", "*_rc.py"]
(
ui/designer
| .*_ui.py
| .*_rc.py
)
'''
[tool.isort] [tool.ruff.lint]
profile = "black" # Full list: https://docs.astral.sh/ruff/rules
extend_skip = ["ui/designer"] select = [
extend_skip_glob = ["*_ui.py", "*_rc.py"] "E", # pycodestyle (Error)
"W", # pycodestyle (Warning)
"F", # pyflakes
"I", # isort
"PL", # pylint
"N", # pep8-naming
"A", # flake8-builtins
"DTZ", # flake8-datetimez
"LOG", # flake8-logging
"Q", # flake8-quotes
"G", # flake8-logging-format
"PIE", # flake8-pie
"PT", # flake8-pytest-style
]
ignore = [
"E501", # line-too-long
"N802", # invalid-function-name
"N803", # invalid-argument-name
"N806", # non-lowercase-variable-in-function
"N815", # mixed-case-variable-in-class-scope
"N816", # mixed-case-variable-in-global-scope
]
[tool.pyright] [tool.pyright]
ignore = ["**/__debug*.*"] ignore = ["**/__debug*.*"]

View File

@ -5,7 +5,6 @@ from typing import Any, Callable, Optional, overload
from arcaea_offline.calculate import calculate_score_range from arcaea_offline.calculate import calculate_score_range
from arcaea_offline.database import Database from arcaea_offline.database import Database
from arcaea_offline.models import Chart, Score from arcaea_offline.models import Chart, Score
from arcaea_offline_ocr.b30.shared import B30OcrResultItem
from arcaea_offline_ocr.device.common import DeviceOcrResult from arcaea_offline_ocr.device.common import DeviceOcrResult
from PIL import Image from PIL import Image
from PIL.ImageQt import ImageQt from PIL.ImageQt import ImageQt
@ -140,7 +139,11 @@ class OcrQueueModel(QAbstractListModel):
return True return True
else: else:
logger.warning( logger.warning(
f"{repr(self)} setData at row {index.row()} with role {role} and value {value} rejected." "%r setData at row %d with role %d and value %s rejected!",
self,
index.row(),
role,
value,
) )
return False return False
@ -150,7 +153,7 @@ class OcrQueueModel(QAbstractListModel):
@iccOption.setter @iccOption.setter
def iccOption(self, opt: IccOption): def iccOption(self, opt: IccOption):
logger.debug(f"ICC option changed to {opt}") logger.debug("ICC option changed to %s", opt)
self.__iccOption = opt self.__iccOption = opt
@overload @overload
@ -159,8 +162,7 @@ class OcrQueueModel(QAbstractListModel):
image: str, image: str,
runnable: OcrRunnable = None, runnable: OcrRunnable = None,
process_func: Callable[[Optional[str], QImage, Any], Score] = None, process_func: Callable[[Optional[str], QImage, Any], Score] = None,
): ): ...
...
@overload @overload
def addItem( def addItem(
@ -168,8 +170,7 @@ class OcrQueueModel(QAbstractListModel):
image: QImage, image: QImage,
runnable: OcrRunnable = None, runnable: OcrRunnable = None,
process_func: Callable[[Optional[str], QImage, Any], Score] = None, process_func: Callable[[Optional[str], QImage, Any], Score] = None,
): ): ...
...
def addItem( def addItem(
self, self,
@ -179,7 +180,7 @@ class OcrQueueModel(QAbstractListModel):
): ):
if isinstance(image, str): if isinstance(image, str):
if image in self.imagePaths or not QFileInfo(image).exists(): if image in self.imagePaths or not QFileInfo(image).exists():
logger.warning(f"Attempting to add an invalid file {image}") logger.warning("Attempting to add an invalid file %s", image)
return return
imagePath = image imagePath = image
if self.iccOption == IccOption.TryFix: if self.iccOption == IccOption.TryFix:
@ -223,7 +224,7 @@ class OcrQueueModel(QAbstractListModel):
index = self.index(row, 0) index = self.index(row, 0)
imagePath: str = index.data(self.ImagePathRole) imagePath: str = index.data(self.ImagePathRole)
qImage: QImage = index.data(self.ImageQImageRole) qImage: QImage = index.data(self.ImageQImageRole)
logger.debug(f"update request: {result}@row{row}") logger.debug("update request: %r@row%d", result, row)
processOcrResultFunc = index.data(self.ProcessOcrResultFuncRole) processOcrResultFunc = index.data(self.ProcessOcrResultFuncRole)
chart, scoreInsert = processOcrResultFunc(imagePath, qImage, result) chart, scoreInsert = processOcrResultFunc(imagePath, qImage, result)
@ -294,8 +295,8 @@ class OcrQueueModel(QAbstractListModel):
self.__items.pop(row) self.__items.pop(row)
self.endRemoveRows() self.endRemoveRows()
return return
except Exception as e: except Exception:
logger.exception(f"Error accepting {repr(item)}") logger.exception("Error accepting %r", item)
return return
def acceptItems(self, __rows: list[int], ignoreValidate: bool = False): def acceptItems(self, __rows: list[int], ignoreValidate: bool = False):
@ -344,17 +345,11 @@ class OcrQueueTableProxyModel(QAbstractTableModel):
def retranslateHeaders(self): def retranslateHeaders(self):
self.__horizontalHeaders = [ self.__horizontalHeaders = [
# fmt: off QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.select"),
QCoreApplication.translate( QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.imagePreview"),
"OcrTableModel", "horizontalHeader.title.select"
),
QCoreApplication.translate(
"OcrTableModel", "horizontalHeader.title.imagePreview"
),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.chart"), QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.chart"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.score"), QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.score"),
# fmt: on ] # fmt: skip
]
def sourceModel(self) -> OcrQueueModel: def sourceModel(self) -> OcrQueueModel:
return self.__sourceModel return self.__sourceModel

View File

@ -1,16 +0,0 @@
from PySide6.QtGui import QColor
def mix_color(source_color: QColor, mix_color: QColor, mix_ratio: float = 0.5):
r = round((mix_color.red() - source_color.red()) * mix_ratio + source_color.red())
g = round(
(mix_color.green() - source_color.green()) * mix_ratio + source_color.green()
)
b = round(
(mix_color.blue() - source_color.blue()) * mix_ratio + source_color.blue()
)
a = round(
(mix_color.alpha() - source_color.alpha()) * mix_ratio + source_color.alpha()
)
return QColor(r, g, b, a)

View File

@ -7,7 +7,7 @@ from typing import Literal, Optional, overload
from arcaea_offline.models import Chart, Difficulty, Song from arcaea_offline.models import Chart, Difficulty, Song
from PySide6.QtCore import QFile from PySide6.QtCore import QFile
from .singleton import Singleton from core.singleton import Singleton
TPartnerModifier = dict[str, Literal[0, 1, 2]] TPartnerModifier = dict[str, Literal[0, 1, 2]]
@ -48,14 +48,12 @@ class Data(metaclass=Singleton):
return self.dataPath / "Arcaea" return self.dataPath / "Arcaea"
@overload @overload
def getJacketPath(self, chart: Chart, /) -> Path | None: def getJacketPath(self, chart: Chart, /) -> Path | None: ...
...
@overload @overload
def getJacketPath( def getJacketPath(
self, song: Song, difficulty: Optional[Difficulty] = None, / self, song: Song, difficulty: Optional[Difficulty] = None, /
) -> Path | None: ) -> Path | None: ...
...
def getJacketPath(self, *args) -> Path | None: def getJacketPath(self, *args) -> Path | None:
if isinstance(args[0], Chart): if isinstance(args[0], Chart):

View File

@ -16,13 +16,11 @@ class DbB30TableModel(DbTableModel):
def retranslateHeaders(self): def retranslateHeaders(self):
self._horizontalHeaders = [ self._horizontalHeaders = [
# fmt: off
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.id"), QCoreApplication.translate("DbB30TableModel", "horizontalHeader.id"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.chart"), QCoreApplication.translate("DbB30TableModel", "horizontalHeader.chart"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.score"), QCoreApplication.translate("DbB30TableModel", "horizontalHeader.score"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.potential"), QCoreApplication.translate("DbB30TableModel", "horizontalHeader.potential"),
# fmt: on ] # fmt: skip
]
def syncDb(self): def syncDb(self):
self.beginResetModel() self.beginResetModel()

View File

@ -24,13 +24,11 @@ class DbScoreTableModel(DbTableModel):
def retranslateHeaders(self): def retranslateHeaders(self):
self._horizontalHeaders = [ self._horizontalHeaders = [
# fmt: off
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.id"), QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.id"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.chart"), QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.chart"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.score"), QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.score"),
QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.potential"), QCoreApplication.translate("DbScoreTableModel", "horizontalHeader.potential"),
# fmt: on ] # fmt: skip
]
def syncDb(self): def syncDb(self):
self.beginResetModel() self.beginResetModel()
@ -154,7 +152,7 @@ class DbScoreTableModel(DbTableModel):
self.syncDb() self.syncDb()
return True return True
except Exception: except Exception:
logger.exception(f"Table[Score]: Cannot remove row {row}") logger.exception("Table[Score]: Cannot remove row %s", row)
return False return False
def removeRow(self, row: int, parent=...): def removeRow(self, row: int, parent=...):

View File

@ -1,122 +0,0 @@
import sys
from PySide6.QtCore import QFileInfo, QSettings, Signal
from .singleton import QObjectSingleton
__all__ = [
"LANGUAGE",
"DATABASE_URL",
"KNN_MODEL_FILE",
"B30_KNN_MODEL_FILE",
"PHASH_DATABASE_FILE",
"SCORE_DATE_SOURCE",
"ANDREAL_FOLDER",
"ANDREAL_EXECUTABLE",
"Settings",
]
# a key without slashes will appear in the "General" section
# see https://doc.qt.io/qt-6/qsettings.html#Format-enum for details
LANGUAGE = "Language"
DATABASE_URL = "DatabaseUrl"
KNN_MODEL_FILE = "Ocr/KnnModelFile"
B30_KNN_MODEL_FILE = "Ocr/B30KnnModelFile"
PHASH_DATABASE_FILE = "Ocr/PHashDatabaseFile"
SCORE_DATE_SOURCE = "Ocr/DateSource"
ANDREAL_FOLDER = "Andreal/AndrealFolder"
ANDREAL_EXECUTABLE = "Andreal/AndrealExecutable"
class Settings(QSettings, metaclass=QObjectSingleton):
updated = Signal(str)
def __init__(self, parent=None):
super().__init__(
QFileInfo(sys.argv[0]).dir().absoluteFilePath("arcaea_offline.ini"),
QSettings.Format.IniFormat,
parent,
)
def setValue(self, key: str, value) -> None:
super().setValue(key, value)
self.updated.emit(key)
def _strItem(self, key: str) -> str | None:
return self.value(key, None, str)
def _setStrItem(self, key: str, value: str):
self.setValue(key, value)
self.sync()
def _resetStrItem(self, key: str):
self.setValue(key, None)
self.sync()
def language(self):
return self._strItem(LANGUAGE)
def setLanguage(self, value: str):
self._setStrItem(LANGUAGE, value)
def databaseUrl(self):
return self._strItem(DATABASE_URL)
def setDatabaseUrl(self, value: str):
self._setStrItem(DATABASE_URL, value)
def knnModelFile(self):
return self._strItem(KNN_MODEL_FILE)
def setKnnModelFile(self, value: str):
self._setStrItem(KNN_MODEL_FILE, value)
def resetKnnModelFile(self):
self._resetStrItem(KNN_MODEL_FILE)
def b30KnnModelFile(self):
return self._strItem(B30_KNN_MODEL_FILE)
def setB30KnnModelFile(self, value: str):
self._setStrItem(B30_KNN_MODEL_FILE, value)
def resetB30KnnModelFile(self):
self._resetStrItem(B30_KNN_MODEL_FILE)
def phashDatabaseFile(self):
return self._strItem(PHASH_DATABASE_FILE)
def setPHashDatabaseFile(self, value: str):
self._setStrItem(PHASH_DATABASE_FILE, value)
def resetPHashDatabaseFile(self):
self._resetStrItem(PHASH_DATABASE_FILE)
def scoreDateSource(self):
return self._strItem(SCORE_DATE_SOURCE)
def setScoreDateSource(self, value: str):
self._setStrItem(SCORE_DATE_SOURCE, value)
def resetScoreDateSource(self):
self._resetStrItem(SCORE_DATE_SOURCE)
def andrealFolder(self):
return self._strItem(ANDREAL_FOLDER)
def setAndrealFolder(self, value: str):
self._setStrItem(ANDREAL_FOLDER, value)
def resetAndrealFolder(self):
self._resetStrItem(ANDREAL_FOLDER)
def andrealExecutable(self):
return self._strItem(ANDREAL_EXECUTABLE)
def setAndrealExecutable(self, value: str):
self._setStrItem(ANDREAL_EXECUTABLE, value)
def resetAndrealExecutable(self):
self._resetStrItem(ANDREAL_EXECUTABLE)

View File

@ -6,10 +6,10 @@ from arcaea_offline_ocr.b30.chieri.v4.ocr import ChieriBotV4Ocr
from arcaea_offline_ocr.b30.shared import B30OcrResultItem from arcaea_offline_ocr.b30.shared import B30OcrResultItem
from PySide6.QtGui import QImage from PySide6.QtGui import QImage
logger = logging.getLogger(__name__)
from ui.extends.components.ocrQueue import OcrRunnable from ui.extends.components.ocrQueue import OcrRunnable
logger = logging.getLogger(__name__)
class ChieriV4OcrRunnable(OcrRunnable): class ChieriV4OcrRunnable(OcrRunnable):
def __init__(self, ocr: ChieriBotV4Ocr, component): def __init__(self, ocr: ChieriBotV4Ocr, component):

View File

@ -19,9 +19,9 @@ from arcaea_offline_ocr.phash_db import ImagePhashDatabase
from arcaea_offline_ocr.utils import imread_unicode from arcaea_offline_ocr.utils import imread_unicode
from PySide6.QtCore import QDateTime, QFileInfo from PySide6.QtCore import QDateTime, QFileInfo
from core.settings import SettingsKeys, SettingsValues, settings
from ui.extends.components.ocrQueue import OcrRunnable from ui.extends.components.ocrQueue import OcrRunnable
from ui.extends.shared.data import Data from ui.extends.shared.data import Data
from ui.extends.shared.settings import Settings
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -70,8 +70,8 @@ def getImageDate(imagePath: str) -> QDateTime:
datetime = QDateTime.fromString(datetimeStr, "yyyy:MM:dd hh:mm:ss") datetime = QDateTime.fromString(datetimeStr, "yyyy:MM:dd hh:mm:ss")
if not isinstance(datetime, QDateTime): if not isinstance(datetime, QDateTime):
dateSource = Settings().scoreDateSource() dateSource = settings.stringValue(SettingsKeys.Ocr.DateSource)
if dateSource == "lastModified": if dateSource == SettingsValues.Ocr.DateSource.FileLastModified:
datetime = QFileInfo(imagePath).lastModified() datetime = QFileInfo(imagePath).lastModified()
else: else:
datetime = QFileInfo(imagePath).birthTime() datetime = QFileInfo(imagePath).birthTime()

View File

@ -41,7 +41,7 @@ class AndrealExecuteRunnable(QRunnable):
self.signals.completed.emit(self.jsonPath, imageBytes) self.signals.completed.emit(self.jsonPath, imageBytes)
except Exception as e: except Exception as e:
imageBytes = None imageBytes = None
logger.exception(f"{self.__class__.__name__} error") logger.exception("%s error", self.__class__.__name__)
self.signals.error.emit(self.jsonPath, str(e)) self.signals.error.emit(self.jsonPath, str(e))
finally: finally:
os.unlink(self.jsonPath) os.unlink(self.jsonPath)
@ -84,7 +84,10 @@ class AndrealHelper(QObject):
def request(self, jsonPath: str, arguments: list[str]): def request(self, jsonPath: str, arguments: list[str]):
logger.debug( logger.debug(
f"{self.__class__.__name__} received request {jsonPath=} {arguments=}" "%s received request jsonPath=%r arguments=%r",
self.__class__.__name__,
jsonPath,
arguments,
) )
runnable = AndrealExecuteRunnable(self.andrealExecutable, jsonPath, arguments) runnable = AndrealExecuteRunnable(self.andrealExecutable, jsonPath, arguments)
runnable.signals.error.connect(self.error) runnable.signals.error.connect(self.error)

View File

@ -2,9 +2,9 @@ from PySide6.QtCore import QDir, QFileInfo, Qt, Signal, Slot
from PySide6.QtGui import QDragEnterEvent, QDragLeaveEvent, QDropEvent from PySide6.QtGui import QDragEnterEvent, QDragLeaveEvent, QDropEvent
from PySide6.QtWidgets import QFileDialog, QWidget from PySide6.QtWidgets import QFileDialog, QWidget
from core.settings import settings
from ui.designer.components.fileSelector_ui import Ui_FileSelector from ui.designer.components.fileSelector_ui import Ui_FileSelector
from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import Settings
class FileSelector(Ui_FileSelector, QWidget): class FileSelector(Ui_FileSelector, QWidget):
@ -122,13 +122,13 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles: if self.__selectedFiles:
return return
if value := Settings().value(self.settingsKey): if value := settings.value(self.settingsKey):
self.selectFile(value) self.selectFile(value)
Settings().updated.connect(self.settingsUpdated) settings.updated.connect(self.settingsUpdated)
def disconnectSettings(self): def disconnectSettings(self):
Settings().updated.disconnect(self.settingsUpdated) settings.updated.disconnect(self.settingsUpdated)
self.settingsKey = None self.settingsKey = None
def settingsUpdated(self, key: str): def settingsUpdated(self, key: str):
@ -139,4 +139,4 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles: if self.__selectedFiles:
return return
self.selectFile(Settings().value(self.settingsKey)) self.selectFile(settings.value(self.settingsKey))

View File

@ -1,8 +1,8 @@
from PySide6.QtCore import Signal from PySide6.QtCore import Signal
from PySide6.QtWidgets import QButtonGroup, QDialog from PySide6.QtWidgets import QButtonGroup, QDialog
from core.settings import SettingsKeys, SettingsValues, settings
from ui.designer.components.ocrQueueOptionsDialog_ui import Ui_OcrQueueOptionsDialog from ui.designer.components.ocrQueueOptionsDialog_ui import Ui_OcrQueueOptionsDialog
from ui.extends.shared.settings import Settings
class OcrQueueOptionsDialog(QDialog, Ui_OcrQueueOptionsDialog): class OcrQueueOptionsDialog(QDialog, Ui_OcrQueueOptionsDialog):
@ -29,13 +29,12 @@ class OcrQueueOptionsDialog(QDialog, Ui_OcrQueueOptionsDialog):
self.on_scoreDateSourceButtonGroup_buttonClicked self.on_scoreDateSourceButtonGroup_buttonClicked
) )
self.settings = Settings() settings.updated.connect(self.syncCheckboxesFromSettings)
self.settings.updated.connect(self.syncCheckboxesFromSettings)
self.syncCheckboxesFromSettings() self.syncCheckboxesFromSettings()
def syncCheckboxesFromSettings(self): def syncCheckboxesFromSettings(self):
scoreDateSource = self.settings.scoreDateSource() scoreDateSource = settings.stringValue(SettingsKeys.Ocr.DateSource)
if scoreDateSource == "lastModified": if scoreDateSource == SettingsValues.Ocr.DateSource.FileLastModified:
self.dateUseModifyDateRadioButton.setChecked(True) self.dateUseModifyDateRadioButton.setChecked(True)
else: else:
self.dateUseCreationDateRadioButton.setChecked(True) self.dateUseCreationDateRadioButton.setChecked(True)
@ -43,6 +42,8 @@ class OcrQueueOptionsDialog(QDialog, Ui_OcrQueueOptionsDialog):
def on_scoreDateSourceButtonGroup_buttonClicked(self, button): def on_scoreDateSourceButtonGroup_buttonClicked(self, button):
buttonId = self.scoreDateSourceButtonGroup.id(button) buttonId = self.scoreDateSourceButtonGroup.id(button)
if buttonId == 1: if buttonId == 1:
self.settings.setScoreDateSource("lastModified") value = SettingsValues.Ocr.DateSource.FileLastModified
else: else:
self.settings.setScoreDateSource("birthTime") value = SettingsValues.Ocr.DateSource.FileCreated
settings.setValue(SettingsKeys.Ocr.DateSource, value)

View File

@ -2,7 +2,7 @@ from PySide6.QtCore import Slot
from PySide6.QtGui import QColor from PySide6.QtGui import QColor
from PySide6.QtWidgets import QGraphicsColorizeEffect, QRadioButton from PySide6.QtWidgets import QGraphicsColorizeEffect, QRadioButton
from ui.extends.shared.color import mix_color from core.color import mixColor
STYLESHEET = """ STYLESHEET = """
QRadioButton {{ QRadioButton {{
@ -40,7 +40,7 @@ class RatingClassRadioButton(QRadioButton):
def setColors(self, dark_color: QColor, text_color: QColor): def setColors(self, dark_color: QColor, text_color: QColor):
self._dark_color = dark_color self._dark_color = dark_color
self._text_color = text_color self._text_color = text_color
self._mid_color = mix_color(dark_color, text_color, 0.616) self._mid_color = mixColor(dark_color, text_color, 0.616)
self.updateEffects() self.updateEffects()
def isColorsSet(self) -> bool: def isColorsSet(self) -> bool:

View File

@ -126,7 +126,7 @@ class RatingClassSelector(QWidget):
elif ratingClass in range(len(self.buttons)): elif ratingClass in range(len(self.buttons)):
button = self.buttons[ratingClass] button = self.buttons[ratingClass]
else: else:
logger.debug(f"Cannot select {ratingClass=}, condition check failed") logger.debug("Cannot select ratingClass=%s, condition check failed", ratingClass)
return return
if not button.isEnabled(): if not button.isEnabled():

View File

@ -61,30 +61,22 @@ class ScoreEditor(Ui_ScoreEditor, QWidget):
VALIDATION_ITEMS_TEXT = [ VALIDATION_ITEMS_TEXT = [
[ [
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.chartIncomplete.title"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.chartIncomplete.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.chartIncomplete.text"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.chartIncomplete.text"),
# fmt: on
], ],
[ [
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreMismatch.title"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreMismatch.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreMismatch.text"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreMismatch.text"),
# fmt: on
], ],
[ [
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.emptyScore.title"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.emptyScore.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.emptyScore.text"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.emptyScore.text"),
# fmt: on
], ],
[ [
# fmt: off
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncompleteForValidate.title"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncompleteForValidate.title"),
lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncompleteForValidate.text"), lambda: QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncompleteForValidate.text"),
# fmt: on,
], ],
] ] # fmt: skip
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
@ -208,20 +200,16 @@ class ScoreEditor(Ui_ScoreEditor, QWidget):
if validate & ScoreValidateResult.ChartNotSet: if validate & ScoreValidateResult.ChartNotSet:
self.__triggerMessageBox( self.__triggerMessageBox(
"critical", "critical",
# fmt: off
QCoreApplication.translate("ScoreEditor", "confirmDialog.chartNotSet.title"), QCoreApplication.translate("ScoreEditor", "confirmDialog.chartNotSet.title"),
QCoreApplication.translate("ScoreEditor", "confirmDialog.chartNotSet.text"), QCoreApplication.translate("ScoreEditor", "confirmDialog.chartNotSet.text"),
# fmt: on ) # fmt: skip
)
return False return False
if validate & ScoreValidateResult.ScoreIncomplete: if validate & ScoreValidateResult.ScoreIncomplete:
self.__triggerMessageBox( self.__triggerMessageBox(
"critical", "critical",
# fmt: off
QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncomplete.title"), QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncomplete.title"),
QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncomplete.text"), QCoreApplication.translate("ScoreEditor", "confirmDialog.scoreIncomplete.text"),
# fmt: on ) # fmt: skip
)
return False return False
# since validate may have multiple results # since validate may have multiple results
@ -347,10 +335,8 @@ class ScoreEditor(Ui_ScoreEditor, QWidget):
) )
if validate & ScoreValidateResult.ScoreIncompleteForValidate: if validate & ScoreValidateResult.ScoreIncompleteForValidate:
texts.append( texts.append(
# fmt: off
QCoreApplication.translate("ScoreEditor", "validate.scoreIncompleteForValidate") QCoreApplication.translate("ScoreEditor", "validate.scoreIncompleteForValidate")
# fmt: on ) # fmt: skip
)
if not texts: if not texts:
texts.append( texts.append(

View File

@ -192,7 +192,7 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
self.fillSongIdComboBox() self.fillSongIdComboBox()
return True return True
else: else:
logger.warning(f'Attempting to select an unknown pack "{packId}"') logger.warning("Attempting to select an unknown pack [%s]", packId)
return False return False
def selectSongId(self, songId: str) -> bool: def selectSongId(self, songId: str) -> bool:
@ -202,7 +202,8 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
return True return True
else: else:
logger.warning( logger.warning(
f'Attempting to select an unknown song "{songId}", maybe try selecting a pack first?' "Attempting to select an unknown song [%s], maybe try selecting a pack first?",
songId,
) )
return False return False

View File

@ -1,6 +1,7 @@
from PySide6.QtCore import QCoreApplication from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QLabel, QPushButton from PySide6.QtWidgets import QLabel, QPushButton
from core.settings import SettingsKeys, settings
from ui.implements.components.fileSelector import FileSelector from ui.implements.components.fileSelector import FileSelector
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
@ -14,8 +15,8 @@ class SettingsAndreal(SettingsBaseWidget):
self.andrealFolderValueWidget.setMode( self.andrealFolderValueWidget.setMode(
self.andrealFolderValueWidget.getExistingDirectory self.andrealFolderValueWidget.getExistingDirectory
) )
if self.settings.andrealFolder(): if andrealFolder := settings.stringValue(SettingsKeys.Andreal.Folder):
self.andrealFolderValueWidget.selectFile(self.settings.andrealFolder()) self.andrealFolderValueWidget.selectFile(andrealFolder)
self.andrealFolderValueWidget.filesSelected.connect(self.setAndrealFolder) self.andrealFolderValueWidget.filesSelected.connect(self.setAndrealFolder)
self.andrealFolderResetButton.clicked.connect(self.resetAndrealFolder) self.andrealFolderResetButton.clicked.connect(self.resetAndrealFolder)
self.insertItem( self.insertItem(
@ -25,10 +26,8 @@ class SettingsAndreal(SettingsBaseWidget):
self.andrealFolderResetButton, self.andrealFolderResetButton,
) )
if self.settings.andrealExecutable(): if andrealExecutable := settings.stringValue(SettingsKeys.Andreal.Executable):
self.andrealExecutableValueWidget.selectFile( self.andrealExecutableValueWidget.selectFile(andrealExecutable)
self.settings.andrealExecutable()
)
self.andrealExecutableValueWidget.filesSelected.connect( self.andrealExecutableValueWidget.filesSelected.connect(
self.setAndrealExecutable self.setAndrealExecutable
) )
@ -44,21 +43,21 @@ class SettingsAndreal(SettingsBaseWidget):
selectedFile = self.andrealFolderValueWidget.selectedFiles() selectedFile = self.andrealFolderValueWidget.selectedFiles()
if selectedFile and selectedFile[0]: if selectedFile and selectedFile[0]:
file = selectedFile[0] file = selectedFile[0]
self.settings.setAndrealFolder(file) settings.setValue(SettingsKeys.Andreal.Folder, file)
def resetAndrealFolder(self): def resetAndrealFolder(self):
self.andrealFolderValueWidget.reset() self.andrealFolderValueWidget.reset()
self.settings.resetAndrealFolder() settings.setValue(SettingsKeys.Andreal.Folder, None)
def setAndrealExecutable(self): def setAndrealExecutable(self):
selectedFile = self.andrealExecutableValueWidget.selectedFiles() selectedFile = self.andrealExecutableValueWidget.selectedFiles()
if selectedFile and selectedFile[0]: if selectedFile and selectedFile[0]:
file = selectedFile[0] file = selectedFile[0]
self.settings.setAndrealExecutable(file) settings.setValue(SettingsKeys.Andreal.Executable, file)
def resetAndrealExecutable(self): def resetAndrealExecutable(self):
self.andrealExecutableValueWidget.reset() self.andrealExecutableValueWidget.reset()
self.settings.resetAndrealExecutable() settings.setValue(SettingsKeys.Andreal.Executable, None)
def setupUi(self, *args): def setupUi(self, *args):
self.andrealFolderLabel = QLabel(self) self.andrealFolderLabel = QLabel(self)

View File

@ -1,15 +1,15 @@
from PySide6.QtCore import Qt from PySide6.QtCore import Qt
from PySide6.QtWidgets import QLabel, QPushButton, QWidget from PySide6.QtWidgets import QLabel, QPushButton, QWidget
from core.settings import settings
from ui.designer.settings.settingsBaseWidget_ui import Ui_SettingsBaseWidget from ui.designer.settings.settingsBaseWidget_ui import Ui_SettingsBaseWidget
from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import Settings
class SettingsBaseWidget(Ui_SettingsBaseWidget, QWidget): class SettingsBaseWidget(Ui_SettingsBaseWidget, QWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.settings = Settings() self.settings = settings
self.languageChangeEventFilter = LanguageChangeEventFilter(self) self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter) self.installEventFilter(self.languageChangeEventFilter)

View File

@ -1,6 +1,4 @@
import sys from PySide6.QtCore import QCoreApplication, QDir, QLocale
from PySide6.QtCore import QCoreApplication, QDir, QLocale, QProcess
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QApplication,
QCheckBox, QCheckBox,
@ -10,8 +8,8 @@ from PySide6.QtWidgets import (
QPushButton, QPushButton,
) )
from core.settings import SettingsKeys, settings
from ui.extends.shared.language import changeAppLanguage, localeToCode, localeToFullName from ui.extends.shared.language import changeAppLanguage, localeToCode, localeToFullName
from ui.extends.shared.settings import DATABASE_URL, LANGUAGE
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
@ -33,8 +31,8 @@ class SettingsGeneral(SettingsBaseWidget):
self.languageFollowSystemCheckBox.toggled.connect( self.languageFollowSystemCheckBox.toggled.connect(
self.changeLanguageFollowSystem self.changeLanguageFollowSystem
) )
if self.settings.language(): if language := settings.stringValue(SettingsKeys.General.Language):
locale = QLocale(self.settings.language()) locale = QLocale(language)
index = self.languageValueWidget.findData(locale) index = self.languageValueWidget.findData(locale)
if index > -1: if index > -1:
self.languageValueWidget.setCurrentIndex(index) self.languageValueWidget.setCurrentIndex(index)
@ -51,7 +49,7 @@ class SettingsGeneral(SettingsBaseWidget):
self.insertItem( self.insertItem(
"dbUrl", "dbUrl",
self.dbUrlLabel, self.dbUrlLabel,
QLabel(self.settings.databaseUrl()), QLabel(settings.stringValue(SettingsKeys.General.DatabaseUrl)),
self.dbUrlResetButton, self.dbUrlResetButton,
) )
@ -59,13 +57,13 @@ class SettingsGeneral(SettingsBaseWidget):
locale = self.languageValueWidget.currentData() locale = self.languageValueWidget.currentData()
if locale: if locale:
changeAppLanguage(locale) changeAppLanguage(locale)
self.settings.setLanguage(localeToCode(locale)) settings.setValue(SettingsKeys.General.Language, localeToCode(locale))
def changeLanguageFollowSystem(self): def changeLanguageFollowSystem(self):
followSystem = self.languageFollowSystemCheckBox.isChecked() followSystem = self.languageFollowSystemCheckBox.isChecked()
self.languageValueWidget.setCurrentIndex(-1) self.languageValueWidget.setCurrentIndex(-1)
if followSystem: if followSystem:
self.settings.remove(LANGUAGE) settings.remove(SettingsKeys.General.Language)
changeAppLanguage(QLocale.system()) changeAppLanguage(QLocale.system())
self.languageValueWidget.setEnabled(False) self.languageValueWidget.setEnabled(False)
else: else:
@ -80,7 +78,7 @@ class SettingsGeneral(SettingsBaseWidget):
QMessageBox.StandardButton.No, QMessageBox.StandardButton.No,
) )
if userConfirm == QMessageBox.StandardButton.Yes: if userConfirm == QMessageBox.StandardButton.Yes:
self.settings.remove(DATABASE_URL) settings.remove(SettingsKeys.General.DatabaseUrl)
QApplication.instance().quit() QApplication.instance().quit()
def setupUi(self, *args): def setupUi(self, *args):

View File

@ -1,6 +1,7 @@
from PySide6.QtCore import QCoreApplication from PySide6.QtCore import QCoreApplication
from PySide6.QtWidgets import QLabel, QPushButton from PySide6.QtWidgets import QLabel, QPushButton
from core.settings import SettingsKeys, settings
from ui.implements.components.fileSelector import FileSelector from ui.implements.components.fileSelector import FileSelector
from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget from ui.implements.settings.settingsBaseWidget import SettingsBaseWidget
@ -11,8 +12,8 @@ class SettingsOcr(SettingsBaseWidget):
self.setupUi(self) self.setupUi(self)
if self.settings.knnModelFile(): if knnModelFile := settings.stringValue(SettingsKeys.Ocr.KnnModelFile):
self.knnModelFileValueWidget.selectFile(self.settings.knnModelFile()) self.knnModelFileValueWidget.selectFile(knnModelFile)
self.knnModelFileValueWidget.filesSelected.connect(self.setKnnModelFile) self.knnModelFileValueWidget.filesSelected.connect(self.setKnnModelFile)
self.knnModelFileResetButton.clicked.connect(self.resetKnnModelFile) self.knnModelFileResetButton.clicked.connect(self.resetKnnModelFile)
self.insertItem( self.insertItem(
@ -22,8 +23,8 @@ class SettingsOcr(SettingsBaseWidget):
self.knnModelFileResetButton, self.knnModelFileResetButton,
) )
if self.settings.b30KnnModelFile(): if b30KnnModelFile := settings.stringValue(SettingsKeys.Ocr.B30KnnModelFile):
self.b30KnnModelFileValueWidget.selectFile(self.settings.b30KnnModelFile()) self.b30KnnModelFileValueWidget.selectFile(b30KnnModelFile)
self.b30KnnModelFileValueWidget.filesSelected.connect(self.setB30KnnModelFile) self.b30KnnModelFileValueWidget.filesSelected.connect(self.setB30KnnModelFile)
self.b30KnnModelFileResetButton.clicked.connect(self.resetB30KnnModelFile) self.b30KnnModelFileResetButton.clicked.connect(self.resetB30KnnModelFile)
self.insertItem( self.insertItem(
@ -33,10 +34,10 @@ class SettingsOcr(SettingsBaseWidget):
self.b30KnnModelFileResetButton, self.b30KnnModelFileResetButton,
) )
if self.settings.phashDatabaseFile(): if phashDatabaseFile := settings.stringValue(
self.phashDatabaseFileValueWidget.selectFile( SettingsKeys.Ocr.PhashDatabaseFile
self.settings.phashDatabaseFile() ):
) self.phashDatabaseFileValueWidget.selectFile(phashDatabaseFile)
self.phashDatabaseFileValueWidget.filesSelected.connect( self.phashDatabaseFileValueWidget.filesSelected.connect(
self.setPHashDatabaseFile self.setPHashDatabaseFile
) )
@ -52,31 +53,31 @@ class SettingsOcr(SettingsBaseWidget):
selectedFile = self.knnModelFileValueWidget.selectedFiles() selectedFile = self.knnModelFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]: if selectedFile and selectedFile[0]:
file = selectedFile[0] file = selectedFile[0]
self.settings.setKnnModelFile(file) settings.setValue(SettingsKeys.Ocr.KnnModelFile, file)
def resetKnnModelFile(self): def resetKnnModelFile(self):
self.knnModelFileValueWidget.reset() self.knnModelFileValueWidget.reset()
self.settings.resetKnnModelFile() settings.setValue(SettingsKeys.Ocr.KnnModelFile, None)
def setB30KnnModelFile(self): def setB30KnnModelFile(self):
selectedFile = self.b30KnnModelFileValueWidget.selectedFiles() selectedFile = self.b30KnnModelFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]: if selectedFile and selectedFile[0]:
file = selectedFile[0] file = selectedFile[0]
self.settings.setB30KnnModelFile(file) settings.setValue(SettingsKeys.Ocr.B30KnnModelFile, file)
def resetB30KnnModelFile(self): def resetB30KnnModelFile(self):
self.b30KnnModelFileValueWidget.reset() self.b30KnnModelFileValueWidget.reset()
self.settings.resetB30KnnModelFile() settings.setValue(SettingsKeys.Ocr.B30KnnModelFile, None)
def setPHashDatabaseFile(self): def setPHashDatabaseFile(self):
selectedFile = self.phashDatabaseFileValueWidget.selectedFiles() selectedFile = self.phashDatabaseFileValueWidget.selectedFiles()
if selectedFile and selectedFile[0]: if selectedFile and selectedFile[0]:
file = selectedFile[0] file = selectedFile[0]
self.settings.setPHashDatabaseFile(file) settings.setValue(SettingsKeys.Ocr.PhashDatabaseFile, file)
def resetPHashDatabaseFile(self): def resetPHashDatabaseFile(self):
self.phashDatabaseFileValueWidget.reset() self.phashDatabaseFileValueWidget.reset()
self.settings.resetPHashDatabaseFile() settings.setValue(SettingsKeys.Ocr.PhashDatabaseFile, None)
def setupUi(self, *args): def setupUi(self, *args):
self.knnModelFileLabel = QLabel(self) self.knnModelFileLabel = QLabel(self)

View File

@ -163,19 +163,15 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
QMessageBox.critical( QMessageBox.critical(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"), QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"),
# fmt: on ) # fmt: skip
)
return return
if not self.constantLineEdit.hasAcceptableInput(): if not self.constantLineEdit.hasAcceptableInput():
QMessageBox.critical( QMessageBox.critical(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.constantRequired"), QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.constantRequired"),
# fmt: on ) # fmt: skip
)
return return
constant = int(self.constantLineEdit.text()) constant = int(self.constantLineEdit.text())
@ -202,10 +198,8 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
QMessageBox.critical( QMessageBox.critical(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"), QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"),
# fmt: on ) # fmt: skip
)
return return
chartInfo = self.db.get_chart_info(chart.song_id, chart.rating_class) chartInfo = self.db.get_chart_info(chart.song_id, chart.rating_class)
@ -213,12 +207,10 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
result = QMessageBox.warning( result = QMessageBox.warning(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "deleteConfirm"), QCoreApplication.translate("TabDb_ChartInfoEditor", "deleteConfirm"),
# fmt: on
QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No, QMessageBox.StandardButton.No,
) ) # fmt: skip
if result == QMessageBox.StandardButton.Yes: if result == QMessageBox.StandardButton.Yes:
with self.db.sessionmaker() as session: with self.db.sessionmaker() as session:
session.delete(chartInfo) session.delete(chartInfo)

View File

@ -91,7 +91,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
session.commit() session.commit()
databaseUpdateSignals.songAddOrDelete.emit() databaseUpdateSignals.songAddOrDelete.emit()
itemNum = len([item for item in parser.parse() if isinstance(item, instance)]) itemNum = len([item for item in parser.parse() if isinstance(item, instance)])
logger.info(f"updated {itemNum} {logName} from {path}") logger.info("updated %d %s from %s", itemNum, logName, path)
return itemNum return itemNum
def importPacklist(self, packlistPath): def importPacklist(self, packlistPath):
@ -161,7 +161,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
return return
try: try:
logger.info(f"Importing {apkFile}") logger.info("Importing %s", apkFile)
with zipfile.ZipFile(apkFile) as zf: with zipfile.ZipFile(apkFile) as zf:
packlistPath = zipfile.Path(zf) / "assets" / "songs" / "packlist" packlistPath = zipfile.Path(zf) / "assets" / "songs" / "packlist"
@ -193,7 +193,9 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database() db = Database()
parser = St3ScoreParser(dbFile) parser = St3ScoreParser(dbFile)
logger.info( logger.info(
f"Got {len(parser.parse())} items from {dbFile}, writing into database..." "Got %d items from %s, writing into database...",
len(parser.parse()),
dbFile,
) )
with db.sessionmaker() as session: with db.sessionmaker() as session:
parser.write_database(session) parser.write_database(session)
@ -218,7 +220,9 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database() db = Database()
parser = ArcaeaOnlineParser(apiResultFile) parser = ArcaeaOnlineParser(apiResultFile)
logger.info( logger.info(
f"Got {len(parser.parse())} items from {apiResultFile}, writing into database..." "Got %d items from %s, writing into database...",
len(parser.parse()),
apiResultFile,
) )
with db.sessionmaker() as session: with db.sessionmaker() as session:
parser.write_database(session) parser.write_database(session)

View File

@ -154,23 +154,17 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget):
self.treeView.setItemDelegateForColumn(1, self.treeViewProxyDelegate) self.treeView.setItemDelegateForColumn(1, self.treeViewProxyDelegate)
self.quickSelect_comboBox.addItem( self.quickSelect_comboBox.addItem(
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.idEarlier"), QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.idEarlier"),
# fmt: on
QuickSelectComboBoxValues.ID_EARLIER QuickSelectComboBoxValues.ID_EARLIER
) ) # fmt: skip
self.quickSelect_comboBox.addItem( self.quickSelect_comboBox.addItem(
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.dateEarlier"), QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.dateEarlier"),
# fmt: on
QuickSelectComboBoxValues.DATE_EARLIER QuickSelectComboBoxValues.DATE_EARLIER
) ) # fmt: skip
self.quickSelect_comboBox.addItem( self.quickSelect_comboBox.addItem(
# fmt: off
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.columnsIntegral"), QCoreApplication.translate("TabDb_RemoveDuplicateScores", "quickSelectComboBox.columnsIntegral"),
# fmt: on
QuickSelectComboBoxValues.COLUMNS_INTEGRAL QuickSelectComboBoxValues.COLUMNS_INTEGRAL
) ) # fmt: skip
def getQueryColumns(self): def getQueryColumns(self):
columns: list[InstrumentedAttribute] = [Score.song_id, Score.rating_class] columns: list[InstrumentedAttribute] = [Score.song_id, Score.rating_class]
@ -291,12 +285,12 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget):
confirm = QMessageBox.warning( confirm = QMessageBox.warning(
self, self,
None, None,
# fmt: off QCoreApplication.translate(
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "deleteSelectionDialog.content {}").format(len(selectedScores)), "TabDb_RemoveDuplicateScores", "deleteSelectionDialog.content {}"
# fmt: on ).format(len(selectedScores)),
QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No, QMessageBox.StandardButton.No,
) ) # fmt: skip
if confirm != QMessageBox.StandardButton.Yes: if confirm != QMessageBox.StandardButton.Yes:
return return
@ -310,12 +304,11 @@ class TabDb_RemoveDuplicateScores(Ui_TabDb_RemoveDuplicateScores, QWidget):
@Slot() @Slot()
def on_scan_scanButton_clicked(self): def on_scan_scanButton_clicked(self):
if len(self.getQueryColumns()) <= 2: if len(self.getQueryColumns()) <= 2:
message = QCoreApplication.translate("TabDb_RemoveDuplicateScores", "scan_noColumnsDialog.content") # fmt: skip
result = QMessageBox.warning( result = QMessageBox.warning(
self, self,
None, None,
# fmt: off message,
QCoreApplication.translate("TabDb_RemoveDuplicateScores", "scan_noColumnsDialog.content"),
# fmt: on
QMessageBox.StandardButton.Yes, QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No, QMessageBox.StandardButton.No,
) )

View File

@ -9,6 +9,7 @@ from PIL import Image
from PySide6.QtCore import Signal, Slot from PySide6.QtCore import Signal, Slot
from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget
from core.settings import SettingsKeys
from ui.designer.tabs.tabOcr.tabOcr_B30_ui import Ui_TabOcr_B30 from ui.designer.tabs.tabOcr.tabOcr_B30_ui import Ui_TabOcr_B30
from ui.extends.components.ocrQueue import OcrQueueModel from ui.extends.components.ocrQueue import OcrQueueModel
from ui.extends.ocr.dependencies import ( from ui.extends.ocr.dependencies import (
@ -16,11 +17,6 @@ from ui.extends.ocr.dependencies import (
getPhashDatabaseStatusText, getPhashDatabaseStatusText,
) )
from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import (
B30_KNN_MODEL_FILE,
KNN_MODEL_FILE,
PHASH_DATABASE_FILE,
)
from ui.extends.tabs.tabOcr.tabOcr_B30 import ChieriV4OcrRunnable, b30ResultToScore from ui.extends.tabs.tabOcr.tabOcr_B30 import ChieriV4OcrRunnable, b30ResultToScore
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -55,9 +51,15 @@ class TabOcr_B30(Ui_TabOcr_B30, QWidget):
self.ocr = None self.ocr = None
logger.info("Applying settings...") logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE) self.dependencies_knnModelSelector.connectSettings(
self.dependencies_b30KnnModelSelector.connectSettings(B30_KNN_MODEL_FILE) SettingsKeys.Ocr.KnnModelFile
self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE) )
self.dependencies_b30KnnModelSelector.connectSettings(
SettingsKeys.Ocr.B30KnnModelFile
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.ocrQueueModel = OcrQueueModel(self) self.ocrQueueModel = OcrQueueModel(self)
self.ocrQueue.setModel(self.ocrQueueModel) self.ocrQueue.setModel(self.ocrQueueModel)

View File

@ -11,10 +11,10 @@ from arcaea_offline_ocr.phash_db import ImagePhashDatabase
from PySide6.QtCore import Slot from PySide6.QtCore import Slot
from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QWidget from PySide6.QtWidgets import QApplication, QFileDialog, QMessageBox, QWidget
from core.settings import SettingsKeys
from ui.designer.tabs.tabOcr.tabOcr_Device_ui import Ui_TabOcr_Device from ui.designer.tabs.tabOcr.tabOcr_Device_ui import Ui_TabOcr_Device
from ui.extends.components.ocrQueue import OcrQueueModel from ui.extends.components.ocrQueue import OcrQueueModel
from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import KNN_MODEL_FILE, PHASH_DATABASE_FILE
from ui.extends.tabs.tabOcr.tabOcr_Device import ScoreConverter, TabDeviceOcrRunnable from ui.extends.tabs.tabOcr.tabOcr_Device import ScoreConverter, TabDeviceOcrRunnable
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -54,8 +54,12 @@ class TabOcr_Device(Ui_TabOcr_Device, QWidget):
) )
logger.info("Applying settings...") logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE) self.dependencies_knnModelSelector.connectSettings(
self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE) SettingsKeys.Ocr.KnnModelFile
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.options_usePresetCheckBox.setChecked(True) self.options_usePresetCheckBox.setChecked(True)
self.options_usePresetCheckBox.setEnabled(False) self.options_usePresetCheckBox.setEnabled(False)

View File

@ -50,6 +50,4 @@ class TabOverview(Ui_TabOverview, QWidget):
def retranslateUi(self, *args): def retranslateUi(self, *args):
super().retranslateUi(self) super().retranslateUi(self)
# fmt: off self.describeFormatString = QCoreApplication.translate("TabOverview", "databaseDescribeLabel {} {} {} {} {} {}") # fmt: skip
self.describeFormatString = QCoreApplication.translate("TabOverview", "databaseDescribeLabel {} {} {} {} {} {}")
# fmt: on

View File

@ -11,9 +11,9 @@ from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, Qt, Slot
from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPaintEvent, QPixmap from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPaintEvent, QPixmap
from PySide6.QtWidgets import QButtonGroup, QFileDialog, QLabel, QMessageBox, QWidget from PySide6.QtWidgets import QButtonGroup, QFileDialog, QLabel, QMessageBox, QWidget
from core.settings import SettingsKeys
from ui.designer.tabs.tabTools.tabTools_Andreal_ui import Ui_TabTools_Andreal from ui.designer.tabs.tabTools.tabTools_Andreal_ui import Ui_TabTools_Andreal
from ui.extends.shared.language import LanguageChangeEventFilter from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import ANDREAL_EXECUTABLE, ANDREAL_FOLDER
from ui.extends.tabs.tabTools.tabTools_Andreal import AndrealHelper from ui.extends.tabs.tabTools.tabTools_Andreal import AndrealHelper
from ui.implements.components.chartSelector import ChartSelector from ui.implements.components.chartSelector import ChartSelector
from ui.implements.components.songIdSelector import SongIdSelectorMode from ui.implements.components.songIdSelector import SongIdSelectorMode
@ -80,8 +80,8 @@ class TabTools_Andreal(Ui_TabTools_Andreal, QWidget):
self.andrealFolderSelector.filesSelected.connect(self.setHelperPaths) self.andrealFolderSelector.filesSelected.connect(self.setHelperPaths)
self.andrealExecutableSelector.filesSelected.connect(self.setHelperPaths) self.andrealExecutableSelector.filesSelected.connect(self.setHelperPaths)
self.andrealFolderSelector.connectSettings(ANDREAL_FOLDER) self.andrealFolderSelector.connectSettings(SettingsKeys.Andreal.Folder)
self.andrealExecutableSelector.connectSettings(ANDREAL_EXECUTABLE) self.andrealExecutableSelector.connectSettings(SettingsKeys.Andreal.Executable)
self.generatePreviewButton.clicked.connect(self.requestPreview) self.generatePreviewButton.clicked.connect(self.requestPreview)
self.generateImageButton.clicked.connect(self.requestGenerate) self.generateImageButton.clicked.connect(self.requestGenerate)
@ -131,13 +131,8 @@ class TabTools_Andreal(Ui_TabTools_Andreal, QWidget):
@Slot() @Slot()
def on_imageTypeWhatIsThisButton_clicked(self): def on_imageTypeWhatIsThisButton_clicked(self):
QMessageBox.information( message = QCoreApplication.translate("TabTools_Andreal", "imageWhatIsThisDialog.description") # fmt: skip
self, QMessageBox.information(self, None, message)
None,
# fmt: off
QCoreApplication.translate("TabTools_Andreal", "imageWhatIsThisDialog.description"),
# fmt: on
)
def imageFormat(self): def imageFormat(self):
buttonId = self.imageFormatButtonGroup.checkedId() buttonId = self.imageFormatButtonGroup.checkedId()

View File

@ -90,10 +90,8 @@ class PlayRatingCalculatorDialog(QDialog):
self.acceptButton = QPushButton(self) self.acceptButton = QPushButton(self)
self.acceptButton.setText( self.acceptButton.setText(
# fmt: off
QCoreApplication.translate("StepCalculator", "playRatingCalculatorDialog.acceptButton") QCoreApplication.translate("StepCalculator", "playRatingCalculatorDialog.acceptButton")
# fmt: on ) # fmt: skip
)
self.acceptButton.setEnabled(False) self.acceptButton.setEnabled(False)
self.verticalLayout.addWidget(self.acceptButton) self.verticalLayout.addWidget(self.acceptButton)

View File

@ -6,8 +6,8 @@ from arcaea_offline.database import Database
from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, QSysInfo, Qt, QUrl, Slot from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, QSysInfo, Qt, QUrl, Slot
from PySide6.QtWidgets import QDialog, QMessageBox from PySide6.QtWidgets import QDialog, QMessageBox
from core.settings import SettingsKeys, settings
from ui.extends.shared.database import create_engine from ui.extends.shared.database import create_engine
from ui.extends.shared.settings import Settings
from .databaseChecker_ui import Ui_DatabaseChecker from .databaseChecker_ui import Ui_DatabaseChecker
@ -29,8 +29,7 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
self.dbDirSelector.setMode(self.dbDirSelector.getExistingDirectory) self.dbDirSelector.setMode(self.dbDirSelector.getExistingDirectory)
self.confirmDbByExistingSettings = False self.confirmDbByExistingSettings = False
self.settings = Settings(self) if dbUrlString := settings.stringValue(SettingsKeys.General.DatabaseUrl):
if dbUrlString := self.settings.databaseUrl():
dbFileUrl = QUrl(dbUrlString.replace("sqlite://", "file://")) dbFileUrl = QUrl(dbUrlString.replace("sqlite://", "file://"))
dbFileInfo = QFileInfo(dbFileUrl.toLocalFile()) dbFileInfo = QFileInfo(dbFileUrl.toLocalFile())
if dbFileInfo.exists(): if dbFileInfo.exists():
@ -45,6 +44,9 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
self.dbDirSelector.selectFile(QDir.currentPath()) self.dbDirSelector.selectFile(QDir.currentPath())
self.dbFilenameLineEdit.setText("arcaea_offline.db") self.dbFilenameLineEdit.setText("arcaea_offline.db")
def writeDatabaseUrlToSettings(self, databaseUrl: str):
settings.setValue(SettingsKeys.General.DatabaseUrl, databaseUrl)
def dbPath(self): def dbPath(self):
return QDir(self.dbDirSelector.selectedFiles()[0]) return QDir(self.dbDirSelector.selectedFiles()[0])
@ -79,7 +81,7 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
db = Database(create_engine(dbSqliteUrl)) db = Database(create_engine(dbSqliteUrl))
if db.check_init(): if db.check_init():
flags |= DatabaseCheckerResult.Initted flags |= DatabaseCheckerResult.Initted
self.settings.setDatabaseUrl(self.dbSqliteUrl().toString()) self.writeDatabaseUrlToSettings(self.dbSqliteUrl().toString())
return flags return flags
@ -108,20 +110,18 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
@Slot() @Slot()
def on_confirmDbPathButton_clicked(self): def on_confirmDbPathButton_clicked(self):
dbSqliteUrl = self.dbSqliteUrl() dbSqliteUrl = self.dbSqliteUrl()
self.settings.setDatabaseUrl(dbSqliteUrl.toString()) self.writeDatabaseUrlToSettings(dbSqliteUrl.toString())
result = self.confirmDb() result = self.confirmDb()
if result & DatabaseCheckerResult.Initted: if result & DatabaseCheckerResult.Initted:
if not self.confirmDbByExistingSettings: if not self.confirmDbByExistingSettings:
self.settings.setDatabaseUrl(dbSqliteUrl.toString()) self.writeDatabaseUrlToSettings(dbSqliteUrl.toString())
elif result & DatabaseCheckerResult.FileExist: elif result & DatabaseCheckerResult.FileExist:
confirm_try_init = QMessageBox.question( confirm_try_init = QMessageBox.question(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.tryInitExistingDatabase"), QCoreApplication.translate("DatabaseChecker", "dialog.tryInitExistingDatabase"),
# fmt: on ) # fmt: skip
)
if confirm_try_init == QMessageBox.StandardButton.Yes: if confirm_try_init == QMessageBox.StandardButton.Yes:
try: try:
Database().init(checkfirst=True) Database().init(checkfirst=True)
@ -134,10 +134,8 @@ class DatabaseChecker(Ui_DatabaseChecker, QDialog):
confirm_new_database = QMessageBox.question( confirm_new_database = QMessageBox.question(
self, self,
None, None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.confirmNewDatabase"), QCoreApplication.translate("DatabaseChecker", "dialog.confirmNewDatabase"),
# fmt: on ) # fmt: skip
)
if confirm_new_database == QMessageBox.StandardButton.Yes: if confirm_new_database == QMessageBox.StandardButton.Yes:
db = Database(create_engine(dbSqliteUrl)) db = Database(create_engine(dbSqliteUrl))
db.init() db.init()