mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-05-21 16:25:10 +00:00
Compare commits
22 Commits
translatio
...
doc/reuse
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a900405b6d | ||
|
|
26eef3a0e4 | ||
|
|
06de198e8b | ||
|
|
bdd06ca933 | ||
|
|
203680054f | ||
|
|
7feda35938 | ||
|
|
57f291d69a | ||
|
|
11ca020230 | ||
|
|
70de571ef4 | ||
|
|
67efc35e96 | ||
|
|
39cb8928cb | ||
|
|
233e73f30a | ||
|
|
b7894b35f6 | ||
|
|
998f55198f | ||
|
|
d6e4fd0f47 | ||
|
|
c865895ef9 | ||
|
|
da69a4bad1 | ||
|
|
1aa526c454 | ||
|
|
a5c27c60be | ||
|
|
59df808f72 | ||
|
|
ccc49440de | ||
|
|
84484d7333 |
@@ -1,22 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# EditorConfig https://editorconfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{css,json,md}]
|
||||
indent_size = 4
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_size = 2
|
||||
@@ -5,7 +5,3 @@
|
||||
# Date: Fri, 13 Sep 2024 00:28:00 -0700
|
||||
# ci(ruff)!: update ruff linter config, refactor to comply
|
||||
b6e216760557c5507b12f210e1e48c531f49ffa3
|
||||
|
||||
# Date: Tue, 12 May 2026 09:24:04 -0400
|
||||
# refactor(docs): uniform formatting pass (#1363)
|
||||
e134cb1ecb888138411e804d15db8b1a7ede0cb0
|
||||
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1,5 +1,6 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
---
|
||||
patreon: cyanvoxel
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -3,8 +3,8 @@
|
||||
---
|
||||
name: Bug Report
|
||||
description: File a bug or issue report.
|
||||
title: "[Bug]: "
|
||||
labels: ["Type: Bug"]
|
||||
title: '[Bug]: '
|
||||
labels: ['Type: Bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
4
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -3,8 +3,8 @@
|
||||
---
|
||||
name: Feature Request
|
||||
description: Suggest a new feature.
|
||||
title: "[Feature Request]: "
|
||||
labels: ["Type: Enhancement"]
|
||||
title: '[Feature Request]: '
|
||||
labels: ['Type: Enhancement']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
25
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,6 +1,5 @@
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
### Summary
|
||||
|
||||
<!--
|
||||
@@ -20,15 +19,15 @@ Thank you for your eagerness to contribute!
|
||||
|
||||
<!-- No requirements, just context for reviewers. -->
|
||||
|
||||
- Platforms Tested:
|
||||
- [ ] Windows x86
|
||||
- [ ] Windows ARM
|
||||
- [ ] macOS x86
|
||||
- [ ] macOS ARM
|
||||
- [ ] Linux x86
|
||||
- [ ] Linux ARM
|
||||
<!-- If an unspecified platform was tested, please add it here -->
|
||||
- Tested For:
|
||||
- [ ] Basic functionality
|
||||
- [ ] PyInstaller executable <!-- Not necessarily required, but appreciated! -->
|
||||
<!-- If other important criteria was tested for, please add it here -->
|
||||
- Platforms Tested:
|
||||
- [ ] Windows x86
|
||||
- [ ] Windows ARM
|
||||
- [ ] macOS x86
|
||||
- [ ] macOS ARM
|
||||
- [ ] Linux x86
|
||||
- [ ] Linux ARM
|
||||
<!-- If an unspecified platform was tested, please add it here -->
|
||||
- Tested For:
|
||||
- [ ] Basic functionality
|
||||
- [ ] PyInstaller executable <!-- Not necessarily required, but appreciated! -->
|
||||
<!-- If other important criteria was tested for, please add it here -->
|
||||
|
||||
@@ -1,31 +1,36 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
---
|
||||
name: Pyright
|
||||
name: MyPy
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
pyright:
|
||||
name: Run Pyright
|
||||
mypy:
|
||||
name: Run MyPy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup reviewdog
|
||||
uses: reviewdog/action-setup@v1
|
||||
with:
|
||||
reviewdog_version: latest
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system .[pyright,pytest]
|
||||
uv pip install --system .[mypy]
|
||||
|
||||
- name: Execute Pyright
|
||||
uses: jordemort/action-pyright@v1
|
||||
- name: Execute MyPy
|
||||
uses: tsuyoshicho/action-mypy@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
reporter: ${{ github.event_name == 'pull_request' && 'github-pr-review' || 'github-check' }}
|
||||
fail_on_error: true
|
||||
4
.github/workflows/publish_docs.yaml
vendored
4
.github/workflows/publish_docs.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
key: mkdocs-material-${{ env.cache_id }}
|
||||
path: .cache
|
||||
restore-keys: |
|
||||
mkdocs-material-
|
||||
mkdocs-material-
|
||||
|
||||
- name: Execute mkdocs
|
||||
run: mkdocs gh-deploy --force
|
||||
|
||||
8
.github/workflows/pytest.yaml
vendored
8
.github/workflows/pytest.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
|
||||
- &install-dependencies
|
||||
@@ -89,9 +89,9 @@ jobs:
|
||||
- name: Check coverage
|
||||
uses: yedpodtrzitko/coverage@main
|
||||
with:
|
||||
thresholdAll: 0.1
|
||||
thresholdNew: 0.1
|
||||
thresholdModified: 0.1
|
||||
thresholdAll: 0.4
|
||||
thresholdNew: 0.4
|
||||
thresholdModified: 0.4
|
||||
coverageFile: coverage.xml
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
sourceDir: tagstudio/src
|
||||
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -12,11 +12,11 @@ jobs:
|
||||
linux:
|
||||
strategy:
|
||||
matrix:
|
||||
build-type: ["", portable]
|
||||
build-type: ['', portable]
|
||||
include:
|
||||
- build-type: ""
|
||||
build-flag: ""
|
||||
suffix: ""
|
||||
- build-type: ''
|
||||
build-flag: ''
|
||||
suffix: ''
|
||||
- build-type: portable
|
||||
build-flag: --portable
|
||||
suffix: _portable
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
@@ -50,18 +50,18 @@ jobs:
|
||||
macos:
|
||||
strategy:
|
||||
matrix:
|
||||
os-version: ["14", "15"]
|
||||
os-version: ['14', '15']
|
||||
include:
|
||||
- os-version: "14"
|
||||
- os-version: '14'
|
||||
arch: x86_64
|
||||
- os-version: "15"
|
||||
- os-version: '15'
|
||||
arch: aarch64
|
||||
|
||||
runs-on: macos-${{ matrix.os-version }}
|
||||
|
||||
env:
|
||||
# INFO: Even though we run on 14, target towards compatibility
|
||||
MACOSX_DEPLOYMENT_TARGET: "11.0"
|
||||
MACOSX_DEPLOYMENT_TARGET: '11.0'
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
@@ -78,6 +78,7 @@ jobs:
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system .[pyinstaller]
|
||||
|
||||
|
||||
- name: Execute PyInstaller
|
||||
run: |
|
||||
pyinstaller tagstudio.spec
|
||||
@@ -91,12 +92,12 @@ jobs:
|
||||
windows:
|
||||
strategy:
|
||||
matrix:
|
||||
build-type: ["", portable]
|
||||
build-type: ['', portable]
|
||||
include:
|
||||
- build-type: ""
|
||||
build-flag: ""
|
||||
suffix: ""
|
||||
file-end: ""
|
||||
- build-type: ''
|
||||
build-flag: ''
|
||||
suffix: ''
|
||||
file-end: ''
|
||||
- build-type: portable
|
||||
build-flag: --portable
|
||||
suffix: _portable
|
||||
@@ -111,7 +112,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: '3.12'
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: pyright
|
||||
name: pyright
|
||||
entry: pyright
|
||||
- id: mypy
|
||||
name: mypy
|
||||
entry: mypy
|
||||
language: system
|
||||
types_or: [python, pyi]
|
||||
require_serial: true
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
overrides/partials/*.html
|
||||
tests/fixtures/json_library/.TagStudio/*.json
|
||||
@@ -1,10 +0,0 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Prettier https://prettier.io/
|
||||
|
||||
bracketSameLine = false
|
||||
bracketSpacing = true
|
||||
objectWrap = "preserve"
|
||||
quoteProps = "as-needed"
|
||||
singleQuote = false
|
||||
109
README.md
109
README.md
@@ -1,6 +1,5 @@
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
# TagStudio: A User-Focused Photo & File Management System
|
||||
|
||||
[](https://github.com/TagStudioDev/TagStudio/releases)
|
||||
@@ -24,11 +23,11 @@ TagStudio is a photo & file organization application with an underlying tag-base
|
||||
|
||||
## Contents
|
||||
|
||||
- [Feature Highlights](#feature-highlights)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Installation](#installation)
|
||||
- [Goals & Priorities](#goals--priorities)
|
||||
- [FAQ](#faq)
|
||||
- [Feature Highlights](#feature-highlights)
|
||||
- [Basic Usage](#basic-usage)
|
||||
- [Installation](#installation)
|
||||
- [Goals & Priorities](#goals--priorities)
|
||||
- [FAQ](#faq)
|
||||
|
||||
Translation hosting generously provided by [Weblate](https://weblate.org/en/). Check out our [project page](https://hosted.weblate.org/projects/tagstudio/) to help translate TagStudio!
|
||||
|
||||
@@ -46,16 +45,16 @@ All file types are supported in TagStudio libraries, just not all have dedicated
|
||||
|
||||
For a generalized list of what's currently supported:
|
||||
|
||||
- **Images**
|
||||
- Raster Images (JPEG, PNG, etc.)
|
||||
- Vector (SVG)
|
||||
- Animated (GIF, WEBP, APNG)
|
||||
- RAW Formats
|
||||
- **Videos**
|
||||
- **Plaintext Files**
|
||||
- **Documents** _(If supported)_
|
||||
- **eBooks** _(If supported)_
|
||||
- **Photoshop PSDs**, **Blender Projects**, **Krita Projects**, and more!
|
||||
- **Images**
|
||||
- Raster Images (JPEG, PNG, etc.)
|
||||
- Vector (SVG)
|
||||
- Animated (GIF, WEBP, APNG)
|
||||
- RAW Formats
|
||||
- **Videos**
|
||||
- **Plaintext Files**
|
||||
- **Documents** _(If supported)_
|
||||
- **eBooks** _(If supported)_
|
||||
- **Photoshop PSDs**, **Blender Projects**, **Krita Projects**, and more!
|
||||
|
||||
### [Tags](https://docs.tagstud.io/tags) and [Fields](https://docs.tagstud.io/fields)
|
||||
|
||||
@@ -63,28 +62,28 @@ Tags represent an object or attribute - this could be a person, place, object, c
|
||||
|
||||
Tags currently consist of the following attributes:
|
||||
|
||||
- **Name**: The full name for your tag. **_This does NOT have to be unique!_**
|
||||
- **Shorthand Name**: The shortest alternate name for your tag, used for abbreviations.
|
||||
- **Aliases**: Alternate names your tag goes by.
|
||||
- **Color**: The display color of your tag.
|
||||
- **Parent Tags**: Other tags in which this tag inherits from. In practice, this means that this tag can be substituted in searches for any listed parent tags.
|
||||
- Parent tags checked with the "disambiguation" checkbox next to them will be used to help disambiguate tag names that may not be unique.
|
||||
- For example: If you had a tag for "Freddy Fazbear", you might add "Five Nights at Freddy's" as one of the parent tags. If the disambiguation box is checked next to "Five Nights at Freddy's" parent tag, then the tag "Freddy Fazbear" will display as "Freddy Fazbear (Five Nights at Freddy's)". Furthermore, if the "Five Nights at Freddy's" tag has a shorthand like "FNAF", then the "Freddy Fazbear" tag will display as "Freddy Fazbear (FNAF)".
|
||||
- **Is Category**: A property that when checked, treats this tag as a category in the preview panel.
|
||||
- **Name**: The full name for your tag. **_This does NOT have to be unique!_**
|
||||
- **Shorthand Name**: The shortest alternate name for your tag, used for abbreviations.
|
||||
- **Aliases**: Alternate names your tag goes by.
|
||||
- **Color**: The display color of your tag.
|
||||
- **Parent Tags**: Other tags in which this tag inherits from. In practice, this means that this tag can be substituted in searches for any listed parent tags.
|
||||
- Parent tags checked with the "disambiguation" checkbox next to them will be used to help disambiguate tag names that may not be unique.
|
||||
- For example: If you had a tag for "Freddy Fazbear", you might add "Five Nights at Freddy's" as one of the parent tags. If the disambiguation box is checked next to "Five Nights at Freddy's" parent tag, then the tag "Freddy Fazbear" will display as "Freddy Fazbear (Five Nights at Freddy's)". Furthermore, if the "Five Nights at Freddy's" tag has a shorthand like "FNAF", then the "Freddy Fazbear" tag will display as "Freddy Fazbear (FNAF)".
|
||||
- **Is Category**: A property that when checked, treats this tag as a category in the preview panel.
|
||||
|
||||
Fields, like tags, are additional pieces of custom metadata that you can add to your file entries. Fields currently have several hardcoded names (e.g. "Title", "Author", "Series") but custom field names are planned for an upcoming update.
|
||||
|
||||
Field types currently include:
|
||||
|
||||
- **Text Lines**: Single lines of text.
|
||||
- **Text Boxes**: Multi-line pieces of text.
|
||||
- **Datetimes**: Dates and times.
|
||||
- **Text Lines**: Single lines of text.
|
||||
- **Text Boxes**: Multi-line pieces of text.
|
||||
- **Datetimes**: Dates and times.
|
||||
|
||||
### [Search](https://docs.tagstud.io/search)
|
||||
|
||||
- Search for file entries based on tags, file path (`path:`), file types (`filetype:`), and even media types! (`mediatype:`). Path searches currently use [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) syntax, so you may need to wrap your filename or filepath in asterisks while searching. This will not be strictly necessary in future versions of the program.
|
||||
- Use and combine boolean operators (`AND`, `OR`, `NOT`) along with parentheses groups, quotation escaping, and underscore substitution to create detailed search queries
|
||||
- Use special search conditions (`special:untagged` and `special:empty`) to find file entries without tags or fields, respectively
|
||||
- Search for file entries based on tags, file path (`path:`), file types (`filetype:`), and even media types! (`mediatype:`). Path searches currently use [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) syntax, so you may need to wrap your filename or filepath in asterisks while searching. This will not be strictly necessary in future versions of the program.
|
||||
- Use and combine boolean operators (`AND`, `OR`, `NOT`) along with parentheses groups, quotation escaping, and underscore substitution to create detailed search queries
|
||||
- Use special search conditions (`special:untagged` and `special:empty`) to find file entries without tags or fields, respectively
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -168,11 +167,11 @@ See the [**Roadmap**](docs/roadmap.md) on the documentation site for a complete
|
||||
|
||||
### Overall Goals
|
||||
|
||||
- To achieve a portable, private, extensible, open-format, and feature-rich system of organizing and rediscovering files.
|
||||
- To provide powerful methods for organization, notably the concept of tag inheritance, or "taggable tags" _(and in the near future, the combination of composition-based tags)._
|
||||
- To create an implementation of such a system that is resilient against a user’s actions outside the program (modifying, moving, or renaming files) while also not burdening the user with mandatory sidecar files or requiring them to change their existing file structures and workflows.
|
||||
- To support a wide range of users spanning across different platforms, multi-user setups, and those with large (several terabyte) libraries.
|
||||
- To make the dang thing look nice, too. It’s 2025, not 1995.
|
||||
- To achieve a portable, private, extensible, open-format, and feature-rich system of organizing and rediscovering files.
|
||||
- To provide powerful methods for organization, notably the concept of tag inheritance, or "taggable tags" _(and in the near future, the combination of composition-based tags)._
|
||||
- To create an implementation of such a system that is resilient against a user’s actions outside the program (modifying, moving, or renaming files) while also not burdening the user with mandatory sidecar files or requiring them to change their existing file structures and workflows.
|
||||
- To support a wide range of users spanning across different platforms, multi-user setups, and those with large (several terabyte) libraries.
|
||||
- To make the dang thing look nice, too. It’s 2025, not 1995.
|
||||
|
||||
### Project Priorities
|
||||
|
||||
@@ -200,29 +199,29 @@ See the [roadmap](https://docs.tagstud.io/roadmap) page for the core features be
|
||||
|
||||
The most important remaining features before I consider the program to be "feature complete" are:
|
||||
|
||||
- Custom names for Fields
|
||||
- List views for files
|
||||
- Multiple root directory support for libraries
|
||||
- Improved file entry relinking
|
||||
- File entry groups
|
||||
- Sorting by file date modified and created
|
||||
- Macros
|
||||
- Improved search bar with visualized tags and improved autocomplete
|
||||
- Side panel for easier tagging (pinned tags, recent tags, tag search, tag palette)
|
||||
- Improved tag management interface
|
||||
- Improved and finalized Tag Categories
|
||||
- Fixed and improved mixed entry data displays (see: [#337](https://github.com/TagStudioDev/TagStudio/issues/337))
|
||||
- Sharable tag data
|
||||
- Separate core library + API
|
||||
- Custom names for Fields
|
||||
- List views for files
|
||||
- Multiple root directory support for libraries
|
||||
- Improved file entry relinking
|
||||
- File entry groups
|
||||
- Sorting by file date modified and created
|
||||
- Macros
|
||||
- Improved search bar with visualized tags and improved autocomplete
|
||||
- Side panel for easier tagging (pinned tags, recent tags, tag search, tag palette)
|
||||
- Improved tag management interface
|
||||
- Improved and finalized Tag Categories
|
||||
- Fixed and improved mixed entry data displays (see: [#337](https://github.com/TagStudioDev/TagStudio/issues/337))
|
||||
- Sharable tag data
|
||||
- Separate core library + API
|
||||
|
||||
### What features will NOT be added?
|
||||
|
||||
- Native Cloud Integration
|
||||
- There are plenty of services already (native or third-party) that allow you to mount your cloud drives as virtual drives on your system. Hosting a TagStudio library on one of these mounts should function similarly to what native integration would look like.
|
||||
- Supporting native cloud integrations such as these would be an unnecessary "reinventing the wheel" burden for us that is outside the scope of this project.
|
||||
- Native ChatGPT/Claude/Gemini/_Non-Local_ LLM Integration
|
||||
- This could mean different things depending on your intentions. Whether it's trying to use an LLM to replace the native search, or to trying to use a model for image recognition, I'm not interested in hooking people's TagStudio libraries into non-local LLMs such as ChatGPT and/or turn the program into a "chatbot" interface (see: [Overall Goals/Privacy](#overall-goals)).
|
||||
- With that being said, the future TagStudio API should be well-suited to connect to any sort of service you'd like, including machine learning models if so you choose. I just won't _personally_ add any native integrations with online services.
|
||||
- Native Cloud Integration
|
||||
- There are plenty of services already (native or third-party) that allow you to mount your cloud drives as virtual drives on your system. Hosting a TagStudio library on one of these mounts should function similarly to what native integration would look like.
|
||||
- Supporting native cloud integrations such as these would be an unnecessary "reinventing the wheel" burden for us that is outside the scope of this project.
|
||||
- Native ChatGPT/Claude/Gemini/_Non-Local_ LLM Integration
|
||||
- This could mean different things depending on your intentions. Whether it's trying to use an LLM to replace the native search, or to trying to use a model for image recognition, I'm not interested in hooking people's TagStudio libraries into non-local LLMs such as ChatGPT and/or turn the program into a "chatbot" interface (see: [Overall Goals/Privacy](#overall-goals)).
|
||||
- With that being said, the future TagStudio API should be well-suited to connect to any sort of service you'd like, including machine learning models if so you choose. I just won't _personally_ add any native integrations with online services.
|
||||
|
||||
### Is a Rust port coming?
|
||||
|
||||
|
||||
@@ -41,9 +41,6 @@ SPDX-FileCopyrightText = "(c) 2026 Boxicons"
|
||||
SPDX-License-Identifier = "MIT"
|
||||
|
||||
[[annotations]]
|
||||
path = [
|
||||
"src/tagstudio/resources/qt/images/volume.svg",
|
||||
"src/tagstudio/resources/qt/images/volume_mute.svg",
|
||||
]
|
||||
path = ["src/tagstudio/resources/qt/images/volume.svg", "src/tagstudio/resources/qt/images/volume_mute.svg"]
|
||||
SPDX-FileCopyrightText = "(c) github:google/material-design-icons Contributors"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
||||
5
contrib/.vscode/launch.json
vendored
5
contrib/.vscode/launch.json
vendored
@@ -8,7 +8,10 @@
|
||||
"program": "${workspaceRoot}/src/tagstudio/main.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": ["-o", "~/Documents/Example"]
|
||||
"args": [
|
||||
"-o",
|
||||
"~/Documents/Example"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1329
docs/changelog.md
1329
docs/changelog.md
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Colors
|
||||
icon: material/palette
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
|
||||
@@ -1,150 +1,163 @@
|
||||
---
|
||||
title: Contributing
|
||||
icon: material/file-plus
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
# :material-file-plus: Contribution Guidelines
|
||||
# :material-file-plus: Contributing
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
If you wish to discuss TagStudio further, feel free to join the [Discord Server](https://discord.com/invite/hRNnVKhF2G)!
|
||||
## Getting Started
|
||||
|
||||
## :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
|
||||
- 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)!
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! 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.
|
||||
!!! 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.
|
||||
|
||||
#### Fixes
|
||||
### Contribution Checklist
|
||||
|
||||
- [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)_
|
||||
- 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!_**
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! 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.
|
||||
!!! failure "Unacceptable Code"
|
||||
The following types of code will NOT be accepted to the project:
|
||||
|
||||
## :material-thumb-down:{.red} Unacceptable Code
|
||||
- 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
|
||||
|
||||
The following types of code will **NOT** be accepted to the project:
|
||||
## Creating a Development Environment
|
||||
|
||||
- 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 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.
|
||||
|
||||
---
|
||||
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)".
|
||||
|
||||
## :material-license: Licenses
|
||||
### Brief Instructions
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! 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.
|
||||
!!! 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`.
|
||||
|
||||
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.
|
||||
### [Ruff](https://github.com/astral-sh/ruff)
|
||||
|
||||
<!-- 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 -->
|
||||
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.
|
||||
|
||||
#### Types of Files That Should Be MIT
|
||||
#### Running Locally
|
||||
|
||||
- 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
|
||||
Inside the root repository directory:
|
||||
|
||||
#### Types of Files That Should Be GPL-3.0
|
||||
- Lint code with `ruff check`
|
||||
- Some linting suggestions can be automatically formatted with `ruff check --fix`
|
||||
- Format code with `ruff format`
|
||||
|
||||
- Code for the Qt frontend
|
||||
- e.g. Qt widgets, views, controllers, Qt rendering code, Qt tests, etc.
|
||||
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).
|
||||
|
||||
---
|
||||
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/).
|
||||
|
||||
## :material-code-braces-box: Commits and Pull Requests
|
||||
### [Mypy](https://github.com/python/mypy)
|
||||
|
||||
- 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.
|
||||
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
|
||||
|
||||
- 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.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! danger "Force Pushing"
|
||||
!!! important
|
||||
**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 "PR Scope"
|
||||
!!! tip
|
||||
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
|
||||
|
||||
Code must function on all of the supported operating systems and versions:
|
||||
- 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
|
||||
|
||||
- Windows 10 & 11
|
||||
- macOS 14.0+
|
||||
- Common Linux distributions and versions
|
||||
## Documentation Guidelines
|
||||
|
||||
## :material-file-document: Documentation Guidelines
|
||||
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/).
|
||||
|
||||
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
|
||||
- Use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" for title capitalization
|
||||
|
||||
- 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 embedded media
|
||||
- Use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" for title capitalization
|
||||
|
||||
## :material-translate: Translation Guidelines
|
||||
## Translation Guidelines
|
||||
|
||||
Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Developing
|
||||
icon: material/code-braces
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -12,7 +10,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 [Contribution Guidelines](contributing.md)!
|
||||
If you wish to contribute to TagStudio's development, please read our [CONTRIBUTING.md](https://github.com/TagStudioDev/TagStudio/blob/main/CONTRIBUTING.md)!
|
||||
|
||||
## Installing Python
|
||||
|
||||
@@ -50,7 +48,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) (recommended) 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) or [Poetry 2.0](https://python-poetry.org). Alternatively you can create a virtual environment and manually install the dependencies yourself.
|
||||
|
||||
### Installing with uv
|
||||
|
||||
@@ -60,7 +58,7 @@ If using [uv](https://docs.astral.sh/uv), you can install the dependencies for T
|
||||
uv pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
TagStudio should now be runnable using the `tagstudio` command.
|
||||
A reference `.envrc` is provided for use with [direnv](#direnv), see [`contrib/.envrc-uv`](https://github.com/TagStudioDev/TagStudio/blob/main/contrib/.envrc-uv).
|
||||
|
||||
---
|
||||
|
||||
@@ -72,8 +70,6 @@ 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
|
||||
@@ -91,9 +87,10 @@ If you choose to manually set up a virtual environment and install dependencies
|
||||
```
|
||||
|
||||
2. Activate your environment:
|
||||
- Windows w/Powershell: `.venv\Scripts\Activate.ps1`
|
||||
- Windows w/Command Prompt: `.venv\Scripts\activate.bat`
|
||||
- Linux/macOS: `source .venv/bin/activate`
|
||||
|
||||
- Windows w/Powershell: `.venv\Scripts\Activate.ps1`
|
||||
- Windows w/Command Prompt: `.venv\Scripts\activate.bat`
|
||||
- Linux/macOS: `source .venv/bin/activate`
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! info "Supported Shells"
|
||||
@@ -112,12 +109,6 @@ 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:
|
||||
@@ -153,54 +144,9 @@ 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 Pyright and Ruff will check your code, catching those nits right away.
|
||||
There is a [pre-commit](https://pre-commit.com/) configuration that will run through some checks before code is committed. Namely, mypy and the Ruff linter and formatter will check your code, catching those nits right away.
|
||||
|
||||
Once you have pre-commit installed, just run:
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Entries
|
||||
icon: material/file
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -26,33 +24,33 @@ To fix file entries that have become unlinked, select the "Fix Unlinked Entries"
|
||||
|
||||
## Internal Structure
|
||||
|
||||
- `id` (`INTEGER`/`int`, `UNIQUE`, `NOT NULL`, `PRIMARY KEY`)
|
||||
- The ID for the file entry.
|
||||
- Used for guaranteed unique references.
|
||||
- `folder` (`INTEGER`/`int`, `NOT NULL`, `FOREIGN KEY`)
|
||||
- _Not currently used, may be removed._
|
||||
- `path` (`VARCHAR`/`Path`, `UNIQUE`, `NOT NULL`)
|
||||
- The filename and filepath relative to the root of the library folder.
|
||||
- (E.g. for library "Folder", path = "any_subfolders/filename.txt")
|
||||
- `suffix` (`VARCHAR`/`str`, `NOT NULL`)
|
||||
- The filename suffix with no leading dot.
|
||||
- Used for quicker file extension checks.
|
||||
- `date_created` (`DATETIME`/`Datetime`)
|
||||
- _Not currently used, will be implemented in an upcoming update._
|
||||
- The creation date of the file (not the entry).
|
||||
- Generated from `st_birthtime` on Windows and Mac, and `st_ctime` on Linux.
|
||||
- `date_modified` (`DATETIME`/`Datetime`)
|
||||
- _Not currently used, will be implemented in an upcoming update._
|
||||
- The latest modification date of the file (not the entry).
|
||||
- Generated from `st_mtime`.
|
||||
- `date_added` (`DATETIME`/`Datetime`)
|
||||
- The date the file entry was added to the TagStudio library.
|
||||
- `id` (`INTEGER`/`int`, `UNIQUE`, `NOT NULL`, `PRIMARY KEY`)
|
||||
- The ID for the file entry.
|
||||
- Used for guaranteed unique references.
|
||||
- `folder` (`INTEGER`/`int`, `NOT NULL`, `FOREIGN KEY`)
|
||||
- _Not currently used, may be removed._
|
||||
- `path` (`VARCHAR`/`Path`, `UNIQUE`, `NOT NULL`)
|
||||
- The filename and filepath relative to the root of the library folder.
|
||||
- (E.g. for library "Folder", path = "any_subfolders/filename.txt")
|
||||
- `suffix` (`VARCHAR`/`str`, `NOT NULL`)
|
||||
- The filename suffix with no leading dot.
|
||||
- Used for quicker file extension checks.
|
||||
- `date_created` (`DATETIME`/`Datetime`)
|
||||
- _Not currently used, will be implemented in an upcoming update._
|
||||
- The creation date of the file (not the entry).
|
||||
- Generated from `st_birthtime` on Windows and Mac, and `st_ctime` on Linux.
|
||||
- `date_modified` (`DATETIME`/`Datetime`)
|
||||
- _Not currently used, will be implemented in an upcoming update._
|
||||
- The latest modification date of the file (not the entry).
|
||||
- Generated from `st_mtime`.
|
||||
- `date_added` (`DATETIME`/`Datetime`)
|
||||
- The date the file entry was added to the TagStudio library.
|
||||
|
||||
### Table Relationships
|
||||
|
||||
- `tag_entries`
|
||||
- A relationship between `entry_id` to `tag_id`s from the `tags` table.
|
||||
- `text_fields`
|
||||
- (TODO: determine the relationship for `entry_id`)
|
||||
- `datetime_fields`
|
||||
- (TODO: determine the relationship for `entry_id`)
|
||||
- `tag_entries`
|
||||
- A relationship between `entry_id` to `tag_id`s from the `tags` table.
|
||||
- `text_fields`
|
||||
- (TODO: determine the relationship for `entry_id`)
|
||||
- `datetime_fields`
|
||||
- (TODO: determine the relationship for `entry_id`)
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Fields
|
||||
icon: material/text-box
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -16,16 +14,16 @@ Fields are additional types of metadata that you can attach to [file entries](./
|
||||
|
||||
A string of text, displayed as a single line.
|
||||
|
||||
- e.g: Title, Author, Artist, URL, etc.
|
||||
- e.g: Title, Author, Artist, URL, etc.
|
||||
|
||||
### Text Box
|
||||
|
||||
A long string of text displayed as a box of text.
|
||||
|
||||
- e.g: Description, Notes, etc.
|
||||
- e.g: Description, Notes, etc.
|
||||
|
||||
### Datetime
|
||||
|
||||
A date and time value.
|
||||
|
||||
- e.g: Date Published, Date Taken, etc.
|
||||
- e.g: Date Published, Date Taken, etc.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Installing FFmpeg
|
||||
icon: material/movie-open-cog
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -27,6 +25,7 @@ To Install:
|
||||
1. Download 7z or zip file and extract it (right click > Extract All)
|
||||
2. Move extracted contents to a unique folder (i.e; `c:\ffmpeg` or `c:\Program Files\ffmpeg`)
|
||||
3. Add FFmpeg to your system PATH
|
||||
|
||||
1. In Windows, search for or go to "Edit the system environment variables" under the Control Panel
|
||||
2. Under "User Variables", select "Path" then edit
|
||||
3. Click new and add `<Your folder>\bin` (e.g; `c:\ffmpeg\bin` or `c:\Program Files\ffmpeg\bin`)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
title: Ignoring Files
|
||||
icon: material/file-document-remove
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -65,7 +64,7 @@ When scanning your library directories, the `.ts_ignore` file is read by either
|
||||
|
||||
A `#` symbol at the start of a line indicates that this line is a comment, and match no items. Blank lines are used to enhance readability and also match no items.
|
||||
|
||||
- Can be escaped by putting a backslash ("`\`") in front of the `#` symbol.
|
||||
- Can be escaped by putting a backslash ("`\`") in front of the `#` symbol.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
=== "Example comment"
|
||||
@@ -107,9 +106,9 @@ A `#` symbol at the start of a line indicates that this line is a comment, and m
|
||||
|
||||
The forward slash "`/`" is used as the directory separator. Separators may occur at the beginning, middle or end of the `.ts_ignore` search pattern.
|
||||
|
||||
- If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular `.TagStudio` library folder itself. Otherwise the pattern may also match at any level below the `.TagStudio` folder level.
|
||||
- If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular `.TagStudio` library folder itself. Otherwise the pattern may also match at any level below the `.TagStudio` folder level.
|
||||
|
||||
- If there is a separator at the end of the pattern then the pattern will only match directories, otherwise the pattern can match both files and directories.
|
||||
- If there is a separator at the end of the pattern then the pattern will only match directories, otherwise the pattern can match both files and directories.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
=== "Example folder pattern"
|
||||
@@ -130,8 +129,8 @@ The forward slash "`/`" is used as the directory separator. Separators may occur
|
||||
|
||||
A `!` prefix before a pattern negates the pattern, allowing any files matched matched by previous patterns to be un-matched.
|
||||
|
||||
- Any matching file excluded by a previous pattern will become included again.
|
||||
- **It is not possible to re-include a file if a parent directory of that file is excluded.**
|
||||
- Any matching file excluded by a previous pattern will become included again.
|
||||
- **It is not possible to re-include a file if a parent directory of that file is excluded.**
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
=== "Example negation"
|
||||
@@ -205,10 +204,10 @@ The character "`?`" matches any one character except "`/`".
|
||||
|
||||
Two consecutive asterisks ("`**`") in patterns matched against full pathname may have special meaning:
|
||||
|
||||
- A leading "`**`" followed by a slash means matches in all directories.
|
||||
- A trailing "`/**`" matches everything inside.
|
||||
- A slash followed by two consecutive asterisks then a slash ("`/**/`") matches zero or more directories.
|
||||
- Other consecutive asterisks are considered regular asterisks and will match according to the previous rules.
|
||||
- A leading "`**`" followed by a slash means matches in all directories.
|
||||
- A trailing "`/**`" matches everything inside.
|
||||
- A slash followed by two consecutive asterisks then a slash ("`/**/`") matches zero or more directories.
|
||||
- Other consecutive asterisks are considered regular asterisks and will match according to the previous rules.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
=== "Leading **"
|
||||
|
||||
@@ -4,7 +4,6 @@ hide:
|
||||
- toc
|
||||
- navigation
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -39,7 +38,7 @@ hide:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-file-multiple:{ .lg .middle } **[All Files](entries.md) Welcome**
|
||||
- :material-file-multiple:{ .lg .middle } **[All Files](entries.md) Welcome**
|
||||
|
||||
***
|
||||
|
||||
@@ -47,25 +46,27 @@ hide:
|
||||
|
||||
[:material-arrow-right: See Full Preview Support](preview-support.md)
|
||||
|
||||
- :material-tag-text:{ .lg .middle } **Create [Tags](tags.md) Your Way**
|
||||
- :material-tag-text:{ .lg .middle } **Create [Tags](tags.md) Your Way**
|
||||
|
||||
***
|
||||
- :material-format-font: No character restrictions
|
||||
- :material-form-textbox: Add aliases/alternate names
|
||||
- :material-palette: Customize colors and styles
|
||||
- :material-tag-multiple: Tags can be tagged with other tags!
|
||||
- :material-star-four-points: And more!
|
||||
|
||||
- :material-magnify:{ .lg .middle } **Powerful [Search](search.md)**
|
||||
- :material-format-font: No character restrictions
|
||||
- :material-form-textbox: Add aliases/alternate names
|
||||
- :material-palette: Customize colors and styles
|
||||
- :material-tag-multiple: Tags can be tagged with other tags!
|
||||
- :material-star-four-points: And more!
|
||||
|
||||
- :material-magnify:{ .lg .middle } **Powerful [Search](search.md)**
|
||||
|
||||
***
|
||||
- Full [Boolean operator](search.md) support
|
||||
- Filenames, paths, and extensions with [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) syntax
|
||||
- General media types (e.g. "Photo", "Video", "Document")
|
||||
- Special searches (e.g. "Untagged")
|
||||
- "[Smartcase](search.md#case-sensitivity)" case sensitivity
|
||||
|
||||
- :material-text-box:{ .lg .middle } **Text and Date [Fields](fields.md)**
|
||||
- Full [Boolean operator](search.md) support
|
||||
- Filenames, paths, and extensions with [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) syntax
|
||||
- General media types (e.g. "Photo", "Video", "Document")
|
||||
- Special searches (e.g. "Untagged")
|
||||
- "[Smartcase](search.md#case-sensitivity)" case sensitivity
|
||||
|
||||
- :material-text-box:{ .lg .middle } **Text and Date [Fields](fields.md)**
|
||||
|
||||
***
|
||||
|
||||
@@ -79,7 +80,7 @@ hide:
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-scale-balance:{ .lg .middle } **Open Source**
|
||||
- :material-scale-balance:{ .lg .middle } **Open Source**
|
||||
|
||||
***
|
||||
|
||||
@@ -89,7 +90,7 @@ hide:
|
||||
|
||||
[:material-arrow-right: Roadmap to MIT Core Library License](roadmap.md#core-library-api)
|
||||
|
||||
- :material-database:{ .lg .middle } **Central Save File**
|
||||
- :material-database:{ .lg .middle } **Central Save File**
|
||||
|
||||
***
|
||||
|
||||
@@ -107,6 +108,6 @@ TagStudio aims to create an **open** and **robust** format for file tagging that
|
||||
|
||||
<div class="grid cards" markdown>
|
||||
|
||||
- :material-map-check:{ .lg .middle } See the [**Roadmap**](roadmap.md) for future features and updates
|
||||
- :material-map-check:{ .lg .middle } See the [**Roadmap**](roadmap.md) for future features and updates
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Installation
|
||||
icon: material/download
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -66,21 +64,22 @@ TagStudio can now be launched via the `tagstudio` command in your terminal.
|
||||
|
||||
Some external dependencies are required for TagStudio to execute. Below is a table of known packages that will be necessary.
|
||||
|
||||
| Package | Reason |
|
||||
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| [dbus](https://repology.org/project/dbus) | required for Qt; opening desktop applications |
|
||||
| [ffmpeg](https://repology.org/project/ffmpeg) | audio/video playback |
|
||||
| libstdc++ | required for Qt |
|
||||
| [libva](https://repology.org/project/libva) | hardware rendering with [VAAPI](https://www.freedesktop.org/wiki/Software/vaapi) |
|
||||
| [libvdpau](https://repology.org/project/libvdpau) | hardware rendering with [VDPAU](https://www.freedesktop.org/wiki/Software/VDPAU) |
|
||||
| [libx11](https://repology.org/project/libx11) | required for Qt |
|
||||
| libxcb-cursor OR [xcb-util-cursor](https://repology.org/project/xcb-util-cursor) | required for Qt |
|
||||
| [libxkbcommon](https://repology.org/project/libxkbcommon) | required for Qt |
|
||||
| [libxrandr](https://repology.org/project/libxrandr) | hardware rendering |
|
||||
| [pipewire](https://repology.org/project/pipewire) | PipeWire audio support |
|
||||
| [qt](https://repology.org/project/qt) | required |
|
||||
| [qt-multimedia](https://repology.org/project/qt) | required |
|
||||
| [qt-wayland](https://repology.org/project/qt) | Wayland support |
|
||||
<!-- prettier-ignore -->
|
||||
| Package | Reason |
|
||||
|--------------- | --------------- |
|
||||
| [dbus](https://repology.org/project/dbus) | required for Qt; opening desktop applications |
|
||||
| [ffmpeg](https://repology.org/project/ffmpeg) | audio/video playback |
|
||||
| libstdc++ | required for Qt |
|
||||
| [libva](https://repology.org/project/libva) | hardware rendering with [VAAPI](https://www.freedesktop.org/wiki/Software/vaapi) |
|
||||
| [libvdpau](https://repology.org/project/libvdpau) | hardware rendering with [VDPAU](https://www.freedesktop.org/wiki/Software/VDPAU) |
|
||||
| [libx11](https://repology.org/project/libx11) | required for Qt |
|
||||
| libxcb-cursor OR [xcb-util-cursor](https://repology.org/project/xcb-util-cursor) | required for Qt |
|
||||
| [libxkbcommon](https://repology.org/project/libxkbcommon) | required for Qt |
|
||||
| [libxrandr](https://repology.org/project/libxrandr) | hardware rendering |
|
||||
| [pipewire](https://repology.org/project/pipewire) | PipeWire audio support |
|
||||
| [qt](https://repology.org/project/qt) | required |
|
||||
| [qt-multimedia](https://repology.org/project/qt) | required |
|
||||
| [qt-wayland](https://repology.org/project/qt) | Wayland support |
|
||||
|
||||
### :material-nix: Nix(OS)
|
||||
|
||||
@@ -226,37 +225,20 @@ 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.
|
||||
- :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.
|
||||
|
||||
<!-- 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-apple: On macOS `unrar` can be installed through Homebrew's [`rar`](https://formulae.brew.sh/cask/rar) formula.
|
||||
|
||||
- :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 -->
|
||||
!!! 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 -->
|
||||
!!! 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.
|
||||
- :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.
|
||||
|
||||
### 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`.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Libraries
|
||||
icon: material/database
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Library Format
|
||||
icon: material/database-edit
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -12,7 +10,7 @@ This page outlines the various changes made to the TagStudio library save file f
|
||||
|
||||
---
|
||||
|
||||
## JSON <small>v1.0.0 - v9.4.2</small>
|
||||
## JSON
|
||||
|
||||
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.
|
||||
|
||||
@@ -28,30 +26,21 @@ Replaced by the new SQLite format introduced in TagStudio [v9.5.0 Pre-Release 1]
|
||||
|
||||
---
|
||||
|
||||
## SQLite <small>v9.5.0+</small>
|
||||
## SQLite
|
||||
|
||||
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`.
|
||||
|
||||
#### "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.
|
||||
|
||||
---
|
||||
```mermaid
|
||||
erDiagram
|
||||
versions {
|
||||
TEXT key PK "Values: ['INITIAL', 'CURRENT']"
|
||||
INTEGER value
|
||||
}
|
||||
```
|
||||
|
||||
### Versions 1 - 5
|
||||
|
||||
@@ -104,13 +93,13 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
---
|
||||
|
||||
### Versions 100 - 104
|
||||
### Versions 100 - 1xx
|
||||
|
||||
#### Version 100
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 74383e3c3c12f72be1481ab0b86c7360b95c2d85 | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [74383e3](https://github.com/TagStudioDev/TagStudio/commit/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.
|
||||
@@ -119,11 +108,11 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 101
|
||||
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
- Deprecates the `preferences` table, set to be removed in a [future](#version-104) TagStudio version.
|
||||
- Deprecates the `preferences` table, set to be removed in a future 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'`
|
||||
@@ -133,36 +122,34 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
#### Version 102
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 71d04254cf87f4200bb7ffc81656e50dfb122e4d | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [71d0425](https://github.com/TagStudioDev/TagStudio/commit/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 |
|
||||
| ---------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 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 |
|
||||
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [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 |
|
||||
|
||||
- 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 |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| ad2cbbca483018d245b44348e2c4f5a0e0bb28f1 | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| [ad2cbbc](https://github.com/TagStudioDev/TagStudio/commit/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 |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| c15e2b56eedd0a3c13391fa43571b8f8f7c7a91f | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
| Used From | Format | Location |
|
||||
| --------- | ------ | ----------------------------------------------- |
|
||||
| TBD | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
|
||||
|
||||
- Adds `text_field_templates` and `date_field_templates` tables.
|
||||
- Drops `boolean_fields` and `value_type` tables.
|
||||
@@ -174,12 +161,3 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
- Values are set to `TRUE` if the field row was previously a "TEXT_BOX" type.
|
||||
- Repairs existing "Description" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE` _(Previously done in [Version 7](#version-7))_.
|
||||
- Repairs existing "Comments" fields inside the `text_fields` table to have their `is_multiline` column set to `TRUE`.
|
||||
|
||||
#### Version 201
|
||||
|
||||
| Used From | Format | Location |
|
||||
| ---------------------------------------- | ------ | ----------------------------------------------- |
|
||||
| 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.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Tools & Macros
|
||||
icon: material/script-text
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Supported Previews
|
||||
icon: material/image-check
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -83,7 +81,7 @@ Audio thumbnails will default to embedded cover art (if any) and fallback to gen
|
||||
Preview support for office documents or well-known project file formats varies by the format and whether or not embedded thumbnails are available to be read from. OpenDocument-based files are typically supported.
|
||||
|
||||
| Filetype | Extensions | Preview Type |
|
||||
| ------------------------------------ | --------------------- | -------------------------------------------------------------------------- |
|
||||
|--------------------------------------| --------------------- | -------------------------------------------------------------------------- |
|
||||
| Blender | `.blend`, `.blend<#>` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
|
||||
| Clip Studio Paint | `.clip` | Embedded thumbnail |
|
||||
| Keynote (Apple iWork) | `.key` | Embedded thumbnail |
|
||||
@@ -105,7 +103,7 @@ Preview support for office documents or well-known project file formats varies b
|
||||
Archive thumbnails will display the first image from the archive within the Preview Panel.
|
||||
|
||||
| Filetype | Extensions |
|
||||
| -------- | -------------- |
|
||||
|----------|----------------|
|
||||
| 7-Zip | `.7z`, `.s7z` |
|
||||
| RAR | `.rar` |
|
||||
| Tar | `.tar`, `.tgz` |
|
||||
@@ -124,8 +122,6 @@ 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.
|
||||
@@ -157,7 +153,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 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.
|
||||
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.
|
||||
|
||||
[^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.
|
||||
|
||||
421
docs/roadmap.md
421
docs/roadmap.md
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Roadmap
|
||||
icon: material/map-check
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -18,60 +16,54 @@ Planned features and changes are assigned **priority levels** to signify how imp
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! info "Priority Level Icons"
|
||||
- :material-chevron-triple-up:{ .priority-high title="High Priority" } **High Priority** - Core features
|
||||
- :material-chevron-double-up:{ .priority-med title="Medium Priority" } **Medium Priority** - Important, but not necessary
|
||||
- :material-chevron-up:{ .priority-low title="Low Priority" } **Low Priority** - Just nice to have
|
||||
- :material-chevron-triple-up:{ .priority-high title="High Priority" } **High Priority** - Core features
|
||||
- :material-chevron-double-up:{ .priority-med title="Medium Priority" } **Medium Priority** - Important, but not necessary
|
||||
- :material-chevron-up:{ .priority-low title="Low Priority" } **Low Priority** - Just nice to have
|
||||
|
||||
## Version Estimates
|
||||
|
||||
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 |
|
||||
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.
|
||||
|
||||
<!-- 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.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-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" }
|
||||
- [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" }
|
||||
- [ ] 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" }
|
||||
- [ ] Word Count :material-chevron-up:{ .priority-low title="Low Priority" }
|
||||
|
||||
### :material-database-cog: Core Library + CLI
|
||||
### :material-database-cog: Core Library + API
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
- [ ] 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]**
|
||||
- [ ] 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]**
|
||||
|
||||
### :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-double-up:{ .priority-med title="Medium Priority" } **[v10.0.0]**
|
||||
- [ ] Format Specification Established :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
|
||||
|
||||
---
|
||||
|
||||
@@ -79,212 +71,215 @@ 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-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
|
||||
- [ ] 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.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
|
||||
- [x] Auto-Hiding Player Controls
|
||||
- [x] Play/Pause
|
||||
- [x] Loop
|
||||
- [x] Toggle Autoplay
|
||||
- [x] Volume Control
|
||||
- [x] Toggle Mute
|
||||
- [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
|
||||
- [x] Basic Support
|
||||
- [ ] 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.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" }
|
||||
- Would serve as an addition/alternative to the Favorite and Archived badges.
|
||||
- [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" }
|
||||
- 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
|
||||
- [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]**
|
||||
- [ ] 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
|
||||
- [x] Auto-Hiding Player Controls
|
||||
- [x] Play/Pause
|
||||
- [x] Loop
|
||||
- [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" }
|
||||
- [ ] STL File Support
|
||||
- [ ] OBJ File Support
|
||||
- [ ] Plaintext Thumbnails/Previews :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [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" }
|
||||
- [ ] 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" }
|
||||
- [ ] 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" }
|
||||
- Would serve as an addition/alternative to the Favorite and Archived badges.
|
||||
|
||||
### :material-cog: Settings
|
||||
|
||||
- [x] Application Settings
|
||||
- [x] Stored in System User Folder/Designated Folder
|
||||
- [x] Language
|
||||
- [x] Date and Time Format
|
||||
- [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" } **[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" }
|
||||
- [x] Application Settings
|
||||
- [x] Stored in System User Folder/Designated Folder
|
||||
- [x] Language
|
||||
- [x] Date and Time Format
|
||||
- [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" }
|
||||
- [ ] 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
|
||||
## [Library](libraries.md)
|
||||
|
||||
### :material-wrench: Library Mechanics
|
||||
|
||||
- [x] Per-Library Tags
|
||||
- [ ] 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 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" }
|
||||
- [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]**
|
||||
- [x] Per-Library Tags
|
||||
- [ ] Global Tags :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] 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]**
|
||||
- [ ] 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]**
|
||||
- [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]**
|
||||
|
||||
### :material-grid: Entries
|
||||
### :material-grid: [Entries](entries.md)
|
||||
|
||||
File or file-like [entries](entries.md) stored in the library.
|
||||
Library representations of files or file-like objects.
|
||||
|
||||
- [x] File Entries **[v1.0.0]**
|
||||
- [ ] 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)]**
|
||||
- [ ] 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" }
|
||||
- [ ] Ability to set sorting method for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Ability to set custom thumbnail for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] 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" }
|
||||
- [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" }
|
||||
- [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]**
|
||||
- [ ] 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" }
|
||||
- [ ] Ability to set sorting method for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] Ability to set custom thumbnail for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
|
||||
- [ ] 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
|
||||
### :material-tag-text: [Tags](tags.md)
|
||||
|
||||
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.
|
||||
|
||||
- [x] Tag Name **[v8.0.0]**
|
||||
- [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.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.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]**
|
||||
- [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.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" } **[v9.8.x]**
|
||||
- [ ] Tag Merging :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.9.x]**
|
||||
- [x] Tag Name **[v8.0.0]**
|
||||
- [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]**
|
||||
- [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]**
|
||||
- [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]**
|
||||
- [ ] 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]**
|
||||
- [ ] 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" }
|
||||
|
||||
### :material-magnify: Search
|
||||
### :material-magnify: [Search](search.md)
|
||||
|
||||
- [x] Tag Search **[v8.0.0]**
|
||||
- [x] Filename Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Glob Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Filetype Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Search by Extension (e.g. ".jpg", ".png") **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Search by media type (e.g. "image", "video", "document") **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [ ] Field Content Search :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
|
||||
- [x] [Boolean Operators](search.md) **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] `AND` Operator
|
||||
- [x] `OR` Operator
|
||||
- [x] `NOT` Operator
|
||||
- [x] Parenthesis Grouping
|
||||
- [x] Character Escaping
|
||||
- [ ] `HAS` Operator (for [Component Tags](tags.md#component-tags)) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
|
||||
- [ ] Conditional Search :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
|
||||
- [ ] Compare Dates :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Compare Durations :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Compare File Sizes :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Compare Dimensions :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [x] Smartcase Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [ ] Search Result Sorting
|
||||
- [x] Sort by Filename **[[v9.5.2](changelog.md#952-march-31st-2025)]**
|
||||
- [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 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" }
|
||||
- [x] Tag Search **[v8.0.0]**
|
||||
- [x] Filename Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Glob Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Filetype Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Search by Extension (e.g. ".jpg", ".png") **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] Search by media type (e.g. "image", "video", "document") **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [ ] Field Content Search :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
|
||||
- [x] [Boolean Operators](search.md) **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [x] `AND` Operator
|
||||
- [x] `OR` Operator
|
||||
- [x] `NOT` Operator
|
||||
- [x] Parenthesis Grouping
|
||||
- [x] Character Escaping
|
||||
- [ ] `HAS` Operator (for [Component Tags](tags.md#component-tags)) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.x]**
|
||||
- [ ] Conditional Search :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.x]**
|
||||
- [ ] Compare Dates :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Compare Durations :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Compare File Sizes :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [ ] Compare Dimensions :material-chevron-double-up:{ .priority-med title="Medium Priority" }
|
||||
- [x] Smartcase Search **[[v9.5.0](changelog.md#950-march-3rd-2025)]**
|
||||
- [ ] Search Result Sorting
|
||||
- [x] Sort by Filename **[[v9.5.2](changelog.md#952-march-31st-2025)]**
|
||||
- [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
|
||||
### :material-file-cog: [Macros](macros.md)
|
||||
|
||||
- [ ] 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.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" }
|
||||
- [ ] [...]
|
||||
- [ ] 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]**
|
||||
- [ ] 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]**
|
||||
- [ ] 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" }
|
||||
- [ ] [...]
|
||||
|
||||
### :material-table-arrow-right: Sharable Data
|
||||
|
||||
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.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.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.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-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
|
||||
- [ ] Color Packs :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.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]**
|
||||
- [ ] 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]**
|
||||
- [ ] 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]**
|
||||
- _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
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Searching
|
||||
icon: material/magnify
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -52,13 +50,13 @@ Sometimes search queries have ambiguous characters and need to be "escaped". Thi
|
||||
|
||||
#### Valid Escaped Tag Searches
|
||||
|
||||
- "Tag Name With Spaces"
|
||||
- Tag_Name_With_Spaces
|
||||
- "Tag Name With Spaces"
|
||||
- Tag_Name_With_Spaces
|
||||
|
||||
#### Invalid Escaped Tag Searches
|
||||
|
||||
- Tag Name With Spaces
|
||||
- Reason: Ambiguity between a tag named "Tag Name With Spaces" and four individual tags called "Tag", "Name", "With", "Spaces".
|
||||
- Tag Name With Spaces
|
||||
- Reason: Ambiguity between a tag named "Tag Name With Spaces" and four individual tags called "Tag", "Name", "With", "Spaces".
|
||||
|
||||
## Tags
|
||||
|
||||
@@ -88,31 +86,31 @@ Optionally, you may use [glob](<https://en.wikipedia.org/wiki/Glob_(programming)
|
||||
|
||||
Given a file "Artwork/Piece.jpg", the following searches will return results for it:
|
||||
|
||||
- `path: artwork/piece.jpg`
|
||||
- `path: Artwork/Piece.jpg`
|
||||
- `path: piece.jpg`
|
||||
- `path: Piece.jpg`
|
||||
- `path: artwork`
|
||||
- `path: rtwor`
|
||||
- `path: ece.jpg`
|
||||
- `path: iec`
|
||||
- `path: artwork/*`
|
||||
- `path: Artwork/*`
|
||||
- `path: *piece.jpg*`
|
||||
- `path: *Piece.jpg*`
|
||||
- `path: *artwork*`
|
||||
- `path: *Artwork*`
|
||||
- `path: *rtwor*`
|
||||
- `path: *ece.jpg*`
|
||||
- `path: *iec*`
|
||||
- `path: *.jpg`
|
||||
- `path: artwork/piece.jpg`
|
||||
- `path: Artwork/Piece.jpg`
|
||||
- `path: piece.jpg`
|
||||
- `path: Piece.jpg`
|
||||
- `path: artwork`
|
||||
- `path: rtwor`
|
||||
- `path: ece.jpg`
|
||||
- `path: iec`
|
||||
- `path: artwork/*`
|
||||
- `path: Artwork/*`
|
||||
- `path: *piece.jpg*`
|
||||
- `path: *Piece.jpg*`
|
||||
- `path: *artwork*`
|
||||
- `path: *Artwork*`
|
||||
- `path: *rtwor*`
|
||||
- `path: *ece.jpg*`
|
||||
- `path: *iec*`
|
||||
- `path: *.jpg`
|
||||
|
||||
While the following searches will **NOT:**
|
||||
|
||||
- `path: ARTWORK/Piece.jpg` _(Reason: Mismatched case)_
|
||||
- `path: *aRtWoRk/Piece*` _(Reason: Mismatched case)_
|
||||
- `path: PieCe.jpg` _(Reason: Mismatched case)_
|
||||
- `path: *PieCe.jpg*` _(Reason: Mismatched case)_
|
||||
- `path: ARTWORK/Piece.jpg` _(Reason: Mismatched case)_
|
||||
- `path: *aRtWoRk/Piece*` _(Reason: Mismatched case)_
|
||||
- `path: PieCe.jpg` _(Reason: Mismatched case)_
|
||||
- `path: *PieCe.jpg*` _(Reason: Mismatched case)_
|
||||
|
||||
## Special Searches
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Style Guide
|
||||
icon: material/sign-text
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -12,39 +10,18 @@ icon: material/sign-text
|
||||
|
||||
Most of the style guidelines can be checked, fixed, and enforced via Ruff. Older code may not be adhering to all of these guidelines, in which case _"do as I say, not as I do"..._
|
||||
|
||||
- Do your best to write clear, concise, and modular code.
|
||||
- This should include making methods private by default (e.g. `__method()`)
|
||||
- Methods should only be protected (e.g. `_method()`) or public (e.g. `method()`) when needed and warranted
|
||||
- Keep a maximum column width of no more than **100** characters.
|
||||
- Code comments should be used to help describe sections of code that can't speak for themselves.
|
||||
- Use [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings for any classes and functions you add.
|
||||
- If you're modifying an existing function that does _not_ have docstrings, you don't _have_ to add docstrings to it... but it would be pretty cool if you did ;)
|
||||
- Imports should be ordered alphabetically.
|
||||
- Lists of values should be ordered using their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
||||
- Some files have their methods ordered alphabetically as well (i.e. [`thumb_renderer`](https://github.com/TagStudioDev/TagStudio/blob/main/src/tagstudio/qt/widgets/thumb_renderer.py)). If you're working in a file and notice this, please try and keep to the pattern.
|
||||
- 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`](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/).
|
||||
- Do your best to write clear, concise, and modular code.
|
||||
- This should include making methods private by default (e.g. `__method()`)
|
||||
- Methods should only be protected (e.g. `_method()`) or public (e.g. `method()`) when needed and warranted
|
||||
- Keep a maximum column width of no more than **100** characters.
|
||||
- Code comments should be used to help describe sections of code that can't speak for themselves.
|
||||
- Use [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings for any classes and functions you add.
|
||||
- If you're modifying an existing function that does _not_ have docstrings, you don't _have_ to add docstrings to it... but it would be pretty cool if you did ;)
|
||||
- Imports should be ordered alphabetically.
|
||||
- Lists of values should be ordered using their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order).
|
||||
- Some files have their methods ordered alphabetically as well (i.e. [`thumb_renderer`](https://github.com/TagStudioDev/TagStudio/blob/main/src/tagstudio/qt/widgets/thumb_renderer.py)). If you're working in a file and notice this, please try and keep to the pattern.
|
||||
- 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/)!
|
||||
|
||||
## Qt
|
||||
|
||||
@@ -105,14 +82,14 @@ class MyCoolWidget(MyCoolWidgetView):
|
||||
|
||||
Observe the following key aspects of this example:
|
||||
|
||||
- The Controller is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code
|
||||
- The UI elements are in private variables
|
||||
- This enforces that the controller shouldn't directly access UI elements
|
||||
- Instead the view should provide a protected API (e.g. `_get_color()`) for things like setting/getting the value of a dropdown, etc.
|
||||
- Instead of `_get_color()` there could also be a `_color` method marked with `@property`
|
||||
- The callback methods are already defined as protected methods with NotImplementedErrors
|
||||
- Defines the interface the callbacks
|
||||
- Enforces that UI events be handled
|
||||
- The Controller is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code
|
||||
- The UI elements are in private variables
|
||||
- This enforces that the controller shouldn't directly access UI elements
|
||||
- Instead the view should provide a protected API (e.g. `_get_color()`) for things like setting/getting the value of a dropdown, etc.
|
||||
- Instead of `_get_color()` there could also be a `_color` method marked with `@property`
|
||||
- The callback methods are already defined as protected methods with NotImplementedErrors
|
||||
- Defines the interface the callbacks
|
||||
- Enforces that UI events be handled
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! tip
|
||||
|
||||
@@ -62,7 +62,11 @@
|
||||
|
||||
/* Mobile Nav Header */
|
||||
.md-nav__source {
|
||||
background: linear-gradient(60deg, rgb(205, 78, 255) 0%, rgb(116, 123, 255) 100%);
|
||||
background: linear-gradient(
|
||||
60deg,
|
||||
rgb(205, 78, 255) 0%,
|
||||
rgb(116, 123, 255) 100%
|
||||
);
|
||||
border-style: solid;
|
||||
border-width: 0 0 2px 0;
|
||||
border-color: #ffffff33;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Tags
|
||||
icon: material/tag-text
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -14,9 +12,9 @@ Tags are discrete objects that represent some attribute. This could be a person,
|
||||
|
||||
TagStudio tags do not share the same naming limitations of many other tagging solutions. The key standouts of tag names in TagStudio are:
|
||||
|
||||
- Tag names do **NOT** have to be unique
|
||||
- Tag names are **NOT** limited to specific characters
|
||||
- Tags can have **aliases**, a.k.a. alternate names to go by
|
||||
- Tag names do **NOT** have to be unique
|
||||
- Tag names are **NOT** limited to specific characters
|
||||
- Tags can have **aliases**, a.k.a. alternate names to go by
|
||||
|
||||
### Name
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
---
|
||||
title: Basic Usage
|
||||
icon: material/mouse
|
||||
---
|
||||
|
||||
<!-- SPDX-FileCopyrightText: (c) TagStudio Contributors -->
|
||||
<!-- SPDX-License-Identifier: GPL-3.0-only -->
|
||||
|
||||
@@ -38,14 +36,14 @@ Hover over the field and click the pencil icon. From there, add or edit text in
|
||||
|
||||
Create a new tag by accessing the "New Tag" option from the Edit menu or by pressing <kbd>Ctrl</kbd>+<kbd>T</kbd>. In the tag creation panel, enter a tag name, optional shorthand name, optional tag aliases, optional parent tags, and an optional color.
|
||||
|
||||
- The tag **name** is the base name of the tag. **_This does NOT have to be unique!_**
|
||||
- The tag **shorthand** is a special type of alias that displays in situations where screen space is more valuable, notably with name disambiguation.
|
||||
- **Aliases** are alternate names for a tag. These let you search for terms other than the exact tag name in order to find the tag again.
|
||||
- **Parent Tags** are tags in which this tag can substitute for in searches. In other words, tags under this section are parents of this tag.
|
||||
- Parent tags with the disambiguation check next to them will be used to help disambiguate tag names that may not be unique.
|
||||
- For example: If you had a tag for "Freddy Fazbear", you might add "Five Nights at Freddy's" as one of the parent tags. If the disambiguation box is checked next to "Five Nights at Freddy's" parent tag, then the tag "Freddy Fazbear" will display as "Freddy Fazbear (Five Nights at Freddy's)". Furthermore, if the "Five Nights at Freddy's" tag has a shorthand like "FNAF", then the "Freddy Fazbear" tag will display as "Freddy Fazbear (FNAF)".
|
||||
- The **color** option lets you select an optional color palette to use for your tag.
|
||||
- The **"Is Category"** property lets you treat this tag as a category under which itself and any child tags inheriting from it will be sorted by inside the preview panel.
|
||||
- The tag **name** is the base name of the tag. **_This does NOT have to be unique!_**
|
||||
- The tag **shorthand** is a special type of alias that displays in situations where screen space is more valuable, notably with name disambiguation.
|
||||
- **Aliases** are alternate names for a tag. These let you search for terms other than the exact tag name in order to find the tag again.
|
||||
- **Parent Tags** are tags in which this tag can substitute for in searches. In other words, tags under this section are parents of this tag.
|
||||
- Parent tags with the disambiguation check next to them will be used to help disambiguate tag names that may not be unique.
|
||||
- For example: If you had a tag for "Freddy Fazbear", you might add "Five Nights at Freddy's" as one of the parent tags. If the disambiguation box is checked next to "Five Nights at Freddy's" parent tag, then the tag "Freddy Fazbear" will display as "Freddy Fazbear (Five Nights at Freddy's)". Furthermore, if the "Five Nights at Freddy's" tag has a shorthand like "FNAF", then the "Freddy Fazbear" tag will display as "Freddy Fazbear (FNAF)".
|
||||
- The **color** option lets you select an optional color palette to use for your tag.
|
||||
- The **"Is Category"** property lets you treat this tag as a category under which itself and any child tags inheriting from it will be sorted by inside the preview panel.
|
||||
|
||||
### Tag Manager
|
||||
|
||||
|
||||
23
mkdocs.yml
23
mkdocs.yml
@@ -1,6 +1,7 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
|
||||
# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json
|
||||
|
||||
# MkDocs: https://www.mkdocs.org/
|
||||
@@ -36,9 +37,9 @@ nav:
|
||||
- install.md
|
||||
- usage.md
|
||||
- Developing:
|
||||
- developing.md
|
||||
- contributing.md
|
||||
- style.md
|
||||
- developing.md
|
||||
- contributing.md
|
||||
- style.md
|
||||
- Help:
|
||||
- help/ffmpeg.md
|
||||
- Using Libraries:
|
||||
@@ -49,7 +50,7 @@ nav:
|
||||
- ignore.md
|
||||
- macros.md
|
||||
- Fields:
|
||||
- fields.md
|
||||
- fields.md
|
||||
- Tags:
|
||||
- tags.md
|
||||
- colors.md
|
||||
@@ -57,7 +58,7 @@ nav:
|
||||
- changelog.md
|
||||
- roadmap.md
|
||||
- Schema History:
|
||||
- library-changes.md
|
||||
- library-changes.md
|
||||
|
||||
theme:
|
||||
name: material
|
||||
@@ -107,6 +108,7 @@ theme:
|
||||
upcoming: material/flask-outline
|
||||
|
||||
markdown_extensions:
|
||||
|
||||
# Python Markdown
|
||||
- abbr
|
||||
- admonition
|
||||
@@ -116,7 +118,6 @@ markdown_extensions:
|
||||
- md_in_html
|
||||
- tables
|
||||
- toc:
|
||||
title: Table of Contents
|
||||
permalink: true
|
||||
toc_depth: 3
|
||||
|
||||
@@ -134,11 +135,6 @@ 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:
|
||||
@@ -156,9 +152,8 @@ markdown_extensions:
|
||||
plugins:
|
||||
- search
|
||||
- tags
|
||||
- typeset
|
||||
- social: # social embed cards
|
||||
enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions)
|
||||
- social: # social embed cards
|
||||
enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions)
|
||||
- redirects:
|
||||
redirect_maps:
|
||||
"develop.md": "developing.md"
|
||||
|
||||
@@ -3,24 +3,13 @@
|
||||
<!-- 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">
|
||||
|
||||
<!-- Typeset title -->
|
||||
{% if toc_item.typeset %}
|
||||
<span class="md-typeset">
|
||||
{{ toc_item.typeset.title }}
|
||||
</span>
|
||||
|
||||
<!-- Regular title -->
|
||||
{% else %}
|
||||
<a href="{{ toc_item.url }}" class="md-nav__link">
|
||||
<span class="md-ellipsis">
|
||||
{{ toc_item.title }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<!-- Table of contents list -->
|
||||
{% if toc_item.children %}
|
||||
|
||||
@@ -42,17 +42,17 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = ["tagstudio[mkdocs,pyright,pre-commit,pyinstaller,pytest,ruff]"]
|
||||
mkdocs = ["mkdocs-material[imaging]>=9.7", "mkdocs-redirects~=1.2"]
|
||||
pyright = ["pyright~=1.1.409"]
|
||||
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"]
|
||||
pre-commit = ["pre-commit~=4.2"]
|
||||
pyinstaller = ["Pyinstaller~=6.13"]
|
||||
pytest = [
|
||||
"pytest==9.0.3",
|
||||
"pytest==8.3.5",
|
||||
"pytest-cov==6.1.1",
|
||||
"pytest-mock==3.15.1",
|
||||
"pytest-qt==4.4.0",
|
||||
"syrupy==5.1.0",
|
||||
"syrupy==4.9.1",
|
||||
]
|
||||
ruff = ["ruff==0.11.8"]
|
||||
|
||||
@@ -62,6 +62,31 @@ tagstudio = "tagstudio.main:main"
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/tagstudio"]
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path = ["src/tagstudio"]
|
||||
disable_error_code = [
|
||||
"annotation-unchecked",
|
||||
"func-returns-value",
|
||||
"import-untyped",
|
||||
]
|
||||
explicit_package_bases = true
|
||||
ignore_missing_imports = true
|
||||
implicit_optional = true
|
||||
strict_optional = false
|
||||
warn_unused_ignores = true
|
||||
exclude = ["build", "dist"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tagstudio.qt.main_window"
|
||||
ignore_errors = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tagstudio.qt.ui.home_ui"
|
||||
ignore_errors = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tagstudio.core.ts_core"
|
||||
ignore_errors = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
#addopts = "-m 'not qt'"
|
||||
@@ -74,20 +99,17 @@ ignore = [
|
||||
"src/tagstudio/qt/previews/vendored/pydub/",
|
||||
]
|
||||
include = ["src/tagstudio", "tests"]
|
||||
# reference for the settings here: https://github.com/microsoft/pyright/blob/main/docs/configuration.md
|
||||
reportAny = false
|
||||
reportIgnoreCommentWithoutRule = false
|
||||
reportImplicitStringConcatenation = false
|
||||
reportImportCycles = false
|
||||
reportMissingTypeArgument = false
|
||||
reportMissingTypeStubs = false
|
||||
# reportOptionalMemberAccess = false
|
||||
reportUnannotatedClassAttribute = false
|
||||
reportUnknownArgumentType = false
|
||||
reportUnknownLambdaType = false
|
||||
reportUnknownMemberType = false
|
||||
reportUnusedCallResult = false
|
||||
reportUnannotatedClassAttribute = false
|
||||
reportUninitializedInstanceVariable = false
|
||||
|
||||
[tool.ruff]
|
||||
exclude = ["home_ui.py", "resources.py", "resources_rc.py"]
|
||||
|
||||
@@ -9,7 +9,7 @@ JSON_FILENAME: str = "ts_library.json"
|
||||
|
||||
DB_VERSION_CURRENT_KEY: str = "CURRENT"
|
||||
DB_VERSION_INITIAL_KEY: str = "INITIAL"
|
||||
DB_VERSION: int = 201
|
||||
DB_VERSION: int = 200
|
||||
|
||||
TAG_CHILDREN_QUERY = text("""
|
||||
WITH RECURSIVE ChildTags AS (
|
||||
|
||||
@@ -20,19 +20,19 @@ class BaseField(Base):
|
||||
|
||||
@declared_attr
|
||||
def id(self) -> Mapped[int]:
|
||||
return mapped_column(primary_key=True, autoincrement=True, sort_order=1)
|
||||
return mapped_column(primary_key=True, autoincrement=True)
|
||||
|
||||
@declared_attr
|
||||
def name(self) -> Mapped[str]:
|
||||
return mapped_column(nullable=False, default="", sort_order=2)
|
||||
return mapped_column(nullable=False, default="")
|
||||
|
||||
@declared_attr
|
||||
def entry_id(self) -> Mapped[int]:
|
||||
return mapped_column(ForeignKey("entries.id"), sort_order=3)
|
||||
return mapped_column(ForeignKey("entries.id"))
|
||||
|
||||
@declared_attr
|
||||
def entry(self) -> Mapped[Entry]:
|
||||
return relationship(foreign_keys=[self.entry_id]) # pyright: ignore[reportArgumentType]
|
||||
return relationship(foreign_keys=[self.entry_id]) # type: ignore # pyright: ignore[reportArgumentType]
|
||||
|
||||
@property
|
||||
def class_name(self) -> str:
|
||||
@@ -47,7 +47,7 @@ class BaseField(Base):
|
||||
class TextField(BaseField):
|
||||
__tablename__ = "text_fields"
|
||||
|
||||
value: Mapped[str | None] = mapped_column(sort_order=4)
|
||||
value: Mapped[str | None]
|
||||
is_multiline: Mapped[bool] = mapped_column(nullable=False, default=False)
|
||||
|
||||
@override
|
||||
@@ -75,7 +75,7 @@ class TextField(BaseField):
|
||||
class DatetimeField(BaseField):
|
||||
__tablename__ = "datetime_fields"
|
||||
|
||||
value: Mapped[str | None] = mapped_column(sort_order=4)
|
||||
value: Mapped[str | None]
|
||||
|
||||
@override
|
||||
def __eq__(self, other: object) -> bool:
|
||||
|
||||
@@ -419,7 +419,6 @@ class Library:
|
||||
# Under -> sqlite-the-sqlite-dialect-now-uses-nullpool-for-file-based-databases
|
||||
poolclass = None if storage_path == ":memory:" else NullPool
|
||||
loaded_db_version: int = 0
|
||||
initial_db_version: int = DB_VERSION
|
||||
|
||||
logger.info(
|
||||
"[Library] Opening SQLite Library",
|
||||
@@ -431,7 +430,6 @@ class Library:
|
||||
# Don't check DB version when creating new library
|
||||
if not is_new:
|
||||
loaded_db_version = self.get_version(DB_VERSION_CURRENT_KEY)
|
||||
initial_db_version = self.get_version(DB_VERSION_INITIAL_KEY)
|
||||
|
||||
# ======================== Library Database Version Checking =======================
|
||||
# DB_VERSION 6 is the first supported SQLite DB version.
|
||||
@@ -454,7 +452,7 @@ class Library:
|
||||
),
|
||||
)
|
||||
|
||||
logger.info(f"[Library] Library DB version: {loaded_db_version}")
|
||||
logger.info(f"[Library] DB_VERSION: {loaded_db_version}")
|
||||
make_tables(self.engine)
|
||||
|
||||
if is_new:
|
||||
@@ -573,9 +571,6 @@ class Library:
|
||||
self.__apply_db104_migrations(session, library_dir)
|
||||
if loaded_db_version < 200:
|
||||
self.__apply_db200_migrations(session)
|
||||
# changes: field tables
|
||||
if initial_db_version < 200 and loaded_db_version < 201:
|
||||
self.__apply_db201_migrations(session)
|
||||
|
||||
session.execute(
|
||||
text("CREATE INDEX IF NOT EXISTS idx_tags_name_shorthand ON tags (name, shorthand)")
|
||||
@@ -593,7 +588,6 @@ class Library:
|
||||
|
||||
# Update DB_VERSION
|
||||
if loaded_db_version < DB_VERSION:
|
||||
logger.info(f"[Library] Library migrated to DB version {DB_VERSION}")
|
||||
self.set_version(DB_VERSION_CURRENT_KEY, DB_VERSION)
|
||||
|
||||
# everything is fine, set the library path
|
||||
@@ -814,6 +808,10 @@ class Library:
|
||||
session.execute(text("UPDATE datetime_fields SET name = type_key"))
|
||||
session.flush()
|
||||
|
||||
# TODO: Remove `type_key` columns from text_fields and datetime_fields tables.
|
||||
# See issue with dropping columns foreign keys in SQLite:
|
||||
# https://www.sqlite.org/lang_altertable.html#making_other_kinds_of_table_schema_changes
|
||||
|
||||
# Change `name` values to title case
|
||||
logger.info("[Library][Migration][200] Normalizing TextField names...")
|
||||
for text_field in session.execute(select(TextField)).scalars():
|
||||
@@ -865,57 +863,6 @@ class Library:
|
||||
|
||||
session.commit()
|
||||
|
||||
def __apply_db201_migrations(self, session: Session):
|
||||
"""Migrate DB to DB_VERSION 201."""
|
||||
with session:
|
||||
create_text_fields_table = text("""
|
||||
CREATE TABLE text_fields_new (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
entry_id INTEGER NOT NULL,
|
||||
value VARCHAR,
|
||||
is_multiline BOOLEAN NOT NULL,
|
||||
FOREIGN KEY(entry_id) REFERENCES entries (id)
|
||||
)
|
||||
""")
|
||||
create_datetime_fields_table = text("""
|
||||
CREATE TABLE datetime_fields_new (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR NOT NULL,
|
||||
entry_id INTEGER NOT NULL,
|
||||
value VARCHAR,
|
||||
FOREIGN KEY(entry_id) REFERENCES entries (id)
|
||||
)
|
||||
""")
|
||||
|
||||
logger.info("[Library][Migration][201] Dropping type_key from text_fields table...")
|
||||
session.execute(create_text_fields_table)
|
||||
session.flush()
|
||||
session.execute(
|
||||
text("""
|
||||
INSERT INTO text_fields_new (id, name, entry_id, value, is_multiline)
|
||||
SELECT id, name, entry_id, value, is_multiline
|
||||
FROM text_fields
|
||||
""")
|
||||
)
|
||||
session.execute(text("DROP TABLE text_fields"))
|
||||
session.execute(text("ALTER TABLE text_fields_new RENAME TO text_fields"))
|
||||
|
||||
logger.info("[Library][Migration][201] Dropping type_key from datetime_fields table...")
|
||||
session.execute(create_datetime_fields_table)
|
||||
session.flush()
|
||||
session.execute(
|
||||
text("""
|
||||
INSERT INTO datetime_fields_new (id, name, entry_id, value)
|
||||
SELECT id, name, entry_id, value
|
||||
FROM datetime_fields
|
||||
""")
|
||||
)
|
||||
session.execute(text("DROP TABLE datetime_fields"))
|
||||
session.execute(text("ALTER TABLE datetime_fields_new RENAME TO datetime_fields"))
|
||||
|
||||
session.commit()
|
||||
|
||||
@property
|
||||
def field_templates(self) -> Sequence[BaseFieldTemplate]:
|
||||
with Session(self.engine) as session:
|
||||
@@ -1010,7 +957,7 @@ class Library:
|
||||
)
|
||||
statement = statement.distinct()
|
||||
entries: ScalarResult[Entry] | list[Entry] = session.execute(statement).scalars()
|
||||
entries = entries.unique()
|
||||
entries = entries.unique() # type: ignore
|
||||
|
||||
entry_order_dict = {e_id: order for order, e_id in enumerate(entry_ids)}
|
||||
entries = sorted(entries, key=lambda e: entry_order_dict[e.id])
|
||||
@@ -1467,7 +1414,7 @@ class Library:
|
||||
if tag:
|
||||
tags.append(tag.id)
|
||||
else:
|
||||
new = session.add(Tag(name=string))
|
||||
new = session.add(Tag(name=string)) # type: ignore
|
||||
if new:
|
||||
tags.append(new.id)
|
||||
session.flush()
|
||||
|
||||
@@ -6,7 +6,7 @@ import re
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import structlog
|
||||
from sqlalchemy import ColumnElement, and_, distinct, false, func, or_, select
|
||||
from sqlalchemy import ColumnElement, and_, distinct, func, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.sql.operators import ilike_op
|
||||
|
||||
@@ -47,21 +47,21 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
|
||||
self.lib = lib
|
||||
|
||||
@override
|
||||
def visit_or_list(self, node: ORList) -> ColumnElement[bool]:
|
||||
def visit_or_list(self, node: ORList) -> ColumnElement[bool]: # type: ignore
|
||||
tag_ids, bool_expressions = self.__separate_tags(node.elements, only_single=False)
|
||||
if len(tag_ids) > 0:
|
||||
bool_expressions.append(self.__entry_has_any_tags(tag_ids))
|
||||
return or_(*bool_expressions)
|
||||
|
||||
@override
|
||||
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]:
|
||||
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]: # type: ignore
|
||||
tag_ids, bool_expressions = self.__separate_tags(node.terms, only_single=True)
|
||||
if len(tag_ids) > 0:
|
||||
bool_expressions.append(self.__entry_has_all_tags(tag_ids))
|
||||
return and_(*bool_expressions)
|
||||
|
||||
@override
|
||||
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]:
|
||||
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]: # type: ignore
|
||||
"""Returns a Boolean Expression that is true, if the Entry satisfies the constraint."""
|
||||
if len(node.properties) != 0:
|
||||
raise NotImplementedError("Properties are not implemented yet") # TODO TSQLANG
|
||||
@@ -113,11 +113,11 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
|
||||
raise NotImplementedError("This type of constraint is not implemented yet")
|
||||
|
||||
@override
|
||||
def visit_property(self, node: Property) -> ColumnElement[bool]:
|
||||
def visit_property(self, node: Property) -> ColumnElement[bool]: # type: ignore
|
||||
raise NotImplementedError("This should never be reached!")
|
||||
|
||||
@override
|
||||
def visit_not(self, node: Not) -> ColumnElement[bool]:
|
||||
def visit_not(self, node: Not) -> ColumnElement[bool]: # type: ignore
|
||||
return ~self.visit(node.child)
|
||||
|
||||
def __get_tag_ids(self, tag_name: str, include_children: bool = True) -> list[int]:
|
||||
@@ -163,9 +163,6 @@ class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
|
||||
continue
|
||||
case ConstraintType.Tag:
|
||||
ids = self.__get_tag_ids(term.value)
|
||||
if len(ids) == 0:
|
||||
bool_expressions.append(false())
|
||||
continue
|
||||
if not only_single:
|
||||
tag_ids.update(ids)
|
||||
continue
|
||||
|
||||
@@ -88,6 +88,6 @@ class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
|
||||
self.driver.library_info_window.update_cleanup()
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QtGui.QShowEvent) -> None:
|
||||
def showEvent(self, event: QtGui.QShowEvent) -> None: # type: ignore
|
||||
self.update_ignored_count()
|
||||
return super().showEvent(event)
|
||||
|
||||
@@ -46,6 +46,6 @@ class IgnoreModal(IgnoreModalView):
|
||||
Ignore.write_ignore_file(self.lib.library_dir, lines)
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QShowEvent) -> None:
|
||||
def showEvent(self, event: QShowEvent) -> None: # type: ignore
|
||||
self.__load_file()
|
||||
return super().showEvent(event)
|
||||
|
||||
@@ -162,6 +162,6 @@ class LibraryInfoWindow(LibraryInfoWindowView):
|
||||
return size
|
||||
|
||||
@override
|
||||
def showEvent(self, event: QtGui.QShowEvent):
|
||||
def showEvent(self, event: QtGui.QShowEvent): # type: ignore
|
||||
self.refresh()
|
||||
return super().showEvent(event)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import io
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, override
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import cv2
|
||||
import rawpy
|
||||
@@ -12,10 +12,6 @@ import structlog
|
||||
from PIL import Image, UnidentifiedImageError
|
||||
from PIL.Image import DecompressionBombError
|
||||
from PySide6.QtCore import QSize
|
||||
from rawpy import (
|
||||
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
|
||||
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
|
||||
)
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.media_types import MediaCategories
|
||||
@@ -54,8 +50,8 @@ class PreviewThumb(PreviewThumbView):
|
||||
stats.width = image.width
|
||||
stats.height = image.height
|
||||
except (
|
||||
LibRawIOError,
|
||||
LibRawFileUnsupportedError,
|
||||
rawpy.LibRawIOError,
|
||||
rawpy.LibRawFileUnsupportedError,
|
||||
FileNotFoundError,
|
||||
):
|
||||
pass
|
||||
@@ -148,22 +144,18 @@ class PreviewThumb(PreviewThumbView):
|
||||
self._display_image(filepath)
|
||||
return self.__get_image_stats(filepath)
|
||||
|
||||
@override
|
||||
def _open_file_action_callback(self):
|
||||
open_file(
|
||||
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
|
||||
)
|
||||
|
||||
@override
|
||||
def _open_explorer_action_callback(self):
|
||||
open_file(self.__current_file, file_manager=True)
|
||||
|
||||
@override
|
||||
def _delete_action_callback(self):
|
||||
if bool(self.__current_file):
|
||||
self.__driver.delete_files_callback(self.__current_file)
|
||||
|
||||
@override
|
||||
def _button_wrapper_callback(self):
|
||||
open_file(
|
||||
self.__current_file, windows_start_command=self.__driver.settings.windows_start_command
|
||||
|
||||
@@ -34,7 +34,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
self.__entries = entries
|
||||
|
||||
@override
|
||||
def _on_click(self, tag: Tag) -> None:
|
||||
def _on_click(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
match self.__driver.settings.tag_click_action:
|
||||
case TagClickActionOption.OPEN_EDIT:
|
||||
self._on_edit(tag)
|
||||
@@ -58,7 +58,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
)
|
||||
|
||||
@override
|
||||
def _on_remove(self, tag: Tag) -> None:
|
||||
def _on_remove(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
logger.info(
|
||||
"[TagBoxWidget] remove_tag",
|
||||
selected=self.__entries,
|
||||
@@ -70,7 +70,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
self.on_update.emit()
|
||||
|
||||
@override
|
||||
def _on_edit(self, tag: Tag) -> None:
|
||||
def _on_edit(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
build_tag_panel = BuildTagPanel(self.__driver.lib, tag=tag)
|
||||
|
||||
edit_modal = PanelModal(
|
||||
@@ -92,7 +92,7 @@ class TagBoxWidget(TagBoxWidgetView):
|
||||
edit_modal.show()
|
||||
|
||||
@override
|
||||
def _on_search(self, tag: Tag) -> None:
|
||||
def _on_search(self, tag: Tag) -> None: # type: ignore[misc]
|
||||
self.__driver.main_window.search_field.setText(f"tag_id:{tag.id}")
|
||||
self.__driver.update_browsing_state(
|
||||
BrowsingState.from_tag_id(tag.id, self.__driver.browsing_history.current)
|
||||
|
||||
@@ -7,7 +7,7 @@ from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
def wrap_line(
|
||||
text: str,
|
||||
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
||||
font: ImageFont.ImageFont,
|
||||
width: int = 256,
|
||||
draw: ImageDraw.ImageDraw | None = None,
|
||||
) -> int:
|
||||
@@ -31,7 +31,7 @@ def wrap_line(
|
||||
|
||||
def wrap_full_text(
|
||||
text: str,
|
||||
font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont,
|
||||
font: ImageFont.ImageFont,
|
||||
width: int = 256,
|
||||
draw: ImageDraw.ImageDraw | None = None,
|
||||
) -> str:
|
||||
|
||||
@@ -138,7 +138,7 @@ class ColorBoxWidget(FieldWidget):
|
||||
)
|
||||
|
||||
self.edit_modal.saved.connect(
|
||||
lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit())
|
||||
lambda: (self.lib.update_color(*build_color_panel.build_color()), self.updated.emit()) # type: ignore
|
||||
)
|
||||
self.edit_modal.show()
|
||||
|
||||
|
||||
@@ -237,7 +237,11 @@ class FieldContainers(QWidget):
|
||||
selected=self.driver.selected,
|
||||
tags=tags,
|
||||
)
|
||||
self.driver.add_tags_to_selected_callback(tags)
|
||||
self.lib.add_tags_to_entries(
|
||||
self.driver.selected,
|
||||
tag_ids=tags,
|
||||
)
|
||||
self.driver.emit_badge_signals(tags, emit_on_absent=False)
|
||||
|
||||
def write_container(self, index: int, field: BaseField, is_mixed: bool = False):
|
||||
"""Update/Create data for a FieldContainer.
|
||||
|
||||
@@ -114,7 +114,6 @@ class FileAttributes(QWidget):
|
||||
if filepath and filepath.is_file():
|
||||
created: dt
|
||||
if platform.system() == "Windows" or platform.system() == "Darwin":
|
||||
# NOTE: Accessing stat().st_birthtime causes linter checks to fail on some systems.
|
||||
created = dt.fromtimestamp(filepath.stat().st_birthtime) # type: ignore[attr-defined, unused-ignore]
|
||||
else:
|
||||
created = dt.fromtimestamp(filepath.stat().st_ctime)
|
||||
|
||||
@@ -467,12 +467,12 @@ class ItemThumb(FlowWidget):
|
||||
badge.setHidden(is_hidden)
|
||||
|
||||
@override
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
def enterEvent(self, event: QEnterEvent) -> None: # type: ignore[misc]
|
||||
self.show_check_badges(show=True)
|
||||
return super().enterEvent(event)
|
||||
|
||||
@override
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
def leaveEvent(self, event: QEvent) -> None: # type: ignore[misc]
|
||||
self.show_check_badges(show=False)
|
||||
return super().leaveEvent(event)
|
||||
|
||||
@@ -502,7 +502,7 @@ class ItemThumb(FlowWidget):
|
||||
)
|
||||
|
||||
@override
|
||||
def mouseMoveEvent(self, event: QMouseEvent) -> None:
|
||||
def mouseMoveEvent(self, event: QMouseEvent) -> None: # type: ignore[misc]
|
||||
if event.buttons() is not Qt.MouseButton.LeftButton:
|
||||
return
|
||||
|
||||
|
||||
@@ -324,7 +324,7 @@ class MediaPlayer(QGraphicsView):
|
||||
"""Manage events for the media player."""
|
||||
if (
|
||||
arg__2.type() == QEvent.Type.MouseButtonPress
|
||||
and arg__2.button() == Qt.MouseButton.LeftButton # pyright: ignore[reportAttributeAccessIssue]
|
||||
and arg__2.button() == Qt.MouseButton.LeftButton # type: ignore
|
||||
):
|
||||
if arg__1 == self.play_pause:
|
||||
self.toggle_play()
|
||||
|
||||
@@ -364,7 +364,7 @@ class JsonMigrationModal(QObject):
|
||||
iterator = FunctionIterator(self.migration_iterator)
|
||||
iterator.value.connect(
|
||||
lambda x: (
|
||||
pb.setLabelText(f"<h4>{x}</h4>"),
|
||||
pb.setLabelText(f"<h4>{x}</h4>"), # type: ignore
|
||||
self.update_sql_value_ui(show_msg_box=False)
|
||||
if x == Translations["json_migration.checking_for_parity"]
|
||||
else (),
|
||||
@@ -377,8 +377,8 @@ class JsonMigrationModal(QObject):
|
||||
r.done.connect(
|
||||
lambda: (
|
||||
self.update_sql_value_ui(show_msg_box=not skip_ui),
|
||||
pb.setMinimum(1),
|
||||
pb.setValue(1),
|
||||
pb.setMinimum(1), # type: ignore
|
||||
pb.setValue(1), # type: ignore
|
||||
# Enable the finish button
|
||||
cast(QPushButtonWrapper, self.stack[1].buttons[4]).setDisabled(False),
|
||||
)
|
||||
@@ -483,26 +483,26 @@ class JsonMigrationModal(QObject):
|
||||
|
||||
def update_json_entry_count(self, value: int):
|
||||
self.old_entry_count = value
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.entries_row, 1).widget() # type:ignore
|
||||
label.setText(self.color_value_default(value))
|
||||
|
||||
def update_json_tag_count(self, value: int):
|
||||
self.old_tag_count = value
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.old_content_layout.itemAtPosition(self.tags_row, 1).widget() # type:ignore
|
||||
label.setText(self.color_value_default(value))
|
||||
|
||||
def update_sql_value(self, row: int, value: int | bool, old_value: int | bool):
|
||||
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # type:ignore
|
||||
warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore
|
||||
label.setText(self.color_value_conditional(old_value, value))
|
||||
warning_icon.setText("" if old_value == value else self.warning)
|
||||
|
||||
def update_parity_value(self, row: int, value: bool):
|
||||
result: str = self.match_text if value else self.differ_text
|
||||
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # pyright: ignore[reportAssignmentType]
|
||||
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # pyright: ignore[reportAssignmentType]
|
||||
old_label: QLabel = self.old_content_layout.itemAtPosition(row, 1).widget() # type:ignore
|
||||
new_label: QLabel = self.new_content_layout.itemAtPosition(row, 1).widget() # type:ignore
|
||||
old_warning_icon: QLabel = self.old_content_layout.itemAtPosition(row, 2).widget() # type:ignore
|
||||
new_warning_icon: QLabel = self.new_content_layout.itemAtPosition(row, 2).widget() # type:ignore
|
||||
old_label.setText(self.color_value_conditional(self.match_text, result))
|
||||
new_label.setText(self.color_value_conditional(self.match_text, result))
|
||||
old_warning_icon.setText("" if value else self.warning)
|
||||
|
||||
@@ -218,7 +218,7 @@ class Pagination(QWidget):
|
||||
if i < index:
|
||||
if (i != 0) and i >= index - 4:
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setHidden(False)
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.start_buffer_layout.itemAt(i - start_offset).widget().setText( # type: ignore
|
||||
str(i + 1)
|
||||
)
|
||||
self._assign_click(
|
||||
@@ -237,7 +237,9 @@ class Pagination(QWidget):
|
||||
elif i > index:
|
||||
if i != page_count - 1 and i <= index + 4:
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setHidden(False)
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setText(str(i + 1)) # pyright: ignore[reportAttributeAccessIssue]
|
||||
self.end_buffer_layout.itemAt(i - end_offset).widget().setText( # type: ignore
|
||||
str(i + 1)
|
||||
)
|
||||
self._assign_click(
|
||||
cast(
|
||||
QPushButtonWrapper,
|
||||
|
||||
@@ -62,6 +62,6 @@ class ProgressWidget(QWidget):
|
||||
|
||||
r = CustomRunnable(lambda: iterator.run())
|
||||
r.done.connect(
|
||||
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks])
|
||||
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks]) # type: ignore
|
||||
)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
|
||||
@@ -59,15 +59,15 @@ class TagDatabasePanel(TagSearchPanel):
|
||||
return
|
||||
|
||||
message_box = QMessageBox(
|
||||
QMessageBox.Question, # pyright: ignore[reportAttributeAccessIssue]
|
||||
QMessageBox.Question, # type: ignore
|
||||
Translations["tag.remove"],
|
||||
Translations.format("tag.confirm_delete", tag_name=self.lib.tag_display_name(tag)),
|
||||
QMessageBox.Ok | QMessageBox.Cancel, # pyright: ignore[reportAttributeAccessIssue]
|
||||
QMessageBox.Ok | QMessageBox.Cancel, # type: ignore
|
||||
)
|
||||
|
||||
result = message_box.exec()
|
||||
|
||||
if result != QMessageBox.Ok: # pyright: ignore[reportAttributeAccessIssue]
|
||||
if result != QMessageBox.Ok: # type: ignore
|
||||
return
|
||||
|
||||
self.lib.remove_tag(tag.id)
|
||||
|
||||
@@ -43,7 +43,7 @@ from PIL import (
|
||||
UnidentifiedImageError,
|
||||
)
|
||||
from PIL.Image import DecompressionBombError
|
||||
from pillow_heif import register_heif_opener # pyright: ignore[reportUnknownVariableType]
|
||||
from pillow_heif import register_heif_opener
|
||||
from PySide6.QtCore import (
|
||||
QBuffer,
|
||||
QFile,
|
||||
@@ -58,10 +58,6 @@ from PySide6.QtCore import (
|
||||
from PySide6.QtGui import QGuiApplication, QImage, QPainter, QPixmap
|
||||
from PySide6.QtPdf import QPdfDocument, QPdfDocumentRenderOptions
|
||||
from PySide6.QtSvg import QSvgRenderer
|
||||
from rawpy import (
|
||||
LibRawFileUnsupportedError, # pyright: ignore[reportPrivateImportUsage]
|
||||
LibRawIOError, # pyright: ignore[reportPrivateImportUsage]
|
||||
)
|
||||
|
||||
from tagstudio.core.constants import (
|
||||
FONT_SAMPLE_SIZES,
|
||||
@@ -79,11 +75,9 @@ from tagstudio.qt.helpers.gradients import four_corner_gradient
|
||||
from tagstudio.qt.helpers.image_effects import replace_transparent_pixels
|
||||
from tagstudio.qt.helpers.text_wrapper import wrap_full_text
|
||||
from tagstudio.qt.models.palette import UI_COLORS, ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.previews.vendored.blender_renderer import (
|
||||
blend_thumb, # pyright: ignore[reportUnknownVariableType]
|
||||
)
|
||||
from tagstudio.qt.previews.vendored.blender_renderer import blend_thumb
|
||||
from tagstudio.qt.previews.vendored.pydub.audio_segment import (
|
||||
_AudioSegment as AudioSegment, # pyright: ignore[reportPrivateUsage]
|
||||
_AudioSegment as AudioSegment,
|
||||
)
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
|
||||
@@ -124,7 +118,7 @@ class _TarFile:
|
||||
def __init__(self, filepath: Path, mode: Literal["r"]) -> None:
|
||||
self.tar: tarfile.TarFile
|
||||
self.filepath = filepath
|
||||
self.mode: Literal["r"] = mode
|
||||
self.mode = mode
|
||||
|
||||
def namelist(self) -> list[str]:
|
||||
return self.tar.getnames()
|
||||
@@ -133,10 +127,10 @@ class _TarFile:
|
||||
return unwrap(self.tar.extractfile(name)).read()
|
||||
|
||||
def __enter__(self) -> "_TarFile":
|
||||
self.tar = tarfile.open(name=self.filepath, mode=self.mode).__enter__()
|
||||
self.tar = tarfile.open(self.filepath, self.mode).__enter__()
|
||||
return self
|
||||
|
||||
def __exit__(self, *args) -> None: # pyright: ignore[reportUnknownParameterType, reportMissingParameterType]
|
||||
def __exit__(self, *args) -> None:
|
||||
self.tar.__exit__(*args)
|
||||
|
||||
|
||||
@@ -299,7 +293,7 @@ class ThumbRenderer(QObject):
|
||||
|
||||
im: Image.Image = Image.new(
|
||||
mode="L",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="black",
|
||||
)
|
||||
draw = ImageDraw.Draw(im)
|
||||
@@ -330,7 +324,7 @@ class ThumbRenderer(QObject):
|
||||
# Highlight
|
||||
im_hl: Image.Image = Image.new(
|
||||
mode="RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#00000000",
|
||||
)
|
||||
draw = ImageDraw.Draw(im_hl)
|
||||
@@ -349,7 +343,7 @@ class ThumbRenderer(QObject):
|
||||
# Shadow
|
||||
im_sh: Image.Image = Image.new(
|
||||
mode="RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#00000000",
|
||||
)
|
||||
draw = ImageDraw.Draw(im_sh)
|
||||
@@ -394,7 +388,7 @@ class ThumbRenderer(QObject):
|
||||
# Create larger blank image based on smooth_factor
|
||||
im: Image.Image = Image.new(
|
||||
"RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#FF000000",
|
||||
)
|
||||
|
||||
@@ -402,13 +396,13 @@ class ThumbRenderer(QObject):
|
||||
bg: Image.Image
|
||||
bg = Image.new(
|
||||
"RGB",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#000000FF",
|
||||
)
|
||||
|
||||
# Use a background image if provided
|
||||
if bg_image:
|
||||
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
|
||||
bg_im = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
|
||||
bg_im = ImageEnhance.Brightness(bg_im).enhance(0.3) # Reduce the brightness
|
||||
bg.paste(bg_im)
|
||||
|
||||
@@ -417,7 +411,7 @@ class ThumbRenderer(QObject):
|
||||
bg,
|
||||
(0, 0),
|
||||
mask=self._get_mask(
|
||||
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
(pixel_ratio * smooth_factor),
|
||||
),
|
||||
)
|
||||
@@ -501,19 +495,19 @@ class ThumbRenderer(QObject):
|
||||
# Create larger blank image based on smooth_factor
|
||||
im: Image.Image = Image.new(
|
||||
"RGBA",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#00000000",
|
||||
)
|
||||
|
||||
bg: Image.Image
|
||||
# Use a background image if provided
|
||||
if bg_image:
|
||||
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # pyright: ignore[reportArgumentType]
|
||||
bg = Image.Image.resize(bg_image, size=tuple([d * smooth_factor for d in size])) # type: ignore
|
||||
# Create solid background color
|
||||
else:
|
||||
bg = Image.new(
|
||||
"RGB",
|
||||
size=tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
size=tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
color="#000000",
|
||||
)
|
||||
# Apply color overlay
|
||||
@@ -527,7 +521,7 @@ class ThumbRenderer(QObject):
|
||||
bg,
|
||||
(0, 0),
|
||||
mask=self._get_mask(
|
||||
tuple([d * smooth_factor for d in size]), # pyright: ignore[reportArgumentType]
|
||||
tuple([d * smooth_factor for d in size]), # type: ignore
|
||||
(pixel_ratio * smooth_factor),
|
||||
),
|
||||
)
|
||||
@@ -668,17 +662,17 @@ class ThumbRenderer(QObject):
|
||||
artwork = None
|
||||
if ext in [".mp3"]:
|
||||
id3_tags: id3.ID3 = id3.ID3(filepath)
|
||||
id3_covers: list = id3_tags.getall("APIC") # pyright: ignore[reportUnknownVariableType]
|
||||
id3_covers: list = id3_tags.getall("APIC")
|
||||
if id3_covers:
|
||||
artwork = Image.open(BytesIO(id3_covers[0].data))
|
||||
elif ext in [".flac"]:
|
||||
flac_tags: flac.FLAC = flac.FLAC(filepath)
|
||||
flac_covers: list = flac_tags.pictures # pyright: ignore[reportUnknownVariableType]
|
||||
flac_covers: list = flac_tags.pictures
|
||||
if flac_covers:
|
||||
artwork = Image.open(BytesIO(flac_covers[0].data))
|
||||
elif ext in [".mp4", ".m4a", ".aac"]:
|
||||
mp4_tags: mp4.MP4 = mp4.MP4(filepath)
|
||||
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportUnknownVariableType]
|
||||
mp4_covers: list | None = mp4_tags.get("covr") # pyright: ignore[reportAssignmentType]
|
||||
if mp4_covers:
|
||||
artwork = Image.open(BytesIO(mp4_covers[0]))
|
||||
if artwork:
|
||||
@@ -1094,7 +1088,7 @@ class ThumbRenderer(QObject):
|
||||
font = ImageFont.truetype(filepath, size=font_size)
|
||||
text_wrapped: str = wrap_full_text(
|
||||
FONT_SAMPLE_TEXT,
|
||||
font=font,
|
||||
font=font, # pyright: ignore[reportArgumentType]
|
||||
width=size,
|
||||
draw=draw,
|
||||
)
|
||||
@@ -1126,8 +1120,8 @@ class ThumbRenderer(QObject):
|
||||
)
|
||||
except (
|
||||
DecompressionBombError,
|
||||
LibRawIOError,
|
||||
LibRawFileUnsupportedError,
|
||||
rawpy.LibRawIOError,
|
||||
rawpy.LibRawFileUnsupportedError,
|
||||
) as e:
|
||||
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
|
||||
return im
|
||||
@@ -1143,7 +1137,6 @@ class ThumbRenderer(QObject):
|
||||
try:
|
||||
# Load the EXR data to an array and rotate the color space from BGRA -> RGBA
|
||||
raw_array = cv2.imread(str(filepath), cv2.IMREAD_UNCHANGED)
|
||||
assert raw_array is not None
|
||||
raw_array[..., :3] = raw_array[..., 2::-1]
|
||||
|
||||
# Correct the gamma of the raw array
|
||||
@@ -1216,7 +1209,7 @@ class ThumbRenderer(QObject):
|
||||
# Write the image to a buffer as png
|
||||
buffer: QBuffer = QBuffer()
|
||||
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
|
||||
q_image.save(device=buffer, format="PNG") # pyright: ignore[reportArgumentType]
|
||||
q_image.save(buffer, "PNG") # type: ignore[call-overload]
|
||||
|
||||
# Load the image from the buffer
|
||||
im = Image.new("RGB", (size, size), color="#1e1e1e")
|
||||
@@ -1265,7 +1258,7 @@ class ThumbRenderer(QObject):
|
||||
return im
|
||||
|
||||
@staticmethod
|
||||
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None: # pyright: ignore[reportUnusedParameter]
|
||||
def _model_stl_thumb(filepath: Path, size: int) -> Image.Image | None:
|
||||
"""Render a thumbnail for an STL file.
|
||||
|
||||
Args:
|
||||
@@ -1338,7 +1331,7 @@ class ThumbRenderer(QObject):
|
||||
buffer: QBuffer = QBuffer()
|
||||
buffer.open(QBuffer.OpenModeFlag.ReadWrite)
|
||||
try:
|
||||
q_image.save(buffer, "PNG") # pyright: ignore
|
||||
q_image.save(buffer, "PNG") # type: ignore # pyright: ignore
|
||||
im = Image.open(BytesIO(buffer.buffer().data()))
|
||||
finally:
|
||||
buffer.close()
|
||||
@@ -1621,7 +1614,6 @@ class ThumbRenderer(QObject):
|
||||
|
||||
def fetch_cached_image(file_name: Path):
|
||||
image: Image.Image | None = None
|
||||
assert self.driver.cache_manager is not None
|
||||
cached_path = self.driver.cache_manager.get_file_path(file_name)
|
||||
|
||||
if cached_path and cached_path.is_file():
|
||||
@@ -1884,7 +1876,6 @@ class ThumbRenderer(QObject):
|
||||
image = self._resize_image(image, (adj_size, adj_size))
|
||||
|
||||
if save_to_file and savable_media_type and image:
|
||||
assert self.driver.cache_manager is not None
|
||||
self.driver.cache_manager.save_image(image, save_to_file, mode="RGBA")
|
||||
|
||||
except (
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# SPDX-FileCopyrightText: (c) TagStudio Contributors
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
# Vendored from pydub
|
||||
# type: ignore
|
||||
|
||||
|
||||
import array
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
"""A Qt driver for TagStudio."""
|
||||
|
||||
import contextlib
|
||||
import ctypes
|
||||
import math
|
||||
import os
|
||||
@@ -193,7 +194,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
applied_theme: Theme
|
||||
|
||||
lib: Library
|
||||
cache_manager: CacheManager | None
|
||||
cache_manager: CacheManager
|
||||
|
||||
browsing_history: History[BrowsingState]
|
||||
|
||||
@@ -350,7 +351,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
if os.name == "nt":
|
||||
appid = "cyanvoxel.tagstudio.9"
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid)
|
||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(appid) # type: ignore[attr-defined,unused-ignore]
|
||||
|
||||
self.app.setApplicationName("tagstudio")
|
||||
self.app.setApplicationDisplayName("TagStudio")
|
||||
@@ -538,7 +539,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
# TODO: Move this to a settings screen.
|
||||
self.main_window.menu_bar.clear_thumb_cache_action.triggered.connect(
|
||||
lambda: unwrap(self.cache_manager).clear_cache()
|
||||
lambda: self.cache_manager.clear_cache()
|
||||
)
|
||||
|
||||
# endregion
|
||||
@@ -896,7 +897,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
selected: list[int] = self.selected
|
||||
self.main_window.thumb_layout.add_tags(selected, tag_ids)
|
||||
self.lib.add_tags_to_entries(selected, tag_ids)
|
||||
self.emit_badge_signals(tag_ids, emit_on_absent=False)
|
||||
self.emit_badge_signals(tag_ids)
|
||||
|
||||
def delete_files_callback(self, origin_path: str | Path, origin_id: int | None = None):
|
||||
"""Callback to send on or more files to the system trash.
|
||||
@@ -912,7 +913,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
origin_id(id): The entry ID associated with the widget making the call.
|
||||
"""
|
||||
entry: Entry | None = None
|
||||
pending: list[tuple[int | None, Path]] = []
|
||||
pending: list[tuple[int, Path]] = []
|
||||
deleted_count: int = 0
|
||||
|
||||
selected = self.selected
|
||||
@@ -920,13 +921,14 @@ class QtDriver(DriverMixin, QObject):
|
||||
|
||||
if len(selected) <= 1 and origin_path:
|
||||
origin_id_ = origin_id
|
||||
if origin_id_ is None:
|
||||
origin_id_ = selected[0] if len(selected) > 0 else None
|
||||
if not origin_id_:
|
||||
with contextlib.suppress(IndexError):
|
||||
origin_id_ = selected[0]
|
||||
|
||||
pending.append((origin_id_, Path(origin_path)))
|
||||
else:
|
||||
for item in selected:
|
||||
entry = unwrap(self.lib.get_entry(item))
|
||||
entry = self.lib.get_entry(item)
|
||||
filepath: Path = entry.path
|
||||
pending.append((item, filepath))
|
||||
|
||||
@@ -948,8 +950,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.status_bar.showMessage(msg)
|
||||
self.main_window.status_bar.repaint()
|
||||
|
||||
if e_id is not None:
|
||||
self.lib.remove_entries([e_id])
|
||||
self.lib.remove_entries([e_id])
|
||||
if delete_file(library_dir / f):
|
||||
deleted_count += 1
|
||||
|
||||
@@ -1100,7 +1101,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
pw.hide(),
|
||||
pw.deleteLater(),
|
||||
# refresh the library only when new items are added
|
||||
files_count and self.update_browsing_state(),
|
||||
files_count and self.update_browsing_state(), # type: ignore
|
||||
)
|
||||
)
|
||||
QThreadPool.globalInstance().start(r)
|
||||
@@ -1225,10 +1226,10 @@ class QtDriver(DriverMixin, QObject):
|
||||
for field in self.copy_buffer["fields"]:
|
||||
exists = False
|
||||
for e in existing_fields:
|
||||
if field == e:
|
||||
if field.type_key == e.type_key and field.value == e.value:
|
||||
exists = True
|
||||
if not exists:
|
||||
self.lib.add_field_to_entries(id, field=field)
|
||||
self.lib.add_field_to_entries(id, field_id=field.type_key, value=field.value)
|
||||
self.lib.add_tags_to_entries(id, self.copy_buffer["tags"])
|
||||
if len(self.selected) > 1:
|
||||
if TAG_ARCHIVED in self.copy_buffer["tags"]:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
from PySide6.QtCore import QObject, QRunnable, Signal
|
||||
|
||||
|
||||
class CustomRunnable(QRunnable, QObject): # pyright: ignore[reportUnsafeMultipleInheritance]
|
||||
class CustomRunnable(QRunnable, QObject):
|
||||
done = Signal()
|
||||
|
||||
def __init__(self, function) -> None:
|
||||
|
||||
@@ -86,7 +86,7 @@ class ThumbButton(QPushButtonWrapper):
|
||||
)
|
||||
|
||||
@override
|
||||
def paintEvent(self, arg__1: QPaintEvent) -> None:
|
||||
def paintEvent(self, arg__1: QPaintEvent) -> None: # type: ignore
|
||||
super().paintEvent(arg__1)
|
||||
if self.hovered or self.selected:
|
||||
painter = QPainter()
|
||||
@@ -127,13 +127,13 @@ class ThumbButton(QPushButtonWrapper):
|
||||
painter.end()
|
||||
|
||||
@override
|
||||
def enterEvent(self, event: QEnterEvent) -> None:
|
||||
def enterEvent(self, event: QEnterEvent) -> None: # type: ignore
|
||||
self.hovered = True
|
||||
self.repaint()
|
||||
return super().enterEvent(event)
|
||||
|
||||
@override
|
||||
def leaveEvent(self, event: QEvent) -> None:
|
||||
def leaveEvent(self, event: QEvent) -> None: # type: ignore
|
||||
self.hovered = False
|
||||
self.repaint()
|
||||
return super().leaveEvent(event)
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"about.module.found": "Encontrado",
|
||||
"about.title": "Acerca de TagStudio",
|
||||
"about.website": "Página web",
|
||||
"app.git": "Commit de Git",
|
||||
"app.pre_release": "Pre-Lanzamiento",
|
||||
"app.git": "Git Commit",
|
||||
"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,17 +71,14 @@
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
@@ -160,7 +157,6 @@
|
||||
"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:",
|
||||
@@ -275,7 +271,6 @@
|
||||
"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",
|
||||
|
||||
@@ -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": "エントリの再リンク",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"about.config_path": "Mlafuplas fu sentakuzma",
|
||||
"about.description": "TagStudio - apli per parjat mlafu au riso mit festaretol, hadafsui per anta sentakuzma au mange dekizma brukdjin made. Nil kinijena apli os mlafufal, nil mare fu flankamlafu, au na nilraz ti kawri hel parjat fu kompyu.",
|
||||
"about.description": "TagStudio zeting ke parjat mlafu au riso mit festaretol, hadafvui per anta sentakuzma au mange dekizma brukdjin made. Nil kinijena zeting os mlafufal, jamnai mare fu flanka mlafu au perpa tumam fu kompyu.",
|
||||
"about.documentation": "Mahaklarazma",
|
||||
"about.license": "Brukruuru",
|
||||
"about.module.found": "Finnajena",
|
||||
@@ -10,11 +10,11 @@
|
||||
"app.pre_release": "De-Gvir",
|
||||
"app.title": "{base_title} - Mlafuhuomi '{library_dir}'",
|
||||
"color.color_border": "Bruk minusvikti varge per flanka",
|
||||
"color.confirm_delete": "Du kestetsa varge \"{color_name}\" we?",
|
||||
"color.confirm_delete": "Du kestetsa varge \"{color_name}\"?",
|
||||
"color.delete": "Keste festaretol",
|
||||
"color.import_pack": "Nasii klaani fu varge",
|
||||
"color.name": "Namae",
|
||||
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam we? Afto zol keste ALTING ine vargetumam au sebja!",
|
||||
"color.namespace.delete.prompt": "Du kestetsa afto vargetumam? Afto zol keste alting ine vargetumam au sebja!",
|
||||
"color.namespace.delete.title": "Keste vargetumam",
|
||||
"color.new": "Neo varge",
|
||||
"color.placeholder": "Varge",
|
||||
@@ -36,61 +36,47 @@
|
||||
"edit.copy_fields": "Mverm shiruzmafal",
|
||||
"edit.paste_fields": "Nasii shiru",
|
||||
"edit.tag_manager": "Jewalt festaretol",
|
||||
"entries.duplicate.merge": "Visk blisnets shiruzmakaban",
|
||||
"entries.duplicate.merge.label": "Visk blisnets shiruzmakaban ima...",
|
||||
"entries.duplicate.refresh": "Gotova blisnets shiruzmakaban gen",
|
||||
"entries.duplicates.description": "Blisnets shiruzmakaban implajena na plus ka ein shiruzmakaban ke tsunaga na sama mlafu na shiruzmabaksu. Na visk afto, zol festa festaretol au mlafushiruzma al mverm fu mlafu kara ine ein stuur shiruzmakaban. Hej nai sama \"mverm fu mlafu\", ke mverm fu mlafu fu du, ekso TagStudio.",
|
||||
"entries.generic.refresh_alt": "&Gengotova",
|
||||
"entries.duplicate.merge": "Visk sama shiruzmakaban",
|
||||
"entries.duplicate.merge.label": "Visk sama shiruzmakaban ima...",
|
||||
"entries.duplicate.refresh": "Gotova sama shiruzmakaban gen",
|
||||
"entries.duplicates.description": "Mverm fu shiruzmakaban implajena na plus ka ein shiruzmakaban ke tsunaga na sama mlafu na shiruzmabaksu. Na visk afto, zol festa festaretol au mlafushiruzma al mverm fu mlafu kara ine ein stuur shiruzmakaban. Hej nai sama \"mverm fu mlafu\", ke mverm fu mlafu fu du, ekso TagStudio.",
|
||||
"entries.generic.remove.removing": "Keste shiruzmakaban ima",
|
||||
"entries.generic.remove.removing_count": "Keste {count} shiruzmakaban...",
|
||||
"entries.ignored.description": "Mlafu shiruzmakaban bli \"hestujena\" li antadan na mlafuhuomi de hesturuuru fu brukdjin (na '.ts_ignore' mlafu) kawaridan per nai eku sore. Hestujena mlafu long mlafuhuomi na snano sit kemuske mlafu na lasazma kesa kawari hesturuuru.",
|
||||
"entries.ignored.ignored_count": "Hestujena Shiruzmakaban: {count}",
|
||||
"entries.ignored.remove": "Keste hestujena shiruzmakaban",
|
||||
"entries.ignored.remove_alt": "Keste hestujena shiruzmakaban (&v)",
|
||||
"entries.ignored.scanning": "Taskame mlafuhuomi grun hestujena shiruzmakaban...",
|
||||
"entries.ignored.title": "Fiks hestujena shiruzmakaban",
|
||||
"entries.mirror": "&Maha melon fu",
|
||||
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban we?",
|
||||
"entries.mirror.confirmation": "Du mahatsa melon fu afto {count} shiruzmakaban?",
|
||||
"entries.mirror.label": "Maha melon fu {idx}/{total} shiruzmakaban ima...",
|
||||
"entries.mirror.title": "Maha melon fu shiruzmakaban ima",
|
||||
"entries.mirror.window_title": "Maha melon fu shiruzmakaban",
|
||||
"entries.remove.plural.confirm": "Du kestetsa afto <b>{count}</b> shiruzmakaban we? Nil mlafu na shiruzmabaksu bli kestejena.",
|
||||
"entries.remove.singular.confirm": "Du kestetsa afto shiruzmakaban long mlafuhuomi we? Nil mlafu na shiruzmabaksu bli kestejena.",
|
||||
"entries.remove.plural.confirm": "Du kestetsa afto {count} shiruzmakaban?",
|
||||
"entries.running.dialog.new_entries": "Nasii {total} neo shiruzmakaban fu mlafu ima...",
|
||||
"entries.running.dialog.title": "Nasii neo shiruzmakaban fu mlafu ima",
|
||||
"entries.tags": "Festaretol",
|
||||
"entries.unlinked.description": "Tont shiruzmakaban fu mlafuhuomi tsunagajena na mlafu ine joku mlafukaban fu du. Li mlafu tsunagajena na shiruzmakaban ugokijena os kestejena ekso TagStudio, sit sore kntsunagajena.<br><br>Tsunaganaijena shiruzmakaban deki tsunaga gen na suha per mlafukaban fu du os keste li du vil.",
|
||||
"entries.unlinked.description": "Tont shiruzmakaban fu mlafuhuomi tsunagajena na mlafu ine joku mlafukaban fu du. Li mlafu tsunagajena na shiruzmakaban ugokijena os kestejena ekso TagStudio, sit sore tsunaganaijena.<br><br>Tsunaganaijena shiruzmakaban deki tsunaga gen na suha per mlafukaban fu du os keste li du vil.",
|
||||
"entries.unlinked.relink.attempting": "Iskat ima na tsunaga gen {index}/{unlinked_count} shiruzmakaban, {fixed_count} tsunagajena gen",
|
||||
"entries.unlinked.relink.manual": "&Tsunaga gen mit hant",
|
||||
"entries.unlinked.relink.title": "Tsunaga shiruzmakaban gen",
|
||||
"entries.unlinked.remove": "Keste kntsunagajena shiruzmakaban",
|
||||
"entries.unlinked.remove_alt": "Keste kntsunagajena shiruzmakaban (&v)",
|
||||
"entries.unlinked.scanning": "Taskame mlafuhuomi ima grun kntsunagajena shiruzmakaban...",
|
||||
"entries.unlinked.scanning": "Suha mlafuhuomi ima per tsunaganaijena shiruzmakaban...",
|
||||
"entries.unlinked.search_and_relink": "&Suha &&Tsunaga gen",
|
||||
"entries.unlinked.title": "Fiks kntsunagajena shiruzmakaban",
|
||||
"entries.unlinked.unlinked_count": "Kntsunagajena shiruzmakaban: {count}",
|
||||
"entries.unlinked.title": "Fiks tsunaganaijena shiruzmakaban",
|
||||
"entries.unlinked.unlinked_count": "Tsunaganaijena shiruzmakaban: {count}",
|
||||
"ffmpeg.missing.description": "FFmpeg au/os FFprobe nai finnajena. TagStudio treng FFmpeg per mahase riso.",
|
||||
"ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}<br>{ffprobe}: {ffprobe_status}",
|
||||
"field.copy": "Mverm shiruzmafal",
|
||||
"field.edit": "Kawari shiruzmafal",
|
||||
"field.paste": "Nasii shiruzmafal",
|
||||
"field_type.datetime": "Dag-tid",
|
||||
"field_type.text": "Tekst",
|
||||
"field_type.unknown": "Fal nai shirujena",
|
||||
"file.date_added": "Dag antajena",
|
||||
"file.date_created": "Dag mahajena",
|
||||
"file.date_modified": "Dag kawarijena",
|
||||
"file.date_added": "Dag nasiijenadan",
|
||||
"file.date_created": "Dag mahajenadan",
|
||||
"file.date_modified": "Dag kawarijenadan",
|
||||
"file.dimensions": "Stuuratailasku",
|
||||
"file.duplicates.description": "TagStudio kjoka nasii shiruzma DupeGuru kara per jewalt blisnets mlafu.",
|
||||
"file.duplicates.dupeguru.advice": "Za melon mahajena, du deki bruk DupeGuru per keste nai vilena mlafu. Sit, bruk tel fu TagStudio, haisa \"Fiks kntsunagajena shiruzmakaban\" na tropos sentakutumam per keste kntsunagajena shiruzmakaban.",
|
||||
"file.duplicates.description": "TagStudio kjoka nasii shiruzma DupeGuru kara per jewalt mverm.",
|
||||
"file.duplicates.dupeguru.advice": "Za melon mahajena, du deki bruk DupeGuru per keste nai vilena mlafu. Sit, bruk tel fu TagStudio, haisa \"Fiks tsunaganaijena shiruzmakaban\" na tropos sentakutumam per keste tsunaganaijena shiruzmakaban.",
|
||||
"file.duplicates.dupeguru.file_extension": "DupeGuru mlafu (*.dupeguru)",
|
||||
"file.duplicates.dupeguru.load_file": "&Gotova mlafu fu DupeGuru",
|
||||
"file.duplicates.dupeguru.no_file": "Jamnai sentakujena mlafu fu DupeGuru",
|
||||
"file.duplicates.dupeguru.open_file": "Auki sitan mlafu fu Dupeguru",
|
||||
"file.duplicates.fix": "Fiks blisnets mlafu",
|
||||
"file.duplicates.matches": "Blisnets-atai: {count}",
|
||||
"file.duplicates.matches_uninitialized": "blisntes-atai: N/A",
|
||||
"file.duplicates.mirror.description": "Maha melon fu shiruzma shiruzmakaban kara na al blisnets fu samakaban, afto zol visk al shiruzma, men zolnai keste os mverm shiruzmafal. Afto zolnai keste joku mlafu os shiruzma.",
|
||||
"file.duplicates.dupeguru.open_file": "Auki shiruzma mlafu fu Dupeguru",
|
||||
"file.duplicates.fix": "Fiks mverm",
|
||||
"file.duplicates.matches": "Mverm-atai: {count}",
|
||||
"file.duplicates.matches_uninitialized": "Mverm-atai: N/A",
|
||||
"file.duplicates.mirror.description": "Maha melon fu shiruzma shiruzmakaban kara na al mverm fu samakaban, afto zol visk al shiruzma, men zolnai keste os mverm shiruzmafal. Afto zolnai keste joku mlafu os shiruzma.",
|
||||
"file.duplicates.mirror_entries": "&Maha melon fu shiruzmakaban",
|
||||
"file.duration": "Pitkatai",
|
||||
"file.not_found": "Mlafu nai finnajena",
|
||||
@@ -124,43 +110,33 @@
|
||||
"generic.missing": "Harnai",
|
||||
"generic.navigation.back": "Suruk",
|
||||
"generic.navigation.next": "Mirai",
|
||||
"generic.no": "Nai",
|
||||
"generic.none": "Nil",
|
||||
"generic.overwrite": "Vasu",
|
||||
"generic.overwrite_alt": "&Vasu",
|
||||
"generic.paste": "Nasii",
|
||||
"generic.recent_libraries": "Moloda mlafuhuomi",
|
||||
"generic.remove": "Keste",
|
||||
"generic.remove_alt": "&Keste",
|
||||
"generic.rename": "Haisagen",
|
||||
"generic.rename_alt": "&Haisagen",
|
||||
"generic.reset": "Gensintua",
|
||||
"generic.save": "Ufne",
|
||||
"generic.skip": "Pal",
|
||||
"generic.skip_alt": "&Pal",
|
||||
"generic.yes": "Akk",
|
||||
"home.search": "Suha",
|
||||
"home.search_entries": "Suha shiruzmakaban",
|
||||
"home.search_library": "Suha mlafuhuomi",
|
||||
"home.search_tags": "Suha festaretol",
|
||||
"home.show_hidden_entries": "Mahase furijena shiruzmakaban",
|
||||
"home.thumbnail_size": "Stuuratai fu risonen",
|
||||
"home.thumbnail_size.extra_large": "Plusstuur risonen",
|
||||
"home.thumbnail_size.large": "Stuur risonen",
|
||||
"home.thumbnail_size.medium": "Mellan risonen",
|
||||
"home.thumbnail_size.mini": "Chisai risonen",
|
||||
"home.thumbnail_size.small": "Chisaidai risonen",
|
||||
"ignore.open_file": "Mahase \"{ts_ignore}\" mlafu na shiruzmabaksu",
|
||||
"json_migration.checking_for_parity": "Taskama per chigauzma ima...",
|
||||
"json_migration.creating_database_tables": "Maha tumam na SQL Database ima...",
|
||||
"json_migration.description": "<br>Hadji au de se shiruzma fu",
|
||||
"json_migration.discrepancies_found": "Chiguazma finnajena na mlafuhuomi",
|
||||
"json_migration.discrepancies_found.description": "Chiguazma finnajena mellan ranja au kjannos. Bitte gense au sentaku per beng na dlabdel os yamete.",
|
||||
"json_migration.finish_migration": "Owari dlabdel",
|
||||
"json_migration.heading.aliases": "Andrnamae:",
|
||||
"json_migration.heading.colors": "Varge:",
|
||||
"json_migration.heading.differ": "Tchigauzma",
|
||||
"json_migration.heading.extensions": "Andr ilo:",
|
||||
"json_migration.heading.match": "Finnajena sama",
|
||||
"json_migration.heading.names": "Namae:",
|
||||
"json_migration.heading.parent_tags": "Atama festaretol:",
|
||||
@@ -180,22 +156,13 @@
|
||||
"library.field.remove": "Keste shiruzmafal",
|
||||
"library.missing": "Mlafuplas fu mlafuhuomi nai finnajenadan",
|
||||
"library.name": "Mlafuhuomi",
|
||||
"library.refresh.scanning.plural": "Taskame mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning.singular": "Taskame mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning_preparing": "Taskame mlafukaban fu neo mlafu ima...\nGotova ima...",
|
||||
"library.refresh.scanning.plural": "Taskama mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning.singular": "Taskama mlafukaban fu neo mlafu ima...\n{searched_count} mlafu suhajenadan, {found_count} neo mlafu finnajenadan",
|
||||
"library.refresh.scanning_preparing": "Taskama mlafukaban fu neo mlafu ima...\nGotova ima...",
|
||||
"library.refresh.title": "Gengotova al mlafukaban",
|
||||
"library.scan_library.title": "Taskame mlafuhuomi ima",
|
||||
"library_info.cleanup": "Parjat",
|
||||
"library_info.cleanup.backups": "Mverm long mlafuhuomi:",
|
||||
"library_info.cleanup.dupe_files": "Blisnets mlafu:",
|
||||
"library_info.cleanup.ignored": "Hestujena shiruzmakaban:",
|
||||
"library_info.cleanup.legacy_json": "Gammel verso fu mlafuhuomi:",
|
||||
"library_info.cleanup.unlinked": "Kntsunagajena shiruzmakaban:",
|
||||
"library_info.stats.colors": "Varge fu festaretol:",
|
||||
"library.scan_library.title": "Taskama mlafuhuomi ima",
|
||||
"library_info.stats.entries": "Shiruzmakaban:",
|
||||
"library_info.stats.fields": "Shiruzmafal:",
|
||||
"library_info.stats.macros": "Aplinen:",
|
||||
"library_info.stats.namespaces": "Tumam:",
|
||||
"library_info.stats.tags": "Festaretol:",
|
||||
"library_object.name": "Namae",
|
||||
"library_object.name_required": "Namae (Trengjena)",
|
||||
@@ -223,16 +190,15 @@
|
||||
"menu.file.save_library": "Ufne mlafuhuomi",
|
||||
"menu.help": "&Aputsa",
|
||||
"menu.help.about": "Shiruplus",
|
||||
"menu.macros": "&Aplinen",
|
||||
"menu.macros": "&Zetingnen",
|
||||
"menu.macros.folders_to_tags": "Mlafukaban festaretol made",
|
||||
"menu.select": "Sentaku",
|
||||
"menu.settings": "Sentakuzma...",
|
||||
"menu.tools": "&Tropos",
|
||||
"menu.tools.fix_duplicate_files": "Fiks &blisnets mlafu",
|
||||
"menu.tools.fix_duplicate_files": "Fiks sama &mlafu",
|
||||
"menu.tools.fix_unlinked_entries": "Fiks &tsunagajenanai shiruzmakaban",
|
||||
"menu.view": "&Anse",
|
||||
"menu.window": "Ekrannen",
|
||||
"namespace.create.description_color": "Varge fu festaretol bruk tumam na vargekaban. Al neo varge mus long tumamklaani na eins.",
|
||||
"namespace.create.title": "Maha vargetumam",
|
||||
"namespace.new.button": "Neo vargetumam",
|
||||
"namespace.new.prompt": "Maha neo vargetumam per nasii varge fu sebja!",
|
||||
@@ -256,17 +222,17 @@
|
||||
"settings.show_filenames_in_grid": "Mahase namae fu mlafu ine karta",
|
||||
"settings.show_recent_libraries": "Mahase moloda mlafuhuomi",
|
||||
"settings.theme.dark": "Kirai",
|
||||
"settings.theme.label": "Ekran klea:",
|
||||
"settings.theme.label": "Klea fu zeting:",
|
||||
"settings.theme.light": "Kirkas",
|
||||
"settings.theme.system": "Kompyu",
|
||||
"settings.title": "Sentakuzma",
|
||||
"sorting.direction.ascending": "Plus made",
|
||||
"sorting.direction.descending": "Minus made",
|
||||
"sorting.direction.ascending": "Leste owaris",
|
||||
"sorting.direction.descending": "Leste eins",
|
||||
"splash.opening_library": "Auki mlafuhuomi \"{library_path}\" ima...",
|
||||
"status.deleted_file_plural": "Kestejenadan {count} mlafu!",
|
||||
"status.deleted_file_singular": "Kestejenadan 1 mlafu!",
|
||||
"status.deleted_none": "Nil mlafu kestejenadan.",
|
||||
"status.deleted_partial_warning": "Mono kestejenadan {count} mlafu! Bitte taskamatsa li du dekinai finna mlafu os ke ima brukjena na andr apli.",
|
||||
"status.deleted_partial_warning": "Mono kestejenadan {count} mlafu! Bitte taskamatsa li du dekinai finna mlafu os ke ima brukjena na andr zeting.",
|
||||
"status.deleting_file": "Keste mlafu [{i}/{count}]: \"{path}\" ima...",
|
||||
"status.library_closing": "Kini mlafuhuomi ima...",
|
||||
"status.library_save_success": "Mlafuhuomi ufnejenadan au kinijenadan!",
|
||||
@@ -284,7 +250,7 @@
|
||||
"tag.all_tags": "Al festaretol",
|
||||
"tag.choose_color": "Sentaku varge fu festaretol",
|
||||
"tag.color": "Varge",
|
||||
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\" we?",
|
||||
"tag.confirm_delete": "Du kestetsa festaretol \"{tag_name}\"?",
|
||||
"tag.create": "Maha festaretol",
|
||||
"tag.create_add": "Maha && Nasii \"{query}\"",
|
||||
"tag.disambiguation.tooltip": "Bruk afto festaretol grun plusklar",
|
||||
@@ -306,8 +272,8 @@
|
||||
"trash.context.singular": "Ugoki mlafu {trash_term} made",
|
||||
"trash.dialog.disambiguation_warning.plural": "Afto zol keste tuo TagStudio kara <i>AU</i> kompyu fu du kara!",
|
||||
"trash.dialog.disambiguation_warning.singular": "Afto zol keste sore TagStudio kara <i>AU</i> kompyu fu du kara!",
|
||||
"trash.dialog.move.confirmation.plural": "Du ugokitsa afto {count} mlafu {trash_term} made we?",
|
||||
"trash.dialog.move.confirmation.singular": "Du ugokitsa afto mlafu {trash_term} made we?",
|
||||
"trash.dialog.move.confirmation.plural": "Du ugokitsa afto {count} mlafu {trash_term} made?",
|
||||
"trash.dialog.move.confirmation.singular": "Du ugokitsa afto mlafu {trash_term} made?",
|
||||
"trash.dialog.permanent_delete_warning": "<b>VIKTI!</b> Li afto mlafu nai dekijena ugoki {trash_term} made, sore zol <b>kestejena!!</b>",
|
||||
"trash.dialog.title.plural": "Keste mlafu",
|
||||
"trash.dialog.title.singular": "Keste mlafu",
|
||||
|
||||
@@ -73,9 +73,6 @@
|
||||
"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",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
5
tests/fixtures/sidecar_newgrounds.json
vendored
5
tests/fixtures/sidecar_newgrounds.json
vendored
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"tags": ["ng_tag", "ng_tag2"],
|
||||
"tags": [
|
||||
"ng_tag",
|
||||
"ng_tag2"
|
||||
],
|
||||
"date": "2024-01-02",
|
||||
"description": "NG description",
|
||||
"user": "NG artist",
|
||||
|
||||
@@ -29,7 +29,7 @@ def test_tag_widget_actions_replaced_correctly(qtbot: QtBot, qt_driver: QtDriver
|
||||
# Set the widget
|
||||
tags = library.tags
|
||||
panel.set_tag_widget(tags[0], 0)
|
||||
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget() # pyright: ignore[reportAssignmentType]
|
||||
tag_widget: TagWidget = panel.scroll_layout.itemAt(0).widget()
|
||||
|
||||
should_replace_actions = {
|
||||
tag_widget: ["on_edit()", "on_remove()"],
|
||||
|
||||
@@ -30,7 +30,6 @@ EMPTY_LIBRARIES = "empty_libraries"
|
||||
# str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_102")),
|
||||
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_103")),
|
||||
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_200")),
|
||||
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_201")),
|
||||
],
|
||||
)
|
||||
def test_library_migrations(path: str):
|
||||
|
||||
Reference in New Issue
Block a user