41 Commits

Author SHA1 Message Date
4409986687 wip: basic theming support 2025-10-28 21:23:06 +08:00
9c96714c8f refactor: replace logging with structlog 2025-10-28 21:18:27 +08:00
9a761f1191 chore: update dependencies, introduce uv 2025-10-25 21:20:35 +08:00
42b3447b43 chore: configure pyside6-project 2025-10-24 22:58:25 +08:00
d90a165df8 wip: replace Text with Label 2025-10-08 16:29:35 +08:00
87304b02f7 wip: qml overview tab 2025-09-13 14:47:24 +08:00
5b44696fd7 wip: database object 2025-09-13 14:46:51 +08:00
de385e9b26 chore: update pre-commit 2025-09-13 14:40:05 +08:00
1453686de6 wip: database checker 2025-09-12 00:02:28 +08:00
5db2207ee0 wip: qml components 2025-08-15 01:49:01 +08:00
06a1ca00bd refactor: database initialize checker 2025-08-13 15:17:46 +08:00
806acd5793 refactor: SongIdSelectorViewModel 2025-06-05 17:05:47 +08:00
ceb6e2932e chore: update dependencies 2025-06-04 19:10:28 +08:00
035e2157a8 log tag support 2025-06-04 18:55:23 +08:00
e964a38e3d change # fmt: labels to ruff compatible 2025-06-04 18:55:23 +08:00
0e2026ff1c change logging format to % 2025-06-04 18:55:23 +08:00
3ce4c7bed9 core.color 2025-06-04 18:55:23 +08:00
ad09c95b03 disable strict ruff rules 2025-06-04 18:55:23 +08:00
c664ed7e8d refactor: moving ui.extends to core
* Settings and Singletons moved
2025-06-04 18:55:23 +08:00
4e7d54fbef using ruff as formatter & linter 2025-06-04 18:55:23 +08:00
0c6f4f4961 chore: v0.3.9 2024-06-20 00:04:46 +08:00
10fb98d530 chore: upgrade dependencies 2024-06-20 00:03:19 +08:00
bf034d1397 ci: update build actions
* Switch to official Nuitka GitHub Actions

* Adding Linux build support

* Upgrade deprecated actions
2024-06-20 00:00:28 +08:00
4f864611ee fix: B30 table order (#11) 2024-06-19 22:20:31 +08:00
d9c163431c feat: OCR score date source (#9)
* New settings entries

* Choose `birthTime`/`lastModified` for OCR score date source if the image EXIF fails
2024-06-19 22:18:25 +08:00
d5895fe230 chore: v0.3.8 2024-04-01 01:00:46 +08:00
cd2e3f51ca ci: update build actions 2024-04-01 01:00:27 +08:00
4a09dc210a Merge pull request #7 from ArcaeaOffline/fix-issue-6
fix: rating class selection logic
2024-03-24 16:40:24 +08:00
cc8ab11b78 fix: rating class selection logic 2024-03-24 16:17:48 +08:00
48c5682e55 Merge pull request #5 from ArcaeaOffline/fix-issue-4
fix: linux dbUrl issue
2024-03-23 19:08:23 +08:00
ee03770764 chore: update README 2024-03-23 18:21:48 +08:00
b45c7f7de5 chore: dependencies 2024-03-23 18:18:53 +08:00
15bc56e6f9 fix: linux dbUrl issue 2024-03-23 17:41:36 +08:00
39ee379010 feat: ETERNAL rating class support 2024-03-20 15:52:26 +08:00
5a71a5822b feat: sync chart info database 2024-03-16 02:14:47 +08:00
c888b312b3 feat: DEF v2 scores export support 2024-02-27 17:24:49 +08:00
8e4fdc30b5 refactor(ui): TabDb_Manage 2024-02-15 17:59:14 +08:00
1ca868cfc6 ci: get full repo history for VERSION generating 2023-11-09 21:53:55 +08:00
d63d2f0d8b ci: build actions improve 2023-11-01 21:30:31 +08:00
3cd187fde3 ci: github actions 2023-11-01 20:00:16 +08:00
cce918a121 chore: update dependencies 2023-11-01 15:57:11 +08:00
98 changed files with 5390 additions and 1544 deletions

View File

@ -0,0 +1,65 @@
name: Build Executable from latest `arcaea-offline-*` dependencies
run-name: ${{ github.actor }} started a build request.
on:
workflow_dispatch:
permissions:
contents: write
discussions: write
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
# install dependencies
- run: "pip install -r requirements.txt"
- run: "pip uninstall arcaea-offline arcaea-offline-ocr -y"
- run: "pip install git+https://github.com/283375/arcaea-offline"
- run: "pip install git+https://github.com/283375/arcaea-offline-ocr"
- run: "pip install imageio"
- name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
- name: Release builtin files
run: |
pyside6-lrelease ui/resources/lang/en_US.ts ui/resources/lang/zh_CN.ts
python prebuild.py
pyside6-rcc ui/resources/resources.qrc -o ui/resources/resources_rc.py
- name: Build Executable
uses: Nuitka/Nuitka-Action@main
with:
nuitka-version: main
script-name: index.py
standalone: true
onefile: true
enable-plugins: pyside6,upx
windows-icon-from-ico: ui/resources/images/icon.png
linux-icon: ui/resources/images/icon.png
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ runner.os }} Build
path: |
build/*.exe
build/*.bin
build/*.app/**/*

78
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: Build Executable
run-name: ${{ github.actor }} started a build request.
on:
workflow_dispatch:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
permissions:
contents: write
discussions: write
jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install imageio
- name: Install UPX
uses: crazy-max/ghaction-upx@v3
with:
install-only: true
- name: Release builtin files
run: |
pyside6-lrelease ui/resources/lang/en_US.ts ui/resources/lang/zh_CN.ts
python prebuild.py
pyside6-rcc ui/resources/resources.qrc -o ui/resources/resources_rc.py
- name: Build Executable
uses: Nuitka/Nuitka-Action@main
with:
nuitka-version: main
script-name: index.py
standalone: true
onefile: true
enable-plugins: pyside6,upx
windows-icon-from-ico: ui/resources/images/icon.png
linux-icon: ui/resources/images/icon.png
- name: Upload Artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ runner.os }} Build
path: |
build/*.exe
build/*.bin
build/*.app/**/*
- name: Draft a release
uses: softprops/action-gh-release@v2
with:
discussion_category_name: New releases
draft: true
generate_release_notes: true
files: |
build/*.exe
build/*.bin
build/*.app/**/*

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
__debug*
.vscode/
# QML type cache
internal/
arcaea_offline.db
arcaea_offline.ini
/data

View File

@ -1,14 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v6.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/psf/black
rev: 23.1.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.13.0
hooks:
- id: black
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
- id: ruff
args: ["--fix"]
- id: ruff-format

View File

@ -1,9 +1,29 @@
# Arcaea Offline PySide UI
GUI for both [283375/arcaea-offline](https://github.com/283375/arcaea-offline) and [283375/arcaea-offline-ocr](https://github.com/283375/arcaea-offline-ocr)
[![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
## Before you run `python index.py`...
GUI for both [283375/arcaea-offline](https://github.com/283375/arcaea-offline) and [ArcaeaOffline/core-ocr](https://github.com/ArcaeaOffline/core-ocr).
## Prerequisites
* Install requirements
* Release translation files from `ui/resources/lang/*.ts`
* Run `prebuild.py`
* Compile `ui/resources/resources.qrc` to `ui/resources/resources_rc.py`
You can refer to the [GitHub Actions file](./.github/workflows/build.yml) for a rough reference.
```
pip install -r ./requirements.txt
pyside6-lrelease ./ui/resources/lang/en_US.ts ./ui/resources/lang/zh_CN.ts
python prebuild.py
pyside6-rcc ./ui/resources/resources.qrc -o ./ui/resources/resources_rc.py
```
Sometimes you have to install the latest, unpublished version of `arcaea-offline` and `arcaea-offline-ocr`.
```
pip uninstall -y arcaea-offline arcaea-offline-ocr
pip install git+https://github.com/283375/arcaea-offline
pip install git+https://github.com/ArcaeaOffline/core-ocr
```

91
app.py Normal file
View File

@ -0,0 +1,91 @@
import sys
from pathlib import Path
import structlog
from PySide6.QtCore import QCoreApplication, QEvent, QObject, Qt, QUrl
from PySide6.QtGui import QGuiApplication, QIcon
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtQuickControls2 import QQuickStyle
from ui.resources import resources_rc # noqa: F401
from ui.theme import ThemeManager
from ui.utils import url # noqa: F401
from ui.viewmodels import overview # noqa: F401
CURRENT_DIRECTORY = Path(__file__).resolve().parent
DEFAULT_FONTS = ["微软雅黑", "Microsoft YaHei UI", "Microsoft YaHei", "Segoe UI"]
logger: structlog.stdlib.BoundLogger = structlog.get_logger()
class ThemeChangeEventFilter(QObject):
logger: structlog.stdlib.BoundLogger = structlog.get_logger(
tag="ThemeChangeEventFilter",
)
def __init__(self, *, themeManager: ThemeManager):
super().__init__(None)
self.themeManager = themeManager
self.scheme = self.themeManager.getCurrentScheme()
def doSomething(self) -> None:
scheme = self.themeManager.getCurrentScheme()
if scheme == self.scheme:
self.logger.debug("Ignored same scheme event (%r)", scheme)
return
self.scheme = scheme
self.themeManager.updateTheme()
self.logger.debug("something done")
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
if event.type() == QEvent.Type.ThemeChange:
self.doSomething()
return False
def main() -> None:
app = QGuiApplication(sys.argv)
app.setFont(DEFAULT_FONTS)
app.setApplicationName("arcaea-offline-pyside-ui")
app.setApplicationDisplayName("Arcaea Offline")
app.setWindowIcon(QIcon(":/images/icon.png"))
engine = QQmlApplicationEngine()
themeManager = ThemeManager(parent=app)
def onThemeManagerThemeChanged():
logger.debug("App palette changed")
app.setPalette(themeManager.qPalette) # pyright: ignore[reportArgumentType]
engine.rootContext().setContextProperty("appTheme", themeManager.qmlExposer)
onThemeManagerThemeChanged()
themeManager.themeChanged.connect(onThemeManagerThemeChanged)
QQuickStyle.setStyle("Fusion")
def onEngineObjectCreated(obj: QObject | None, objUrl: QUrl) -> None:
if obj is None:
logger.critical("rootObject is None! Exiting!")
QCoreApplication.exit(-1)
engine.objectCreated.connect(
onEngineObjectCreated,
Qt.ConnectionType.QueuedConnection,
)
engine.load("ui/qmls/App.qml")
rootObject = engine.rootObjects()[0]
ef = ThemeChangeEventFilter(themeManager=themeManager)
rootObject.installEventFilter(ef)
sys.exit(app.exec())
if __name__ == "__main__":
main()

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)

12
core/database/__init__.py Normal file
View File

@ -0,0 +1,12 @@
from .conn import Database
from .init_checker import DatabaseInitCheckResult, check_db_init
from .utils import create_engine, db_path_to_sqlite_url, sqlite_url_to_db_path
__all__ = [
"check_db_init",
"create_engine",
"db_path_to_sqlite_url",
"Database",
"DatabaseInitCheckResult",
"sqlite_url_to_db_path",
]

29
core/database/conn.py Normal file
View File

@ -0,0 +1,29 @@
from pathlib import Path
from sqlalchemy import text
from sqlalchemy.orm import sessionmaker
from core.settings import SettingsKeys, settings
from .utils import create_engine, db_path_to_sqlite_url
class Database:
def __init__(self):
if settings.stringValue(SettingsKeys.General.DatabaseType) != "file":
raise ValueError("DatabaseType is not file")
db_path = settings.stringValue(SettingsKeys.General.DatabaseConn)
if not db_path:
raise ValueError("DatabaseConn is empty")
self.engine = create_engine(db_path_to_sqlite_url(Path(db_path)))
self.sessionmaker = sessionmaker(bind=self.engine)
@property
def b30(self) -> float | None:
with self.sessionmaker() as session:
result = session.execute(
text("SELECT b30 FROM calculated_potential")
).fetchone()
return result[0] if result else None

View File

@ -0,0 +1,30 @@
from enum import Flag, auto
from pathlib import Path
from arcaea_offline.database import Database
from .utils import create_engine, db_path_to_sqlite_url
class DatabaseInitCheckResult(Flag):
NONE = 0
FILE_EXISTS = auto()
INITIALIZED = auto()
OK = FILE_EXISTS | INITIALIZED
def check_db_init(file: Path) -> DatabaseInitCheckResult:
flags = DatabaseInitCheckResult.NONE
if not file.exists():
return flags
flags |= DatabaseInitCheckResult.FILE_EXISTS
db_url = db_path_to_sqlite_url(file)
db = Database(create_engine(db_url))
if db.check_init():
flags |= DatabaseInitCheckResult.INITIALIZED
return flags

28
core/database/utils.py Normal file
View File

@ -0,0 +1,28 @@
from pathlib import Path
from PySide6.QtCore import QSysInfo, QUrl
from sqlalchemy import Engine
from sqlalchemy import create_engine as sa_create_engine
from sqlalchemy.pool import NullPool, Pool
def db_path_to_sqlite_url(file: Path) -> QUrl:
kernelType = QSysInfo.kernelType()
# the slash count varies depending on the kernel
# https://docs.sqlalchemy.org/en/20/core/engines.html#sqlite
uri = file.resolve().as_uri()
if kernelType == "winnt":
return QUrl(uri.replace("file://", "sqlite://"))
else:
return QUrl(uri.replace("file://", "sqlite:///"))
def sqlite_url_to_db_path(url: str) -> Path:
db_file_url = url.replace("sqlite://", "file://")
return Path(QUrl(db_file_url).toLocalFile()).resolve()
def create_engine(_url: str | QUrl, pool: type[Pool] = NullPool) -> Engine:
url = _url.toString() if isinstance(_url, QUrl) else _url
return sa_create_engine(url, poolclass=pool)

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()

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

@ -0,0 +1,27 @@
from dataclasses import dataclass
from enum import StrEnum
class _General(StrEnum):
Language = "Language"
DatabaseType = "DatabaseType"
DatabaseConn = "DatabaseConn"
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

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

@ -0,0 +1,23 @@
from dataclasses import dataclass
from enum import StrEnum
class GeneralDatabaseType(StrEnum):
FILE = "file"
URL = "url"
@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
class QObjectSingleton(type(QObject), Singleton):
class QSingleton(type(QObject), Singleton):
pass

View File

@ -9,25 +9,21 @@ from PySide6.QtCore import QCoreApplication, QLocale
from PySide6.QtGui import QFontDatabase, QIcon
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.settings import Settings
from ui.implements.mainwindow import MainWindow
from ui.startup.databaseChecker import DatabaseChecker, DatabaseCheckerResult
from ui.startup.databaseChecker import DatabaseChecker, DatabaseInitCheckResult
rootLogger = logging.getLogger("root")
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):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
sys.__excepthook__(exc_type, exc_value, exc_traceback)
if issubclass(exc_type, KeyboardInterrupt):
return
rootLogger.critical(
"Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)
)
@ -46,6 +42,11 @@ if __name__ == "__main__":
ymd = now.strftime("%Y%m%d")
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(
str(logFolder / f"arcaea-offline-pyside-ui-{ymd}-{hms}_debug.log"),
encoding="utf-8",
@ -59,18 +60,21 @@ if __name__ == "__main__":
rootLogger.addHandler(rootLoggerStdOutHandler)
app = QApplication(sys.argv)
locale = (
QLocale(Settings().language()) if Settings().language() else QLocale.system()
)
settingsLanguage = settings.stringValue(SettingsKeys.General.Language)
locale = QLocale(settingsLanguage) if settingsLanguage else QLocale.system()
changeAppLanguage(locale)
QFontDatabase.addApplicationFont(":/fonts/GeosansLight.ttf")
databaseChecker = DatabaseChecker()
databaseChecker.setWindowIcon(QIcon(":/images/icon.png"))
databaseCheckResult = databaseChecker.confirmDb() if Settings().databaseUrl() else 0
databaseCheckResult = (
databaseChecker.confirmDb()
if settings.stringValue(SettingsKeys.General.DatabaseUrl)
else DatabaseInitCheckResult.NONE
)
if not databaseCheckResult & DatabaseCheckerResult.Initted:
if not databaseCheckResult & DatabaseInitCheckResult.INITIALIZED:
result = databaseChecker.exec()
if result == QDialog.DialogCode.Accepted:

View File

@ -40,7 +40,6 @@ def getBuildToolsVer():
def writeVersionFile():
versionFile = Path("ui/resources/VERSION")
assert versionFile.exists()
versionText = (
"arcaea-offline-pyside-ui\n{gitDesc}\n{buildToolsVer}\n\n"

View File

@ -1,42 +1,82 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "arcaea-offline-pyside-ui"
version = "0.1.0"
version = "0.3.9"
authors = [{ name = "283375", email = "log_283375@163.com" }]
description = "No description."
readme = "README.md"
requires-python = ">=3.9"
dependencies = [
"arcaea-offline==0.1.0",
"arcaea-offline-ocr==0.1.0",
"exif==1.6.0",
"PySide6==6.5.2",
"arcaea-offline==0.2.2",
"arcaea-offline-ocr==0.0.99",
"structlog~=25.4",
"colorama~=0.4.6",
"rich~=14.2",
"exif~=1.6.0",
"PySide6==6.10.0",
"Pillow~=10.1.0",
"materialyoucolor~=2.0.10",
]
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
]
[project.optional-dependencies]
dev = [
"ruff>=0.14.2",
"pre-commit>=4.3.0",
"imageio",
"Nuitka~=2.7.6",
]
[project.urls]
"Homepage" = "https://github.com/283375/arcaea-offline-pyside-ui"
"Bug Tracker" = "https://github.com/283375/arcaea-offline-pyside-ui/issues"
"Homepage" = "https://github.com/ArcaeaOffline/client-pyside6"
"Bug Tracker" = "https://github.com/ArcaeaOffline/client-pyside6/issues"
[tool.black]
force-exclude = '''
(
ui/designer
| .*_ui.py
| .*_rc.py
)
'''
[tool.ruff]
exclude = ["*_ui.py", "*_rc.py"]
[tool.isort]
profile = "black"
extend_skip = ["ui/designer"]
extend_skip_glob = ["*_ui.py", "*_rc.py"]
[tool.ruff.lint]
# Full list: https://docs.astral.sh/ruff/rules
select = [
"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
"N999", # invalid-module-name
]
[tool.pyright]
ignore = ["**/__debug*.*"]
[tool.pyside6-project]
files = [
"app.py",
"ui/qmls/App.qml",
"ui/qmls/AppMain.qml",
"ui/viewmodels/overview.py",
"ui/qmls/Overview.qml",
"ui/qmls/404.qml",
]

View File

@ -1,4 +0,0 @@
black == 23.7.0
isort == 5.12.0
imageio==2.31.4
Nuitka==1.8.4

View File

@ -1,4 +0,0 @@
arcaea-offline==0.1.0
arcaea-offline-ocr==0.1.0
exif==1.6.0
PySide6==6.5.2

View File

@ -22,39 +22,6 @@
<string>queue.title</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>iccOptionsGroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QRadioButton" name="iccUseQtRadioButton">
<property name="text">
<string>icc.useQt</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccUsePILRadioButton">
<property name="text">
<string>icc.usePIL</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccTryFixRadioButton">
<property name="text">
<string>icc.tryFix</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="ocr_addImageButton">
<property name="text">
@ -95,6 +62,13 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="optionsDialogButton">
<property name="text">
<string>queue.optionsButton</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ocr_startButton">
<property name="text">

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OcrQueueOptionsDialog</class>
<widget class="QDialog" name="OcrQueueOptionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>331</width>
<height>157</height>
</rect>
</property>
<property name="windowTitle">
<string>OCR Options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>iccOptionsGroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="iccUseQtRadioButton">
<property name="text">
<string>icc.useQt</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccUsePILRadioButton">
<property name="text">
<string>icc.usePIL</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="iccTryFixRadioButton">
<property name="text">
<string>icc.tryFix</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>dateOptionsGroupBox</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QCheckBox" name="dateReadFromExifCheckbox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>date.readFromExif</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="dateUseCreationDateRadioButton">
<property name="text">
<string>date.useCreationDate</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="dateUseModifyDateRadioButton">
<property name="text">
<string>date.useModifyDate</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>OcrQueueOptionsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>OcrQueueOptionsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'ocrQueueOptionsDialog.ui'
##
## Created by: Qt User Interface Compiler version 6.5.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
QDialogButtonBox, QGroupBox, QHBoxLayout, QRadioButton,
QSizePolicy, QVBoxLayout, QWidget)
class Ui_OcrQueueOptionsDialog(object):
def setupUi(self, OcrQueueOptionsDialog):
if not OcrQueueOptionsDialog.objectName():
OcrQueueOptionsDialog.setObjectName(u"OcrQueueOptionsDialog")
OcrQueueOptionsDialog.resize(331, 157)
self.verticalLayout = QVBoxLayout(OcrQueueOptionsDialog)
self.verticalLayout.setObjectName(u"verticalLayout")
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.groupBox = QGroupBox(OcrQueueOptionsDialog)
self.groupBox.setObjectName(u"groupBox")
self.verticalLayout_2 = QVBoxLayout(self.groupBox)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.iccUseQtRadioButton = QRadioButton(self.groupBox)
self.iccUseQtRadioButton.setObjectName(u"iccUseQtRadioButton")
self.verticalLayout_2.addWidget(self.iccUseQtRadioButton)
self.iccUsePILRadioButton = QRadioButton(self.groupBox)
self.iccUsePILRadioButton.setObjectName(u"iccUsePILRadioButton")
self.iccUsePILRadioButton.setChecked(True)
self.verticalLayout_2.addWidget(self.iccUsePILRadioButton)
self.iccTryFixRadioButton = QRadioButton(self.groupBox)
self.iccTryFixRadioButton.setObjectName(u"iccTryFixRadioButton")
self.verticalLayout_2.addWidget(self.iccTryFixRadioButton)
self.horizontalLayout.addWidget(self.groupBox)
self.groupBox_2 = QGroupBox(OcrQueueOptionsDialog)
self.groupBox_2.setObjectName(u"groupBox_2")
self.verticalLayout_3 = QVBoxLayout(self.groupBox_2)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.dateReadFromExifCheckbox = QCheckBox(self.groupBox_2)
self.dateReadFromExifCheckbox.setObjectName(u"dateReadFromExifCheckbox")
self.dateReadFromExifCheckbox.setEnabled(False)
self.dateReadFromExifCheckbox.setChecked(True)
self.verticalLayout_3.addWidget(self.dateReadFromExifCheckbox)
self.dateUseCreationDateRadioButton = QRadioButton(self.groupBox_2)
self.dateUseCreationDateRadioButton.setObjectName(u"dateUseCreationDateRadioButton")
self.dateUseCreationDateRadioButton.setChecked(True)
self.verticalLayout_3.addWidget(self.dateUseCreationDateRadioButton)
self.dateUseModifyDateRadioButton = QRadioButton(self.groupBox_2)
self.dateUseModifyDateRadioButton.setObjectName(u"dateUseModifyDateRadioButton")
self.verticalLayout_3.addWidget(self.dateUseModifyDateRadioButton)
self.horizontalLayout.addWidget(self.groupBox_2)
self.verticalLayout.addLayout(self.horizontalLayout)
self.buttonBox = QDialogButtonBox(OcrQueueOptionsDialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(OcrQueueOptionsDialog)
self.buttonBox.accepted.connect(OcrQueueOptionsDialog.accept)
self.buttonBox.rejected.connect(OcrQueueOptionsDialog.reject)
QMetaObject.connectSlotsByName(OcrQueueOptionsDialog)
# setupUi
def retranslateUi(self, OcrQueueOptionsDialog):
OcrQueueOptionsDialog.setWindowTitle(QCoreApplication.translate("OcrQueueOptionsDialog", u"OCR Options", None))
self.groupBox.setTitle(QCoreApplication.translate("OcrQueueOptionsDialog", u"iccOptionsGroupBox", None))
self.iccUseQtRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"icc.useQt", None))
self.iccUsePILRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"icc.usePIL", None))
self.iccTryFixRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"icc.tryFix", None))
self.groupBox_2.setTitle(QCoreApplication.translate("OcrQueueOptionsDialog", u"dateOptionsGroupBox", None))
self.dateReadFromExifCheckbox.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"date.readFromExif", None))
self.dateUseCreationDateRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"date.useCreationDate", None))
self.dateUseModifyDateRadioButton.setText(QCoreApplication.translate("OcrQueueOptionsDialog", u"date.useModifyDate", None))
# retranslateUi

View File

@ -17,8 +17,8 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractItemView, QApplication, QCheckBox, QGroupBox,
QHBoxLayout, QHeaderView, QLabel, QProgressBar,
QPushButton, QRadioButton, QSizePolicy, QSpacerItem,
QTableView, QVBoxLayout, QWidget)
QPushButton, QSizePolicy, QSpacerItem, QTableView,
QVBoxLayout, QWidget)
class Ui_OcrQueue(object):
def setupUi(self, OcrQueue):
@ -34,29 +34,6 @@ class Ui_OcrQueue(object):
self.groupBox_3.setObjectName(u"groupBox_3")
self.verticalLayout_2 = QVBoxLayout(self.groupBox_3)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.groupBox = QGroupBox(self.groupBox_3)
self.groupBox.setObjectName(u"groupBox")
self.verticalLayout = QVBoxLayout(self.groupBox)
self.verticalLayout.setObjectName(u"verticalLayout")
self.iccUseQtRadioButton = QRadioButton(self.groupBox)
self.iccUseQtRadioButton.setObjectName(u"iccUseQtRadioButton")
self.verticalLayout.addWidget(self.iccUseQtRadioButton)
self.iccUsePILRadioButton = QRadioButton(self.groupBox)
self.iccUsePILRadioButton.setObjectName(u"iccUsePILRadioButton")
self.iccUsePILRadioButton.setChecked(True)
self.verticalLayout.addWidget(self.iccUsePILRadioButton)
self.iccTryFixRadioButton = QRadioButton(self.groupBox)
self.iccTryFixRadioButton.setObjectName(u"iccTryFixRadioButton")
self.verticalLayout.addWidget(self.iccTryFixRadioButton)
self.verticalLayout_2.addWidget(self.groupBox)
self.ocr_addImageButton = QPushButton(self.groupBox_3)
self.ocr_addImageButton.setObjectName(u"ocr_addImageButton")
@ -78,6 +55,11 @@ class Ui_OcrQueue(object):
self.verticalLayout_2.addItem(self.verticalSpacer)
self.optionsDialogButton = QPushButton(self.groupBox_3)
self.optionsDialogButton.setObjectName(u"optionsDialogButton")
self.verticalLayout_2.addWidget(self.optionsDialogButton)
self.ocr_startButton = QPushButton(self.groupBox_3)
self.ocr_startButton.setObjectName(u"ocr_startButton")
@ -154,13 +136,10 @@ class Ui_OcrQueue(object):
def retranslateUi(self, OcrQueue):
self.groupBox_3.setTitle(QCoreApplication.translate("OcrQueue", u"queue.title", None))
self.groupBox.setTitle(QCoreApplication.translate("OcrQueue", u"iccOptionsGroupBox", None))
self.iccUseQtRadioButton.setText(QCoreApplication.translate("OcrQueue", u"icc.useQt", None))
self.iccUsePILRadioButton.setText(QCoreApplication.translate("OcrQueue", u"icc.usePIL", None))
self.iccTryFixRadioButton.setText(QCoreApplication.translate("OcrQueue", u"icc.tryFix", None))
self.ocr_addImageButton.setText(QCoreApplication.translate("OcrQueue", u"queue.addImageButton", None))
self.ocr_removeSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"queue.removeSelected", None))
self.ocr_removeAllButton.setText(QCoreApplication.translate("OcrQueue", u"queue.removeAll", None))
self.optionsDialogButton.setText(QCoreApplication.translate("OcrQueue", u"queue.optionsButton", None))
self.ocr_startButton.setText(QCoreApplication.translate("OcrQueue", u"queue.startOcrButton", None))
self.groupBox_5.setTitle(QCoreApplication.translate("OcrQueue", u"results", None))
self.ocr_acceptSelectedButton.setText(QCoreApplication.translate("OcrQueue", u"results.acceptSelectedButton", None))
@ -169,4 +148,3 @@ class Ui_OcrQueue(object):
self.statusLabel.setText("")
pass
# retranslateUi

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>528</height>
<width>580</width>
<height>551</height>
</rect>
</property>
<property name="windowTitle">
@ -17,70 +17,21 @@
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="syncArcSongDbButton">
<property name="text">
<string>syncArcSongDbButton</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>syncArcSongDb.description</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QPushButton" name="importSt3Button">
<property name="text">
<string>importSt3Button</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>importSt3.description</string>
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="exportScoresButton">
<property name="text">
<string>exportScoresButton</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>exportScores.description</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="1" column="0">
<widget class="QPushButton" name="importPacklistButton">
<property name="text">
<string>importPacklistButton</string>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="1" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>importPacklist.description</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="importSonglistButton">
<property name="text">
<string>importSonglistButton</string>
@ -88,69 +39,129 @@
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>importPacklist.description</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="label_5">
<property name="text">
<string>importSonglist.description</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QPushButton" name="exportArcsongJsonButton">
<property name="text">
<string>exportArcsongJsonButton</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>exportArcsongJson.description</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="3" column="0">
<widget class="QPushButton" name="importApkButton">
<property name="text">
<string>importApkButton</string>
</property>
</widget>
</item>
<item row="4" column="1">
<item row="3" column="1">
<widget class="QLabel" name="label_7">
<property name="text">
<string>importApk.description</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_11">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>chartInfoGroup</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QPushButton" name="syncArcSongDbButton">
<property name="text">
<string>syncArcSongDbButton</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>syncArcSongDb.description</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QLabel" name="label_12">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>importScoreGroup</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QPushButton" name="importSt3Button">
<property name="text">
<string>importSt3Button</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>importSt3.description</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QPushButton" name="importOnlineButton">
<property name="text">
<string>importOnlineButton</string>
</property>
</widget>
</item>
<item row="6" column="1">
<item row="11" column="1">
<widget class="QLabel" name="label_8">
<property name="text">
<string>importOnline.description</string>
</property>
</widget>
</item>
<item row="10" column="0">
<item row="13" column="0" colspan="2">
<widget class="QLabel" name="label_13">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>exportScoreGroup</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QPushButton" name="exportScoresButton">
<property name="text">
<string>exportScoresButton</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>exportScores.description</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QPushButton" name="exportSmartRteB30Button">
<property name="text">
<string>exportSmartRteB30Button</string>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="15" column="1">
<widget class="QLabel" name="label_9">
<property name="text">
<string>exportSmartRteB30.description</string>
@ -163,6 +174,124 @@
</property>
</widget>
</item>
<item row="17" column="0" colspan="2">
<widget class="QLabel" name="label_14">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>miscGroup</string>
</property>
</widget>
</item>
<item row="18" column="0">
<widget class="QPushButton" name="exportArcsongJsonButton">
<property name="text">
<string>exportArcsongJsonButton</string>
</property>
</widget>
</item>
<item row="18" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>exportArcsongJson.description</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_10">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>packSongInfoGroup</string>
</property>
</widget>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="12" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="16" column="0">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0">
<widget class="QPushButton" name="syncChartInfoDbButton">
<property name="text">
<string>syncChartInfoDbButton</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="label_15">
<property name="text">
<string>syncChartInfoDb.description</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>

View File

@ -15,123 +15,168 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QFormLayout, QFrame, QLabel,
QPushButton, QSizePolicy, QWidget)
from PySide6.QtWidgets import (QApplication, QFormLayout, QLabel, QPushButton,
QSizePolicy, QSpacerItem, QWidget)
class Ui_TabDb_Manage(object):
def setupUi(self, TabDb_Manage):
if not TabDb_Manage.objectName():
TabDb_Manage.setObjectName(u"TabDb_Manage")
TabDb_Manage.resize(630, 528)
TabDb_Manage.resize(580, 551)
TabDb_Manage.setWindowTitle(u"TabDb_Manage")
self.formLayout = QFormLayout(TabDb_Manage)
self.formLayout.setObjectName(u"formLayout")
self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.syncArcSongDbButton = QPushButton(TabDb_Manage)
self.syncArcSongDbButton.setObjectName(u"syncArcSongDbButton")
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.syncArcSongDbButton)
self.label = QLabel(TabDb_Manage)
self.label.setObjectName(u"label")
self.formLayout.setWidget(0, QFormLayout.FieldRole, self.label)
self.importSt3Button = QPushButton(TabDb_Manage)
self.importSt3Button.setObjectName(u"importSt3Button")
self.formLayout.setWidget(5, QFormLayout.LabelRole, self.importSt3Button)
self.label_2 = QLabel(TabDb_Manage)
self.label_2.setObjectName(u"label_2")
self.formLayout.setWidget(5, QFormLayout.FieldRole, self.label_2)
self.line = QFrame(TabDb_Manage)
self.line.setObjectName(u"line")
self.line.setFrameShape(QFrame.HLine)
self.line.setFrameShadow(QFrame.Sunken)
self.formLayout.setWidget(7, QFormLayout.SpanningRole, self.line)
self.exportScoresButton = QPushButton(TabDb_Manage)
self.exportScoresButton.setObjectName(u"exportScoresButton")
self.formLayout.setWidget(8, QFormLayout.LabelRole, self.exportScoresButton)
self.label_3 = QLabel(TabDb_Manage)
self.label_3.setObjectName(u"label_3")
self.formLayout.setWidget(8, QFormLayout.FieldRole, self.label_3)
self.line_2 = QFrame(TabDb_Manage)
self.line_2.setObjectName(u"line_2")
self.line_2.setFrameShape(QFrame.HLine)
self.line_2.setFrameShadow(QFrame.Sunken)
self.formLayout.setWidget(1, QFormLayout.SpanningRole, self.line_2)
self.importPacklistButton = QPushButton(TabDb_Manage)
self.importPacklistButton.setObjectName(u"importPacklistButton")
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.importPacklistButton)
self.importSonglistButton = QPushButton(TabDb_Manage)
self.importSonglistButton.setObjectName(u"importSonglistButton")
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.importSonglistButton)
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.importPacklistButton)
self.label_4 = QLabel(TabDb_Manage)
self.label_4.setObjectName(u"label_4")
self.formLayout.setWidget(2, QFormLayout.FieldRole, self.label_4)
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.label_4)
self.importSonglistButton = QPushButton(TabDb_Manage)
self.importSonglistButton.setObjectName(u"importSonglistButton")
self.formLayout.setWidget(2, QFormLayout.LabelRole, self.importSonglistButton)
self.label_5 = QLabel(TabDb_Manage)
self.label_5.setObjectName(u"label_5")
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.label_5)
self.exportArcsongJsonButton = QPushButton(TabDb_Manage)
self.exportArcsongJsonButton.setObjectName(u"exportArcsongJsonButton")
self.formLayout.setWidget(9, QFormLayout.LabelRole, self.exportArcsongJsonButton)
self.label_6 = QLabel(TabDb_Manage)
self.label_6.setObjectName(u"label_6")
self.formLayout.setWidget(9, QFormLayout.FieldRole, self.label_6)
self.formLayout.setWidget(2, QFormLayout.FieldRole, self.label_5)
self.importApkButton = QPushButton(TabDb_Manage)
self.importApkButton.setObjectName(u"importApkButton")
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.importApkButton)
self.formLayout.setWidget(3, QFormLayout.LabelRole, self.importApkButton)
self.label_7 = QLabel(TabDb_Manage)
self.label_7.setObjectName(u"label_7")
self.formLayout.setWidget(4, QFormLayout.FieldRole, self.label_7)
self.formLayout.setWidget(3, QFormLayout.FieldRole, self.label_7)
self.label_11 = QLabel(TabDb_Manage)
self.label_11.setObjectName(u"label_11")
font = QFont()
font.setPointSize(12)
font.setBold(False)
self.label_11.setFont(font)
self.formLayout.setWidget(5, QFormLayout.SpanningRole, self.label_11)
self.syncArcSongDbButton = QPushButton(TabDb_Manage)
self.syncArcSongDbButton.setObjectName(u"syncArcSongDbButton")
self.formLayout.setWidget(6, QFormLayout.LabelRole, self.syncArcSongDbButton)
self.label = QLabel(TabDb_Manage)
self.label.setObjectName(u"label")
self.formLayout.setWidget(6, QFormLayout.FieldRole, self.label)
self.label_12 = QLabel(TabDb_Manage)
self.label_12.setObjectName(u"label_12")
self.label_12.setFont(font)
self.formLayout.setWidget(9, QFormLayout.SpanningRole, self.label_12)
self.importSt3Button = QPushButton(TabDb_Manage)
self.importSt3Button.setObjectName(u"importSt3Button")
self.formLayout.setWidget(10, QFormLayout.LabelRole, self.importSt3Button)
self.label_2 = QLabel(TabDb_Manage)
self.label_2.setObjectName(u"label_2")
self.formLayout.setWidget(10, QFormLayout.FieldRole, self.label_2)
self.importOnlineButton = QPushButton(TabDb_Manage)
self.importOnlineButton.setObjectName(u"importOnlineButton")
self.formLayout.setWidget(6, QFormLayout.LabelRole, self.importOnlineButton)
self.formLayout.setWidget(11, QFormLayout.LabelRole, self.importOnlineButton)
self.label_8 = QLabel(TabDb_Manage)
self.label_8.setObjectName(u"label_8")
self.formLayout.setWidget(6, QFormLayout.FieldRole, self.label_8)
self.formLayout.setWidget(11, QFormLayout.FieldRole, self.label_8)
self.label_13 = QLabel(TabDb_Manage)
self.label_13.setObjectName(u"label_13")
self.label_13.setFont(font)
self.formLayout.setWidget(13, QFormLayout.SpanningRole, self.label_13)
self.exportScoresButton = QPushButton(TabDb_Manage)
self.exportScoresButton.setObjectName(u"exportScoresButton")
self.formLayout.setWidget(14, QFormLayout.LabelRole, self.exportScoresButton)
self.label_3 = QLabel(TabDb_Manage)
self.label_3.setObjectName(u"label_3")
self.formLayout.setWidget(14, QFormLayout.FieldRole, self.label_3)
self.exportSmartRteB30Button = QPushButton(TabDb_Manage)
self.exportSmartRteB30Button.setObjectName(u"exportSmartRteB30Button")
self.formLayout.setWidget(10, QFormLayout.LabelRole, self.exportSmartRteB30Button)
self.formLayout.setWidget(15, QFormLayout.LabelRole, self.exportSmartRteB30Button)
self.label_9 = QLabel(TabDb_Manage)
self.label_9.setObjectName(u"label_9")
self.label_9.setOpenExternalLinks(True)
self.label_9.setTextInteractionFlags(Qt.LinksAccessibleByKeyboard|Qt.LinksAccessibleByMouse)
self.formLayout.setWidget(10, QFormLayout.FieldRole, self.label_9)
self.formLayout.setWidget(15, QFormLayout.FieldRole, self.label_9)
self.label_14 = QLabel(TabDb_Manage)
self.label_14.setObjectName(u"label_14")
self.label_14.setFont(font)
self.formLayout.setWidget(17, QFormLayout.SpanningRole, self.label_14)
self.exportArcsongJsonButton = QPushButton(TabDb_Manage)
self.exportArcsongJsonButton.setObjectName(u"exportArcsongJsonButton")
self.formLayout.setWidget(18, QFormLayout.LabelRole, self.exportArcsongJsonButton)
self.label_6 = QLabel(TabDb_Manage)
self.label_6.setObjectName(u"label_6")
self.formLayout.setWidget(18, QFormLayout.FieldRole, self.label_6)
self.label_10 = QLabel(TabDb_Manage)
self.label_10.setObjectName(u"label_10")
self.label_10.setFont(font)
self.formLayout.setWidget(0, QFormLayout.SpanningRole, self.label_10)
self.verticalSpacer = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(4, QFormLayout.LabelRole, self.verticalSpacer)
self.verticalSpacer_2 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(8, QFormLayout.LabelRole, self.verticalSpacer_2)
self.verticalSpacer_3 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(12, QFormLayout.LabelRole, self.verticalSpacer_3)
self.verticalSpacer_4 = QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Minimum)
self.formLayout.setItem(16, QFormLayout.LabelRole, self.verticalSpacer_4)
self.syncChartInfoDbButton = QPushButton(TabDb_Manage)
self.syncChartInfoDbButton.setObjectName(u"syncChartInfoDbButton")
self.formLayout.setWidget(7, QFormLayout.LabelRole, self.syncChartInfoDbButton)
self.label_15 = QLabel(TabDb_Manage)
self.label_15.setObjectName(u"label_15")
self.formLayout.setWidget(7, QFormLayout.FieldRole, self.label_15)
self.retranslateUi(TabDb_Manage)
@ -140,24 +185,30 @@ class Ui_TabDb_Manage(object):
# setupUi
def retranslateUi(self, TabDb_Manage):
self.syncArcSongDbButton.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDbButton", None))
self.label.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDb.description", None))
self.importSt3Button.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3Button", None))
self.label_2.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3.description", None))
self.exportScoresButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportScoresButton", None))
self.label_3.setText(QCoreApplication.translate("TabDb_Manage", u"exportScores.description", None))
self.importPacklistButton.setText(QCoreApplication.translate("TabDb_Manage", u"importPacklistButton", None))
self.importSonglistButton.setText(QCoreApplication.translate("TabDb_Manage", u"importSonglistButton", None))
self.label_4.setText(QCoreApplication.translate("TabDb_Manage", u"importPacklist.description", None))
self.importSonglistButton.setText(QCoreApplication.translate("TabDb_Manage", u"importSonglistButton", None))
self.label_5.setText(QCoreApplication.translate("TabDb_Manage", u"importSonglist.description", None))
self.exportArcsongJsonButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJsonButton", None))
self.label_6.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJson.description", None))
self.importApkButton.setText(QCoreApplication.translate("TabDb_Manage", u"importApkButton", None))
self.label_7.setText(QCoreApplication.translate("TabDb_Manage", u"importApk.description", None))
self.label_11.setText(QCoreApplication.translate("TabDb_Manage", u"chartInfoGroup", None))
self.syncArcSongDbButton.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDbButton", None))
self.label.setText(QCoreApplication.translate("TabDb_Manage", u"syncArcSongDb.description", None))
self.label_12.setText(QCoreApplication.translate("TabDb_Manage", u"importScoreGroup", None))
self.importSt3Button.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3Button", None))
self.label_2.setText(QCoreApplication.translate("TabDb_Manage", u"importSt3.description", None))
self.importOnlineButton.setText(QCoreApplication.translate("TabDb_Manage", u"importOnlineButton", None))
self.label_8.setText(QCoreApplication.translate("TabDb_Manage", u"importOnline.description", None))
self.label_13.setText(QCoreApplication.translate("TabDb_Manage", u"exportScoreGroup", None))
self.exportScoresButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportScoresButton", None))
self.label_3.setText(QCoreApplication.translate("TabDb_Manage", u"exportScores.description", None))
self.exportSmartRteB30Button.setText(QCoreApplication.translate("TabDb_Manage", u"exportSmartRteB30Button", None))
self.label_9.setText(QCoreApplication.translate("TabDb_Manage", u"exportSmartRteB30.description", None))
self.label_14.setText(QCoreApplication.translate("TabDb_Manage", u"miscGroup", None))
self.exportArcsongJsonButton.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJsonButton", None))
self.label_6.setText(QCoreApplication.translate("TabDb_Manage", u"exportArcsongJson.description", None))
self.label_10.setText(QCoreApplication.translate("TabDb_Manage", u"packSongInfoGroup", None))
self.syncChartInfoDbButton.setText(QCoreApplication.translate("TabDb_Manage", u"syncChartInfoDbButton", None))
self.label_15.setText(QCoreApplication.translate("TabDb_Manage", u"syncChartInfoDb.description", None))
pass
# retranslateUi

View File

@ -1,124 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabOverview</class>
<widget class="QWidget" name="TabOverview">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>696</width>
<height>509</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">TabOverview</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="widget" native="true"/>
</item>
<item>
<widget class="QLabel" name="databaseDescribeLabel">
<property name="text">
<string notr="true" extracomment="This database now have {} packs, {} songs, {} difficulties, {} chart info ({} complete) and {} scores.">...</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="widget_3" native="true">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="b30Label">
<property name="font">
<font>
<pointsize>30</pointsize>
</font>
</property>
<property name="text">
<string notr="true">0.00</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string notr="true">B30</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="widget_4" native="true">
<property name="enabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="r10Label">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>30</pointsize>
</font>
</property>
<property name="text">
<string notr="true">--</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4">
<property name="enabled">
<bool>false</bool>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string notr="true">R10</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,114 +0,0 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'tabOverview.ui'
##
## Created by: Qt User Interface Compiler version 6.5.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QSizePolicy,
QVBoxLayout, QWidget)
class Ui_TabOverview(object):
def setupUi(self, TabOverview):
if not TabOverview.objectName():
TabOverview.setObjectName(u"TabOverview")
TabOverview.resize(696, 509)
TabOverview.setWindowTitle(u"TabOverview")
self.verticalLayout = QVBoxLayout(TabOverview)
self.verticalLayout.setObjectName(u"verticalLayout")
self.widget = QWidget(TabOverview)
self.widget.setObjectName(u"widget")
self.verticalLayout.addWidget(self.widget)
self.databaseDescribeLabel = QLabel(TabOverview)
self.databaseDescribeLabel.setObjectName(u"databaseDescribeLabel")
self.databaseDescribeLabel.setText(u"...")
self.verticalLayout.addWidget(self.databaseDescribeLabel)
self.widget_2 = QWidget(TabOverview)
self.widget_2.setObjectName(u"widget_2")
sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.widget_2.sizePolicy().hasHeightForWidth())
self.widget_2.setSizePolicy(sizePolicy)
self.horizontalLayout = QHBoxLayout(self.widget_2)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.widget_3 = QWidget(self.widget_2)
self.widget_3.setObjectName(u"widget_3")
self.verticalLayout_2 = QVBoxLayout(self.widget_3)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.b30Label = QLabel(self.widget_3)
self.b30Label.setObjectName(u"b30Label")
font = QFont()
font.setPointSize(30)
self.b30Label.setFont(font)
self.b30Label.setText(u"0.00")
self.b30Label.setAlignment(Qt.AlignBottom|Qt.AlignHCenter)
self.verticalLayout_2.addWidget(self.b30Label)
self.label_2 = QLabel(self.widget_3)
self.label_2.setObjectName(u"label_2")
font1 = QFont()
font1.setPointSize(20)
self.label_2.setFont(font1)
self.label_2.setText(u"B30")
self.label_2.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
self.verticalLayout_2.addWidget(self.label_2)
self.horizontalLayout.addWidget(self.widget_3)
self.widget_4 = QWidget(self.widget_2)
self.widget_4.setObjectName(u"widget_4")
self.widget_4.setEnabled(False)
self.verticalLayout_3 = QVBoxLayout(self.widget_4)
self.verticalLayout_3.setObjectName(u"verticalLayout_3")
self.r10Label = QLabel(self.widget_4)
self.r10Label.setObjectName(u"r10Label")
self.r10Label.setEnabled(False)
self.r10Label.setFont(font)
self.r10Label.setText(u"--")
self.r10Label.setAlignment(Qt.AlignBottom|Qt.AlignHCenter)
self.verticalLayout_3.addWidget(self.r10Label)
self.label_4 = QLabel(self.widget_4)
self.label_4.setObjectName(u"label_4")
self.label_4.setEnabled(False)
self.label_4.setFont(font1)
self.label_4.setText(u"R10")
self.label_4.setAlignment(Qt.AlignHCenter|Qt.AlignTop)
self.verticalLayout_3.addWidget(self.label_4)
self.horizontalLayout.addWidget(self.widget_4)
self.verticalLayout.addWidget(self.widget_2)
self.retranslateUi(TabOverview)
QMetaObject.connectSlotsByName(TabOverview)
# setupUi
def retranslateUi(self, TabOverview):
pass
# retranslateUi

View File

@ -5,7 +5,6 @@ from typing import Any, Callable, Optional, overload
from arcaea_offline.calculate import calculate_score_range
from arcaea_offline.database import Database
from arcaea_offline.models import Chart, Score
from arcaea_offline_ocr.b30.shared import B30OcrResultItem
from arcaea_offline_ocr.device.common import DeviceOcrResult
from PIL import Image
from PIL.ImageQt import ImageQt
@ -140,7 +139,11 @@ class OcrQueueModel(QAbstractListModel):
return True
else:
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
@ -150,6 +153,7 @@ class OcrQueueModel(QAbstractListModel):
@iccOption.setter
def iccOption(self, opt: IccOption):
logger.debug("ICC option changed to %s", opt)
self.__iccOption = opt
@overload
@ -158,8 +162,7 @@ class OcrQueueModel(QAbstractListModel):
image: str,
runnable: OcrRunnable = None,
process_func: Callable[[Optional[str], QImage, Any], Score] = None,
):
...
): ...
@overload
def addItem(
@ -167,8 +170,7 @@ class OcrQueueModel(QAbstractListModel):
image: QImage,
runnable: OcrRunnable = None,
process_func: Callable[[Optional[str], QImage, Any], Score] = None,
):
...
): ...
def addItem(
self,
@ -178,7 +180,7 @@ class OcrQueueModel(QAbstractListModel):
):
if isinstance(image, str):
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
imagePath = image
if self.iccOption == IccOption.TryFix:
@ -222,7 +224,7 @@ class OcrQueueModel(QAbstractListModel):
index = self.index(row, 0)
imagePath: str = index.data(self.ImagePathRole)
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)
chart, scoreInsert = processOcrResultFunc(imagePath, qImage, result)
@ -293,8 +295,8 @@ class OcrQueueModel(QAbstractListModel):
self.__items.pop(row)
self.endRemoveRows()
return
except Exception as e:
logger.exception(f"Error accepting {repr(item)}")
except Exception:
logger.exception("Error accepting %r", item)
return
def acceptItems(self, __rows: list[int], ignoreValidate: bool = False):
@ -343,13 +345,11 @@ class OcrQueueTableProxyModel(QAbstractTableModel):
def retranslateHeaders(self):
self.__horizontalHeaders = [
# fmt: off
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.select"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.imagePreview"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.chart"),
QCoreApplication.translate("OcrTableModel", "horizontalHeader.title.score"),
# fmt: on
]
] # fmt: skip
def sourceModel(self) -> OcrQueueModel:
return self.__sourceModel

View File

@ -1,39 +0,0 @@
from arcaea_offline.database import Database
from arcaea_offline.models import Chart
from arcaea_offline.searcher import Searcher
from arcaea_offline.utils.rating import rating_class_to_short_text
from PySide6.QtCore import Qt
from PySide6.QtGui import QStandardItem, QStandardItemModel
class SearchCompleterModel(QStandardItemModel):
def __init__(self, parent=None):
super().__init__(parent)
self.searcher = Searcher()
self.db = Database()
def updateSearcherSongs(self):
with self.db.sessionmaker() as session:
self.searcher.import_songs(session)
def getSearchResult(self, kw: str):
self.clear()
songIds = self.searcher.search(kw)
charts: list[Chart] = []
for songId in songIds:
_charts = self.db.get_charts_by_song_id(songId)
_charts = sorted(_charts, key=lambda c: c.rating_class, reverse=True)
charts += _charts
for chart in charts:
displayText = (
f"{chart.title} [{rating_class_to_short_text(chart.rating_class)}]"
)
item = QStandardItem(kw)
item.setData(kw)
item.setData(displayText, Qt.ItemDataRole.UserRole + 75)
item.setData(f"{chart.song_id}, {chart.set}", Qt.ItemDataRole.UserRole + 76)
item.setData(chart, Qt.ItemDataRole.UserRole + 10)
self.appendRow(item)

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

View File

@ -1,14 +1,4 @@
from typing import Type
from PySide6.QtCore import QObject, QUrl, Signal
from sqlalchemy import Engine
from sqlalchemy import create_engine as sa_create_engine
from sqlalchemy.pool import NullPool, Pool
def create_engine(_url: str | QUrl, pool: Type[Pool] = NullPool) -> Engine:
url = _url.toString() if isinstance(_url, QUrl) else _url
return sa_create_engine(url, poolclass=pool)
from PySide6.QtCore import QObject, Signal
class DatabaseUpdateSignals(QObject):

View File

@ -82,6 +82,7 @@ class ChartDelegate(TextSegmentDelegate):
QColor("#809955"),
QColor("#702d60"),
QColor("#710f25"),
QColor("#8b77a4"),
]
ChartInvalidBackgroundColor = QColor("#e6a23c")

View File

@ -16,13 +16,11 @@ class DbB30TableModel(DbTableModel):
def retranslateHeaders(self):
self._horizontalHeaders = [
# fmt: off
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.id"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.chart"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.score"),
QCoreApplication.translate("DbB30TableModel", "horizontalHeader.potential"),
# fmt: on
]
] # fmt: skip
def syncDb(self):
self.beginResetModel()
@ -39,7 +37,7 @@ class DbB30TableModel(DbTableModel):
(ScoreBest.song_id == Chart.song_id)
& (ScoreBest.rating_class == Chart.rating_class),
)
.order_by(ScoreBest.potential)
.order_by(ScoreBest.potential.desc())
.limit(50)
.all()
)

View File

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

View File

@ -1,111 +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",
"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"
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 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 PySide6.QtGui import QImage
logger = logging.getLogger(__name__)
from ui.extends.components.ocrQueue import OcrRunnable
logger = logging.getLogger(__name__)
class ChieriV4OcrRunnable(OcrRunnable):
def __init__(self, ocr: ChieriBotV4Ocr, component):

View File

@ -19,6 +19,7 @@ from arcaea_offline_ocr.phash_db import ImagePhashDatabase
from arcaea_offline_ocr.utils import imread_unicode
from PySide6.QtCore import QDateTime, QFileInfo
from core.settings import SettingsKeys, SettingsValues, settings
from ui.extends.components.ocrQueue import OcrRunnable
from ui.extends.shared.data import Data
@ -67,8 +68,14 @@ def getImageDate(imagePath: str) -> QDateTime:
if exifImage.has_exif and exifImage.get("datetime_original"):
datetimeStr = exifImage.get("datetime_original")
datetime = QDateTime.fromString(datetimeStr, "yyyy:MM:dd hh:mm:ss")
if not isinstance(datetime, QDateTime):
datetime = QFileInfo(imagePath).birthTime()
dateSource = settings.stringValue(SettingsKeys.Ocr.DateSource)
if dateSource == SettingsValues.Ocr.DateSource.FileLastModified:
datetime = QFileInfo(imagePath).lastModified()
else:
datetime = QFileInfo(imagePath).birthTime()
return datetime

View File

@ -41,7 +41,7 @@ class AndrealExecuteRunnable(QRunnable):
self.signals.completed.emit(self.jsonPath, imageBytes)
except Exception as e:
imageBytes = None
logger.exception(f"{self.__class__.__name__} error")
logger.exception("%s error", self.__class__.__name__)
self.signals.error.emit(self.jsonPath, str(e))
finally:
os.unlink(self.jsonPath)
@ -84,7 +84,10 @@ class AndrealHelper(QObject):
def request(self, jsonPath: str, arguments: list[str]):
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.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.QtWidgets import QFileDialog, QWidget
from core.settings import settings
from ui.designer.components.fileSelector_ui import Ui_FileSelector
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.extends.shared.settings import Settings
class FileSelector(Ui_FileSelector, QWidget):
@ -122,13 +122,13 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles:
return
if value := Settings().value(self.settingsKey):
if value := settings.value(self.settingsKey):
self.selectFile(value)
Settings().updated.connect(self.settingsUpdated)
settings.updated.connect(self.settingsUpdated)
def disconnectSettings(self):
Settings().updated.disconnect(self.settingsUpdated)
settings.updated.disconnect(self.settingsUpdated)
self.settingsKey = None
def settingsUpdated(self, key: str):
@ -139,4 +139,4 @@ class FileSelector(Ui_FileSelector, QWidget):
if self.__selectedFiles:
return
self.selectFile(Settings().value(self.settingsKey))
self.selectFile(settings.value(self.settingsKey))

View File

@ -2,7 +2,7 @@ from typing import Optional
from PySide6.QtCore import Qt, QTimer, Slot
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QButtonGroup, QWidget
from PySide6.QtWidgets import QWidget
from ui.designer.components.ocrQueue_ui import Ui_OcrQueue
from ui.extends.components.ocrQueue import (
@ -13,6 +13,7 @@ from ui.extends.components.ocrQueue import (
OcrScoreDelegate,
)
from ui.extends.shared.language import LanguageChangeEventFilter
from ui.implements.components.ocrQueueOptionsDialog import OcrQueueOptionsDialog
class OcrQueue(Ui_OcrQueue, QWidget):
@ -26,6 +27,9 @@ class OcrQueue(Ui_OcrQueue, QWidget):
self.__model: Optional[OcrQueueModel] = None
self.__tableProxyModel: Optional[OcrQueueTableProxyModel] = None
self.optionsDialog = OcrQueueOptionsDialog(self)
self.optionsDialog.iccOptionsChanged.connect(self.setIccOption)
self.__firstResizeDone = False
self.resizeTimer = QTimer(self)
self.resizeTimer.timeout.connect(self.tableView.resizeRowsToContents)
@ -41,13 +45,6 @@ class OcrQueue(Ui_OcrQueue, QWidget):
tableViewPalette.setColor(QPalette.ColorRole.Highlight, highlightColor)
self.tableView.setPalette(tableViewPalette)
self.iccOptionButtonGroup = QButtonGroup(self)
self.iccOptionButtonGroup.buttonToggled.connect(self.updateIccOption)
self.iccOptionButtonGroup.addButton(self.iccUseQtRadioButton, 0)
self.iccOptionButtonGroup.addButton(self.iccUsePILRadioButton, 1)
self.iccOptionButtonGroup.addButton(self.iccTryFixRadioButton, 2)
self.updateIccOption()
self.statusLabelClearTimer = QTimer(self)
self.statusLabelClearTimer.setSingleShot(True)
self.statusLabelClearTimer.timeout.connect(self.clearStatusMessage)
@ -93,9 +90,10 @@ class OcrQueue(Ui_OcrQueue, QWidget):
self.ocr_acceptAllButton.setEnabled(__bool)
self.ocr_ignoreValidateCheckBox.setEnabled(__bool)
def updateIccOption(self):
@Slot(int)
def setIccOption(self, option):
if self.model():
self.model().iccOption = self.iccOptionButtonGroup.checkedId()
self.model().iccOption = option
def showStatusMessage(self, message: str):
self.statusLabel.setText(message)
@ -131,6 +129,10 @@ class OcrQueue(Ui_OcrQueue, QWidget):
def modelReseted(self):
self.progressBar.setMaximum(0)
@Slot()
def on_optionsDialogButton_clicked(self):
self.optionsDialog.exec()
@Slot()
def on_ocr_removeSelectedButton_clicked(self):
if self.model():

View File

@ -0,0 +1,49 @@
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QButtonGroup, QDialog
from core.settings import SettingsKeys, SettingsValues, settings
from ui.designer.components.ocrQueueOptionsDialog_ui import Ui_OcrQueueOptionsDialog
class OcrQueueOptionsDialog(QDialog, Ui_OcrQueueOptionsDialog):
iccOptionsChanged = Signal(int)
def __init__(self, parent=None):
super(OcrQueueOptionsDialog, self).__init__(parent)
self.setupUi(self)
self.iccOptionButtonGroup = QButtonGroup(self)
self.iccOptionButtonGroup.buttonToggled.connect(
lambda: self.iccOptionsChanged.emit(self.iccOptionButtonGroup.checkedId())
)
self.iccOptionButtonGroup.addButton(self.iccUseQtRadioButton, 0)
self.iccOptionButtonGroup.addButton(self.iccUsePILRadioButton, 1)
self.iccOptionButtonGroup.addButton(self.iccTryFixRadioButton, 2)
self.scoreDateSourceButtonGroup = QButtonGroup(self)
self.scoreDateSourceButtonGroup.addButton(
self.dateUseCreationDateRadioButton, 0
)
self.scoreDateSourceButtonGroup.addButton(self.dateUseModifyDateRadioButton, 1)
self.scoreDateSourceButtonGroup.buttonClicked.connect(
self.on_scoreDateSourceButtonGroup_buttonClicked
)
settings.updated.connect(self.syncCheckboxesFromSettings)
self.syncCheckboxesFromSettings()
def syncCheckboxesFromSettings(self):
scoreDateSource = settings.stringValue(SettingsKeys.Ocr.DateSource)
if scoreDateSource == SettingsValues.Ocr.DateSource.FileLastModified:
self.dateUseModifyDateRadioButton.setChecked(True)
else:
self.dateUseCreationDateRadioButton.setChecked(True)
def on_scoreDateSourceButtonGroup_buttonClicked(self, button):
buttonId = self.scoreDateSourceButtonGroup.id(button)
if buttonId == 1:
value = SettingsValues.Ocr.DateSource.FileLastModified
else:
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.QtWidgets import QGraphicsColorizeEffect, QRadioButton
from ui.extends.shared.color import mix_color
from core.color import mixColor
STYLESHEET = """
QRadioButton {{
@ -40,7 +40,7 @@ class RatingClassRadioButton(QRadioButton):
def setColors(self, dark_color: QColor, text_color: QColor):
self._dark_color = dark_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()
def isColorsSet(self) -> bool:

View File

@ -1,3 +1,4 @@
import logging
from typing import Type
from PySide6.QtCore import Signal
@ -6,6 +7,8 @@ from PySide6.QtWidgets import QHBoxLayout, QSizePolicy, QVBoxLayout, QWidget
from ui.implements.components.ratingClassRadioButton import RatingClassRadioButton
logger = logging.getLogger(__name__)
class RatingClassSelector(QWidget):
valueChanged = Signal()
@ -41,16 +44,30 @@ class RatingClassSelector(QWidget):
self.bydButton.setAutoExclusive(False)
self.preferredLayout.addWidget(self.bydButton)
self.buttons = [self.pstButton, self.prsButton, self.ftrButton, self.bydButton]
self.etrButton = RatingClassRadioButton(self)
self.etrButton.setObjectName("etrButton")
self.etrButton.setText("ETERNAL")
self.etrButton.setAutoExclusive(False)
self.preferredLayout.addWidget(self.etrButton)
self.buttons = [
self.pstButton,
self.prsButton,
self.ftrButton,
self.bydButton,
self.etrButton,
]
self.pstButton.setColors(QColor("#399bb2"), QColor("#f0f8fa"))
self.prsButton.setColors(QColor("#809955"), QColor("#f7f9f4"))
self.ftrButton.setColors(QColor("#702d60"), QColor("#f7ebf4"))
self.bydButton.setColors(QColor("#710f25"), QColor("#f9ced8"))
self.etrButton.setColors(QColor("#4f2c7a"), QColor("#e4daf1"))
self.pstButton.clicked.connect(self.select)
self.prsButton.clicked.connect(self.select)
self.ftrButton.clicked.connect(self.select)
self.bydButton.clicked.connect(self.select)
self.etrButton.clicked.connect(self.select)
self.reset()
self.setButtonsEnabled([])
@ -106,9 +123,10 @@ class RatingClassSelector(QWidget):
if ratingClass is None or isinstance(ratingClass, bool):
button = self.sender()
elif ratingClass in range(4):
elif ratingClass in range(len(self.buttons)):
button = self.buttons[ratingClass]
else:
logger.debug("Cannot select ratingClass=%s, condition check failed", ratingClass)
return
if not button.isEnabled():

View File

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

View File

@ -1,14 +1,18 @@
import dataclasses
import logging
import re
from enum import IntEnum
from typing import Any
from arcaea_offline.database import Database
from arcaea_offline.models import Chart
from PySide6.QtCore import QModelIndex, QSignalMapper, Qt, Signal, Slot
from PySide6.QtWidgets import QCompleter, QWidget
from arcaea_offline.searcher import Searcher
from arcaea_offline.utils.rating import rating_class_to_short_text
from PySide6.QtCore import QModelIndex, QObject, QSignalMapper, Qt, Signal, Slot
from PySide6.QtGui import QStandardItem, QStandardItemModel
from PySide6.QtWidgets import QComboBox, QCompleter, QWidget
from ui.designer.components.songIdSelector_ui import Ui_SongIdSelector
from ui.extends.components.songIdSelector import SearchCompleterModel
from ui.extends.shared.database import databaseUpdateSignals
from ui.extends.shared.delegates.descriptionDelegate import DescriptionDelegate
from ui.extends.shared.language import LanguageChangeEventFilter
@ -21,6 +25,168 @@ class SongIdSelectorMode(IntEnum):
Chart = 1
# region logics
@dataclasses.dataclass
class _ComboBoxItem:
text: str
userData: Any
additionalData: dict[int, Any]
def apply(self, comboBox: QComboBox):
comboBox.addItem(self.text, self.userData)
index = comboBox.findText(self.text)
if index > -1:
for role, value in self.additionalData.items():
comboBox.setItemData(index, value, role)
class SearchCompleterModel(QStandardItemModel):
ChartRole = Qt.ItemDataRole.UserRole + 10
def __init__(self, parent=None):
super().__init__(parent)
self.searcher = Searcher()
self.db = Database()
def updateSearcherSongs(self):
with self.db.sessionmaker() as session:
self.searcher.import_songs(session)
def getSearchResult(self, kw: str):
self.clear()
songIds = self.searcher.search(kw)
charts: list[Chart] = []
for songId in songIds:
_charts = self.db.get_charts_by_song_id(songId)
_charts = sorted(_charts, key=lambda c: c.rating_class, reverse=True)
charts += _charts
for chart in charts:
displayText = (
f"{chart.title} [{rating_class_to_short_text(chart.rating_class)}]"
)
item = QStandardItem(kw)
item.setData(kw)
item.setData(displayText, DescriptionDelegate.MainTextRole)
item.setData(
f"{chart.song_id}, {chart.set}", DescriptionDelegate.DescriptionTextRole
)
item.setData(chart, self.ChartRole)
self.appendRow(item)
class SongIdSelectorViewModel(QObject):
packComboBoxItemsReady = Signal()
songIdComboBoxItemsReady = Signal()
def __init__(self, parent=None, *, db: Database):
super().__init__(parent)
if not isinstance(db, Database):
raise TypeError(
"`db` should be an instance of arcaea_offline.database.Database"
)
self.__db: Database = None # type: ignore
self.__mode = SongIdSelectorMode.SongId
self.__packComboBoxItems: list[_ComboBoxItem] = []
self.__songIdComboBoxItems: list[_ComboBoxItem] = []
self.setDatabase(db)
@property
def packComboBoxItems(self):
return self.__packComboBoxItems
@property
def songIdComboBoxItems(self):
return self.__songIdComboBoxItems
@property
def mode(self):
return self.__mode
@mode.setter
def mode(self, value):
if not isinstance(value, SongIdSelectorMode):
raise TypeError("value is not SongIdSelectorMode")
self.__mode = value
def setDatabase(self, db: Database):
self.__db = db
def updatePackComboBoxItems(self):
packs = self.__db.get_packs()
self.__packComboBoxItems.clear()
for pack in packs:
if re.search(r"_append_.*$", pack.id):
basePackId = re.sub(r"_append_.*$", "", pack.id)
basePackName = self.__db.get_pack(basePackId).name # type: ignore
packName = f"{basePackName} - {pack.name}"
else:
packName = pack.name
self.__packComboBoxItems.append(
_ComboBoxItem(
text=f"{packName} ({pack.id})",
userData=pack.id,
additionalData={
DescriptionDelegate.MainTextRole: packName,
DescriptionDelegate.DescriptionTextRole: pack.id,
},
)
)
self.packComboBoxItemsReady.emit()
def updateSongIdComboBoxItems(self, packId: str):
self.__songIdComboBoxItems.clear()
items = []
if self.mode == SongIdSelectorMode.SongId:
items = self.__db.get_songs_by_pack_id(packId)
elif self.mode == SongIdSelectorMode.Chart:
items = self.__db.get_charts_by_pack_id(packId)
else:
assert not "reachable"
insertedSongIds = []
for item in items:
if self.mode == SongIdSelectorMode.SongId:
itemId = item.id # type: ignore
elif self.mode == SongIdSelectorMode.Chart:
itemId = item.song_id # type: ignore
else:
continue
if itemId not in insertedSongIds:
self.__songIdComboBoxItems.append(
_ComboBoxItem(
text=f"{item.title} ({itemId})",
userData=itemId,
additionalData={
DescriptionDelegate.MainTextRole: item.title,
DescriptionDelegate.DescriptionTextRole: itemId,
},
)
)
insertedSongIds.append(itemId)
self.songIdComboBoxItemsReady.emit()
# endregion
class SongIdSelector(Ui_SongIdSelector, QWidget):
valueChanged = Signal()
quickSearchActivated = Signal(Chart)
@ -33,6 +199,10 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.vm = SongIdSelectorViewModel(self, db=self.db)
self.vm.packComboBoxItemsReady.connect(self.fillPackComboBox)
self.vm.songIdComboBoxItemsReady.connect(self.fillSongIdComboBox)
# quick switch bindings
self.quickSwitchSignalMapper = QSignalMapper(self)
self.previousPackageButton.clicked.connect(self.quickSwitchSignalMapper.map)
@ -75,6 +245,7 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
databaseUpdateSignals.songAddOrDelete.connect(self.updateDatabase)
def setMode(self, mode: SongIdSelectorMode):
self.vm.mode = mode
self.mode = mode
@Slot(str)
@ -116,7 +287,7 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
pack = self.packComboBox.currentData()
songId = self.songIdComboBox.currentData()
self.fillPackComboBox()
self.vm.updatePackComboBoxItems()
if pack:
self.selectPack(pack)
@ -125,58 +296,24 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
def fillPackComboBox(self):
self.packComboBox.clear()
packs = self.db.get_packs()
for pack in packs:
if isAppendPack := re.search(r"_append_.*$", pack.id):
basePackId = re.sub(r"_append_.*$", "", pack.id)
basePackName = self.db.get_pack(basePackId).name
packName = f"{basePackName} - {pack.name}"
else:
packName = pack.name
self.packComboBox.addItem(f"{packName} ({pack.id})", pack.id)
row = self.packComboBox.count() - 1
self.packComboBox.setItemData(
row, packName, DescriptionDelegate.MainTextRole
)
self.packComboBox.setItemData(
row, pack.id, DescriptionDelegate.DescriptionTextRole
)
for item in self.vm.packComboBoxItems:
item.apply(self.packComboBox)
self.packComboBox.setCurrentIndex(-1)
def fillSongIdComboBox(self):
self.songIdComboBox.clear()
if packId := self.packComboBox.currentData():
if self.mode == SongIdSelectorMode.SongId:
items = self.db.get_songs_by_pack_id(packId)
elif self.mode == SongIdSelectorMode.Chart:
items = self.db.get_charts_by_pack_id(packId)
else:
raise ValueError("Unknown SongIdSelectorMode.")
insertedSongIds = []
for item in items:
if self.mode == SongIdSelectorMode.SongId:
itemId = item.id
elif self.mode == SongIdSelectorMode.Chart:
itemId = item.song_id
else:
continue
if itemId not in insertedSongIds:
self.songIdComboBox.addItem(f"{item.title} ({itemId})", itemId)
insertedSongIds.append(itemId)
row = self.songIdComboBox.count() - 1
self.songIdComboBox.setItemData(
row, item.title, DescriptionDelegate.MainTextRole
)
self.songIdComboBox.setItemData(
row, itemId, DescriptionDelegate.DescriptionTextRole
)
for item in self.vm.songIdComboBoxItems:
item.apply(self.songIdComboBox)
self.songIdComboBox.setCurrentIndex(-1)
@Slot()
def on_packComboBox_currentIndexChanged(self):
self.fillSongIdComboBox()
if packId := self.packComboBox.currentData():
self.vm.updateSongIdComboBoxItems(packId)
@Slot(str)
def on_searchLineEdit_textChanged(self, text: str):
@ -189,10 +326,10 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
packIdIndex = self.packComboBox.findData(packId)
if packIdIndex > -1:
self.packComboBox.setCurrentIndex(packIdIndex)
self.fillSongIdComboBox()
self.vm.updateSongIdComboBoxItems(packId)
return True
else:
logger.warning(f'Attempting to select an unknown pack "{packId}"')
logger.warning("Attempting to select an unknown pack [%s]", packId)
return False
def selectSongId(self, songId: str) -> bool:
@ -202,7 +339,8 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
return True
else:
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
@ -213,7 +351,7 @@ class SongIdSelector(Ui_SongIdSelector, QWidget):
@Slot(QModelIndex)
def searchCompleterSetSelection(self, index: QModelIndex):
chart: Chart = index.data(Qt.ItemDataRole.UserRole + 10)
chart: Chart = index.data(SearchCompleterModel.ChartRole)
self.selectChart(chart)
self.quickSearchActivated.emit(chart)

View File

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

View File

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

View File

@ -1,6 +1,4 @@
import sys
from PySide6.QtCore import QCoreApplication, QDir, QLocale, QProcess
from PySide6.QtCore import QCoreApplication, QDir, QLocale
from PySide6.QtWidgets import (
QApplication,
QCheckBox,
@ -10,8 +8,8 @@ from PySide6.QtWidgets import (
QPushButton,
)
from core.settings import SettingsKeys, settings
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
@ -33,8 +31,8 @@ class SettingsGeneral(SettingsBaseWidget):
self.languageFollowSystemCheckBox.toggled.connect(
self.changeLanguageFollowSystem
)
if self.settings.language():
locale = QLocale(self.settings.language())
if language := settings.stringValue(SettingsKeys.General.Language):
locale = QLocale(language)
index = self.languageValueWidget.findData(locale)
if index > -1:
self.languageValueWidget.setCurrentIndex(index)
@ -51,7 +49,7 @@ class SettingsGeneral(SettingsBaseWidget):
self.insertItem(
"dbUrl",
self.dbUrlLabel,
QLabel(self.settings.databaseUrl()),
QLabel(settings.stringValue(SettingsKeys.General.DatabaseUrl)),
self.dbUrlResetButton,
)
@ -59,13 +57,13 @@ class SettingsGeneral(SettingsBaseWidget):
locale = self.languageValueWidget.currentData()
if locale:
changeAppLanguage(locale)
self.settings.setLanguage(localeToCode(locale))
settings.setValue(SettingsKeys.General.Language, localeToCode(locale))
def changeLanguageFollowSystem(self):
followSystem = self.languageFollowSystemCheckBox.isChecked()
self.languageValueWidget.setCurrentIndex(-1)
if followSystem:
self.settings.remove(LANGUAGE)
settings.remove(SettingsKeys.General.Language)
changeAppLanguage(QLocale.system())
self.languageValueWidget.setEnabled(False)
else:
@ -80,7 +78,7 @@ class SettingsGeneral(SettingsBaseWidget):
QMessageBox.StandardButton.No,
)
if userConfirm == QMessageBox.StandardButton.Yes:
self.settings.remove(DATABASE_URL)
settings.remove(SettingsKeys.General.DatabaseUrl)
QApplication.instance().quit()
def setupUi(self, *args):
@ -99,10 +97,10 @@ class SettingsGeneral(SettingsBaseWidget):
# fmt: off
self.setTitle(QCoreApplication.translate("Settings", "general.title"))
self.languageLabel.setText(QCoreApplication.translate("Settings", "general.language.label"))
self.languageFollowSystemCheckBox.setText(QCoreApplication.translate("Settings", "general.language.followSystem"))
self.dbUrlLabel.setText(QCoreApplication.translate("Settings", "general.dbUrl.label"))
self.dbUrlResetButton.setText(QCoreApplication.translate("Settings", "resetButton"))
# fmt: on

View File

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

View File

@ -163,19 +163,15 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
QMessageBox.critical(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"),
# fmt: on
)
) # fmt: skip
return
if not self.constantLineEdit.hasAcceptableInput():
QMessageBox.critical(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.constantRequired"),
# fmt: on
)
) # fmt: skip
return
constant = int(self.constantLineEdit.text())
@ -202,10 +198,8 @@ class TabDb_ChartInfoEditor(Ui_TabDb_ChartInfoEditor, QWidget):
QMessageBox.critical(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "commit.chartNotSelected"),
# fmt: on
)
) # fmt: skip
return
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(
self,
None,
# fmt: off
QCoreApplication.translate("TabDb_ChartInfoEditor", "deleteConfirm"),
# fmt: on
QMessageBox.StandardButton.Yes,
QMessageBox.StandardButton.No,
)
) # fmt: skip
if result == QMessageBox.StandardButton.Yes:
with self.db.sessionmaker() as session:
session.delete(chartInfo)

View File

@ -14,9 +14,10 @@ from arcaea_offline.external.arcaea import (
)
from arcaea_offline.external.arcaea.common import ArcaeaParser
from arcaea_offline.external.arcsong import ArcsongDbParser
from arcaea_offline.external.chart_info_db import ChartInfoDbParser
from arcaea_offline.external.smartrte import SmartRteB30CsvConverter
from arcaea_offline.models import Difficulty, Pack, Song
from PySide6.QtCore import QDir, Slot
from PySide6.QtCore import QDateTime, QDir, Slot
from PySide6.QtWidgets import QFileDialog, QMessageBox, QWidget
from ui.designer.tabs.tabDb.tabDb_Manage_ui import Ui_TabDb_Manage
@ -57,6 +58,29 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
self, "Sync Error", "\n".join(traceback.format_exception(e))
)
@Slot()
def on_syncChartInfoDbButton_clicked(self):
dbFile, filter = QFileDialog.getOpenFileName(
self, None, "", "DB File (*.db);;*"
)
if not dbFile:
return
try:
db = Database()
parser = ChartInfoDbParser(dbFile)
with db.sessionmaker() as session:
parser.write_database(session)
session.commit()
databaseUpdateSignals.chartInfoUpdated.emit()
QMessageBox.information(self, None, "OK")
except Exception as e:
logging.exception("Sync chart info database error")
QMessageBox.critical(
self, "Sync Error", "\n".join(traceback.format_exception(e))
)
def importFromArcaeaParser(
self, parser: ArcaeaParser, instance, logName, path
) -> int:
@ -67,7 +91,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
session.commit()
databaseUpdateSignals.songAddOrDelete.emit()
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
def importPacklist(self, packlistPath):
@ -137,7 +161,7 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
return
try:
logger.info(f"Importing {apkFile}")
logger.info("Importing %s", apkFile)
with zipfile.ZipFile(apkFile) as zf:
packlistPath = zipfile.Path(zf) / "assets" / "songs" / "packlist"
@ -169,7 +193,9 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database()
parser = St3ScoreParser(dbFile)
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:
parser.write_database(session)
@ -194,7 +220,9 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
db = Database()
parser = ArcaeaOnlineParser(apiResultFile)
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:
parser.write_database(session)
@ -208,14 +236,14 @@ class TabDb_Manage(Ui_TabDb_Manage, QWidget):
@Slot()
def on_exportScoresButton_clicked(self):
scores = Database().export_scores()
version = Database().version()
scores = Database().export_scores_def_v2()
timestamp = QDateTime.currentMSecsSinceEpoch()
content = json.dumps(scores, ensure_ascii=False)
exportLocation, _filter = QFileDialog.getSaveFileName(
self,
"Save your scores to...",
QDir.current().filePath(f"arcaea-offline-scores-v{version}.json"),
QDir.current().filePath(f"arcaea-offline-def-v2-scores-{timestamp}.json"),
"JSON (*.json);;*",
)
with open(exportLocation, "w", encoding="utf-8") as f:

View File

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

View File

@ -9,6 +9,7 @@ from PIL import Image
from PySide6.QtCore import Signal, Slot
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.extends.components.ocrQueue import OcrQueueModel
from ui.extends.ocr.dependencies import (
@ -16,11 +17,6 @@ from ui.extends.ocr.dependencies import (
getPhashDatabaseStatusText,
)
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
logger = logging.getLogger(__name__)
@ -55,9 +51,15 @@ class TabOcr_B30(Ui_TabOcr_B30, QWidget):
self.ocr = None
logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE)
self.dependencies_b30KnnModelSelector.connectSettings(B30_KNN_MODEL_FILE)
self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE)
self.dependencies_knnModelSelector.connectSettings(
SettingsKeys.Ocr.KnnModelFile
)
self.dependencies_b30KnnModelSelector.connectSettings(
SettingsKeys.Ocr.B30KnnModelFile
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.ocrQueueModel = OcrQueueModel(self)
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.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.extends.components.ocrQueue import OcrQueueModel
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
logger = logging.getLogger(__name__)
@ -54,8 +54,12 @@ class TabOcr_Device(Ui_TabOcr_Device, QWidget):
)
logger.info("Applying settings...")
self.dependencies_knnModelSelector.connectSettings(KNN_MODEL_FILE)
self.dependencies_phashDatabaseSelector.connectSettings(PHASH_DATABASE_FILE)
self.dependencies_knnModelSelector.connectSettings(
SettingsKeys.Ocr.KnnModelFile
)
self.dependencies_phashDatabaseSelector.connectSettings(
SettingsKeys.Ocr.PhashDatabaseFile
)
self.options_usePresetCheckBox.setChecked(True)
self.options_usePresetCheckBox.setEnabled(False)

View File

@ -1,55 +0,0 @@
import logging
from arcaea_offline.database import Database
from PySide6.QtCore import QCoreApplication
from PySide6.QtGui import QShowEvent
from PySide6.QtWidgets import QWidget
from ui.designer.tabs.tabOverview_ui import Ui_TabOverview
from ui.extends.shared.language import LanguageChangeEventFilter
logger = logging.getLogger(__name__)
class TabOverview(Ui_TabOverview, QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.languageChangeEventFilter = LanguageChangeEventFilter(self)
self.installEventFilter(self.languageChangeEventFilter)
self.db = Database()
def showEvent(self, event: QShowEvent) -> None:
self.updateOverview()
return super().showEvent(event)
def updateOverview(self):
try:
b30 = self.db.get_b30() or 0.00
self.b30Label.setText(str(f"{b30:.3f}"))
except Exception:
logger.exception("Cannot get b30:")
self.b30Label.setText("ERR")
try:
self.databaseDescribeLabel.setText(
self.describeFormatString.format(
self.db.count_packs(),
self.db.count_songs(),
self.db.count_difficulties(),
self.db.count_chart_infos(),
self.db.count_complete_chart_infos(),
self.db.count_scores(),
)
)
except Exception:
logger.exception("Cannot update overview:")
self.databaseDescribeLabel.setText("ERR")
def retranslateUi(self, *args):
super().retranslateUi(self)
# fmt: off
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.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.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.implements.components.chartSelector import ChartSelector
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.andrealExecutableSelector.filesSelected.connect(self.setHelperPaths)
self.andrealFolderSelector.connectSettings(ANDREAL_FOLDER)
self.andrealExecutableSelector.connectSettings(ANDREAL_EXECUTABLE)
self.andrealFolderSelector.connectSettings(SettingsKeys.Andreal.Folder)
self.andrealExecutableSelector.connectSettings(SettingsKeys.Andreal.Executable)
self.generatePreviewButton.clicked.connect(self.requestPreview)
self.generateImageButton.clicked.connect(self.requestGenerate)
@ -131,13 +131,8 @@ class TabTools_Andreal(Ui_TabTools_Andreal, QWidget):
@Slot()
def on_imageTypeWhatIsThisButton_clicked(self):
QMessageBox.information(
self,
None,
# fmt: off
QCoreApplication.translate("TabTools_Andreal", "imageWhatIsThisDialog.description"),
# fmt: on
)
message = QCoreApplication.translate("TabTools_Andreal", "imageWhatIsThisDialog.description") # fmt: skip
QMessageBox.information(self, None, message)
def imageFormat(self):
buttonId = self.imageFormatButtonGroup.checkedId()

View File

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

22
ui/qmls/404.qml Normal file
View File

@ -0,0 +1,22 @@
import QtQuick
import QtQuick.Controls
Page {
Column {
anchors.centerIn: parent
width: parent.width
Label {
width: parent.width
text: '?'
font.pointSize: 50
horizontalAlignment: Qt.AlignCenter
}
Label {
width: parent.width
text: 'Placeholder page'
horizontalAlignment: Qt.AlignCenter
}
}
}

63
ui/qmls/App.qml Normal file
View File

@ -0,0 +1,63 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "./DatabaseChecker" as DatabaseChecker
ApplicationWindow {
visible: true
width: 800
height: 600
SystemPalette {
id: systemPaletteActive
colorGroup: SystemPalette.Active
}
SystemPalette {
id: systemPaletteDisabled
colorGroup: SystemPalette.Disabled
}
palette {
accent: systemPaletteActive.accent
alternateBase: systemPaletteActive.alternateBase
base: systemPaletteActive.base
button: systemPaletteActive.button
buttonText: systemPaletteActive.buttonText
dark: systemPaletteActive.dark
highlight: systemPaletteActive.highlight
highlightedText: systemPaletteActive.highlightedText
light: systemPaletteActive.light
mid: systemPaletteActive.mid
midlight: systemPaletteActive.midlight
placeholderText: systemPaletteActive.placeholderText
shadow: systemPaletteActive.shadow
text: systemPaletteActive.text
window: systemPaletteActive.window
windowText: systemPaletteActive.windowText
disabled {
button: systemPaletteDisabled.button
buttonText: systemPaletteDisabled.buttonText
}
inactive {
button: systemPaletteDisabled.button
buttonText: systemPaletteDisabled.buttonText
}
}
StackLayout {
id: stackLayout
anchors.fill: parent
currentIndex: 0
DatabaseChecker.Index {
onReady: parent.currentIndex = 1
}
AppMain {}
}
}

71
ui/qmls/AppMain.qml Normal file
View File

@ -0,0 +1,71 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
RowLayout {
id: layout
spacing: 5
ListModel {
id: navListModel
ListElement {
_id: 'home'
label: 'Overview'
qmlSource: 'Overview.qml'
}
ListElement {
_id: 'database'
label: 'Database'
qmlSource: '404.qml'
}
}
ListView {
id: navListView
Layout.preferredWidth: 200
Layout.fillHeight: true
model: navListModel
focus: true
delegate: Item {
id: navListItem
required property int index
required property string label
width: parent.width
height: 30
MouseArea {
anchors.fill: parent
onClicked: () => {
navListView.currentIndex = navListItem.index;
}
}
Label {
anchors.margins: 5
anchors.fill: parent
text: parent.label
}
}
highlight: Rectangle {
width: parent.width
height: 30
color: "#FFFF88"
y: ListView.view.currentItem.y
}
}
Loader {
Layout.preferredWidth: 500
Layout.fillWidth: true
Layout.fillHeight: true
source: navListView.currentIndex > -1 ? navListModel.get(navListView.currentIndex).qmlSource : '404.qml'
}
}

View File

@ -0,0 +1,25 @@
import QtQuick
import QtQuick.Dialogs
import internal.ui.utils
SelectorBase {
id: base
FolderDialog {
id: folderDialog
selectedFolder: base.directoryUrl
onAccepted: {
base.directoryUrl = this.selectedFolder;
this.currentFolder = this.selectedFolder;
}
}
property alias directoryUrl: base.url
shouldAcceptUrl: url => UrlUtils.isDir(url)
onBrowseButtonClicked: {
folderDialog.open();
}
placeholderText: '<font color="gray">Select a directory…</font>'
}

View File

@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Dialogs
import internal.ui.utils
SelectorBase {
id: base
FileDialog {
id: fileDialog
onAccepted: {
base.url = this.selectedFile;
}
}
function isFileUrlValid(url: url): bool {
return url.toString().startsWith("file://");
}
property alias fileUrl: base.url
onFileUrlChanged: {
if (isFileUrlValid(fileUrl)) {
fileDialog.selectedFile = fileUrl;
fileDialog.currentFolder = UrlUtils.parent(fileUrl);
}
}
shouldAcceptUrl: url => UrlUtils.isFile(url)
onBrowseButtonClicked: {
fileDialog.open();
}
placeholderText: '<font color="gray">Select a file…</font>'
}

View File

@ -0,0 +1,13 @@
import QtQuick.Controls
import QtQuick.Layouts
Label {
Layout.topMargin: 7
Layout.bottomMargin: 10
anchors.topMargin: 7
anchors.bottomMargin: 10
font.pointSize: 14
font.bold: true
}

View File

@ -0,0 +1,64 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import internal.ui.utils
RowLayout {
id: root
required property var shouldAcceptUrl // function (url)
signal browseButtonClicked
property string placeholderText: '<font color="gray">…</font>'
required property url url
onUrlChanged: {
Qt.callLater(() => {
updateLabel();
});
}
function updateLabel(): void {
urlLabel.text = url.toString().length > 0 ? UrlFormatUtils.toLocalFile(url) : root.placeholderText;
}
spacing: 2
Label {
id: urlLabel
Layout.fillWidth: true
text: root.placeholderText
DropArea {
anchors.fill: parent
onEntered: drag => {
if (!drag.hasUrls || drag.urls.length !== 1) {
drag.accepted = false;
return false;
}
const url = drag.urls[0];
const shouldAccept = root.shouldAcceptUrl(url);
if (!shouldAccept) {
drag.accepted = false;
return false;
}
urlLabel.text = `<font color="gray">Drop "<font color="text">${UrlUtils.name(url)}</font>"?</font>`;
}
onExited: {
root.updateLabel();
}
onDropped: drop => {
root.url = drop.urls[0];
root.updateLabel();
}
}
}
Button {
text: "Browse"
onClicked: root.browseButtonClicked()
}
}

View File

@ -0,0 +1,6 @@
module Components
internal SelectorBase SelectorBase.qml
DirectorySelector 1.0 DirectorySelector.qml
FileSelector 1.0 FileSelector.qml
SectionTitle 1.0 SectionTitle.qml

View File

@ -0,0 +1,56 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../Components"
ColumnLayout {
id: root
signal confirm
required property url directoryUrl
required property string filename
GridLayout {
columns: 2
Label {
text: "Directory"
}
DirectorySelector {
Layout.fillWidth: true
directoryUrl: root.directoryUrl
onDirectoryUrlChanged: {
root.directoryUrl = this.directoryUrl;
}
}
Label {
text: "Filename"
}
TextField {
Layout.fillWidth: true
text: root.filename
placeholderText: 'Please enter…'
onEditingFinished: {
root.filename = this.text;
}
onAccepted: {
confirmButton.click();
}
}
}
Button {
id: confirmButton
Layout.alignment: Qt.AlignRight
text: 'Confirm'
onClicked: root.confirm()
}
}

View File

@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Dialog {
id: root
required property string databaseUrl
padding: 10
title: qsTr('Confirm Database Connection')
standardButtons: Dialog.Ok | Dialog.Cancel
modal: true
Overlay.modal: Rectangle {
color: Qt.alpha('gray', 0.2)
}
ColumnLayout {
Label {
text: 'The connection below will be saved to settings. Confirm?'
}
Pane {
Label {
id: databaseUrlLabel
text: root.databaseUrl
}
}
}
}

View File

@ -0,0 +1,34 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Dialog {
id: root
required property string errorTitle
required property string errorMessage
padding: 10
title: qsTr('Error')
standardButtons: Dialog.Ok
modal: true
Overlay.modal: Rectangle {
color: Qt.alpha('darkgray', 0.5)
}
ColumnLayout {
spacing: 5
Label {
font.pointSize: 12
font.bold: true
text: root.errorTitle
}
Label {
text: root.errorMessage
}
}
}

View File

@ -0,0 +1,13 @@
import QtQuick
import QtQuick.Layouts
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.topMargin: 5
Layout.bottomMargin: 5
color: "lightgray"
opacity: 0.5
}

View File

@ -0,0 +1,124 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import internal.ui.vm 1.0
import "../Components"
Page {
id: root
property bool showConfirmConnectionDialog: false
signal ready
function confirm(source: string): void {
if (!source)
throw new Error("source is required");
const shouldConfirm = (source === 'continueButton' && vm.canConfirmSilently) || source === 'dialog';
if (shouldConfirm) {
vm.confirmCurrentConnection();
root.ready();
} else {
root.showConfirmConnectionDialog = true;
}
}
DatabaseInitViewModel {
id: vm
onSelectFileUrlChanged: {
selectOrCreate.selectFileUrl = this.selectFileUrl;
}
onDirectoryUrlChanged: {
selectOrCreate.directoryUrl = this.directoryUrl;
}
onFilenameChanged: {
selectOrCreate.filename = this.filename;
}
onDatabaseUrlChanged: {
confirmConnectionDialog.databaseUrl = this.databaseUrl;
}
onDatabaseInfoChanged: {
databaseInfo.info = this.databaseInfo;
}
onCanContinueChanged: {
continueButton.enabled = this.canContinue;
}
}
Page {
padding: 10
anchors.fill: parent
Dialog_ConfirmConnection {
id: confirmConnectionDialog
anchors.centerIn: parent
visible: root.showConfirmConnectionDialog
onAccepted: root.confirm('dialog')
onClosed: root.showConfirmConnectionDialog = false
databaseUrl: vm.databaseUrl
}
ColumnLayout {
width: parent.width
spacing: 2
SectionTitle {
text: qsTr('Select or Create Database')
}
Section_SelectOrCreate {
id: selectOrCreate
selectFileUrl: vm.selectFileUrl
createDirectoryUrl: vm.directoryUrl
createFilename: vm.filename
onUiModeChanged: uiMode => vm.uiMode = uiMode
onSelectFileUrlChanged: {
vm.selectFileUrl = selectFileUrl;
}
onCreateDirectoryUrlChanged: {
vm.directoryUrl = createDirectoryUrl;
}
onCreateFilenameChanged: {
vm.filename = createFilename;
}
onConfirmCreate: {
vm.loadDatabaseInfo();
}
}
HorizontalDivider {}
SectionTitle {
text: 'Database Status'
}
Section_DatabaseInfo {
id: databaseInfo
info: vm.databaseInfo
}
}
footer: Pane {
padding: 5
RowLayout {
Button {
id: continueButton
Layout.alignment: Qt.AlignHCenter
text: qsTr('Continue')
enabled: vm.canContinue
onClicked: root.confirm('continueButton')
}
}
}
}
}

View File

@ -0,0 +1,137 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
ColumnLayout {
id: root
property var info: {
'url': undefined,
'error': {
'title': undefined,
'message': undefined
},
'initialized': undefined,
'version': undefined
}
property bool showErrorDialog: false
function hasError(): bool {
return info?.error?.title !== undefined || info?.error?.message !== undefined;
}
function displayText(value): string {
return value ?? '-';
}
function displayBool(value): string {
if (value === undefined)
return '-';
return value ? `<font color="${appTheme.success}">Yes</font>` : `<font color="${appTheme.error}">No</font>`;
}
component LabelLabel: Label {
Layout.alignment: Qt.AlignRight | Qt.AlignBaseline
font.pointSize: 10
}
SystemPalette {
id: palette
}
Dialog_Error {
parent: Overlay.overlay
anchors.centerIn: parent
visible: root.hasError() && root.showErrorDialog
errorTitle: root.displayText(root.info?.error?.title)
errorMessage: root.displayText(root.info?.error?.message)
onClosed: root.showErrorDialog = false
}
Pane {
clip: true
background: Rectangle {
color: Qt.darker(Qt.alpha(palette.window, 0.9), 0.2)
}
// Layout.preferredHeight: root.hasError() ? this.implicitHeight : 0
Layout.preferredHeight: 0
Behavior on Layout.preferredHeight {
PropertyAnimation {
duration: 300
easing.type: Easing.InOutCubic
}
}
ColumnLayout {
anchors.fill: parent
Label {
font.bold: true
text: root.displayText(root.info.error?.title)
}
Label {
text: root.displayText(root.info.error?.message)
}
}
}
GridLayout {
columns: 2
columnSpacing: 10
LabelLabel {
text: 'Connection'
}
Label {
text: root.info.url
}
LabelLabel {
text: 'Initialized'
}
Label {
text: root.displayBool(root.info?.initialized)
}
LabelLabel {
text: 'Version'
}
Label {
text: root.displayText(root.info?.version)
}
Column {
Layout.alignment: Qt.AlignRight | Qt.AlignBaseline
LabelLabel {
text: 'Error'
}
ToolButton {
Layout.preferredWidth: root.hasError() ? this.implicitWidth : 0
Behavior on Layout.preferredWidth {
PropertyAnimation {
duration: 300
easing.type: Easing.InOutCubic
}
}
text: '[?]'
onClicked: root.showErrorDialog = true
}
}
Label {
Layout.alignment: Qt.AlignBaseline
text: root.displayText(root.info?.error?.title)
}
}
}

View File

@ -0,0 +1,69 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import "../Components"
ColumnLayout {
id: root
property alias selectFileUrl: fileSelector.url
property alias createDirectoryUrl: fileCreator.directoryUrl
property alias createFilename: fileCreator.filename
signal uiModeChanged(string value)
signal confirmCreate
ListModel {
id: uiModeModel
ListElement {
value: 'select'
}
ListElement {
value: 'create'
}
}
TabBar {
id: tabBar
Layout.fillWidth: true
TabButton {
text: qsTr("Select Existing")
width: implicitWidth + 10
}
TabButton {
text: qsTr("Create New File")
width: implicitWidth + 10
}
onCurrentIndexChanged: {
const idx = this.currentIndex;
root.uiModeChanged(uiModeModel.get(idx).value);
}
}
StackLayout {
currentIndex: tabBar.currentIndex
Layout.fillWidth: true
Layout.preferredHeight: children[currentIndex].height
Behavior on Layout.preferredHeight {
PropertyAnimation {
duration: 300
easing.type: Easing.InOutCubic
}
}
clip: true
FileSelector {
id: fileSelector
}
DatabaseFileCreator {
id: fileCreator
onConfirm: root.confirmCreate()
}
}
}

62
ui/qmls/Overview.qml Normal file
View File

@ -0,0 +1,62 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import internal.ui.vm 1.0
Page {
id: root
property alias b30: vm.b30
OverviewViewModel {
id: vm
}
component Display: RowLayout {
required property string label
required property string value
spacing: 5
// implicitHeight: valueText.implicitHeight
Label {
text: parent.label
Layout.alignment: Qt.AlignBaseline
}
Label {
id: valueText
text: parent.value
font.pointSize: 18
Layout.alignment: Qt.AlignBaseline
}
}
RowLayout {
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
Display {
label: 'B30'
value: root.b30 >= 0 ? root.b30.toFixed(3) : 'N/A'
}
Display {
label: 'R10'
value: 'Not supported'
}
}
Button {
Layout.alignment: Qt.AlignBottom
// TODO: icon
text: 'Reload'
onClicked: vm.reload()
}
}
}

View File

@ -70,12 +70,12 @@
<translation>Continue</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="117"/>
<location filename="../../startup/databaseChecker.py" line="122"/>
<source>dialog.tryInitExistingDatabase</source>
<translation>The existing database doesn&apos;t seem to be initialized properly, try initialize again?</translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="133"/>
<location filename="../../startup/databaseChecker.py" line="138"/>
<source>dialog.confirmNewDatabase</source>
<translation>Database file does not exist. Create now?</translation>
</message>
@ -224,85 +224,118 @@
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="28"/>
<source>iccOptionsGroupBox</source>
<translation>ICC Profile Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="34"/>
<source>icc.useQt</source>
<translation>Use Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="41"/>
<source>icc.usePIL</source>
<translation>Use PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="51"/>
<source>icc.tryFix</source>
<translation>Try fix</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="61"/>
<source>queue.addImageButton</source>
<translation>Add Image</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="71"/>
<location filename="../../designer/components/ocrQueue.ui" line="38"/>
<source>queue.removeSelected</source>
<translation>Remove Selected</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="81"/>
<location filename="../../designer/components/ocrQueue.ui" line="48"/>
<source>queue.removeAll</source>
<translation>Remove All</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="101"/>
<location filename="../../designer/components/ocrQueue.ui" line="68"/>
<source>queue.optionsButton</source>
<translation>Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="75"/>
<source>queue.startOcrButton</source>
<translation>Start OCR</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="153"/>
<location filename="../../designer/components/ocrQueue.ui" line="127"/>
<source>results</source>
<translation>Results</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="162"/>
<location filename="../../designer/components/ocrQueue.ui" line="136"/>
<source>results.acceptSelectedButton</source>
<translation>Accept Selected</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="169"/>
<location filename="../../designer/components/ocrQueue.ui" line="143"/>
<source>results.acceptAllButton</source>
<translation>Accept All</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="189"/>
<location filename="../../designer/components/ocrQueue.ui" line="163"/>
<source>results.ignoreValidate</source>
<translation>Ignore
validation</translation>
</message>
</context>
<context>
<name>OcrQueueOptionsDialog</name>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="14"/>
<source>OCR Options</source>
<translation>OCR Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="22"/>
<source>iccOptionsGroupBox</source>
<translation>ICC Profile Options</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="28"/>
<source>icc.useQt</source>
<translation>Use Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="35"/>
<source>icc.usePIL</source>
<translation>Use PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="45"/>
<source>icc.tryFix</source>
<translation>Try fix</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="55"/>
<source>dateOptionsGroupBox</source>
<translation>Date Source</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="64"/>
<source>date.readFromExif</source>
<translation>Read from image EXIF</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="74"/>
<source>date.useCreationDate</source>
<translation>File creation time</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="84"/>
<source>date.useModifyDate</source>
<translation>File last modification time</translation>
</message>
</context>
<context>
<name>OcrTableModel</name>
<message>
<location filename="../../extends/components/ocrQueue.py" line="347"/>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<source>horizontalHeader.title.select</source>
<translation>Select</translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<location filename="../../extends/components/ocrQueue.py" line="351"/>
<source>horizontalHeader.title.imagePreview</source>
<translation>Image Preview</translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="349"/>
<location filename="../../extends/components/ocrQueue.py" line="352"/>
<source>horizontalHeader.title.chart</source>
<translation>Chart</translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="350"/>
<location filename="../../extends/components/ocrQueue.py" line="353"/>
<source>horizontalHeader.title.score</source>
<translation>Score</translation>
</message>
@ -672,92 +705,127 @@ validation</translation>
<context>
<name>TabDb_Manage</name>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="78"/>
<source>syncArcSongDbButton</source>
<translation>Sync arcsong.db</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="85"/>
<source>syncArcSongDb.description</source>
<translation>Update chart info</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="98"/>
<source>importScoreGroup</source>
<translation>Import Score</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="105"/>
<source>importSt3Button</source>
<translation>Import from Game Save</translation>
<translation>Game Save Database</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="112"/>
<source>importSt3.description</source>
<translation>Import scores from your game save</translation>
<translation>Import scores from your game save database</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>exportScoresButton</source>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="139"/>
<source>exportScoreGroup</source>
<translation>Export Scores</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="65"/>
<source>exportScores.description</source>
<translation>Export all your scores to a JSON file</translation>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="146"/>
<source>exportScoresButton</source>
<translation>D.E.F. V2</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="79"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="153"/>
<source>exportScores.description</source>
<translation>Export all your scores in &lt;i&gt;Arcaea Offline Data Exchange Format V2&lt;/i&gt; formed JSON file</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="186"/>
<source>miscGroup</source>
<translation>Miscellaneous</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="284"/>
<source>syncChartInfoDbButton</source>
<translation>Sync Chart Info Database</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="291"/>
<source>syncChartInfoDb.description</source>
<translation>Update chart info</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>importPacklistButton</source>
<translation>Import packlist</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="86"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/>
<source>importSonglistButton</source>
<translation>Import songlist</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="93"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<source>importPacklist.description</source>
<translation>Import packlist file</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="100"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/>
<source>importSonglist.description</source>
<translation>Import songlist file</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="107"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="193"/>
<source>exportArcsongJsonButton</source>
<translation>Export arcsong.json</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="114"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="200"/>
<source>exportArcsongJson.description</source>
<translation>Export arcsong.json file</translation>
<translation>Export arcsong.json file based on the information in database</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="121"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="51"/>
<source>importApkButton</source>
<translation>Import from APK</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="128"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="213"/>
<source>packSongInfoGroup</source>
<translation>Pack/Song Info</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>importApk.description</source>
<translation>Import packlist and songlist from .apk file</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="135"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="71"/>
<source>chartInfoGroup</source>
<translation>Chart Info</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="119"/>
<source>importOnlineButton</source>
<translation>Import from Arcaea Online</translation>
<translation>Arcaea Online</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="142"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="126"/>
<source>importOnline.description</source>
<translation>Import scores from the result of Arcaea Online API</translation>
<translation>Import scores from the Arcaea Online API result</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="149"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="160"/>
<source>exportSmartRteB30Button</source>
<translation>Export Scores (CSV, SmartRTE B30)</translation>
<translation>SmartRTE B30</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="156"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="167"/>
<source>exportSmartRteB30.description</source>
<translation>Export all your scores to &lt;a href=&quot;https://smartrte.github.io/b30gen.html&quot;&gt;smartrte.github.io&lt;/a&gt; compatible CSV file</translation>
</message>
@ -840,27 +908,27 @@ validation</translation>
<translation>Delete Selected Scores</translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="142"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="158"/>
<source>quickSelectComboBox.idEarlier</source>
<translation>Earlier ID</translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="148"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="164"/>
<source>quickSelectComboBox.dateEarlier</source>
<translation>Earlier date</translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="154"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="170"/>
<source>quickSelectComboBox.columnsIntegral</source>
<translation>More complete data</translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="279"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="295"/>
<source>deleteSelectionDialog.content {}</source>
<translation>Deleting {} scores from database, this cannot be undone!&lt;br&gt;Confirm?</translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="301"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="317"/>
<source>scan_noColumnsDialog.content</source>
<translation>You haven&apos;t selected any column! Are you sure to continue?</translation>
</message>

View File

@ -70,12 +70,12 @@
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="117"/>
<location filename="../../startup/databaseChecker.py" line="122"/>
<source>dialog.tryInitExistingDatabase</source>
<translation></translation>
</message>
<message>
<location filename="../../startup/databaseChecker.py" line="133"/>
<location filename="../../startup/databaseChecker.py" line="138"/>
<source>dialog.confirmNewDatabase</source>
<translation></translation>
</message>
@ -224,84 +224,117 @@
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="28"/>
<source>iccOptionsGroupBox</source>
<translation>ICC </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="34"/>
<source>icc.useQt</source>
<translation>使 Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="41"/>
<source>icc.usePIL</source>
<translation>使 PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="51"/>
<source>icc.tryFix</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="61"/>
<source>queue.addImageButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="71"/>
<location filename="../../designer/components/ocrQueue.ui" line="38"/>
<source>queue.removeSelected</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="81"/>
<location filename="../../designer/components/ocrQueue.ui" line="48"/>
<source>queue.removeAll</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="101"/>
<location filename="../../designer/components/ocrQueue.ui" line="68"/>
<source>queue.optionsButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="75"/>
<source>queue.startOcrButton</source>
<translation> OCR</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="153"/>
<location filename="../../designer/components/ocrQueue.ui" line="127"/>
<source>results</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="162"/>
<location filename="../../designer/components/ocrQueue.ui" line="136"/>
<source>results.acceptSelectedButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="169"/>
<location filename="../../designer/components/ocrQueue.ui" line="143"/>
<source>results.acceptAllButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueue.ui" line="189"/>
<location filename="../../designer/components/ocrQueue.ui" line="163"/>
<source>results.ignoreValidate</source>
<translation></translation>
</message>
</context>
<context>
<name>OcrQueueOptionsDialog</name>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="14"/>
<source>OCR Options</source>
<translation>OCR </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="22"/>
<source>iccOptionsGroupBox</source>
<translation>ICC </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="28"/>
<source>icc.useQt</source>
<translation>使 Qt</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="35"/>
<source>icc.usePIL</source>
<translation>使 PIL</translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="45"/>
<source>icc.tryFix</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="55"/>
<source>dateOptionsGroupBox</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="64"/>
<source>date.readFromExif</source>
<translation> EXIF </translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="74"/>
<source>date.useCreationDate</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/components/ocrQueueOptionsDialog.ui" line="84"/>
<source>date.useModifyDate</source>
<translation></translation>
</message>
</context>
<context>
<name>OcrTableModel</name>
<message>
<location filename="../../extends/components/ocrQueue.py" line="347"/>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<source>horizontalHeader.title.select</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="348"/>
<location filename="../../extends/components/ocrQueue.py" line="351"/>
<source>horizontalHeader.title.imagePreview</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="349"/>
<location filename="../../extends/components/ocrQueue.py" line="352"/>
<source>horizontalHeader.title.chart</source>
<translation></translation>
</message>
<message>
<location filename="../../extends/components/ocrQueue.py" line="350"/>
<location filename="../../extends/components/ocrQueue.py" line="353"/>
<source>horizontalHeader.title.score</source>
<translation></translation>
</message>
@ -671,92 +704,127 @@
<context>
<name>TabDb_Manage</name>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="78"/>
<source>syncArcSongDbButton</source>
<translation> arcsong.db</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="85"/>
<source>syncArcSongDb.description</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/>
<source>importSt3Button</source>
<translation></translation>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="98"/>
<source>importScoreGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="105"/>
<source>importSt3Button</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="112"/>
<source>importSt3.description</source>
<translation>姿&lt;br&gt;退</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>exportScoresButton</source>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="139"/>
<source>exportScoreGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="65"/>
<source>exportScores.description</source>
<translation> JSON </translation>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="146"/>
<source>exportScoresButton</source>
<translation> V2</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="79"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="153"/>
<source>exportScores.description</source>
<translation> &lt;i&gt;Arcaea Offline V2&lt;/i&gt; JSON </translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="186"/>
<source>miscGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="284"/>
<source>syncChartInfoDbButton</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="291"/>
<source>syncChartInfoDb.description</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="23"/>
<source>importPacklistButton</source>
<translation> packlist</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="86"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="37"/>
<source>importSonglistButton</source>
<translation> songlist</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="93"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="30"/>
<source>importPacklist.description</source>
<translation> packlist </translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="100"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="44"/>
<source>importSonglist.description</source>
<translation> songlist </translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="107"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="193"/>
<source>exportArcsongJsonButton</source>
<translation> arcsong.json</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="114"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="200"/>
<source>exportArcsongJson.description</source>
<translation> arcsong.json </translation>
<translation> arcsong.json </translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="121"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="51"/>
<source>importApkButton</source>
<translation> APK </translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="128"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="213"/>
<source>packSongInfoGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="58"/>
<source>importApk.description</source>
<translation> .apk packlist songlist</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="135"/>
<source>importOnlineButton</source>
<translation> Arcaea Online </translation>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="71"/>
<source>chartInfoGroup</source>
<translation></translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="142"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="119"/>
<source>importOnlineButton</source>
<translation>Arcaea Online</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="126"/>
<source>importOnline.description</source>
<translation> Arcaea Online API </translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="149"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="160"/>
<source>exportSmartRteB30Button</source>
<translation>CSVSmartRTE B30</translation>
<translation>SmartRTE B30</translation>
</message>
<message>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="156"/>
<location filename="../../designer/tabs/tabDb/tabDb_Manage.ui" line="167"/>
<source>exportSmartRteB30.description</source>
<translation> &lt;a href=&quot;https://smartrte.github.io/b30gen.html&quot;&gt;smartrte.github.io&lt;/a&gt; 的 CSV 文件</translation>
</message>
@ -839,27 +907,27 @@
<translation></translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="142"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="158"/>
<source>quickSelectComboBox.idEarlier</source>
<translation>ID </translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="148"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="164"/>
<source>quickSelectComboBox.dateEarlier</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="154"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="170"/>
<source>quickSelectComboBox.columnsIntegral</source>
<translation></translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="279"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="295"/>
<source>deleteSelectionDialog.content {}</source>
<translation> {} &lt;br&gt;</translation>
</message>
<message>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="301"/>
<location filename="../../implements/tabs/tabDb/tabDb_RemoveDuplicateScores.py" line="317"/>
<source>scan_noColumnsDialog.content</source>
<translation></translation>
</message>

View File

@ -19,5 +19,8 @@
<file>lang/zh_CN.qm</file>
<file>lang/en_US.qm</file>
<file>themes/default.json</file>
<file>themes/tempest.json</file>
</qresource>
</RCC>

View File

@ -0,0 +1,427 @@
{
"//url": "http://material-foundation.github.io/material-theme-builder/?primary=%234E486C&custom%3ASuccess=%2300C555&colorMatch=true",
"name": "default",
"description": "TYPE: CUSTOM\nMaterial Theme Builder export",
"seed": "#4E486C",
"coreColors": {
"primary": "#4E486C"
},
"extendedColors": [
{
"name": "Success",
"color": "#00C555",
"description": "",
"harmonized": true
}
],
"schemes": {
"light": {
"primary": "#373154",
"surfaceTint": "#605A7F",
"onPrimary": "#FFFFFF",
"primaryContainer": "#4E486C",
"onPrimaryContainer": "#C0B8E3",
"secondary": "#605C6C",
"onSecondary": "#FFFFFF",
"secondaryContainer": "#E3DDF0",
"onSecondaryContainer": "#646071",
"tertiary": "#4F2B40",
"onTertiary": "#FFFFFF",
"tertiaryContainer": "#684157",
"onTertiaryContainer": "#E3B0CA",
"error": "#BA1A1A",
"onError": "#FFFFFF",
"errorContainer": "#FFDAD6",
"onErrorContainer": "#93000A",
"background": "#FDF8FC",
"onBackground": "#1C1B1E",
"surface": "#FDF8FC",
"onSurface": "#1C1B1E",
"surfaceVariant": "#E6E1EA",
"onSurfaceVariant": "#48464D",
"outline": "#79767E",
"outlineVariant": "#C9C5CE",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#313033",
"inverseOnSurface": "#F4EFF3",
"inversePrimary": "#C9C1EC",
"primaryFixed": "#E6DEFF",
"onPrimaryFixed": "#1C1738",
"primaryFixedDim": "#C9C1EC",
"onPrimaryFixedVariant": "#484266",
"secondaryFixed": "#E6E0F3",
"onSecondaryFixed": "#1C1A27",
"secondaryFixedDim": "#C9C4D7",
"onSecondaryFixedVariant": "#484554",
"tertiaryFixed": "#FFD8EA",
"onTertiaryFixed": "#301024",
"tertiaryFixedDim": "#ECB8D2",
"onTertiaryFixedVariant": "#613B51",
"surfaceDim": "#DDD9DC",
"surfaceBright": "#FDF8FC",
"surfaceContainerLowest": "#FFFFFF",
"surfaceContainerLow": "#F7F2F6",
"surfaceContainer": "#F1ECF0",
"surfaceContainerHigh": "#EBE7EA",
"surfaceContainerHighest": "#E6E1E5"
},
"light-medium-contrast": {
"primary": "#373154",
"surfaceTint": "#605A7F",
"onPrimary": "#FFFFFF",
"primaryContainer": "#4E486C",
"onPrimaryContainer": "#EDE7FF",
"secondary": "#373443",
"onSecondary": "#FFFFFF",
"secondaryContainer": "#6F6B7B",
"onSecondaryContainer": "#FFFFFF",
"tertiary": "#4E2B40",
"onTertiary": "#FFFFFF",
"tertiaryContainer": "#684157",
"onTertiaryContainer": "#FFE4EF",
"error": "#740006",
"onError": "#FFFFFF",
"errorContainer": "#CF2C27",
"onErrorContainer": "#FFFFFF",
"background": "#FDF8FC",
"onBackground": "#1C1B1E",
"surface": "#FDF8FC",
"onSurface": "#121113",
"surfaceVariant": "#E6E1EA",
"onSurfaceVariant": "#37353D",
"outline": "#545159",
"outlineVariant": "#6F6C74",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#313033",
"inverseOnSurface": "#F4EFF3",
"inversePrimary": "#C9C1EC",
"primaryFixed": "#6F688E",
"onPrimaryFixed": "#FFFFFF",
"primaryFixedDim": "#565075",
"onPrimaryFixedVariant": "#FFFFFF",
"secondaryFixed": "#6F6B7B",
"onSecondaryFixed": "#FFFFFF",
"secondaryFixedDim": "#565363",
"onSecondaryFixedVariant": "#FFFFFF",
"tertiaryFixed": "#8B6078",
"onTertiaryFixed": "#FFFFFF",
"tertiaryFixedDim": "#71495F",
"onTertiaryFixedVariant": "#FFFFFF",
"surfaceDim": "#C9C5C9",
"surfaceBright": "#FDF8FC",
"surfaceContainerLowest": "#FFFFFF",
"surfaceContainerLow": "#F7F2F6",
"surfaceContainer": "#EBE7EA",
"surfaceContainerHigh": "#E0DCDF",
"surfaceContainerHighest": "#D4D0D4"
},
"light-high-contrast": {
"primary": "#2D2749",
"surfaceTint": "#605A7F",
"onPrimary": "#FFFFFF",
"primaryContainer": "#4A4468",
"onPrimaryContainer": "#FFFFFF",
"secondary": "#2D2A39",
"onSecondary": "#FFFFFF",
"secondaryContainer": "#4A4757",
"onSecondaryContainer": "#FFFFFF",
"tertiary": "#432135",
"onTertiary": "#FFFFFF",
"tertiaryContainer": "#643D53",
"onTertiaryContainer": "#FFFFFF",
"error": "#600004",
"onError": "#FFFFFF",
"errorContainer": "#98000A",
"onErrorContainer": "#FFFFFF",
"background": "#FDF8FC",
"onBackground": "#1C1B1E",
"surface": "#FDF8FC",
"onSurface": "#000000",
"surfaceVariant": "#E6E1EA",
"onSurfaceVariant": "#000000",
"outline": "#2D2B32",
"outlineVariant": "#4A4850",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#313033",
"inverseOnSurface": "#FFFFFF",
"inversePrimary": "#C9C1EC",
"primaryFixed": "#4A4468",
"onPrimaryFixed": "#FFFFFF",
"primaryFixedDim": "#342E50",
"onPrimaryFixedVariant": "#FFFFFF",
"secondaryFixed": "#4A4757",
"onSecondaryFixed": "#FFFFFF",
"secondaryFixedDim": "#34313F",
"onSecondaryFixedVariant": "#FFFFFF",
"tertiaryFixed": "#643D53",
"onTertiaryFixed": "#FFFFFF",
"tertiaryFixedDim": "#4A273C",
"onTertiaryFixedVariant": "#FFFFFF",
"surfaceDim": "#BBB8BB",
"surfaceBright": "#FDF8FC",
"surfaceContainerLowest": "#FFFFFF",
"surfaceContainerLow": "#F4EFF3",
"surfaceContainer": "#E6E1E5",
"surfaceContainerHigh": "#D7D3D7",
"surfaceContainerHighest": "#C9C5C9"
},
"dark": {
"primary": "#C9C1EC",
"surfaceTint": "#C9C1EC",
"onPrimary": "#312C4E",
"primaryContainer": "#4E486C",
"onPrimaryContainer": "#C0B8E3",
"secondary": "#C9C4D7",
"onSecondary": "#312E3D",
"secondaryContainer": "#4A4757",
"onSecondaryContainer": "#BBB6C8",
"tertiary": "#ECB8D2",
"onTertiary": "#48253A",
"tertiaryContainer": "#684157",
"onTertiaryContainer": "#E3B0CA",
"error": "#FFB4AB",
"onError": "#690005",
"errorContainer": "#93000A",
"onErrorContainer": "#FFDAD6",
"background": "#141315",
"onBackground": "#E6E1E5",
"surface": "#141315",
"onSurface": "#E6E1E5",
"surfaceVariant": "#48464D",
"onSurfaceVariant": "#C9C5CE",
"outline": "#938F98",
"outlineVariant": "#48464D",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#E6E1E5",
"inverseOnSurface": "#313033",
"inversePrimary": "#605A7F",
"primaryFixed": "#E6DEFF",
"onPrimaryFixed": "#1C1738",
"primaryFixedDim": "#C9C1EC",
"onPrimaryFixedVariant": "#484266",
"secondaryFixed": "#E6E0F3",
"onSecondaryFixed": "#1C1A27",
"secondaryFixedDim": "#C9C4D7",
"onSecondaryFixedVariant": "#484554",
"tertiaryFixed": "#FFD8EA",
"onTertiaryFixed": "#301024",
"tertiaryFixedDim": "#ECB8D2",
"onTertiaryFixedVariant": "#613B51",
"surfaceDim": "#141315",
"surfaceBright": "#3A393B",
"surfaceContainerLowest": "#0F0E10",
"surfaceContainerLow": "#1C1B1E",
"surfaceContainer": "#201F22",
"surfaceContainerHigh": "#2B292C",
"surfaceContainerHighest": "#363437"
},
"dark-medium-contrast": {
"primary": "#E0D7FF",
"surfaceTint": "#C9C1EC",
"onPrimary": "#262142",
"primaryContainer": "#938CB4",
"onPrimaryContainer": "#000000",
"secondary": "#DFD9ED",
"onSecondary": "#262432",
"secondaryContainer": "#938EA0",
"onSecondaryContainer": "#000000",
"tertiary": "#FFCFE7",
"onTertiary": "#3C1A2E",
"tertiaryContainer": "#B2839C",
"onTertiaryContainer": "#000000",
"error": "#FFD2CC",
"onError": "#540003",
"errorContainer": "#FF5449",
"onErrorContainer": "#000000",
"background": "#141315",
"onBackground": "#E6E1E5",
"surface": "#141315",
"onSurface": "#FFFFFF",
"surfaceVariant": "#48464D",
"onSurfaceVariant": "#DFDAE4",
"outline": "#B4B0BA",
"outlineVariant": "#928F98",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#E6E1E5",
"inverseOnSurface": "#2B292C",
"inversePrimary": "#494367",
"primaryFixed": "#E6DEFF",
"onPrimaryFixed": "#120C2D",
"primaryFixedDim": "#C9C1EC",
"onPrimaryFixedVariant": "#373254",
"secondaryFixed": "#E6E0F3",
"onSecondaryFixed": "#120F1D",
"secondaryFixedDim": "#C9C4D7",
"onSecondaryFixedVariant": "#373443",
"tertiaryFixed": "#FFD8EA",
"onTertiaryFixed": "#230619",
"tertiaryFixedDim": "#ECB8D2",
"onTertiaryFixedVariant": "#4E2B40",
"surfaceDim": "#141315",
"surfaceBright": "#454447",
"surfaceContainerLowest": "#080709",
"surfaceContainerLow": "#1E1D20",
"surfaceContainer": "#28272A",
"surfaceContainerHigh": "#333235",
"surfaceContainerHighest": "#3F3D40"
},
"dark-high-contrast": {
"primary": "#F3EDFF",
"surfaceTint": "#C9C1EC",
"onPrimary": "#000000",
"primaryContainer": "#C6BDE8",
"onPrimaryContainer": "#0B0627",
"secondary": "#F3EDFF",
"onSecondary": "#000000",
"secondaryContainer": "#C5C0D3",
"onSecondaryContainer": "#0C0916",
"tertiary": "#FFEBF3",
"onTertiary": "#000000",
"tertiaryContainer": "#E7B4CE",
"onTertiaryContainer": "#1C0213",
"error": "#FFECE9",
"onError": "#000000",
"errorContainer": "#FFAEA4",
"onErrorContainer": "#220001",
"background": "#141315",
"onBackground": "#E6E1E5",
"surface": "#141315",
"onSurface": "#FFFFFF",
"surfaceVariant": "#48464D",
"onSurfaceVariant": "#FFFFFF",
"outline": "#F3EEF8",
"outlineVariant": "#C5C1CA",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#E6E1E5",
"inverseOnSurface": "#000000",
"inversePrimary": "#494367",
"primaryFixed": "#E6DEFF",
"onPrimaryFixed": "#000000",
"primaryFixedDim": "#C9C1EC",
"onPrimaryFixedVariant": "#120C2D",
"secondaryFixed": "#E6E0F3",
"onSecondaryFixed": "#000000",
"secondaryFixedDim": "#C9C4D7",
"onSecondaryFixedVariant": "#120F1D",
"tertiaryFixed": "#FFD8EA",
"onTertiaryFixed": "#000000",
"tertiaryFixedDim": "#ECB8D2",
"onTertiaryFixedVariant": "#230619",
"surfaceDim": "#141315",
"surfaceBright": "#514F52",
"surfaceContainerLowest": "#000000",
"surfaceContainerLow": "#201F22",
"surfaceContainer": "#313033",
"surfaceContainerHigh": "#3C3B3E",
"surfaceContainerHighest": "#484649"
}
},
"palettes": {
"primary": {
"0": "#000000",
"5": "#110B2C",
"10": "#1C1738",
"15": "#272142",
"20": "#312C4E",
"25": "#3C375A",
"30": "#484266",
"35": "#544E72",
"40": "#605A7F",
"50": "#797299",
"60": "#938CB4",
"70": "#AEA6CF",
"80": "#C9C1EC",
"90": "#E6DEFF",
"95": "#F4EEFF",
"98": "#FDF8FF",
"99": "#FFFBFF",
"100": "#FFFFFF"
},
"secondary": {
"0": "#000000",
"5": "#111018",
"10": "#1C1A23",
"15": "#26252D",
"20": "#312F38",
"25": "#3C3A43",
"30": "#48454F",
"35": "#54515B",
"40": "#605D67",
"50": "#797580",
"60": "#938F9A",
"70": "#AEA9B5",
"80": "#C9C4D0",
"90": "#E6E0EC",
"95": "#F4EEFB",
"98": "#FDF8FF",
"99": "#FFFBFF",
"100": "#FFFFFF"
},
"tertiary": {
"0": "#000000",
"5": "#1B0C13",
"10": "#27171E",
"15": "#322128",
"20": "#3E2B33",
"25": "#49363E",
"30": "#564149",
"35": "#624D55",
"40": "#6F5861",
"50": "#897179",
"60": "#A38A93",
"70": "#BFA4AD",
"80": "#DCBFC9",
"90": "#F9DBE5",
"95": "#FFECF1",
"98": "#FFF8F8",
"99": "#FFFBFF",
"100": "#FFFFFF"
},
"neutral": {
"0": "#000000",
"5": "#111112",
"10": "#1C1B1D",
"15": "#262527",
"20": "#313032",
"25": "#3C3B3D",
"30": "#484648",
"35": "#545254",
"40": "#605E60",
"50": "#797678",
"60": "#939092",
"70": "#ADAAAC",
"80": "#C9C5C7",
"90": "#E5E1E3",
"95": "#F4EFF1",
"98": "#FDF8FA",
"99": "#FFFBFF",
"100": "#FFFFFF"
},
"neutral-variant": {
"0": "#000000",
"5": "#111014",
"10": "#1C1B1F",
"15": "#262529",
"20": "#313034",
"25": "#3C3B3F",
"30": "#48464A",
"35": "#545256",
"40": "#605D62",
"50": "#79767B",
"60": "#939094",
"70": "#AEAAAF",
"80": "#C9C5CA",
"90": "#E6E1E6",
"95": "#F4EFF4",
"98": "#FDF8FD",
"99": "#FFFBFF",
"100": "#FFFFFF"
}
}
}

View File

@ -0,0 +1,427 @@
{
"//url": "http://material-foundation.github.io/material-theme-builder/?primary=%23186D98&custom%3ASuccess=%2300C555&colorMatch=true",
"name": "tempest",
"description": "TYPE: CUSTOM\nMaterial Theme Builder export",
"seed": "#186D98",
"coreColors": {
"primary": "#186D98"
},
"extendedColors": [
{
"name": "Success",
"color": "#00C555",
"description": "",
"harmonized": true
}
],
"schemes": {
"light": {
"primary": "#005479",
"surfaceTint": "#03658F",
"onPrimary": "#FFFFFF",
"primaryContainer": "#186D98",
"onPrimaryContainer": "#CEE9FF",
"secondary": "#496173",
"onSecondary": "#FFFFFF",
"secondaryContainer": "#C9E3F8",
"onSecondaryContainer": "#4D6678",
"tertiary": "#683D7A",
"onTertiary": "#FFFFFF",
"tertiaryContainer": "#825594",
"onTertiaryContainer": "#F9DCFF",
"error": "#BA1A1A",
"onError": "#FFFFFF",
"errorContainer": "#FFDAD6",
"onErrorContainer": "#93000A",
"background": "#F7F9FD",
"onBackground": "#191C1F",
"surface": "#F7F9FD",
"onSurface": "#191C1F",
"surfaceVariant": "#DCE3EB",
"onSurfaceVariant": "#40484E",
"outline": "#70787F",
"outlineVariant": "#C0C7CF",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#2D3134",
"inverseOnSurface": "#EFF1F5",
"inversePrimary": "#88CEFE",
"primaryFixed": "#C8E6FF",
"onPrimaryFixed": "#001E2E",
"primaryFixedDim": "#88CEFE",
"onPrimaryFixedVariant": "#004C6D",
"secondaryFixed": "#CCE6FB",
"onSecondaryFixed": "#021E2D",
"secondaryFixedDim": "#B0CADF",
"onSecondaryFixedVariant": "#314A5B",
"tertiaryFixed": "#F8D8FF",
"onTertiaryFixed": "#300443",
"tertiaryFixedDim": "#E8B4FA",
"onTertiaryFixedVariant": "#603572",
"surfaceDim": "#D8DADE",
"surfaceBright": "#F7F9FD",
"surfaceContainerLowest": "#FFFFFF",
"surfaceContainerLow": "#F2F4F7",
"surfaceContainer": "#ECEEF2",
"surfaceContainerHigh": "#E6E8EC",
"surfaceContainerHighest": "#E0E2E6"
},
"light-medium-contrast": {
"primary": "#003A55",
"surfaceTint": "#03658F",
"onPrimary": "#FFFFFF",
"primaryContainer": "#186D98",
"onPrimaryContainer": "#FFFFFF",
"secondary": "#203949",
"onSecondary": "#FFFFFF",
"secondaryContainer": "#577082",
"onSecondaryContainer": "#FFFFFF",
"tertiary": "#4E2460",
"onTertiary": "#FFFFFF",
"tertiaryContainer": "#825594",
"onTertiaryContainer": "#FFFFFF",
"error": "#740006",
"onError": "#FFFFFF",
"errorContainer": "#CF2C27",
"onErrorContainer": "#FFFFFF",
"background": "#F7F9FD",
"onBackground": "#191C1F",
"surface": "#F7F9FD",
"onSurface": "#0E1214",
"surfaceVariant": "#DCE3EB",
"onSurfaceVariant": "#2F373D",
"outline": "#4C535A",
"outlineVariant": "#666E75",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#2D3134",
"inverseOnSurface": "#EFF1F5",
"inversePrimary": "#88CEFE",
"primaryFixed": "#23749F",
"onPrimaryFixed": "#FFFFFF",
"primaryFixedDim": "#005B82",
"onPrimaryFixedVariant": "#FFFFFF",
"secondaryFixed": "#577082",
"onSecondaryFixed": "#FFFFFF",
"secondaryFixedDim": "#3F5869",
"onSecondaryFixedVariant": "#FFFFFF",
"tertiaryFixed": "#895C9B",
"onTertiaryFixed": "#FFFFFF",
"tertiaryFixedDim": "#6F4381",
"onTertiaryFixedVariant": "#FFFFFF",
"surfaceDim": "#C4C7CA",
"surfaceBright": "#F7F9FD",
"surfaceContainerLowest": "#FFFFFF",
"surfaceContainerLow": "#F2F4F7",
"surfaceContainer": "#E6E8EC",
"surfaceContainerHigh": "#DBDDE1",
"surfaceContainerHighest": "#CFD2D5"
},
"light-high-contrast": {
"primary": "#003046",
"surfaceTint": "#03658F",
"onPrimary": "#FFFFFF",
"primaryContainer": "#004E71",
"onPrimaryContainer": "#FFFFFF",
"secondary": "#152F3F",
"onSecondary": "#FFFFFF",
"secondaryContainer": "#344C5D",
"onSecondaryContainer": "#FFFFFF",
"tertiary": "#421955",
"onTertiary": "#FFFFFF",
"tertiaryContainer": "#623874",
"onTertiaryContainer": "#FFFFFF",
"error": "#600004",
"onError": "#FFFFFF",
"errorContainer": "#98000A",
"onErrorContainer": "#FFFFFF",
"background": "#F7F9FD",
"onBackground": "#191C1F",
"surface": "#F7F9FD",
"onSurface": "#000000",
"surfaceVariant": "#DCE3EB",
"onSurfaceVariant": "#000000",
"outline": "#252D33",
"outlineVariant": "#424A51",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#2D3134",
"inverseOnSurface": "#FFFFFF",
"inversePrimary": "#88CEFE",
"primaryFixed": "#004E71",
"onPrimaryFixed": "#FFFFFF",
"primaryFixedDim": "#003650",
"onPrimaryFixedVariant": "#FFFFFF",
"secondaryFixed": "#344C5D",
"onSecondaryFixed": "#FFFFFF",
"secondaryFixedDim": "#1C3546",
"onSecondaryFixedVariant": "#FFFFFF",
"tertiaryFixed": "#623874",
"onTertiaryFixed": "#FFFFFF",
"tertiaryFixedDim": "#4A205C",
"onTertiaryFixedVariant": "#FFFFFF",
"surfaceDim": "#B6B9BD",
"surfaceBright": "#F7F9FD",
"surfaceContainerLowest": "#FFFFFF",
"surfaceContainerLow": "#EFF1F5",
"surfaceContainer": "#E0E2E6",
"surfaceContainerHigh": "#D2D4D8",
"surfaceContainerHighest": "#C4C7CA"
},
"dark": {
"primary": "#88CEFE",
"surfaceTint": "#88CEFE",
"onPrimary": "#00344D",
"primaryContainer": "#186D98",
"onPrimaryContainer": "#CEE9FF",
"secondary": "#B0CADF",
"onSecondary": "#1A3343",
"secondaryContainer": "#314A5B",
"onSecondaryContainer": "#9FB8CD",
"tertiary": "#E8B4FA",
"onTertiary": "#471E59",
"tertiaryContainer": "#825594",
"onTertiaryContainer": "#F9DCFF",
"error": "#FFB4AB",
"onError": "#690005",
"errorContainer": "#93000A",
"onErrorContainer": "#FFDAD6",
"background": "#101417",
"onBackground": "#E0E2E6",
"surface": "#101417",
"onSurface": "#E0E2E6",
"surfaceVariant": "#40484E",
"onSurfaceVariant": "#C0C7CF",
"outline": "#8A9299",
"outlineVariant": "#40484E",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#E0E2E6",
"inverseOnSurface": "#2D3134",
"inversePrimary": "#03658F",
"primaryFixed": "#C8E6FF",
"onPrimaryFixed": "#001E2E",
"primaryFixedDim": "#88CEFE",
"onPrimaryFixedVariant": "#004C6D",
"secondaryFixed": "#CCE6FB",
"onSecondaryFixed": "#021E2D",
"secondaryFixedDim": "#B0CADF",
"onSecondaryFixedVariant": "#314A5B",
"tertiaryFixed": "#F8D8FF",
"onTertiaryFixed": "#300443",
"tertiaryFixedDim": "#E8B4FA",
"onTertiaryFixedVariant": "#603572",
"surfaceDim": "#101417",
"surfaceBright": "#363A3D",
"surfaceContainerLowest": "#0B0F11",
"surfaceContainerLow": "#191C1F",
"surfaceContainer": "#1D2023",
"surfaceContainerHigh": "#272A2D",
"surfaceContainerHighest": "#323538"
},
"dark-medium-contrast": {
"primary": "#BBE1FF",
"surfaceTint": "#88CEFE",
"onPrimary": "#00293D",
"primaryContainer": "#5098C5",
"onPrimaryContainer": "#000000",
"secondary": "#C6E0F5",
"onSecondary": "#0D2838",
"secondaryContainer": "#7B94A7",
"onSecondaryContainer": "#000000",
"tertiary": "#F5D0FF",
"onTertiary": "#3B114E",
"tertiaryContainer": "#AF7FC1",
"onTertiaryContainer": "#000000",
"error": "#FFD2CC",
"onError": "#540003",
"errorContainer": "#FF5449",
"onErrorContainer": "#000000",
"background": "#101417",
"onBackground": "#E0E2E6",
"surface": "#101417",
"onSurface": "#FFFFFF",
"surfaceVariant": "#40484E",
"onSurfaceVariant": "#D6DDE5",
"outline": "#ABB3BB",
"outlineVariant": "#899199",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#E0E2E6",
"inverseOnSurface": "#272A2D",
"inversePrimary": "#004D6F",
"primaryFixed": "#C8E6FF",
"onPrimaryFixed": "#00131F",
"primaryFixedDim": "#88CEFE",
"onPrimaryFixedVariant": "#003A55",
"secondaryFixed": "#CCE6FB",
"onSecondaryFixed": "#00131F",
"secondaryFixedDim": "#B0CADF",
"onSecondaryFixedVariant": "#203949",
"tertiaryFixed": "#F8D8FF",
"onTertiaryFixed": "#220032",
"tertiaryFixedDim": "#E8B4FA",
"onTertiaryFixedVariant": "#4E2460",
"surfaceDim": "#101417",
"surfaceBright": "#424548",
"surfaceContainerLowest": "#05080A",
"surfaceContainerLow": "#1B1E21",
"surfaceContainer": "#25282B",
"surfaceContainerHigh": "#303336",
"surfaceContainerHighest": "#3B3E41"
},
"dark-high-contrast": {
"primary": "#E3F2FF",
"surfaceTint": "#88CEFE",
"onPrimary": "#000000",
"primaryContainer": "#84CAFA",
"onPrimaryContainer": "#000D17",
"secondary": "#E3F2FF",
"onSecondary": "#000000",
"secondaryContainer": "#ACC6DB",
"onSecondaryContainer": "#000D17",
"tertiary": "#FDEAFF",
"onTertiary": "#000000",
"tertiaryContainer": "#E4B0F6",
"onTertiaryContainer": "#190026",
"error": "#FFECE9",
"onError": "#000000",
"errorContainer": "#FFAEA4",
"onErrorContainer": "#220001",
"background": "#101417",
"onBackground": "#E0E2E6",
"surface": "#101417",
"onSurface": "#FFFFFF",
"surfaceVariant": "#40484E",
"onSurfaceVariant": "#FFFFFF",
"outline": "#E9F1F9",
"outlineVariant": "#BCC3CB",
"shadow": "#000000",
"scrim": "#000000",
"inverseSurface": "#E0E2E6",
"inverseOnSurface": "#000000",
"inversePrimary": "#004D6F",
"primaryFixed": "#C8E6FF",
"onPrimaryFixed": "#000000",
"primaryFixedDim": "#88CEFE",
"onPrimaryFixedVariant": "#00131F",
"secondaryFixed": "#CCE6FB",
"onSecondaryFixed": "#000000",
"secondaryFixedDim": "#B0CADF",
"onSecondaryFixedVariant": "#00131F",
"tertiaryFixed": "#F8D8FF",
"onTertiaryFixed": "#000000",
"tertiaryFixedDim": "#E8B4FA",
"onTertiaryFixedVariant": "#220032",
"surfaceDim": "#101417",
"surfaceBright": "#4D5054",
"surfaceContainerLowest": "#000000",
"surfaceContainerLow": "#1D2023",
"surfaceContainer": "#2D3134",
"surfaceContainerHigh": "#383C3F",
"surfaceContainerHighest": "#44474A"
}
},
"palettes": {
"primary": {
"0": "#000000",
"5": "#00131F",
"10": "#001E2E",
"15": "#00293D",
"20": "#00344D",
"25": "#00405D",
"30": "#004C6D",
"35": "#00587E",
"40": "#03658F",
"50": "#317EAA",
"60": "#5098C5",
"70": "#6CB3E1",
"80": "#88CEFE",
"90": "#C8E6FF",
"95": "#E5F2FF",
"98": "#F6FAFF",
"99": "#FBFCFF",
"100": "#FFFFFF"
},
"secondary": {
"0": "#000000",
"5": "#05121B",
"10": "#0F1D26",
"15": "#1A2731",
"20": "#25323C",
"25": "#303D47",
"30": "#3B4853",
"35": "#46545F",
"40": "#52606B",
"50": "#6B7984",
"60": "#84929E",
"70": "#9FADB9",
"80": "#BAC8D5",
"90": "#D6E4F2",
"95": "#E5F2FF",
"98": "#F6FAFF",
"99": "#FBFCFF",
"100": "#FFFFFF"
},
"tertiary": {
"0": "#000000",
"5": "#140D25",
"10": "#1E1730",
"15": "#29223B",
"20": "#342C46",
"25": "#3F3752",
"30": "#4B425E",
"35": "#564E6A",
"40": "#635A76",
"50": "#7C7290",
"60": "#968CAB",
"70": "#B1A6C6",
"80": "#CDC1E2",
"90": "#E9DDFF",
"95": "#F6EDFF",
"98": "#FEF7FF",
"99": "#FFFBFF",
"100": "#FFFFFF"
},
"neutral": {
"0": "#000000",
"5": "#0F1113",
"10": "#1A1C1E",
"15": "#242628",
"20": "#2F3132",
"25": "#3A3B3D",
"30": "#454749",
"35": "#515254",
"40": "#5D5E60",
"50": "#767779",
"60": "#909193",
"70": "#AAABAD",
"80": "#C6C6C8",
"90": "#E2E2E4",
"95": "#F1F0F3",
"98": "#F9F9FB",
"99": "#FCFCFE",
"100": "#FFFFFF"
},
"neutral-variant": {
"0": "#000000",
"5": "#0C1215",
"10": "#171C20",
"15": "#21262B",
"20": "#2C3135",
"25": "#373C40",
"30": "#42474C",
"35": "#4E5358",
"40": "#5A5F64",
"50": "#73787D",
"60": "#8C9196",
"70": "#A7ACB1",
"80": "#C2C7CC",
"90": "#DEE3E8",
"95": "#EDF1F7",
"98": "#F6FAFF",
"99": "#FBFCFF",
"100": "#FFFFFF"
}
}
}

View File

@ -1,148 +0,0 @@
import logging
import traceback
from enum import IntEnum
from arcaea_offline.database import Database
from PySide6.QtCore import QCoreApplication, QDir, QFileInfo, Qt, QUrl, Slot
from PySide6.QtWidgets import QDialog, QMessageBox
from ui.extends.shared.database import create_engine
from ui.extends.shared.settings import Settings
from .databaseChecker_ui import Ui_DatabaseChecker
logger = logging.getLogger(__name__)
class DatabaseCheckerResult(IntEnum):
FileExist = 0x001
Initted = 0x002
class DatabaseChecker(Ui_DatabaseChecker, QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
self.setWindowFlag(Qt.WindowType.WindowMinimizeButtonHint, False)
self.setWindowFlag(Qt.WindowType.WindowMaximizeButtonHint, False)
self.setWindowFlag(Qt.WindowType.WindowCloseButtonHint, True)
self.dbDirSelector.setMode(self.dbDirSelector.getExistingDirectory)
self.confirmDbByExistingSettings = False
self.settings = Settings(self)
if dbUrlString := self.settings.databaseUrl():
dbFileUrl = QUrl(dbUrlString.replace("sqlite://", "file://"))
dbFileInfo = QFileInfo(dbFileUrl.toLocalFile())
if dbFileInfo.exists():
self.dbDirSelector.selectFile(dbFileInfo.path())
self.dbFilenameLineEdit.setText(dbFileInfo.fileName())
self.confirmDbByExistingSettings = True
self.confirmDbPathButton.click()
else:
self.dbDirSelector.selectFile(QDir.currentPath())
self.dbFilenameLineEdit.setText("arcaea_offline.db")
else:
self.dbDirSelector.selectFile(QDir.currentPath())
self.dbFilenameLineEdit.setText("arcaea_offline.db")
def dbPath(self):
return QDir(self.dbDirSelector.selectedFiles()[0])
def dbFileInfo(self):
return QFileInfo(
QDir.cleanPath(
self.dbPath().absoluteFilePath(self.dbFilenameLineEdit.text())
)
)
def dbFileUrl(self):
return QUrl.fromLocalFile(self.dbFileInfo().filePath())
def dbSqliteUrl(self):
# dbSqliteUrl.setScheme("sqlite")
return QUrl(self.dbFileUrl().toString().replace("file://", "sqlite://"))
def confirmDb(self) -> DatabaseCheckerResult:
flags = 0x000
dbFileInfo = self.dbFileInfo()
dbSqliteUrl = self.dbSqliteUrl()
if not dbFileInfo.exists():
return flags
flags |= DatabaseCheckerResult.FileExist
db = Database(create_engine(dbSqliteUrl))
if db.check_init():
flags |= DatabaseCheckerResult.Initted
self.settings.setDatabaseUrl(self.dbSqliteUrl().toString())
return flags
def updateLabels(self):
result = self.confirmDb()
try:
db = Database()
version = db.version()
initted = db.check_init()
self.dbVersionLabel.setText(str(version))
self.dbCheckConnLabel.setText(
'<font color="green">OK</font>'
if initted
else '<font color="red">Not initted</font>'
)
self.continueButton.setEnabled(initted)
except Exception as e:
self.dbVersionLabel.setText("-")
self.dbCheckConnLabel.setText(
f'<font color="red">Error: {e}</font>'
if result & DatabaseCheckerResult.FileExist
else "-"
)
self.continueButton.setEnabled(False)
@Slot()
def on_confirmDbPathButton_clicked(self):
dbSqliteUrl = self.dbSqliteUrl()
self.settings.setDatabaseUrl(dbSqliteUrl.toString())
result = self.confirmDb()
if result & DatabaseCheckerResult.Initted:
if not self.confirmDbByExistingSettings:
self.settings.setDatabaseUrl(dbSqliteUrl.toString())
elif result & DatabaseCheckerResult.FileExist:
confirm_try_init = QMessageBox.question(
self,
None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.tryInitExistingDatabase"),
# fmt: on
)
if confirm_try_init == QMessageBox.StandardButton.Yes:
try:
Database().init(checkfirst=True)
except Exception as e:
logger.exception("Error while initializing an existing database")
QMessageBox.critical(
self, None, "\n".join(traceback.format_exception(e))
)
else:
confirm_new_database = QMessageBox.question(
self,
None,
# fmt: off
QCoreApplication.translate("DatabaseChecker", "dialog.confirmNewDatabase"),
# fmt: on
)
if confirm_new_database == QMessageBox.StandardButton.Yes:
db = Database(create_engine(dbSqliteUrl))
db.init()
self.updateLabels()
@Slot()
def on_dbReInitButton_clicked(self):
Database().init(checkfirst=True)
QMessageBox.information(self, None, "OK")
@Slot()
def on_continueButton_clicked(self):
self.accept()

View File

@ -1,154 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DatabaseChecker</class>
<widget class="QWidget" name="DatabaseChecker">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>250</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">DatabaseChecker</string>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>dbPathLabel</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="FileSelector" name="dbDirSelector" native="true"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>dbFilenameLabel</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="dbFilenameLineEdit"/>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="confirmDbPathButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>confirmDbPathButton</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="1">
<widget class="QLabel" name="dbVersionLabel">
<property name="text">
<string notr="true">-</string>
</property>
</widget>
</item>
<item row="6" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>dbCheckConnLabel</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="dbCheckConnLabel">
<property name="text">
<string notr="true">...</string>
</property>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QPushButton" name="continueButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>continueButton</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>dbVersionLabel</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>dbReInitLabel</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QPushButton" name="dbReInitButton">
<property name="text">
<string>dbReInitButton</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>FileSelector</class>
<extends>QWidget</extends>
<header>ui.implements.components.fileSelector</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -1,138 +0,0 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'databaseChecker.ui'
##
## Created by: Qt User Interface Compiler version 6.5.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QFormLayout, QFrame, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QSizePolicy,
QSpacerItem, QWidget)
from ui.implements.components.fileSelector import FileSelector
class Ui_DatabaseChecker(object):
def setupUi(self, DatabaseChecker):
if not DatabaseChecker.objectName():
DatabaseChecker.setObjectName(u"DatabaseChecker")
DatabaseChecker.resize(350, 250)
DatabaseChecker.setWindowTitle(u"DatabaseChecker")
self.formLayout = QFormLayout(DatabaseChecker)
self.formLayout.setObjectName(u"formLayout")
self.formLayout.setLabelAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
self.label = QLabel(DatabaseChecker)
self.label.setObjectName(u"label")
self.formLayout.setWidget(0, QFormLayout.LabelRole, self.label)
self.dbDirSelector = FileSelector(DatabaseChecker)
self.dbDirSelector.setObjectName(u"dbDirSelector")
self.formLayout.setWidget(0, QFormLayout.FieldRole, self.dbDirSelector)
self.label_3 = QLabel(DatabaseChecker)
self.label_3.setObjectName(u"label_3")
self.formLayout.setWidget(1, QFormLayout.LabelRole, self.label_3)
self.dbFilenameLineEdit = QLineEdit(DatabaseChecker)
self.dbFilenameLineEdit.setObjectName(u"dbFilenameLineEdit")
self.formLayout.setWidget(1, QFormLayout.FieldRole, self.dbFilenameLineEdit)
self.horizontalLayout = QHBoxLayout()
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(self.horizontalSpacer)
self.confirmDbPathButton = QPushButton(DatabaseChecker)
self.confirmDbPathButton.setObjectName(u"confirmDbPathButton")
sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.confirmDbPathButton.sizePolicy().hasHeightForWidth())
self.confirmDbPathButton.setSizePolicy(sizePolicy)
self.horizontalLayout.addWidget(self.confirmDbPathButton)
self.formLayout.setLayout(2, QFormLayout.FieldRole, self.horizontalLayout)
self.dbVersionLabel = QLabel(DatabaseChecker)
self.dbVersionLabel.setObjectName(u"dbVersionLabel")
self.dbVersionLabel.setText(u"-")
self.formLayout.setWidget(4, QFormLayout.FieldRole, self.dbVersionLabel)
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
self.formLayout.setItem(6, QFormLayout.FieldRole, self.verticalSpacer)
self.label_5 = QLabel(DatabaseChecker)
self.label_5.setObjectName(u"label_5")
self.formLayout.setWidget(7, QFormLayout.LabelRole, self.label_5)
self.dbCheckConnLabel = QLabel(DatabaseChecker)
self.dbCheckConnLabel.setObjectName(u"dbCheckConnLabel")
self.dbCheckConnLabel.setText(u"...")
self.formLayout.setWidget(7, QFormLayout.FieldRole, self.dbCheckConnLabel)
self.continueButton = QPushButton(DatabaseChecker)
self.continueButton.setObjectName(u"continueButton")
self.continueButton.setEnabled(False)
self.formLayout.setWidget(8, QFormLayout.SpanningRole, self.continueButton)
self.label_2 = QLabel(DatabaseChecker)
self.label_2.setObjectName(u"label_2")
self.formLayout.setWidget(4, QFormLayout.LabelRole, self.label_2)
self.line = QFrame(DatabaseChecker)
self.line.setObjectName(u"line")
self.line.setFrameShape(QFrame.HLine)
self.line.setFrameShadow(QFrame.Sunken)
self.formLayout.setWidget(3, QFormLayout.SpanningRole, self.line)
self.label_4 = QLabel(DatabaseChecker)
self.label_4.setObjectName(u"label_4")
self.formLayout.setWidget(5, QFormLayout.LabelRole, self.label_4)
self.dbReInitButton = QPushButton(DatabaseChecker)
self.dbReInitButton.setObjectName(u"dbReInitButton")
self.formLayout.setWidget(5, QFormLayout.FieldRole, self.dbReInitButton)
self.retranslateUi(DatabaseChecker)
QMetaObject.connectSlotsByName(DatabaseChecker)
# setupUi
def retranslateUi(self, DatabaseChecker):
self.label.setText(QCoreApplication.translate("DatabaseChecker", u"dbPathLabel", None))
self.label_3.setText(QCoreApplication.translate("DatabaseChecker", u"dbFilenameLabel", None))
self.confirmDbPathButton.setText(QCoreApplication.translate("DatabaseChecker", u"confirmDbPathButton", None))
self.label_5.setText(QCoreApplication.translate("DatabaseChecker", u"dbCheckConnLabel", None))
self.continueButton.setText(QCoreApplication.translate("DatabaseChecker", u"continueButton", None))
self.label_2.setText(QCoreApplication.translate("DatabaseChecker", u"dbVersionLabel", None))
self.label_4.setText(QCoreApplication.translate("DatabaseChecker", u"dbReInitLabel", None))
self.dbReInitButton.setText(QCoreApplication.translate("DatabaseChecker", u"dbReInitButton", None))
pass
# retranslateUi

3
ui/theme/__init__.py Normal file
View File

@ -0,0 +1,3 @@
from .manager import ThemeManager
__all__ = ["ThemeManager"]

160
ui/theme/manager.py Normal file
View File

@ -0,0 +1,160 @@
import dataclasses
import json
from typing import overload
import structlog
from PySide6.QtCore import Property, QObject, QResource, Qt, Signal
from PySide6.QtGui import QColor, QGuiApplication, QPalette
from .material3 import Material3DynamicThemeImpl, Material3ThemeImpl
from .qml import ThemeQmlExposer
from .shared import ThemeImpl, ThemeInfo, _TCustomPalette, _TScheme
QML_IMPORT_NAME = "internal.ui.theme"
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0
_THEME_CACHES: dict[ThemeInfo, ThemeImpl] = {}
logger: structlog.stdlib.BoundLogger = structlog.get_logger()
class ThemeManager(QObject):
_void = Signal()
themeChanged = Signal()
def __init__(self, parent=None):
super().__init__(parent)
self._qPalette = QPalette()
self._customPalette: _TCustomPalette = {
"primary": QColor.fromString("#616161"),
"success": QColor.fromString("#616161"),
"error": QColor.fromString("#616161"),
}
self._lastThemeInfo = ThemeInfo(
series="material3",
name="default",
scheme=self.getCurrentScheme(),
)
self._qmlExposer = ThemeQmlExposer(themeImpl=ThemeImpl())
self._cacheMaterial3Theme(themeName="default", scheme="light")
self._cacheMaterial3Theme(themeName="default", scheme="dark")
self._cacheMaterial3Theme(themeName="tempest", scheme="light")
self._cacheMaterial3Theme(themeName="tempest", scheme="dark")
self._cacheMaterial3DynamicTheme(themeName="default", scheme="light")
self._cacheMaterial3DynamicTheme(themeName="default", scheme="dark")
self._cacheMaterial3DynamicTheme(themeName="tempest", scheme="light")
self._cacheMaterial3DynamicTheme(themeName="tempest", scheme="dark")
self.setTheme("material3-dynamic", "default")
def getCurrentScheme(self) -> _TScheme:
qApp: QGuiApplication = QGuiApplication.instance() # pyright: ignore[reportAssignmentType]
return (
"dark"
if qApp.styleHints().colorScheme() == Qt.ColorScheme.Dark
else "light"
)
def getMaterial3Theme(self, themeName: str, scheme: _TScheme) -> Material3ThemeImpl:
themeDataResource = QResource(f":/themes/{themeName}.json")
if not themeDataResource.isValid():
raise ValueError(f"Material3 theme {themeName!r} not found")
themeData = json.loads(
themeDataResource.uncompressedData().data().decode("utf-8")
)
return Material3ThemeImpl(themeData=themeData, scheme=scheme)
def getMaterial3DynamicTheme(
self, themeName: str, scheme: _TScheme
) -> Material3DynamicThemeImpl:
themeDataResource = QResource(f":/themes/{themeName}.json")
if not themeDataResource.isValid():
raise ValueError(f"Material3 theme {themeName!r} not found")
themeData = json.loads(
themeDataResource.uncompressedData().data().decode("utf-8")
)
return Material3DynamicThemeImpl(
sourceColorHex=themeData["seed"],
scheme=scheme,
name=themeName,
)
def _cacheTheme(self, *, themeImpl: ThemeImpl):
_THEME_CACHES[themeImpl.info] = themeImpl
logger.debug("Theme %r cached", themeImpl.info)
def _getCachedTheme(self, *, themeInfo: ThemeInfo):
cachedTheme = _THEME_CACHES.get(themeInfo)
if cachedTheme is None:
raise KeyError(f"Theme {themeInfo!r} not cached")
return cachedTheme
def _cacheMaterial3Theme(self, *, themeName: str, scheme: _TScheme):
self._cacheTheme(
themeImpl=self.getMaterial3Theme(themeName=themeName, scheme=scheme),
)
def _cacheMaterial3DynamicTheme(self, *, themeName: str, scheme: _TScheme):
self._cacheTheme(
themeImpl=self.getMaterial3DynamicTheme(themeName=themeName, scheme=scheme)
)
@overload
def setTheme(self, *, themeInfo: ThemeInfo): ...
@overload
def setTheme(
self, themeSeries: str, themeName: str, scheme: _TScheme | None = None, /
): ...
def setTheme(self, *args, **kwargs):
if "themeInfo" in kwargs:
themeInfo = kwargs["themeInfo"]
elif 2 <= len(args) <= 3:
themeSeries = args[0]
themeName = args[1]
schemeArg = args[2] if len(args) > 2 else None
scheme = schemeArg or self.getCurrentScheme()
themeInfo = ThemeInfo(series=themeSeries, name=themeName, scheme=scheme)
else:
raise TypeError("Invalid setTheme() call")
logger.debug("Preparing to set theme %r", themeInfo)
cachedTheme = self._getCachedTheme(themeInfo=themeInfo)
self._qPalette = cachedTheme.qPalette
self._customPalette = cachedTheme.customPalette
self._lastThemeInfo = themeInfo
self._qmlExposer.themeImpl = cachedTheme
self.themeChanged.emit()
def updateTheme(self, scheme: _TScheme | None = None):
themeInfo = dataclasses.replace(self._lastThemeInfo) # make a copy
scheme = scheme or self.getCurrentScheme()
themeInfo.scheme = scheme
self.setTheme(themeInfo=themeInfo)
@Property(QPalette, notify=themeChanged)
def qPalette(self) -> QPalette:
return self._qPalette
@Property(dict, notify=themeChanged)
def customPalette(self) -> _TCustomPalette:
return self._customPalette
@Property(ThemeQmlExposer, notify=themeChanged)
def qmlExposer(self) -> ThemeQmlExposer:
return self._qmlExposer

245
ui/theme/material3.py Normal file
View File

@ -0,0 +1,245 @@
from typing import TypedDict
from materialyoucolor.blend import Blend
from materialyoucolor.dynamiccolor.contrast_curve import ContrastCurve
from materialyoucolor.dynamiccolor.dynamic_color import DynamicColor, FromPaletteOptions
from materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors
from materialyoucolor.hct import Hct
from materialyoucolor.palettes.tonal_palette import TonalPalette
from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot
from PySide6.QtGui import QColor, QPalette
from .shared import ThemeImpl, ThemeInfo, _TCustomPalette, _TScheme
class _M3ThemeDataExtendedColorItem(TypedDict):
name: str
color: str
description: str
harmonized: bool
_M3ThemeDataSchemes = TypedDict(
"_M3ThemeDataSchemes",
{
"light": dict[str, str],
"light-medium-contrast": dict[str, str],
"light-high-contrast": dict[str, str],
"dark": dict[str, str],
"dark-medium-contrast": dict[str, str],
"dark-high-contrast": dict[str, str],
},
)
_M3ThemeDataPalettes = TypedDict(
"_M3ThemeDataPalettes",
{
"primary": dict[str, str],
"secondary": dict[str, str],
"tertiary": dict[str, str],
"neutral": dict[str, str],
"neutral-variant": dict[str, str],
},
)
class _M3ThemeData(TypedDict):
name: str
description: str
seed: str
coreColors: dict[str, str]
extendedColors: list[_M3ThemeDataExtendedColorItem]
schemes: _M3ThemeDataSchemes
palettes: _M3ThemeDataPalettes
def _hexToHct(hexColor: str) -> Hct:
pureHexPart = hexColor[1:] if hexColor.startswith("#") else hexColor
return Hct.from_int(int(f"0xff{pureHexPart}", 16))
def _hctToQColor(hct: Hct) -> QColor:
return QColor.fromRgba(hct.to_int())
class Material3ThemeImpl(ThemeImpl):
COLOR_ROLE_MAPPING: dict[QPalette.ColorRole, str] = {
QPalette.ColorRole.Window: "surface",
QPalette.ColorRole.WindowText: "onSurface",
QPalette.ColorRole.Base: "surfaceContainer",
QPalette.ColorRole.AlternateBase: "surfaceContainerHighest",
QPalette.ColorRole.ToolTipBase: "secondaryContainer",
QPalette.ColorRole.ToolTipText: "onSecondaryContainer",
QPalette.ColorRole.PlaceholderText: "inverseSurface",
QPalette.ColorRole.Text: "onSurface",
QPalette.ColorRole.Button: "primaryContainer",
QPalette.ColorRole.ButtonText: "onPrimaryContainer",
QPalette.ColorRole.BrightText: "onSecondary",
QPalette.ColorRole.Light: "surfaceContainerLowest",
QPalette.ColorRole.Midlight: "surfaceContainerLow",
QPalette.ColorRole.Dark: "inverseSurface",
QPalette.ColorRole.Mid: "surfaceContainer",
QPalette.ColorRole.Shadow: "shadow",
QPalette.ColorRole.Highlight: "primary",
QPalette.ColorRole.Accent: "primary",
QPalette.ColorRole.HighlightedText: "onPrimary",
QPalette.ColorRole.Link: "tertiary",
QPalette.ColorRole.LinkVisited: "tertiaryContainer",
}
def __init__(self, *, themeData: _M3ThemeData, scheme: _TScheme):
self.themeData = themeData
self.scheme: _TScheme = scheme
if self.themeData["schemes"].get(scheme) is None:
raise ValueError(f"Invalid scheme: {scheme}")
def _findExtendedColor(
self, colorName: str
) -> _M3ThemeDataExtendedColorItem | None:
return next(
(it for it in self.themeData["extendedColors"] if it["name"] == colorName),
None,
)
@property
def info(self):
return ThemeInfo(
series="material3",
name=self.themeData["name"],
scheme=self.scheme,
)
@property
def qPalette(self) -> QPalette:
qPalette = QPalette()
for role, name in self.COLOR_ROLE_MAPPING.items():
color = QColor.fromString(self.themeData["schemes"][self.scheme][name])
qPalette.setColor(role, color)
return qPalette
@property
def customPalette(self) -> _TCustomPalette:
primaryHct = _hexToHct(self.themeData["schemes"][self.scheme]["primary"])
successColorItem = self._findExtendedColor("Success")
if successColorItem is None:
raise Exception("Success color not found")
successHct = _hexToHct(successColorItem["color"])
successHarmonizedHct = Hct.from_int(
Blend.harmonize(successHct.to_int(), primaryHct.to_int())
)
return {
"primary": _hctToQColor(primaryHct),
"success": _hctToQColor(successHarmonizedHct),
"error": QColor.fromString(self.themeData["schemes"][self.scheme]["error"]),
}
class Material3DynamicThemeImpl(ThemeImpl):
ACTIVE_COLOR_ROLE_MAPPING: dict[QPalette.ColorRole, DynamicColor] = {
QPalette.ColorRole.Window: MaterialDynamicColors.surface,
QPalette.ColorRole.WindowText: MaterialDynamicColors.onSurface,
QPalette.ColorRole.Base: MaterialDynamicColors.surfaceContainer,
QPalette.ColorRole.AlternateBase: MaterialDynamicColors.surfaceContainerHighest,
QPalette.ColorRole.ToolTipBase: MaterialDynamicColors.secondaryContainer,
QPalette.ColorRole.ToolTipText: MaterialDynamicColors.onSecondaryContainer,
QPalette.ColorRole.PlaceholderText: MaterialDynamicColors.inverseSurface,
QPalette.ColorRole.Text: MaterialDynamicColors.onSurface,
QPalette.ColorRole.Button: MaterialDynamicColors.primaryContainer,
QPalette.ColorRole.ButtonText: MaterialDynamicColors.onPrimaryContainer,
QPalette.ColorRole.BrightText: MaterialDynamicColors.onSecondary,
QPalette.ColorRole.Light: MaterialDynamicColors.surfaceContainerLowest,
QPalette.ColorRole.Midlight: MaterialDynamicColors.surfaceContainerLow,
QPalette.ColorRole.Dark: MaterialDynamicColors.inverseSurface,
QPalette.ColorRole.Mid: MaterialDynamicColors.surfaceContainer,
QPalette.ColorRole.Shadow: MaterialDynamicColors.shadow,
QPalette.ColorRole.Highlight: MaterialDynamicColors.primary,
QPalette.ColorRole.Accent: MaterialDynamicColors.primary,
QPalette.ColorRole.HighlightedText: MaterialDynamicColors.onPrimary,
QPalette.ColorRole.Link: MaterialDynamicColors.tertiary,
QPalette.ColorRole.LinkVisited: MaterialDynamicColors.tertiaryContainer,
}
EXTENDED_COLORS = {
"success": "#00c555",
}
def __init__(self, sourceColorHex: str, scheme: _TScheme, *, name: str):
self.material3Scheme = SchemeTonalSpot(
_hexToHct(sourceColorHex),
is_dark=scheme == "dark",
contrast_level=0.0,
)
self.name = name
@property
def info(self):
return ThemeInfo(
series="material3-dynamic",
name=self.name,
scheme="dark" if self.material3Scheme.is_dark else "light",
)
@property
def qPalette(self) -> QPalette:
qPalette = QPalette()
for role, dynamicColor in self.ACTIVE_COLOR_ROLE_MAPPING.items():
hct = dynamicColor.get_hct(self.material3Scheme)
qColor = QColor.fromRgba(hct.to_int())
qPalette.setColor(QPalette.ColorGroup.Active, role, qColor)
# TODO: disabled palette seems to work only after a theme reload, needs further investigation
if role in [QPalette.ColorRole.Button, QPalette.ColorRole.ButtonText]:
disabledHct = Hct.from_hct(hct.hue, 1.0, hct.tone)
disabledQColor = QColor.fromRgba(disabledHct.to_int())
qPalette.setColor(QPalette.ColorGroup.Disabled, role, disabledQColor)
return qPalette
@property
def customPalette(self) -> _TCustomPalette:
primaryHct = MaterialDynamicColors.primary.get_hct(self.material3Scheme)
errorHct = MaterialDynamicColors.error.get_hct(self.material3Scheme)
extendedPalettes: dict[str, DynamicColor] = {}
for colorName, colorHex in self.EXTENDED_COLORS.items():
colorHct = _hexToHct(colorHex)
colorHarmonized = Blend.harmonize(colorHct.to_int(), primaryHct.to_int())
colorTonalPalette = TonalPalette.from_int(colorHarmonized)
colorSurfacePaletteOptions = DynamicColor.from_palette(
FromPaletteOptions(
name=f"{colorName}_container",
palette=lambda s: colorTonalPalette,
tone=lambda s: 30 if s.is_dark else 90,
is_background=True,
background=lambda s: MaterialDynamicColors.highestSurface(s),
contrast_curve=ContrastCurve(1, 1, 3, 4.5),
)
)
extendedPalettes[colorName] = DynamicColor.from_palette(
FromPaletteOptions(
name=colorName, # pyright: ignore[reportArgumentType]
palette=lambda s: colorTonalPalette,
tone=lambda s: 80 if s.is_dark else 40, # pyright: ignore[reportArgumentType]
is_background=False, # pyright: ignore[reportArgumentType]
background=lambda s: MaterialDynamicColors.highestSurface(s),
contrast_curve=ContrastCurve(3, 4.5, 7, 7),
)
)
return {
"primary": _hctToQColor(primaryHct),
"success": _hctToQColor(
extendedPalettes["success"].get_hct(self.material3Scheme)
),
"error": _hctToQColor(errorHct),
}

37
ui/theme/qml.py Normal file
View File

@ -0,0 +1,37 @@
from PySide6.QtCore import Property, QObject, Signal
from PySide6.QtGui import QColor
from .shared import ThemeImpl
QML_IMPORT_NAME = "internal.ui.theme"
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0
class ThemeQmlExposer(QObject):
themeChanged = Signal()
def __init__(self, *, themeImpl: ThemeImpl, parent: QObject | None = None):
super().__init__(parent)
self._themeImpl = themeImpl
@property
def themeImpl(self) -> ThemeImpl:
return self._themeImpl
@themeImpl.setter
def themeImpl(self, themeImpl: ThemeImpl):
self._themeImpl = themeImpl
self.themeChanged.emit()
@Property(QColor, notify=themeChanged)
def primary(self):
return self._themeImpl.customPalette["primary"]
@Property(QColor, notify=themeChanged)
def success(self):
return self._themeImpl.customPalette["success"]
@Property(QColor, notify=themeChanged)
def error(self):
return self._themeImpl.customPalette["error"]

43
ui/theme/shared.py Normal file
View File

@ -0,0 +1,43 @@
from dataclasses import dataclass
from typing import Literal, TypedDict
from PySide6.QtGui import QColor, QPalette
class _TCustomPalette(TypedDict):
primary: QColor
success: QColor
error: QColor
_TScheme = Literal["light", "dark"]
@dataclass
class ThemeInfo:
series: str
name: str
scheme: _TScheme
def __hash__(self) -> int:
return hash((self.series, self.name, self.scheme))
class ThemeImpl:
DEFAULT_CUSTOM_PALETTE = {
"primary": QColor.fromString("#616161"),
"success": QColor.fromString("#616161"),
"error": QColor.fromString("#616161"),
}
@property
def info(self) -> ThemeInfo:
return ThemeInfo(series="placeholder", name="placeholder", scheme="dark")
@property
def qPalette(self) -> QPalette:
return QPalette()
@property
def customPalette(self) -> _TCustomPalette:
return self.DEFAULT_CUSTOM_PALETTE # pyright: ignore[reportReturnType]

0
ui/utils/__init__.py Normal file
View File

1
ui/utils/common.py Normal file
View File

@ -0,0 +1 @@
UTILS_QML_IMPORT_NAME = "internal.ui.utils"

48
ui/utils/url.py Normal file
View File

@ -0,0 +1,48 @@
from pathlib import Path
from PySide6.QtCore import QFileInfo, QObject, QUrl, Slot
from PySide6.QtQml import QmlElement, QmlSingleton
from .common import UTILS_QML_IMPORT_NAME
QML_IMPORT_NAME = UTILS_QML_IMPORT_NAME
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0
@QmlElement
@QmlSingleton
class UrlUtils(QObject):
@Slot(str, result=bool)
@staticmethod
def isDir(url: str):
return QFileInfo(QUrl(url).toLocalFile()).isDir()
@Slot(str, result=bool)
@staticmethod
def isFile(url: str):
return QFileInfo(QUrl(url).toLocalFile()).isFile()
@Slot(str, result=str)
@staticmethod
def stem(url: str):
return Path(QUrl(url).toLocalFile()).stem
@Slot(str, result=str)
@staticmethod
def name(url: str):
return Path(QUrl(url).toLocalFile()).name
@Slot(str, result=QUrl)
@staticmethod
def parent(url: str):
return QUrl.fromLocalFile(Path(QUrl(url).toLocalFile()).parent)
@QmlElement
@QmlSingleton
class UrlFormatUtils(QObject):
@Slot(str, result=str)
@staticmethod
def toLocalFile(url: str):
return QUrl(url).toLocalFile()

View File

@ -0,0 +1,2 @@
from .databaseInit import DatabaseInitViewModel
from .overview import OverviewViewModel

1
ui/viewmodels/common.py Normal file
View File

@ -0,0 +1 @@
VM_QML_IMPORT_NAME = "internal.ui.vm"

View File

@ -0,0 +1,369 @@
import dataclasses
from enum import StrEnum
from pathlib import Path
import structlog
from arcaea_offline.models import (
CalculatedPotential,
Chart,
ConfigBase,
ScoreBest,
ScoreCalculated,
ScoresBase,
ScoresViewBase,
SongsBase,
SongsViewBase,
)
from arcaea_offline.models import (
Property as AoProperty,
)
from PySide6.QtCore import Property, QObject, QUrl, Signal, Slot
from PySide6.QtQml import QmlElement
from sqlalchemy import inspect, select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from core.database import create_engine, db_path_to_sqlite_url, sqlite_url_to_db_path
from core.settings import SettingsKeys, settings
from core.settings.values import GeneralDatabaseType
from .common import VM_QML_IMPORT_NAME
logger: structlog.stdlib.BoundLogger = structlog.get_logger()
QML_IMPORT_NAME = VM_QML_IMPORT_NAME
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0
class _FmwiwsDatabase:
"""Fuck Me Why I Wrote Singleton Database"""
def __init__(self, url: str):
self.engine = create_engine(url)
def init(self, *, checkfirst: bool = True):
# create tables & views
if checkfirst:
# > https://github.com/kvesteri/sqlalchemy-utils/issues/396
# > view.create_view() causes DuplicateTableError on
# > Base.metadata.create_all(checkfirst=True)
# so if `checkfirst` is True, drop these views before creating
SongsViewBase.metadata.drop_all(self.engine)
ScoresViewBase.metadata.drop_all(self.engine)
SongsBase.metadata.create_all(self.engine, checkfirst=checkfirst)
SongsViewBase.metadata.create_all(self.engine)
ScoresBase.metadata.create_all(self.engine, checkfirst=checkfirst)
ScoresViewBase.metadata.create_all(self.engine)
ConfigBase.metadata.create_all(self.engine, checkfirst=checkfirst)
version_property = AoProperty(key="version", value="4")
with Session(self.engine) as session:
session.merge(version_property)
session.commit()
def is_initialized(self):
expect_tables = (
list(SongsBase.metadata.tables.keys())
+ list(ScoresBase.metadata.tables.keys())
+ list(ConfigBase.metadata.tables.keys())
+ [
Chart.__tablename__,
ScoreCalculated.__tablename__,
ScoreBest.__tablename__,
CalculatedPotential.__tablename__,
]
)
return all(inspect(self.engine).has_table(t) for t in expect_tables)
def version(self):
with Session(self.engine) as session:
stmt = select(AoProperty.value).where(AoProperty.key == "version")
result = session.scalar(stmt)
return None if result is None else int(result)
class _UiMode(StrEnum):
SELECT = "select"
CREATE = "create"
@dataclasses.dataclass
class DatabaseInfo:
url: str
error: Exception | None = None
initialized: bool | None = None
version: int | None = None
@property
def error_dict(self):
e = self.error
if e is None:
return None
return {
"title": e.__class__.__name__,
"message": str(e),
}
def to_qml_dict(self):
return {
"url": self.url,
"error": self.error_dict,
"initialized": self.initialized,
"version": self.version,
}
@QmlElement
class DatabaseInitViewModel(QObject):
_void = Signal()
uiModeChanged = Signal()
selectFileUrlChanged = Signal()
directoryUrlChanged = Signal()
filenameChanged = Signal()
databaseUrlChanged = Signal()
databaseInfoChanged = Signal()
canContinueChanged = Signal()
canConfirmSilentlyChanged = Signal()
def __init__(self):
super().__init__()
self._selectFileUrl: QUrl | None = None
self._directoryUrl: QUrl = QUrl()
self._filename: str = ""
self._databaseInfo: DatabaseInfo | None = None
self._uiMode = _UiMode.SELECT
self.directoryUrlChanged.connect(lambda: self.databaseUrlChanged.emit())
self.filenameChanged.connect(lambda: self.databaseUrlChanged.emit())
self.selectFileUrlChanged.connect(self.databaseUrlChanged)
self.databaseInfoChanged.connect(self.canContinueChanged)
self.databaseUrlChanged.connect(self.onDatabaseUrlChanged)
self._loadSettings()
def onDatabaseUrlChanged(self):
self.loadDatabaseInfo()
@property
def _settingsDatabaseFile(self) -> Path | None:
# TODO: process database type when available
if (
settings.stringValue(SettingsKeys.General.DatabaseType)
!= GeneralDatabaseType.FILE
):
return None
file = settings.stringValue(SettingsKeys.General.DatabaseConn)
if not file:
logger.debug("No database file specified in settings")
return
filepath = Path(file)
if not filepath.exists():
logger.warning("Cannot find database file: %s", file)
return
return filepath
def _loadSettings(self) -> None:
fileUrl = self._settingsDatabaseFile
if fileUrl is None:
return
logger.info("Loading database from settings: %s", fileUrl)
self.setUiMode(_UiMode.SELECT)
self.setSelectFileUrl(fileUrl)
self.loadDatabaseInfo()
def _makeDatabaseInfo(self, dbUrl: str):
info = DatabaseInfo(url=dbUrl)
path = sqlite_url_to_db_path(dbUrl)
if not path.exists():
e = FileNotFoundError()
e.strerror = f"{path} does not exist"
info.error = e
return info
try:
db = _FmwiwsDatabase(dbUrl)
info.initialized = db.is_initialized()
info.version = db.version()
except SQLAlchemyError as e:
logger.exception("Error loading database info")
info.error = e
logger.debug("Database info for %r: %r", dbUrl, info)
return info
@Slot()
def loadDatabaseInfo(self):
dbUrl = self.getDatabaseUrl()
logger.info("Loading database info: %s", dbUrl)
if dbUrl is None:
logger.warning("Database URL is None")
return
self._databaseInfo = self._makeDatabaseInfo(dbUrl.toString())
self.databaseInfoChanged.emit()
@Slot(str)
def createFile(self, dbUrl: str):
file = sqlite_url_to_db_path(dbUrl)
if file.exists():
logger.warning(
"Attempted to create an existing file, check UI logic? (%s)", file
)
return
file.touch(mode=0o644)
logger.info("Created file %s", file)
@Slot()
def confirmCurrentConnection(self):
dbInfo = self._databaseInfo
if dbInfo is None:
logger.warning("Current database info is None, ignoring")
return
settings.setValue(
SettingsKeys.General.DatabaseType,
GeneralDatabaseType.FILE.value,
)
settings.setValue(
SettingsKeys.General.DatabaseConn,
str(sqlite_url_to_db_path(dbInfo.url).resolve().as_posix()),
)
@Slot(str)
def initialize(self, dbUrl: str):
try:
db = _FmwiwsDatabase(dbUrl)
db.init()
except SQLAlchemyError:
logger.exception("Error initializing database %s", dbUrl)
# region properties
def getUiMode(self):
return self._uiMode.value
def setUiMode(self, mode: str | _UiMode):
if isinstance(mode, _UiMode):
self._uiMode = mode
elif isinstance(mode, str):
try:
self._uiMode = _UiMode(mode)
except ValueError:
logger.warning("Invalid UI mode: %s", mode)
self.uiModeChanged.emit()
def getSelectFileUrl(self):
return self._selectFileUrl
def setSelectFileUrl(self, value: Path | QUrl | None):
if isinstance(value, Path):
value = QUrl.fromLocalFile(value.as_posix())
self._selectFileUrl = value
self.selectFileUrlChanged.emit()
def getDirectoryUrl(self):
return self._directoryUrl
def setDirectoryUrl(self, value: QUrl | None):
self._directoryUrl = value or QUrl()
self.directoryUrlChanged.emit()
def getFilename(self):
return self._filename
def setFilename(self, value: str | None):
self._filename = value or ""
self.filenameChanged.emit()
def getDatabaseUrl(self):
if self._uiMode == _UiMode.SELECT:
fileUrl = self.getSelectFileUrl()
if fileUrl is None:
return None
return db_path_to_sqlite_url(Path(fileUrl.toLocalFile()))
directoryUrl = self.getDirectoryUrl()
filename = self.getFilename()
databasePath = Path(directoryUrl.toLocalFile()) / filename
databaseUrl = db_path_to_sqlite_url(databasePath)
return databaseUrl
def getDatabaseInfo(self):
if self._databaseInfo is None:
return {
"url": "",
"initialized": None,
"version": None,
"error": None,
}
return self._databaseInfo.to_qml_dict()
def getCanContinue(self):
return (
self._databaseInfo is not None
and self._databaseInfo.error is None
and self._databaseInfo.version == 4 # noqa: PLR2004
and self._databaseInfo.initialized
)
def getCanConfirmSilently(self):
"""Whether the user can confirm database connection without a dialog popping up"""
if self.getUiMode() != _UiMode.SELECT:
return False
filepath = self._settingsDatabaseFile
if filepath is None:
return False
return (
self._databaseInfo is not None
and self._databaseInfo.error is None
and self.getDatabaseUrl() == db_path_to_sqlite_url(filepath)
)
uiMode = Property(str, getUiMode, setUiMode, notify=uiModeChanged)
selectFileUrl = Property(
QUrl,
getSelectFileUrl,
setSelectFileUrl,
notify=selectFileUrlChanged,
)
directoryUrl = Property(
QUrl,
getDirectoryUrl,
setDirectoryUrl,
notify=directoryUrlChanged,
)
filename = Property(str, getFilename, setFilename, notify=filenameChanged)
databaseUrl = Property(QUrl, getDatabaseUrl, None, notify=databaseUrlChanged)
databaseInfo = Property(dict, getDatabaseInfo, None, notify=databaseInfoChanged)
canContinue = Property(bool, getCanContinue, None, notify=canContinueChanged)
canConfirmSilently = Property(
bool,
getCanConfirmSilently,
None,
notify=canConfirmSilentlyChanged,
)
# endregion

31
ui/viewmodels/overview.py Normal file
View File

@ -0,0 +1,31 @@
from PySide6.QtCore import Property, QObject, Signal, Slot
from PySide6.QtQml import QmlElement
from core.database import Database
QML_IMPORT_NAME = "internal.ui.vm"
QML_IMPORT_MAJOR_VERSION = 1
QML_IMPORT_MINOR_VERSION = 0
@QmlElement
class OverviewViewModel(QObject):
_void = Signal()
b30Changed = Signal()
def __init__(self):
super().__init__()
self._b30 = -1.0
self.reload()
@Slot()
def reload(self):
conn = Database()
b30 = conn.b30
self._b30 = b30 if b30 is not None else -1.0
self.b30Changed.emit()
@Property(float, fset=None)
def b30(self):
return self._b30

913
uv.lock generated Normal file
View File

@ -0,0 +1,913 @@
version = 1
revision = 3
requires-python = ">=3.9"
resolution-markers = [
"python_full_version >= '3.11' and sys_platform == 'darwin'",
"python_full_version >= '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version >= '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version == '3.10.*' and sys_platform == 'darwin'",
"python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
"python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
[[package]]
name = "arcaea-offline"
version = "0.2.2"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "beautifulsoup4" },
{ name = "sqlalchemy" },
{ name = "sqlalchemy-utils" },
{ name = "whoosh" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/9e/a6f86d8d852fef890f67f40bf5b867db32836f9fa267b49d1e3dd92629e7/arcaea-offline-0.2.2.tar.gz", hash = "sha256:0c747b630f5c8df5b0bdfc78d320b40a06f194dd8c0e4cc500700e8ca872617f", size = 33801, upload-time = "2024-03-31T16:20:16.014Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/21/cfd1651122abbabc2b3ac0c4cd22909b87e46e8e66860f0c182330ad8a40/arcaea_offline-0.2.2-py3-none-any.whl", hash = "sha256:59b6a55c4793419fa99fb019c39801953f83aa4a21a6105f3d4db5a823aba02d", size = 41812, upload-time = "2024-03-31T16:20:14.15Z" },
]
[[package]]
name = "arcaea-offline-ocr"
version = "0.0.99"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "attrs" },
{ name = "numpy" },
{ name = "opencv-python" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/7c/e5f89ee4a1e2221e5fa1d6b769d41c388d2470f3a29a628d30e7a65082da/arcaea_offline_ocr-0.0.99.tar.gz", hash = "sha256:9a3ab918de8e275e5190f27c7b4d5e6516ad355e3ebca79735ebd8ae95559126", size = 27521, upload-time = "2024-06-19T14:25:19.292Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/49/0f11fb00e7a75208fbfac799a5a77a4ace0ae2a8e5ffef791ca70ab6f262/arcaea_offline_ocr-0.0.99-py3-none-any.whl", hash = "sha256:f875da1a7e3f183fef3cfe3885635c40f3b4a9e3f9292dcffcdc008cad849fe3", size = 32165, upload-time = "2024-06-19T14:25:13.586Z" },
]
[[package]]
name = "arcaea-offline-pyside-ui"
version = "0.3.9"
source = { virtual = "." }
dependencies = [
{ name = "arcaea-offline" },
{ name = "arcaea-offline-ocr" },
{ name = "colorama" },
{ name = "exif" },
{ name = "materialyoucolor" },
{ name = "pillow" },
{ name = "pyside6" },
{ name = "rich" },
{ name = "structlog" },
]
[package.optional-dependencies]
dev = [
{ name = "imageio" },
{ name = "nuitka" },
{ name = "pre-commit" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "arcaea-offline", specifier = "==0.2.2" },
{ name = "arcaea-offline-ocr", specifier = "==0.0.99" },
{ name = "colorama", specifier = "~=0.4.6" },
{ name = "exif", specifier = "~=1.6.0" },
{ name = "imageio", marker = "extra == 'dev'" },
{ name = "materialyoucolor", specifier = "~=2.0.10" },
{ name = "nuitka", marker = "extra == 'dev'", specifier = "~=2.7.6" },
{ name = "pillow", specifier = "~=10.1.0" },
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.3.0" },
{ name = "pyside6", specifier = "==6.10.0" },
{ name = "rich", specifier = "~=14.2" },
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.14.2" },
{ name = "structlog", specifier = "~=25.4" },
]
provides-extras = ["dev"]
[[package]]
name = "attrs"
version = "23.1.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/90/81f95d5f705be17872843536b1868f351805acf6971251ff07c1b8334dbb/attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015", size = 212878, upload-time = "2023-04-16T10:48:18.214Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/eb/fcb708c7bf5056045e9e98f62b93bd7467eb718b0202e7698eb11d66416c/attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", size = 61160, upload-time = "2023-04-16T10:48:16.358Z" },
]
[[package]]
name = "beautifulsoup4"
version = "4.12.2"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "soupsieve" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/af/0b/44c39cf3b18a9280950ad63a579ce395dda4c32193ee9da7ff0aed547094/beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da", size = 505113, upload-time = "2023-04-07T15:02:49.038Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/f4/a69c20ee4f660081a7dedb1ac57f29be9378e04edfcb90c526b923d4bebc/beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a", size = 142979, upload-time = "2023-04-07T15:02:50.77Z" },
]
[[package]]
name = "cfgv"
version = "3.4.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "distlib"
version = "0.4.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
]
[[package]]
name = "exif"
version = "1.6.1"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "plum-py" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/52/21/34c46fbda54648afdcc66f0eec0ac91675361017abcdcdd23bd88fa74cc5/exif-1.6.1.tar.gz", hash = "sha256:763599b89b9b67495713060a703f32d1874abf8f0628c9d77711c2c06a5f44c8", size = 24052, upload-time = "2024-12-10T04:38:35.812Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/67/07/a47a78be02b0a2d273358312f3e2eecc4981d4604f8a6e6b8ef27c1e9c2d/exif-1.6.1-py3-none-any.whl", hash = "sha256:2879830e2d8f0e5f1503110736bceb83a1e9c2121b32c23208c04284be5afbec", size = 30461, upload-time = "2024-12-10T04:38:33.547Z" },
]
[[package]]
name = "filelock"
version = "3.19.1"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
resolution-markers = [
"python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
"python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" },
]
[[package]]
name = "filelock"
version = "3.20.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
resolution-markers = [
"python_full_version >= '3.11' and sys_platform == 'darwin'",
"python_full_version >= '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version >= '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version == '3.10.*' and sys_platform == 'darwin'",
"python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" },
]
[[package]]
name = "greenlet"
version = "3.2.4"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", size = 271061, upload-time = "2025-08-07T13:17:15.373Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", size = 629475, upload-time = "2025-08-07T13:42:54.009Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", size = 640802, upload-time = "2025-08-07T13:45:25.52Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", size = 636703, upload-time = "2025-08-07T13:53:12.622Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", size = 635417, upload-time = "2025-08-07T13:18:25.189Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", size = 584358, upload-time = "2025-08-07T13:18:23.708Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", size = 1113550, upload-time = "2025-08-07T13:42:37.467Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", size = 1137126, upload-time = "2025-08-07T13:18:20.239Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", size = 298654, upload-time = "2025-08-07T13:50:00.469Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f7/c0/93885c4106d2626bf51fdec377d6aef740dfa5c4877461889a7cf8e565cc/greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c", size = 269859, upload-time = "2025-08-07T13:16:16.003Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/f5/33f05dc3ba10a02dedb1485870cf81c109227d3d3aa280f0e48486cac248/greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d", size = 627610, upload-time = "2025-08-07T13:43:01.345Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/a7/9476decef51a0844195f99ed5dc611d212e9b3515512ecdf7321543a7225/greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58", size = 639417, upload-time = "2025-08-07T13:45:32.094Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/e0/849b9159cbb176f8c0af5caaff1faffdece7a8417fcc6fe1869770e33e21/greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4", size = 634751, upload-time = "2025-08-07T13:53:18.848Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/d3/844e714a9bbd39034144dca8b658dcd01839b72bb0ec7d8014e33e3705f0/greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433", size = 634020, upload-time = "2025-08-07T13:18:36.841Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/4c/f3de2a8de0e840ecb0253ad0dc7e2bb3747348e798ec7e397d783a3cb380/greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df", size = 582817, upload-time = "2025-08-07T13:18:35.48Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/80/7332915adc766035c8980b161c2e5d50b2f941f453af232c164cff5e0aeb/greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594", size = 1111985, upload-time = "2025-08-07T13:42:42.425Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/66/71/1928e2c80197353bcb9b50aa19c4d8e26ee6d7a900c564907665cf4b9a41/greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98", size = 1136137, upload-time = "2025-08-07T13:18:26.168Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/48/a5dc74dde38aeb2b15d418cec76ed50e1dd3d620ccda84d8199703248968/greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b", size = 281400, upload-time = "2025-08-07T14:02:20.263Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/44/342c4591db50db1076b8bda86ed0ad59240e3e1da17806a4cf10a6d0e447/greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb", size = 298533, upload-time = "2025-08-07T13:56:34.168Z" },
]
[[package]]
name = "identify"
version = "2.6.15"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" },
]
[[package]]
name = "imageio"
version = "2.37.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "numpy" },
{ name = "pillow" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload-time = "2025-01-20T02:42:37.089Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" },
]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
resolution-markers = [
"python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
"python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "mdurl", marker = "python_full_version < '3.10'" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" },
]
[[package]]
name = "markdown-it-py"
version = "4.0.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
resolution-markers = [
"python_full_version >= '3.11' and sys_platform == 'darwin'",
"python_full_version >= '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version >= '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version == '3.10.*' and sys_platform == 'darwin'",
"python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
dependencies = [
{ name = "mdurl", marker = "python_full_version >= '3.10'" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
]
[[package]]
name = "materialyoucolor"
version = "2.0.10"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/83/b3/8835bacf50ea32bf000f9118f60da4c8e5febe80ab5929be18ce99a4baea/materialyoucolor-2.0.10.tar.gz", hash = "sha256:31b4d407b9a4fd4b54b30559b0d0313f6d6da1f9d19b75546ed65da52671ea76", size = 250103, upload-time = "2025-01-09T07:56:43.05Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/52/36cf8bbb3989f31328ca2a06819b17c021052bcd97a7e703eadace1f225f/materialyoucolor-2.0.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4de08ee488369cf0e055ae17ac43c62684f618781939feaf0ad11bc32572a4b6", size = 316148, upload-time = "2025-01-09T09:02:52.364Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/96/4f3919588aa341f8152ad01de784924ac26059077b095069f2ca83525053/materialyoucolor-2.0.10-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ad22b0fb6980e0995d5b8437cad3411f628e1ea88c223c94a20b59d5dbcbacb2", size = 206497, upload-time = "2025-01-09T08:34:31.692Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5e/91/05303aa40da51a71c4e828781a0a34b5ba8c88a9a0f2eec245c1257fe82e/materialyoucolor-2.0.10-cp310-cp310-win_amd64.whl", hash = "sha256:997787ec4224f3e2d9c90e99d82db746f5581670564a22a0f182ad377e704761", size = 150420, upload-time = "2025-01-09T09:02:55.842Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/eb/78d067df3948bd6a347677c10d35d89b6092916f78e0b98c51d47fd080f5/materialyoucolor-2.0.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e9a4dd1e444a64ea54982be06314ce4611f3c38fdfae2c33cca7d106f6383288", size = 319432, upload-time = "2025-01-09T09:02:51.999Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/b1/04424f2bd811fe7e803d8a21cc28e8b4f9dea8b07cbc948318d4b4de3dd8/materialyoucolor-2.0.10-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:144fdd9dca272fee60a1779ce35acf24830cd3bc33ff20982ecba4936cb5bf91", size = 208262, upload-time = "2025-01-09T08:34:34.198Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/43/926ae82c04e5a5d02523f66e48af8021c0fdd8e01463a51e934137f7a5a0/materialyoucolor-2.0.10-cp311-cp311-win_amd64.whl", hash = "sha256:f126196a051673707297e86b8d0fbd90805a552dc3ac79e1d90442cc3fc877cb", size = 151620, upload-time = "2025-01-09T09:02:52.028Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/69/e8/847974020cde0e09124d7fb51bf41aa92cc3a9cad83f83911e5d67c1d665/materialyoucolor-2.0.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0fc0b43a684536270177b71bf509b247dab4ff816e49d1ac1b124fc1a32e082e", size = 318810, upload-time = "2025-01-09T09:02:52.905Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/67/2f3e70d707f86be3c541327693db5105622aa6aa1079d48ff90876d2e650/materialyoucolor-2.0.10-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fe5bbc63124c3ea2f5560ea4a5250af72fce9ab55d29925e6bac7c7ec65329d", size = 208373, upload-time = "2025-01-09T08:34:35.656Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/f2/1119e97e741b33717e26c494b4aa3c5ca57d770df4fa42707b698bfb7588/materialyoucolor-2.0.10-cp312-cp312-win_amd64.whl", hash = "sha256:aedc8596fd7583fd9e23f3e6d4f7b9defd7d5ef6e7889967814b9900c1e3d5b3", size = 151716, upload-time = "2025-01-09T09:02:57.1Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/49/c4517231ec21bdd91a301cb40fdf86c632a95b10d86bc3278bd5d619480c/materialyoucolor-2.0.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:71de9c581c8227c3010c61108a00af9a007c96f99358fdb0628626d0ade817d9", size = 318890, upload-time = "2025-01-09T09:02:52.276Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4d/62/807bea1db749dc45debc2afa51ea4b27f5a219fbe73f6458c2bae2a15427/materialyoucolor-2.0.10-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:978748eca7725d8b569cdecfd9f86a8e34bf946182f79a48b059c98a10f1a0de", size = 208339, upload-time = "2025-01-09T08:34:38.213Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/01/05075b959b228c5c99c80703b7c63532ac6ee49abdf8054077e70908d6e3/materialyoucolor-2.0.10-cp313-cp313-win_amd64.whl", hash = "sha256:2ffc8963a2f10d8acb6776e60e97f0e7929d3392eb282cf2bc63b8e56cd930b3", size = 151751, upload-time = "2025-01-09T09:02:53.299Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/56/29e59a5bcc92f0dc0a1de728e711edfcd971a6ef269a97cf461642fc339d/materialyoucolor-2.0.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e4827307b1c78897f97f7989237ef688a5c21663a63d327d7cec8e47f107e509", size = 316289, upload-time = "2025-01-09T09:02:53.101Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3c/be/039f46e565b77ce088902c19352c073e16c34a4a8743c95fcad1a537d81f/materialyoucolor-2.0.10-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8cebae884c6106584e1792b910d9588778353255a1a8156cb634e21f1b8670e", size = 206468, upload-time = "2025-01-09T07:56:41.508Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6b/b9/d8946d2d9901ca7bd088ea8efcdc975c73f51766b234fc3a83b821988b2b/materialyoucolor-2.0.10-cp39-cp39-win_amd64.whl", hash = "sha256:4c5141fdd973ab386ebd0242072a7faab28d19cc083ee0d14f5b70c31ad75d60", size = 150332, upload-time = "2025-01-09T09:02:56.525Z" },
]
[[package]]
name = "mdurl"
version = "0.1.2"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
]
[[package]]
name = "nodeenv"
version = "1.9.1"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
]
[[package]]
name = "nuitka"
version = "2.7.16"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "ordered-set" },
{ name = "zstandard" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/87/36bae02fb19e98cdfa5778faf14f90acff396b9b17586355fbefbe971631/Nuitka-2.7.16.tar.gz", hash = "sha256:768909faf365b21ae4777727fc4ae88efc29239c664bd177061fc907e263e0fa", size = 3889164, upload-time = "2025-09-19T09:47:21.982Z" }
[[package]]
name = "numpy"
version = "1.26.1"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/78/23/f78fd8311e0f710fe1d065d50b92ce0057fe877b8ed7fd41b28ad6865bfc/numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe", size = 15651806, upload-time = "2023-10-14T20:15:36.407Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/34/11/055802bf85abbb61988e6313e8b0a85167ee0795fc2c6141ee5b539e7b11/numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af", size = 20621341, upload-time = "2023-10-14T19:39:25.82Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/63/fd76159cb76c682171e3bf50ed0ee8704103035a9347684a2ec0914b84a1/numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575", size = 13960979, upload-time = "2023-10-14T19:39:49.073Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a2/e0/008311a728bd77084b207840bbeb1e5e3a8412994851ec06856413ca7a7a/numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244", size = 14169086, upload-time = "2023-10-14T19:40:11.508Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/5e/cb38e3d1916cc29880c84a9332a9122a8f49a7b57ec7aea63e0f678587a2/numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67", size = 18178951, upload-time = "2023-10-14T19:40:40.441Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/d9/58db30222fcfa35411d83150d313c5028680298ec8c69f85d0fcec4d4664/numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2", size = 18016432, upload-time = "2023-10-14T19:41:08.368Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/8f/7df6e01a44742088aacc985da04bbc2019575fe684b7b9d9057f4f0e22e0/numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297", size = 20757353, upload-time = "2023-10-14T19:41:40.163Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/57/09/fe9282ffb0217176b0185900945189b6beaec4f94ff46afb76bcd9b68e30/numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab", size = 15797631, upload-time = "2023-10-14T19:42:05.418Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c5/9e/2d6d9d8f0ec910539ee721f86f23489a0eedb25bd51f4268ae0899f6a3ab/numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a", size = 20621224, upload-time = "2023-10-14T19:42:37.88Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e8/06/0512e2582fd27bb7b358fa1e4ffc0f6c89c89f5ada31df58c5fa93171098/numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9", size = 13991582, upload-time = "2023-10-14T19:42:59.875Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/21/ae3276d5f7c255a7c821d54d94c1270448b9a5936b618d7a8f5fb3c91c02/numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3", size = 14175129, upload-time = "2023-10-14T19:43:22.596Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8a/08/a7e5dadc21fe193baea5f257e11b7b70cc27a89692fc9e3ed690e55cc4b6/numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974", size = 18190343, upload-time = "2023-10-14T19:43:50.377Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/c2/68bbc08cd8af52f52f7d978c97062eab1e627f5423d591ba67f732b5f265/numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c", size = 18028828, upload-time = "2023-10-14T19:44:19.972Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/9e/73af202f778cae92d3789cc06e6f2be14102e9c8f9af9bbe2cc6c81b326f/numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b", size = 20760215, upload-time = "2023-10-14T19:44:52.458Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/0f/3f712cd84371636c5375d2dd70e7514d264cec6bdfc3d7997a4236e9f948/numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53", size = 15799803, upload-time = "2023-10-14T19:45:17.161Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ad/00/adb57a4974931c97a9bbbc92fd2cc998aa47569fcd7fb65ded4b81b72455/numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f", size = 20327461, upload-time = "2023-10-14T19:45:48.113Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/5a/87a862acf8aa3ef5896577db5baf29e56df0fdbda025fbb67fd5039794d1/numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24", size = 13679398, upload-time = "2023-10-14T19:46:10.162Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/70/cd8a18c109a8ba45477e4bddcff6cdbb31b1694148eb152087a3c264a4ae/numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e", size = 13875842, upload-time = "2023-10-14T19:46:32.163Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/c0/ccbb2a4c75b283d6100400a907087bfa4d89cee9df73fa6af85268115d81/numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124", size = 17889868, upload-time = "2023-10-14T19:47:00.785Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/4b/02caaf93a1afaebb12c053df360c3bec4b1818a39d24f4eca0efe1d7ab5f/numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c", size = 17720430, upload-time = "2023-10-14T19:47:29.726Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/64/89efc3809fb6a733a6e10e7fe6498e404cabf57f063d232ded3d7cc4ef08/numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66", size = 19967092, upload-time = "2023-10-14T19:48:00.998Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/95/908d0caa051beae4f7c77652dbbeb781e7b717f3040c5c5fcaed4d3ed08f/numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7", size = 15507291, upload-time = "2023-10-14T19:48:25.741Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/97/ef9a9cfabbf97092475dda30179bcd86b261e678d8968003f7e8effc4fdd/numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e", size = 20626258, upload-time = "2023-10-14T19:48:57.34Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/54/fafc9282d2510aa56694897029a96e524c72dfe9a1148294fe8f2bcbe974/numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617", size = 13965659, upload-time = "2023-10-14T19:49:19.936Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9b/07/f1d5e7a4840c96f7d44be31c39d1f8025fca32cdebf2bce36511a4a64e82/numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e", size = 14176423, upload-time = "2023-10-14T19:49:43.03Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/ac/53100546dcd9aa400a73c7770b13cad9a3b18bf83433499e36b5efe9850f/numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908", size = 18186982, upload-time = "2023-10-14T19:50:11.286Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/20/ee/8fac67ed6c74a246e3634f2e7d709f54478b09d0f5dd97361ad6b03eb5a8/numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5", size = 18019713, upload-time = "2023-10-14T19:50:38.863Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/36/3593d482565bdb3d6a016565c56edaa18fac20f71bb18741ee030140a793/numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104", size = 20770117, upload-time = "2023-10-14T19:51:10.193Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/ed/022fc4106f6d97e41e156201274138e0369b27dbfc8c206034f24ebd97d9/numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2", size = 15803923, upload-time = "2023-10-14T19:51:35.406Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/40/c7b748b9d247f7d062ed6735d5f484a7e52fe13546318872a489cfb810bf/numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668", size = 20445721, upload-time = "2023-10-14T19:52:08.649Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/37/ba/2512f7b48e619259ee38d4f803fdc6ed92ec9f4975772378de1eef6e6a5c/numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42", size = 17995458, upload-time = "2023-10-14T19:52:36.55Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/13/28dad1f91605d519b6899f7a26ab61938ecbd9e1770e219e7030b15d58e4/numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f", size = 15697297, upload-time = "2023-10-14T19:53:04.904Z" },
]
[[package]]
name = "opencv-python"
version = "4.8.1.78"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c0/52/9fe76a56e01078a612812b40764a7b138f528b503f7653996c6cfadfa8ec/opencv-python-4.8.1.78.tar.gz", hash = "sha256:cc7adbbcd1112877a39274106cb2752e04984bc01a031162952e97450d6117f6", size = 92094255, upload-time = "2023-09-28T11:10:40.539Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/58/7ee92b21cb98689cbe28c69e3cf8ee51f261bfb6bc904ae578736d22d2e7/opencv_python-4.8.1.78-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:91d5f6f5209dc2635d496f6b8ca6573ecdad051a09e6b5de4c399b8e673c60da", size = 54739488, upload-time = "2023-09-28T11:00:22.939Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a1/f6/57de91ea40c670527cd47a6548bf2cbedc68cec57c041793b256356abad7/opencv_python-4.8.1.78-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31f47e05447da8b3089faa0a07ffe80e114c91ce0b171e6424f9badbd1c5cd", size = 33127231, upload-time = "2023-09-28T11:00:31.878Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/a5/dd3735d08c1afc2e801f564f6602392cd86cf59bcb5a6712582ad0610a22/opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9814beca408d3a0eca1bae7e3e5be68b07c17ecceb392b94170881216e09b319", size = 41016265, upload-time = "2023-09-28T11:00:39.652Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/8a/b2f7e1a434d56bf1d7570fc5941ace0847404e1032d7f1f0b8fed896568d/opencv_python-4.8.1.78-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4c406bdb41eb21ea51b4e90dfbc989c002786c3f601c236a99c59a54670a394", size = 61712178, upload-time = "2023-09-28T11:00:49.714Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/13/92/6f3194559d4e2a17826240f2466076728f4c92a2d124a32bfd51ca070019/opencv_python-4.8.1.78-cp37-abi3-win32.whl", hash = "sha256:a7aac3900fbacf55b551e7b53626c3dad4c71ce85643645c43e91fcb19045e47", size = 28281329, upload-time = "2023-09-28T11:00:57.464Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/38/d2/3e8c13ffc37ca5ebc6f382b242b44acb43eb489042e1728407ac3904e72f/opencv_python-4.8.1.78-cp37-abi3-win_amd64.whl", hash = "sha256:b983197f97cfa6fcb74e1da1802c7497a6f94ed561aba6980f1f33123f904956", size = 38065100, upload-time = "2023-09-28T11:01:04.856Z" },
]
[[package]]
name = "ordered-set"
version = "4.1.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" },
]
[[package]]
name = "pillow"
version = "10.1.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/80/d7/c4b258c9098b469c4a4e77b0a99b5f4fd21e359c2e486c977d231f52fc71/Pillow-10.1.0.tar.gz", hash = "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38", size = 50781360, upload-time = "2023-10-15T13:03:15.681Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/64/1a4fb688fb4e1a8c621b49ac398858d49f0d6c9289b06027a3f0d4027568/Pillow-10.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106", size = 3484047, upload-time = "2023-10-15T13:01:25.469Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/a4/c164eb1f692585982e1aa9bf2c1126da9721c2193cd1aba1eaf46fe7f1d7/Pillow-10.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273", size = 3294761, upload-time = "2023-10-15T13:01:28.27Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/d1/ffdda319c2f62fb20b3ece231caecedcc8af42fc2c0d4900dca92996c356/Pillow-10.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666", size = 3424149, upload-time = "2023-10-15T13:01:30.76Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/95/7b/71e2665760b5c33af00fa9bb6d6bca068b51bf021a4ceaeee03e18689f51/Pillow-10.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2", size = 3499990, upload-time = "2023-10-15T13:01:32.811Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/f0/446e3568f8365dff2c37efd35411a61ad72aa1b613ba4ac123bbacf0e5ce/Pillow-10.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593", size = 3475329, upload-time = "2023-10-15T13:01:34.93Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/b9/5c6ad3241f1ccca4b781dfeddbab2dac4480f95aedc351a0e60c9f4c8aa9/Pillow-10.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db", size = 3621698, upload-time = "2023-10-15T13:01:37.19Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/ce/745220339fed3a5a0052b443c482625ec6c889da1fef0f9791e30d571e19/Pillow-10.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f", size = 3561230, upload-time = "2023-10-15T13:01:40.922Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/ff/2c4ffdccc9a29d5f010e59abd7d62d172e84472000f32f1d64e177453906/Pillow-10.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818", size = 3600892, upload-time = "2023-10-15T13:01:43.089Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/7e/18ffce67b6e7637eead295b8a78d293d170d404a633010c3549da9a5e674/Pillow-10.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57", size = 2609029, upload-time = "2023-10-15T13:01:44.598Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/22/93d6b5aa5917d09ec7088a2c4d1821848f3f95fbdc2633ba9d9fc28444a1/Pillow-10.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7", size = 3484089, upload-time = "2023-10-15T13:01:46.981Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c3/5b/6bcfd0c2631d1ce4bb29ea597556ed2783404c5ad38635caf7b3f2b19073/Pillow-10.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7", size = 3294744, upload-time = "2023-10-15T13:01:49.084Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/ae/6b7673ae38cbd4821742acdb209e3e52c564dbe8ef8409d64d68ea0f9e6f/Pillow-10.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610", size = 3424164, upload-time = "2023-10-15T13:01:51.284Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cd/fa/87c27a90d97600edc639b06c14c63c8dac709e13e04714eb1dc949788f41/Pillow-10.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839", size = 3499981, upload-time = "2023-10-15T13:01:53.594Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5f/0a/8301b9384cbbd8542c2a5540fda1bce18c3203a0cc7becc9073bdee79ccb/Pillow-10.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172", size = 3475329, upload-time = "2023-10-15T13:01:55.827Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/d8/f31dd84b4363b5f24c71b25a13ec3855f5ff233e07e1c3f1f8e979e12be2/Pillow-10.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061", size = 3621689, upload-time = "2023-10-15T13:01:57.56Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/20/5000e1a9696ee28c18c4bd158aaaed0dd65d3b13b6547c43b29f9851c2cc/Pillow-10.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262", size = 3561247, upload-time = "2023-10-15T13:01:59.326Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/f7/4c4428d56df4cf7dfc6b9fc9a8b0268cdbca7c6b5130bc090fbf7562b223/Pillow-10.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992", size = 3600912, upload-time = "2023-10-15T13:02:01.139Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/38/31def4109acd4db10672df6f806b175c0d21458f845ddc0890e43238ba7c/Pillow-10.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a", size = 2609067, upload-time = "2023-10-15T13:02:03.443Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/80/9df9bb85b3209d62b85064c956a819e9e06279c6accf7e0f6a89ff4d9d6d/Pillow-10.1.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b", size = 3483728, upload-time = "2023-10-15T13:02:05.418Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/6f/6f716f16bcb9bf39f54b9d2d993f535a0ee42cc0fec973c80839b0720ca2/Pillow-10.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d", size = 3293897, upload-time = "2023-10-15T13:02:07.61Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/33/c6/1abfffbbdd803a44fb4aa009218502a0e353bfcc96b045a24ad1a194c705/Pillow-10.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27", size = 3424287, upload-time = "2023-10-15T13:02:09.756Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/40/ff02ca10167c3d68c84b61142a5acd28b09ea5f833fffe9a77c4d8d5f96a/Pillow-10.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312", size = 3500620, upload-time = "2023-10-15T13:02:11.483Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/57/925008390581a15c024dd57206ca622fd0ea85fbd194169efc3ff48ecda1/Pillow-10.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de", size = 3475368, upload-time = "2023-10-15T13:02:13.353Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/44/ed/a6f7dcd6631ec55b8d26c6a8bca762b04b7025daa3aa67e860a886abed89/Pillow-10.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651", size = 3621896, upload-time = "2023-10-15T13:02:15.053Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/e7/cf988402f838843362c471ca2a240d4be46adcabc508be4e70ba7721e9ee/Pillow-10.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b", size = 3561709, upload-time = "2023-10-15T13:02:16.745Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/04/3d/bf353b366d1a39a95ff861129ba3af8499d48e06634d50c10cf4136cbe7d/Pillow-10.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f", size = 3602069, upload-time = "2023-10-15T13:02:18.634Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/e4/978865107d097dd9cb650331676d8dc29ed9fcd0aaab46486e9d6e5123f0/Pillow-10.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996", size = 2609160, upload-time = "2023-10-15T13:02:20.875Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/2a/b87cb8b319d3d755eae0ef61129bfd83f536ef121891001733baefecebe2/Pillow-10.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099", size = 3484069, upload-time = "2023-10-15T13:02:41.339Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/67/9bf0b3c0f43d5e1aa6795318feefad2ec5e5a10b51454b047dc608619fbb/Pillow-10.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616", size = 3294823, upload-time = "2023-10-15T13:02:43.035Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/8c/a4abc8937041ea811fd81a4bab9205ebf78f9a4edfdb6b1a1623a10082d2/Pillow-10.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb", size = 3424109, upload-time = "2023-10-15T13:02:44.83Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/3a/ada56d489446dbb7679d242bfd7bb159cee8a7989c34dd34045103d5280d/Pillow-10.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219", size = 3500040, upload-time = "2023-10-15T13:02:46.981Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/54/3413b3498053af77efebc57d99a6152c67a56b3627c6cbf3a4f9da3da30f/Pillow-10.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34", size = 3475354, upload-time = "2023-10-15T13:02:48.68Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/dc/acccca38a87272cb2eed372f112595439418dfb6119770b04dc06d3b78bd/Pillow-10.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd", size = 3621722, upload-time = "2023-10-15T13:02:50.572Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/99/691942ab88b3d22c36c704f2c52b3b9d93fb86a05eaeabe006a216c3e882/Pillow-10.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28", size = 3561221, upload-time = "2023-10-15T13:02:52.431Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/3e/849a9958af171445cca8112d0938df7e40337c2d5b836d999edcf1c6bb27/Pillow-10.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2", size = 3600943, upload-time = "2023-10-15T13:02:54.264Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/56/0eb5a210de84b8c73720e6b691fe8fe1a99d62c8e8f0564b95f2c12988e8/Pillow-10.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256", size = 2609113, upload-time = "2023-10-15T13:02:56.468Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/68/39/18abea241c252a9a563e97f97af69c0f0ef34a88a84720db5b0111281a73/Pillow-10.1.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7", size = 3437796, upload-time = "2023-10-15T13:02:58.415Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/69/6ee650d047770ec1ee8844b62b4401b4e4fbac3959a7663af1b62c00fdca/Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba", size = 3413457, upload-time = "2023-10-15T13:03:00.297Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/09/07957ab28bac4e62d1417a35b3bed2298c1cb4f2869e6ba5f95ae7ccf08d/Pillow-10.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4", size = 3503087, upload-time = "2023-10-15T13:03:02.342Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/b4/627903ee2d02ab8685918d20a644feb32ad6c5e181aedebf57182838abca/Pillow-10.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9", size = 2609263, upload-time = "2023-10-15T13:03:04.461Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/0d/d6845b86ece023aafe023aa303092b5c7686bd88d60464a93316b28e92d5/Pillow-10.1.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e", size = 3437731, upload-time = "2023-10-15T13:03:06.16Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/25/723cae5b564f0f1301176b38a2ba2f36dbb5fad658ad45972496e2f24298/Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412", size = 3413318, upload-time = "2023-10-15T13:03:08.035Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/90/53/ed3059ab37e7633c6cc7c6a2decd906f3f3e104b732dfb71ac78096e1008/Pillow-10.1.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b", size = 3503107, upload-time = "2023-10-15T13:03:09.891Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/32/2d/5a62c0924dbbb7585c9e39acbfef3809109e8e08b9a3c9d8457c77132758/Pillow-10.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f", size = 2609332, upload-time = "2023-10-15T13:03:11.673Z" },
]
[[package]]
name = "platformdirs"
version = "4.4.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
resolution-markers = [
"python_full_version < '3.10' and platform_machine == 'arm64' and sys_platform == 'darwin'",
"python_full_version < '3.10' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version < '3.10' and platform_machine != 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.10' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.10' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" },
]
[[package]]
name = "platformdirs"
version = "4.5.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
resolution-markers = [
"python_full_version >= '3.11' and sys_platform == 'darwin'",
"python_full_version >= '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version >= '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
"python_full_version == '3.10.*' and sys_platform == 'darwin'",
"python_full_version == '3.10.*' and platform_machine == 'aarch64' and sys_platform == 'linux'",
"(python_full_version == '3.10.*' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version == '3.10.*' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
]
[[package]]
name = "plum-py"
version = "0.8.7"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/2a/a650b0cde5a15f4a9375df43bd7eacaffb65b30c8395b6b6cd84df2bf7ae/plum-py-0.8.7.tar.gz", hash = "sha256:40a25a70f7fd213f57cdd1decc65874df2d213d19d63478a1addcca748c13c4e", size = 116231, upload-time = "2024-05-12T02:01:48.18Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/da/8a9bdf5d3ea4b13d3142e0c77d6e85e4c2668aef6e4b9d5e9a6619049bdc/plum_py-0.8.7-py3-none-any.whl", hash = "sha256:d791f059ef159adbe3c3b036557b5b431fa07db226a9de106a74e5e57441741d", size = 69991, upload-time = "2023-08-14T21:56:31.953Z" },
]
[[package]]
name = "pre-commit"
version = "4.3.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "cfgv" },
{ name = "identify" },
{ name = "nodeenv" },
{ name = "pyyaml" },
{ name = "virtualenv" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" },
]
[[package]]
name = "pygments"
version = "2.19.2"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
[[package]]
name = "pyside6"
version = "6.10.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "pyside6-addons" },
{ name = "pyside6-essentials" },
{ name = "shiboken6" },
]
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e6/98/84b16f78b5d92dd234fb1eb9890a350a5b0c83d985bb8c44a92f813a2d02/pyside6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:c2cbc5dc2a164e3c7c51b3435e24203e90e5edd518c865466afccbd2e5872bb0", size = 558115, upload-time = "2025-10-08T09:47:09.246Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/76/0961c8c5653ecb60a6881b649dcb6b71a6be5bd1c8d441ecc48ac7f50b1a/pyside6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ae8c3c8339cd7c3c9faa7cc5c52670dcc8662ccf4b63a6fed61c6345b90c4c01", size = 557762, upload-time = "2025-10-08T09:47:11.819Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c8/73/6187502fff8b6599443d15c46dd900b2ded24be5aacb2becce33f6faf566/pyside6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:9f402f883e640048fab246d36e298a5e16df9b18ba2e8c519877e472d3602820", size = 558299, upload-time = "2025-10-08T09:47:14.255Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/67/94794ebaf198bbdb35cb77f19f38370f9b323b036ab149874bc33c38faab/pyside6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:70a8bcc73ea8d6baab70bba311eac77b9a1d31f658d0b418e15eb6ea36c97e6f", size = 564367, upload-time = "2025-10-08T09:47:16.287Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1d/cc/552331d413c1b933d54ed45e33cc7ff29d0b239677975fe2528e7ac8bfbc/pyside6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:4b709bdeeb89d386059343a5a706fc185cee37b517bda44c7d6b64d5fdaf3339", size = 548826, upload-time = "2025-10-08T09:47:18.399Z" },
]
[[package]]
name = "pyside6-addons"
version = "6.10.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "pyside6-essentials" },
{ name = "shiboken6" },
]
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/47/23/9fbdec2ce16244ac3fe28e6d44c39c70465c93a03325939a792fd00fde7f/pyside6_addons-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:88e61e21ee4643cdd9efb39ec52f4dc1ac74c0b45c5b7fa453d03c094f0a8a5c", size = 322248256, upload-time = "2025-10-08T09:47:37.844Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/b8/d129210f2c7366b4e1bf5bb6230be42052b29e8ba1b1d7db6ef333cf5a39/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:08d4ed46c4c9a353a9eb84134678f8fdd4ce17fb8cce2b3686172a7575025464", size = 170238987, upload-time = "2025-10-08T09:47:51.446Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cf/ae/ede1edd009395092219f3437d2ee59f9ba93739c28c040542ed47c6cc831/pyside6_addons-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:15d32229d681be0bba1b936c4a300da43d01e1917ada5b57f9e03a387c245ab0", size = 165939425, upload-time = "2025-10-08T09:48:02.073Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/5d/a3c32f85ac7f905c95679967c0ddda0ba043c273b75623cc90d8185064e4/pyside6_addons-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:99d93a32c17c5f6d797c3b90dd58f2a8bae13abde81e85802c34ceafaee11859", size = 164814172, upload-time = "2025-10-08T09:48:12.891Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/2a/4ff71b09571202c8e1320c45276fc1d0cd81ee53107dfc17bb22d4243f88/pyside6_addons-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:92536427413f3b6557cf53f1a515cd766725ee46a170aff57ad2ff1dfce0ffb1", size = 34104251, upload-time = "2025-10-08T09:48:18.287Z" },
]
[[package]]
name = "pyside6-essentials"
version = "6.10.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "shiboken6" },
]
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e5/55/bad02ab890c8b8101abef0db4a2e5304be78a69e23a438e4d8555b664467/pyside6_essentials-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:003e871effe1f3e5b876bde715c15a780d876682005a6e989d89f48b8b93e93a", size = 105034090, upload-time = "2025-10-08T09:48:24.944Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/75/e17efc7eb900993e0e3925885635c6cf373c817196f09bcbcc102b00ac94/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:1d5e013a8698e37ab8ef360e6960794eb5ef20832a8d562e649b8c5a0574b2d8", size = 76362150, upload-time = "2025-10-08T09:48:31.849Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/06/62/fbd1e81caafcda97b147c03f5b06cfaadd8da5fa8298f527d2ec648fa5b7/pyside6_essentials-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b1dd0864f0577a448fb44426b91cafff7ee7cccd1782ba66491e1c668033f998", size = 75454169, upload-time = "2025-10-08T09:48:38.21Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/3a/d8211d17e6ca70f641c6ebd309f08ef18930acda60e74082c75875a274da/pyside6_essentials-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:fc167eb211dd1580e20ba90d299e74898e7a5a1306d832421e879641fc03b6fe", size = 74361794, upload-time = "2025-10-08T09:48:44.335Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/e9/0e22e3c10325c4ff09447fadb43f7962afb82cef0b65358f5704251c6b32/pyside6_essentials-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:6dd0936394cb14da2fd8e869899f5e0925a738b1c8d74c2f22503720ea363fb1", size = 55099467, upload-time = "2025-10-08T09:48:50.902Z" },
]
[[package]]
name = "pyyaml"
version = "6.0.3"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" },
]
[[package]]
name = "rich"
version = "14.2.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "markdown-it-py", version = "3.0.0", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.10'" },
{ name = "markdown-it-py", version = "4.0.0", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.10'" },
{ name = "pygments" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
]
[[package]]
name = "ruff"
version = "0.14.2"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ee/34/8218a19b2055b80601e8fd201ec723c74c7fe1ca06d525a43ed07b6d8e85/ruff-0.14.2.tar.gz", hash = "sha256:98da787668f239313d9c902ca7c523fe11b8ec3f39345553a51b25abc4629c96", size = 5539663, upload-time = "2025-10-23T19:37:00.956Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/16/dd/23eb2db5ad9acae7c845700493b72d3ae214dce0b226f27df89216110f2b/ruff-0.14.2-py3-none-linux_armv6l.whl", hash = "sha256:7cbe4e593505bdec5884c2d0a4d791a90301bc23e49a6b1eb642dd85ef9c64f1", size = 12533390, upload-time = "2025-10-23T19:36:18.044Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5a/8c/5f9acff43ddcf3f85130d0146d0477e28ccecc495f9f684f8f7119b74c0d/ruff-0.14.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8d54b561729cee92f8d89c316ad7a3f9705533f5903b042399b6ae0ddfc62e11", size = 12887187, upload-time = "2025-10-23T19:36:22.664Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/99/fa/047646491479074029665022e9f3dc6f0515797f40a4b6014ea8474c539d/ruff-0.14.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c8753dfa44ebb2cde10ce5b4d2ef55a41fb9d9b16732a2c5df64620dbda44a3", size = 11925177, upload-time = "2025-10-23T19:36:24.778Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/8b/c44cf7fe6e59ab24a9d939493a11030b503bdc2a16622cede8b7b1df0114/ruff-0.14.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d0bbeffb8d9f4fccf7b5198d566d0bad99a9cb622f1fc3467af96cb8773c9e3", size = 12358285, upload-time = "2025-10-23T19:36:26.979Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/01/47701b26254267ef40369aea3acb62a7b23e921c27372d127e0f3af48092/ruff-0.14.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7047f0c5a713a401e43a88d36843d9c83a19c584e63d664474675620aaa634a8", size = 12303832, upload-time = "2025-10-23T19:36:29.192Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2d/5c/ae7244ca4fbdf2bee9d6405dcd5bc6ae51ee1df66eb7a9884b77b8af856d/ruff-0.14.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bf8d2f9aa1602599217d82e8e0af7fd33e5878c4d98f37906b7c93f46f9a839", size = 13036995, upload-time = "2025-10-23T19:36:31.861Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/4c/0860a79ce6fd4c709ac01173f76f929d53f59748d0dcdd662519835dae43/ruff-0.14.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1c505b389e19c57a317cf4b42db824e2fca96ffb3d86766c1c9f8b96d32048a7", size = 14512649, upload-time = "2025-10-23T19:36:33.915Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/7f/d365de998069720a3abfc250ddd876fc4b81a403a766c74ff9bde15b5378/ruff-0.14.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a307fc45ebd887b3f26b36d9326bb70bf69b01561950cdcc6c0bdf7bb8e0f7cc", size = 14088182, upload-time = "2025-10-23T19:36:36.983Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6c/ea/d8e3e6b209162000a7be1faa41b0a0c16a133010311edc3329753cc6596a/ruff-0.14.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61ae91a32c853172f832c2f40bd05fd69f491db7289fb85a9b941ebdd549781a", size = 13599516, upload-time = "2025-10-23T19:36:39.208Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fa/ea/c7810322086db68989fb20a8d5221dd3b79e49e396b01badca07b433ab45/ruff-0.14.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1967e40286f63ee23c615e8e7e98098dedc7301568bd88991f6e544d8ae096", size = 13272690, upload-time = "2025-10-23T19:36:41.453Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a9/39/10b05acf8c45786ef501d454e00937e1b97964f846bf28883d1f9619928a/ruff-0.14.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2877f02119cdebf52a632d743a2e302dea422bfae152ebe2f193d3285a3a65df", size = 13496497, upload-time = "2025-10-23T19:36:43.61Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/59/a1/1f25f8301e13751c30895092485fada29076e5e14264bdacc37202e85d24/ruff-0.14.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e681c5bc777de5af898decdcb6ba3321d0d466f4cb43c3e7cc2c3b4e7b843a05", size = 12266116, upload-time = "2025-10-23T19:36:45.625Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/fa/0029bfc9ce16ae78164e6923ef392e5f173b793b26cc39aa1d8b366cf9dc/ruff-0.14.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e21be42d72e224736f0c992cdb9959a2fa53c7e943b97ef5d081e13170e3ffc5", size = 12281345, upload-time = "2025-10-23T19:36:47.618Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a5/ab/ece7baa3c0f29b7683be868c024f0838770c16607bea6852e46b202f1ff6/ruff-0.14.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b8264016f6f209fac16262882dbebf3f8be1629777cf0f37e7aff071b3e9b92e", size = 12629296, upload-time = "2025-10-23T19:36:49.789Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/7f/638f54b43f3d4e48c6a68062794e5b367ddac778051806b9e235dfb7aa81/ruff-0.14.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ca36b4cb4db3067a3b24444463ceea5565ea78b95fe9a07ca7cb7fd16948770", size = 13371610, upload-time = "2025-10-23T19:36:51.882Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/35/3654a973ebe5b32e1fd4a08ed2d46755af7267da7ac710d97420d7b8657d/ruff-0.14.2-py3-none-win32.whl", hash = "sha256:41775927d287685e08f48d8eb3f765625ab0b7042cc9377e20e64f4eb0056ee9", size = 12415318, upload-time = "2025-10-23T19:36:53.961Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/71/30/3758bcf9e0b6a4193a6f51abf84254aba00887dfa8c20aba18aa366c5f57/ruff-0.14.2-py3-none-win_amd64.whl", hash = "sha256:0df3424aa5c3c08b34ed8ce099df1021e3adaca6e90229273496b839e5a7e1af", size = 13565279, upload-time = "2025-10-23T19:36:56.578Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2e/5d/aa883766f8ef9ffbe6aa24f7192fb71632f31a30e77eb39aa2b0dc4290ac/ruff-0.14.2-py3-none-win_arm64.whl", hash = "sha256:ea9d635e83ba21569fbacda7e78afbfeb94911c9434aff06192d9bc23fd5495a", size = 12554956, upload-time = "2025-10-23T19:36:58.714Z" },
]
[[package]]
name = "shiboken6"
version = "6.10.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/78/3e730aea82089dd82b1e092bc265778bda329459e6ad9b7134eec5fff3f2/shiboken6-6.10.0-cp39-abi3-macosx_13_0_universal2.whl", hash = "sha256:7a5f5f400ebfb3a13616030815708289c2154e701a60b9db7833b843e0bee543", size = 476535, upload-time = "2025-10-08T09:49:08Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/09/4ffa3284a17b6b765d45b41c9a7f1b2cde6c617c853ac6f170fb62bbbece/shiboken6-6.10.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:e612734da515d683696980107cdc0396a3ae0f07b059f0f422ec8a2333810234", size = 271098, upload-time = "2025-10-08T09:49:09.47Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/29/00e26f33a0fb259c2edce9c761a7a438d7531ca514bdb1a4c072673bd437/shiboken6-6.10.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b01377e68d14132360efb0f4b7233006d26aa8ae9bb50edf00960c2a5f52d148", size = 267698, upload-time = "2025-10-08T09:49:10.694Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/11/30/e4624a7e3f0dc9796b701079b77defcce0d32d1afc86bb1d0df04bc3d9e2/shiboken6-6.10.0-cp39-abi3-win_amd64.whl", hash = "sha256:0bc5631c1bf150cbef768a17f5f289aae1cb4db6c6b0c19b2421394e27783717", size = 1234227, upload-time = "2025-10-08T09:49:12.774Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dd/e5/0ab862005ea87dc8647ba958a3099b3b0115fd6491c65da5c5a0f6364db1/shiboken6-6.10.0-cp39-abi3-win_arm64.whl", hash = "sha256:dfc4beab5fec7dbbebbb418f3bf99af865d6953aa0795435563d4cbb82093b61", size = 1794775, upload-time = "2025-10-08T09:49:14.641Z" },
]
[[package]]
name = "soupsieve"
version = "2.8"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" },
]
[[package]]
name = "sqlalchemy"
version = "2.0.20"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/85/b7fbadd64b30e9d4b882dd323341fc263687bc95b22edec262b12cac8fd7/SQLAlchemy-2.0.20.tar.gz", hash = "sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6", size = 9437690, upload-time = "2023-08-15T18:50:38.577Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fe/1c/d3ca3095528c262125251ee42782086d1d2bad4e59adf0dbdd9540147c23/SQLAlchemy-2.0.20-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98", size = 2049134, upload-time = "2023-08-15T20:03:03.9Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/7f/9a11e808fdf1187c8206f204352fbbd0d72b68d6bc8233121058f8bde73d/SQLAlchemy-2.0.20-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e", size = 2040214, upload-time = "2023-08-15T20:03:05.888Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7f/3f/612c89009ff6194f28682929a839fcc25d46c8ccaddf4ee186debc67c7e8/SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8", size = 3023253, upload-time = "2023-08-15T22:46:47.043Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9f/94/8a20e0b867857cacf7e1269efc02aa81866071df844a2598fa34245a50df/SQLAlchemy-2.0.20-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50", size = 3022383, upload-time = "2023-08-15T20:44:30.135Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/fb/6089bd8137570e90a2afdc51043a0eeedb6fd818ff91a5b02862f6eb7da9/SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed", size = 3040062, upload-time = "2023-08-15T22:46:49.408Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f3/d5/99c6f160ec195dd1fbe7bd1f0d715b9598496825f8ec05be7089508a735f/SQLAlchemy-2.0.20-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61", size = 3036901, upload-time = "2023-08-15T20:44:32.506Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/a8/9bc1bffb61b68ad3cc8a7cfb1412b59059fc97c9dbeb42d5da865e4ba8c9/SQLAlchemy-2.0.20-cp310-cp310-win32.whl", hash = "sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d", size = 2019158, upload-time = "2023-08-15T19:55:13.495Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/01/e251a1e721d93c584471d56f7f6d1ae310831522b04d74306a0f522e7c1a/SQLAlchemy-2.0.20-cp310-cp310-win_amd64.whl", hash = "sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14", size = 2044344, upload-time = "2023-08-15T19:55:16.593Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b4/2b/570a1caa015115a34562965498276de4251bc6cb049a349541f5693e43c3/SQLAlchemy-2.0.20-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7", size = 2052682, upload-time = "2023-08-15T19:23:17.432Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/a7/3c8c8a36f336880e4dca47bf30d5c723384c40b67e649b35a582d6df45ef/SQLAlchemy-2.0.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4", size = 2042936, upload-time = "2023-08-15T19:23:20.046Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fb/2e/015ad3b62da87717666e6ac2959085fda651d667f572f86490008e07c0d8/SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179", size = 3160735, upload-time = "2023-08-15T20:15:00.251Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/dc/3e/d0c97146e4c707451c05b80ac712025d635b19aed1d867fed351de70c71c/SQLAlchemy-2.0.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c", size = 3156962, upload-time = "2023-08-15T19:27:15.86Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/29/03/9ae3fd33d743219ec3651404b509af4aff1d7ed729be5aec56aa4f760579/SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b", size = 3162243, upload-time = "2023-08-15T20:15:04.422Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7d/c5/ce43c4734585c393f0cf000e75f454e46a71828b3e21f78cb4560c7bdd94/SQLAlchemy-2.0.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0", size = 3155001, upload-time = "2023-08-15T19:27:19.323Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/ee/dd75fd26a485a53c1f8eefad4a94ab811ec95584725fc6823e15283275c1/SQLAlchemy-2.0.20-cp311-cp311-win32.whl", hash = "sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53", size = 2020912, upload-time = "2023-08-15T19:34:36.643Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/10/81/230bec5713913a472003b71c8dd7e3572de44499e741df576fd6b39d82c8/SQLAlchemy-2.0.20-cp311-cp311-win_amd64.whl", hash = "sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e", size = 2047477, upload-time = "2023-08-15T19:34:38.939Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4b/dd/8fe0fc21fc4338e7479f1b254a67b5515bd31b85c28925045bc8b0d5a1c3/SQLAlchemy-2.0.20-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208", size = 2052105, upload-time = "2023-08-15T20:03:07.991Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/07/04/ac0cb7e5164d9c6e27dd80c2031e9c0ee3763d0d9c12688ba5c98499d83e/SQLAlchemy-2.0.20-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e", size = 2043162, upload-time = "2023-08-15T20:03:09.931Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/6c/722ba2885fe78f4467b9b0f76aeb7ac9387362a5c5ffc22aa270294921d2/SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec", size = 3039009, upload-time = "2023-08-15T22:46:51.641Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/91/2b/92aadcea86b9ebd681de0b6b2cbfa75193227e607893cfb5feea0cefc461/SQLAlchemy-2.0.20-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0", size = 3038404, upload-time = "2023-08-15T20:44:34.765Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/45/ea/b06f96c04c6bf3dec93be59d44aed9a777addbbeb8c611da75c01f7d76b6/SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501", size = 3059584, upload-time = "2023-08-15T22:46:53.999Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ab/1d/146d589a252c5e18da25b91ff657cf8f2546c4c5d573a6d9ca0d84158591/SQLAlchemy-2.0.20-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6", size = 3054874, upload-time = "2023-08-15T20:44:37.017Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/15/55/70a0da35ee555769cfe92b70666c3af7a1fc2340bd3faf5105a52d5fd030/SQLAlchemy-2.0.20-cp39-cp39-win32.whl", hash = "sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9", size = 2021584, upload-time = "2023-08-15T19:55:18.079Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/01/fb/becd158acfec58b7a34d04affe79df9f8e5f95aa2114b5dacf50a8752f20/SQLAlchemy-2.0.20-cp39-cp39-win_amd64.whl", hash = "sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267", size = 2046802, upload-time = "2023-08-15T19:55:20.162Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/b4/018f41f16d6c2c198f36ef10799ae8f96daa4f17f887105224918fea501a/SQLAlchemy-2.0.20-py3-none-any.whl", hash = "sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf", size = 1840591, upload-time = "2023-08-15T20:17:56.794Z" },
]
[[package]]
name = "sqlalchemy-utils"
version = "0.41.1"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "sqlalchemy" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a3/e0/6906a8a9b8e9deb82923e02e2c1f750c567d69a34f6e1fe566792494a682/SQLAlchemy-Utils-0.41.1.tar.gz", hash = "sha256:a2181bff01eeb84479e38571d2c0718eb52042f9afd8c194d0d02877e84b7d74", size = 136579, upload-time = "2023-04-27T11:40:14.959Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/d8/3863fdfe6b27f6c0dffc650aaa2929f313b33aea615b102279fd46ab550b/SQLAlchemy_Utils-0.41.1-py3-none-any.whl", hash = "sha256:6c96b0768ea3f15c0dc56b363d386138c562752b84f647fb8d31a2223aaab801", size = 92612, upload-time = "2023-04-27T11:40:12.624Z" },
]
[[package]]
name = "structlog"
version = "25.4.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/b9/6e672db4fec07349e7a8a8172c1a6ae235c58679ca29c3f86a61b5e59ff3/structlog-25.4.0.tar.gz", hash = "sha256:186cd1b0a8ae762e29417095664adf1d6a31702160a46dacb7796ea82f7409e4", size = 1369138, upload-time = "2025-06-02T08:21:12.971Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/4a/97ee6973e3a73c74c8120d59829c3861ea52210667ec3e7a16045c62b64d/structlog-25.4.0-py3-none-any.whl", hash = "sha256:fe809ff5c27e557d14e613f45ca441aabda051d119ee5a0102aaba6ce40eed2c", size = 68720, upload-time = "2025-06-02T08:21:11.43Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "virtualenv"
version = "20.35.3"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
dependencies = [
{ name = "distlib" },
{ name = "filelock", version = "3.19.1", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.10'" },
{ name = "filelock", version = "3.20.0", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.10'" },
{ name = "platformdirs", version = "4.4.0", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }, marker = "python_full_version < '3.10'" },
{ name = "platformdirs", version = "4.5.0", source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }, marker = "python_full_version >= '3.10'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/d5/b0ccd381d55c8f45d46f77df6ae59fbc23d19e901e2d523395598e5f4c93/virtualenv-20.35.3.tar.gz", hash = "sha256:4f1a845d131133bdff10590489610c98c168ff99dc75d6c96853801f7f67af44", size = 6002907, upload-time = "2025-10-10T21:23:33.178Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/73/d9a94da0e9d470a543c1b9d3ccbceb0f59455983088e727b8a1824ed90fb/virtualenv-20.35.3-py3-none-any.whl", hash = "sha256:63d106565078d8c8d0b206d48080f938a8b25361e19432d2c9db40d2899c810a", size = 5981061, upload-time = "2025-10-10T21:23:30.433Z" },
]
[[package]]
name = "whoosh"
version = "2.7.4"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/25/2b/6beed2107b148edc1321da0d489afc4617b9ed317ef7b72d4993cad9b684/Whoosh-2.7.4.tar.gz", hash = "sha256:7ca5633dbfa9e0e0fa400d3151a8a0c4bec53bd2ecedc0a67705b17565c31a83", size = 968741, upload-time = "2016-04-04T01:19:32.327Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ba/19/24d0f1f454a2c1eb689ca28d2f178db81e5024f42d82729a4ff6771155cf/Whoosh-2.7.4-py2.py3-none-any.whl", hash = "sha256:aa39c3c3426e3fd107dcb4bde64ca1e276a65a889d9085a6e4b54ba82420a852", size = 468790, upload-time = "2016-04-04T01:19:40.379Z" },
]
[[package]]
name = "zstandard"
version = "0.25.0"
source = { registry = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple/" }
sdist = { url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" }
wheels = [
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/56/7a/28efd1d371f1acd037ac64ed1c5e2b41514a6cc937dd6ab6a13ab9f0702f/zstandard-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e59fdc271772f6686e01e1b3b74537259800f57e24280be3f29c8a0deb1904dd", size = 795256, upload-time = "2025-09-14T22:15:56.415Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/96/34/ef34ef77f1ee38fc8e4f9775217a613b452916e633c4f1d98f31db52c4a5/zstandard-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4d441506e9b372386a5271c64125f72d5df6d2a8e8a2a45a0ae09b03cb781ef7", size = 640565, upload-time = "2025-09-14T22:15:58.177Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/1b/4fdb2c12eb58f31f28c4d28e8dc36611dd7205df8452e63f52fb6261d13e/zstandard-0.25.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:ab85470ab54c2cb96e176f40342d9ed41e58ca5733be6a893b730e7af9c40550", size = 5345306, upload-time = "2025-09-14T22:16:00.165Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/73/28/a44bdece01bca027b079f0e00be3b6bd89a4df180071da59a3dd7381665b/zstandard-0.25.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e05ab82ea7753354bb054b92e2f288afb750e6b439ff6ca78af52939ebbc476d", size = 5055561, upload-time = "2025-09-14T22:16:02.22Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e9/74/68341185a4f32b274e0fc3410d5ad0750497e1acc20bd0f5b5f64ce17785/zstandard-0.25.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:78228d8a6a1c177a96b94f7e2e8d012c55f9c760761980da16ae7546a15a8e9b", size = 5402214, upload-time = "2025-09-14T22:16:04.109Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8b/67/f92e64e748fd6aaffe01e2b75a083c0c4fd27abe1c8747fee4555fcee7dd/zstandard-0.25.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b6bd67528ee8b5c5f10255735abc21aa106931f0dbaf297c7be0c886353c3d0", size = 5449703, upload-time = "2025-09-14T22:16:06.312Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fd/e5/6d36f92a197c3c17729a2125e29c169f460538a7d939a27eaaa6dcfcba8e/zstandard-0.25.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4b6d83057e713ff235a12e73916b6d356e3084fd3d14ced499d84240f3eecee0", size = 5556583, upload-time = "2025-09-14T22:16:08.457Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d7/83/41939e60d8d7ebfe2b747be022d0806953799140a702b90ffe214d557638/zstandard-0.25.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9174f4ed06f790a6869b41cba05b43eeb9a35f8993c4422ab853b705e8112bbd", size = 5045332, upload-time = "2025-09-14T22:16:10.444Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b3/87/d3ee185e3d1aa0133399893697ae91f221fda79deb61adbe998a7235c43f/zstandard-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25f8f3cd45087d089aef5ba3848cd9efe3ad41163d3400862fb42f81a3a46701", size = 5572283, upload-time = "2025-09-14T22:16:12.128Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0a/1d/58635ae6104df96671076ac7d4ae7816838ce7debd94aecf83e30b7121b0/zstandard-0.25.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3756b3e9da9b83da1796f8809dd57cb024f838b9eeafde28f3cb472012797ac1", size = 4959754, upload-time = "2025-09-14T22:16:14.225Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/d6/57e9cb0a9983e9a229dd8fd2e6e96593ef2aa82a3907188436f22b111ccd/zstandard-0.25.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:81dad8d145d8fd981b2962b686b2241d3a1ea07733e76a2f15435dfb7fb60150", size = 5266477, upload-time = "2025-09-14T22:16:16.343Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d1/a9/ee891e5edf33a6ebce0a028726f0bbd8567effe20fe3d5808c42323e8542/zstandard-0.25.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:a5a419712cf88862a45a23def0ae063686db3d324cec7edbe40509d1a79a0aab", size = 5440914, upload-time = "2025-09-14T22:16:18.453Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/58/08/a8522c28c08031a9521f27abc6f78dbdee7312a7463dd2cfc658b813323b/zstandard-0.25.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e7360eae90809efd19b886e59a09dad07da4ca9ba096752e61a2e03c8aca188e", size = 5819847, upload-time = "2025-09-14T22:16:20.559Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6f/11/4c91411805c3f7b6f31c60e78ce347ca48f6f16d552fc659af6ec3b73202/zstandard-0.25.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:75ffc32a569fb049499e63ce68c743155477610532da1eb38e7f24bf7cd29e74", size = 5363131, upload-time = "2025-09-14T22:16:22.206Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ef/d6/8c4bd38a3b24c4c7676a7a3d8de85d6ee7a983602a734b9f9cdefb04a5d6/zstandard-0.25.0-cp310-cp310-win32.whl", hash = "sha256:106281ae350e494f4ac8a80470e66d1fe27e497052c8d9c3b95dc4cf1ade81aa", size = 436469, upload-time = "2025-09-14T22:16:25.002Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/93/90/96d50ad417a8ace5f841b3228e93d1bb13e6ad356737f42e2dde30d8bd68/zstandard-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea9d54cc3d8064260114a0bbf3479fc4a98b21dffc89b3459edd506b69262f6e", size = 506100, upload-time = "2025-09-14T22:16:23.569Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/14/0d/d0a405dad6ab6f9f759c26d866cca66cb209bff6f8db656074d662a953dd/zstandard-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b9af1fe743828123e12b41dd8091eca1074d0c1569cc42e6e1eee98027f2bbd0", size = 795263, upload-time = "2025-09-14T22:18:21.683Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/ca/aa/ceb8d79cbad6dabd4cb1178ca853f6a4374d791c5e0241a0988173e2a341/zstandard-0.25.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b14abacf83dfb5c25eb4e4a79520de9e7e205f72c9ee7702f91233ae57d33a2", size = 640560, upload-time = "2025-09-14T22:18:22.867Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/88/cd/2cf6d476131b509cc122d25d3416a2d0aa17687ddbada7599149f9da620e/zstandard-0.25.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:a51ff14f8017338e2f2e5dab738ce1ec3b5a851f23b18c1ae1359b1eecbee6df", size = 5344244, upload-time = "2025-09-14T22:18:24.724Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/5c/71/e14820b61a1c137966b7667b400b72fa4a45c836257e443f3d77607db268/zstandard-0.25.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3b870ce5a02d4b22286cf4944c628e0f0881b11b3f14667c1d62185a99e04f53", size = 5054550, upload-time = "2025-09-14T22:18:26.445Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f9/ce/26dc5a6fa956be41d0e984909224ed196ee6f91d607f0b3fd84577741a77/zstandard-0.25.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:05353cef599a7b0b98baca9b068dd36810c3ef0f42bf282583f438caf6ddcee3", size = 5401150, upload-time = "2025-09-14T22:18:28.745Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f2/1b/402cab5edcfe867465daf869d5ac2a94930931c0989633bc01d6a7d8bd68/zstandard-0.25.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:19796b39075201d51d5f5f790bf849221e58b48a39a5fc74837675d8bafc7362", size = 5448595, upload-time = "2025-09-14T22:18:30.475Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/86/b2/fc50c58271a1ead0e5a0a0e6311f4b221f35954dce438ce62751b3af9b68/zstandard-0.25.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:53e08b2445a6bc241261fea89d065536f00a581f02535f8122eba42db9375530", size = 5555290, upload-time = "2025-09-14T22:18:32.336Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/d2/20/5f72d6ba970690df90fdd37195c5caa992e70cb6f203f74cc2bcc0b8cf30/zstandard-0.25.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1f3689581a72eaba9131b1d9bdbfe520ccd169999219b41000ede2fca5c1bfdb", size = 5043898, upload-time = "2025-09-14T22:18:34.215Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/e4/f1/131a0382b8b8d11e84690574645f528f5c5b9343e06cefd77f5fd730cd2b/zstandard-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d8c56bb4e6c795fc77d74d8e8b80846e1fb8292fc0b5060cd8131d522974b751", size = 5571173, upload-time = "2025-09-14T22:18:36.117Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/53/f6/2a37931023f737fd849c5c28def57442bbafadb626da60cf9ed58461fe24/zstandard-0.25.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:53f94448fe5b10ee75d246497168e5825135d54325458c4bfffbaafabcc0a577", size = 4958261, upload-time = "2025-09-14T22:18:38.098Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b5/52/ca76ed6dbfd8845a5563d3af4e972da3b9da8a9308ca6b56b0b929d93e23/zstandard-0.25.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c2ba942c94e0691467ab901fc51b6f2085ff48f2eea77b1a48240f011e8247c7", size = 5265680, upload-time = "2025-09-14T22:18:39.834Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/7a/59/edd117dedb97a768578b49fb2f1156defb839d1aa5b06200a62be943667f/zstandard-0.25.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:07b527a69c1e1c8b5ab1ab14e2afe0675614a09182213f21a0717b62027b5936", size = 5439747, upload-time = "2025-09-14T22:18:41.647Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/75/71/c2e9234643dcfbd6c5e975e9a2b0050e1b2afffda6c3a959e1b87997bc80/zstandard-0.25.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:51526324f1b23229001eb3735bc8c94f9c578b1bd9e867a0a646a3b17109f388", size = 5818805, upload-time = "2025-09-14T22:18:43.602Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/f5/93/8ebc19f0a31c44ea0e7348f9b0d4b326ed413b6575a3c6ff4ed50222abb6/zstandard-0.25.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89c4b48479a43f820b749df49cd7ba2dbc2b1b78560ecb5ab52985574fd40b27", size = 5362280, upload-time = "2025-09-14T22:18:45.625Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/b8/e9/29cc59d4a9d51b3fd8b477d858d0bd7ab627f700908bf1517f46ddd470ae/zstandard-0.25.0-cp39-cp39-win32.whl", hash = "sha256:1cd5da4d8e8ee0e88be976c294db744773459d51bb32f707a0f166e5ad5c8649", size = 436460, upload-time = "2025-09-14T22:18:49.077Z" },
{ url = "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/packages/41/b5/bc7a92c116e2ef32dc8061c209d71e97ff6df37487d7d39adb51a343ee89/zstandard-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:37daddd452c0ffb65da00620afb8e17abd4adaae6ce6310702841760c2c26860", size = 506097, upload-time = "2025-09-14T22:18:47.342Z" },
]