Compare commits

...

13 Commits

Author SHA1 Message Date
Hosted Weblate
cbb0eedb85 Translated using Weblate (Viossa)
Currently translated at 88.2% (322 of 365 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/qpv/
Translation: TagStudio/Strings
2026-05-20 20:11:48 +00:00
Hosted Weblate
202cffcd79 Translated using Weblate (Toki Pona)
Currently translated at 95.8% (350 of 365 strings)

Co-authored-by: Cyborus <cyborus@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings
2026-05-20 20:11:47 +00:00
Hosted Weblate
0c4143576e Translated using Weblate (Spanish)
Currently translated at 100.0% (365 of 365 strings)

Co-authored-by: Julen Arratibel Etxabe <jarratibeletxabe@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings
2026-05-20 20:11:47 +00:00
Hosted Weblate
791c2088d8 Translated using Weblate (Japanese)
Currently translated at 98.6% (360 of 365 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: wany-oh <wany-oh@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ja/
Translation: TagStudio/Strings
2026-05-20 20:11:46 +00:00
Travis Abendshien
0dd0616281 docs: update roadmap and library changes 2026-05-20 02:38:33 -07:00
Travis Abendshien
0380f1b0ca fix(docs): move back prettier-ignore sections that were moved by prettier 2026-05-19 03:14:01 -07:00
Travis Abendshien
e1a02ca843 docs: add github magiclinks, update navigation titles and styling 2026-05-19 03:04:11 -07:00
Jann Stute
c7eb22b253 doc(install): add common error message help text (#1369) 2026-05-18 22:26:36 -07:00
Travis Abendshien
49fdfc8248 docs: update contributing, developing, and style pages 2026-05-18 19:06:36 -07:00
Travis Abendshien
5e1c1f2014 fix(ci): lower coverage check threshold 2026-05-18 12:35:36 -07:00
Travis Abendshien
38da7bb3a9 fix: drop type_key column from text_fields and datetime_fields tables (#1370)
* fix: drop type_key column from text_fields and datetime_fields tables

* fix: commit empty DB_VERSION 200 library

* fix: use recommended renaming order for field tables
2026-05-14 13:38:52 -07:00
dependabot[bot]
ab3394ad40 chore(deps-dev): bump pytest from 8.3.5 to 9.0.3 (#1372)
* chore(deps-dev): bump pytest from 8.3.5 to 9.0.3

Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.5 to 9.0.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.5...9.0.3)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.0.3
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: bump syrupy to 5.1.0

Version prior to 5.0.0 are incompatible with pytest 9.x.x

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
2026-05-14 12:25:40 -07:00
Travis Abendshien
41f9749814 refactor(ci): remove mypy (#1371)
* refactor(ci): remove mypy

* fix(ci): restore 'type: ignore' comment for st_birthtime property
2026-05-14 14:24:26 +02:00
50 changed files with 1005 additions and 738 deletions

View File

@@ -1,36 +0,0 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
---
name: MyPy
on: [push, pull_request]
jobs:
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:
python-version: "3.12"
cache: pip
- name: Install Python dependencies
run: |
python -m pip install --upgrade uv
uv pip install --system .[mypy]
- name: Execute MyPy
uses: tsuyoshicho/action-mypy@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
fail_on_error: true

View File

@@ -89,9 +89,9 @@ jobs:
- name: Check coverage
uses: yedpodtrzitko/coverage@main
with:
thresholdAll: 0.4
thresholdNew: 0.4
thresholdModified: 0.4
thresholdAll: 0.1
thresholdNew: 0.1
thresholdModified: 0.1
coverageFile: coverage.xml
token: ${{ secrets.GITHUB_TOKEN }}
sourceDir: tagstudio/src

View File

@@ -2,9 +2,9 @@
repos:
- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy
- id: pyright
name: pyright
entry: pyright
language: system
types_or: [python, pyi]
require_serial: true

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
---
title: Colors
icon: material/palette
---

View File

@@ -1,164 +1,150 @@
---
title: Contributing
icon: material/file-plus
---
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
<!-- SPDX-License-Identifier: GPL-3.0-only -->
# :material-file-plus: Contributing
# :material-file-plus: Contribution Guidelines
Thank you so much for showing interest in contributing to TagStudio! Here are a set of instructions and guidelines for contributing code or documentation to the project. This document will change over time, so make sure that your contributions still line up with the requirements here before submitting a pull request.
Thank you so much for showing interest in contributing to TagStudio! This page goes over the instructions and guidelines for contributing to the TagStudio. This page will change over time, so make sure that your contributions still line up with the current guidelines before submitting a pull request.
## Getting Started
If you wish to discuss TagStudio further, feel free to join the [Discord Server](https://discord.com/invite/hRNnVKhF2G)!
- Check the [Feature Roadmap](roadmap.md) page to see what priority features there are, the [FAQ](https://github.com/TagStudioDev/TagStudio/blob/main/README.md#faq), as well as the project's [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls).
- If you'd like to add a feature that isn't on the feature roadmap or doesn't have an open issue, **PLEASE create a feature request** issue for it discussing your intentions so any feedback or important information can be given by the team first.
- We don't want you wasting time developing a feature or making a change that can't/won't be added for any reason ranging from pre-existing refactors to design philosophy differences.
- **Please don't** create pull requests that consist of large refactors, _especially_ without discussing them with us first. These end up doing more harm than good for the project by continuously delaying progress and disrupting everyone else's work.
- If you wish to discuss TagStudio further, feel free to join the [Discord Server](https://discord.com/invite/hRNnVKhF2G)!
## :material-order-bool-ascending-variant: Contribution Checklist
#### All Contributions
- [x] I've read the Contribution Guidelines (this page) and the [Style Guide](style.md)
- [x] I've read the [FAQ](https://github.com/TagStudioDev/TagStudio/blob/main/README.md#faq)
- [x] I've checked the project's [pull requests](https://github.com/TagStudioDev/TagStudio/pulls) for any existing or conflicting PRs
- [x] I've set up my [development environment](developing.md) including [Ruff](developing.md#ruff), [Pyright](developing.md#pyright), and [Pytest](developing.md#pytest)
#### Feature Additions
- [x] I've read the [Feature Roadmap](roadmap.md) and understand what core features are planned, their priorities, and their scheduled timelines
- [x] I've found an existing [feature request](https://github.com/TagStudioDev/TagStudio/issues) or created my own **_before starting work on a feature_** so that the feature can be discussed beforehand
<!-- prettier-ignore -->
!!! note
If the fix is small and self-explanatory (i.e. a typo), then it doesn't require an issue to be opened first. Issue tracking is supposed to make our lives easier, not harder. Please use your best judgement to minimize the amount of work for everyone involved.
!!! danger "Before Developing Features"
**PLEASE** open a [feature request](https://github.com/TagStudioDev/TagStudio/issues) or ensure that one already exists **_before you begin work on the feature_**. This allows us to discuss the feature idea beforehand, approve or reject it, and give any specific implementation requirements. We **do not want** to have to close pull requests because they add features that conflict with the project's goals, guidelines, or other planned features.
### Contribution Checklist
#### Fixes
- I've read the [Feature Roadmap](roadmap.md) page
- 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, 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!_**
- [x] I've found an existing [bug report](https://github.com/TagStudioDev/TagStudio/issues) or created my own for this fix _(as long as the fix is substantial enough to warrant opening a bug report for)_
<!-- prettier-ignore -->
!!! failure "Unacceptable Code"
The following types of code will NOT be accepted to the project:
!!! note "Issue Exceptions for Small Fixes"
If the fix is small and self-explanatory (e.g. a typo), then it doesn't require an issue to be opened first. Issue tracking is supposed to make our lives easier, not harder. Please use your best judgement to minimize the amount of work for everyone involved.
- Code that is not yours or does not have a compatible license with TagStudio's [own one](../LICENSE)
- Code that you do not understand and/or cannot explain
## :material-thumb-down:{.red} Unacceptable Code
## Creating a Development Environment
The following types of code will **NOT** be accepted to the project:
If you wish to develop for TagStudio, you'll need to create a development environment by installing the required dependencies. You have a number of options depending on your level of experience and familiarly with existing Python toolchains.
- Code that does not follow the [Contribution Checklist](#contribution-checklist) or violates the Contribution Guidelines
- Large refactors that have not been discussed with us first
- These types of refactors end up doing more harm than good for the project by continuously delaying progress and disrupting everyone else's work
- Other people/projects' code that is used without consent or does not have a [compatible license](#licenses)
- Code that you do not understand and/or cannot explain (i.e. "vibe coding")
- Code that adds a drastic amount of complexity with minimal utility
If you know what you're doing and have developed for Python projects in the past, you can get started quickly with the "Brief Instructions" below. Otherwise, please see the full instructions on the documentation website for "[Creating a Development Environment](https://docs.tagstud.io/install/#creating-a-development-environment)".
---
### Brief Instructions
## :material-license: Licenses
1. Have [Python 3.12](https://www.python.org/downloads/) and PIP installed. Also have [FFmpeg](https://ffmpeg.org/download.html) installed if you wish to have audio/video playback and thumbnails.
2. Clone the repository to the folder of your choosing:
```
git clone https://github.com/TagStudioDev/TagStudio.git
```
3. Use a dependency manager such as [uv](https://docs.astral.sh/uv/) or [Poetry 2.0](https://python-poetry.org/blog/category/releases/) to install the required dependencies, or alternatively create and activate a [virtual environment](https://docs.tagstud.io/install/#manual-installation) with `venv`.
4. If using a virtual environment instead of a dependency manager, install an editable version of the program and development dependencies with the following PIP command:
```
pip install -e ".[dev]"
```
Otherwise, modify the command above for use with your dependency manager of choice. For example if using uv, you may use this:
```
uv pip install -e ".[dev]"
```
## Workflow Checks
When pushing your code, several automated workflows will check it against predefined tests and style checks. It's _highly recommended_ that you run these checks locally beforehand to avoid having to fight back-and-forth with the workflow checks inside your pull requests.
As of May 2026, the TagStudio project has begun migrating to different licenses for different sections of the codebase where possible. Any new code contributed inside the "core" of TagStudio must be under the [MIT license](https://github.com/TagStudioDev/TagStudio/blob/main/LICENSES/MIT.txt), while any code specific to the Qt frontend is to remain under [GPL-3.0](https://github.com/TagStudioDev/TagStudio/blob/main/LICENSES/GPL-3.0-only.txt) where possible.
<!-- prettier-ignore -->
!!! tip
To format the code automatically before each commit, there's a configured action available for the `pre-commit` hook. Install it by running `pre-commit install`. The hook will be executed each time on running `git commit`.
!!! question "Relicensing Process"
Existing GPL-3.0 core code is **only** migrated to MIT if all of the original contributors have given their consent, or if the code becomes replaced by a significantly different implementation.
### [Ruff](https://github.com/astral-sh/ruff)
Licensing is now accomplished using the [REUSE](https://reuse.software/spec-3.3/) specification. This means that each file is licensed separately, with text files having a comment header with the license and copyright and other files having this information declared in the [RESUSE.toml](https://github.com/TagStudioDev/TagStudio/blob/main/REUSE.toml) file.
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.
<!-- prettier-ignore-start -->
=== "Python GPL-3.0 Comment"
```py
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
```
=== "Python MIT Comment"
```py
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: MIT
```
=== "Markdown/HTML Comment"
```html
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
<!-- SPDX-License-Identifier: GPL-3.0-only -->
```
=== "CSS Comment"
```css
/*
* SPDX-FileCopyrightText: (c) TagStudio Contributors
* SPDX-License-Identifier: GPL-3.0-only
*/
```
<!-- prettier-ignore-end -->
#### Running Locally
#### Types of Files That Should Be MIT
Inside the root repository directory:
- Backend code that supports TagStudio's core systems, irrespective of the frontend used
- e.g. The database backend for storing TagStudio data, the search query system, database tests, etc.
- Frontend code for the CLI _(to be developed)_
- Platform-agnostic thumbnail extraction, rendering, and caching
- Translations added to an MIT-labeled [Weblate](https://hosted.weblate.org/projects/tagstudio/) component
- Lint code with `ruff check`
- Some linting suggestions can be automatically formatted with `ruff check --fix`
- Format code with `ruff format`
#### Types of Files That Should Be GPL-3.0
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).
- Code for the Qt frontend
- e.g. Qt widgets, views, controllers, Qt rendering code, Qt tests, etc.
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/).
---
### [Mypy](https://github.com/python/mypy)
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
- **(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!)_
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
- Run all tests by running `pytest tests/` in the repository root.
## Code Style
See the [Style Guide](style.md)
### Modules & Implementations
- **Do not** modify legacy library code in the `src/core/library/json/` directory
- Avoid direct calls to `os`
- Use `Pathlib` library instead of `os.path`
- Use `platform.system()` instead of `os.name` and `sys.platform`
- Don't prepend local imports with `tagstudio`, stick to `src`
- Use the `logger` system instead of `print` statements
- Avoid nested f-strings
- Use HTML-like tags inside Qt widgets over stylesheets where possible
### Commit and Pull Request Style
## :material-code-braces-box: Commits and Pull Requests
- Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0) as a guideline for commit messages. This allows us to easily generate changelogs for releases.
- See some [examples](https://www.conventionalcommits.org/en/v1.0.0/#examples) of what this looks like in practice.
- Use clear and concise commit messages. If your commit does too much, either consider breaking it up into smaller commits or providing extra detail in the commit description.
- Pull requests should have an adequate title and description which clearly outline your intentions and changes/additions. Feel free to provide screenshots, GIFs, or videos, especially for UI changes.
- Pull requests should ideally be limited to **a single** feature or fix.
- Pull requests should ideally be limited to a **single** feature or fix.
<!-- prettier-ignore -->
!!! important
!!! danger "Force Pushing"
**Please do not force push if your PR is open for review!** Force pushing makes it impossible to discern which changes have already been reviewed and which haven't. This means a reviewer will then have to re-review all the already reviewed code, which is a lot of unnecessary work for reviewers.
<!-- prettier-ignore -->
!!! tip
!!! tip "PR Scope"
If you're unsure where to stop the scope of your PR, ask yourself: _"If I broke this up, could any parts of it still be used by the project in the meantime?"_
### Workflow Checks
When pushing your code, several automated [workflows](https://github.com/TagStudioDev/TagStudio/tree/main/.github/workflows) will check it against predefined tests and style checks. It's _highly recommended_ that you run these checks locally beforehand to avoid having to fight back-and-forth with the workflow checks inside your pull requests. These checks currently include:
- [Ruff](developing.md#ruff) [`check`](https://docs.astral.sh/ruff/linter) and [`format`](https://docs.astral.sh/ruff/formatter/) (read-only)
- [Pyright](developing.md#pyright) type checking
- [Pytest](developing.md#pytest) tests
- REUSE [license compliance](#licenses)
### Runtime Requirements
- Final code must function on supported versions of Windows, macOS, and Linux:
- Windows: 10, 11
- macOS: 13.0+
- Linux: _Varies_
- Final code must **_NOT:_**
- Contain superfluous or unnecessary logging statements
- Cause unreasonable slowdowns to the program outside of a progress-indicated task
- Cause undesirable visual glitches or artifacts on screen
Code must function on all of the supported operating systems and versions:
## Documentation Guidelines
- Windows 10 & 11
- macOS 14.0+
- Common Linux distributions and versions
Documentation contributions include anything inside of the `docs/` folder, as well as the `README.md` and `CONTRIBUTING.md` files. Documentation inside the `docs/` folder is built and hosted on our static documentation site, [docs.tagstud.io](https://docs.tagstud.io/).
## :material-file-document: Documentation Guidelines
Documentation contributions include anything inside of the `docs/` folder as well as the `README.md`. Documentation inside the `docs/` folder is built and hosted on our static documentation site, [docs.tagstud.io](https://docs.tagstud.io/).
- Use "[dash-case / kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case)" for file and folder names
- Follow the folder structure pattern
- Don't add images or other media with excessively large file sizes
- Provide alt text for all embedded media
- Provide alt text for embedded media
- Use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" for title capitalization
## Translation Guidelines
## :material-translate: Translation Guidelines
Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).

View File

@@ -1,4 +1,5 @@
---
title: Developing
icon: material/code-braces
---
@@ -11,7 +12,7 @@ If you wish to develop for TagStudio, you'll need to create a development enviro
<!-- prettier-ignore -->
!!! tip "Contributing"
If you wish to contribute to TagStudio's development, please read our [CONTRIBUTING.md](https://github.com/TagStudioDev/TagStudio/blob/main/CONTRIBUTING.md)!
If you wish to contribute to TagStudio's development, please read our [Contribution Guidelines](contributing.md)!
## Installing Python
@@ -49,7 +50,7 @@ git clone https://github.com/TagStudioDev/TagStudio.git
## Installing Dependencies
To install the required dependencies, you can use a dependency manager such as [uv](https://docs.astral.sh/uv) or [Poetry 2.0](https://python-poetry.org). Alternatively you can create a virtual environment and manually install the dependencies yourself.
To install the required dependencies, you can use a dependency manager such as [uv](https://docs.astral.sh/uv) (recommended) or [Poetry 2.0](https://python-poetry.org). Alternatively you can create a virtual environment and manually install the dependencies yourself.
### Installing with uv
@@ -59,7 +60,7 @@ If using [uv](https://docs.astral.sh/uv), you can install the dependencies for T
uv pip install -e ".[dev]"
```
A reference `.envrc` is provided for use with [direnv](#direnv), see [`contrib/.envrc-uv`](https://github.com/TagStudioDev/TagStudio/blob/main/contrib/.envrc-uv).
TagStudio should now be runnable using the `tagstudio` command.
---
@@ -71,6 +72,8 @@ If using [Poetry](https://python-poetry.org), you can install the dependencies f
poetry install --with dev
```
TagStudio should now be runnable using the `tagstudio` command.
---
### Manual Installation
@@ -109,6 +112,12 @@ If you choose to manually set up a virtual environment and install dependencies
pip install -e ".[dev]"
```
4. TagStudio should now be runnable using the `tagstudio` command.
<!-- prettier-ignore -->
!!! Warning "Linux Library Dependencies"
If developing TagStudio on Linux, certain libraries are required that may not be included with your distribution. A full list of these can be found [here](install.md#linux).
## Nix(OS)
If using [Nix](https://nixos.org/), there is a development environment already provided in the [flake](https://wiki.nixos.org/wiki/Flakes) that is accessible with the following command:
@@ -144,9 +153,54 @@ The entry point for TagStudio is `src/tagstudio/main.py`. You can target this fi
}
```
<!-- prettier-ignore -->
!!! tip
To format the code automatically before each commit, there's a configured action available for the `pre-commit` hook. Install it by running `pre-commit install`. The hook will be executed each time on running `git commit`.
### Ruff
[Ruff](https://github.com/astral-sh/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/).
```sh title="Lint Code"
ruff check
# Apply automatic fixes with
ruff check --fix
```
```sh title="Format Code"
ruff format
```
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
[Pyright](https://github.com/microsoft/pyright) is a static type checker for Python that helps enforce type strictness and prevent easy-to-miss errors across our codebase.
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/).
```sh title="Run Checks"
pyright
```
Pyright/basedpyright should automatically discover the configuration options inside the [pyproject.toml](https://github.com/TagStudioDev/TagStudio/blob/main/pyproject.toml) file.
### Pytest
[Pytest](https://github.com/pytest-dev/pytest) runs our Python code against the tests inside the [`tests/`](https://github.com/TagStudioDev/TagStudio/tree/main/tests) directory.
Pytest is installed alongside the `pip install -e ".[dev]"` command.
```sh title="Run Tests"
pytest tests/
```
### pre-commit
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.
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.
Once you have pre-commit installed, just run:

View File

@@ -1,4 +1,5 @@
---
title: Entries
icon: material/file
---

View File

@@ -1,4 +1,5 @@
---
title: Fields
icon: material/text-box
---

View File

@@ -1,4 +1,5 @@
---
title: Installing FFmpeg
icon: material/movie-open-cog
---

View File

@@ -1,4 +1,5 @@
---
title: Installation
icon: material/download
---
@@ -226,19 +227,36 @@ For audio/video thumbnails and playback you'll need [FFmpeg](https://ffmpeg.org/
To generate thumbnails for RAR-based files (like `.cbr`) you'll need an extractor capable of handling them.
- :material-penguin: On Linux you'll need to install either `unrar` (likely in you distro's non-free repository) or `unrar-free` from your package manager.
- :fontawesome-brands-apple: On macOS `unrar` can be installed through Homebrew's [`rar`](https://formulae.brew.sh/cask/rar) formula.
<!-- prettier-ignore -->
!!! warning ":fontawesome-brands-apple: macOS "Privacy & Security" Popup"
On macOS, you may be met with a message similar to "**"unrar" Not Opened. Apple could not verify "unrar" is free of malware that may harm your Mac or compromise your privacy**" If you encounter this, then you'll need to go to the "Settings" app, navigate to "Privacy & Security", and scroll down to a section that says "**"unrar" was blocked from use because it is not from an identified developer.**" Click the "Open Anyway" button to allow unrar to be used.
<!-- prettier-ignore -->
!!! warning ":fontawesome-brands-apple: macOS "Privacy & Security" Popup"
On macOS, you may be met with a message similar to "**"unrar" Not Opened. Apple could not verify "unrar" is free of malware that may harm your Mac or compromise your privacy**" If you encounter this, then you'll need to go to the "Settings" app, navigate to "Privacy & Security", and scroll down to a section that says "**"unrar" was blocked from use because it is not from an identified developer.**" Click the "Open Anyway" button to allow unrar to be used.
- :fontawesome-brands-windows: On Windows you'll need to install either [`WinRAR`](https://www.rarlab.com/download.htm) or [`7-zip`](https://www.7-zip.org/) and add their folder to you `PATH`.
<!-- prettier-ignore -->
!!! tip "WinRAR License"
Both `unrar` and `WinRAR` require a license, but since the evaluation copy has no time limit you can simply dismiss the prompt.
<!-- prettier-ignore -->
!!! tip "WinRAR License"
Both `unrar` and `WinRAR` require a license, but since the evaluation copy has no time limit you can simply dismiss the prompt.
### ripgrep
A recommended tool to improve the performance of directory scanning is [`ripgrep`](https://github.com/BurntSushi/ripgrep), a Rust-based directory walker that natively integrates with our [`.ts_ignore`](ignore.md) (`.gitignore`-style) pattern matching system for excluding files and directories. Ripgrep is already pre-installed on some Linux distributions and also available from several package managers.
## Common Error Messages
### Could not load the Qt platform plugin "xcb"
If you get an error message like this one:
```
qt.qpa.plugin: From 6.5.0, xcb-cursor0 or libxcb-cursor0 is needed to load the Qt xcb platform plugin.
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "/tmp/_MEIayuTiW/cv2/qt/plugins" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.
Available platform plugins are: vnc, wayland-egl, offscreen, wayland, linuxfb, minimalegl, eglfs, minimal, vkkhrdisplay, xcb.
Aborted (core dumped)
```
Make sure you installed `libxcb-cursor` or `xcb-util-cursor`.

View File

@@ -1,4 +1,5 @@
---
title: Libraries
icon: material/database
---

View File

@@ -1,4 +1,5 @@
---
title: Library Format
icon: material/database-edit
---
@@ -11,7 +12,7 @@ This page outlines the various changes made to the TagStudio library save file f
---
## JSON
## JSON <small>v1.0.0 - v9.4.2</small>
Legacy (JSON) library save format versions were tied to the release version of the program itself. This number was stored in a `version` key inside the JSON file.
@@ -27,21 +28,30 @@ Replaced by the new SQLite format introduced in TagStudio [v9.5.0 Pre-Release 1]
---
## SQLite
## SQLite <small>v9.5.0+</small>
Starting with TagStudio [v9.5.0-pr1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1), the library save format has been moved to a [SQLite](https://sqlite.org) format. Legacy JSON libraries are migrated (with the user's consent) to the new format when opening in current versions of the program. The save format versioning is now separate from the program's versioning number.
### Versioning
Versions **1-100** stored the database version in a table called `preferences` in a row with the `key` column of `"DB_VERSION"` inside the corresponding `value` column.
Versions **>101** store the database version in a table called `versions` in a row with the `key` column of `'CURRENT'` inside the corresponding `value` column. The `versions` table also stores the initial database version in which the file was created with under the `'INITIAL'` key. Databases created before this key was introduced will always have `'INITIAL'` value of `100`.
```mermaid
erDiagram
versions {
TEXT key PK "Values: ['INITIAL', 'CURRENT']"
INTEGER value
}
```
#### "versions" Table
| key (`VARCHAR`) | value (`INTEGER`) |
| --------------- | --------------------------------------------- |
| `'INITIAL'` | <Version DB was created with, minimum `100`\> |
| `'CURRENT'` | <Current version of DB\> |
#### Major and Minor Versioning
Version **100** came along with a major/minor versioning system built into to the single version number. The version number divided by 100 denotes the major version, while remaining digits denote the minor version. TagStudio will allow reading from "future" databases so long as the major version does not increase past the last one it understands.
For example, a database with version 204 would still be readable in an older version of TagStudio that understands version 200. A database with version 300, on the other hand, would no longer be readable in that same older version and an error message would display.
---
### Versions 1 - 5
@@ -94,13 +104,13 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
---
### Versions 100 - 1xx
### Versions 100 - 104
#### Version 100
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [74383e3](https://github.com/TagStudioDev/TagStudio/commit/74383e3c3c12f72be1481ab0b86c7360b95c2d85) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
| Used From | Format | Location |
| ---------------------------------------- | ------ | ----------------------------------------------- |
| 74383e3c3c12f72be1481ab0b86c7360b95c2d85 | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Introduces built-in minor versioning
- The version number divided by 100 (and floored) constitutes the **major** version. Major version indicate breaking changes that prevent libraries from being opened in TagStudio versions older than the ones they were created in.
@@ -109,11 +119,11 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
#### Version 101
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [12e074b](https://github.com/TagStudioDev/TagStudio/commit/12e074b71d8860282b44e49e0e1a41b7a2e4bae8)/[v9.5.4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.4) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| 12e074b71d8860282b44e49e0e1a41b7a2e4bae8/[v9.5.4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.4) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Deprecates the `preferences` table, set to be removed in a future TagStudio version.
- Deprecates the `preferences` table, set to be removed in a [future](#version-104) TagStudio version.
- Introduces the `versions` table
- Has a string `key` column and an int `value` column
- The `key` column stores one of two values: `'INITIAL'` and `'CURRENT'`
@@ -123,34 +133,36 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
#### Version 102
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [71d0425](https://github.com/TagStudioDev/TagStudio/commit/71d04254cf87f4200bb7ffc81656e50dfb122e4d) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
| Used From | Format | Location |
| ---------------------------------------- | ------ | ----------------------------------------------- |
| 71d04254cf87f4200bb7ffc81656e50dfb122e4d | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Applies repairs to the `tag_parents` table created in [version 100](#version-100), removing rows that reference tags that have been deleted.
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [88d0b47](https://github.com/TagStudioDev/TagStudio/commit/88d0b47a86821ccfadba653f30a515abce5b24b0)/[v9.5.7](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.7) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| 88d0b47a86821ccfadba653f30a515abce5b24b0/[v9.5.7](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.7) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Adds the `is_hidden` column to the `tags` table (default `0`). Used for excluding entries tagged with hidden tags from library searches.
- Sets the `is_hidden` field on the built-in Archived tag to `1`, to match the Archived tag now being hidden by default.
#### Version 104
| Used From | Format | Location |
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [ad2cbbc](https://github.com/TagStudioDev/TagStudio/commit/ad2cbbca483018d245b44348e2c4f5a0e0bb28f1) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
| Used From | Format | Location |
| ---------------------------------------- | ------ | ----------------------------------------------- |
| ad2cbbca483018d245b44348e2c4f5a0e0bb28f1 | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Removes the `preferences` table, after migrating the contained extension list to the .ts_ignore file, if necessary.
---
### Versions 200 - 2xx
#### Version 200
| Used From | Format | Location |
| --------- | ------ | ----------------------------------------------- |
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
| Used From | Format | Location |
| ---------------------------------------- | ------ | ----------------------------------------------- |
| c15e2b56eedd0a3c13391fa43571b8f8f7c7a91f | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Adds `text_field_templates` and `date_field_templates` tables.
- Drops `boolean_fields` and `value_type` tables.
@@ -162,3 +174,12 @@ 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 |
| ---------------------------------------- | ------ | ----------------------------------------------- |
| 38da7bb3a920a01d4d70fa065fd19c83ff6eecb1 | 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.

View File

@@ -1,4 +1,5 @@
---
title: Tools & Macros
icon: material/script-text
---

View File

@@ -1,4 +1,5 @@
---
title: Supported Previews
icon: material/image-check
---
@@ -123,6 +124,8 @@ Archive thumbnails will display the first image from the archive within the Prev
!!! failure "3D Model Support"
TagStudio does not currently support previews for 3D model files *(outside of Blender project embedded thumbnails)*. This is on our [roadmap](roadmap.md#uiux) for a future release.
See the [GitHub discussion](https://github.com/TagStudioDev/TagStudio/discussions/1231) relating to status of this feature.
### :material-format-font: Fonts
Font thumbnails will use a "Aa" example preview of the font, with a full alphanumeric of the font available in the Preview Panel.
@@ -154,7 +157,7 @@ Text files render the first 256 bytes of text information to an image preview fo
<!-- prettier-ignore-start -->
[^1]:
The `.jpg_large` extension is unofficial and instead the byproduct of how [Google Chrome used to download images from Twitter](https://fileinfo.com/extension/jpg_large). Since this mangled extension is still in circulation, TagStudio supports it.
The `.jpg_large` extension is unofficial and is instead the byproduct of how [Google Chrome used to download images from Twitter](https://fileinfo.com/extension/jpg_large). Since this mangled extension is still in circulation, TagStudio supports it.
[^2]:
Apple Lossless traditionally uses `.m4a` and `.caf` containers, but may unofficially use the `.alac` extension. The `.m4a` container is also used for separate compressed audio codecs.

View File

@@ -1,4 +1,5 @@
---
title: Roadmap
icon: material/map-check
---
@@ -23,48 +24,54 @@ Planned features and changes are assigned **priority levels** to signify how imp
## Version Estimates
Features are given rough estimations for which version they will be completed in, and are listed next to their names (e.g. Feature **[v9.0.0]**). They are eventually replaced with links to the version changelog in which they were completed in, if applicable.
Features are given rough estimations for which version they will be completed in listed next to their names (e.g. Feature **[v9.0.0]**). When the feature is completed they're linked to their respective changelog release, if applicable.
| Version Cycle | Focused Features |
| ---------------- | -------------------------------------------------------- |
| ~~Alpha v9.5.x~~ | ~~Migrate from JSON to SQLite database format~~ |
| Alpha v9.6.x | Necessary database changes for upcoming features |
| Alpha v9.7.x | Implement currently solidified features |
| Beta v9.8.x | Solidify remaining features and implementations |
| Beta v9.9.x | Make any additions and fixes from earlier release cycles |
| v10.0.x | Full release |
<!-- prettier-ignore -->
!!! tip
For a more definitive and up-to-date list of features planned for near-future updates, please reference the current GitHub [Milestones](https://github.com/TagStudioDev/TagStudio/milestones)!
---
## Core
### :material-database: SQL Library Database
An improved SQLite-based library save file format in which legacy JSON libraries are be migrated to.
Must be finalized or deemed "feature complete" before other core features are developed or finalized.
<!-- prettier-ignore -->
!!! note
See the "[Library](#library)" section for features related to the library database rather than the underlying schema.
- [x] A SQLite-based library save file format **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
- [ ] Cached File Properties Table :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [x] Date Entry Added to Library :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Cached File Properties Table :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [x] Date Entry Added to Library
- [ ] Date File Created :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Date File Modified :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Date Photo Taken :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Media Duration :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Media Dimensions :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Date Photo Taken :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Media Duration :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Media Dimensions :material-chevron-up:{ .priority-low title="Low Priority" }
- [ ] Word Count :material-chevron-up:{ .priority-low title="Low Priority" }
### :material-database-cog: Core Library + API
### :material-database-cog: Core Library + CLI
A separated, UI agnostic core library that would be used to interface with the TagStudio library format. Would host an API for communication from outside the program. This would be licensed under the more permissive [MIT](https://en.wikipedia.org/wiki/MIT_License) license to foster wider adoption compared to the TagStudio application source code.
A separated, UI agnostic core library that would be used to interface with the TagStudio library format. Would come with a CLI to allow for interfacing with scripts and external programs, and to make bulk operations easier. This would be licensed under the more permissive [MIT](https://en.wikipedia.org/wiki/MIT_License) license to foster wider adoption compared to the TagStudio GUI application source code.
- [ ] Core Library :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- [ ] Core Library API :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- [ ] MIT License :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- [ ] Core Library :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.9.0]**
- [ ] CLI :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.9.0]**
- [ ] MIT License :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.9.0]**
### :material-clipboard-text: Format Specification
A detailed written specification for the TagStudio tag and/or library format. Intended for used by third-parties to build alternative cores or protocols that can remain interoperable.
- [ ] Format Specification Established :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- [ ] Format Specification Established :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v10.0.0]**
---
@@ -73,23 +80,23 @@ A detailed written specification for the TagStudio tag and/or library format. In
### :material-button-cursor: UI/UX
- [x] Library Grid View
- [ ] Explore Filesystem in Grid View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Infinite Scrolling (No Pagination) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Library List View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Explore Filesystem in List View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Lightbox View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Explore Filesystem in Grid View :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [x] Infinite Scrolling (No Pagination) **[[9.5.6](changelog.md#956-october-20th-2025)]**
- [ ] Library List View :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Explore Filesystem in List View :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Lightbox View :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- Similar to List View in concept, but displays one large preview that can cycle back/forth between entries.
- [ ] Smaller thumbnails of immediate adjacent entries below :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [x] Library Statistics Screen **[[v9.5.4](changelog.md#954-september-1st-2025)]**
- [x] Unified Library Health/Cleanup Screen **[[v9.5.4](changelog.md#954-september-1st-2025)]**
- [x] Fix Unlinked Entries
- [x] Fix Duplicate Files
- [ ] Fix Duplicate Files <small>(Regression)</small> **[v9.6.x]**
- [x] ~~Fix Duplicate Entries~~
- [x] Remove Ignored Entries **[[v9.5.4](changelog.md#954-september-1st-2025)]**
- [x] Delete Old Backups **[[v9.5.4](changelog.md#954-september-1st-2025)]**
- [x] Delete Legacy JSON File **[[v9.5.4](changelog.md#954-september-1st-2025)]**
- [x] Translations
- [ ] Search Bar Rework :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Search Bar Rework :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Improved Tag Autocomplete :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Tags appear as widgets in search bar :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [x] Unified Media Player
@@ -99,23 +106,23 @@ A detailed written specification for the TagStudio tag and/or library format. In
- [x] Toggle Autoplay
- [x] Volume Control
- [x] Toggle Mute
- [x] Timeline scrubber
- [ ] Fullscreen :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Fine-Tuned UI/UX :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6]**
- [ ] 3D Model Thumbnails/Previews :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [x] Timeline Scrubber
- [ ] Fullscreen Mode :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Fine-Tuned UI/UX :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] 3D Model Thumbnails/Previews :material-chevron-triple-up:{ .priority-high title="High Priority" } _(See #1231)_
- [ ] STL File Support
- [ ] OBJ File Support
- [ ] Plaintext Thumbnails/Previews :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Plaintext Thumbnails/Previews
- [x] Basic Support
- [ ] Full File Preview :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Syntax Highlighting :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Toggleable Persistent Tagging Panel :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Full File Preview :material-chevron-triple-up:{ .priority-high title="High Priority" } **[[v9.6.x]]**
- [ ] Syntax Highlighting :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[[v9.6.x]]**
- [ ] Toggleable Persistent Tagging Panel :material-chevron-triple-up:{ .priority-high title="High Priority" } **[[v9.8.x]]**
- [ ] Top Tags
- [ ] Recent Tags
- [ ] Tag Search
- [ ] Pinned Tags
- [ ] New Tabbed Tag Building UI to Support New Tag Features :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Custom Thumbnail Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] New Tabbed Tag Building UI to Support New Tag Features :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.8.x]**
- [ ] Custom Thumbnail Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
- [ ] Media Duration Labels :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Word/Line Count Labels :material-chevron-up:{ .priority-low title="Low Priority" }
- [ ] Custom Tag Badges :material-chevron-up:{ .priority-low title="Low Priority" }
@@ -130,49 +137,46 @@ A detailed written specification for the TagStudio tag and/or library format. In
- [x] Theme
- [x] Thumbnail Generation **[[v9.5.4](changelog.md#954-september-1st-2025)]**
- [x] Configurable Page Size
- [ ] Library Settings :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Library Settings :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Stored in `.TagStudio` folder :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Toggle File Extension Label :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Toggle Duration Label :material-chevron-double-up:{ .priority-med title="Medium Priority" }
### :material-puzzle: Plugin Support
Some form of official plugin support for TagStudio, likely with its own API that may connect to or encapsulate part of the the [core library API](#core-library-api).
- [ ] Plugin Support :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
---
## [Library](libraries.md)
## Library
### :material-wrench: Library Mechanics
- [x] Per-Library Tags
- [ ] Global Tags :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Global Tags :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.8.x]**
- [ ] Multiple Root Directories :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Ability to store TagStudio library folder separate from library files :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Automatic Entry Relinking :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Ability to store TagStudio data folder separate from library content folder(s) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Automatic Entry Relinking :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.8.x]**
- [ ] Detect Renames :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Detect Moves :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Detect Deletions :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Performant :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Background File Scanning :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Background File Scanning :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [x] Thumbnail Caching **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
- [ ] Audio Waveform Caching :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
- [ ] Large Image Caching :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
### :material-grid: [Entries](entries.md)
### :material-grid: Entries
Library representations of files or file-like objects.
File or file-like [entries](entries.md) stored in the library.
- [x] File Entries **[v1.0.0]**
- [ ] Folder Entries :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] URL Entries / Bookmarks :material-chevron-up:{ .priority-low title="Low Priority" }
- [ ] Folder Entries :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] URL Entries / Bookmarks :material-chevron-up:{ .priority-low title="Low Priority" } **[v9.7.x]**
- [x] Fields
- [x] Text Lines
- [x] Text Boxes
- [x] Datetimes **[[v9.5.4](changelog.md#954-september-1st-2025)]**
- [ ] User-Titled Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Removal of Deprecated Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Numeric Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Optional Units (e.g. inches, cm, height notation, degrees, bytes, etc.) :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Custom Field Names :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [x] Removal of Deprecated Fields **[v9.6.0]**
- [ ] Entry Groups :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Non-exclusive; Entries can be in multiple groups :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Ability to number entries within group :material-chevron-triple-up:{ .priority-high title="High Priority" }
@@ -181,7 +185,7 @@ Library representations of files or file-like objects.
- [ ] Group is treated as entry with tags and metadata :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Nested groups :material-chevron-double-up:{ .priority-med title="Medium Priority" }
### :material-tag-text: [Tags](tags.md)
### :material-tag-text: Tags
Discrete library objects representing [attributes](<https://en.wikipedia.org/wiki/Property_(philosophy)>). Can be applied to library [entries](entries.md), or applied to other tags to build traversable relationships.
@@ -189,30 +193,31 @@ Discrete library objects representing [attributes](<https://en.wikipedia.org/wik
- [x] Tag Shorthand Name **[v8.0.0]**
- [x] Tag Aliases List **[v8.0.0]**
- [x] Tag Color **[v8.0.0]**
- [ ] Tag Description :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.6.x]**
- [ ] Tag Description :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
- [x] Tag Colors
- [x] Built-in Color Palette **[v8.0.0]**
- [x] User-Defined Colors **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
- [x] Primary and Secondary Colors **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
- [ ] Tag Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Small Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Large Icons for Profiles :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.6.x]**
- [ ] Built-in Icon Packs (i.e. Boxicons) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] User-Defined Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Tag Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Small Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Large Icons for Profiles :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
- [ ] Built-in Icon Packs (i.e. Boxicons) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] User-Defined Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Tint Icons with Text Color :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [x] [Category Property](tags.md#is-category) **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
- [x] Property available for tags that allow the tag and any inheriting from it to be displayed separately in the preview panel under a title
- [ ] Fine-tuned exclusion from categories :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Hidden Property :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Built-in "Archived" tag has this property by default :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Checkbox near search bar to show hidden tags in search :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [x] Hidden Property **[[v9.5.7]](changelog.md#957-may-5th-2026)**
- [x] Built-in "Archived" tag has this property by default **[[v9.5.7]](changelog.md#957-may-5th-2026)**
- [x] Checkbox near search bar to show hidden tags in search **[[v9.5.7]](changelog.md#957-may-5th-2026)**
- [ ] Tag Relationships
- [x] [Parent Tags](tags.md#parent-tags) ([Inheritance](<https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)>) Relationship) **[v9.0.0]**
- [ ] [Component Tags](tags.md#component-tags) ([Composition](https://en.wikipedia.org/wiki/Object_composition) Relationship) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] [Component Tags](tags.md#component-tags) ([Composition](https://en.wikipedia.org/wiki/Object_composition) Relationship) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.8.x]**
- [ ] Multiple Language Support :material-chevron-up:{ .priority-low title="Low Priority" } **[v9.9.x]**
- [ ] Tag Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Tag Merging :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Tag Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.8.x]**
- [ ] Tag Merging :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.9.x]**
### :material-magnify: [Search](search.md)
### :material-magnify: Search
- [x] Tag Search **[v8.0.0]**
- [x] Filename Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
@@ -240,21 +245,20 @@ Discrete library objects representing [attributes](<https://en.wikipedia.org/wik
- [x] Sort by Date Entry Added to Library **[[v9.5.2](changelog.md#952-march-31st-2025)]**
- [ ] Sort by File Creation Date :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Sort by File Modification Date :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Sort by File Modification Date :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Sort by Date Taken (Photos) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [x] Random/Shuffle Sort
- [ ] OCR Search :material-chevron-up:{ .priority-low title="Low Priority" }
- [ ] Fuzzy Search :material-chevron-up:{ .priority-low title="Low Priority" }
### :material-file-cog: [Macros](macros.md)
### :material-file-cog: Macros
- [ ] Standard, Human Readable Format (TOML) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.x]**
- [ ] Versioning System :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.x]**
- [ ] Triggers **[v9.5.x]**
- [ ] Standard, Human Readable Format (TOML) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Versioning System :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Triggers **[v9.7.x]**
- [ ] On File Added :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] On Library Refresh :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] [...]
- [ ] Actions **[v9.5.x]**
- [ ] Actions **[v9.7.x]**
- [ ] Add Tag(s) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Add Field(s) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Set Field Content :material-chevron-triple-up:{ .priority-high title="High Priority" }
@@ -265,22 +269,22 @@ Discrete library objects representing [attributes](<https://en.wikipedia.org/wik
Sharable TagStudio library data in the form of data packs (tags, colors, etc.) or other formats.
Packs are intended as an easy way to import and export specific data between libraries and users, while export-only formats are intended to be imported by other programs.
- [ ] Color Packs :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.x]**
- [ ] Color Packs :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
- [ ] Importable
- [ ] Exportable
- [x] UUIDs + Namespaces :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [x] Standard, Human Readable Format (TOML) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Versioning System :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Tag Packs :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.9.x]**
- [ ] Tag Packs :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.8.x]**
- [ ] Importable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Exportable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] UUIDs + Namespaces :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Standard, Human Readable Format (TOML) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Versioning System :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Macro Sharing :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.x]**
- [ ] Macro Sharing :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.x]**
- [ ] Importable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Exportable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Sharable Entry Data :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.9.x]**
- [ ] Sharable Entry Data :material-chevron-up:{ .priority-low title="Low Priority" }
- _Specifics of this are yet to be determined_
- [ ] Export Library to Human Readable Format :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- Intended to give users more flexible options with their data if they wish to migrate away from TagStudio

View File

@@ -1,4 +1,5 @@
---
title: Searching
icon: material/magnify
---

View File

@@ -1,4 +1,5 @@
---
title: Style Guide
icon: material/sign-text
---
@@ -24,9 +25,26 @@ Most of the style guidelines can be checked, fixed, and enforced via Ruff. Older
- When writing text for window titles or form titles, use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" capitalization. Your IDE may have a command to format this for you automatically, although some may incorrectly capitalize short prepositions. In a pinch you can use a website such as [capitalizemytitle.com](https://capitalizemytitle.com/) to check.
- If it wasn't mentioned above, then stick to [**PEP-8**](https://peps.python.org/pep-0008/)!
### Modules & Implementations
- **Do not** modify legacy library code in the `src/core/library/json/` directory
- Avoid direct calls to `os`
- Use `Pathlib` library instead of `os.path`
- Use `platform.system()` instead of `os.name` and `sys.platform`
- Don't prepend local imports with `tagstudio`, stick to `src`
- Use the `logger` system instead of `print` statements
- Avoid nested f-strings
- Use HTML-like tags inside Qt widgets over stylesheets where possible
Final submitted code must **_NOT:_**
- Contain superfluous or unnecessary logging statements
- Cause unreasonable slowdowns to the program outside of a progress-indicated task
- Cause undesirable visual glitches or artifacts on screen
### Formatter Configs
TagStudio provides an [EditorConfig](https://editorconfig.org/#example-file) file ([`.editorconfig`](../.editorconfig)) along with a [Prettier](https://prettier.io/) config file ([`.prettierrc.toml`](../.prettierrc.toml)) for formatting files other than .py files (Markdown, JSON, YAML, HTML, CSS, etc.). If editing these types of files it's recommended that you use a formatter that supports EditorConfig or has its settings matched to the EditorConfig and Prettier configs. Lastly, please pay attention to the `prettier-ignore` flags in present in some files if you are not using Prettier, as formatting these sections will break formatting used elsewhere such as the [MkDocs site](https://docs.tagstud.io/).
TagStudio provides an [EditorConfig](https://editorconfig.org/#example-file) file ([`.editorconfig`](https://github.com/TagStudioDev/TagStudio/blob/main/.editorconfig)) along with a [Prettier](https://prettier.io/) config file ([`.prettierrc.toml`](https://github.com/TagStudioDev/TagStudio/blob/main/.prettierrc.toml)) for formatting files other than .py files (Markdown, JSON, YAML, HTML, CSS, etc.). If editing these types of files it's recommended that you use a formatter that supports EditorConfig or has its settings matched to the EditorConfig and Prettier configs. Lastly, please pay attention to the `prettier-ignore` flags in present in some files if you are not using Prettier, as formatting these sections will break formatting used elsewhere such as the [MkDocs site](https://docs.tagstud.io/).
## Qt

View File

@@ -1,4 +1,5 @@
---
title: Tags
icon: material/tag-text
---

View File

@@ -1,4 +1,5 @@
---
title: Basic Usage
icon: material/mouse
---

View File

@@ -116,6 +116,7 @@ markdown_extensions:
- md_in_html
- tables
- toc:
title: Table of Contents
permalink: true
toc_depth: 3
@@ -133,6 +134,11 @@ markdown_extensions:
- pymdownx.inlinehilite
- pymdownx.keys
- pymdownx.mark
- pymdownx.magiclink:
repo_url_shorthand: True
provider: github
user: TagStudioDev
repo: TagStudio
- pymdownx.smartsymbols
- pymdownx.snippets
- pymdownx.superfences:
@@ -150,6 +156,7 @@ markdown_extensions:
plugins:
- search
- tags
- typeset
- social: # social embed cards
enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions)
- redirects:

View File

@@ -3,13 +3,24 @@
<!-- SPDX-License-Identifier: MIT -->
<!-- Table of contents item -->
<li class="md-nav__item">
<a href="{{ toc_item.url }}" class="md-nav__link">
<span class="md-ellipsis">
<a href="{{ toc_item.url }}" class="md-nav__link">
<span class="md-ellipsis">
<!-- Typeset title -->
{% if toc_item.typeset %}
<span class="md-typeset">
{{ toc_item.typeset.title }}
</span>
<!-- Regular title -->
{% else %}
{{ toc_item.title }}
</span>
</a>
{% endif %}
</span>
</a>
<!-- Table of contents list -->
{% if toc_item.children %}

View File

@@ -42,18 +42,17 @@ dependencies = [
]
[project.optional-dependencies]
dev = ["tagstudio[mkdocs,mypy,pre-commit,pyinstaller,pytest,ruff]"]
mkdocs = ["mkdocs-material[imaging]>=9.6.14", "mkdocs-redirects~=1.2"]
mypy = ["mypy==1.15.0", "mypy-extensions==1.*", "types-ujson~=5.10"]
dev = ["tagstudio[mkdocs,pyright,pre-commit,pyinstaller,pytest,ruff]"]
mkdocs = ["mkdocs-material[imaging]>=9.7", "mkdocs-redirects~=1.2"]
pyright = ["pyright~=1.1.409"]
pre-commit = ["pre-commit~=4.2"]
pyinstaller = ["Pyinstaller~=6.13"]
pytest = [
"pytest==8.3.5",
"pytest==9.0.3",
"pytest-cov==6.1.1",
"pytest-mock==3.15.1",
"pytest-qt==4.4.0",
"syrupy==4.9.1",
"syrupy==5.1.0",
]
ruff = ["ruff==0.11.8"]
@@ -63,31 +62,6 @@ 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'"

View File

@@ -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 = 200
DB_VERSION: int = 201
TAG_CHILDREN_QUERY = text("""
WITH RECURSIVE ChildTags AS (

View File

@@ -20,19 +20,19 @@ class BaseField(Base):
@declared_attr
def id(self) -> Mapped[int]:
return mapped_column(primary_key=True, autoincrement=True)
return mapped_column(primary_key=True, autoincrement=True, sort_order=1)
@declared_attr
def name(self) -> Mapped[str]:
return mapped_column(nullable=False, default="")
return mapped_column(nullable=False, default="", sort_order=2)
@declared_attr
def entry_id(self) -> Mapped[int]:
return mapped_column(ForeignKey("entries.id"))
return mapped_column(ForeignKey("entries.id"), sort_order=3)
@declared_attr
def entry(self) -> Mapped[Entry]:
return relationship(foreign_keys=[self.entry_id]) # type: ignore # pyright: ignore[reportArgumentType]
return relationship(foreign_keys=[self.entry_id]) # 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]
value: Mapped[str | None] = mapped_column(sort_order=4)
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]
value: Mapped[str | None] = mapped_column(sort_order=4)
@override
def __eq__(self, other: object) -> bool:

View File

@@ -419,6 +419,7 @@ 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",
@@ -430,6 +431,7 @@ 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.
@@ -452,7 +454,7 @@ class Library:
),
)
logger.info(f"[Library] DB_VERSION: {loaded_db_version}")
logger.info(f"[Library] Library DB version: {loaded_db_version}")
make_tables(self.engine)
if is_new:
@@ -571,6 +573,9 @@ 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)")
@@ -588,6 +593,7 @@ 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
@@ -808,10 +814,6 @@ 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():
@@ -863,6 +865,57 @@ 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:
@@ -957,7 +1010,7 @@ class Library:
)
statement = statement.distinct()
entries: ScalarResult[Entry] | list[Entry] = session.execute(statement).scalars()
entries = entries.unique() # type: ignore
entries = entries.unique()
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])
@@ -1414,7 +1467,7 @@ class Library:
if tag:
tags.append(tag.id)
else:
new = session.add(Tag(name=string)) # type: ignore
new = session.add(Tag(name=string))
if new:
tags.append(new.id)
session.flush()

View File

@@ -47,21 +47,21 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
self.lib = lib
@override
def visit_or_list(self, node: ORList) -> ColumnElement[bool]: # type: ignore
def visit_or_list(self, node: ORList) -> ColumnElement[bool]:
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]: # type: ignore
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]:
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]: # type: ignore
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]:
"""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]: # type: ignore
def visit_property(self, node: Property) -> ColumnElement[bool]:
raise NotImplementedError("This should never be reached!")
@override
def visit_not(self, node: Not) -> ColumnElement[bool]: # type: ignore
def visit_not(self, node: Not) -> ColumnElement[bool]:
return ~self.visit(node.child)
def __get_tag_ids(self, tag_name: str, include_children: bool = True) -> list[int]:

View File

@@ -88,6 +88,6 @@ class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
self.driver.library_info_window.update_cleanup()
@override
def showEvent(self, event: QtGui.QShowEvent) -> None: # type: ignore
def showEvent(self, event: QtGui.QShowEvent) -> None:
self.update_ignored_count()
return super().showEvent(event)

View File

@@ -46,6 +46,6 @@ class IgnoreModal(IgnoreModalView):
Ignore.write_ignore_file(self.lib.library_dir, lines)
@override
def showEvent(self, event: QShowEvent) -> None: # type: ignore
def showEvent(self, event: QShowEvent) -> None:
self.__load_file()
return super().showEvent(event)

View File

@@ -162,6 +162,6 @@ class LibraryInfoWindow(LibraryInfoWindowView):
return size
@override
def showEvent(self, event: QtGui.QShowEvent): # type: ignore
def showEvent(self, event: QtGui.QShowEvent):
self.refresh()
return super().showEvent(event)

View File

@@ -34,7 +34,7 @@ class TagBoxWidget(TagBoxWidgetView):
self.__entries = entries
@override
def _on_click(self, tag: Tag) -> None: # type: ignore[misc]
def _on_click(self, tag: Tag) -> None:
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: # type: ignore[misc]
def _on_remove(self, tag: Tag) -> None:
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: # type: ignore[misc]
def _on_edit(self, tag: Tag) -> None:
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: # type: ignore[misc]
def _on_search(self, tag: Tag) -> None:
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)

View File

@@ -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()) # type: ignore
lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit())
)
self.edit_modal.show()

View File

@@ -114,6 +114,7 @@ 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)

View File

@@ -467,12 +467,12 @@ class ItemThumb(FlowWidget):
badge.setHidden(is_hidden)
@override
def enterEvent(self, event: QEnterEvent) -> None: # type: ignore[misc]
def enterEvent(self, event: QEnterEvent) -> None:
self.show_check_badges(show=True)
return super().enterEvent(event)
@override
def leaveEvent(self, event: QEvent) -> None: # type: ignore[misc]
def leaveEvent(self, event: QEvent) -> None:
self.show_check_badges(show=False)
return super().leaveEvent(event)
@@ -502,7 +502,7 @@ class ItemThumb(FlowWidget):
)
@override
def mouseMoveEvent(self, event: QMouseEvent) -> None: # type: ignore[misc]
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if event.buttons() is not Qt.MouseButton.LeftButton:
return

View File

@@ -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 # type: ignore
and arg__2.button() == Qt.MouseButton.LeftButton # pyright: ignore[reportAttributeAccessIssue]
):
if arg__1 == self.play_pause:
self.toggle_play()

View File

@@ -364,7 +364,7 @@ class JsonMigrationModal(QObject):
iterator = FunctionIterator(self.migration_iterator)
iterator.value.connect(
lambda x: (
pb.setLabelText(f"<h4>{x}</h4>"), # type: ignore
pb.setLabelText(f"<h4>{x}</h4>"),
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), # type: ignore
pb.setValue(1), # type: ignore
pb.setMinimum(1),
pb.setValue(1),
# 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() # type:ignore
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # pyright: ignore[reportAssignmentType]
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() # type:ignore
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # pyright: ignore[reportAssignmentType]
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() # type:ignore
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore
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.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() # 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: 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.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)

View File

@@ -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( # type: ignore
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # pyright: ignore[reportAttributeAccessIssue]
str(i + 1)
)
self._assign_click(
@@ -237,9 +237,7 @@ 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( # type: ignore
str(i + 1)
)
self.end_buffer_layout.itemAt(i - end_offset).widget().setText(str(i + 1)) # pyright: ignore[reportAttributeAccessIssue]
self._assign_click(
cast(
QPushButtonWrapper,

View File

@@ -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]) # type: ignore
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks])
)
QThreadPool.globalInstance().start(r)

View File

@@ -59,15 +59,15 @@ class TagDatabasePanel(TagSearchPanel):
return
message_box = QMessageBox(
QMessageBox.Question, # type: ignore
QMessageBox.Question, # pyright: ignore[reportAttributeAccessIssue]
Translations["tag.remove"],
Translations.format("tag.confirm_delete", tag_name=self.lib.tag_display_name(tag)),
QMessageBox.Ok | QMessageBox.Cancel, # type: ignore
QMessageBox.Ok | QMessageBox.Cancel, # pyright: ignore[reportAttributeAccessIssue]
)
result = message_box.exec()
if result != QMessageBox.Ok: # type: ignore
if result != QMessageBox.Ok: # pyright: ignore[reportAttributeAccessIssue]
return
self.lib.remove_tag(tag.id)

View File

@@ -299,7 +299,7 @@ class ThumbRenderer(QObject):
im: Image.Image = Image.new(
mode="L",
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
color="black",
)
draw = ImageDraw.Draw(im)
@@ -330,7 +330,7 @@ class ThumbRenderer(QObject):
# Highlight
im_hl: Image.Image = Image.new(
mode="RGBA",
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
color="#00000000",
)
draw = ImageDraw.Draw(im_hl)
@@ -349,7 +349,7 @@ class ThumbRenderer(QObject):
# Shadow
im_sh: Image.Image = Image.new(
mode="RGBA",
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
color="#00000000",
)
draw = ImageDraw.Draw(im_sh)
@@ -394,7 +394,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]), # type: ignore # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
color="#FF000000",
)
@@ -402,13 +402,13 @@ class ThumbRenderer(QObject):
bg: Image.Image
bg = Image.new(
"RGB",
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
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])) # type: ignore # pyright: ignore[reportArgumentType]
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
bg_im = ImageEnhance.Brightness(bg_im).enhance(0.3) # Reduce the brightness
bg.paste(bg_im)
@@ -417,7 +417,7 @@ class ThumbRenderer(QObject):
bg,
(0, 0),
mask=self._get_mask(
tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
(pixel_ratio * smooth_factor),
),
)
@@ -501,19 +501,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]), # type: ignore # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
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])) # type: ignore # pyright: ignore[reportArgumentType]
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
# Create solid background color
else:
bg = Image.new(
"RGB",
size=tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
color="#000000",
)
# Apply color overlay
@@ -527,7 +527,7 @@ class ThumbRenderer(QObject):
bg,
(0, 0),
mask=self._get_mask(
tuple([d * smooth_factor for d in size]), # type: ignore # pyright: ignore[reportArgumentType]
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
(pixel_ratio * smooth_factor),
),
)
@@ -1216,7 +1216,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") # type: ignore # pyright: ignore[reportArgumentType]
q_image.save(device=buffer, format="PNG") # pyright: ignore[reportArgumentType]
# Load the image from the buffer
im = Image.new("RGB", (size, size), color="#1e1e1e")
@@ -1338,7 +1338,7 @@ class ThumbRenderer(QObject):
buffer: QBuffer = QBuffer()
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
try:
q_image.save(buffer, "PNG") # type: ignore # pyright: ignore
q_image.save(buffer, "PNG") # pyright: ignore
im = Image.open(BytesIO(buffer.buffer().data()))
finally:
buffer.close()

View File

@@ -2,7 +2,6 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# Vendored from pydub
# type: ignore
import array

View File

@@ -350,7 +350,7 @@ class QtDriver(DriverMixin, QObject):
if os.name == "nt":
appid = "cyanvoxel.tagstudio.9"
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) # type: ignore[attr-defined,unused-ignore]
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
self.app.setApplicationName("tagstudio")
self.app.setApplicationDisplayName("TagStudio")
@@ -1100,7 +1100,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(), # type: ignore
files_count and self.update_browsing_state(),
)
)
QThreadPool.globalInstance().start(r)

View File

@@ -86,7 +86,7 @@ class ThumbButton(QPushButtonWrapper):
)
@override
def paintEvent(self, arg__1: QPaintEvent) -> None: # type: ignore
def paintEvent(self, arg__1: QPaintEvent) -> None:
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: # type: ignore
def enterEvent(self, event: QEnterEvent) -> None:
self.hovered = True
self.repaint()
return super().enterEvent(event)
@override
def leaveEvent(self, event: QEvent) -> None: # type: ignore
def leaveEvent(self, event: QEvent) -> None:
self.hovered = False
self.repaint()
return super().leaveEvent(event)

View File

@@ -6,8 +6,8 @@
"about.module.found": "Encontrado",
"about.title": "Acerca de TagStudio",
"about.website": "Página web",
"app.git": "Git Commit",
"app.pre_release": "Pre-lanzamiento",
"app.git": "Commit de Git",
"app.pre_release": "Pre-Lanzamiento",
"app.title": "{base_title} - Biblioteca '{library_dir}'",
"color.color_border": "Usar color secundario para el Borde",
"color.confirm_delete": "¿Estás seguro de que quieres eliminar el color \"{color_name}\"?",
@@ -71,14 +71,17 @@
"entries.unlinked.unlinked_count": "Entradas no vinculadas: {count}",
"ffmpeg.missing.description": "No se ha encontrado FFmpeg y/o FFprobe. Se requiere de FFmpeg para la reproducción de contenido multimedia y las miniaturas.",
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
"field.copy": "Copiar campo",
"field.edit": "Editar campo",
"field.paste": "Pegar campo",
"file.date_added": "Fecha de adición",
"file.date_created": "Fecha de creación",
"file.date_modified": "Fecha de modificación",
"field.copy": "Copiar Campo",
"field.edit": "Editar Campo",
"field.paste": "Pegar Campo",
"field_type.datetime": "Fecha y Hora",
"field_type.text": "Texto",
"field_type.unknown": "Tipo Desconocido",
"file.date_added": "Fecha de Adición",
"file.date_created": "Fecha de Creación",
"file.date_modified": "Fecha de Modificación",
"file.dimensions": "Dimensiones",
"file.duplicates.description": "TagStudio es compatible con Importación de resultados de DupeGuru para administrar archivos duplicados.",
"file.duplicates.description": "TagStudio es compatible con importación de resultados de DupeGuru para administrar archivos duplicados.",
"file.duplicates.dupeguru.advice": "Después de la duplicación, puede utilizar DupeGuru para eliminar los archivos no deseados. Luego, utilice la función \"Reparar entradas no vinculadas\" de TagStudio en el menú Herramientas para eliminar las entradas no vinculadas.",
"file.duplicates.dupeguru.file_extension": "Archivos de DupeGuru (*.dupeguru)",
"file.duplicates.dupeguru.load_file": "&Cargar archivo DupeGuru",
@@ -157,6 +160,7 @@
"json_migration.heading.aliases": "Alias:",
"json_migration.heading.colors": "Colores:",
"json_migration.heading.differ": "Discrepancia",
"json_migration.heading.extensions": "Extensiones:",
"json_migration.heading.match": "Igualado",
"json_migration.heading.names": "Nombres:",
"json_migration.heading.parent_tags": "Etiquetas principales:",
@@ -271,6 +275,7 @@
"settings.open_library_on_start": "Abrir biblioteca al iniciar",
"settings.page_size": "Tamaño de la página",
"settings.restart_required": "Por favor, reinicia TagStudio para que se los cambios surtan efecto.",
"settings.scan_files_on_open": "Cargar Nuevos Archivos Automáticamente",
"settings.show_filenames_in_grid": "Mostrar el nombre de archivo en la cuadrícula",
"settings.show_recent_libraries": "Mostrar bibliotecas recientes",
"settings.splash.label": "Pantalla de Bienvenida",

View File

@@ -59,7 +59,7 @@
"entries.running.dialog.new_entries": "{total} 件の新しいファイル エントリを追加しています...",
"entries.running.dialog.title": "新しいファイルエントリを追加",
"entries.tags": "タグ",
"entries.unlinked.description": "ライブラリの各エントリは、ディレクトリ内のファイルにリンクされています。エントリにリンクされたファイルがTagStudio以外で移動または削除された場合、そのエントリはリンク切れとして扱われます。<br><br>リンク切れのエントリは、ディレクトリを検索して自動的に再リンクすることも、必要に応じて削除することもできます。",
"entries.unlinked.description": "ライブラリの各エントリは、ディレクトリ内のファイルにリンクされています。エントリにリンクされたファイルが TagStudio 以外で移動または削除された場合、そのエントリはリンク切れとして扱われます。<br><br>リンク切れのエントリは、ディレクトリを検索して自動的に再リンクすることも、必要に応じて削除することもできます。",
"entries.unlinked.relink.attempting": "{unlinked_count} 件中 {index} 件のエントリを再リンク中、{fixed_count} 件を正常に再リンクしました",
"entries.unlinked.relink.manual": "手動で再リンク(&M)",
"entries.unlinked.relink.title": "エントリの再リンク",

View File

@@ -1,6 +1,6 @@
{
"about.config_path": "Mlafuplas fu sentakuzma",
"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.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.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}\"?",
"color.confirm_delete": "Du kestetsa varge \"{color_name}\" we?",
"color.delete": "Keste festaretol",
"color.import_pack": "Nasii klaani fu varge",
"color.name": "Namae",
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam? Afto zol keste alting ine vargetumam au sebja!",
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam we? Afto zol keste ALTING ine vargetumam au sebja!",
"color.namespace.delete.title": "Keste vargetumam",
"color.new": "Neo varge",
"color.placeholder": "Varge",
@@ -36,47 +36,61 @@
"edit.copy_fields": "Mverm shiruzmafal",
"edit.paste_fields": "Nasii shiru",
"edit.tag_manager": "Jewalt festaretol",
"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.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.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?",
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban we?",
"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 {count} 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.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 tsunaganaijena.<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 kntsunagajena.<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.scanning": "Suha mlafuhuomi ima per tsunaganaijena shiruzmakaban...",
"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.search_and_relink": "&Suha &&Tsunaga gen",
"entries.unlinked.title": "Fiks tsunaganaijena shiruzmakaban",
"entries.unlinked.unlinked_count": "Tsunaganaijena shiruzmakaban: {count}",
"entries.unlinked.title": "Fiks kntsunagajena shiruzmakaban",
"entries.unlinked.unlinked_count": "Kntsunagajena 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",
"file.date_added": "Dag nasiijenadan",
"file.date_created": "Dag mahajenadan",
"file.date_modified": "Dag kawarijenadan",
"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.dimensions": "Stuuratailasku",
"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.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.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 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.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.mirror_entries": "&Maha melon fu shiruzmakaban",
"file.duration": "Pitkatai",
"file.not_found": "Mlafu nai finnajena",
@@ -110,33 +124,43 @@
"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:",
@@ -156,13 +180,22 @@
"library.field.remove": "Keste shiruzmafal",
"library.missing": "Mlafuplas fu mlafuhuomi nai finnajenadan",
"library.name": "Mlafuhuomi",
"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.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.title": "Gengotova al mlafukaban",
"library.scan_library.title": "Taskama mlafuhuomi ima",
"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_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)",
@@ -190,15 +223,16 @@
"menu.file.save_library": "Ufne mlafuhuomi",
"menu.help": "&Aputsa",
"menu.help.about": "Shiruplus",
"menu.macros": "&Zetingnen",
"menu.macros": "&Aplinen",
"menu.macros.folders_to_tags": "Mlafukaban festaretol made",
"menu.select": "Sentaku",
"menu.settings": "Sentakuzma...",
"menu.tools": "&Tropos",
"menu.tools.fix_duplicate_files": "Fiks sama &mlafu",
"menu.tools.fix_duplicate_files": "Fiks &blisnets 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!",
@@ -222,17 +256,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": "Klea fu zeting:",
"settings.theme.label": "Ekran klea:",
"settings.theme.light": "Kirkas",
"settings.theme.system": "Kompyu",
"settings.title": "Sentakuzma",
"sorting.direction.ascending": "Leste owaris",
"sorting.direction.descending": "Leste eins",
"sorting.direction.ascending": "Plus made",
"sorting.direction.descending": "Minus made",
"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 zeting.",
"status.deleted_partial_warning": "Mono kestejenadan {count} mlafu! Bitte taskamatsa li du dekinai finna mlafu os ke ima brukjena na andr apli.",
"status.deleting_file": "Keste mlafu [{i}/{count}]: \"{path}\" ima...",
"status.library_closing": "Kini mlafuhuomi ima...",
"status.library_save_success": "Mlafuhuomi ufnejenadan au kinijenadan!",
@@ -250,7 +284,7 @@
"tag.all_tags": "Al festaretol",
"tag.choose_color": "Sentaku varge fu festaretol",
"tag.color": "Varge",
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\"?",
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\" we?",
"tag.create": "Maha festaretol",
"tag.create_add": "Maha && Nasii \"{query}\"",
"tag.disambiguation.tooltip": "Bruk afto festaretol grun plusklar",
@@ -272,8 +306,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?",
"trash.dialog.move.confirmation.singular": "Du ugokitsa afto mlafu {trash_term} made?",
"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.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",

View File

@@ -73,6 +73,9 @@
"field.copy": "o kama jo e ma sama",
"field.edit": "o ante e ma",
"field.paste": "o pana e ma sama",
"field_type.datetime": "tenpo",
"field_type.text": "toki",
"field_type.unknown": "nasa",
"file.date_added": "tenpo pi kama namako",
"file.date_created": "tenpo pi kama sin",
"file.date_modified": "tenpo pi kama ante",

View File

@@ -30,6 +30,7 @@ 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):