mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-07-13 09:18:04 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0719e1e886 |
@@ -1,18 +1,23 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
---
|
||||
name: Pyright
|
||||
name: MyPy
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
pyright:
|
||||
name: Run Pyright
|
||||
mypy:
|
||||
name: Run MyPy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup reviewdog
|
||||
uses: reviewdog/action-setup@v1
|
||||
with:
|
||||
reviewdog_version: latest
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
@@ -22,10 +27,10 @@ jobs:
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system .[pyright,pytest]
|
||||
uv pip install --system .[mypy]
|
||||
|
||||
- name: Execute Pyright
|
||||
uses: jordemort/action-pyright@v1
|
||||
- name: Execute MyPy
|
||||
uses: tsuyoshicho/action-mypy@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
reporter: ${{ github.event_name == 'pull_request' && 'github-pr-review' || 'github-check' }}
|
||||
fail_on_error: true
|
||||
@@ -2,9 +2,9 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pyright
|
||||
name: pyright
|
||||
entry: pyright
|
||||
- id: mypy
|
||||
name: mypy
|
||||
entry: mypy
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
require_serial: true
|
||||
|
||||
+12
-23
@@ -27,7 +27,7 @@ Thank you so much for showing interest in contributing to TagStudio! Here are a
|
||||
- I've read the [FAQ](https://github.com/TagStudioDev/TagStudio/blob/main/README.md#faq)
|
||||
- I've checked the project's [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls)
|
||||
- **I've created a new issue for my feature/fix _before_ starting work on it**, or have at least notified others in the relevant existing issue(s) of my intention to work on it
|
||||
- I've set up my development environment including Ruff, Pyright, and Pytest
|
||||
- I've set up my development environment including Ruff, Mypy, and PyTest
|
||||
- I've read the CONTRIBUTING.md/Contributing page on the documentation site as well as the and/or [Style Guide](style.md)
|
||||
- **_I mean it, I've found or created an issue for my feature/fix!_**
|
||||
|
||||
@@ -75,11 +75,7 @@ When pushing your code, several automated workflows will check it against predef
|
||||
|
||||
### [Ruff](https://github.com/astral-sh/ruff)
|
||||
|
||||
Ruff is a Python linter and code formatter that helps enforce a consistent formatting style across our codebase.
|
||||
|
||||
Ruff is installed alongside the `pip install -e ".[dev]"` command, but is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff), PyCharm [plugin](https://plugins.jetbrains.com/plugin/20574-ruff), and [more](https://docs.astral.sh/ruff/integrations/).
|
||||
|
||||
Our Ruff workflow runs whenever a pull request is opened or commits are pushed. Note that this workflow **does NOT** format code, but just serves to report any issues that have not been addressed by that point.
|
||||
A Python linter and code formatter. Ruff uses the `pyproject.toml` as its config file and runs whenever code is pushed or pulled into the project.
|
||||
|
||||
#### Running Locally
|
||||
|
||||
@@ -91,31 +87,24 @@ Inside the root repository directory:
|
||||
|
||||
Ruff should automatically discover the configuration options inside the [pyproject.toml](https://github.com/TagStudioDev/TagStudio/blob/main/pyproject.toml) file. For more information, see the [ruff configuration discovery docs](https://docs.astral.sh/ruff/configuration/#config-file-discovery).
|
||||
|
||||
### [Pyright](https://github.com/microsoft/pyright)
|
||||
Ruff is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff), PyCharm [plugin](https://plugins.jetbrains.com/plugin/20574-ruff), and [more](https://docs.astral.sh/ruff/integrations/).
|
||||
|
||||
Pyright is a static type checker for Python that helps enforce type strictness and prevent easy-to-miss errors across our codebase.
|
||||
### [Mypy](https://github.com/python/mypy)
|
||||
|
||||
Pyright is installed alongside the `pip install -e ".[dev]"` command, but is also available as VS Code extensions (see the [Pyright](https://marketplace.visualstudio.com/items?itemName=ms-pyright.pyright), [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance), and [basedpyright](https://marketplace.visualstudio.com/items?itemName=detachhead.basedpyright) extensions), a PyCharm [setting](https://www.jetbrains.com/help/pycharm/lsp-tools.html#pyright), or in the form of forks such as [basedpyright](https://docs.basedpyright.com/latest/).
|
||||
|
||||
Our Pyright workflow runs whenever a pull request is opened or commits are pushed. Note that this **does NOT** format code or fix issues, but just serves to report any issues that have not been addressed by that point.
|
||||
Mypy is a static type checker for Python. It sure has a lot to say sometimes, but we recommend you take its advice when possible. Mypy also uses the `pyproject.toml` as its config file and runs whenever code is pushed or pulled into the project.
|
||||
|
||||
#### Running Locally
|
||||
|
||||
- Run `pyright` to check code
|
||||
- **(First time only)** Run the following:
|
||||
- `mkdir -p .mypy_cache`
|
||||
- `mypy --install-types --non-interactive`
|
||||
- You can now check code by running `mypy --config-file pyproject.toml .` in the repository root. _(Don't forget the "." at the end!)_
|
||||
|
||||
Pyright/basedpyright should automatically discover the configuration options inside the [pyproject.toml](https://github.com/TagStudioDev/TagStudio/blob/main/pyproject.toml) file.
|
||||
Mypy is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=matangover.mypy), PyCharm [plugin](https://plugins.jetbrains.com/plugin/11086-mypy), and [more](https://plugins.jetbrains.com/plugin/11086-mypy).
|
||||
|
||||
### [Pytest](https://github.com/pytest-dev/pytest)
|
||||
### PyTest
|
||||
|
||||
Pytest is our testing suite and runs all our Python code against the tests in the [`tests/`](https://github.com/TagStudioDev/TagStudio/tree/main/tests) directory.
|
||||
|
||||
Pytest is installed alongside the `pip install -e ".[dev]"` command.
|
||||
|
||||
Our Pytest workflow runs whenever a pull request is opened or commits are pushed.
|
||||
|
||||
#### Running Locally
|
||||
|
||||
- Run all tests by running `pytest tests/` in the repository root
|
||||
- Run all tests by running `pytest tests/` in the repository root.
|
||||
|
||||
## Code Style
|
||||
|
||||
|
||||
+1
-1
@@ -146,7 +146,7 @@ The entry point for TagStudio is `src/tagstudio/main.py`. You can target this fi
|
||||
|
||||
### pre-commit
|
||||
|
||||
There is a [pre-commit](https://pre-commit.com/) configuration that will run through some checks before code is committed. Namely Pyright and Ruff will check your code, catching those nits right away.
|
||||
There is a [pre-commit](https://pre-commit.com/) configuration that will run through some checks before code is committed. Namely, mypy and the Ruff linter and formatter will check your code, catching those nits right away.
|
||||
|
||||
Once you have pre-commit installed, just run:
|
||||
|
||||
|
||||
+3
-12
@@ -148,9 +148,9 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 200
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [c15e2b5](https://github.com/TagStudioDev/TagStudio/commit/c15e2b56eedd0a3c13391fa43571b8f8f7c7a91f) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Used From | Format | Location |
|
||||
| --------- | ------ | ----------------------------------------------- |
|
||||
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
|
||||
- Adds `text_field_templates` and `date_field_templates` tables.
|
||||
- Drops `boolean_fields` and `value_type` tables.
|
||||
@@ -162,12 +162,3 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
- Values are set to `TRUE` if the field row was previously a "TEXT_BOX" type.
|
||||
- Repairs existing "Description" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE` _(Previously done in [Version 7](#version-7))_.
|
||||
- Repairs existing "Comments" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE`.
|
||||
|
||||
#### Version 201
|
||||
|
||||
| Used From | Format | Location |
|
||||
| --------- | ------ | ----------------------------------------------- |
|
||||
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
|
||||
- Drops `type_key` columns from `text_fields` and `datetime_fields` tables.
|
||||
- Enforces column positions for `text_fields` and `datetime_fields` tables.
|
||||
|
||||
+30
-8
@@ -42,17 +42,17 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["tagstudio[mkdocs,pyright,pre-commit,pyinstaller,pytest,ruff]"]
|
||||
dev = ["tagstudio[mkdocs,mypy,pre-commit,pyinstaller,pytest,ruff]"]
|
||||
mkdocs = ["mkdocs-material[imaging]>=9.6.14", "mkdocs-redirects~=1.2"]
|
||||
pyright = ["pyright~=1.1.409"]
|
||||
mypy = ["mypy==1.15.0", "mypy-extensions==1.*", "types-ujson~=5.10"]
|
||||
pre-commit = ["pre-commit~=4.2"]
|
||||
pyinstaller = ["Pyinstaller~=6.13"]
|
||||
pytest = [
|
||||
"pytest==9.0.3",
|
||||
"pytest==8.3.5",
|
||||
"pytest-cov==6.1.1",
|
||||
"pytest-mock==3.15.1",
|
||||
"pytest-qt==4.4.0",
|
||||
"syrupy==5.1.0",
|
||||
"syrupy==4.9.1",
|
||||
]
|
||||
ruff = ["ruff==0.11.8"]
|
||||
|
||||
@@ -62,6 +62,31 @@ tagstudio = "tagstudio.main:main"
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/tagstudio"]
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path = ["src/tagstudio"]
|
||||
disable_error_code = [
|
||||
"annotation-unchecked",
|
||||
"func-returns-value",
|
||||
"import-untyped",
|
||||
]
|
||||
explicit_package_bases = true
|
||||
ignore_missing_imports = true
|
||||
implicit_optional = true
|
||||
strict_optional = false
|
||||
warn_unused_ignores = true
|
||||
exclude = ["build", "dist"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tagstudio.qt.main_window"
|
||||
ignore_errors = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tagstudio.qt.ui.home_ui"
|
||||
ignore_errors = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tagstudio.core.ts_core"
|
||||
ignore_errors = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
#addopts = "-m 'not qt'"
|
||||
@@ -74,20 +99,17 @@ ignore = [
|
||||
"src/tagstudio/qt/previews/vendored/pydub/",
|
||||
]
|
||||
include = ["src/tagstudio", "tests"]
|
||||
# reference for the settings here: https://github.com/microsoft/pyright/blob/main/docs/configuration.md
|
||||
reportAny = false
|
||||
reportIgnoreCommentWithoutRule = false
|
||||
reportImplicitStringConcatenation = false
|
||||
reportImportCycles = false
|
||||
reportMissingTypeArgument = false
|
||||
reportMissingTypeStubs = false
|
||||
# reportOptionalMemberAccess = false
|
||||
reportUnannotatedClassAttribute = false
|
||||
reportUnknownArgumentType = false
|
||||
reportUnknownLambdaType = false
|
||||
reportUnknownMemberType = false
|
||||
reportUnusedCallResult = false
|
||||
reportUnannotatedClassAttribute = false
|
||||
reportUninitializedInstanceVariable = false
|
||||
|
||||
[tool.ruff]
|
||||
exclude = ["home_ui.py", "resources.py", "resources_rc.py"]
|
||||
|
||||
@@ -9,7 +9,7 @@ JSON_FILENAME: str = "ts_library.json"
|
||||
|
||||
DB_VERSION_CURRENT_KEY: str = "CURRENT"
|
||||
DB_VERSION_INITIAL_KEY: str = "INITIAL"
|
||||
DB_VERSION: int = 201
|
||||
DB_VERSION: int = 200
|
||||
|
||||
TAG_CHILDREN_QUERY = text("""
|
||||
WITH RECURSIVE ChildTags AS (
|
||||
|
||||
@@ -20,19 +20,19 @@ class BaseField(Base):
|
||||
|
||||
@declared_attr
|
||||
def id(self) -> Mapped[int]:
|
||||
return mapped_column(primary_key=True, autoincrement=True, sort_order=1)
|
||||
return mapped_column(primary_key=True, autoincrement=True)
|
||||
|
||||
@declared_attr
|
||||
def name(self) -> Mapped[str]:
|
||||
return mapped_column(nullable=False, default="", sort_order=2)
|
||||
return mapped_column(nullable=False, default="")
|
||||
|
||||
@declared_attr
|
||||
def entry_id(self) -> Mapped[int]:
|
||||
return mapped_column(ForeignKey("entries.id"), sort_order=3)
|
||||
return mapped_column(ForeignKey("entries.id"))
|
||||
|
||||
@declared_attr
|
||||
def entry(self) -> Mapped[Entry]:
|
||||
return relationship(foreign_keys=[self.entry_id]) # pyright: ignore[reportArgumentType]
|
||||
return relationship(foreign_keys=[self.entry_id]) # type: ignore # pyright: ignore[reportArgumentType]
|
||||
|
||||
@property
|
||||
def class_name(self) -> str:
|
||||
@@ -47,7 +47,7 @@ class BaseField(Base):
|
||||
class TextField(BaseField):
|
||||
__tablename__ = "text_fields"
|
||||
|
||||
value: Mapped[str | None] = mapped_column(sort_order=4)
|
||||
value: Mapped[str | None]
|
||||
is_multiline: Mapped[bool] = mapped_column(nullable=False, default=False)
|
||||
|
||||
@override
|
||||
@@ -75,7 +75,7 @@ class TextField(BaseField):
|
||||
class DatetimeField(BaseField):
|
||||
__tablename__ = "datetime_fields"
|
||||
|
||||
value: Mapped[str | None] = mapped_column(sort_order=4)
|
||||
value: Mapped[str | None]
|
||||
|
||||
@override
|
||||
def __eq__(self, other: object) -> bool:
|
||||
|
||||
@@ -419,7 +419,6 @@ class Library:
|
||||
# Under -> sqlite-the-sqlite-dialect-now-uses-nullpool-for-file-based-databases
|
||||
poolclass = None if storage_path == ":memory:" else NullPool
|
||||
loaded_db_version: int = 0
|
||||
initial_db_version: int = DB_VERSION
|
||||
|
||||
logger.info(
|
||||
"[Library] Opening SQLite Library",
|
||||
@@ -431,7 +430,6 @@ class Library:
|
||||
# Don't check DB version when creating new library
|
||||
if not is_new:
|
||||
loaded_db_version = self.get_version(DB_VERSION_CURRENT_KEY)
|
||||
initial_db_version = self.get_version(DB_VERSION_INITIAL_KEY)
|
||||
|
||||
# ======================== Library Database Version Checking =======================
|
||||
# DB_VERSION 6 is the first supported SQLite DB version.
|
||||
@@ -454,7 +452,7 @@ class Library:
|
||||
),
|
||||
)
|
||||
|
||||
logger.info(f"[Library] Library DB version: {loaded_db_version}")
|
||||
logger.info(f"[Library] DB_VERSION: {loaded_db_version}")
|
||||
make_tables(self.engine)
|
||||
|
||||
if is_new:
|
||||
@@ -573,9 +571,6 @@ class Library:
|
||||
self.__apply_db104_migrations(session, library_dir)
|
||||
if loaded_db_version < 200:
|
||||
self.__apply_db200_migrations(session)
|
||||
# changes: field tables
|
||||
if initial_db_version < 200 and loaded_db_version < 201:
|
||||
self.__apply_db201_migrations(session)
|
||||
|
||||
session.execute(
|
||||
text("CREATE INDEX IF NOT EXISTS idx_tags_name_shorthand ON tags (name, shorthand)")
|
||||
@@ -593,7 +588,6 @@ class Library:
|
||||
|
||||
# Update DB_VERSION
|
||||
if loaded_db_version < DB_VERSION:
|
||||
logger.info(f"[Library] Library migrated to DB version {DB_VERSION}")
|
||||
self.set_version(DB_VERSION_CURRENT_KEY, DB_VERSION)
|
||||
|
||||
# everything is fine, set the library path
|
||||
@@ -814,6 +808,10 @@ class Library:
|
||||
session.execute(text("UPDATE datetime_fields SET name = type_key"))
|
||||
session.flush()
|
||||
|
||||
# TODO: Remove `type_key` columns from text_fields and datetime_fields tables.
|
||||
# See issue with dropping columns foreign keys in SQLite:
|
||||
# https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
|
||||
|
||||
# Change `name` values to title case
|
||||
logger.info("[Library][Migration][200] Normalizing TextField names...")
|
||||
for text_field in session.execute(select(TextField)).scalars():
|
||||
@@ -865,57 +863,6 @@ class Library:
|
||||
|
||||
session.commit()
|
||||
|
||||
def __apply_db201_migrations(self, session: Session):
|
||||
"""Migrate DB to DB_VERSION 201."""
|
||||
with session:
|
||||
create_text_fields_table = text("""
|
||||
CREATE TABLE text_fields_new (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
entry_id INTEGER NOT NULL,
|
||||
value VARCHAR,
|
||||
is_multiline BOOLEAN NOT NULL,
|
||||
FOREIGN KEY(entry_id) REFERENCES entries (id)
|
||||
)
|
||||
""")
|
||||
create_datetime_fields_table = text("""
|
||||
CREATE TABLE datetime_fields_new (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
entry_id INTEGER NOT NULL,
|
||||
value VARCHAR,
|
||||
FOREIGN KEY(entry_id) REFERENCES entries (id)
|
||||
)
|
||||
""")
|
||||
|
||||
logger.info("[Library][Migration][201] Dropping type_key from text_fields table...")
|
||||
session.execute(create_text_fields_table)
|
||||
session.flush()
|
||||
session.execute(
|
||||
text("""
|
||||
INSERT INTO text_fields_new (id, name, entry_id, value, is_multiline)
|
||||
SELECT id, name, entry_id, value, is_multiline
|
||||
FROM text_fields
|
||||
""")
|
||||
)
|
||||
session.execute(text("DROP TABLE text_fields"))
|
||||
session.execute(text("ALTER TABLE text_fields_new RENAME TO text_fields"))
|
||||
|
||||
logger.info("[Library][Migration][201] Dropping type_key from datetime_fields table...")
|
||||
session.execute(create_datetime_fields_table)
|
||||
session.flush()
|
||||
session.execute(
|
||||
text("""
|
||||
INSERT INTO datetime_fields_new (id, name, entry_id, value)
|
||||
SELECT id, name, entry_id, value
|
||||
FROM datetime_fields
|
||||
""")
|
||||
)
|
||||
session.execute(text("DROP TABLE datetime_fields"))
|
||||
session.execute(text("ALTER TABLE datetime_fields_new RENAME TO datetime_fields"))
|
||||
|
||||
session.commit()
|
||||
|
||||
@property
|
||||
def field_templates(self) -> Sequence[BaseFieldTemplate]:
|
||||
with Session(self.engine) as session:
|
||||
@@ -1010,7 +957,7 @@ class Library:
|
||||
)
|
||||
statement = statement.distinct()
|
||||
entries: ScalarResult[Entry] | list[Entry] = session.execute(statement).scalars()
|
||||
entries = entries.unique()
|
||||
entries = entries.unique() # type: ignore
|
||||
|
||||
entry_order_dict = {e_id: order for order, e_id in enumerate(entry_ids)}
|
||||
entries = sorted(entries, key=lambda e: entry_order_dict[e.id])
|
||||
@@ -1467,7 +1414,7 @@ class Library:
|
||||
if tag:
|
||||
tags.append(tag.id)
|
||||
else:
|
||||
new = session.add(Tag(name=string))
|
||||
new = session.add(Tag(name=string)) # type: ignore
|
||||
if new:
|
||||
tags.append(new.id)
|
||||
session.flush()
|
||||
|
||||
@@ -47,21 +47,21 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
|
||||
self.lib = lib
|
||||
|
||||
@override
|
||||
def visit_or_list(self, node: ORList) -> ColumnElement[bool]:
|
||||
def visit_or_list(self, node: ORList) -> ColumnElement[bool]: # type: ignore
|
||||
tag_ids, bool_expressions = self.__separate_tags(node.elements, only_single=False)
|
||||
if len(tag_ids) > 0:
|
||||
bool_expressions.append(self.__entry_has_any_tags(tag_ids))
|
||||
return or_(*bool_expressions)
|
||||
|
||||
@override
|
||||
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]:
|
||||
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]: # type: ignore
|
||||
tag_ids, bool_expressions = self.__separate_tags(node.terms, only_single=True)
|
||||
if len(tag_ids) > 0:
|
||||
bool_expressions.append(self.__entry_has_all_tags(tag_ids))
|
||||
return and_(*bool_expressions)
|
||||
|
||||
@override
|
||||
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]:
|
||||
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]: # type: ignore
|
||||
"""Returns a Boolean Expression that is true, if the Entry satisfies the constraint."""
|
||||
if len(node.properties) != 0:
|
||||
raise NotImplementedError("Properties are not implemented yet") # TODO TSQLANG
|
||||
@@ -113,11 +113,11 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
|
||||
raise NotImplementedError("This type of constraint is not implemented yet")
|
||||
|
||||
@override
|
||||
def visit_property(self, node: Property) -> ColumnElement[bool]:
|
||||
def visit_property(self, node: Property) -> ColumnElement[bool]: # type: ignore
|
||||
raise NotImplementedError("This should never be reached!")
|
||||
|
||||
@override
|
||||
def visit_not(self, node: Not) -> ColumnElement[bool]:
|
||||
def visit_not(self, node: Not) -> ColumnElement[bool]: # type: ignore
|
||||
return ~self.visit(node.child)
|
||||
|
||||
def __get_tag_ids(self, tag_name: str, include_children: bool = True) -> list[int]:
|
||||
|
||||
@@ -88,6 +88,6 @@ class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
|
||||
self.driver.library_info_window.update_cleanup()
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QtGui.QShowEvent) -> None:
|
||||
def showEvent(self, event: QtGui.QShowEvent) -> None: # type: ignore
|
||||
self.update_ignored_count()
|
||||
return super().showEvent(event)
|
||||
|
||||
@@ -46,6 +46,6 @@ class IgnoreModal(IgnoreModalView):
|
||||
Ignore.write_ignore_file(self.lib.library_dir, lines)
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QShowEvent) -> None:
|
||||
def showEvent(self, event: QShowEvent) -> None: # type: ignore
|
||||
self.__load_file()
|
||||
return super().showEvent(event)
|
||||
|
||||
@@ -162,6 +162,6 @@ class LibraryInfoWindow(LibraryInfoWindowView):
|
||||
return size
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QtGui.QShowEvent):
|
||||
def showEvent(self, event: QtGui.QShowEvent): # type: ignore
|
||||
self.refresh()
|
||||
return super().showEvent(event)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import io
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, override
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import cv2
|
||||
import rawpy
|
||||
@@ -12,10 +12,6 @@ import structlog
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from PIL.Image import DecompressionBombError
|
||||
from PySide6.QtCore import QSize
|
||||
from rawpy import (
|
||||
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
|
||||
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
|
||||
)
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.media_types import MediaCategories
|
||||
@@ -54,8 +50,8 @@ class PreviewThumb(PreviewThumbView):
|
||||
stats.width = image.width
|
||||
stats.height = image.height
|
||||
except (
|
||||
LibRawIOError,
|
||||
LibRawFileUnsupportedError,
|
||||
rawpy.LibRawIOError,
|
||||
rawpy.LibRawFileUnsupportedError,
|
||||
FileNotFoundError,
|
||||
):
|
||||
pass
|
||||
@@ -148,22 +144,18 @@ class PreviewThumb(PreviewThumbView):
|
||||
self._display_image(filepath)
|
||||
return self.__get_image_stats(filepath)
|
||||
|
||||
@override
|
||||
def _open_file_action_callback(self):
|
||||
open_file(
|
||||
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
|
||||
)
|
||||
|
||||
@override
|
||||
def _open_explorer_action_callback(self):
|
||||
open_file(self.__current_file, file_manager=True)
|
||||
|
||||
@override
|
||||
def _delete_action_callback(self):
|
||||
if bool(self.__current_file):
|
||||
self.__driver.delete_files_callback(self.__current_file)
|
||||
|
||||
@override
|
||||
def _button_wrapper_callback(self):
|
||||
open_file(
|
||||
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
|
||||
|
||||
@@ -34,7 +34,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
self.__entries = entries
|
||||
|
||||
@override
|
||||
def _on_click(self, tag: Tag) -> None:
|
||||
def _on_click(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
match self.__driver.settings.tag_click_action:
|
||||
case TagClickActionOption.OPEN_EDIT:
|
||||
self._on_edit(tag)
|
||||
@@ -58,7 +58,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
)
|
||||
|
||||
@override
|
||||
def _on_remove(self, tag: Tag) -> None:
|
||||
def _on_remove(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
logger.info(
|
||||
"[TagBoxWidget] remove_tag",
|
||||
selected=self.__entries,
|
||||
@@ -70,7 +70,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
self.on_update.emit()
|
||||
|
||||
@override
|
||||
def _on_edit(self, tag: Tag) -> None:
|
||||
def _on_edit(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
build_tag_panel = BuildTagPanel(self.__driver.lib, tag=tag)
|
||||
|
||||
edit_modal = PanelModal(
|
||||
@@ -92,7 +92,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
edit_modal.show()
|
||||
|
||||
@override
|
||||
def _on_search(self, tag: Tag) -> None:
|
||||
def _on_search(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
self.__driver.main_window.search_field.setText(f"tag_id:{tag.id}")
|
||||
self.__driver.update_browsing_state(
|
||||
BrowsingState.from_tag_id(tag.id, self.__driver.browsing_history.current)
|
||||
|
||||
@@ -7,7 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
def wrap_line(
|
||||
text: str,
|
||||
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
||||
font: ImageFont.ImageFont,
|
||||
width: int = 256,
|
||||
draw: ImageDraw.ImageDraw | None = None,
|
||||
) -> int:
|
||||
@@ -31,7 +31,7 @@ def wrap_line(
|
||||
|
||||
def wrap_full_text(
|
||||
text: str,
|
||||
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
||||
font: ImageFont.ImageFont,
|
||||
width: int = 256,
|
||||
draw: ImageDraw.ImageDraw | None = None,
|
||||
) -> str:
|
||||
|
||||
@@ -138,7 +138,7 @@ class ColorBoxWidget(FieldWidget):
|
||||
)
|
||||
|
||||
self.edit_modal.saved.connect(
|
||||
lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit())
|
||||
lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit()) # type: ignore
|
||||
)
|
||||
self.edit_modal.show()
|
||||
|
||||
|
||||
@@ -114,7 +114,6 @@ class FileAttributes(QWidget):
|
||||
if filepath and filepath.is_file():
|
||||
created: dt
|
||||
if platform.system() == "Windows" or platform.system() == "Darwin":
|
||||
# NOTE: Accessing stat().st_birthtime causes linter checks to fail on some systems.
|
||||
created = dt.fromtimestamp(filepath.stat().st_birthtime) # type: ignore[attr-defined, unused-ignore]
|
||||
else:
|
||||
created = dt.fromtimestamp(filepath.stat().st_ctime)
|
||||
|
||||
@@ -467,12 +467,12 @@ class ItemThumb(FlowWidget):
|
||||
badge.setHidden(is_hidden)
|
||||
|
||||
@override
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
def enterEvent(self, event: QEnterEvent) -> None: # type: ignore[misc]
|
||||
self.show_check_badges(show=True)
|
||||
return super().enterEvent(event)
|
||||
|
||||
@override
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
def leaveEvent(self, event: QEvent) -> None: # type: ignore[misc]
|
||||
self.show_check_badges(show=False)
|
||||
return super().leaveEvent(event)
|
||||
|
||||
@@ -502,7 +502,7 @@ class ItemThumb(FlowWidget):
|
||||
)
|
||||
|
||||
@override
|
||||
def mouseMoveEvent(self, event: QMouseEvent) -> None:
|
||||
def mouseMoveEvent(self, event: QMouseEvent) -> None: # type: ignore[misc]
|
||||
if event.buttons() is not Qt.MouseButton.LeftButton:
|
||||
return
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ class MediaPlayer(QGraphicsView):
|
||||
"""Manage events for the media player."""
|
||||
if (
|
||||
arg__2.type() == QEvent.Type.MouseButtonPress
|
||||
and arg__2.button() == Qt.MouseButton.LeftButton # pyright: ignore[reportAttributeAccessIssue]
|
||||
and arg__2.button() == Qt.MouseButton.LeftButton # type: ignore
|
||||
):
|
||||
if arg__1 == self.play_pause:
|
||||
self.toggle_play()
|
||||
|
||||
@@ -364,7 +364,7 @@ class JsonMigrationModal(QObject):
|
||||
iterator = FunctionIterator(self.migration_iterator)
|
||||
iterator.value.connect(
|
||||
lambda x: (
|
||||
pb.setLabelText(f"<h4>{x}</h4>"),
|
||||
pb.setLabelText(f"<h4>{x}</h4>"), # type: ignore
|
||||
self.update_sql_value_ui(show_msg_box=False)
|
||||
if x == Translations["json_migration.checking_for_parity"]
|
||||
else (),
|
||||
@@ -377,8 +377,8 @@ class JsonMigrationModal(QObject):
|
||||
r.done.connect(
|
||||
lambda: (
|
||||
self.update_sql_value_ui(show_msg_box=not skip_ui),
|
||||
pb.setMinimum(1),
|
||||
pb.setValue(1),
|
||||
pb.setMinimum(1), # type: ignore
|
||||
pb.setValue(1), # type: ignore
|
||||
# Enable the finish button
|
||||
cast(QPushButtonWrapper, self.stack[1].buttons[4]).setDisabled(False),
|
||||
)
|
||||
@@ -483,26 +483,26 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
def update_json_entry_count(self, value: int):
|
||||
self.old_entry_count = value
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # type:ignore
|
||||
label.setText(self.color_value_default(value))
|
||||
|
||||
def update_json_tag_count(self, value: int):
|
||||
self.old_tag_count = value
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # type:ignore
|
||||
label.setText(self.color_value_default(value))
|
||||
|
||||
def update_sql_value(self, row: int, value: int | bool, old_value: int | bool):
|
||||
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # type:ignore
|
||||
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore
|
||||
label.setText(self.color_value_conditional(old_value, value))
|
||||
warning_icon.setText("" if old_value == value else self.warning)
|
||||
|
||||
def update_parity_value(self, row: int, value: bool):
|
||||
result: str = self.match_text if value else self.differ_text
|
||||
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # type:ignore
|
||||
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # type:ignore
|
||||
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # type:ignore
|
||||
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore
|
||||
old_label.setText(self.color_value_conditional(self.match_text, result))
|
||||
new_label.setText(self.color_value_conditional(self.match_text, result))
|
||||
old_warning_icon.setText("" if value else self.warning)
|
||||
|
||||
@@ -218,7 +218,7 @@ class Pagination(QWidget):
|
||||
if i < index:
|
||||
if (i != 0) and i >= index - 4:
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setHidden(False)
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # type: ignore
|
||||
str(i + 1)
|
||||
)
|
||||
self._assign_click(
|
||||
@@ -237,7 +237,9 @@ class Pagination(QWidget):
|
||||
elif i > index:
|
||||
if i != page_count - 1 and i <= index + 4:
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setHidden(False)
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setText(str(i + 1)) # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setText( # type: ignore
|
||||
str(i + 1)
|
||||
)
|
||||
self._assign_click(
|
||||
cast(
|
||||
QPushButtonWrapper,
|
||||
|
||||
@@ -62,6 +62,6 @@ class ProgressWidget(QWidget):
|
||||
|
||||
r = CustomRunnable(lambda: iterator.run())
|
||||
r.done.connect(
|
||||
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks])
|
||||
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks]) # type: ignore
|
||||
)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
|
||||
@@ -59,15 +59,15 @@ class TagDatabasePanel(TagSearchPanel):
|
||||
return
|
||||
|
||||
message_box = QMessageBox(
|
||||
QMessageBox.Question, # pyright: ignore[reportAttributeAccessIssue]
|
||||
QMessageBox.Question, # type: ignore
|
||||
Translations["tag.remove"],
|
||||
Translations.format("tag.confirm_delete", tag_name=self.lib.tag_display_name(tag)),
|
||||
QMessageBox.Ok | QMessageBox.Cancel, # pyright: ignore[reportAttributeAccessIssue]
|
||||
QMessageBox.Ok | QMessageBox.Cancel, # type: ignore
|
||||
)
|
||||
|
||||
result = message_box.exec()
|
||||
|
||||
if result != QMessageBox.Ok: # pyright: ignore[reportAttributeAccessIssue]
|
||||
if result != QMessageBox.Ok: # type: ignore
|
||||
return
|
||||
|
||||
self.lib.remove_tag(tag.id)
|
||||
|
||||
@@ -43,7 +43,7 @@ from PIL import (
|
||||
UnidentifiedImageError,
|
||||
)
|
||||
from PIL.Image import DecompressionBombError
|
||||
from pillow_heif import register_heif_opener # pyright: ignore[reportUnknownVariableType]
|
||||
from pillow_heif import register_heif_opener
|
||||
from PySide6.QtCore import (
|
||||
QBuffer,
|
||||
QFile,
|
||||
@@ -58,10 +58,6 @@ from PySide6.QtCore import (
|
||||
from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPixmap
|
||||
from PySide6.QtPdf import QPdfDocument, QPdfDocumentRenderOptions
|
||||
from PySide6.QtSvg import QSvgRenderer
|
||||
from rawpy import (
|
||||
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
|
||||
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
|
||||
)
|
||||
|
||||
from tagstudio.core.constants import (
|
||||
FONT_SAMPLE_SIZES,
|
||||
@@ -79,11 +75,9 @@ from tagstudio.qt.helpers.gradients import four_corner_gradient
|
||||
from tagstudio.qt.helpers.image_effects import replace_transparent_pixels
|
||||
from tagstudio.qt.helpers.text_wrapper import wrap_full_text
|
||||
from tagstudio.qt.models.palette import UI_COLORS, ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.previews.vendored.blender_renderer import (
|
||||
blend_thumb, # pyright: ignore[reportUnknownVariableType]
|
||||
)
|
||||
from tagstudio.qt.previews.vendored.blender_renderer import blend_thumb
|
||||
from tagstudio.qt.previews.vendored.pydub.audio_segment import (
|
||||
_AudioSegment as AudioSegment, # pyright: ignore[reportPrivateUsage]
|
||||
_AudioSegment as AudioSegment,
|
||||
)
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
|
||||
@@ -124,7 +118,7 @@ class _TarFile:
|
||||
def __init__(self, filepath: Path, mode: Literal["r"]) -> None:
|
||||
self.tar: tarfile.TarFile
|
||||
self.filepath = filepath
|
||||
self.mode: Literal["r"] = mode
|
||||
self.mode = mode
|
||||
|
||||
def namelist(self) -> list[str]:
|
||||
return self.tar.getnames()
|
||||
@@ -133,10 +127,10 @@ class _TarFile:
|
||||
return unwrap(self.tar.extractfile(name)).read()
|
||||
|
||||
def __enter__(self) -> "_TarFile":
|
||||
self.tar = tarfile.open(name=self.filepath, mode=self.mode).__enter__()
|
||||
self.tar = tarfile.open(self.filepath, self.mode).__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args) -> None: # pyright: ignore[reportUnknownParameterType, reportMissingParameterType]
|
||||
def __exit__(self, *args) -> None:
|
||||
self.tar.__exit__(*args)
|
||||
|
||||
|
||||
@@ -299,7 +293,7 @@ class ThumbRenderer(QObject):
|
||||
|
||||
im: Image.Image = Image.new(
|
||||
mode="L",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="black",
|
||||
)
|
||||
draw = ImageDraw.Draw(im)
|
||||
@@ -330,7 +324,7 @@ class ThumbRenderer(QObject):
|
||||
# Highlight
|
||||
im_hl: Image.Image = Image.new(
|
||||
mode="RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#00000000",
|
||||
)
|
||||
draw = ImageDraw.Draw(im_hl)
|
||||
@@ -349,7 +343,7 @@ class ThumbRenderer(QObject):
|
||||
# Shadow
|
||||
im_sh: Image.Image = Image.new(
|
||||
mode="RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#00000000",
|
||||
)
|
||||
draw = ImageDraw.Draw(im_sh)
|
||||
@@ -394,7 +388,7 @@ class ThumbRenderer(QObject):
|
||||
# Create larger blank image based on smooth_factor
|
||||
im: Image.Image = Image.new(
|
||||
"RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#FF000000",
|
||||
)
|
||||
|
||||
@@ -402,13 +396,13 @@ class ThumbRenderer(QObject):
|
||||
bg: Image.Image
|
||||
bg = Image.new(
|
||||
"RGB",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#000000FF",
|
||||
)
|
||||
|
||||
# Use a background image if provided
|
||||
if bg_image:
|
||||
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
|
||||
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
|
||||
bg_im = ImageEnhance.Brightness(bg_im).enhance(0.3) # Reduce the brightness
|
||||
bg.paste(bg_im)
|
||||
|
||||
@@ -417,7 +411,7 @@ class ThumbRenderer(QObject):
|
||||
bg,
|
||||
(0, 0),
|
||||
mask=self._get_mask(
|
||||
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
(pixel_ratio * smooth_factor),
|
||||
),
|
||||
)
|
||||
@@ -501,19 +495,19 @@ class ThumbRenderer(QObject):
|
||||
# Create larger blank image based on smooth_factor
|
||||
im: Image.Image = Image.new(
|
||||
"RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#00000000",
|
||||
)
|
||||
|
||||
bg: Image.Image
|
||||
# Use a background image if provided
|
||||
if bg_image:
|
||||
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
|
||||
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
|
||||
# Create solid background color
|
||||
else:
|
||||
bg = Image.new(
|
||||
"RGB",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#000000",
|
||||
)
|
||||
# Apply color overlay
|
||||
@@ -527,7 +521,7 @@ class ThumbRenderer(QObject):
|
||||
bg,
|
||||
(0, 0),
|
||||
mask=self._get_mask(
|
||||
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
(pixel_ratio * smooth_factor),
|
||||
),
|
||||
)
|
||||
@@ -668,17 +662,17 @@ class ThumbRenderer(QObject):
|
||||
artwork = None
|
||||
if ext in [".mp3"]:
|
||||
id3_tags: id3.ID3 = id3.ID3(filepath)
|
||||
id3_covers: list = id3_tags.getall("APIC") # pyright: ignore[reportUnknownVariableType]
|
||||
id3_covers: list = id3_tags.getall("APIC")
|
||||
if id3_covers:
|
||||
artwork = Image.open(BytesIO(id3_covers[0].data))
|
||||
elif ext in [".flac"]:
|
||||
flac_tags: flac.FLAC = flac.FLAC(filepath)
|
||||
flac_covers: list = flac_tags.pictures # pyright: ignore[reportUnknownVariableType]
|
||||
flac_covers: list = flac_tags.pictures
|
||||
if flac_covers:
|
||||
artwork = Image.open(BytesIO(flac_covers[0].data))
|
||||
elif ext in [".mp4", ".m4a", ".aac"]:
|
||||
mp4_tags: mp4.MP4 = mp4.MP4(filepath)
|
||||
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportUnknownVariableType]
|
||||
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportAssignmentType]
|
||||
if mp4_covers:
|
||||
artwork = Image.open(BytesIO(mp4_covers[0]))
|
||||
if artwork:
|
||||
@@ -1094,7 +1088,7 @@ class ThumbRenderer(QObject):
|
||||
font = ImageFont.truetype(filepath, size=font_size)
|
||||
text_wrapped: str = wrap_full_text(
|
||||
FONT_SAMPLE_TEXT,
|
||||
font=font,
|
||||
font=font, # pyright: ignore[reportArgumentType]
|
||||
width=size,
|
||||
draw=draw,
|
||||
)
|
||||
@@ -1126,8 +1120,8 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
except (
|
||||
DecompressionBombError,
|
||||
LibRawIOError,
|
||||
LibRawFileUnsupportedError,
|
||||
rawpy.LibRawIOError,
|
||||
rawpy.LibRawFileUnsupportedError,
|
||||
) as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return im
|
||||
@@ -1143,7 +1137,6 @@ class ThumbRenderer(QObject):
|
||||
try:
|
||||
# Load the EXR data to an array and rotate the color space from BGRA -> RGBA
|
||||
raw_array = cv2.imread(str(filepath), cv2.IMREAD_UNCHANGED)
|
||||
assert raw_array is not None
|
||||
raw_array[..., :3] = raw_array[..., 2::-1]
|
||||
|
||||
# Correct the gamma of the raw array
|
||||
@@ -1216,7 +1209,7 @@ class ThumbRenderer(QObject):
|
||||
# Write the image to a buffer as png
|
||||
buffer: QBuffer = QBuffer()
|
||||
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
|
||||
q_image.save(device=buffer, format="PNG") # pyright: ignore[reportArgumentType]
|
||||
q_image.save(buffer, "PNG") # type: ignore[call-overload]
|
||||
|
||||
# Load the image from the buffer
|
||||
im = Image.new("RGB", (size, size), color="#1e1e1e")
|
||||
@@ -1265,7 +1258,7 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None: # pyright: ignore[reportUnusedParameter]
|
||||
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None:
|
||||
"""Render a thumbnail for an STL file.
|
||||
|
||||
Args:
|
||||
@@ -1338,7 +1331,7 @@ class ThumbRenderer(QObject):
|
||||
buffer: QBuffer = QBuffer()
|
||||
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
|
||||
try:
|
||||
q_image.save(buffer, "PNG") # pyright: ignore
|
||||
q_image.save(buffer, "PNG") # type: ignore # pyright: ignore
|
||||
im = Image.open(BytesIO(buffer.buffer().data()))
|
||||
finally:
|
||||
buffer.close()
|
||||
@@ -1621,7 +1614,6 @@ class ThumbRenderer(QObject):
|
||||
|
||||
def fetch_cached_image(file_name: Path):
|
||||
image: Image.Image | None = None
|
||||
assert self.driver.cache_manager is not None
|
||||
cached_path = self.driver.cache_manager.get_file_path(file_name)
|
||||
|
||||
if cached_path and cached_path.is_file():
|
||||
@@ -1884,7 +1876,6 @@ class ThumbRenderer(QObject):
|
||||
image = self._resize_image(image, (adj_size, adj_size))
|
||||
|
||||
if save_to_file and savable_media_type and image:
|
||||
assert self.driver.cache_manager is not None
|
||||
self.driver.cache_manager.save_image(image, save_to_file, mode="RGBA")
|
||||
|
||||
except (
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
# Vendored from pydub
|
||||
# type: ignore
|
||||
|
||||
|
||||
import array
|
||||
|
||||
+13
-12
@@ -8,6 +8,7 @@
|
||||
|
||||
"""A Qt driver for TagStudio."""
|
||||
|
||||
import contextlib
|
||||
import ctypes
|
||||
import math
|
||||
import os
|
||||
@@ -193,7 +194,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
applied_theme: Theme
|
||||
|
||||
lib: Library
|
||||
cache_manager: CacheManager | None
|
||||
cache_manager: CacheManager
|
||||
|
||||
browsing_history: History[BrowsingState]
|
||||
|
||||
@@ -350,7 +351,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
if os.name == "nt":
|
||||
appid = "cyanvoxel.tagstudio.9"
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) # type: ignore[attr-defined,unused-ignore]
|
||||
|
||||
self.app.setApplicationName("tagstudio")
|
||||
self.app.setApplicationDisplayName("TagStudio")
|
||||
@@ -538,7 +539,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
# TODO: Move this to a settings screen.
|
||||
self.main_window.menu_bar.clear_thumb_cache_action.triggered.connect(
|
||||
lambda: unwrap(self.cache_manager).clear_cache()
|
||||
lambda: self.cache_manager.clear_cache()
|
||||
)
|
||||
|
||||
# endregion
|
||||
@@ -912,7 +913,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
origin_id(id): The entry ID associated with the widget making the call.
|
||||
"""
|
||||
entry: Entry | None = None
|
||||
pending: list[tuple[int | None, Path]] = []
|
||||
pending: list[tuple[int, Path]] = []
|
||||
deleted_count: int = 0
|
||||
|
||||
selected = self.selected
|
||||
@@ -920,13 +921,14 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
if len(selected) <= 1 and origin_path:
|
||||
origin_id_ = origin_id
|
||||
if origin_id_ is None:
|
||||
origin_id_ = selected[0] if len(selected) > 0 else None
|
||||
if not origin_id_:
|
||||
with contextlib.suppress(IndexError):
|
||||
origin_id_ = selected[0]
|
||||
|
||||
pending.append((origin_id_, Path(origin_path)))
|
||||
else:
|
||||
for item in selected:
|
||||
entry = unwrap(self.lib.get_entry(item))
|
||||
entry = self.lib.get_entry(item)
|
||||
filepath: Path = entry.path
|
||||
pending.append((item, filepath))
|
||||
|
||||
@@ -948,8 +950,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.status_bar.showMessage(msg)
|
||||
self.main_window.status_bar.repaint()
|
||||
|
||||
if e_id is not None:
|
||||
self.lib.remove_entries([e_id])
|
||||
self.lib.remove_entries([e_id])
|
||||
if delete_file(library_dir / f):
|
||||
deleted_count += 1
|
||||
|
||||
@@ -1100,7 +1101,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
pw.hide(),
|
||||
pw.deleteLater(),
|
||||
# refresh the library only when new items are added
|
||||
files_count and self.update_browsing_state(),
|
||||
files_count and self.update_browsing_state(), # type: ignore
|
||||
)
|
||||
)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
@@ -1225,10 +1226,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
for field in self.copy_buffer["fields"]:
|
||||
exists = False
|
||||
for e in existing_fields:
|
||||
if field == e:
|
||||
if field.type_key == e.type_key and field.value == e.value:
|
||||
exists = True
|
||||
if not exists:
|
||||
self.lib.add_field_to_entries(id, field=field)
|
||||
self.lib.add_field_to_entries(id, field_id=field.type_key, value=field.value)
|
||||
self.lib.add_tags_to_entries(id, self.copy_buffer["tags"])
|
||||
if len(self.selected) > 1:
|
||||
if TAG_ARCHIVED in self.copy_buffer["tags"]:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from PySide6.QtCore import QObject, QRunnable, Signal
|
||||
|
||||
|
||||
class CustomRunnable(QRunnable, QObject): # pyright: ignore[reportUnsafeMultipleInheritance]
|
||||
class CustomRunnable(QRunnable, QObject):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, function) -> None:
|
||||
|
||||
@@ -86,7 +86,7 @@ class ThumbButton(QPushButtonWrapper):
|
||||
)
|
||||
|
||||
@override
|
||||
def paintEvent(self, arg__1: QPaintEvent) -> None:
|
||||
def paintEvent(self, arg__1: QPaintEvent) -> None: # type: ignore
|
||||
super().paintEvent(arg__1)
|
||||
if self.hovered or self.selected:
|
||||
painter = QPainter()
|
||||
@@ -127,13 +127,13 @@ class ThumbButton(QPushButtonWrapper):
|
||||
painter.end()
|
||||
|
||||
@override
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
def enterEvent(self, event: QEnterEvent) -> None: # type: ignore
|
||||
self.hovered = True
|
||||
self.repaint()
|
||||
return super().enterEvent(event)
|
||||
|
||||
@override
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
def leaveEvent(self, event: QEvent) -> None: # type: ignore
|
||||
self.hovered = False
|
||||
self.repaint()
|
||||
return super().leaveEvent(event)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"about.config_path": "Mlafuplas fu sentakuzma",
|
||||
"about.description": "TagStudio - apli per parjat mlafu au riso mit festaretol, hadafsui per anta sentakuzma au mange dekizma brukdjin made. Nil kinijena apli os mlafufal, nil mare fu flankamlafu, au na nilraz ti kawri hel parjat fu kompyu.",
|
||||
"about.description": "TagStudio zeting ke parjat mlafu au riso mit festaretol, hadafvui per anta sentakuzma au mange dekizma brukdjin made. Nil kinijena zeting os mlafufal, jamnai mare fu flanka mlafu au perpa tumam fu kompyu.",
|
||||
"about.documentation": "Mahaklarazma",
|
||||
"about.license": "Brukruuru",
|
||||
"about.module.found": "Finnajena",
|
||||
@@ -10,11 +10,11 @@
|
||||
"app.pre_release": "De-Gvir",
|
||||
"app.title": "{base_title} - Mlafuhuomi '{library_dir}'",
|
||||
"color.color_border": "Bruk minusvikti varge per flanka",
|
||||
"color.confirm_delete": "Du kestetsa varge \"{color_name}\" we?",
|
||||
"color.confirm_delete": "Du kestetsa varge \"{color_name}\"?",
|
||||
"color.delete": "Keste festaretol",
|
||||
"color.import_pack": "Nasii klaani fu varge",
|
||||
"color.name": "Namae",
|
||||
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam we? Afto zol keste ALTING ine vargetumam au sebja!",
|
||||
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam? Afto zol keste alting ine vargetumam au sebja!",
|
||||
"color.namespace.delete.title": "Keste vargetumam",
|
||||
"color.new": "Neo varge",
|
||||
"color.placeholder": "Varge",
|
||||
@@ -36,61 +36,47 @@
|
||||
"edit.copy_fields": "Mverm shiruzmafal",
|
||||
"edit.paste_fields": "Nasii shiru",
|
||||
"edit.tag_manager": "Jewalt festaretol",
|
||||
"entries.duplicate.merge": "Visk blisnets shiruzmakaban",
|
||||
"entries.duplicate.merge.label": "Visk blisnets shiruzmakaban ima...",
|
||||
"entries.duplicate.refresh": "Gotova blisnets shiruzmakaban gen",
|
||||
"entries.duplicates.description": "Blisnets shiruzmakaban implajena na plus ka ein shiruzmakaban ke tsunaga na sama mlafu na shiruzmabaksu. Na visk afto, zol festa festaretol au mlafushiruzma al mverm fu mlafu kara ine ein stuur shiruzmakaban. Hej nai sama \"mverm fu mlafu\", ke mverm fu mlafu fu du, ekso TagStudio.",
|
||||
"entries.generic.refresh_alt": "&Gengotova",
|
||||
"entries.duplicate.merge": "Visk sama shiruzmakaban",
|
||||
"entries.duplicate.merge.label": "Visk sama shiruzmakaban ima...",
|
||||
"entries.duplicate.refresh": "Gotova sama shiruzmakaban gen",
|
||||
"entries.duplicates.description": "Mverm fu shiruzmakaban implajena na plus ka ein shiruzmakaban ke tsunaga na sama mlafu na shiruzmabaksu. Na visk afto, zol festa festaretol au mlafushiruzma al mverm fu mlafu kara ine ein stuur shiruzmakaban. Hej nai sama \"mverm fu mlafu\", ke mverm fu mlafu fu du, ekso TagStudio.",
|
||||
"entries.generic.remove.removing": "Keste shiruzmakaban ima",
|
||||
"entries.generic.remove.removing_count": "Keste {count} shiruzmakaban...",
|
||||
"entries.ignored.description": "Mlafu shiruzmakaban bli \"hestujena\" li antadan na mlafuhuomi de hesturuuru fu brukdjin (na '.ts_ignore' mlafu) kawaridan per nai eku sore. Hestujena mlafu long mlafuhuomi na snano sit kemuske mlafu na lasazma kesa kawari hesturuuru.",
|
||||
"entries.ignored.ignored_count": "Hestujena Shiruzmakaban: {count}",
|
||||
"entries.ignored.remove": "Keste hestujena shiruzmakaban",
|
||||
"entries.ignored.remove_alt": "Keste hestujena shiruzmakaban (&v)",
|
||||
"entries.ignored.scanning": "Taskame mlafuhuomi grun hestujena shiruzmakaban...",
|
||||
"entries.ignored.title": "Fiks hestujena shiruzmakaban",
|
||||
"entries.mirror": "&Maha melon fu",
|
||||
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban we?",
|
||||
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban?",
|
||||
"entries.mirror.label": "Maha melon fu {idx}/{total} shiruzmakaban ima...",
|
||||
"entries.mirror.title": "Maha melon fu shiruzmakaban ima",
|
||||
"entries.mirror.window_title": "Maha melon fu shiruzmakaban",
|
||||
"entries.remove.plural.confirm": "Du kestetsa afto <b>{count}</b> shiruzmakaban we? Nil mlafu na shiruzmabaksu bli kestejena.",
|
||||
"entries.remove.singular.confirm": "Du kestetsa afto shiruzmakaban long mlafuhuomi we? Nil mlafu na shiruzmabaksu bli kestejena.",
|
||||
"entries.remove.plural.confirm": "Du kestetsa afto {count} shiruzmakaban?",
|
||||
"entries.running.dialog.new_entries": "Nasii {total} neo shiruzmakaban fu mlafu ima...",
|
||||
"entries.running.dialog.title": "Nasii neo shiruzmakaban fu mlafu ima",
|
||||
"entries.tags": "Festaretol",
|
||||
"entries.unlinked.description": "Tont shiruzmakaban fu mlafuhuomi tsunagajena na mlafu ine joku mlafukaban fu du. Li mlafu tsunagajena na shiruzmakaban ugokijena os kestejena ekso TagStudio, sit sore kntsunagajena.<br><br>Tsunaganaijena shiruzmakaban deki tsunaga gen na suha per mlafukaban fu du os keste li du vil.",
|
||||
"entries.unlinked.description": "Tont shiruzmakaban fu mlafuhuomi tsunagajena na mlafu ine joku mlafukaban fu du. Li mlafu tsunagajena na shiruzmakaban ugokijena os kestejena ekso TagStudio, sit sore tsunaganaijena.<br><br>Tsunaganaijena shiruzmakaban deki tsunaga gen na suha per mlafukaban fu du os keste li du vil.",
|
||||
"entries.unlinked.relink.attempting": "Iskat ima na tsunaga gen {index}/{unlinked_count} shiruzmakaban, {fixed_count} tsunagajena gen",
|
||||
"entries.unlinked.relink.manual": "&Tsunaga gen mit hant",
|
||||
"entries.unlinked.relink.title": "Tsunaga shiruzmakaban gen",
|
||||
"entries.unlinked.remove": "Keste kntsunagajena shiruzmakaban",
|
||||
"entries.unlinked.remove_alt": "Keste kntsunagajena shiruzmakaban (&v)",
|
||||
"entries.unlinked.scanning": "Taskame mlafuhuomi ima grun kntsunagajena shiruzmakaban...",
|
||||
"entries.unlinked.scanning": "Suha mlafuhuomi ima per tsunaganaijena shiruzmakaban...",
|
||||
"entries.unlinked.search_and_relink": "&Suha &&Tsunaga gen",
|
||||
"entries.unlinked.title": "Fiks kntsunagajena shiruzmakaban",
|
||||
"entries.unlinked.unlinked_count": "Kntsunagajena shiruzmakaban: {count}",
|
||||
"entries.unlinked.title": "Fiks tsunaganaijena shiruzmakaban",
|
||||
"entries.unlinked.unlinked_count": "Tsunaganaijena shiruzmakaban: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg au/os FFprobe nai finnajena. TagStudio treng FFmpeg per mahase riso.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Mverm shiruzmafal",
|
||||
"field.edit": "Kawari shiruzmafal",
|
||||
"field.paste": "Nasii shiruzmafal",
|
||||
"field_type.datetime": "Dag-tid",
|
||||
"field_type.text": "Tekst",
|
||||
"field_type.unknown": "Fal nai shirujena",
|
||||
"file.date_added": "Dag antajena",
|
||||
"file.date_created": "Dag mahajena",
|
||||
"file.date_modified": "Dag kawarijena",
|
||||
"file.date_added": "Dag nasiijenadan",
|
||||
"file.date_created": "Dag mahajenadan",
|
||||
"file.date_modified": "Dag kawarijenadan",
|
||||
"file.dimensions": "Stuuratailasku",
|
||||
"file.duplicates.description": "TagStudio kjoka nasii shiruzma DupeGuru kara per jewalt blisnets mlafu.",
|
||||
"file.duplicates.dupeguru.advice": "Za melon mahajena, du deki bruk DupeGuru per keste nai vilena mlafu. Sit, bruk tel fu TagStudio, haisa \"Fiks kntsunagajena shiruzmakaban\" na tropos sentakutumam per keste kntsunagajena shiruzmakaban.",
|
||||
"file.duplicates.description": "TagStudio kjoka nasii shiruzma DupeGuru kara per jewalt mverm.",
|
||||
"file.duplicates.dupeguru.advice": "Za melon mahajena, du deki bruk DupeGuru per keste nai vilena mlafu. Sit, bruk tel fu TagStudio, haisa \"Fiks tsunaganaijena shiruzmakaban\" na tropos sentakutumam per keste tsunaganaijena shiruzmakaban.",
|
||||
"file.duplicates.dupeguru.file_extension": "DupeGuru mlafu (*.dupeguru)",
|
||||
"file.duplicates.dupeguru.load_file": "&Gotova mlafu fu DupeGuru",
|
||||
"file.duplicates.dupeguru.no_file": "Jamnai sentakujena mlafu fu DupeGuru",
|
||||
"file.duplicates.dupeguru.open_file": "Auki sitan mlafu fu Dupeguru",
|
||||
"file.duplicates.fix": "Fiks blisnets mlafu",
|
||||
"file.duplicates.matches": "Blisnets-atai: {count}",
|
||||
"file.duplicates.matches_uninitialized": "blisntes-atai: N/A",
|
||||
"file.duplicates.mirror.description": "Maha melon fu shiruzma shiruzmakaban kara na al blisnets fu samakaban, afto zol visk al shiruzma, men zolnai keste os mverm shiruzmafal. Afto zolnai keste joku mlafu os shiruzma.",
|
||||
"file.duplicates.dupeguru.open_file": "Auki shiruzma mlafu fu Dupeguru",
|
||||
"file.duplicates.fix": "Fiks mverm",
|
||||
"file.duplicates.matches": "Mverm-atai: {count}",
|
||||
"file.duplicates.matches_uninitialized": "Mverm-atai: N/A",
|
||||
"file.duplicates.mirror.description": "Maha melon fu shiruzma shiruzmakaban kara na al mverm fu samakaban, afto zol visk al shiruzma, men zolnai keste os mverm shiruzmafal. Afto zolnai keste joku mlafu os shiruzma.",
|
||||
"file.duplicates.mirror_entries": "&Maha melon fu shiruzmakaban",
|
||||
"file.duration": "Pitkatai",
|
||||
"file.not_found": "Mlafu nai finnajena",
|
||||
@@ -124,43 +110,33 @@
|
||||
"generic.missing": "Harnai",
|
||||
"generic.navigation.back": "Suruk",
|
||||
"generic.navigation.next": "Mirai",
|
||||
"generic.no": "Nai",
|
||||
"generic.none": "Nil",
|
||||
"generic.overwrite": "Vasu",
|
||||
"generic.overwrite_alt": "&Vasu",
|
||||
"generic.paste": "Nasii",
|
||||
"generic.recent_libraries": "Moloda mlafuhuomi",
|
||||
"generic.remove": "Keste",
|
||||
"generic.remove_alt": "&Keste",
|
||||
"generic.rename": "Haisagen",
|
||||
"generic.rename_alt": "&Haisagen",
|
||||
"generic.reset": "Gensintua",
|
||||
"generic.save": "Ufne",
|
||||
"generic.skip": "Pal",
|
||||
"generic.skip_alt": "&Pal",
|
||||
"generic.yes": "Akk",
|
||||
"home.search": "Suha",
|
||||
"home.search_entries": "Suha shiruzmakaban",
|
||||
"home.search_library": "Suha mlafuhuomi",
|
||||
"home.search_tags": "Suha festaretol",
|
||||
"home.show_hidden_entries": "Mahase furijena shiruzmakaban",
|
||||
"home.thumbnail_size": "Stuuratai fu risonen",
|
||||
"home.thumbnail_size.extra_large": "Plusstuur risonen",
|
||||
"home.thumbnail_size.large": "Stuur risonen",
|
||||
"home.thumbnail_size.medium": "Mellan risonen",
|
||||
"home.thumbnail_size.mini": "Chisai risonen",
|
||||
"home.thumbnail_size.small": "Chisaidai risonen",
|
||||
"ignore.open_file": "Mahase \"{ts_ignore}\" mlafu na shiruzmabaksu",
|
||||
"json_migration.checking_for_parity": "Taskama per chigauzma ima...",
|
||||
"json_migration.creating_database_tables": "Maha tumam na SQL Database ima...",
|
||||
"json_migration.description": "<br>Hadji au de se shiruzma fu",
|
||||
"json_migration.discrepancies_found": "Chiguazma finnajena na mlafuhuomi",
|
||||
"json_migration.discrepancies_found.description": "Chiguazma finnajena mellan ranja au kjannos. Bitte gense au sentaku per beng na dlabdel os yamete.",
|
||||
"json_migration.finish_migration": "Owari dlabdel",
|
||||
"json_migration.heading.aliases": "Andrnamae:",
|
||||
"json_migration.heading.colors": "Varge:",
|
||||
"json_migration.heading.differ": "Tchigauzma",
|
||||
"json_migration.heading.extensions": "Andr ilo:",
|
||||
"json_migration.heading.match": "Finnajena sama",
|
||||
"json_migration.heading.names": "Namae:",
|
||||
"json_migration.heading.parent_tags": "Atama festaretol:",
|
||||
@@ -180,22 +156,13 @@
|
||||
"library.field.remove": "Keste shiruzmafal",
|
||||
"library.missing": "Mlafuplas fu mlafuhuomi nai finnajenadan",
|
||||
"library.name": "Mlafuhuomi",
|
||||
"library.refresh.scanning.plural": "Taskame mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning.singular": "Taskame mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning_preparing": "Taskame mlafukaban fu neo mlafu ima...\nGotova ima...",
|
||||
"library.refresh.scanning.plural": "Taskama mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning.singular": "Taskama mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning_preparing": "Taskama mlafukaban fu neo mlafu ima...\nGotova ima...",
|
||||
"library.refresh.title": "Gengotova al mlafukaban",
|
||||
"library.scan_library.title": "Taskame mlafuhuomi ima",
|
||||
"library_info.cleanup": "Parjat",
|
||||
"library_info.cleanup.backups": "Mverm long mlafuhuomi:",
|
||||
"library_info.cleanup.dupe_files": "Blisnets mlafu:",
|
||||
"library_info.cleanup.ignored": "Hestujena shiruzmakaban:",
|
||||
"library_info.cleanup.legacy_json": "Gammel verso fu mlafuhuomi:",
|
||||
"library_info.cleanup.unlinked": "Kntsunagajena shiruzmakaban:",
|
||||
"library_info.stats.colors": "Varge fu festaretol:",
|
||||
"library.scan_library.title": "Taskama mlafuhuomi ima",
|
||||
"library_info.stats.entries": "Shiruzmakaban:",
|
||||
"library_info.stats.fields": "Shiruzmafal:",
|
||||
"library_info.stats.macros": "Aplinen:",
|
||||
"library_info.stats.namespaces": "Tumam:",
|
||||
"library_info.stats.tags": "Festaretol:",
|
||||
"library_object.name": "Namae",
|
||||
"library_object.name_required": "Namae (Trengjena)",
|
||||
@@ -223,16 +190,15 @@
|
||||
"menu.file.save_library": "Ufne mlafuhuomi",
|
||||
"menu.help": "&Aputsa",
|
||||
"menu.help.about": "Shiruplus",
|
||||
"menu.macros": "&Aplinen",
|
||||
"menu.macros": "&Zetingnen",
|
||||
"menu.macros.folders_to_tags": "Mlafukaban festaretol made",
|
||||
"menu.select": "Sentaku",
|
||||
"menu.settings": "Sentakuzma...",
|
||||
"menu.tools": "&Tropos",
|
||||
"menu.tools.fix_duplicate_files": "Fiks &blisnets mlafu",
|
||||
"menu.tools.fix_duplicate_files": "Fiks sama &mlafu",
|
||||
"menu.tools.fix_unlinked_entries": "Fiks &tsunagajenanai shiruzmakaban",
|
||||
"menu.view": "&Anse",
|
||||
"menu.window": "Ekrannen",
|
||||
"namespace.create.description_color": "Varge fu festaretol bruk tumam na vargekaban. Al neo varge mus long tumamklaani na eins.",
|
||||
"namespace.create.title": "Maha vargetumam",
|
||||
"namespace.new.button": "Neo vargetumam",
|
||||
"namespace.new.prompt": "Maha neo vargetumam per nasii varge fu sebja!",
|
||||
@@ -256,17 +222,17 @@
|
||||
"settings.show_filenames_in_grid": "Mahase namae fu mlafu ine karta",
|
||||
"settings.show_recent_libraries": "Mahase moloda mlafuhuomi",
|
||||
"settings.theme.dark": "Kirai",
|
||||
"settings.theme.label": "Ekran klea:",
|
||||
"settings.theme.label": "Klea fu zeting:",
|
||||
"settings.theme.light": "Kirkas",
|
||||
"settings.theme.system": "Kompyu",
|
||||
"settings.title": "Sentakuzma",
|
||||
"sorting.direction.ascending": "Plus made",
|
||||
"sorting.direction.descending": "Minus made",
|
||||
"sorting.direction.ascending": "Leste owaris",
|
||||
"sorting.direction.descending": "Leste eins",
|
||||
"splash.opening_library": "Auki mlafuhuomi \"{library_path}\" ima...",
|
||||
"status.deleted_file_plural": "Kestejenadan {count} mlafu!",
|
||||
"status.deleted_file_singular": "Kestejenadan 1 mlafu!",
|
||||
"status.deleted_none": "Nil mlafu kestejenadan.",
|
||||
"status.deleted_partial_warning": "Mono kestejenadan {count} mlafu! Bitte taskamatsa li du dekinai finna mlafu os ke ima brukjena na andr apli.",
|
||||
"status.deleted_partial_warning": "Mono kestejenadan {count} mlafu! Bitte taskamatsa li du dekinai finna mlafu os ke ima brukjena na andr zeting.",
|
||||
"status.deleting_file": "Keste mlafu [{i}/{count}]: \"{path}\" ima...",
|
||||
"status.library_closing": "Kini mlafuhuomi ima...",
|
||||
"status.library_save_success": "Mlafuhuomi ufnejenadan au kinijenadan!",
|
||||
@@ -284,7 +250,7 @@
|
||||
"tag.all_tags": "Al festaretol",
|
||||
"tag.choose_color": "Sentaku varge fu festaretol",
|
||||
"tag.color": "Varge",
|
||||
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\" we?",
|
||||
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\"?",
|
||||
"tag.create": "Maha festaretol",
|
||||
"tag.create_add": "Maha && Nasii \"{query}\"",
|
||||
"tag.disambiguation.tooltip": "Bruk afto festaretol grun plusklar",
|
||||
@@ -306,8 +272,8 @@
|
||||
"trash.context.singular": "Ugoki mlafu {trash_term} made",
|
||||
"trash.dialog.disambiguation_warning.plural": "Afto zol keste tuo TagStudio kara <i>AU</i> kompyu fu du kara!",
|
||||
"trash.dialog.disambiguation_warning.singular": "Afto zol keste sore TagStudio kara <i>AU</i> kompyu fu du kara!",
|
||||
"trash.dialog.move.confirmation.plural": "Du ugokitsa afto {count} mlafu {trash_term} made we?",
|
||||
"trash.dialog.move.confirmation.singular": "Du ugokitsa afto mlafu {trash_term} made we?",
|
||||
"trash.dialog.move.confirmation.plural": "Du ugokitsa afto {count} mlafu {trash_term} made?",
|
||||
"trash.dialog.move.confirmation.singular": "Du ugokitsa afto mlafu {trash_term} made?",
|
||||
"trash.dialog.permanent_delete_warning": "<b>VIKTI!</b> Li afto mlafu nai dekijena ugoki {trash_term} made, sore zol <b>kestejena!!</b>",
|
||||
"trash.dialog.title.plural": "Keste mlafu",
|
||||
"trash.dialog.title.singular": "Keste mlafu",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -29,7 +29,7 @@ def test_tag_widget_actions_replaced_correctly(qtbot: QtBot, qt_driver: QtDriver
|
||||
# Set the widget
|
||||
tags = library.tags
|
||||
panel.set_tag_widget(tags[0], 0)
|
||||
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget() # pyright: ignore[reportAssignmentType]
|
||||
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget()
|
||||
|
||||
should_replace_actions = {
|
||||
tag_widget: ["on_edit()", "on_remove()"],
|
||||
|
||||
@@ -30,7 +30,6 @@ EMPTY_LIBRARIES = "empty_libraries"
|
||||
# str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_102")),
|
||||
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_103")),
|
||||
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_200")),
|
||||
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_201")),
|
||||
],
|
||||
)
|
||||
def test_library_migrations(path: str):
|
||||
|
||||
Reference in New Issue
Block a user