Compare commits

..

308 Commits

Author SHA1 Message Date
Travis Abendshien
7c5b8e51e6 chore: bump version to v9.5.2 2025-03-31 15:39:46 -07:00
Weblate (bot)
efb4c4a6ed translations: update Hungarian, French, Toki Pona (#891)
* Translated using Weblate (Hungarian)

Currently translated at 100.0% (312 of 312 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (309 of 309 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (312 of 312 strings)

Translated using Weblate (French)

Currently translated at 100.0% (309 of 309 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 80.9% (250 of 309 strings)

Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
2025-03-31 15:33:17 -07:00
Travis Abendshien
f88200f38e fix(ui): seamlessly loop videos (#902) 2025-03-31 15:28:08 -07:00
VasigaranAndAngel
7f7d861800 refactor: fix type hints and overrides in flowlayout.py (#880)
* refactor and fixes

* type annotations and parameter name changes

* Update src/tagstudio/qt/flowlayout.py

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>

* Update src/tagstudio/qt/flowlayout.py

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>

* ruff format

---------

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>
2025-03-31 11:51:36 -07:00
Tony
cccd858078 fix(ui): remove media player border (#900) 2025-03-31 11:45:26 -07:00
VasigaranAndAngel
46996d50e5 refactor: fix various missing and broken type hints (#901)
* fix type hints

* text_wrapper.py type fix and minor improvements

* fields.py fixes
2025-03-31 11:14:15 -07:00
Travis Abendshien
1939f118f1 ci: add reportUnknownLambdaType = false to pyproject.toml 2025-03-31 09:49:32 -07:00
Travis Abendshien
3b5a9605d1 fix: use proper not check against MatLike type
Fixes video thumbnails not rendering.
2025-03-31 02:13:06 -07:00
Travis Abendshien
7dd1905b6e translations: add Japanese to settings, catch FileNotFound error 2025-03-30 23:37:22 -07:00
Travis Abendshien
9c24272caf settings: update default settings values 2025-03-30 21:28:09 -07:00
Travis Abendshien
d7d7e21d13 feat(ui): add more default icons and file type equivalencies (#882)
* feat(ui): expand file and thumbnail support

* feat: add iwork and powerpoint thumb support

Note: a lot of the zip-based code is becoming duplicated - this should be consolidated in the future.

* fix: remove decompression bomb check and catch others

* feat: add .aiff file equivalencies

* ui: update database icon

* feat: add .effect and .shader to shader set

* fix: correct malformed or missing media types

* feat: add misc code/plaintext types to media types

* fix: catch BadZipFile error for iWork thumbs

* chore: add type hints to thumb_renderer dicts

* refactor: change most internal render methods to static
2025-03-30 19:24:10 -07:00
Travis Abendshien
33e6bc180d ui: recent libraries list improvements (#881)
* ui: improve missing library message

* ui: update recent library max to 10
2025-03-30 19:23:52 -07:00
csponge
13afb0f664 feat(ui): merge media controls (#805)
* feat: merge media controls.

Initial commit to merge audio/video files. There are
still a few bugs around widget sizing that need fixing.

* fix: center widgets in preview area

Add widgets to a sublayout to allow for centering
in a QStackedLayout.

Remove references to the legacy video player in
the thumb preview.

* fix: resolve commit suggestions.

Subclass QSlider to handle click events
and allow for easier seeking.

Implement context menu along with autoplay
setting for the media widget.

Pause video when media player is clicked
instead of opening file.

* fix: start media muted

Start video/audio muted on initial load of
the media player.

Remove code causing mypy issue.

Add new method for getting slider click state.

* refactor: use layouts instead of manual positioning.

Add various layouts for positioning widgets instead
of manually moving widgets.

Change the volume slider orientation at smaller
media sizes.

* fix: color position label white

Fix position label color to white so it stays visible
regardless of theme.

* fix: allow dragging slider after click

* Apply suggestions from code review

fix: apply suggestions from code review.

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>

* fix: remove references to legacy video player.

Combine the stats logic for video/audio into one method.

Fix several issues after incorrectly implementing suggestions.

* fix: add loop setting and other actions.

* refactor: simplify widget state management.

Make a single method to control widget state.

Works with the main QStackLayout and cleans up
widget state if it is needed (i.e., stopping the media
player when switching to a different preview).

* fix: add pages to QStackLayout to fix widget position.

Fixes a regression in commit 4c6934. We need the pages
to properly center the widgets in the QStackLayout.

* fix: ensure media_player doesn't exceed maximum size if thumbnail.

Fix and issue where the media_player would expand past the
thumbnail on resize.

* refactor: move settings to new system
2025-03-30 19:23:24 -07:00
Leonard2
27fb54ed65 build: update spec file to use proper pathex and datas paths (#895)
* build: add "src" to pathex.

Instead of having to pre-install tagstudio, this will have PyInstaller directly build it from source.

* build: be more selective about the data files.

Since PyInstaller builds from source now there is no need to manually add it.

---------

Co-authored-by: Léonard <Leeooonaard@gmail.com>
2025-03-30 17:34:01 -07:00
Jann Stute
e3b2eaf96a perf: improve responsiveness of GIF entries (#894)
* feat: create QMovie with file path instead of copying image around

* fix: make ignore pyright specific so mypy doesn't complain

* fix: restore ability to delete file while viewing it
2025-03-30 17:21:46 -07:00
Jann Stute
33dd330c73 fix: close pdf file object in thumb renderer (#893) 2025-03-30 16:36:04 -07:00
Emmanuel Ferdman
1c02e75dd9 docs: update ThumbRenderer source (#896)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2025-03-30 09:18:37 -07:00
Xarvex
6400236823 fix(nix/package): add missing dependencies from #859
Adds toml and pydantic as Python packages, and ignore tests that require
mutating files.

Fixes: #890
2025-03-26 17:26:17 -05:00
Travis Abendshien
a4e61f9387 fix: remove unescaped ampersand from "about.description" (#885) 2025-03-25 15:04:45 -07:00
Jann Stute
adb996e1d2 feat: new settings menu + settings backend (#859)
* feat: add tab widget

* refactor: move languages dict to translations.py

* refactor: move build of Settings Modal to SettingsPanel class

* feat: hide title label

* feat: global settings class

* fix: initialise settings

* fix: properly store grid files changes

* fix: placeholder text for library settings

* feat: add ui elements for remaining global settings

* feat: add page size setting

* fix: version mismatch between pydantic and typing_extensions

* fix: update test_driver.py

* fix(test_file_path_options): replace patch with change of settings

* feat: setting for dark mode

* fix: only show restart_label when necessary

* fix: change modal from "done" type to "Save/Cancel" type

* feat: add test for GlobalSettings

* docs: mark roadmap item as completed

* fix(test_filepath_setting): Mock the app field of QtDriver

* Update src/tagstudio/main.py

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>

* fix: address review suggestions

* fix: page size setting

* feat: change dark mode option to theme dropdown

* fix: test was expecting wrong behaviour

* fix: test was testing for correct behaviour, fix behaviour instead

* fix: test fr fr

* fix: tests fr fr fr

* fix: tests fr fr fr fr

* fix: update test

* fix: tests fr fr fr fr fr

* fix: select all was selecting hidden entries

* fix: create more thumbitems as necessary
2025-03-25 15:02:53 -07:00
Weblate (bot)
e112788466 translations: update Hungarian, Spanish, French, Toki Pona (#884)
* Translated using Weblate (Hungarian)

Currently translated at 100.0% (302 of 302 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 100.0% (302 of 302 strings)

Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (302 of 302 strings)

Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 75.8% (229 of 302 strings)

Translated using Weblate (Toki Pona)

Currently translated at 75.9% (227 of 299 strings)

Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
2025-03-25 15:01:01 -07:00
Travis Abendshien
64dc88afa9 fix(ui): display 0 frame webp files in preview panel 2025-03-22 12:09:31 -07:00
VasigaranAndAngel
477173661a refactor: type hints and improvements in file_opener.py (#876)
* type fixes and minor improvements

* remove `# noqa: N802`
2025-03-21 08:30:25 -07:00
Travis Abendshien
9d60c78adc ci: update pyright rules
- Ignore "reportIgnoreCommentWithoutRule"
- Ignore "reportMissingTypeArgument"
2025-03-20 01:15:45 -07:00
Travis Abendshien
5b5e878a69 fix: use UNION instead of UNION ALL (#877) 2025-03-20 01:09:46 -07:00
Travis Abendshien
d1eb7d646e fix: hide mnemonics on macOS (#856) 2025-03-20 01:09:23 -07:00
Travis Abendshien
880ca07a6f fix: stop ffmpeg cmd windows, refactor ffmpeg_checker (#855)
* fix: remove log statement as it is redundant (#840)

* refactor: rework ffmpeg_checker.py

Move backend logic from ffmpeg_checker.py to vendored/ffmpeg.py, add translation strings for ffmpeg_checker, update vendored/ffmpeg.py

* fix: stop ffmpeg cmd windows, fix version outputs

* chore: ensure stdout is cast to str

---------

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>
2025-03-20 01:09:02 -07:00
Weblate (bot)
0701a45e75 translations: add Japanese, Viossa; update others (#861)
* Translated using Weblate (Russian)

Currently translated at 89.9% (269 of 299 strings)

Co-authored-by: Dott-rus <antonamelin8@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/
Translation: TagStudio/Strings

* Translated using Weblate (Japanese)

Currently translated at 52.8% (158 of 299 strings)

Added translation using Weblate (Japanese)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: needledetector <kenshinzumi.mail@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ja/
Translation: TagStudio/Strings

* Translated using Weblate (Czech)

Currently translated at 15.7% (47 of 299 strings)

Translated using Weblate (Czech)

Currently translated at 14.3% (43 of 299 strings)

Co-authored-by: Filip Jaruška <filip.jaruska@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/cs/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (299 of 299 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 100.0% (299 of 299 strings)

Translated using Weblate (Spanish)

Currently translated at 96.9% (290 of 299 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (299 of 299 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 67.8% (203 of 299 strings)

Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

* Translated using Weblate (Viossa)

Currently translated at 21.0% (63 of 299 strings)

Translated using Weblate (Viossa)

Currently translated at 21.0% (63 of 299 strings)

Added translation using Weblate (Viossa)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/qpv/
Translation: TagStudio/Strings

---------

Co-authored-by: Dott-rus <antonamelin8@gmail.com>
Co-authored-by: needledetector <kenshinzumi.mail@gmail.com>
Co-authored-by: Filip Jaruška <filip.jaruska@gmail.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
2025-03-20 01:07:58 -07:00
Gawi
35f409089a docs: fix typos and grammar (#879) 2025-03-20 00:43:34 -07:00
Xarvex
010a4524d6 fix(flake): remove pinned input, only consume in Nix shell (#872) 2025-03-17 18:36:06 -07:00
Xarvex
861df898e2 feat(resources): provide desktop file (#870)
* feat(resources): provide desktop file

Co-authored-by: Florian Zier <9168602+zierf@users.noreply.github.com>

* fix(ts_qt): remove duplicate logic

* fix(ts_qt): add fallback values

---------

Co-authored-by: Florian Zier <9168602+zierf@users.noreply.github.com>
2025-03-17 18:35:45 -07:00
Xarvex
0ac06a125a fix(ui): do not set palette for Linux-like systems that offer theming (#869) 2025-03-17 18:34:04 -07:00
Xarvex
b8ee63ef73 fix(nix/package): account for GTK platform (#868) 2025-03-17 18:33:33 -07:00
Xarvex
a5e535ba78 feat(ci): development tooling refresh and split documentation (#867)
* feat(nix/shell): use uv for faster evaluation

* feat(contrib): define developer configurations

* feat(ci): configure pre-commit to use project dependencies, add mypy

* fix(docs): typo

* docs: split develop and install, document integrations

* nit(contrib): add shellcheck directive to envrc's

* docs: move third-party dependencies to install page

* nit(flake): use pythonPackages variable

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-03-17 18:32:08 -07:00
Travis Abendshien
ed6ac246f4 docs: update schema_changes.md for DB_VERSION 9 2025-03-12 17:40:07 -07:00
Jann Stute
31833245a4 feat: add filename and path sorting (#842)
* feat: add filename sorting to dropdown

* feat: add file path sorting to dropdown

* feat: implement path sorting

* feat: add filename column and bump db version

* feat: implement filename sorting

* doc: tick off roadmap item for filename sorting

* fix: use existing filename translation instead

* fix: populate Entry.filename in constructor

* fix: add missing assertion in search_library fixture

* fix: update search test library

* feat: add db migration test

* fix: add missing library for test
2025-03-12 17:28:42 -07:00
Weblate (bot)
93dcfdd51c translations: update Turkish, Portuguese(BR), Hungarian, French (#857)
* Translated using Weblate (Turkish)

Currently translated at 100.0% (294 of 294 strings)

Co-authored-by: Nyghl <hknimre@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/
Translation: TagStudio/Strings

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 89.1% (262 of 294 strings)

Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (294 of 294 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (294 of 294 strings)

Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

---------

Co-authored-by: Nyghl <hknimre@gmail.com>
Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
2025-03-12 17:27:53 -07:00
HermanKassler
f680ecb648 feat: add setting to not display full filepath (#841)
* feat: add show file path option to settings menu (#4)

* feat: implement file path option for file attributes (#10)

* feat: implement file path option for file attributes

---------

Co-authored-by: Zarko Sesto <sesto@kth.se>

* feat: update ui after changing file path setting (#9)

* feat: implement file path options for main window (#11)

Co-authored-by: Zarko Sesto <sesto@kth.se>

* feat: add realtime feedback for setting changes (#13)

Co-authored-by: Zarko Sesto <sesto@kth.se>

style: formatted code changes with ruff  (#16)

* tests: Added tests to test file path display and settings menu

enhancement: formated using RUFF

test: addeded test to check title bar update

* fix: move check for file option to correct spot in open_library (#17)

* fix: change translate_with_setter to new method (#18)

* fix: update call to translator to be inline with upstream (#19)

* refactor: update some imports to reflect refactor on upstream (#20)

* refactor: update import paths to reflect recent reformat

* format: run ruff format

* translation: add translations for filepath setting

* refactor: store filepath options in integer enum

---------

Co-authored-by: RubenSocha <40490633+RubenSocha@users.noreply.github.com>
Co-authored-by: ErzaDuNord <102242407+ErzaDuNord@users.noreply.github.com>
Co-authored-by: Zarko Sesto <sesto@kth.se>
Co-authored-by: BitGatito <usmanbinmujeeb@gmail.com>
2025-03-12 15:51:55 -07:00
Travis Abendshien
234ddec78b refactor: remove unused ui files
Remove unused UI files (Port #605)

Co-Authored-By: VasigaranAndAngel <72515046+VasigaranAndAngel@users.noreply.github.com>
2025-03-12 03:35:35 -07:00
Travis Abendshien
9858ad1078 translations: sort en.json file 2025-03-10 19:06:41 -07:00
Travis Abendshien
b0047b2065 refactor: split translation keys for about screen (#845)
* fix(translations): remove errant `<b>` tag

* refactor: split translation keys for about screen

* ui: change form field style

* fix: split new translation keys

* fix: remove unused key

* translations: re-split toki pona "about.content"
2025-03-10 19:02:34 -07:00
Weblate (bot)
039cebddae translations: update Turkish, Danish, Spanish, French, Toki Pona (#852)
* Translated using Weblate (Turkish)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Nyghl <hknimre@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/
Translation: TagStudio/Strings

* Translated using Weblate (Danish)

Currently translated at 4.4% (13 of 289 strings)

Co-authored-by: Emma J. M <em@juulsgaard.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 63.3% (183 of 289 strings)

Translated using Weblate (Toki Pona)

Currently translated at 57.4% (166 of 289 strings)

Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Nyghl <hknimre@gmail.com>
Co-authored-by: Emma J. M <em@juulsgaard.org>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
2025-03-10 18:47:31 -07:00
Jann Stute
e308e8e80d fix: log all problems in translations test (#839) 2025-03-10 18:41:40 -07:00
Travis Abendshien
8c90f21c13 fix: catch NotImplementedError for Float16 JPEG-XL files (#849) 2025-03-10 00:58:09 -07:00
Travis Abendshien
e0fb73117f ci: exclude "build" and "dist" folders from mypy 2025-03-09 23:28:56 -07:00
Travis Abendshien
55d4f1882b docs: add horizontal rules to install instructions 2025-03-09 23:18:04 -07:00
Travis Abendshien
301157df00 fix(docs): remove incorrect format flags 2025-03-09 22:38:39 -07:00
Weblate (bot)
7c48cdefab translations: update translations (#846)
* Translated using Weblate (Turkish)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nyghl <hknimre@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/
Translation: TagStudio/Strings

* Translated using Weblate (Filipino)

Currently translated at 59.5% (172 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: searinminecraft <kitakita@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fil/
Translation: TagStudio/Strings

* Translated using Weblate (Tamil)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (Tamil)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/
Translation: TagStudio/Strings

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 88.5% (256 of 289 strings)

Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/
Translation: TagStudio/Strings

* Translated using Weblate (German)

Currently translated at 99.3% (287 of 289 strings)

Translated using Weblate (German)

Currently translated at 98.6% (285 of 289 strings)

Co-authored-by: DontBlameMe99 <github@addymem.anonaddy.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings

* Translated using Weblate (Russian)

Currently translated at 90.3% (261 of 289 strings)

Translated using Weblate (Russian)

Currently translated at 82.3% (238 of 289 strings)

Translated using Weblate (Russian)

Currently translated at 79.2% (229 of 289 strings)

Translated using Weblate (Russian)

Currently translated at 78.8% (228 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: werdei <6pe3ep1lu@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 95.8% (277 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 49.4% (143 of 289 strings)

Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Nyghl <hknimre@gmail.com>
Co-authored-by: searinminecraft <kitakita@disroot.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: DontBlameMe99 <github@addymem.anonaddy.com>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Co-authored-by: werdei <6pe3ep1lu@mozmail.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
2025-03-09 22:22:06 -07:00
Xarvex
42577b792c Merge pull request #844 from TagStudioDev/layout-refactor-1
refactor!: restructure project to use proper src-layout
2025-03-09 23:25:13 -05:00
Xarvex
55bc7aac88 refactor!: change layout; import and build change
Fixes: #200
Fixes: #365
Fixes: #512
Fixes: #800

fix(pyproject): resolve mix-up of mypy and pytest

chore(ci): remove legacy scripts

chore: format with new mypy rules; fix translation test

wip(ci/mypy): remove config flag

fix(pyinstaller): use correct dict access

fix(resources): usage in ts_qt.py

feat(nix/package): validate tests with pytest hook

fix(nix/package): remove old dependency patch

feat(nix): support Darwin

fix(nix/package): move check deps to checkInputs

fix(nix/shell): typo

fix(nix/shell): correctly wrap Python with Qt args

fix(pyproject): specify mypy-extensions

feat(nix/package): provide pillow-jxl-plugin

nix(nix/package): split into multiple files, allow overriding of JXL and vtf2img

fix(nix/shell): provide FFmpeg on runtime

feat(flake): output pillow-jxl-plugin and vtf2img

fix(nix/package): load pipewire

feat(nix/package): run tests on pillow-jxl-plugin

fix: remove extra noqa comment

docs: update installation docs

docs: shrink table size on docs site

nit(nix/package): pipewire not needed in buildInputs

docs: update commands, environment, setup

fix: use consistent possessives

chore: format with prettier, add ignore flags

fix(pyinstaller): consume from pyproject

Revert "fix(pyinstaller): consume from pyproject"

This reverts commit 398cd4e5630a3e83d22d15286d7ac59b4c07c5d6.

refactor: use icon from resource manager

Also fixes incorrect path currently used in ts_qt.py.

nix(pyinstaller): replace use of sys.platform with platform.system

docs: add build section

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-03-09 18:55:32 -05:00
Xarvex
226d18e743 refactor!: change layout; renaming, import and build change incoming 2025-03-09 18:54:25 -05:00
Travis Abendshien
981cc60135 chore: bump version to v9.5.1 2025-03-06 16:32:19 -08:00
Travis Abendshien
2073a284fe fix: separate about screen title from translations (#836)
* fix: separate about screen title from translations

* chore: format with ruff's evil twin
2025-03-06 16:22:54 -08:00
Jann Stute
6d1ff90355 test: add tests for translations (#833)
* refactor: simplify pathlib imports

* feat: add tests for invalid format keys and missing / unnecessary translations

* feat: parametrise format key test

* feat: parametrise completeness test

* fix: include more useful information in the error message

* refactor: remove unused method

* refactor: extract translation loading to a separate function

* fix: include more useful information in the error message

* fix: don't fail test when language hasn't been completely translated
2025-03-06 15:28:48 -08:00
Weblate (bot)
4fe76f13fd fix(translations): fix invalid placeholders (#835)
* Translated using Weblate (Turkish)

Currently translated at 24.9% (72 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/
Translation: TagStudio/Strings

* Translated using Weblate (Filipino)

Currently translated at 44.6% (129 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fil/
Translation: TagStudio/Strings

* Translated using Weblate (Tamil)

Currently translated at 24.9% (72 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/
Translation: TagStudio/Strings

* Translated using Weblate (Danish)

Currently translated at 3.8% (11 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 96.1% (278 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 17.9% (52 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 36.3% (105 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-03-06 15:22:57 -08:00
Weblate (bot)
6690f5bc09 translations: update Portuguese(BR), German, Russian, Spanish, French; fix formatting keys (#826)
* Translated using Weblate (Turkish)

Currently translated at 22.4% (65 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/
Translation: TagStudio/Strings

* Translated using Weblate (Filipino)

Currently translated at 43.9% (127 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fil/
Translation: TagStudio/Strings

* Translated using Weblate (Tamil)

Currently translated at 22.4% (65 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/
Translation: TagStudio/Strings

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 88.9% (257 of 289 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 88.9% (257 of 289 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 64.7% (187 of 289 strings)

Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/
Translation: TagStudio/Strings

* Translated using Weblate (German)

Currently translated at 98.9% (286 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Theasacraft <spam@accounts.samuelbellmann.de>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings

* Translated using Weblate (Danish)

Currently translated at 3.8% (11 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/
Translation: TagStudio/Strings

* Translated using Weblate (Russian)

Currently translated at 73.3% (212 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: werdei <6pe3ep1lu@mozmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/
Translation: TagStudio/Strings

* Translated using Weblate (Polish)

Currently translated at 97.5% (282 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pl/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 95.8% (277 of 289 strings)

Translated using Weblate (Spanish)

Currently translated at 95.8% (277 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Aless <dev@aless.ch>
Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Swedish)

Currently translated at 25.2% (73 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/
Translation: TagStudio/Strings

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 68.8% (199 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/zh_Hant/
Translation: TagStudio/Strings

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 16.9% (49 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 33.2% (96 of 289 strings)

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: Theasacraft <spam@accounts.samuelbellmann.de>
Co-authored-by: werdei <6pe3ep1lu@mozmail.com>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Aless <dev@aless.ch>
Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
2025-03-06 14:02:21 -08:00
Travis Abendshien
7e75087b24 fix: restore translate_formatted() method as format() (#830)
* fix: restore `translate_formatted()` method

* fix: set "Create & Add" button text

* refactor: rename "translate_formatted" to "format"

* translations: replace invalid format key names with "{unknown_key}"
2025-03-06 13:05:56 -08:00
Paula Rumeu
07b7d40926 docs: fix category typo (#834) 2025-03-06 03:54:44 -08:00
Travis Abendshien
80aab1ec96 chore: tweak pull request template 2025-03-04 14:27:40 -08:00
Travis Abendshien
bcf3b2f96b fix: prevent future library versions from being opened 2025-03-03 15:02:35 -08:00
Travis Abendshien
d94befed5d Merge branch 'main' of https://github.com/TagStudioDev/TagStudio 2025-03-03 14:18:25 -08:00
Travis Abendshien
d91ee5dbf7 chore: bump version to v9.5.0 2025-03-03 14:16:44 -08:00
Travis Abendshien
ea8d074c51 chore: remove leftover log statement 2025-03-03 14:16:34 -08:00
Travis Abendshien
186c6e2139 ui: change default tag color to white 2025-03-03 14:15:10 -08:00
Xarvex
c695f1765f chore(ci): add PR template (#824) 2025-03-03 14:14:46 -06:00
Travis Abendshien
077f91af88 ui: don't show the deprecated "Autofill" macro 2025-03-03 11:39:32 -08:00
Travis Abendshien
accf50ccf9 ui: change default sorting direction to descending 2025-03-03 11:32:41 -08:00
Travis Abendshien
9f9a5ccf44 fix: catch OverflowError when displaying video duration 2025-03-03 11:26:07 -08:00
Weblate (bot)
ee23ef3451 translations: update Filipino, Portuguese(BR), German, Hungarian, Polish, Spanish, French (#809)
* Translated using Weblate (Filipino)

Currently translated at 43.9% (127 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: searinminecraft <114207889+searinminecraft@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fil/
Translation: TagStudio/Strings

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 64.7% (187 of 289 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 63.3% (183 of 289 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 49.4% (143 of 289 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 49.4% (143 of 289 strings)

Co-authored-by: Alexander Lennart Formiga Johnsson <alexander_formiga@hotmail.com>
Co-authored-by: DaviMarquezeli <Davi.Marquezeli@gmail.com>
Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/
Translation: TagStudio/Strings

* Translated using Weblate (German)

Currently translated at 98.9% (286 of 289 strings)

Translated using Weblate (German)

Currently translated at 98.9% (286 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: JoeJoeTV <hannes1033@gmail.com>
Co-authored-by: Kurt S <mail@kurtys.de>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Polish)

Currently translated at 97.5% (282 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pl/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 57.7% (167 of 289 strings)

Translated using Weblate (Spanish)

Currently translated at 55.0% (159 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Translated using Weblate (French)

Currently translated at 100.0% (289 of 289 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Obscaeris <translation@obscaeris.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

---------

Co-authored-by: searinminecraft <114207889+searinminecraft@users.noreply.github.com>
Co-authored-by: Alexander Lennart Formiga Johnsson <alexander_formiga@hotmail.com>
Co-authored-by: DaviMarquezeli <Davi.Marquezeli@gmail.com>
Co-authored-by: Helder Lima <vinicius-helder@hotmail.com>
Co-authored-by: JoeJoeTV <hannes1033@gmail.com>
Co-authored-by: Kurt S <mail@kurtys.de>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Obscaeris <translation@obscaeris.com>
2025-03-03 11:17:33 -08:00
Jann Stute
2a787592b6 fix: resouce leak in translate_with_setter by deleting the offending code (#817)
* feat: implement first class for translated widget

* feat: add further translated widget types

* refactor: replace usages of translate_qobject with appropriate classes

* refactor: remove wrapper classes

* refactor: remove unnecessary used of Translations.formatted

* refactor: remove further unnecessary format calls

* refactor: replace calls for Translations.formatted with calls to str.format and remove some unused code

* refactor: remove TranslatedString class

* refactor: replace translate_with_setter with direct calls to the setter
2025-03-03 11:13:49 -08:00
Travis Abendshien
152c2b20ba docs: add "sort by filename" to roadmap 2025-03-02 22:13:00 -08:00
Travis Abendshien
1b5b40792c Create FUNDING.yml 2025-03-02 21:58:42 -08:00
SkeleyM
4b381e78ba docs: fix slight spelling error (#820) 2025-02-27 14:23:05 -08:00
Travis Abendshien
b1126d5313 fix: open libraries from v9.5.0-pr1 in newer versions (#815)
* ui: show more informative library error messages

* tests: add sqlite db migration tests

* tests: fix and refactor migration tests

* fix: apply db8 schema changes before repairing db6

* docs: add save file format change log

* chore: remove db version explanations from docstrings
2025-02-24 15:14:54 -08:00
Travis Abendshien
f9ca743b64 docs: add custom color info 2025-02-20 15:14:51 -08:00
Travis Abendshien
04bd1bc027 fix(docs): restore broken links, ignore callout formatting 2025-02-20 14:24:12 -08:00
Haider Ali
ecc0bb57fb docs: change "Tag Manager" to "Manage Tags" (#806) 2025-02-17 20:42:48 -08:00
Travis Abendshien
d11b514bab fix: use remove() instead of difference() for self collision checks 2025-02-17 15:39:43 -08:00
Travis Abendshien
fe94d84b94 ci: ignore specific pyright flags 2025-02-17 14:52:59 -08:00
Travis Abendshien
db408f09c4 chore: bump version to v9.5.0-pr4 2025-02-17 14:47:22 -08:00
Travis Abendshien
61b9fcf764 feat(ui): add language setting (#803)
* feat(ui): add language setting

* translations: implement remaining todos

* ui: show language names in settings instead of codes

* translations: add Dutch setting, anticipating #798
2025-02-17 14:45:30 -08:00
Travis Abendshien
28de21ade7 feat(ui)!: user-created tag colors (#801)
* feat: custom tag colors

* ui: minor ui polish

* ui: add confirmation for deleting colors

* ui: match tag_color_preview focused style

* ui: reduce spacing between color swatch groups

* ui!: change default behavior of secondary color

The secondary color now acts as only the text color by default, with the new `color_border` bool serving to optionally restore the previous text + colored border behavior.

* ui: adjust focused tag/color button styles

* fix: avoid namespace collision

* fix: make reserved namespace check case-insensitive

* ui: add namespace description + prompt

* fix: don't reset tag color if none are chosen

* refactor(ui): use form layout for build_color

* fix(ui): dynamically scale field title widget

* feat(ui): add additional tag shade colors

Add "burgundy", "dark-teal", and "dark_lavender" tag colors.

* fix: don't check for self in collision checks

* fix: update tag references on color update

* fix(ui): stop fields widgets expanding indefinitely
2025-02-17 14:36:34 -08:00
Weblate (bot)
2173d1d4f4 translations: add Dutch; update Hungarian, Spanish, French (#798)
* Translated using Weblate (Hungarian)

Currently translated at 100.0% (259 of 259 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Dutch)

Currently translated at 40.1% (104 of 259 strings)

Added translation using Weblate (Dutch)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Pheubel <luca.guagliardo@live.nl>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nl/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 51.7% (134 of 259 strings)

Translated using Weblate (Spanish)

Currently translated at 51.3% (133 of 259 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Co-authored-by: noceno <pasitobajet@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (French)

Currently translated at 100.0% (259 of 259 strings)

Translated using Weblate (French)

Currently translated at 100.0% (259 of 259 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Obscaeris <translation@obscaeris.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

---------

Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Pheubel <luca.guagliardo@live.nl>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Co-authored-by: noceno <pasitobajet@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Obscaeris <translation@obscaeris.com>
2025-02-17 14:33:21 -08:00
SkeleyM
c1ec8a6650 feat(ui): clickable links in about section (#799) 2025-02-14 09:32:00 -08:00
Travis Abendshien
69d3a6ed09 docs: update library search 2025-02-14 09:25:57 -08:00
Travis Abendshien
e481ab64c9 docs: consolidate feature roadmap 2025-02-14 08:42:44 -08:00
Travis Abendshien
986ccabc81 chore: bump version to v9.5.0-pr3 2025-02-10 11:22:00 -08:00
Travis Abendshien
297fdf22e8 feat: add smartcase and globless path searches (#743)
* fix: return path_strings in session

* feat: add smartcase and globless path search

Known issues: failing tests, sluggish autocomplete

* fix: all operational searches

* fix: limit path autocomplete to 100 items

* tests: add test cases
2025-02-10 10:26:02 -08:00
Weblate (bot)
319ef9a5fe translations: update Danish, Hungarian (#785)
* Translated using Weblate (Danish)

Currently translated at 4.6% (11 of 237 strings)

Co-authored-by: Jumle <juliusv135@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (237 of 237 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (236 of 236 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

---------

Co-authored-by: Jumle <juliusv135@gmail.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
2025-02-10 10:23:58 -08:00
Travis Abendshien
6b646f8955 perf: optimize query methods and reduce preview panel updates (#794)
* fix(ui): don't update preview for non-selected badge toggles

* ui: optimize badge updating

NOTE: In theory this approach opens the doors for a visual state mismatch between the tags shown and the tags on the actual entry. The performance increase comes from the fact that `get_entry_full()` isn't called, which is the real bottleneck here.

* perf: optimize query methods
2025-02-09 18:33:47 -08:00
Travis Abendshien
a2b9237be4 fix(ui): expand usage of esc for closing modals (#793)
* fix(ui): expand usage of esc for closing modals

* chore: remove log statements

* refactor: use Qt enum in place of magic number

* ui: use enter key to save panel widgets
2025-02-09 18:33:29 -08:00
ChloeVZ
abc7cc3915 docs: improve python and venv setup instructions in CONTRIBUTING.md (#791)
* docs: refer to pyenv in CONTRIBUTING.md

This commit was made in order to account for users who may already have
a Python installation that isn't 3.12.

A good real world scenario where this may be a problem is users running
on rolling release Linux distributions, such as Arch Linux which
provides bleeding edge updates in a weekly basis.

Continuing with Arch Linux as an example, it provides Python 3.13, which
the current version of TagStudio doesn't seem to work with, an issue I
encountered myself this morning.

In some systems like Windows, this may not matter, but it is an issue
nonetheless, and users using a package manager centric system can't be
expected to downgrade/upgrade their Python version for TagStudio, as
this may cause issues in their system.

The easiest way to solve this is with pyenv, which enables users to
install other Python versions independent from each other, aside from
the system install, and allows specifying a version to use for a
directory (And its subdirectories), the current terminal session, or the
entire system if desired.

Here is a short, technical and objective summary of the changes:
- Updated `.gitignore` to to ignore `.python-version` which pyenv uses.
- Updated CONTRIBUTING.md to refer to pyenv for versioning issues.
    - pyenv is listed as a prerequisite, specifying in parenthesis that it's only for when your Python install isn't 3.12.
    - The "Creating a Python Virtual Environment" section now has an additional step at the beginning, to check that the current Python installation is 3.12.
        - As part of this added step, there are steps to use pyenv to install Python 3.12 separately from the existing Python installation, and activate it for the TagStudio root folder, in case it's not the appropriate version.
    - The numbering of the other steps was offset accordingly.
    - The "Manually Launching (Outside of an IDE)" section now refers to the above first step, should the user encounter some kind of error.

* docs: refer to alternate shells in CONTRIBUTING.md

Some users may not be using the default shell for their system, and
Linux users are the most likely to do this, though this can be seen in
macOS as well. People will often use shells like fish which is not POSIX
compliant but works very well and has great auto-completion, it's very
user friendly.

The reason why I care about this is that the instructions specify to use
.venv/bin/activate which is a bash script and will not run on fish or
csh, and if you take a look at the script, indeed it says it will only
run with bash.

Python will provide alternate scripts for other shells, and at least on
my system (CachyOS), those are scripts for Powershell, bash, fish and
csh. People using these alternate shells, who don't know this may be
confused when running the script referenced in CONTRIBUTING.md and
seeing it fail completely.

The only real change in this commit was adding under the "Linux/macOS"
command in step 3 (step 2 prior to my last commit) of the "Creating a
Python Virtual Environment" section, a short instruction to switch to
the default shell, or use the appropriate script for the user's
preferred shell if any (Since, at least on my system, there aren't
scripts for every shell, for example there is no activate.zsh file).

* fix: modal windows now have the Qt.Dialog flag

Set the Qt.Dialog flag for modal windows, such as "Add Field" so that
they will be seen as dialogs in the backend. This change is unlikely to
be at all noticeable in systems like Windows, but for users running
tiling window managers like bspwm or Sway, this will prevent these modal
windows from being tiled alongside the main window, instead they will be
floating on top, which is the expected behaviour seen on floating window
managers, like the ones used by Windows and macOS.

Added self.setWindowFlag(Qt.Dialog, on=True) # type: ignore to the
following files:
- tagstudio/src/qt/modals/add_field.py
- tagstudio/src/qt/modals/delete_unlinked.py
- tagstudio/src/qt/modals/drop_import.py
- tagstudio/src/qt/modals/file_extension.py
- tagstudio/src/qt/modals/fix_dupes.py
- tagstudio/src/qt/modals/fix_unlinked.py
- tagstudio/src/qt/modals/folders_to_tags.py
- tagstudio/src/qt/modals/mirror_entities.py
- tagstudio/src/qt/widgets/paged_panel/paged_panel.py
- tagstudio/src/qt/widgets/panel.py
- tagstudio/src/qt/widgets/progress.py

Note that without adding # type: ignore, MyPy *will* give out an error,
presumably because PySide6 is missing type hints for several things, and
this may *or* may not be a justifiable use of # type: ignore, which
throws the MyPy type checking for that line out the window.

Ref: #392, #464

* fix: I forgot to run ruff format

* revert: changes to docs and gitignore

This revert is for moving the referenced changes to their own pull request.

Refs: 15f4f38, a25ef8c

* docs: other shells and pyenv in CONTRIBUTING.md

This commit changes CONTRIBUTING.md to refer to pyenv in the case of
issues with Python versions, as well as downloading the correct version
off of the Python website alternatively. It also now elaborates on the
process of running the Python virtual environment on Linux and macOS, by
referencing activation scripts for alternative shells such as fish and
CSH.

The table added to CONTRIBUTING.md is taken from the official Python
documentation: https://docs.python.org/3.12/library/venv.html

* revert: accidental inclution of #464

Refs: a51550f, 5854ccc
2025-02-06 21:41:01 -08:00
Travis Abendshien
a3df70bb8d feat: port file trashing (#409) to v9.5 (#792)
* feat: port file trashing (#409) to sql

* translations: translate file deletion actions

* fix: rename method from refactor conflict

* refactor: implement feedback
2025-02-05 19:25:10 -08:00
Travis Abendshien
466af1e6a6 refactor(ui): recycle tag list in TagSearchPanel (#788)
* feat(ui): recycle tag list in `TagSearchPanel`

* chore: address mypy warnings

* fix: order results from sql before limiting

* fix(ui): check for self.exclude before remaking sets

* fix(ui): only init tag manager and file ext manager once

* fix(ui:): remove redundant tag search panel updates

* update code comments and docstrings

* feat(ui): add tag view limit dropdown

* ensure disconnection of file_extension_panel.saved
2025-02-05 19:15:28 -08:00
Travis Abendshien
26d3b1908b fix(ui): hide library actions when no library is open (#787) 2025-02-04 13:53:03 -08:00
Travis Abendshien
f38a79b06e chore: bump version to v9.5.0-pr2 2025-02-03 16:22:02 -08:00
Travis Abendshien
dbf7353bdf fix(ui): improve tagging ux (#784)
* fix(ui): always reset tag search panel when opened

* feat: return parent tags in tag search

Known issue: this bypasses the tag_limit

* refactor: use consistant `datetime` imports

* refactor: sort by base tag name to improve performance

* fix: escape `&` when displaying tag names

* ui: show "create and add" tag with other results

* fix: optimize and fix tag result sorting

* feat(ui): allow tags in list to be selected and added by keyboard

* ui: use `esc` to reset search focus and/or close modal

* fix(ui): add pressed+focus styling to "create tag" button

* ui: use `esc` key to close `PanelWidget`

* ui: move disambiguation button to right side

* ui: expand clickable area of "-" tag button, improve styling

* ui: add "Ctrl+M" shortcut to open tag manager

* fix(ui): show "add tags" window title when accessing from home
2025-02-03 16:15:40 -08:00
Travis Abendshien
480328b83b fix: patch incorrect description type & invalid disambiguation_id refs (#782)
* fix: update DESCRIPTION to the TEXT_BOX type

* fix: remove `disambiguation_id` refs with a tag is deleted
2025-02-03 15:32:11 -08:00
Travis Abendshien
6a54323307 refactor: wrap migration_iterator lambda in a try/except block (#773) 2025-02-03 15:31:43 -08:00
Travis Abendshien
f48b363383 fix: catch ParsingError (#779)
* fix: move `path_strings` var inside `with` block

* refactor: move lambda to local function

* fix: catch `ParsingError` and return no results

* refactor: move `ParsingError` handling out to `QtDriver`

Reverts changes made to `test_search.py` and `enums.py`.
2025-02-03 15:31:12 -08:00
Travis Abendshien
634e1c7fe9 chore: update pyproject.toml 2025-02-02 23:29:53 -08:00
Travis Abendshien
90a826d128 fix(ui): reduce field title width to make room for edit and delete buttons 2025-02-02 17:13:08 -08:00
SkeleyM
93fc28e092 fix: allow tag names with colons in search (#765)
* Refactor allowing colons

* fix formatting
2025-02-02 15:56:50 -08:00
SkeleyM
80c7e81e69 fix: save all tag attributes from "Create & Add" modal (#762) 2025-02-02 13:49:17 -08:00
SkeleyM
f212e2393a fix(docs): fix screenshot sometimes not rendering (#775)
* Fix image not showing up

* formatting
2025-02-02 13:44:34 -08:00
Travis Abendshien
d7958892b7 docs: add more links to index.md 2025-02-01 14:09:17 -08:00
Travis Abendshien
5be7dfc314 docs: add library_search page 2025-02-01 14:06:41 -08:00
Travis Abendshien
6e402ac34d docs: add note about glob searching in the readme 2025-01-31 23:45:31 -08:00
Travis Abendshien
9bdbafa40c docs: add information about "tag manager" 2025-01-31 23:42:37 -08:00
Travis Abendshien
2215403201 fix: don't wrap field names too early 2025-01-31 23:40:47 -08:00
pinhead
16ebd89196 docs: fix typo for "category" in usage.md (#760) 2025-01-31 21:46:30 -08:00
Travis Abendshien
f5ff4d78c1 docs: update field and library pages 2025-01-31 17:23:33 -08:00
Travis Abendshien
df04d49770 translations: fix typo in json_migration.info.description 2025-01-31 16:39:27 -08:00
Travis Abendshien
a2f9685bc0 fix: catch ImportError for pillow_jxl module 2025-01-31 16:30:06 -08:00
Travis Abendshien
880c8ef45f chore: bump version to v9.5.0 Pre-Release 1; update docs (#751)
* chore: change version branch to "Pre-Release 1"

* docs: update docstring for JSON `open_library()`

Update method docstring with exact starting and ending versions now that they exist.

* docs: update README, CONTRIBUTING.md, & index.md

* docs: update roadmap

* docs: fix typo in README, change callout

* docs: update docs site with v9.5 features

* docs: remove warning from index.md
2025-01-31 15:43:47 -08:00
Travis Abendshien
1b3b32de22 ci: bump minimum macOS build target to 13 2025-01-31 15:40:53 -08:00
Weblate (bot)
787af60833 translations: update Czech, Hungarian, Polish, French (#746)
* Translated using Weblate (Czech)

Currently translated at 7.8% (18 of 228 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jirka Čapek <jirkacapek13@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/cs/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (234 of 234 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (231 of 231 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (228 of 228 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Polish)

Currently translated at 94.4% (221 of 234 strings)

Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pl/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 73.1% (169 of 231 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: aless <61836900+alessdangelo@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

---------

Co-authored-by: Jirka Čapek <jirkacapek13@gmail.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
Co-authored-by: aless <61836900+alessdangelo@users.noreply.github.com>
2025-01-31 14:46:55 -08:00
mashed5894
225fe98c20 feat: port copy/paste fields and tags to sql (#722)
* implement copy/paste for fields and tags

* fix tests

* fixed badge refresh on pasting

* renamed translation field

* ignore duplicate tags on insert

* fixes

* break into several statements to satisfy tests

* chore: format with ruff
2025-01-31 14:38:01 -08:00
Travis Abendshien
496c87c7c3 ui: tweak build_tag and tag styles 2025-01-31 13:17:22 -08:00
Travis Abendshien
7c89975165 fix(ui): (mostly) fix right-click search option on tags (#756) 2025-01-30 23:32:14 -08:00
Travis Abendshien
ca7e84d1b4 fix(ui): use consistent dark mode colors for all systems (#752) 2025-01-30 22:10:06 -08:00
Travis Abendshien
e0d21c14a3 fix(ui): restore Windows accent color on PySide 6.8.0.1 (Fix #668) (#755)
* fix(ui): use the new correct(ish) accent color role for Windows

* ui: boost Windows `AlternateBase` lightness
2025-01-30 22:09:50 -08:00
Travis Abendshien
82edbee276 ui: shortcut to add tags to selected entries; change click behavior of tags to edit (#749)
* ui: change on_click behavior of tags to edit

* fix: pass library to tag_widget

* feat: add `add_tag_to_selected` shortcut

* fix: only enable `add_tag_to_selected` shortcut when usable
2025-01-29 21:28:08 -08:00
Travis Abendshien
d1b006a897 feat(ui): re-implement tag display names on sql (#747)
* feat: add disambiguation_tag column to tags table

* feat(ui): re-add tag display names

* fix(ui): allow empty disambiguation selection

* ui: restore basic tab functionality

* fix: don't set disambiguation_id for self-parented JSON tags

* fix: return consistent search results
2025-01-29 19:42:55 -08:00
Travis Abendshien
54b8397e92 fix: check for tag name partiy during JSON migration (#748)
* fix: apply JSON user edits to built-in tag names and shorthands

* fix: test for tag name parity when migrating from JSON
2025-01-29 19:42:41 -08:00
Travis Abendshien
607aafb4cd ci: lower test coverage threshold to 40% 2025-01-29 16:17:26 -08:00
Travis Abendshien
6ee5304b52 fix(ui): use camera white balance for raw images 2025-01-29 00:34:32 -08:00
Travis Abendshien
275cd1b20e fix(ui): revert all item_thumb "pressed" changes (#742) 2025-01-28 11:00:39 -08:00
Travis Abendshien
ba7e13041b feat: add date columns to the entries table (#740)
* feat: add date_created, date_modified, and date_added columns to entries table

* feat: start storing date_added dates
2025-01-28 01:57:01 -08:00
Travis Abendshien
1ba97d50c2 refactor: change exception log levels to error inside library.py 2025-01-28 01:19:30 -08:00
Travis Abendshien
20d788af29 fix: refactor and fix bugs with missing_files.py (#739)
* fix: relink unlinked entry to existing entry without sql error (#720)

* edited and added db functions get_entry_full_by_path & merge_entries

* implemented edge case for entry existing on relinking

* added test for merge_entries

* fix: don't remove item while iterating over list

* fix: catch `FileNotFoundError` for unlinked raw files

* refactor: rename methods and variables in missing_files.py

* refactor: rename `missing_files_count` to `missing_file_entries_count`

---------

Co-authored-by: mashed5894 <mashed5894@gmail.com>
2025-01-27 23:49:39 -08:00
Travis Abendshien
458925fbf7 translations: fix typo in drop import 2025-01-27 12:08:16 -08:00
Travis Abendshien
f96ea6b546 fix: always catch db mismatch (#738)
* fix: always catch db mismatch

* chore: remove/modify log statements

* translations: translate library version mismatch

* style: fix line over col limit
2025-01-27 11:48:57 -08:00
Weblate (bot)
19cdd1bf92 translations: update Czech, Hungarian (#727)
* Translated using Weblate (Czech)

Currently translated at 1.7% (4 of 224 strings)

Added translation using Weblate (Czech)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jirka Čapek <jirkacapek13@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/cs/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (224 of 224 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

---------

Co-authored-by: Jirka Čapek <jirkacapek13@gmail.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
2025-01-27 11:48:28 -08:00
Travis Abendshien
2a97a5b3aa perf: add all new library entries at once (#737) 2025-01-27 11:43:04 -08:00
Travis Abendshien
14599e90a3 fix: keep initial id order in get_entries_full() (#736)
* fix: keep initial id order in `get_entries_full()`

* fix: correct variable names
2025-01-27 11:41:01 -08:00
Travis Abendshien
ee14e2c200 fix: search for tag aliases in tag search (#726) 2025-01-27 11:40:06 -08:00
Travis Abendshien
c06f3bb336 feat!: expanded tag color system (#709)
* attempt at adding `TagColor` table

* store both columns of `TagColor` inside `Tag` table

* fix: fix tag color relationships

* refactor: replace TagColor enums with TagColorGroup system

* ui: derive tag accent colors from primary

* refactor: move dynamic tag color logic, apply to "+" button

* feat(ui): add neon tag colors

* remove tag text color field

* ui: use secondary color for border and text

* ui: add TagColorPreview widget

* feat(ui): add new tag color selector

* ui: add color name tooltips

* ui: tweak tag colors and selector

* feat: add `namespaces` table

* translations: add + update translation keys

* tests: update fixtures

* chore: code cleanup

* ui: add spacing between color groups

* fix: expand refactor to create and add button

* chore: format with ruff
2025-01-27 11:39:42 -08:00
Travis Abendshien
4c337cb1a3 feat(ui): add thumbnail caching (#694)
* feat(ui): add thumbnail caching

* feat: enforce total size limit for thumb cache

* fix: add file modification date to thumb hash

This change allows for modified files to have their cached thumbnail regenerated.

* feat: add menu option to clear thumbnail cache

* fix: check if `cached_path` is None further on

* refactor: move configurable cache vars to class level

* docs: complete roadmap items

* feat: use cache size limit from config file

TODO: Store this on a per-library basis and allow it to be configurable inside the program.

* fix: move `last_cache_folder` to class level

* chore: add/tweak type hints

* refactor: use make `CacheManager` a singleton
2025-01-27 11:39:10 -08:00
Travis Abendshien
3606edf615 fix: have pydub use known ffmpeg+ffprobe locations (#724)
* fix: have pydub use known ffmpeg+ffprobe locations

* chore: format with ruff
2025-01-26 01:51:15 -08:00
Travis Abendshien
f2a2f9a36d fix: replace calls to translate_qobject with translate_formatted inside TagWidget (#735)
* fix: replace `translate_qobject` calls with `translate_formatted`

* refactor: misc code cleanup
2025-01-26 01:50:36 -08:00
SkeleyM
921a8875de fix(ui): don't always create tag on enter ( #731) 2025-01-24 17:03:39 -08:00
mashed5894
2a41c152b3 fix: restore environment before launching external programs (#707)
* rename promptless_Popen

* linux: restore env before calling popen

* windows: set dll directory to default before calling popen

* ruff formatting suggestions

* edited silent_popen docstring
2025-01-24 13:56:35 -08:00
SkeleyM
0ead238aac fix: restore opening last library on startup (#729) 2025-01-24 13:38:42 -08:00
Travis Abendshien
9116cdc9a5 docs: fix typo in readme 2025-01-23 20:15:54 -08:00
mashed5894
17c62802c0 fix: sort tag results (#721)
* sort search results

* ruff fix
2025-01-23 13:41:57 -08:00
Travis Abendshien
ac6774ed23 fix(ui): don't set frame when playing videos
Fixes some videos not being able to play. The frame setting step was copied from thumbnail rendering and shouldn't be used here regardless.
2025-01-23 00:22:03 -08:00
Travis Abendshien
eb1f634d38 fix: don't add ._ files to libraries 2025-01-23 00:19:35 -08:00
Travis Abendshien
a1ab335dcb translations: fix fr.json syntax error 2025-01-22 16:38:45 -08:00
SkeleyM
89a8abca75 feat(ui): add audio volume slider (#691)
* Add Volume Slider

* Slider reflects muted state

* Default Volume Value

* Update roadmap to reflect current state

* Forgot a period in a comment

* Add suggestions

* Update tagstudio/src/qt/widgets/media_player.py

Co-authored-by: VasigaranAndAngel <72515046+VasigaranAndAngel@users.noreply.github.com>

* remove unwanted line

---------

Co-authored-by: VasigaranAndAngel <72515046+VasigaranAndAngel@users.noreply.github.com>
2025-01-22 16:07:13 -08:00
Travis Abendshien
a02c43c115 feat(ui): add configurable splash screens (#703)
* refactor: move splash images to resource_manager

* feat(ui): add new splash screen widget

* style: restore old resource manager log style

* fix: scale font size for Segoe UI
2025-01-22 16:02:58 -08:00
Travis Abendshien
857f40f2e3 fix: preview panel + main window fixes and optimizations (#700)
* fix: only update file preview when necessary

* fix: don't update thumb from tag_box callbacks

* fix: reset preview panel and filters when loading new library

* fix: clear library state on close; close old library on open

* fix: reset scrollbar position on library close
2025-01-22 16:00:01 -08:00
Travis Abendshien
16fb0290a0 fix: don't add default title field, use proper phrasing for adding files (#701)
* fix: use correct message when adding macros

* fix: no longer add title field to new entries

* fix: use `entries` dialog title instead of `macros`
2025-01-22 15:58:49 -08:00
SkeleyM
441ffce4a3 feat(ui): port "create and add tag" to main branch (#711)
* Port create & add tag

* Fix Name Conflict

* Add translation keys

* unify uses of BuildTagPanel

* replace tabs with spaces

* Add tag on creation

* Add Suggestions

* Trigger on return

---------

Co-authored-by: bjorn-out <b.g.out@uva.nl>
2025-01-22 15:56:54 -08:00
mashed5894
778028923e feat(ui): add about section (#712)
* added about section to the help menu and modal to display information

* load image through resource manager

* cleaned up about text

* remove visit github repo menu, moved to about modal

* added function to check ffmpeg version

* fixed version formatting

* copied in ffmpeg checker code from v9.4 with a warning message on startup if ffmpeg is not detected

* mypy fixes

* about.content spacing

* about.content title formatting

* added config path to about screen

* ruff fixes

---------

Co-authored-by: Sean Krueger <skrueger2270@gmail.com>
2025-01-22 15:48:57 -08:00
Weblate (bot)
ea9b57b85f translations: update German, Hungarian, French, Swedish (#697)
* Translated using Weblate (German)

Currently translated at 99.5% (217 of 218 strings)

Co-authored-by: Aaron M <muelleraaron0@users.noreply.hosted.weblate.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (218 of 218 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (216 of 216 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 57.7% (126 of 218 strings)

Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Swedish)

Currently translated at 34.4% (75 of 218 strings)

Co-authored-by: mashed5894 <mashed5894@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/
Translation: TagStudio/Strings

---------

Co-authored-by: Aaron M <muelleraaron0@users.noreply.hosted.weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: mashed5894 <mashed5894@gmail.com>
2025-01-22 15:24:04 -08:00
Jann Stute
2a836b0d25 docs: discourage force-pushes on prs that are open for review (#714)
* Update CONTRIBUTING.md

* Update CONTRIBUTING.md
2025-01-19 13:07:40 -08:00
SkeleyM
6ea2c99abb fix: drag and drop no longer resets (#710) 2025-01-17 15:04:13 -08:00
Travis Abendshien
44ff17c0b3 fix: dragging files references correct entry IDs
Fixes #705
2025-01-15 14:23:45 -08:00
Jann Stute
0cdb1a8c06 ui: keyboard navigation for editing tags (#407)
* build_tag modal: make Tag Title selected when opening modal

* build_tag modal: Tab now changes focus when aliases field is in focus

* fix: unpredictable tab order in build tag modal

* feat: implement proper tab order

* ci: fix mypy errors

* fix: merge issue 1

* fix: merge issue 2

* refactor: TagDatabasePanel now inherits from TagSearchPanel for code deduplication

* Revert "refactor: TagDatabasePanel now inherits from TagSearchPanel for code deduplication"

This reverts commit fac589b3e3.
2025-01-14 03:19:44 -08:00
Travis Abendshien
5caf869867 docs: update roadmap 2025-01-13 18:41:08 -08:00
Travis Abendshien
604837fcaa fix: store tag.id to variable before using in lambda
This fixes an issue where every tag added from the "+" button had ID 0 (the "Archived" tag).

Co-Authored-By: Jann Stute <46534683+Computerdores@users.noreply.github.com>
2025-01-13 14:42:19 -08:00
Jann Stute
5c8f2c507f feat: optimise AND queries (#679)
* feat: optimise tag constraint

* feat: use less subqueries

* refactoring: __entry_satisfies_ast was unnecessary

* feat: reduce time consumption of counting total results massively

* feat: log the time it takes to fetch the results

* Revert "feat: reduce time consumption of counting total results massively"

This reverts commit 30af514681.

* feat: log the time it takes to count the results

* feat: optimise __entry_has_all_tags
2025-01-13 13:48:02 -08:00
Jann Stute
5bab00aa6d refactor: TagDatabasePanel now inherits from TagSearchPanel for code deduplication (#699)
* refactor: TagDatabasePanel now inherits from TagSearchPanel for code deduplication

* refactor: rename callback method

* extract creation of row items to separate method
2025-01-13 13:46:57 -08:00
Jann Stute
a272b18637 feat: improve performance of "Delete Missing Entries" (#696)
* feat: Delete all unlinked entries at once (#617)

This technically removes the usefulness progress indicator, but brief
testing on my end had it delete ~8000 entries in less time than it took
me to see if the progress indicator was properly indeterminate or not

* fix(fmt): remove unused import

* fix: wasn't able to delete more than 32766 entries

* chose: ruff check --fix

* fix: address review comment

---------

Co-authored-by: Tobias Berger <toby@tobot.dev>
2025-01-11 13:26:01 -08:00
Travis Abendshien
fce97852d3 feat!: tag categories (#655)
* refactor: remove TagBoxField and TagField (NOT WORKING)

* refactor: remove tag field types

* ci: fix mypy and ruff tests

* refactor: split up preview_panel

* fix: search now uses `TagEntry` (#656)

* fix: move theme check inside class

* refactor: reimplement file previews

* refactor: modularize `file_attributes.py`

* ui: show fields in preview panel

known issues:
- fields to not visually update after being edited until the entries are reloaded from the thumbnail grid (yes, the thumbnail grid)
- add field button currently non-functional
- surprise segfaults

* search: remove TagEntry join

* fix: remove extra `self.filter` assignment

* add success return flag to `add_tags_to_entry()`

* refactor: use entry IDs instead of objects and indices

- fixes preview panel not updating after entry edits
- fixes slow selection performance
- fixes double render call

* feat: add tag categories to preview panel

* ui: add "is category" checkbox in tag panel

* fix: tags can be compared for name sorting

* fix: don't add tags to previous selections

* fix: badges now properly update

* ui: hide sizeGrip

* ui: add blue ui color

* ui: display empty selection; better multi-selection

* cleanup comments; rename tsp to tag_search_panel

* fix(ui): properly unset container callbacks

* fix: optimize queries

* fix: catch int cast exception

* fix: remove unnecessary update calls

* fix: restore try/except block in preview_panel

* fix: correct type hints for get_tag_categories

* fix: tags no longer lazy load subtags and aliases

* fix: recursively include parent tag categories

* chore: update copyright info

* chore: remove unused code

* fix: load fields for selected entry

* refactor: remove `is_connected` from AddFieldModal

* fix: include category tags under their own categories

* fix: badges now update when last tag is removed

* fix: resolve differences with main

* fix: return empty set in place of `None`

* ui: add field highlighting, tweak theming

* refactor!: eradicate use of the term "subtag"

- Removes ambiguity between the use of the term "parent tag" and "subtag"
- Fixes inconstancies between the use of the term "subtag" to refer to either parent tags or child tags
- Fixes duplicate and ambiguous subtags mapped relationship for the Tag model
- Does NOT fix tests

* fix: catch and show library load errors

* tests: fix and/or remove tests

* suppress db preference warnings

* tests: add field container tests

* tests: add tag category tests

* refactor(ui): move recent libraries list to file menu

* docs: update roadmap and docs for tag categories

* fix: restore json migration functionality

* logs: remove/update debug logs

* chore: remove unused code

* tests: remove tests related to `TagBoxWidget`

* ui: optimize selection and badge updates

* docs: update usage

* fix: change typo of `tag.id` to `tag_id`

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>

* fix: use term "child tags" instead of "subtags" in docstring

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>

* fix: reference `child_id` instead of `parent_id` when deleting tags

Co-Authored-By: Jann Stute <46534683+Computerdores@users.noreply.github.com>

* add TODO comment for `update_thumbs()` optimization

* fix: combine and check (most) built-in tag data from JSON

Known issue: Tag colors from built-in JSON tags are not updated. This can be seen in the failing test.

* refactor: rename `select_item()` to `toggle_item_selection()`

* add TODO to optimize `add_tags_to_entry()`

* fix: remove unnecessary joins in search

* Revert "fix: remove unnecessary joins in search"

This reverts commit 4c019ca19c.

* fix: remove unnecessary joins in search

* reremove unused method `get_all_child_tag_ids()`

* fix: migrate user-edited tag colors for built-in tags

* style: update header for contributor-created files

* fix: use absolute path in "open file" context menu

* chore: change paramater type hint

---------

Co-authored-by: python357-1 <jb2101554@gmail.com>
Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>
2025-01-11 09:44:09 -08:00
Weblate (bot)
5860a2ca9b translations: update German, Hungarian, Spanish, and Chinese (Traditional Han script) (#677)
* Translated using Weblate (German)

Currently translated at 73.3% (157 of 214 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (214 of 214 strings)

Translated using Weblate (Hungarian)

Currently translated at 59.8% (128 of 214 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Spanish)

Currently translated at 63.5% (136 of 214 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/
Translation: TagStudio/Strings

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 94.8% (203 of 214 strings)

Co-authored-by: Brian Su <brisu@me.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/zh_Hant/
Translation: TagStudio/Strings

---------

Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
Co-authored-by: Brian Su <brisu@me.com>
2025-01-11 07:05:26 -08:00
Travis Abendshien
d8d61cc8c8 docs: fix broken weblate link in README 2025-01-11 07:03:05 -08:00
Travis Abendshien
fc81c43af1 docs: add a linux install notice (#695)
* docs: add a linux install notice (#689)

* docs: add a linux install notice to readme (#690)

---------

Co-authored-by: Qronikarz <84787215+Qronikarz@users.noreply.github.com>
2025-01-11 07:00:13 -08:00
Jann Stute
3cd56a881f feat: sort by "date added" to library (#674)
* ui: add sorting mode dropdown

* feat: pass sorting mode to Library.search_library

* feat: implement sorting by creation date

* ui: add dropdown for sorting direction

* ui: update shown entries after changing sorting mode / direction

* docs: mark sorting by "Date Created" as completed

* fix: remove sorting options that have not been implemented

* fix: rename sorting mode to "Date Added"

* fix: check off the right item on the roadmap

* feat: translate sorting UI

* fix: address review comments
2025-01-10 07:46:23 -08:00
Travis Abendshien
936157c3c2 fix(ui): properly mask macOS icon 2025-01-07 01:17:31 -08:00
Travis Abendshien
c4f4bba5e0 build: update macos bundle identifier and version
Update bundle to use `com.companyname.appname` reverse-domain structure
2025-01-06 11:44:18 -08:00
Travis Abendshien
29c0dfdb2d feat(ui): use tag query as default new tag name 2025-01-05 23:40:41 -08:00
Travis Abendshien
bf03e28fdb ui: update macOS icon 2025-01-05 22:49:58 -08:00
Travis Abendshien
0b6b07d0b4 fix(ui): don't create new QFonts in main_window.py
This fix uses stylesheets instead of initializing new QFonts in the Ui_MainWindow class. This fixes the fonts appearing differently on different OSes, including a fix for text using these QFonts appearing small on macOS. The use of stylesheets also puts the styling in line with how the rest of the program operates.
2025-01-05 21:02:36 -08:00
Travis Abendshien
af760ee61a ui: port splash screen from Alpha-v9.4
Reimplements:
- 7f3b3d06af
- 1e4883c577
- 1c53f05e4f
2025-01-05 19:40:37 -08:00
SkeleyM
c5c86747fe fix: only close add tag menu with no search (#685) 2025-01-05 16:34:55 -08:00
SkeleyM
ef042ef070 fix: call filepaths instead of using start (#667) 2025-01-03 00:37:18 -08:00
Jann Stute
7b672e03a1 feat: implement parent tag search (#673)
* feat: implement parent tag search

* feat: add tests for parent tag search

* fix: typo

* feat: log the time it takes to build the SQL Expression

* feat: instead of hardcoding child tag ids into main query, include subquery

* Revert "feat: instead of hardcoding child tag ids into main query, include subquery"

This reverts commit 2615e7dab4.
2025-01-01 02:36:11 -08:00
python357-1
5a0ba54454 fix: strip whitespace around --open/-o flag (#670)
* fix: fix -o flag not working if path has whitespace around it

* fix: change strip to lstrip/rstrip

* fix: manually expand "~"
2025-01-01 02:35:35 -08:00
Nginearing
d948c0ce4c docs: update readme (#676) 2025-01-01 02:34:18 -08:00
Travis Abendshien
d5cebf39d4 fix: add missing pillow_jxl import
This import was accidentally removed in #569. This commit restores the import.
2024-12-30 20:50:57 -08:00
Weblate (bot)
584f3aa358 translations: remove library.refresh.scanning key (#675)
* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings
2024-12-30 19:18:23 -08:00
Weblate (bot)
1a317a1826 translations: update Hungarian, Polish, French, Chinese (Traditional Han script), Toki Pona (#663)
* Translated using Weblate (Hungarian)

Currently translated at 100.0% (130 of 130 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/
Translation: TagStudio/Strings

* Translated using Weblate (Polish)

Currently translated at 100.0% (130 of 130 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (130 of 130 strings)

Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pl/
Translation: TagStudio/Strings

* Translated using Weblate (French)

Currently translated at 100.0% (130 of 130 strings)

Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/
Translation: TagStudio/Strings

* Translated using Weblate (Chinese (Traditional Han script))

Currently translated at 90.0% (117 of 130 strings)

Added translation using Weblate (Chinese (Traditional Han script))

Co-authored-by: Brian Su <brisu@me.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/zh_Hant/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 96.1% (125 of 130 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: gold <goldstargloww@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Brian Su <brisu@me.com>
Co-authored-by: gold <goldstargloww@gmail.com>
2024-12-30 19:16:46 -08:00
Jann Stute
69aed92f24 feat: translations (#662)
* feat: implement Translator class

* feat: add translate_with_setter and implement formatting of translations

* feat: extend PanelModal to allow for translation

* feat: extend ProgressWidget to allow for translation

* feat: translation in ts_qt.py

* debug: set default lang to DE and show "Not Translated" when replacing untranslated stuff

* add translation todos

* feat: translate modals

* feat: translate more stuff

* fix: UI test wasn't comparing to translated strings

* feat: translations for most of the remaining stuff

* fix: replace debug changes with simpler one

* uncomment debug change

* fix: missing parameter in call

* fix: various review feedback

* fix: don't translate json migration discrepancies list

* fix: typo

* fix: various PR feedback

* fix: correctly read non-ascii characters

* fix: add missing new line at eof

* fix: comment out line of debug code

* fix: address latest review comment

* fix: KeyError that occurred when formatting translations

* fix: regression of d594e84

* fix: add newline to en.json

* fix: organize en.json, fix typo

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2024-12-30 19:15:39 -08:00
SkeleyM
40bfee0502 fix(ui): prevent duplicate parent tags in UI (#665) 2024-12-23 19:39:23 -08:00
Travis Abendshien
020a73d095 fix(ui): use consistent tag outline colors 2024-12-23 18:55:22 -08:00
Travis Abendshien
431efe4fe9 fix(ui): use correct pink tag color 2024-12-23 18:39:56 -08:00
Travis Abendshien
ca4dc0bebd chore: add 089c8dd to .git-blame-ignore-revs 2024-12-23 16:50:10 -08:00
DandyDev01
82c659c8a4 feat(ui): new tag alias UI (#534)
* updated parents and aliases to use the flowLaout in the build tag panel

added shortcuts for adding and removing aliases and updated the alias ui
to always show remove button and not cover alias

aliases use flowlayout

wrote test for buildTagPanel removeSelectedAlias

parent tags now use flowlayout in build tag panel

moved buttons for adding aliases and parents to be at the end of the
flowLayout

* added aliases and subtags to search results

* aliases now use a table, removed unnecessary keyboard shortcuts

* reverted subtags to regular list from flowlayout

* chor remove redundant lambda

* feat: added display names for tags

* fix: aliases table enter/return and backspace work as expected, display names work as expected, adding aliases outputs them in order

* format

* fix: add parent button on build tag panel

* fix: empty aliases where not being removed all the time

* fix: alias name changes would be discarded when a new alias was created or an alias was removed

* fix: removed display names, as they didn't display properly and should be added in a different PR

* fix: mypy

* fix: added missing session.expunge_all

session.expunge_all() on line 621 was removed, added it back.

* fix: parent_tags relationship in Tag class

parent_tags primaryJoin and secondaryJoin where in the wrong order. They have been switched back to the proper order.

* fix: pillow_jxl import was missing

* fix: ruff

* fix: type hint fixes

* fix: fix the type hint fixes

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2024-12-22 22:14:37 -08:00
Travis Abendshien
dc2eed431b add .sqlite-journal to .gitignore 2024-12-22 22:11:18 -08:00
Jann Stute
9eed3b64d9 feat: implement search equivalence of "jpg" and "jpeg" filetypes (#649)
* feat: implement search equivalence of "jpg" and "jpeg" filetypes in an extensible manner

* docs: update completion for search features on roadmap

* fix: move FILETYPE_EQUIVALENTS to media_types.py
2024-12-22 13:58:10 -08:00
SkeleyM
4c3ff42169 fix: show correct unlinked files count (#653) 2024-12-22 12:21:04 -08:00
Weblate (bot)
b209abb1a4 translations: propagate en.json key changes (#654)
* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/
Translation: TagStudio/Strings
2024-12-21 17:29:49 -08:00
Travis Abendshien
45b45f5846 translations: remove unused keys, add missing ones 2024-12-21 15:01:29 -08:00
Travis Abendshien
b3f393e59b style(translations): sort en.json keys 2024-12-21 14:53:56 -08:00
Travis Abendshien
f4b30c15ce refactor(translations): fix + normalize translation keys 2024-12-21 14:50:47 -08:00
Weblate (bot)
8a52bfea69 translations: fix German, add Polish (#650)
* Translated using Weblate (German)

Currently translated at 93.6% (133 of 142 strings)

Translated using Weblate (German)

Currently translated at 87.3% (124 of 142 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings

* Translated using Weblate (Polish)

Currently translated at 95.7% (136 of 142 strings)

Translated using Weblate (Polish)

Currently translated at 23.2% (33 of 142 strings)

Added translation using Weblate (Polish)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pl/
Translation: TagStudio/Strings

---------

Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Co-authored-by: qronikarz <qronikarz@users.noreply.hosted.weblate.org>
2024-12-21 10:31:30 -08:00
SkeleyM
934c98a4d4 fix: enter/return adds top result tag (#651)
* fix return not adding searched tag

* add first_tag_id type hint
2024-12-20 17:00:33 -08:00
Travis Abendshien
36c1c180b3 refactor: consolidate reserved tag values as ints 2024-12-20 16:24:04 -08:00
Travis Abendshien
d6280f7ead style: remove whitespace 2024-12-20 16:07:19 -08:00
Travis Abendshien
933af1c405 ui: update remove tag message box text 2024-12-20 16:00:44 -08:00
Travis Abendshien
2903dd22c4 fix: tags created from tag database now add aliases 2024-12-20 15:56:23 -08:00
DandyDev01
fdfd6490bd feat: delete and create tags from tag database panel (#569)
* [feat] can now add a new tag from the tag library panel

* [feat] can remove tags from the tag library panel
[fix] library panel updates tag list when a new tag is create

* [test] added test for library->remove_tag

* [fix] type error

* removed redundant lambda

Co-authored-by: VasigaranAndAngel <72515046+VasigaranAndAngel@users.noreply.github.com>

* fix: tags with a reserved id could be edited or removed, now they cannot.

* fix: when a tag is removed or edited the preivew panel will update to reflect the changes

Co-authored-by: Sean Krueger <skrueger2270@gmail.com>

* fix: mypy check

* fix: aliases and subtags not being removed from DB when tag they reference was removed.

* feat: added a confirmation message box when removing tags.

* fix: mypy

---------

Co-authored-by: VasigaranAndAngel <72515046+VasigaranAndAngel@users.noreply.github.com>
Co-authored-by: Sean Krueger <skrueger2270@gmail.com>
2024-12-20 15:22:23 -08:00
Travis Abendshien
8387676d79 feat(ui): show filenames in thumbnail grid (Closes #85) (#633)
* feat(ui): add filename label under thumbnails

* refactor(ui): organize menu items

* feat: make thumbnail filenames toggleable

* refactor variables, tweak spacing

* fix(ui): only change cursor on thumb_button

* revert changes to ../search_library/../ts_library.sqlite

* fix: don't ever call .setHidden(False) on visible widgets

* refactor: rename filename toggles to setters

* fix: remove duplicate open_on_start_action
2024-12-20 14:34:05 -08:00
python357-1
24fa76ee30 tests(fix): stop updating sqlite db during tests (#648)
* fix: stop sqlite db from being updated while running tests

* refactor: small refactor in db checking code
2024-12-20 14:03:11 -08:00
Travis Abendshien
9e0c4f39b8 docs: remove closed pr warning from CONTRIBUTING.md 2024-12-14 11:42:17 -08:00
DandyDev01
c33d0203e4 fix: editing tags from preview panel updates database (#641)
* fix: bug where preview_panel tag was out of date compared to tag in database after edits where made using the tagDatabasePanel

* fix: changes made in the preview panel are saved to the database
2024-12-14 11:40:48 -08:00
Travis Abendshien
ebc487eac4 docs: add refactor warning to CONTRIBUTING.md 2024-12-12 21:43:13 -08:00
Travis Abendshien
dec9f1dd28 docs: add additional detail to ffmpeg help 2024-12-11 18:28:09 -08:00
python357-1
a519c9a737 chore: update pytest workflow to 24.04 (#639)
* fix: try fixing pytest workflow

* chore: update pytest workflow to 24.04

* fix: fix libglx package name

* fix: try a different package

* fix: try apt-update first

* Revert "fix: try apt-update first"

This reverts commit 34c04f985b.

* Reapply "fix: try apt-update first"

This reverts commit 775bc2634d.
2024-12-11 17:04:53 -08:00
Sean Krueger
385aaf42d8 feat: reimplement drag drop files (Port #153) (#528)
* feat: Drag and drop files in and out of TagStudio (#153)

* Ability to drop local files in to TagStudio to add to library

* Added renaming option to drop import

* Improved readability and switched to pathLib

* format

* Apply suggestions from code review

Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com>

* Revert Change

* Update tagstudio/src/qt/modals/drop_import.py

Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com>

* Added support for folders

* formatting

* Progress bars added

* Added Ability to Drag out of window

* f

* format

* Ability to drop local files in to TagStudio to add to library

* Added renaming option to drop import

* Improved readability and switched to pathLib

* format

* Apply suggestions from code review

Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com>

* Revert Change

* Update tagstudio/src/qt/modals/drop_import.py

Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com>

* Added support for folders

* formatting

* Progress bars added

* Added Ability to Drag out of window

* f

* format

* format

* formatting and refactor

* format again

* formatting for mypy

* convert lambda to func for clarity

* mypy fixes

* fixed dragout only worked on selected

* Refactor typo, Add license

* Reformat QMessageBox

* Disable drops when no library is open

Co-authored-by: Sean Krueger <skrueger2270@gmail.com>

* Rebased onto SQL migration

* Updated logic to based on selected grid_idx instead of selected ids

* Add newly dragged-in files to SQL database

* Fix buttons being inconsistant across platforms

* Fix ruff formatting

* Rename "override" button to "overwrite"

---------

Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com>
Co-authored-by: Travis Abendshien <lvnvtravis@gmail.com>
Co-authored-by: Sean Krueger <skrueger2270@gmail.com>

* refactor: Update dialog and simplify drop import logic

* Handle Qt events for main window in ts_qt.py

* Replace magic values with enums

* Match import duplicate file dialog to delete missing entry dialog

* Remove excessive progess widgets

* Add docstrings and logging

* refactor: add function for common ProgressWidget use

Extracts the create_progress_bar function from drop_import to the
ProgressWidget class. Instead of creating a ProgressWidget,
FunctionIterator, and CustomRunnable every time a thread-safe progress
widget is needed, the from_iterable function in ProgressWidget now
handles all of that.

---------

Co-authored-by: Creepler13 <denis.schlichting03@gmail.com>
Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com>
Co-authored-by: Travis Abendshien <lvnvtravis@gmail.com>
2024-12-11 16:00:27 -08:00
Travis Abendshien
a1daf5ab6a fix: use absolute ffprobe path on macos (Fix #511) (#629)
* bump pyside version to 6.8.0.1

* fix: try for absolute ffprobe path on macos
2024-12-09 11:51:36 -08:00
Travis Abendshien
aaea0b1475 fix: don't allow blank tag alias values in db (#628) 2024-12-09 11:44:51 -08:00
Jann Stute
a535ed12b0 feat: implement query language (#606)
* add files

* fix: term was parsing ANDList instead of ORList

* make mypy happy

* ruff format

* add missing todo

* add more constraint types

* add parent property to AST

* add BaseVisitor class

* make mypy happy

* add __init__.py

* Revert "make mypy happy"

This reverts commit 926d0dd2e79d06203e84e2f83c06c7fe5b33de23.

* refactoring and fixes

* rudimentary search field integration

* fix: check for None properly

* fix: Entries without Tags are now searchable

* make mypy happy

* Revert "fix: Entries without Tags are now searchable"

This reverts commit 19b40af7480b0c068b81b642b51536a9ec96d030.

* fix: changed joins to outerjoins and added missing outerjoin

* use query lang instead of tag_id FIlterState

* add todos

* fix: remove uncecessary line that broke search when searching for exact name

* fix tag search

* refactoring

* fix: path now uses GLOB operator for proper GLOBs

* refactoring: remove FilterState.id and implement Library.get_entry_full as replacement

* fix: use default value notation instead of if None statement in __post_init__

* remove obsolete Search Mode UI and related code

* ruff fixes

* remove obsolete tests

* fix: item_thumb didn't query entries correctly

* fix: search_library now correctly returns the number of *unique* entries

* make mypy happy

* implement NOT

* remove obsolete filename search

* remove summary as it is not applicable anymore

* finish refactoring of FilterState

* implement special:untagged

* fix: make mypy happy

* Revert changes to search_tags in favor of changes from #604

* fix: also port test changes

* fix: remove unneccessary import

* fix: remove unused dataclass

* fix: AND now works correctly with tags

* simplify structure of parsed AST

* add performance logging

* perf: Improve performance of search by reducing number of required joins from 4 to 1

* perf: double NOT is now optimized out of the AST

* fix: bug where pages would show less than the configured number of entries

* Revert "add performance logging"

This reverts commit c3c7d7546d.

* fix: tag_id search was broken

* somewhat adapt the existing autocompletion to this PR

* perf: Use Relational Division Queries to improve Query Execution Time

* fix: raise Exception so as to not fail silently

* fix: Parser bug broke parentheses

* little bit of clean up

* remove unnecessary comment

* add library for testing search

* feat: add basic tests

* fix: and queries containing just one tag were broken

* chore: remove debug code

* feat: more tests

* refactor: more consistent name for variable

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>

* fix: ruff check complaint over double import

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2024-12-06 15:43:08 -08:00
Travis Abendshien
056e600466 bump numpy to version 2.1.0 2024-12-05 01:30:18 -08:00
Jann Stute
3196678666 fix: multiple macro errors (#612)
* port fixes from json branch

* fix: correctly pass grid_idx in autofill macro

* fix(BUILD_URL macro): only add url if url was successfully built

* fix(AUTOFILL macro): error when trying to add tag with name that already exists

* fix: test was wrongly renaming sidecar file
2024-12-05 00:43:39 -08:00
Дмитрий
bea6913814 fix: add check to see if library is loaded in filter_items (#547)
* Added a check to see if the library is loaded in filter_items

* Returned check to see if there is an engine

---------

Co-authored-by: gred <gred25@yandex.ru>
2024-12-04 21:53:44 -08:00
Travis Abendshien
b72a2f2331 add ".DS_Store" to GLOBAL_IGNORE_SET 2024-12-04 11:23:42 -08:00
Travis Abendshien
4c33901a47 chore: format with ruff 2024-12-04 11:16:47 -08:00
Travis Abendshien
7d7c8b2348 docs: adjust formatting 2024-12-03 13:04:02 -08:00
Travis Abendshien
47babdd5b5 docs: update README.md and index.md 2024-12-03 12:59:27 -08:00
Weblate (bot)
461d103026 translations: add Filipino translation (#584)
Currently translated at 75.3% (107 of 142 strings)

Translated using Weblate (Filipino)

Currently translated at 50.0% (71 of 142 strings)

Added translation using Weblate (Filipino)



Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fil/
Translation: TagStudio/Strings

Co-authored-by: searinminecraft <114207889+searinminecraft@users.noreply.github.com>
2024-12-03 10:48:23 -08:00
VasigaranAndAngel
a630b09b4f fix: remove/rework windows path tests (#625)
* fix: tagstudio/tests/test_driver.py::test_evaluate_path_last_lib_present always fails in windows.

* refactor: removal tagstudio/tests/test_library.py::test_save_windows_path

Fixes #616
2024-12-03 10:39:44 -08:00
VasigaranAndAngel
8ba23c5d54 fix: clear all setting values when opening a library (#622) 2024-12-02 12:11:39 -08:00
Travis Abendshien
e4d8f995bb docs: remove partial checkboxes 2024-12-01 13:33:39 -08:00
Travis Abendshien
691c63e659 docs: update v9.5 roadmap 2024-12-01 13:24:18 -08:00
Travis Abendshien
1fcd31b62f chore: bump ruff to 0.8.1, pillow-jxl-plugin to 1.3.0 2024-12-01 11:56:10 -08:00
Travis Abendshien
1974ff169c refactor: remove 3.12 nested f-string 2024-11-30 15:11:17 -08:00
Theasacraft
dffa3635b0 fix: remove qt disconnect warning (#613)
* fix: cannot disconnect from None Warning

* fix: cannot disconnect from None Warning mypy compliant
2024-11-30 13:03:57 -08:00
Travis Abendshien
ef68603322 feat(parity): migrate json libraries to sqlite (#604)
* feat(ui): add PagedPanel widget

* feat(ui): add MigrationModal widget

* feat: add basic json to sql conversion

* fix: chose `poolclass` based on file or memory db

* feat: migrate tag colors from json to sql

* feat: migrate entry fields from json to sql

- fix: tag name column no longer has unique constraint
- fix: tags are referenced by id in db queries
- fix: tag_search_panel no longer queries db on initialization; does not regress to empty search window when shown
- fix: tag name search no longer uses library grid FilterState object
- fix: tag name search now respects tag limit

* set default `is_new` case

* fix: limit correct tag query

* feat: migrate tag aliases and subtags from json to sql

* add migration timer

* fix(tests): fix broken tests

* rename methods, add docstrings

* revert tag id search, split tag name search

* fix: use correct type in sidecar macro

* tests: add json migration tests

* fix: drop leading dot from json extensions

* add special characters to json db test

* tests: add file path and entry field parity checks

* fix(ui): tag manager no longer starts empty

* fix: read old windows paths as posix

Addresses #298

* tests: add posix + windows paths to json library

* tests: add subtag, alias, and shorthand parity tests

* tests: ensure no none values in parity checks

* tests: add tag color test, use tag id in tag tests

* tests: fix and optimize tests

* tests: add discrepancy tracker

* refactor: reduce duplicate UI code

* fix: load non-sequential entry ids

* fix(ui): sort tags in the preview panel

* tests(fix): prioritize `None` check over equality

* fix(tests): fix multi "same tag field type" tests

* ui: increase height of migration modal

* feat: add progress bar to migration ui

* fix(ui): sql values update earlier

* refactor: use `get_color_from_str` in test

* refactor: migrate tags before aliases and subtags

* remove unused assertion

* refactor: use `json_migration_req` flag
2024-11-30 13:00:08 -08:00
Travis Abendshien
b7e652ad8d docs: remove db_migration 2024-11-29 15:40:04 -08:00
csponge
bc366fc34d feat: audio playback (#576)
* feat: Audio Playback

Add the ability to play audio files.

Add a slider to seek through an audio file.

Add play/pause and mute/unmute buttons for audio files.

Note: This is a continuation of a mistakenly closed PR:
Ref: https://github.com/TagStudioDev/TagStudio/pull/529

While redoing the changes, I made a couple of improvements.
When the end of the track is reached, the pause button will
swap to the play button and allow the track to be replayed.

Here is the original feature request:
Ref: https://github.com/TagStudioDev/TagStudio/issues/450

* fix: prevent autoplay on new track when paused

* refactor: Add MediaPlayer base class.

Added a MediaPlayer base class per some suggestions
in the PR comments. Hopefully this reduces duplicate code
between the audio/video player in the future.

* refactor: add controls to base MediaPlayer class

Move media controls from the AudioPlayer widget
to the MediaPlayer base class. This removes the need
for a separate AudioPlayer class, and allows the
video player to reuse the media controls.

* fix: position_label update with slider

Update the position_label when the slider is moving.

* fix: replace platform dependent time formatting

Replace the use of `-` in the time format since this
is not availabile on all platforms.

Update initial `position_label` value to '0:00'.
2024-11-29 14:47:27 -08:00
Travis Abendshien
1fb1a80d53 fix: ui/ux parity fixes for thumbnails and files (#608)
* fix(ui): display loading icon before rendered thumb

* fix: skip out of range thumbs

* fix: optimize library refreshing

* fix(ui): tag colors show correct names

* fix(ui): ensure inner field containers are deleted

* fix(ui): don't show default preview label text

* fix: catch all missing file thumbs; clean up logs
2024-11-29 12:35:18 -08:00
Kiril Bourakov
d152cd75d8 fix: "open in explorer" opens correct folder (#603)
* replaced as_posix with str

* replaced addition with f string

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2024-11-29 09:34:28 -08:00
Jann Stute
20f93719d7 fix(ci): surpress errant mypy warnings (#609)
* fix: mypy error in ts_qt

* fix: mypy error in file_opener due to conflicting types

* fix: remove unnecessary type ignores

* refix type ignore comments

* partially revert "refix type ignore comments" due to being implemented in #608
2024-11-28 13:19:08 -08:00
Travis Abendshien
262893a1bb add .DS_Store to .gitignore 2024-11-27 21:25:03 -08:00
Travis Abendshien
7b2636e4a7 add syncthing to .gitignore 2024-11-24 10:29:29 -08:00
DandyDev01
0d166e63c0 feat(parity): backend for aliases and parent tags (#596)
* backend for aliases and parents

* resolve merge conflics
2024-11-21 12:29:35 -08:00
Travis Abendshien
f6a1ca6783 docs: update contribution guidelines 2024-11-19 17:54:44 -08:00
Coolio
7ae2bc24d6 feat(ui): pre-select default tag name in BuildTagPanel (#592)
This changes the behavior of the tag name inside `BuildTagPanel` for newly created tags:
* The default "New Tag" name is now automatically highlighted
* Blank tag names (including spaces) are no longer allowed to be created
* NOTE: This does not change the tag name column rules in the db, nor does it necessarily need to

***

* [Feature Request]: Make the create tag panel have empty tag name field

* [Feature Request]: Make the create tag panel have empty tag name field

* Revert "[Feature Request]: Make the create tag panel have empty tag name field"

This reverts commit f9c7f5d889.

* [Feature Request]: Make the create tag panel have empty tag name field

* Revert "[Feature Request]: Make the create tag panel have empty tag name field"

This reverts commit e5df3e0f15.

* Update .gitignore

* Updated as per disscussion in issue #591 (DRAFT

* Updated as per disscussion in issue #591 (DRAFT

* Added formatting

* Updated code as per discussion is #592

* Updated code as per discussion is #592 (again)

* Fixed spacing

* Add placeholder text to name field.

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>

* Use universal red color for red border.

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>

* fix: add `src.core.palette` imports

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2024-11-19 14:14:34 -08:00
Дмитрий
2977e07223 ui: select thumb on press instead of click (#556)
Co-authored-by: gred <gred25@yandex.ru>
2024-11-18 15:35:00 -08:00
DandyDev01
774c886288 fix(ui): update ui when removing fields (#560) 2024-11-18 15:19:28 -08:00
python357-1
bec513f558 feat: add autocomplete for search engine (#586)
* feat: add autocomplete for mediatype, filetype, path, tag, and tag_id searches

* fix: address issues brought up in review

* fix: fix mypy issue

* fix: fix mypy issues for real this time
2024-11-18 10:45:51 -08:00
Justine Akehurst
9078feec0c fix(docs): fix link to feature roadmap page (#594) 2024-11-17 22:03:51 -08:00
Travis Abendshien
f9ef76c2e1 add .idea/ to .gitignore 2024-11-17 16:35:30 -08:00
yed
139836d9c8 fix: stop thumbnail jobs when closing library (#583) 2024-11-14 19:55:16 -08:00
python357-1
fd0df94830 feat: make path search use globs (#582)
* feat: make path search use globs

* fix: specify types in path search

* chore: format with ruff
2024-11-14 14:02:34 -08:00
python357-1
97e0e80f6f feat: add filetype and mediatype searches (#575)
* feat: add filetype and mediatype searches

* style: fix some style issues

* fix: parametrize mediatype and filetype tests

* style: fix remaining unordered import

* style: fix pytest parametrize calls

* feat: add human-readable names to mediacategories

* feat: use human-readable names in mediacategory: search

* feat: add human-readable name to open document

* fix: fix returning multiple filetypes issue and add regression test
2024-11-14 13:52:00 -08:00
Travis Abendshien
fb7ad928af docs(roadmap): add translations to v9.5 2024-11-11 11:13:44 -08:00
python357-1
d75729b578 docs: update CONTRIBUTING.md ruff instructions (#581)
* docs: add warning about ruff on linux

* Update CONTRIBUTING.md

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2024-11-08 11:07:51 -08:00
Travis Abendshien
f21d49df7f feat: add JXL thumbnail and animated APNG + WEBP support (port #344 and partially port #357) (#549)
* feat: add JXL image thumbnail support

Co-Authored-By: BPplays <58504799+bpplays@users.noreply.github.com>

* feat: add animated previews for webp and apng

Co-Authored-By: BPplays <58504799+bpplays@users.noreply.github.com>

---------

Co-authored-by: BPplays <58504799+bpplays@users.noreply.github.com>
2024-11-07 15:12:57 -08:00
Travis Abendshien
96026b66bc feat: add OpenDocument thumbnail support (port #366) (#545)
* feat: add OpenDocument thumbnail support

Co-Authored-By: Josh Beatty <joshuatb6@gmail.com>

* tests: add test comparing odt to png snapshot

* tests: add test comparing ods to png snapshot

* test: combine OpenDocument tests

* test: combine compatible preview tests

* test: combine preview render tests

* fix: update test snapshots

---------

Co-authored-by: Josh Beatty <joshuatb6@gmail.com>
2024-11-07 15:00:51 -08:00
Travis Abendshien
c7171c5455 chore: update resources_rc.py 2024-11-07 13:31:13 -08:00
Weblate (bot)
5ee118c1fb translations: update from Hosted Weblate (#579)
* Added translation using Weblate (Russian)

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Added translation using Weblate (Portuguese)

* Added translation using Weblate (Portuguese (Brazil))

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.4% (66 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.4% (66 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Added translation using Weblate (Tamil)

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Tamil)

Currently translated at 51.4% (73 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Added translation using Weblate (Spanish)

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Tamil)

Currently translated at 88.0% (125 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Spanish)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 19.7% (28 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 19.7% (28 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Translated using Weblate (Spanish)

Currently translated at 29.5% (42 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 29.5% (42 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 49.2% (70 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 49.2% (70 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 52.1% (74 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (Tamil)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 55.6% (79 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Added translation using Weblate (French)

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Translated using Weblate (Spanish)

Currently translated at 56.3% (80 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 56.3% (80 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 68.3% (97 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Translated using Weblate (French)

Currently translated at 40.1% (57 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (Spanish)

Currently translated at 65.4% (93 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 65.4% (93 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 93.6% (133 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 93.6% (133 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Added translation using Weblate (Danish)

* Added translation using Weblate (German)

* Translated using Weblate (Danish)

Currently translated at 1.4% (2 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Added translation using Weblate (Cantonese (Traditional Han script))

* Translated using Weblate (Tamil)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (German)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Added translation using Weblate (Hungarian)

* Translated using Weblate (German)

Currently translated at 14.0% (20 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Hungarian)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/

* Translated using Weblate (German)

Currently translated at 14.7% (21 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Added translation using Weblate (Swedish)

* Translated using Weblate (German)

Currently translated at 71.8% (102 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Swedish)

Currently translated at 78.8% (112 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/

* Added translation using Weblate (Toki Pona)

* Translated using Weblate (Tamil)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Danish)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Translated using Weblate (Toki Pona)

Currently translated at 83.0% (118 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/

* Added translation using Weblate (Italian)

* Translated using Weblate (French)

Currently translated at 64.0% (91 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (German)

Currently translated at 76.0% (108 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Hungarian)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/

* Translated using Weblate (Toki Pona)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/

* Translated using Weblate (Italian)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/

* Added translation using Weblate (Turkish)

* Translated using Weblate (Turkish)

Currently translated at 88.7% (126 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (French)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (French)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Translated using Weblate (Portuguese)

Currently translated at 0.0% (0 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt/

* Translated using Weblate (Portuguese)

Currently translated at 0.0% (0 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Tamil)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 67.6% (96 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 67.6% (96 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Translated using Weblate (French)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (Danish)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Translated using Weblate (Danish)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Translated using Weblate (German)

Currently translated at 76.0% (108 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (German)

Currently translated at 76.0% (108 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (German)

Currently translated at 76.0% (108 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 0.0% (0 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/yue_Hant/

* Translated using Weblate (Cantonese (Traditional Han script))

Currently translated at 0.0% (0 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/yue_Hant/

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/

* Translated using Weblate (Swedish)

Currently translated at 78.8% (112 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/

* Translated using Weblate (Swedish)

Currently translated at 78.8% (112 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/

* Translated using Weblate (Swedish)

Currently translated at 78.8% (112 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/

* Translated using Weblate (Toki Pona)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/

* Translated using Weblate (Italian)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/

* Translated using Weblate (Italian)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/

* Translated using Weblate (Italian)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/

* Update translation files

Updated by "Remove blank strings" add-on in Weblate.

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/

---------

Co-authored-by: Artyom Ognev <greatprincestolas@gmail.com>
Co-authored-by: Space_Fox <indtekworldbaj99@gmail.com>
Co-authored-by: Lobo Metalúrgico <hugolino1000@gmail.com>
Co-authored-by: Vasi <vasigaranvip195@gmail.com>
Co-authored-by: Nginearing <leonmarshall08@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Ryussei <weblate.or30w@aleeas.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Zoinx <ammesammegaming@gmail.com>
Co-authored-by: gold <goldstargloww@gmail.com>
Co-authored-by: William de Castro <williamtdcastro@gmail.com>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Co-authored-by: Nyghl <hknimre@gmail.com>
Co-authored-by: Obscaeris <mathysuzin@gmail.com>
2024-11-07 12:01:43 -08:00
Travis Abendshien
fc4e124cd8 Merge branch 'main' of github.com:TagStudioDev/TagStudio 2024-11-05 10:25:30 -08:00
Travis Abendshien
15bf354c88 docs(roadmap): add drag and drop 2024-11-05 10:25:27 -08:00
Travis Abendshien
10abd18def docs(roadmap): add thumbnail overrides 2024-11-05 10:24:44 -08:00
Travis Abendshien
73daa39bf1 docs(roadmap): clarify filetype search 2024-11-05 10:20:28 -08:00
Дмитрий
0358f51f99 feat: add IMAGE_RASTER_TYPES (Fix #550) (#551)
* fix resolution info

* Fix for Raw and Vector Image types

* Small refactor

* Create IMAGE_RASTER_TYPES and remove is_image_ext_raster

* Change if statment only for raster

* Rename _IMAGE_SET to _IMAGE_RASTER_SET

---------

Co-authored-by: gred <gred25@yandex.ru>
2024-11-04 12:28:44 -08:00
Hissymaster
e02eb39ae2 docs: change reference to planned_features.md to roadmap.md (#564)
Co-authored-by: Hissymaster <Hissymaster@gmail.com>
2024-10-28 20:46:27 -07:00
Travis Abendshien
6e5a1a0e52 docs: add issues numbers to roadmap 2024-10-28 14:38:34 -07:00
Travis Abendshien
2376380245 docs: remove planned_features.md 2024-10-28 13:02:05 -07:00
Travis Abendshien
04149f6454 docs: add feature roadmap 2024-10-28 13:01:13 -07:00
Travis Abendshien
d3c3e634b9 feat: add ePub thumbnail support (port #387) (#539)
* feat: add ePub thumbnail support

Co-Authored-By: Jorge Rui Da Silva Barrios <29062316+jorgerui@users.noreply.github.com>

* tests: compare epub cover against png snapshot

Co-Authored-By: yed <yedpodtrzitko@users.noreply.github.com>

* test: optimize epub test file

---------

Co-authored-by: Jorge Rui Da Silva Barrios <29062316+jorgerui@users.noreply.github.com>
Co-authored-by: yed <yedpodtrzitko@users.noreply.github.com>
2024-10-17 15:15:51 -07:00
Travis Abendshien
3d7629bc73 feat: add pdf thumbnail support (port #378) (#543)
* feat: add pdf thumbnail support

Co-Authored-By: Heiholf <71659566+heiholf@users.noreply.github.com>

* fix: remove redef

* tests: add test comparing pdf to png snapshot

Co-Authored-By: yed <yedpodtrzitko@users.noreply.github.com>

* fix: fix info in docstrings

* fix: remove sample png generation

* fix: change the pdf snapshot to use a black square

* chore: fix whitespace

---------

Co-authored-by: Heiholf <71659566+heiholf@users.noreply.github.com>
Co-authored-by: yed <yedpodtrzitko@users.noreply.github.com>
2024-10-14 16:34:49 -07:00
Travis Abendshien
9255a86ad1 feat: add svg thumbnail support (port #442) (#540)
* feat: add svg thumbnail support

Co-Authored-By: Tyrannicodin <86689800+Tyrannicodin@users.noreply.github.com>

* flip `svg.isValid()` logic check

* tests: add test comparing svg to png snapshot

Co-Authored-By: yed <yedpodtrzitko@users.noreply.github.com>

---------

Co-authored-by: Tyrannicodin <86689800+Tyrannicodin@users.noreply.github.com>
Co-authored-by: yed <yedpodtrzitko@users.noreply.github.com>
2024-10-14 13:30:46 -07:00
Travis Abendshien
5b85462cfa ci: pin pytest ubuntu version to 22..04 (#544) 2024-10-12 23:59:08 -07:00
xarvex
abeb0c1ce3 fix(ci): complete 7c253226d5 2024-10-11 17:12:57 -05:00
xarvex
7c253226d5 fix(ci): replace obselete package 2024-10-11 17:01:49 -05:00
Travis Abendshien
68c166d8d3 Bump version to v9.5.0 Experimental 2024-10-10 12:40:09 -07:00
Jann Stute
c348c763f8 fix: enable mypy to run from project directory (#520) 2024-10-07 19:04:22 -07:00
yed
7574ad3846 fix: don't check db version with new library (#536) 2024-10-07 18:56:02 -07:00
Travis Abendshien
7dd0f3dabb feat: port thumbnail (#390) and related features to v9.5 (#522)
* feat: port v9.4 thumbnail + related feats to v9.5

Ports the following thumbnail and related PRs from the `Alpha-v9.4` branch to `main` (v9.5+):
- (#273) Blender thumbnail support
- (#307) Add font thumbnail preview support
- (#331) refactor: move type constants to new media classes
- (#390) feat(ui): expanded thumbnail and preview features
- (#370) ui: "open in explorer" action follows os name
- (#373) feat(ui): preview support for source engine files
- (#274) Refactor video_player.py (Fix #270)
- (#430) feat(ui): show file creation/modified dates + restyle path label
- (#471) fix(ui): use default audio icon if ffmpeg is absent
- (#472) fix(ui): use birthtime for creation time on mac & win

Co-Authored-By: Ethnogeny <111099761+050011-code@users.noreply.github.com>
Co-Authored-By: Theasacraft <91694323+Thesacraft@users.noreply.github.com>
Co-Authored-By: SupKittyMeow <77246128+supkittymeow@users.noreply.github.com>
Co-Authored-By: EJ Stinson <93455158+favroitegamers@users.noreply.github.com>
Co-Authored-By: Sean Krueger <71362472+seakrueger@users.noreply.github.com>

* remove vscode exceptions from `.gitignore`

* delete .vscode directory

* style: format for `ruff check`

* fix(tests): update `test_update_widgets_not_selected` test

* remove Send2Trash dependency

* refactor: use dataclass for MediaCateogry

* refactor: use enums for UI colors

* docs: add file docstring for silent_Popen

* refactor: replace logger with structlog

* use early return inside `ResourceManager.get()`

* add `is_ext_in_category()` method to `MediaCategory`

Add method to check if an extension is a member of a given MediaCategory.

* style: fix docstring style, missing type hints, rename `afm`

* fix: use structlog vars in logging

* refactor: move platform-dependent strings to PlatformStrings

* refactor: move `parents[2]` path to variable

* fix: undo logger regressions

---------

Co-authored-by: Ethnogeny <111099761+050011-code@users.noreply.github.com>
Co-authored-by: Theasacraft <91694323+Thesacraft@users.noreply.github.com>
Co-authored-by: SupKittyMeow <77246128+supkittymeow@users.noreply.github.com>
Co-authored-by: EJ Stinson <93455158+favroitegamers@users.noreply.github.com>
Co-authored-by: Sean Krueger <71362472+seakrueger@users.noreply.github.com>
2024-10-07 14:14:01 -07:00
yed
e0752828db feat: store Entry suffix separately (#503)
* feat: save entry suffix separately

* change LibraryPrefs to allow identical values, add test
2024-10-07 14:09:57 -07:00
Travis Abendshien
1c7aaf0a16 Revert "translations: update from Hosted Weblate (#530)"
This reverts commit fe207062d5.
2024-10-02 14:48:01 -07:00
Weblate (bot)
fe207062d5 translations: update from Hosted Weblate (#530)
* Added translation using Weblate (Russian)

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Added translation using Weblate (Portuguese)

* Added translation using Weblate (Portuguese (Brazil))

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.4% (66 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.4% (66 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Added translation using Weblate (Tamil)

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Tamil)

Currently translated at 51.4% (73 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Added translation using Weblate (Spanish)

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Tamil)

Currently translated at 88.0% (125 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Spanish)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 19.7% (28 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 19.7% (28 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Translated using Weblate (Spanish)

Currently translated at 29.5% (42 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 29.5% (42 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 49.2% (70 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 49.2% (70 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 52.1% (74 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (Tamil)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 55.6% (79 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Added translation using Weblate (French)

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Translated using Weblate (Spanish)

Currently translated at 56.3% (80 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 56.3% (80 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 68.3% (97 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Translated using Weblate (French)

Currently translated at 40.1% (57 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (Spanish)

Currently translated at 65.4% (93 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 65.4% (93 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 93.6% (133 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 93.6% (133 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Added translation using Weblate (Danish)

* Added translation using Weblate (German)

* Translated using Weblate (Danish)

Currently translated at 1.4% (2 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Added translation using Weblate (Cantonese (Traditional Han script))

* Translated using Weblate (Tamil)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (German)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Added translation using Weblate (Hungarian)

* Translated using Weblate (German)

Currently translated at 14.0% (20 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Hungarian)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/

* Translated using Weblate (German)

Currently translated at 14.7% (21 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Added translation using Weblate (Swedish)

* Translated using Weblate (German)

Currently translated at 71.8% (102 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Swedish)

Currently translated at 78.8% (112 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/

* Added translation using Weblate (Toki Pona)

* Translated using Weblate (Tamil)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Danish)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Translated using Weblate (Toki Pona)

Currently translated at 83.0% (118 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/

* Added translation using Weblate (Italian)

* Translated using Weblate (French)

Currently translated at 64.0% (91 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (German)

Currently translated at 76.0% (108 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Hungarian)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/

* Translated using Weblate (Toki Pona)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/

* Translated using Weblate (Italian)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/

* Added translation using Weblate (Turkish)

* Translated using Weblate (Turkish)

Currently translated at 88.7% (126 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (French)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (French)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

---------

Co-authored-by: Artyom Ognev <greatprincestolas@gmail.com>
Co-authored-by: Space_Fox <indtekworldbaj99@gmail.com>
Co-authored-by: Lobo Metalúrgico <hugolino1000@gmail.com>
Co-authored-by: Vasi <vasigaranvip195@gmail.com>
Co-authored-by: Nginearing <leonmarshall08@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Ryussei <weblate.or30w@aleeas.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Zoinx <ammesammegaming@gmail.com>
Co-authored-by: gold <goldstargloww@gmail.com>
Co-authored-by: William de Castro <williamtdcastro@gmail.com>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Co-authored-by: Nyghl <hknimre@gmail.com>
Co-authored-by: Obscaeris <mathysuzin@gmail.com>
2024-10-01 17:12:07 -07:00
Travis Abendshien
02ac69978d Revert "translations: update from Hosted Weblate (#526)"
This reverts commit c37c4a95a7.
2024-10-01 17:07:01 -07:00
Weblate (bot)
c37c4a95a7 translations: update from Hosted Weblate (#526)
* Added translation using Weblate (Russian)

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Added translation using Weblate (Portuguese)

* Added translation using Weblate (Portuguese (Brazil))

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.4% (66 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 46.4% (66 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Added translation using Weblate (Tamil)

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Tamil)

Currently translated at 51.4% (73 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Added translation using Weblate (Spanish)

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/pt_BR/

* Translated using Weblate (Tamil)

Currently translated at 88.0% (125 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Spanish)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 19.7% (28 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 19.7% (28 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Translated using Weblate (Spanish)

Currently translated at 29.5% (42 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 29.5% (42 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 49.2% (70 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 49.2% (70 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 52.1% (74 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Added translation using Weblate (Norwegian Bokmål)

* Translated using Weblate (Tamil)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 55.6% (79 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Added translation using Weblate (French)

* Translated using Weblate (Russian)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/

* Translated using Weblate (Spanish)

Currently translated at 56.3% (80 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 56.3% (80 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 68.3% (97 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nb_NO/

* Translated using Weblate (French)

Currently translated at 40.1% (57 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (Spanish)

Currently translated at 65.4% (93 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 65.4% (93 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 74.6% (106 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 83.8% (119 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 93.6% (133 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 93.6% (133 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/es/

* Added translation using Weblate (Danish)

* Added translation using Weblate (German)

* Translated using Weblate (Danish)

Currently translated at 1.4% (2 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Added translation using Weblate (Cantonese (Traditional Han script))

* Translated using Weblate (Tamil)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (German)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Added translation using Weblate (Hungarian)

* Translated using Weblate (German)

Currently translated at 14.0% (20 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Hungarian)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/

* Translated using Weblate (German)

Currently translated at 14.7% (21 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Added translation using Weblate (Swedish)

* Translated using Weblate (German)

Currently translated at 71.8% (102 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Swedish)

Currently translated at 78.8% (112 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/sv/

* Added translation using Weblate (Toki Pona)

* Translated using Weblate (Tamil)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ta/

* Translated using Weblate (Danish)

Currently translated at 2.8% (4 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/da/

* Translated using Weblate (Toki Pona)

Currently translated at 83.0% (118 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/

* Added translation using Weblate (Italian)

* Translated using Weblate (French)

Currently translated at 64.0% (91 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (German)

Currently translated at 76.0% (108 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/

* Translated using Weblate (Hungarian)

Currently translated at 99.2% (141 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/hu/

* Translated using Weblate (Toki Pona)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/

* Translated using Weblate (Italian)

Currently translated at 11.2% (16 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/

* Added translation using Weblate (Turkish)

* Translated using Weblate (Turkish)

Currently translated at 88.7% (126 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tr/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* Translated using Weblate (French)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fr/

* Translated using Weblate (English)

Currently translated at 100.0% (142 of 142 strings)

Translation: TagStudio/Strings
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/en/

* fix: remove unused strings and sort lists

* chore: update .git-blame-ignore-revs

---------

Co-authored-by: Artyom Ognev <greatprincestolas@gmail.com>
Co-authored-by: Space_Fox <indtekworldbaj99@gmail.com>
Co-authored-by: Lobo Metalúrgico <hugolino1000@gmail.com>
Co-authored-by: Vasi <vasigaranvip195@gmail.com>
Co-authored-by: Nginearing <leonmarshall08@gmail.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Co-authored-by: Bamowen <mathieu.monsauret@gmail.com>
Co-authored-by: Ryussei <weblate.or30w@aleeas.com>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Zoinx <ammesammegaming@gmail.com>
Co-authored-by: gold <goldstargloww@gmail.com>
Co-authored-by: William de Castro <williamtdcastro@gmail.com>
Co-authored-by: Jann Stute <jann.stute@protonmail.com>
Co-authored-by: Nyghl <hknimre@gmail.com>
Co-authored-by: Travis Abendshien <lvnvtravis@gmail.com>
2024-09-27 15:21:51 -07:00
Travis Abendshien
49d071cf2b docs: add weblate link to readme 2024-09-22 20:27:40 -07:00
Bamowen
4cd70d2c4f add string tokens for en.json (#507)
* Add en.json with strings found in code

* remove unused, internal, and logging strings

This removes any string tokens for unused/unfinished features, internally facing code, and log outputs.

---------

Co-authored-by: Travis Abendshien <lvnvtravis@gmail.com>
2024-09-22 20:18:34 -07:00
FB100
073d51734b fix: correct typo in test_driver.py comment (#496) 2024-09-13 17:26:05 -07:00
Travis Abendshien
e51da278fa squash .git-blame-ignore.revs 2024-09-13 00:30:48 -07:00
Travis Abendshien
b6e2167605 ci(ruff)!: update ruff linter config, refactor to comply (#499)
* ci: update ruff linter config

- Set line length to 100
- Enforce Google-style docstrings
- Lint docstrings and imports

* ci(ruff): exclude missing docstring warnings

* ci(ruff): exclude docstring checks from tests dir

* fix(ruff): change top level linter setting

Fix Ruff warning: `warning: The top-level linter settings are deprecated in favour of their counterparts in the `lint` section. Please update the following options in `pyproject.toml`:
  - 'per-file-ignores' -> 'lint.per-file-ignores'`.

* chore: format with ruff

* add `.git-blame-ignore-revs`

* ci(ruff): add E402 and F541 checks

* ci(ruff): add FBT003 check (#500)

* ci(ruff): add T20 check

* ci(ruff)!: add N check, refactor method names

* ci(ruff): add E501 check, refactor strings

Much commented-out code is removed in this commit.

* update `.git-blame-ignore.revs`

---------

Co-authored-by: yed <yedpodtrzitko@users.noreply.github.com>
2024-09-13 00:28:00 -07:00
yed
c15963868e feat: make search results more ergonomic (#498) 2024-09-12 17:34:27 -07:00
Travis Abendshien
a8fdae8ebc docs: refer to conventional commits in CONTRIBUTING.md
Co-Authored-By: Xarvex <60973030+xarvex@users.noreply.github.com>
2024-09-12 15:58:08 -07:00
Tyrannicodin
2f2f763a29 fix(search): remove wildcard requirement for tags (#481)
* Fix tag search to not require wildcards

* Add partial tag check to test_tag_search

* chore: format with ruff

---------

Co-authored-by: Tyrannicodin <tyrannicodin@gmail.com>
Co-authored-by: Travis Abendshien <lvnvtravis@gmail.com>
2024-09-12 15:02:56 -07:00
yed
4942d1633c refactor: cleanup the refresh_dir code, update tests (#494)
* feat: take Ignore List into consideration when refreshing directory

* undo the extension check in refresh_dir
2024-09-12 14:54:20 -07:00
yed
af642a7d29 fix: prevent error on closing library (#484) 2024-09-10 01:31:33 -07:00
Sean Krueger
2e8efa288d fix(flake): add missing x11 dependency (#478) 2024-09-09 11:34:55 -07:00
Travis Abendshien
fb949b82b7 Revert "ci: add README.md to publish_docs workflow"
This reverts commit 98f4246ff4.
2024-09-08 22:36:03 -07:00
Travis Abendshien
98f4246ff4 ci: add README.md to publish_docs workflow 2024-09-08 22:19:21 -07:00
Travis Abendshien
d75d344439 fix(docs): use valid note callout 2024-09-08 22:13:03 -07:00
Travis Abendshien
aeb7972206 docs: add SQL warning to CONTRIBUTING.md 2024-09-08 22:08:45 -07:00
Travis Abendshien
693a7bc160 docs: update top README warnings 2024-09-08 22:08:37 -07:00
yed
e5e7b8afc6 refactor!: use SQLite and SQLAlchemy for database backend (#332)
* use sqlite + sqlalchemy as a database backend

* change entries getter

* page filterstate.page_size persistent

* add test for entry.id filter

* fix closing library

* fix tag search, adding field

* add field position

* add fields reordering

* use folder

* take field position into consideration

* fix adding tag

* fix test

* try to catch the correct exception, moron

* dont expunge subtags

* DRY models

* rename LibraryField, add is_default property

* remove field.position unique constraint
2024-09-08 22:06:01 -07:00
Sean Krueger
67f7e4dcf9 fix(docs): use correct formatting on FFmpeg help page (#475)
* fix: Add spacing between list elements

* fix: Add title and fixup headers
2024-09-08 11:11:29 -07:00
Sean Krueger
99d3235774 docs: add ffmpeg installation guide (#473)
* docs: Add ffmpeg installation guide

* docs: Add discord invite link

* grammar

* spelling
2024-09-08 10:29:00 -07:00
Bamowen
60cad202da docs: add discord server link in CONTRIBUTING.md (#474) 2024-09-08 10:28:13 -07:00
Knaughty
9b5c26a61e fix(docs): correct grammar mistake in CONTRIBUTING.md. (#452)
Removed unnecessary with in "- Lint code *with* by [doing x]"
2024-09-07 20:34:11 -07:00
Travis Abendshien
f472d22e10 ci: add publish_docs.yaml to publish_docs paths 2024-09-07 07:39:28 -07:00
CarterPillow
844dae0f58 fix(ci): update site_url in mkdocs.yml (#467)
Configure MkDocs to use the custom domain: `docs.tagstud.io`
2024-09-07 07:34:20 -07:00
Travis Abendshien
d04d3b1f0a ci: specify push paths for publish_docs 2024-09-07 07:30:08 -07:00
Travis Abendshien
686e803f3e ci: add CNAME docs.tagstud.io 2024-09-07 00:35:19 -07:00
Travis Abendshien
a8f9bec65c ci: route mkdocs edit_uri to blob/main/docs 2024-09-07 00:20:06 -07:00
xarvex
0b56e7344f fix(ci): single character typo for mkdocs 2024-09-07 01:30:25 -05:00
Paul Friederichsen
af4ef217a5 ci: use MkDocs for documentation site (#460)
* Use MkDocs for documentation

Using Material for MkDocs

* Enable edit buttons on pages

* Updates to mkdocs.yml
2024-09-06 23:10:39 -07:00
Travis Abendshien
8469131006 docs: add ffmpeg requirement 2024-09-03 16:32:47 -07:00
388 changed files with 29511 additions and 43586 deletions

View File

@@ -1,16 +0,0 @@
# vi: ft=bash
#
# If you wish to use this file, copy or symlink it to `.envrc` for direnv to read it.
# This will use the flake development shell.
if ! has nix_direnv_version || ! nix_direnv_version 3.0.5; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.5/direnvrc" "sha256-RuwIS+QKFj/T9M2TFXScjBsLR6V3A17YVoEW/Q6AZ1w="
fi
devenv_root_file="$(mktemp -t devenv-root-XXXXXXXX)"
printf %s "${PWD}" >"${devenv_root_file}"
if ! use flake . --override-input devenv-root "file+file://${devenv_root_file}"; then
printf '%s\n' "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
fi
rm "${devenv_root_file}"
unset devenv_root_file

7
.git-blame-ignore-revs Normal file
View File

@@ -0,0 +1,7 @@
# Date: Fri, 3 May 2024 19:40:36 -0700
# Reformatted using Ruff
089c8dd50cbf22e75721ecd2c0e0acfd3deca7f3
# Date: Fri, 13 Sep 2024 00:28:00 -0700
# ci(ruff)!: update ruff linter config, refactor to comply
b6e216760557c5507b12f210e1e48c531f49ffa3

2
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
---
patreon: cyanvoxel

View File

@@ -1,3 +1,4 @@
---
name: Bug Report
description: File a bug or issue report.
title: '[Bug]: '
@@ -8,7 +9,7 @@ body:
value: |
*Please add an appropriate title for this issue.*
Before reporting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
Before reporting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
Validate that you are using an up-to-date version[^1], your issue might already be fixed!
Questions, guidance, and usage goes in [discussions](https://github.com/TagStudioDev/TagStudio/discussions). Invalid issues will be closed.
@@ -20,7 +21,7 @@ body:
options:
- label: I am using an up-to-date version.
required: true
- label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md).
- label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md).
required: true
- label: I have searched existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
required: true
@@ -51,7 +52,7 @@ body:
- type: textarea
attributes:
label: Steps to Reproduce
description: Minimal steps neded for the problem to occur.
description: Minimal steps needed for the problem to occur.
placeholder: |
1.
2.

View File

@@ -1,3 +1,4 @@
---
name: Feature Request
description: Suggest a new feature.
title: '[Feature Request]: '
@@ -8,7 +9,7 @@ body:
value: |
*Please add an appropriate title for this feature request.*
Before suggesting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
Before suggesting, read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md) and search existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
Validate that you are using an up-to-date version[^1], your feature might already be implemented!
Questions, guidance, and usage goes in [discussions](https://github.com/TagStudioDev/TagStudio/discussions). Invalid issues will be closed.
@@ -20,7 +21,7 @@ body:
options:
- label: I am using an up-to-date version.
required: true
- label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/doc/index.md).
- label: I have read the [documentation](https://github.com/TagStudioDev/TagStudio/blob/main/docs/index.md).
required: true
- label: I have searched existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
required: true

30
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,30 @@
### Summary
<!--
^^^ Summarize the changes done and why they were done above.
By submitting this pull request, you certify that you have read the
[CONTRIBUTING.md](https://github.com/TagStudioDev/TagStudio/blob/main/CONTRIBUTING.md).
IMPORTANT FOR FEATURES: Please verify that a feature request or some other form
of communication with maintainers was already conducted in terms of approving.
Thank you for your eagerness to contribute!
-->
### Tasks Completed
<!-- 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 -->

View File

@@ -1,52 +0,0 @@
name: PySide App Test
on: [ push, pull_request ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install system dependencies
run: |
# dont run update, it is slow
# sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libxkbcommon-x11-0 \
x11-utils \
libyaml-dev \
libegl1-mesa \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-xinerama0 \
libopengl0 \
libxcb-cursor0 \
libpulse0 \
ffmpeg
- name: Install dependencies
run: |
pip install -Ur requirements.txt
- name: Run TagStudio app and check exit code
run: |
xvfb-run --server-args="-screen 0, 1920x1200x24 -ac +extension GLX +render -noreset" python tagstudio/tag_studio.py --ci -o /tmp/
exit_code=$?
if [ $exit_code -eq 0 ]; then
echo "TagStudio ran successfully"
else
echo "TagStudio failed with exit code $exit_code"
exit 1
fi

View File

@@ -1,37 +1,34 @@
---
name: MyPy
on: [ push, pull_request ]
on: [push, pull_request]
jobs:
mypy:
name: Run MyPy
runs-on: ubuntu-latest
steps:
- name: Checkout code
- name: Checkout repo
uses: actions/checkout@v4
- uses: reviewdog/action-setup@v1
- name: Setup reviewdog
uses: reviewdog/action-setup@v1
with:
reviewdog_version: latest
- uses: actions/setup-python@v5
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
cache: pip
- name: Install dependencies
- name: Install Python dependencies
run: |
pip install -r requirements.txt
pip install mypy==1.10.0
mkdir tagstudio/.mypy_cache
python -m pip install --upgrade uv
uv pip install --system .[mypy]
- uses: tsuyoshicho/action-mypy@v4
- name: Execute MyPy
uses: tsuyoshicho/action-mypy@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-check
fail_on_error: true
workdir: tagstudio
level: error
mypy_flags: --config-file ../pyproject.toml

49
.github/workflows/publish_docs.yaml vendored Normal file
View File

@@ -0,0 +1,49 @@
---
name: Publish Docs
on:
push:
branches:
- main
paths:
- .github/workflows/publish_docs.yaml
- docs/**
- mkdocs.yml
- CHANGELOG.md
permissions:
contents: write
concurrency:
group: publish-docs
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Install Python dependencies
run: |
python -m pip install --upgrade uv
uv pip install --system .[mkdocs]
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
- uses: actions/cache@v4
with:
key: mkdocs-material-${{ env.cache_id }}
path: .cache
restore-keys: |
mkdocs-material-
- name: Execute mkdocs
run: mkdocs gh-deploy --force

View File

@@ -1,22 +1,74 @@
---
name: pytest
on: [push, pull_request]
jobs:
pytest:
name: Run tests
runs-on: ubuntu-latest
name: Run pytest
runs-on: ubuntu-24.04
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- name: Run tests
- name: Install Python dependencies
run: |
pytest tagstudio/tests/
python -m pip install --upgrade uv
uv pip install --system .[pytest]
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libegl1 \
libgl1 \
libopengl0 \
libpulse0 \
libxcb-cursor0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-xinerama0 \
libxkbcommon-x11-0 \
libyaml-dev \
x11-utils
- name: Execute pytest
run: |
xvfb-run pytest --cov-report xml --cov=tagstudio
- name: Upload coverage
uses: actions/upload-artifact@v4
with:
name: coverage
path: coverage.xml
coverage:
name: Check coverage
runs-on: ubuntu-latest
needs: pytest
steps:
- name: Fetch coverage
uses: actions/download-artifact@v4
with:
name: coverage
- name: Check coverage
uses: yedpodtrzitko/coverage@main
with:
thresholdAll: 0.4
thresholdNew: 0.4
thresholdModified: 0.4
coverageFile: coverage.xml
token: ${{ secrets.GITHUB_TOKEN }}
sourceDir: tagstudio/src

View File

@@ -1,3 +1,4 @@
---
name: Release
on:
@@ -19,15 +20,27 @@ jobs:
suffix: _portable
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -Ur requirements.txt pyinstaller
- run: pyinstaller tagstudio.spec -- ${{ matrix.build-flag }}
- run: tar czfC dist/tagstudio_linux_x86_64${{ matrix.suffix }}.tar.gz dist tagstudio
- uses: actions/upload-artifact@v4
cache: pip
- name: Install Python dependencies
run: |
python -m pip install --upgrade uv
uv pip install --system .[pyinstaller]
- name: Execute PyInstaller
run: |
pyinstaller tagstudio.spec -- ${{ matrix.build-flag }}
tar czfC dist/tagstudio_linux_x86_64${{ matrix.suffix }}.tar.gz dist tagstudio
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tagstudio_linux_x86_64${{ matrix.suffix }}
path: dist/tagstudio_linux_x86_64${{ matrix.suffix }}.tar.gz
@@ -35,26 +48,41 @@ jobs:
macos:
strategy:
matrix:
os-version: ['12', '14']
os-version: ['13', '14']
include:
- os-version: '12'
- os-version: '13'
arch: x86_64
- os-version: '14'
arch: aarch64
runs-on: macos-${{ matrix.os-version }}
env:
# even though we run on 12, target towards compatibility
# INFO: Even though we run on 13, target towards compatibility
MACOSX_DEPLOYMENT_TARGET: '11.0'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -Ur requirements.txt pyinstaller
- run: pyinstaller tagstudio.spec
- run: tar czfC dist/tagstudio_macos_${{ matrix.arch }}.tar.gz dist TagStudio.app
- uses: actions/upload-artifact@v4
cache: pip
- name: Install Python dependencies
run: |
python -m pip install --upgrade uv
uv pip install --system .[pyinstaller]
- name: Execute PyInstaller
run: |
pyinstaller tagstudio.spec
tar czfC dist/tagstudio_macos_${{ matrix.arch }}.tar.gz dist TagStudio.app
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tagstudio_macos_${{ matrix.arch }}
path: dist/tagstudio_macos_${{ matrix.arch }}.tar.gz
@@ -72,30 +100,51 @@ jobs:
build-flag: --portable
suffix: _portable
file-end: .exe
runs-on: windows-2019
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- name: Checkout repo
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install -Ur requirements.txt pyinstaller
- run: PyInstaller tagstudio.spec -- ${{ matrix.build-flag }}
- run: Compress-Archive -Path dist/TagStudio${{ matrix.file-end }} -DestinationPath dist/tagstudio_windows_x86_64${{ matrix.suffix }}.zip
- uses: actions/upload-artifact@v4
cache: pip
- name: Install Python dependencies
run: |
python -m pip install --upgrade uv
uv pip install --system .[pyinstaller]
- name: Execute PyInstaller
run: |
PyInstaller tagstudio.spec -- ${{ matrix.build-flag }}
Compress-Archive -Path dist/TagStudio${{ matrix.file-end }} -DestinationPath dist/tagstudio_windows_x86_64${{ matrix.suffix }}.zip
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: tagstudio_windows_x86_64${{ matrix.suffix }}
path: dist/tagstudio_windows_x86_64${{ matrix.suffix }}.zip
publish:
needs: [linux, macos, windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: softprops/action-gh-release@v2
- name: Checkout repo
uses: actions/checkout@v4
- name: Fetch artifacts
uses: actions/download-artifact@v4
- name: Publish release
uses: softprops/action-gh-release@v2
with:
files: |
tagstudio_linux_x86_64/*

View File

@@ -1,11 +1,31 @@
---
name: Ruff
on: [ push, pull_request ]
on: [push, pull_request]
jobs:
ruff:
ruff-format:
name: Run Ruff format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: chartboost/ruff-action@v1
- name: Checkout repo
uses: actions/checkout@v4
- name: Execute Ruff format
uses: chartboost/ruff-action@v1
with:
version: 0.4.2
args: 'format --check'
version: 0.8.1
args: format --check
ruff-check:
name: Run Ruff check
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Execute Ruff check
uses: chartboost/ruff-action@v1
with:
version: 0.8.1
args: check

33
.gitignore vendored
View File

@@ -55,7 +55,6 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/
tagstudio/tests/fixtures/library/*
# Translations
*.mo
@@ -89,9 +88,7 @@ profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
@@ -141,6 +138,9 @@ venv.bak/
# Rope project settings
.ropeproject
# macoS
*.DS_Store
# mkdocs documentation
/site
@@ -163,7 +163,7 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
@@ -175,6 +175,10 @@ poetry.toml
# LSP config files
pyrightconfig.json
# Syncthing
.stfolder/
.stignore
### Qt ###
# C++ objects and libs
*.slo
@@ -232,11 +236,11 @@ compile_commands.json
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# !.vscode/settings.json
# !.vscode/tasks.json
# !.vscode/launch.json
# !.vscode/extensions.json
# !.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
@@ -248,12 +252,19 @@ compile_commands.json
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,qt
# TagStudio
.TagStudio
!*/tests/**/.TagStudio
tagstudio/tests/fixtures/library/*
tagstudio/tests/fixtures/json_library/.TagStudio/*.sqlite
TagStudio.ini
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,qt
*.sqlite-journal
.envrc
.direnv
.devenv
result
result-*

View File

@@ -1,6 +1,26 @@
---
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.4.2
- repo: local
hooks:
- id: mypy
name: mypy
entry: mypy
language: system
types_or: [python, pyi]
require_serial: true
- id: ruff
name: ruff
entry: ruff check
language: system
types_or: [python, pyi, jupyter]
args: [--force-exclude]
require_serial: true
- id: ruff-format
name: ruff-format
entry: ruff format
language: system
types_or: [python, pyi, jupyter]
args: [--force-exclude, --check]
require_serial: true

17
.vscode/launch.json vendored
View File

@@ -1,17 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "TagStudio",
"type": "python",
"request": "launch",
"program": "${workspaceRoot}/tagstudio/tag_studio.py",
"console": "integratedTerminal",
"justMyCode": true,
"args": []
}
]
}

View File

@@ -1,69 +0,0 @@
#! /usr/bin/env bash
# GETTING BASE DIR
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
# SETTING UP CONSTANTS
TAGSTUDIO_NAME="TagStudio"
TAGSTUDIO_DIR="$SCRIPT_DIR/tagstudio"
TAGSTUDIO_DIR_RESOURCES="$TAGSTUDIO_DIR/resources"
TAGSTUDIO_ICON="$TAGSTUDIO_DIR/resources/icon.ico"
TAGSTUDIO_SRC="$TAGSTUDIO_DIR/src"
TAGSTUDIO_MAIN="$TAGSTUDIO_DIR/tag_studio.py"
DIST_PATH="$SCRIPT_DIR/dist"
BUILD_PATH="$SCRIPT_DIR/build"
LOGS_PATH="$BUILD_PATH/logs"
printf -- "🏁 Starting Script \n"
# CREATE VENV AND INSTALL REQUIREMENTS
printf -- "🐍 Creating Python virtual env\n"
python3 -m venv .venv
source .venv/bin/activate
if [ ! -d $LOGS_PATH ]; then
printf -- "📁 Creating Logs folder\n"
mkdir -p $LOGS_PATH;
fi
printf -- "💻 Installing Requirements \n"
pip install -r requirements.txt > "$LOGS_PATH/pip.log" 2>&1
pip install PyInstaller > "$LOGS_PATH/pip.log" 2>&1
if [[ "$OSTYPE" == "darwin"* ]]; then
printf -- "🍏 MacOS Detected \n"
SYS_CMD="--windowed"
OS=0
fi
SECONDS=0
# CREATE COMMAND
printf -- "⏳ Building App \n"
COMMAND=$( python -m PyInstaller \
--name "$TAGSTUDIO_NAME" \
--icon "$TAGSTUDIO_ICON" \
--add-data "$TAGSTUDIO_DIR_RESOURCES:./resources" \
--add-data "$TAGSTUDIO_SRC:./src" \
--distpath "$DIST_PATH" \
-p "$TAGSTUDIO_DIR" \
--noconsole \
--workpath "$BUILD_PATH" \
-y "$SYS_CMD" "$TAGSTUDIO_MAIN" \
> "$LOGS_PATH/pyinstaller.log" 2>&1 )
duration=$SECONDS
if $COMMAND; then
printf -- "✅ Build Successfull \n"
printf -- "$((duration)) seconds of build\n"
if [[ "$OS" == 0 ]]; then
printf -- "📁 Opening App folder \n"
open $DIST_PATH
fi
else
printf -- "❌ Error Building the app\nPlease read the logs\navailable at build/logs\n"
fi
printf -- "🏁 END OF TRANSMISSION"

View File

@@ -1,31 +0,0 @@
@echo off
set TAGSTUDIO_NAME=TagStudio
set TAGSTUDIO_DIR=tagstudio
set TAGSTUDIO_DIR_RESOURCES=%TAGSTUDIO_DIR%/resources
set TAGSTUDIO_ICON=%TAGSTUDIO_DIR%/resources/icon.ico
set TAGSTUDIO_SRC=%TAGSTUDIO_DIR%/src
set TAGSTUDIO_MAIN=%TAGSTUDIO_DIR%/tag_studio.py
set BUILD_MODE=--onedir
if "%1" == "--help" (
echo run "%~nx0" for normal Build
echo run "%~nx0 --portable" for Build packaged into one file
goto end
)
if "%1" == "--portable" (
echo Building portable executable...
set BUILD_MODE=--onefile
goto run
)
if not "%1" == "" (
echo Invalid argument run "%~nx0 --help" for help
goto end
)
:run
echo Building executable...
set COMMAND=PyInstaller --name "%TAGSTUDIO_NAME%" --icon "%TAGSTUDIO_ICON%" --add-data "%TAGSTUDIO_DIR_RESOURCES%:./resources" --add-data "%TAGSTUDIO_SRC%:./src" -p "%TAGSTUDIO_DIR%" --console %BUILD_MODE% "%TAGSTUDIO_MAIN%" -y
call .venv\Scripts\activate.bat
%COMMAND%
deactivate
:end

View File

@@ -1,77 +1,56 @@
# Contributing to TagStudio
_Last Updated: June 10th, 2024_
_Last Updated: March 8th, 2025_
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.
## Getting Started
- Check the [Planned Features](https://github.com/TagStudioDev/TagStudio/blob/main/doc/updates/planned_features.md) page, [FAQ](/README.md/#faq), as well as the open [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 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.
- Check the [Feature Roadmap](/docs/updates/roadmap.md) page to see what priority features there are, the [FAQ](/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)!
### Contribution Checklist
- I've read the [Planned Features](https://github.com/TagStudioDev/TagStudio/blob/main/doc/updates/planned_features.md) page
- I've read the [FAQ](/README.md/#faq), including the "[Features I Likely Won't Add/Pull](/README.md/#features-i-likely-wont-addpull)" section
- I've checked the open [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 _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 and Mypy
- I've read the [Code Guidelines](#code-guidelines) and/or [Documentation Guidelines](#documentation-guidelines)
- **_I mean it, I've found or created a new issue for my feature!_**
- I've read the [Feature Roadmap](/docs/updates/roadmap.md) page
- I've read the [FAQ](/README.md/#faq), including the "[Features I Likely Won't Add/Pull](/README.md/#features-i-likely-wont-addpull)" section
- 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 [Code Guidelines](#code-guidelines) and/or [Documentation Guidelines](#documentation-guidelines)
- **_I mean it, I've found or created an issue for my feature/fix!_**
> [!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.
## Creating a Development Environment
### Prerequisites
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.
- [Python](https://www.python.org/downloads/) 3.12
- [Ruff](https://github.com/astral-sh/ruff) (Included in `requirements-dev.txt`)
- [Mypy](https://github.com/python/mypy) (Included in `requirements-dev.txt`)
- [PyTest](https://docs.pytest.org) (Included in `requirements-dev.txt`)
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)".
### Creating a Python Virtual Environment
### Brief Instructions
If you wish to launch the source version of TagStudio outside of your IDE:
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`.
> [!IMPORTANT]
> Depending on your system, Python may be called `python`, `py`, `python3`, or `py3`. These instructions use the alias `python3` for consistency. You can check to see which alias your system uses and if it's for the correct Python version by typing `python3 --version` (or whichever alias) into your terminal.
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:
> [!TIP]
> On Linux and macOS, you can launch the `tagstudio.sh` script to skip the following process, minus the `requirements-dev.txt` installation step. _Using the script is fine if you just want to launch the program from source._
```
pip install -e .[dev]
```
1. In the root repository directory, create a python virtual environment:
`python3 -m venv .venv`
2. Activate your environment:
Otherwise, modify the command above for use with your dependency manager of choice. For example if using uv, you may use this:
- Windows w/Powershell: `.venv\Scripts\Activate.ps1`
- Windows w/Command Prompt: `.venv\Scripts\activate.bat`
- Linux/macOS: `source .venv/bin/activate`
3. Install the required packages:
- `pip install -r requirements.txt`
- If developing (includes Ruff and Mypy): `pip install -r requirements-dev.txt`
_Learn more about setting up a virtual environment [here](https://docs.python.org/3/tutorial/venv.html)._
### Manually Launching (Outside of an IDE)
- **Windows** (start_win.bat)
- To launch TagStudio, launch the `start_win.bat` file. You can modify this .bat file or create a shortcut and add one or more additional arguments if desired.
- **Linux/macOS** (TagStudio.sh)
- Run the "TagStudio.sh" script and the program should launch! (Make sure that the script is marked as executable if on Linux). Note that launching from the script from outside of a terminal will not launch a terminal window with any debug or crash information. If you wish to see this information, just launch the shell script directly from your terminal with `./TagStudio.sh`.
- **NixOS** (Nix Flake)
> [!WARNING]
> Support for NixOS is still a work in progress.
- Use the provided [Flake](https://nixos.wiki/wiki/Flakes) to create and enter a working environment by running `nix develop`. Then, run the program via `python3 tagstudio/tag_studio.py` from the root directory.
- **Any** (No Scripts)
- Alternatively, with the virtual environment loaded, run the python file at `tagstudio\tag_studio.py` from your terminal. If you're in the project's root directory, simply run `python3 tagstudio/tag_studio.py`.
```
uv pip install -e .[dev]
```
## Workflow Checks
@@ -86,8 +65,13 @@ A Python linter and code formatter. Ruff uses the `pyproject.toml` as its config
#### Running Locally
- Lint code with by moving into the `/tagstudio` directory with `cd tagstudio` and running `ruff --config ../pyproject.toml`.
- Format code with `ruff format` inside the repository directory
Inside the root repository directory:
- Lint code with `ruff check`
- Some linting suggestions can be automatically formatted with `ruff check --fix`
- Format code with `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).
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/).
@@ -97,74 +81,82 @@ Mypy is a static type checker for Python. It sure has a lot to say sometimes, bu
#### Running Locally
- **First time only:** Move into the `/tagstudio` directory with `cd tagstudio` and run the following:
- `mkdir -p .mypy_cache`
- `mypy --install-types --non-interactive`
- Check code by moving into the `/tagstudio` directory with `cd tagstudio` _(if you aren't already inside)_ and running `mypy --config-file ../pyproject.toml .`. _(Don't forget the `.` at the end!)_
> [!CAUTION]
> There's a known issue between PySide v6.6.3 and Mypy where Mypy will detect issues with the `.pyi` files inside of PySide and prematurely stop checking files. This issue is not present in PySide v6.6.2, which _should_ be compatible with everything else if you wish to try using that version in the meantime.
- **(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 moving into the `/tagstudio` directory with `cd tagstudio` and running `pytest tests/`.
- Run all tests by running `pytest tests/` in the repository root.
## Code Guidelines
### Style
## Code Style
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.
- Try to keep a maximum column with of no more than **100** characters.
- Code comments should be used to help describe sections of code that don'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 (in newly created python files).
- When writing text for window titles, form titles, or dropdown options, 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/)!
> [!WARNING]
> Column width limits, docstring formatting, and import sorting aren't currently checked in the Ruff workflow but likely will be in the near future.
- Do your best to write clear, concise, and modular code.
- Keep a maximum column with 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/)!
### Implementations
### Modules & Implementations
- Avoid direct calls to `os`
- Use `Pathlib` library instead of `os.path`
- Use `sys.platform` instead of `os.name`
- Don't prepend local imports with `tagstudio`, stick to `src`
- Use `logging` instead of `print` statements
- Avoid nested `f-string`s
- **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
#### Runtime
### Commit and Pull Request Style
- Code must function on supported versions of Windows, macOS, and Linux:
- Windows: 10, 11
- macOS: 12.0+
- Linux: TBD
- Avoid use of unnecessary logging statements in final submitted code.
- Code should not cause unreasonable slowdowns to the program outside of a progress-indicated task.
- 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.
#### Git/GitHub Specifics
> [!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.
- 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.
- Use imperative-style present-tense commit messages. Examples:
- "Add feature foo"
- "Change method bar"
- "Fix function foobar"
- 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.
> [!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?"_
### Runtime Requirements
- Final code must function on supported versions of Windows, macOS, and Linux:
- Windows: 10, 11
- macOS: 13.0+
- Linux: _Varies_
- Final code must **_NOT:_**
- Contain superfluous or unnecessary logging statements
- Cause unreasonable slowdowns to the program outside of a progress-indicated task
- Cause undesirable visual glitches or artifacts on screen
## Documentation Guidelines
Documentation contributions include anything inside of the `doc/` folder, as well as the `README.md` and `CONTRIBUTING.md` files.
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/).
- Use "[snake_case](https://developer.mozilla.org/en-US/docs/Glossary/Snake_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 "[snake_case](https://developer.mozilla.org/en-US/docs/Glossary/Snake_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
## Translation Guidelines
_TBA_
Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).
_Translation guidelines coming soon._

268
README.md
View File

@@ -1,82 +1,118 @@
# TagStudio (Alpha): A User-Focused Document Management System
# TagStudio: A User-Focused Document Management System
[![Translation](https://hosted.weblate.org/widget/tagstudio/strings/svg-badge.svg)](https://hosted.weblate.org/projects/tagstudio/strings/)
[![PyTest](https://github.com/TagStudioDev/TagStudio/actions/workflows/pytest.yaml/badge.svg)](https://github.com/TagStudioDev/TagStudio/actions/workflows/pytest.yaml)
[![MyPy](https://github.com/TagStudioDev/TagStudio/actions/workflows/mypy.yaml/badge.svg)](https://github.com/TagStudioDev/TagStudio/actions/workflows/mypy.yaml)
[![Ruff](https://github.com/TagStudioDev/TagStudio/actions/workflows/ruff.yaml/badge.svg)](https://github.com/TagStudioDev/TagStudio/actions/workflows/ruff.yaml)
[![Downloads](https://img.shields.io/github/downloads/TagStudioDev/TagStudio/total.svg?maxAge=2592001)](https://github.com/TagStudioDev/TagStudio/releases)
<p align="center">
<img width="60%" src="github_header.png">
<img width="60%" src="docs/assets/github_header.png">
</p>
> [!CAUTION]
> This is still a **_very_** rough personal project of mine in its infancy. Im open-sourcing it now in order to accept contributors sooner and to better facilitate the direction of the project from an earlier stage.
> There **_are_** bugs, and there will **_very likely_** be breaking changes!
TagStudio is a photo & file organization application with an underlying tag-based system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure. **Read the documentation and more at [docs.tagstud.io](https://docs.tagstud.io)!**
TagStudio is a photo & file organization application with an underlying system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure.
> [!NOTE]
> Thank you for being patient as we've migrated our database backend from JSON to SQL! The previous warnings about the main branch being experimental and unsupported have now been removed, and any pre-existing library save files created with official TagStudio releases are able to be opened and migrated with the new v9.5+ releases!
<figure align="center">
<img width="80%" src="screenshot.jpg" alt="TagStudio Screenshot" align="center">
> [!IMPORTANT]
> This project is still in an early state. There are many missing optimizations and QoL features, as well as the presence of general quirks and occasional jankiness. Making frequent backups of your library save data is **always** important, regardless of what state the program is in.
>
> With this in mind, TagStudio will _NOT:_
>
> - Touch, move, or mess with your files in any way _(unless explicitly using the "Delete File(s)" feature, which is locked behind a confirmation dialog)_.
> - Ask you to recreate your tags or libraries after new releases. It's our highest priority to ensure that your data safely and smoothly transfers over to newer versions.
> - Cause you to suddenly be able to recall your 10 trillion downloaded images that you probably haven't even seen firsthand before. You're in control here, and even tools out there that use machine learning still needed to be verified by human eyes before being deemed accurate.
<figcaption><i>TagStudio Alpha v9.1.0 running on Windows 10.</i></figcaption>
</figure>
<p align="center">
<img width="80%" src="docs/assets/screenshot.png" alt="TagStudio Screenshot">
</p>
<p align="center">
<i>TagStudio Alpha v9.5.0 running on macOS Sequoia.</i>
</p>
## Contents
- [Goals](#goals)
- [Priorities](#priorities)
- [Current Features](#current-features)
- [Contributing](#contributing)
- [Installation](#installation)
- [Usage](#usage)
- [FAQ](#faq)
- [Goals](#goals)
- [Priorities](#priorities)
- [Current Features](#current-features)
- [Contributing](#contributing)
- [Installation](#installation)
- [Usage](#usage)
- [FAQ](#faq)
## Goals
- To achieve a portable, privacy-oriented, open, extensible, and feature-rich system of organizing and rediscovering files.
- To provide powerful methods for organization, notably the concept of tag composition, or taggable tags”.
- To create an implementation of such a system that is resilient against a users actions outside the program (modifying, moving, or renaming files) while also not burdening the user with mandatory sidecar files or otherwise 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 darn thing look like nice, too. Its 2024, not 1994.
- 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 users 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. Its 2025, not 1995.
## Priorities
1. **The concept.** Even if TagStudio as a project or application fails, Id hope that the idea lives on in a superior project. The [goals](#goals) outlined above dont reference TagStudio once - _TagStudio_ is what references the _goals._
1. **The concept.** Even if TagStudio as an application fails, Id hope that the idea lives on in a superior project. The [goals](#goals) outlined above dont reference TagStudio once - _TagStudio_ is what references the _goals._
2. **The system.** Frontends and implementations can vary, as they should. The core underlying metadata management system is what should be interoperable between different frontends, programs, and operating systems. A standard implementation for this should settle as development continues. This opens up the doors for improved and varied clients, integration with third-party applications, and more.
3. **The application.** If nothing else, TagStudio the application serves as the first (and so far only) implementation for this system of metadata management. This has the responsibility of doing the idea justice and showing just whats possible when it comes to user file management.
4. (The name.) I think its fine for an app or client, but it doesnt really make sense for a system or standard. I suppose this will evolve with time.
## Current Features
- Create libraries/vaults centered around a system directory. Libraries contain a series of entries: the representations of your files combined with metadata fields. Each entry represents a file in your librarys directory, and is linked to its location.
- Add metadata to your library entries, including:
- Name, Author, Artist (Single-Line Text Fields)
- Description, Notes (Multiline Text Fields)
- Tags, Meta Tags, Content Tags (Tag Boxes)
- Create rich tags composed of a name, a list of aliases, and a list of “subtags” - being tags in which these tags inherit values from.
- Search for entries based on tags, ~~metadata~~ (TBA), or filenames/filetypes (using `filename: <query>`)
- Special search conditions for entries that are: `untagged`/`no tags` and `empty`/`no fields`.
> [!NOTE]
> For more information on the project itself, please see the [FAQ](#faq) section as well as the [documentation](/doc/index.md).
4. (The name.) I think its fine for an app or client, but it doesnt really make sense for a system or standard. I suppose this will evolve with time...
## Contributing
If you're interested in contributing to TagStudio, please take a look at the [contribution guidelines](/CONTRIBUTING.md) for how to get started!
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!
## Current Features
### Libraries
- Create libraries/vaults centered around a system directory. Libraries contain a series of entries: the representations of your files combined with metadata fields. Each entry represents a file in your librarys directory, and is linked to its location.
- Address moved, deleted, or otherwise "unlinked" files by using the "Fix Unlinked Entries" option in the Tools menu.
### Tagging + Custom Metadata
- Add custom powerful tags to your library entries
- Add metadata to your library entries, including:
- Name, Author, Artist (Single-Line Text Fields)
- Description, Notes (Multiline Text Fields)
- Create rich tags composed of a name, color, a list of aliases, and a list of "parent tags" - these being tags in which these tags inherit values from.
- Copy and paste tags and fields across file entries
- Automatically organize tags into groups based on parent tags marked as "categories"
- Generate tags from your existing folder structure with the "Folders to Tags" macro (NOTE: these tags do NOT sync with folders after they are created)
### 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
### File Entries
- Nearly all file types are supported in TagStudio libraries - just not all have dedicated thumbnail support.
- Preview most image file types, animated GIFs, videos, plain text documents, audio files, Blender projects, and more!
- Open files or file locations by right-clicking on thumbnails and previews and selecting the respective context menu options. You can also click on the preview panel image to open the file, and click the file path label to open its location.
- Delete files from both your library and drive by right-clicking the thumbnail(s) and selecting the "Move to Trash"/"Move to Recycle Bin" option.
> [!NOTE]
> For more information on the project itself, please see the [FAQ](#faq) section as well as the [documentation](https://docs.tagstud.io/)!
## Installation
To download TagStudio, visit the [Releases](https://github.com/TagStudioDev/TagStudio/releases) section of the GitHub repository and download the latest release for your system under the "Assets" section. TagStudio is available for **Windows**, **macOS** _(Apple Silicon & Intel)_, and **Linux**. Windows and Linux builds are also available in portable versions if you want a more self-contained executable to move around.
To download executable builds of TagStudio, visit the [Releases](https://github.com/TagStudioDev/TagStudio/releases) page of the GitHub repository and download the latest release for your system under the "Assets" section at the bottom of the release.
For video thumbnails and playback, you'll also need [FFmpeg](https://ffmpeg.org/download.html) installed on your system.
TagStudio has builds for **Windows**, **macOS** _(Apple Silicon & Intel)_, and **Linux**. We also offer portable releases for Windows and Linux which are self-contained and easier to move around.
> [!IMPORTANT]
> On macOS, you may be met with a message saying _""TagStudio" can't be opened because Apple cannot check it for malicious software."_ 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 _""TagStudio" was blocked from use because it is not from an identified developer."_ Click the "Open Anyway" button to allow TagStudio to run. You should only have to do this once after downloading the application.
For detailed instructions, installation help, and instructions for developing for TagStudio, please see the "[Installation](https://docs.tagstud.io/install/)" page on our documentation website.
#### Optional Arguments
<!-- prettier-ignore -->
> [!CAUTION]
> **We do not currently publish TagStudio to any package managers. Any TagStudio distributions outside of the GitHub [Releases](https://github.com/TagStudioDev/TagStudio/releases) page are _unofficial_ and not maintained by us.**
>
> Installation support will not be given to users installing from unofficial sources. Use these versions at your own risk!
Optional arguments to pass to the program.
### Third-Party Dependencies
> `--open <path>` / `-o <path>`
> Path to a TagStudio Library folder to open on start.
> `--config-file <path>` / `-c <path>`
> Path to the TagStudio config file to load.
For video thumbnails and playback, you'll also need [FFmpeg](https://ffmpeg.org/download.html) installed on your system. If you encounter any issues with this, please reference our [FFmpeg Help](/docs/help/ffmpeg.md) guide.
## Usage
@@ -86,14 +122,19 @@ With TagStudio opened, start by creating a new library or opening an existing on
### Refreshing the Library
In order to scan for new files or file changes, youll need to manually go to File -> Refresh Directories.
Libraries under 10,000 files automatically scan for new or modified files when opened. In order to refresh the library manually, select "Refresh Directories" under the File menu.
> [!NOTE]
> In the future, library refreshing will also be automatically done in the background, or additionally on app startup.
### Adding Tags to File Entries
### Adding Metadata to Entries
Access the "Add Tag" search box by either clicking on the "Add Tag" button at the bottom of the right sidebar, accessing the "Add Tags to Selected" option from the File menu, or by pressing <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>T</kbd>.
To add a metadata field to a file entry, start by clicking the “Add Field” button under the file preview in the right-hand preview panel. From the dropdown menu, select the type of metadata field youd like to add to the entry.
From here you can search for existing tags or create a new one if the one you're looking for doesn't exist. Click the "+" button next to any tags you want to to the currently selected file entries. To quickly add the top result, press the <kbd>Enter</kbd>/<kbd>Return</kbd> key to add the the topmost tag and reset the tag search. Press <kbd>Enter</kbd>/<kbd>Return</kbd> once more to close the dialog box. By using this method, you can quickly add various tags in quick succession just by using the keyboard!
To remove a tag from a file entry, hover over the tag in the preview panel and click on the "-" icon that appears.
### Adding Metadata to File Entries
To add a metadata field to a file entry, start by clicking the "Add Field" button at the bottom of the preview panel. From the dropdown menu, select the type of metadata field youd like to add to the entry
### Editing Metadata Fields
@@ -101,56 +142,55 @@ To add a metadata field to a file entry, start by clicking the “Add Field” b
Hover over the field and click the pencil icon. From there, add or edit text in the dialog box popup.
#### Tag Box
Click the “+” button at the end of the Tags list, and search for tags to add inside the new dialog popup. Click the “+” button next to whichever tags you want to add. Alternatively, after you search for a tag, press the Enter/Return key to add the add the first item in the list. Press Enter/Return once more to close the dialog box
> [!WARNING]
> Keyboard control and navigation is currently _very_ buggy, but will be improved in future versions.
### Creating Tags
To create a new tag, click on Edit -> New Tag from the menu bar. From there, enter a tag name, shorthand name, any tag aliases separated by newlines, any subtags, and an optional color.
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 **shorthand** is a type of alias that displays in situations when screen space is more valuable (ex. as a subtag for other tags).
- **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.
- **Subtags** are tags in which this tag is a child tag of. In other words, tags under this section are parents of this tag. For example, if you had a tag for a character from a show, you would make the show a subtag of this character. This would display as “Character (Show)” in most areas of the app. The first tag in this list is used as the tag shown in parentheses for specification.
- The **color** dropdown lets you select an optional color for this tag to display as.
- 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 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
You can manage your library of tags from opening the "Tag Manager" panel from Edit -> "Tag Manager". From here you can create, search for, edit, and permanently delete any tags you've created in your library.
### Editing Tags
To edit a tag, right-click the tag in the tag field of the preview pane and select Edit Tag
To edit a tag, click on it inside the preview panel or right-click the tag and select "Edit Tag" from the context menu.
### Relinking Renamed/Moved Files
### Relinking Moved Files
Inevitably, some of the files inside your library will be renamed, moved, or deleted. If a file has been renamed or moved, TagStudio will display the thumbnail as a red tag with a cross through it _(this icon is also used for items with broken thumbnails)._ To relink moved files or delete these entries, go to Tools -> Manage Unlinked Entries. Click the Refresh button to scan your library for unlinked entries. Once complete, you can attempt to Search & Relink any unlinked entries to their respective files, or Delete Unlinked Entries in the event the original files have been deleted and you no longer wish to keep their metadata entries inside your library.
Inevitably some of the files inside your library will be renamed, moved, or deleted. If a file has been renamed or moved, TagStudio will display the thumbnail as a red broken chain link. To relink moved files or delete these entries, select the "Manage Unlinked Entries" option under the Tools menu. Click the "Refresh" button to scan your library for unlinked entries. Once complete, you can attempt to "Search & Relink" any unlinked file entries to their respective files, or "Delete Unlinked Entries" in the event the original files have been deleted and you no longer wish to keep their entries inside your library.
> [!WARNING]
> There is currently no method to relink entries to files that have been renamed - only moved or deleted. This is a top priority for future releases.
> There is currently no method to relink entries to files that have been renamed - only moved or deleted. This is a high priority for future releases.
> [!WARNING]
> If multiple matches for a moved file are found (matches are currently defined as files with a matching filename as the original), TagStudio will currently ignore the match groups. Adding a GUI for manual selection, as well as smarter automated relinking, are top priorities for future versions.
> If multiple matches for a moved file are found (matches are currently defined as files with a matching filename as the original), TagStudio will currently ignore the match groups. Adding a GUI for manual selection, as well as smarter automated relinking, are high priorities for future versions.
### Saving the Library
Libraries are saved upon exiting the program. To manually save, select File -> Save Library from the menu bar. To save a backup of your library, select File -> Save Library Backup from the menu bar.
As of version 9.5, libraries are saved automatically as you go. To save a backup of your library, select File -> Save Library Backup from the menu bar.
### Half-Implemented Features
These features were present in pre-public versions of TagStudio (9.0 and below) and have yet to be fully implemented.
#### Fix Duplicate Files
Load in a .dupeguru file generated by [dupeGuru](https://github.com/arsenetar/dupeguru/) and mirror metadata across entries marked as duplicates. After mirroring, return to dupeGuru to manage deletion of the duplicate files. After deletion, use the Fix Unlinked Entries feature in TagStudio to delete the duplicate set of entries for the now-deleted files
Load in a .dupeguru file generated by [dupeGuru](https://github.com/arsenetar/dupeguru/) and mirror metadata across entries marked as duplicates. After mirroring, return to dupeGuru to manage deletion of the duplicate files. After deletion, use the "Fix Unlinked Entries" feature in TagStudio to delete the duplicate set of entries for the now-deleted files
> [!CAUTION]
> While this feature is functional, its a pretty roundabout process and can be streamlined in the future.
#### Image Collage
Create an image collage of your photos and videos.
> [!CAUTION]
> Collage sizes and options are hardcoded, and there's no GUI indicating the process of the collage creation.
#### Macros
Apply tags and other metadata automatically depending on certain criteria. Set specific macros to run when the files are added to the library. Part of this includes applying tags automatically based on parent folders.
@@ -173,74 +213,20 @@ See instructions in the "[Creating Development Environment](/CONTRIBUTING.md/#cr
### What State Is the Project Currently In?
As of writing (Alpha v9.3.0) the project is in a useable state, however it lacks proper testing and quality of life features.
As of writing (Alpha v9.5.0) the project is very usable, however there's some plenty of quirks and missing QoL features. Several additional features and changes are still planned (see: [Feature Roadmap](https://docs.tagstud.io/updates/roadmap/)) that add even more power and flexibility to the tagging and field systems while making it easier to tag in bulk and perform automated operations. Bugfixes and polish are constantly trickling in along with the larger feature releases.
### What Features Are You Planning on Adding?
> [!IMPORTANT]
> See the [Planned Features](/doc/updates/planned_features.md) documentation for the latest feature lists. The lists here are currently being migrated over there with individual pages for larger features.
See the [Feature Roadmap](https://docs.tagstud.io/updates/roadmap/) page for the core features being planned and implemented for TagStudio. For a more up to date look on what's currently being added for upcoming releases, see our GitHub [milestones](https://github.com/TagStudioDev/TagStudio/milestones) for versioned releases.
Of the several features I have planned for the project, these are broken up into “priority” features and “future” features. Priority features were originally intended for the first public release, however are currently absent from the Alpha v9.x.x builds.
### Features That Will NOT Be Added
#### Priority Features
- 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/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: [Goals/Privacy](#goals)). I wouldn't, however, mind using **locally** hosted models to provide the _optional_ ability for additional searching and tagging methods (especially when it comes to facial recognition) - but this would likely take the form of plugins external to the core program anyway.
- Improved search
- Sortable Search
- Boolean Search
- Coexisting Text + Tag Search
- Searchable File Metadata
- Comprehensive Tag management tab
- Easier ways to apply tags in bulk
- Tag Search Panel
- Recent Tags Panel
- Top Tags Panel
- Pinned Tags Panel
- Better (stable, performant) library grid view
- Improved entry relinking
- Cached thumbnails
- Tag-like Groups
- Resizable thumbnail grid
- User-defined metadata fields
- Multiple directory support
- SQLite (or similar) save files
- Reading of EXIF and XMP fields
- Improved UI/UX
- Better internal API for accessing Entries, Tags, Fields, etc. from the library.
- Proper testing workflow
- Continued code cleanup and modularization
- Exportable/importable library data including "Tag Packs"
### Why Is this Already Version 9?
#### Future Features
- Support for multiple simultaneous users/clients
- Draggable files outside the program
- Comprehensive filetype whitelist
- A finished “macro system” for automatic tagging based on predetermined criteria.
- Different library views
- Date and time fields
- Entry linking/referencing
- Audio waveform previews
- 3D object previews
- Additional previews for miscellaneous file types
- Optional global tags and settings, spanning across libraries
- Importing & exporting libraries to/from other programs
- Port to a more performant language and modern frontend (Rust?, Tauri?, etc.)
- Plugin system
- Local OCR search
- Support for local machine learning-based tag suggestions for images
- Mobile version _(FAR future)_
#### Features I Likely Wont Add/Pull
- 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. Pointing TagStudio to one of these mounts should function similarly to what native integration would look like.
- Native ChatGPT/Non-Local LLM Integration
- This could mean different things depending on what you're intending. 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: [Goals/Privacy](#goals)). I wouldn't, however, mind using **locally** hosted models to provide the _optional_ ability for additional searching and tagging methods (especially when it comes to facial recognition).
### Why Is the Version Already v9?
Ive been developing this project over several years in private, and have gone through several major iterations and rewrites in that time. This “major version” is just a number at the end of the day, and if I wanted to I couldnt released this as “Version 0” or “Version 1.0”, but Ive decided to stick to my original version numbers to avoid needing to go in and change existing documentation and code comments. Version 10 is intended to include all of the “Priority Features” Ive outlined in the [previous](#what-features-are-you-planning-on-adding) section. Ive also labeled this version as an Alpha, and will likely reset the numbers when a feature-complete beta is reached.
### Wait, Is There a CLI Version?
As of right now, **no**. However, I _did_ have a CLI version in the recent past before dedicating my efforts to the Qt GUI version. Ive left in the currently-inoperable CLI code just in case anyone was curious about it. Also yes, its just a bunch of glorified print statements (_the outlook for some form of curses on Windows didnt look great at the time, and I just needed a driver for the newly refactored code...)._
Over the first few years of private development the project went through several major iterations and rewrites. These major version bumps came quickly, and by the time TagStudio was opened-sourced the version number had already reached v9.0. Instead of resetting to "v0.0" or "v1.0" for this public release I decided to keep my v9.x numbering scheme and reserve v10.0 for when all the core features on the [Feature Roadmap](https://docs.tagstud.io/updates/roadmap/) are implemented. Ive also labeled this version as an "Alpha" and will drop this once either all of the core features are implemented or the project feels stable and feature-rich enough to be considered "Beta" and beyond.

View File

@@ -1,7 +0,0 @@
#! /usr/bin/env bash
set -e
cd "$(dirname "$0")"
! [ -d .venv ] && python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python tagstudio/tag_studio.py

19
contrib/.envrc-nix Normal file
View File

@@ -0,0 +1,19 @@
# shellcheck shell=bash
# If you wish to use this file, symlink or copy it to `.envrc` for direnv to read it.
# This will use the Nix flake development shell.
#
# ln -s contrib/.envrc-nix .envrc
if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc" "sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM="
fi
watch_file nix/shell.nix pyproject.toml
use flake
# Only watch now, or direnv will execute again if created or modified by itself.
watch_file "${UV_PROJECT_ENVIRONMENT:-.venv}"/bin/activate
# vi: ft=bash

32
contrib/.envrc-uv Normal file
View File

@@ -0,0 +1,32 @@
# shellcheck shell=bash
# If you wish to use this file, symlink or copy it to `.envrc` for direnv to read it.
# This will use a virtual environment created by uv.
#
# ln -s contrib/.envrc-uv .envrc
watch_file .python-version pyproject.toml uv.lock
venv="$(expand_path "${UV_PROJECT_ENVIRONMENT:-.venv}")"
if [ ! -f "${venv}"/bin/activate ]; then
printf '%s\n' 'Generating virtual environment...' >&2
rm -rf "${venv}"
uv venv "${venv}"
fi
# Only watch now, or direnv will execute again if created or modified by itself.
watch_file "${venv}"/bin/activate
# shellcheck disable=SC1091
source "${venv}"/bin/activate
if [ ! -f "${venv}"/pyproject.toml ] || ! diff --brief pyproject.toml "${venv}"/pyproject.toml >/dev/null; then
printf '%s\n' 'Installing dependencies, pyproject.toml changed...' >&2
uv pip install --quiet --editable '.[dev]'
cp pyproject.toml "${venv}"/pyproject.toml
fi
pre-commit install
# vi: ft=bash

17
contrib/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "TagStudio",
"type": "python",
"request": "launch",
"program": "${workspaceRoot}/src/tagstudio/main.py",
"console": "integratedTerminal",
"justMyCode": true,
"args": [
"-o",
"~/Documents/Example"
]
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

View File

@@ -1,24 +0,0 @@
# Welcome to the TagStudio Documentation!
> [!WARNING]
> This documentation is still a work in progress, and is intended to aide with deconstructing and understanding of the core mechanics of TagStudio and how it operates.
<div align="center">
<img src="../github_header.png" alt="TagStudio Alpha" height="100">
<img src="https://i0.wp.com/www.bapl.org/wp-content/uploads/2019/02/old-under-construction-gif.gif" alt="Under Construction" height="100">
</div>
## Table of Contents
- [Library](/doc/library/library.md)
- [Entries](/doc/library/entry.md)
- [Fields](/doc/library/field.md)
- [Tags](/doc/library/tag.md)
- [Tools & Macros](/doc/utilities/macro.md)
- [Planned Features](/doc/updates/planned_features.md)
---
### [Database Migration](/doc/updates/db_migration.md)
The "Database Migration", "DB Migration", or "SQLite Migration" is an upcoming update to TagStudio which will replace the current JSON [library](/doc/library/library.md) with a SQL-based one, and will additionally include some fundamental changes to how some features such as [tags](/doc/library/tag.md) will work.

View File

@@ -1,25 +0,0 @@
# Entry
Entries are the units that fill a [library](/doc/library/library.md). Each one corresponds to a file, holding a reference to it along with the metadata associated with it.
### Entry Object Structure
1. `id`:
- Int, Unique, **Required**
- The ID for the Entry.
- Used for internal processing
2. `filename`:
- String, **Required**
- The filename with extension of the referenced media file.
3. `path`:
- String, **Required**, OS Agnostic
- The folder path in which the media file is located in.
4. [`fields`](/doc/library/field.md):
- List of dicts, Optional
- A list of Field ID/Value dicts.
NOTE: _Entries currently have several unused optional fields intended for later features._
## Retrieving Entries based on [Tag](/doc/library/tag.md) Cluster
By default when querying Entries, each Entry's `tags` list (stored in the form of Tag `id`s) is compared against the Tag `id`s in a given Tag cluster (list of Tag `id`s) or appended clusters in the case of multi-term queries. The type of comparison depends on the type of query and whether or not it is an inclusive or exclusive query, or a combination of both. This default searching behavior is done in _O(n)_ time, but can be sped up in the future by building indexes on certain search terms. These indexes can be stored on disk and loaded back into memory in future sessions. These indexes will also need to be updated as new Tags and Entries are added or edited.

View File

@@ -1,3 +0,0 @@
# Entry Groups (Upcoming Feature)
Entries can be grouped via tags marked as “groups” which when applied to different entries will signal TagStudio to treat those entries as a single group inside of searches and browsing.

View File

@@ -1,34 +0,0 @@
# Field
Fields are the building blocks of metadata stored in [entries](/doc/library/entry.md). Fields have several base types for representing different kinds of information, including:
#### `text_line`
- A string of text, displayed as a single line.
- 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.
#### `tag_box`
- A box of [tags](/doc/library/tag.md) defined and added by the user.
- Multiple tag boxes can be used to separate classifications of tags.
- e.g: Content Tags, Meta Tags, etc.
#### `datetime` [WIP]
- A date and time value.
- e.g: Date Created, Date Modified, Date Taken, etc.
#### `checkbox` [WIP]
- A simple two-state checkbox.
- Can be associated with a tag for quick organization.
- e.g: Archive, Favorite, etc.
#### `collation` [obsolete]
- Previously used for associating files to be used in a [collation](/doc/utilities/macro.md#create-collage), will be removed in favor of a more flexible feature in future updates.

View File

@@ -1,11 +0,0 @@
# Library
The library is how TagStudio represents your chosen directory, with every file inside of it being displayed as an [entry](/doc/library/entry.md). You can have as many or few libraries as you wish, since each libraries' data is stored within a "`.TagStudio`" folder at its root.
Note that this means [tags](/doc/library/tag.md) you create only exist _per-library_.
### Library Contents
- [Entries](/doc/library/entry.md)
- [Fields](/doc/library/field.md)
- [Tags](/doc/library/tag.md)
- [Macros](/doc/utilities/macro.md)

View File

@@ -1,85 +0,0 @@
# Tag
Tags are user-defined attributes made up of one or more keywords, aliases, and relationships to other tags. A person, place, thing, concept, you name it! Tags allow for a more sophisticated way to organize and search [entries](/doc/library/entry.md) thanks to their aliases, parent tags, and more.
Tags can be as simple or complex as wanted, so that any user can tune TagStudio to fit their needs.
Among the things that make tags so useful, aliases give the ability to contain alternate names and spellings, making searches intuitive and expansive. Furthermore, parent-tags/subtags offer relational organization capabilities for the structuring and connection of the [library's](/doc/library/library.md) contents.
## Tag Object Structure
#### `id`
ID for the tag.
- Int, Unique, Required
- Used for internal processing
#### `name`
The normal name of the tag, with no shortening or specification.
- String, Required
- Doesn't have to be unique
- Used for display, searching, and storing
#### `shorthand`
The shorthand name for the tag. Works like an alias but is used for specific display purposes.
- String, Optional
- Doesn't have to be unique
- Used for display and searching
#### `aliases`
Alternate names for the tag.
- List of Strings, Optional
- Recommended to be unique to this tag
- Used for searching
#### `subtags`
Other Tags that make up properties of this tag. Also called "parent tags".
- List of Strings, Optional
- Used for display (first parent tag only) and searching.
#### `color`
A color name string for customizing the tag's display color
- String, Optional
- Used for display
## Tag Search Examples:
Using for example, a library of files including some tagged with the following tags:
| Tag | `name` | `shorthand` | `aliases` | `parent tags` |
| ------------------- | ------------------- | ----------- | ---------------------- | -------------------------------------------- |
| _League of Legends_ | "League of Legends" | "LoL" | ["League"] | ["Game", "Fantasy"] |
| _Arcane_ | "Arcane" | "" | [] | ["League of Legends", "Cartoon"] |
| _Jinx (LoL)_ | "Jinx Piltover" | "Jinx" | ["Jinxy", "Jinxy Poo"] | ["League of Legends", "Arcane", "Character"] |
| _Zander (Arcane)_ | "Zander Zanderson" | "Zander" | [] | ["Arcane", "Character"] |
| _Mr. Legend (LoL)_ | "Mr. Legend" | "" | [] | ["League of Legends", "Character"] |
**The query "Arcane" will display results tagged with:**
| Tag | Cause of Inclusion | Tag Tree Lineage |
| --------------- | -------------------------------- | -------------------------- |
| Arcane | Direct match of tag name | "Arcane" |
| Jinx (LoL) | Search term is set as parent tag | "Jinx (LoL) > Arcane" |
| Zander (Arcane) | Search term is set as parent tag | "Zander (Arcane) > Arcane" |
**The query "League of Legends" will display results tagged with:**
| Tag | Cause of Inclusion | Tag Tree Lineage |
| ----------------- | ------------------------------------------------------ | ---------------------------------------------- |
| League of Legends | Direct match of tag name | "League of Legends" |
| Arcane | Search term is set as parent tag | "Arcane > League of Legends" |
| Jinx (LoL) | Search term is set as parent tag | "Jinx (LoL) > League of Legends" |
| Mr. Legend (LoL) | Search term is set as parent tag | "Mr. Legend (LoL) > League of Legends" |
| Zander (Arcane) | Search term is a parent tag of a tag set as parent tag | "Zander (Arcane) > Arcane > League of Legends" |
Note: The query "LoL" will display the same results as the above example since "LoL" is the shorthand for "League of Legends".

View File

@@ -1,3 +0,0 @@
# Tag Categories (Upcoming Feature)
Replaces [Tag Fields](/doc/library/field.md#tag_box). Tags are able to be marked as a “category” which then displays as tag fields currently do, with any tags inheriting from that category being displayed underneath.

View File

@@ -1,16 +0,0 @@
# Tag Overrides (Upcoming Feature)
Tag overrides are the ability to add or remove [parent tags](/doc/library/tag.md#subtags) from a [tag](/doc/library/tag.md) on a per- [entry](/doc/library/entry.md) basis. Relies on the [Database Migration](/doc/updates/db_migration.md) update being complete.
## Examples
<figure>
<img src="../assets/tag_override_ex-1.png" alt="Example 1" height="300">
<figcaption>Ex. 1 - Comparing standard tag composition vs additive and subtractive inheritance overrides.</figcaption>
</figure>
<figure>
<img src="../assets/tag_override_ex-2.png" alt="Example 2" height="300">
<figcaption>Ex. 2 - Parent tag swap using tag overrides.</figcaption>
</figure>

View File

@@ -1,43 +0,0 @@
# Database Migration
The database migration is an upcoming refactor to TagStudio's library data storage system. The database will be migrated from a JSON-based one to a SQLite-based one. Part of this migration will include a reworked schema, which will allow for several new features and changes to how [tags](/doc/library/tag.md) and [fields](/doc/library/field.md) operate.
## Schema
<img src="../assets/db_schema.png" alt="Database Schema" width="500">
### `alias` Table
_Description TBA_
### `entry` Table
_Description TBA_
### `entry_attribute` Table
_Description TBA_
### `entry_page` Table
_Description TBA_
### `location` Table
_Description TBA_
### `tag` Table
_Description TBA_
### `tag_relation` Table
_Description TBA_
## Resulting New Features and Changes
- Multiple Directory Support
- [Tag Categories](/doc/library/tag_categories.md) (Replaces [Tag Fields](/doc/library/field.md#tag_box))
- [Tag Overrides](/doc/library/tag_overrides.md)
- User-Defined [Fields](/doc/library/field.md)
- Tag Icons

View File

@@ -1,59 +0,0 @@
# Planned Features
The following lists outline the planned major and minor features for TagStudio, in no particular order.
# Major Features
- [SQL Database Migration](/doc/updates/db_migration.md)
- Multiple Directory Support
- [Tags Categories](/doc/library/tag_categories.md)
- [Entry Groups](/doc/library/entry_groups.md)
- [Tag Overrides](/doc/library/tag_overrides.md)
- Tagging Panel
- Top Tags
- Recent Tags
- Tag Search
- Pinned Tags
- Configurable Default Fields (May be part of [Macros](/doc/utilities/macro.md))
- Deep File Extension Control
- Settings Menu
- Custom User Colors
- Search Engine Rework
- Boolean Search
- Tag Objects In Search
- Search For Fields
- Sortable Search Results
- Automatic Entry Relinking
- Detect Renames
- Detect Moves
- Thumbnail Caching
- User-Defined Fields
- Exportable Library/Tag Data
- Exportable Human-Readable Library
- Exportable/Importable Human-Readable “Tag Packs”
- Exportable/Importable Color Palettes
- Configurable Thumbnail Labels
- Toggle Extension Label
- Toggle File Size Label
- Configurable Thumbnail Tag Badges
- Customize tags that appear instead of just “Archive” and “Favorite”
- OCR Search
## Minor Features
- Deleting Tags
- Merging Tags
- Tag Icons
- Tag/Field Copy + Paste
- Collage UI
- Resizable Thumbnail Grid
- Draggable Files Outside The Program
- File Property Caching
- 3D Previews
- Audio Waveform Previews
- Toggle Between Waveform And Album Artwork
- PDF Previews
- SVG Previews
- Full Video Player
- Duration Properties For Video + Audio Files
- Optional Starter Tag Packs

View File

@@ -1,43 +0,0 @@
# Tools & Macros
Tools and macros are features that serve to create a more fluid [library](/doc/library/library.md)-managing process, or provide some extra functionality. Please note that some are still in active development and will be fleshed out in future updates.
## Tools
### Fix Unlinked Entries
This tool displays the number of unlinked [entries](/doc/library/entry.md), and some options for their resolution.
1. Refresh
- Scans through the library and updates the unlinked entry count.
2. Search & Relink
- Attempts to automatically find and reassign missing files.
3. Delete Unlinked Entries
- Displays a confirmation prompt containing the list of all missing files to be deleted before committing to or cancelling the operation.
### Fix Duplicate Files
This tool allows for management of duplicate files in the library using a [DupeGuru](https://dupeguru.voltaicideas.net/) file.
1. Load DupeGuru File
- load the "results" file created from a DupeGuru scan
2. Mirror Entries
- Duplicate entries will have their contents mirrored across all instances. This allows for duplicate files to then be deleted with DupeGuru as desired, without losing the [field](/doc/library/field.md) data that has been assigned to either. (Once deleted, the "Fix Unlinked Entries" tool can be used to clean up the duplicates)
### Create Collage
This tool is a preview of an upcoming feature. When selected, TagStudio will generate a collage of all the contents in a Library, which can be found in the Library folder ("/your-folder/.TagStudio/collages/"). Note that this feature is still in early development, and doesn't yet offer any customization options.
## Macros
### Auto-fill [WIP]
Tool is in development and will be documented in future update.
### Sort fields
Tool is in development, will allow for user-defined sorting of [fields](/doc/library/field.md).
### Folders to Tags
Creates tags from the existing folder structure in the library, which are previewed in a hierarchy view for the user to confirm. A tag will be created for each folder and applied to all entries, with each subfolder being linked to the parent folder as a [parent tag](/doc/library/tag.md#subtags). Tags will initially be named after the folders, but can be fully edited and customized afterwards.

1
docs/CNAME Normal file
View File

@@ -0,0 +1 @@
docs.tagstud.io

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

View File

Before

Width:  |  Height:  |  Size: 677 KiB

After

Width:  |  Height:  |  Size: 677 KiB

BIN
docs/assets/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

180
docs/develop.md Normal file
View File

@@ -0,0 +1,180 @@
# Developing
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 familiarity with existing Python toolchains.
<!-- prettier-ignore -->
!!! tip "Contributing"
If you wish to contribute to TagStudio's development, please read our [CONTRIBUTING.md](https://github.com/TagStudioDev/TagStudio/blob/main/CONTRIBUTING.md)!
## Install Python
Python [3.12](https://www.python.org/downloads) is required to develop for TagStudio. Any version matching "Python 3.12.x" should work, with "x" being any number. Alternatively you can use a tool such as [pyenv](https://github.com/pyenv/pyenv) to install this version of Python without affecting any existing Python installations on your system. Tools such as [uv](#installing-with-uv) can also install Python versions.
<!-- prettier-ignore -->
!!! info "Python Aliases"
Depending on your system, Python may be called `python`, `py`, `python3`, or `py3`. These instructions use the alias `python` for consistency.
If you already have Python installed on your system, you can check the version by running the following command:
```sh
python --version
```
---
#### Installing with pyenv
If you choose to install Python using pyenv, please refer to the following instructions:
1. Follow pyenv's [install instructions](https://github.com/pyenv/pyenv/?tab=readme-ov-file#installation) for your system.
2. Install the appropriate Python version with pyenv by running `pyenv install 3.12` (This will **not** mess with your existing Python installation).
3. Navigate to the repository root folder in your terminal and run `pyenv local 3.12`. You could alternatively use `pyenv shell 3.12` or `pyenv global 3.12` instead to set the Python version for the current terminal session or the entire system respectively, however using `local` is recommended.
---
### Installing Dependencies
To install the required dependencies, you can use a dependency manager such as [uv](https://docs.astral.sh/uv) or [Poetry 2.0](https://python-poetry.org). Alternatively you can create a virtual environment and manually install the dependencies yourself.
#### Installing with uv
If using [uv](https://docs.astral.sh/uv), you can install the dependencies for TagStudio with the following command:
```sh
uv pip install -e .[dev]
```
A reference `.envrc` is provided for use with [direnv](#direnv), see [`contrib/.envrc-uv`](https://github.com/TagStudioDev/TagStudio/blob/main/contrib/.envrc-uv).
---
#### Installing with Poetry
If using [Poetry](https://python-poetry.org), you can install the dependencies for TagStudio with the following command:
```sh
poetry install --with dev
```
---
#### Manual Installation
If you choose to manually set up a virtual environment and install dependencies instead of using a dependency manager, please refer to the following instructions:
<!-- prettier-ignore -->
!!! tip "Virtual Environments"
Learn more about setting up a virtual environment with Python's [official tutorial](https://docs.python.org/3/tutorial/venv.html).
1. In the root repository directory, create a python virtual environment:
```sh
python -m venv .venv
```
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`
<!-- prettier-ignore -->
!!! info "Supported Shells"
Depending on your system, the regular activation script _might_ not work on alternative shells. In this case, refer to the table below for supported shells:
| Shell | Script |
| ---------: | :------------------------ |
| Bash/ZSH | `.venv/bin/activate` |
| Fish | `.venv/bin/activate.fish` |
| CSH/TCSH | `.venv/bin/activate.csh` |
| PowerShell | `.venv/bin/activate.ps1` |
3. Use the following PIP command to create an editable installation and install the required development dependencies:
```sh
pip install -e .[dev]
```
## 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:
```sh
nix develop
```
A reference `.envrc` is provided for use with [direnv](#direnv), see [`contrib/.envrc-nix`](https://github.com/TagStudioDev/TagStudio/blob/main/contrib/.envrc-nix).
## Tooling
### Editor Integration
The entry point for TagStudio is `src/tagstudio/main.py`. You can target this file from your IDE to run or connect a debug session. The example(s) below show off example launch scripts for different IDEs. Here you can also take advantage of [launch arguments](./usage.md/#launch-arguments) to pass your own test [libraries](./library/index.md) to use while developing. You can find more editor configurations in [`contrib`](https://github.com/TagStudioDev/TagStudio/tree/main/contrib).
<!-- prettier-ignore -->
=== "VS Code"
```json title=".vscode/launch.json"
{
"version": "0.2.0",
"configurations": [
{
"name": "TagStudio",
"type": "python",
"request": "launch",
"program": "${workspaceRoot}/src/tagstudio/main.py",
"console": "integratedTerminal",
"justMyCode": true,
"args": ["-o", "~/Documents/Example"]
}
]
}
```
### pre-commit
There is a [pre-commit](https://pre-commit.com/) configuration that will run through some checks before code is committed. Namely, mypy and the Ruff linter and formatter will check your code, catching those nits right away.
Once you have pre-commit installed, just run:
```sh
pre-commit install
```
From there, Git will automatically run through the hooks during commit actions!
### direnv
You can automatically enter this development shell, and keep your user shell, with a tool like [direnv](https://direnv.net/). Some reference `.envrc` files are provided in the repository at [`contrib`](https://github.com/TagStudioDev/TagStudio/tree/main/contrib).
Two currently available are for [Nix](#nixos) and [uv](#installing-with-uv), to use one:
```sh
ln -s .envrc-$variant .envrc
```
You will have to allow usage of it.
<!-- prettier-ignore -->
!!! warning "direnv Security Framework"
These files are generally a good idea to check, as they execute commands on directory load. direnv has a security framework to only run `.envrc` files you have allowed, and does keep track on if it has changed. So, with that being said, the file may need to be allowed again if modifications are made.
```sh
cat .envrc # You are checking them, right?
direnv allow
```
## Building
To build your own executables of TagStudio, first follow the steps in "[Installing Dependencies](#installing-dependencies)." Once that's complete, run the following PyInstaller command:
```
pyinstaller tagstudio.spec
```
If you're on Windows or Linux and wish to build a portable executable, then pass the following flag:
```
pyinstaller tagstudio.spec -- --portable
```
The resulting executable file(s) will be located in a new folder named "dist".

54
docs/help/ffmpeg.md Normal file
View File

@@ -0,0 +1,54 @@
# FFmpeg
FFmpeg is required for thumbnail previews and playback features on audio and video files. FFmpeg is a free Open Source project dedicated to the handling of multimedia (video, audio, etc) files. For more information, see their official website at [ffmpeg.org](https://www.ffmpeg.org/).
## Installation on Windows
### Prebuilt Binaries
Pre-built binaries from trusted sources are available on the [FFmpeg website](https://www.ffmpeg.org/download.html). Under "More downloading options" click on the Windows section, then under "Windows EXE Files" select a source to download a build from. Follow any further download instructions from whichever build website you choose.
![Windows Download Location](../assets/ffmpeg_windows_download.png)
<!-- prettier-ignore -->
!!! warning
Do NOT download the source code by mistake!
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`)
4. Click "Okay"
### Package Managers
FFmpeg is also available from:
1. WinGet (`winget install ffmpeg`)
2. Scoop (`scoop install main/ffmpeg`)
3. Chocolatey (`choco install ffmpeg-full`)
## Installation on Mac
### Homebrew
FFmpeg is available under the macOS section of the [FFmpeg website](https://www.ffmpeg.org/download.html) or can be installed via [Homebrew](https://brew.sh/) using `brew install ffmpeg`.
## Installation on Linux
### Package Managers
FFmpeg may be installed by default on some Linux distributions, but if not, it is available via your distribution package manager of choice:
1. Debian/Ubuntu (`sudo apt install ffmpeg`)
2. Fedora (`sudo dnf install ffmpeg-free`)
3. Arch (`sudo pacman -S ffmpeg`)
# Help
For additional help, please join the [Discord](https://discord.gg/hRNnVKhF2G) or create an Issue on the [GitHub repository](https://github.com/TagStudioDev/TagStudio)

52
docs/index.md Normal file
View File

@@ -0,0 +1,52 @@
---
title: Home
---
# Welcome to the TagStudio Documentation!
![TagStudio Alpha](./assets/github_header.png)
TagStudio is a photo & file organization application with an underlying tag-based system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure.
<figure width="60%" markdown="span">
![TagStudio screenshot](./assets/screenshot.png)
<figcaption>TagStudio Alpha v9.5.0 running on macOS Sequoia.</figcaption>
</figure>
## Feature Roadmap
The [Feature Roadmap](./updates/roadmap.md) lists all of the planned core features for TagStudio to be considered "feature complete" along with estimated release milestones. The development and testing of these features takes priority over all other requested or submitted features unless they are later added to this roadmap. This helps ensure that TagStudio eventually sees a full release and becomes more usable by more people more quickly.
## Current Features
### Libraries
- Create [libraries](./library/index.md) centered around a system directory. Libraries contain a series of entries: the representations of your files combined with metadata fields. Each entry represents a file in your librarys directory, and is linked to its location.
- Address moved, deleted, or otherwise "unlinked" files by using the "Fix Unlinked Entries" option in the Tools menu.
### Tagging + Metadata Fields
- Add custom powerful [tags](./library/tag.md) to your library entries
- Add [metadata fields](./library/field.md) to your library entries, including:
- Name, Author, Artist (Single-Line Text Fields)
- Description, Notes (Multi-Line Text Fields)
- Create rich tags composed of a name, color, a list of aliases, and a list of "parent tags" - these being tags in which these tags inherit values from.
- Copy and paste tags and fields across file entries
- Automatically organize tags into groups based on parent tags marked as "categories"
- Generate tags from your existing folder structure with the "Folders to Tags" macro (NOTE: these tags do NOT sync with folders after they are created)
### Search
- [Search](./library/library_search.md) for file entries based on tags, file path (`path:`), file types (`filetype:`), and even media types! (`mediatype:`)
- 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`) to find file entries without tags or fields, respectively
### File Entries
- Nearly all [file](./library/entry.md) types are supported in TagStudio libraries - just not all have dedicated thumbnail support.
- Preview most image file types, animated GIFs, videos, plain text documents, audio files, Blender projects, and more!
- Open files or file locations by right-clicking on thumbnails and previews and selecting the respective context menu options. You can also click on the preview panel image to open the file, and click the file path label to open its location.
- Delete files from both your library and drive by right-clicking the thumbnail(s) and selecting the "Move to Trash"/"Move to Recycle Bin" option.

212
docs/install.md Normal file
View File

@@ -0,0 +1,212 @@
# Installation
TagStudio provides [releases](https://github.com/TagStudioDev/TagStudio/releases) as well as full access to its [source code](https://github.com/TagStudioDev/TagStudio) under the [GPLv3](https://github.com/TagStudioDev/TagStudio/blob/main/LICENSE) license.
## Executables
To download executable builds of TagStudio, visit the [Releases](https://github.com/TagStudioDev/TagStudio/releases) page of the GitHub repository and download the latest release for your system under the "Assets" section at the bottom of the release.
TagStudio has builds for **Windows**, **macOS** _(Apple Silicon & Intel)_, and **Linux**. We also offer portable releases for Windows and Linux which are self-contained and easier to move around.
<!-- prettier-ignore -->
!!! info "Third-Party Dependencies"
You may need to install [third-party dependencies](#third-party-dependencies) such as [FFmpeg](https://ffmpeg.org/download.html) to use the full feature set of TagStudio.
<!-- prettier-ignore -->
!!! warning "For macOS Users"
On macOS, you may be met with a message saying "**"TagStudio" can't be opened because Apple cannot check it for malicious software.**" 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 "**"TagStudio" was blocked from use because it is not from an identified developer.**" Click the "Open Anyway" button to allow TagStudio to run. You should only have to do this once after downloading the application.
---
## Package Managers
<!-- prettier-ignore -->
!!! danger "Unofficial Releases"
**We do not currently publish TagStudio to _remote_ package repositories. Any TagStudio distributions outside of the [GitHub repository](https://github.com/TagStudioDev/TagStudio) are _unofficial_ and not maintained by us!**
Installation support will not be given to users installing from unofficial sources. Use these versions at your own risk!
### Installing with PIP
TagStudio is installable via [PIP](https://pip.pypa.io/). Note that since we don't currently distribute on PyPI, the repository needs to be cloned and installed locally. Make sure you have Python 3.12 and PIP installed if you choose to install using this method.
The repository can be cloned/downloaded via `git` in your terminal, or by downloading the zip file from the "Code" button on the [repository page](https://github.com/TagStudioDev/TagStudio).
```sh
git clone https://github.com/TagStudioDev/TagStudio.git
```
Once cloned or downloaded, you can install TagStudio with the following PIP command:
```sh
pip install .
```
<!-- prettier-ignore -->
!!! note "Developer Dependencies"
If you wish to create an editable install with the additional dependencies required for developing TagStudio, use this modified PIP command instead:
```sh
pip install -e .[dev]
```
_See more under "[Developing](./develop.md)"_
TagStudio can now be launched via the `tagstudio` command in your terminal.
---
### Linux
Some external dependencies are required for TagStudio to execute. Below is a table of known packages that will be necessary.
<!-- 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 |
### Nix(OS)
For [Nix(OS)](https://nixos.org/), the TagStudio repository includes a [flake](https://wiki.nixos.org/wiki/Flakes) that provides some outputs such as a development shell and package.
Two packages are provided: `tagstudio` and `tagstudio-jxl`. The distinction was made because `tagstudio-jxl` has an extra compilation step for [JPEG-XL](https://jpeg.org/jpegxl) image support. To give either of them a test run, you can execute `nix run github:TagStudioDev/TagStudio#tagstudio`. If you are in a cloned repository and wish to run a package with the context of the repository, you can simply use `nix run` with no arguments.
`nix build` can be used in place of `nix run` if you only want to build. **The packages will only build if tests pass.**
<!-- prettier-ignore -->
!!! info "Nix Support"
Support for Nix is handled on a best-effort basis by one of our maintainers. Issues related to Nix may be slower to resolve, and could require further details.
Want to add TagStudio into your configuration?
This can be done by first adding the flake input into your `flake.nix`:
```nix title="flake.nix"
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
tagstudio = {
url = "github:TagStudioDev/TagStudio";
inputs.nixpkgs.follows = "nixpkgs"; # Use the same package set as your flake.
};
};
}
```
Then, make sure you add the `inputs` context to your configuration:
<!-- prettier-ignore-start -->
=== "NixOS with Home Manager"
```nix title="flake.nix"
{
outputs =
inputs@{ home-manager, nixpkgs, ... }:
{
nixosConfigurations.HOSTNAME = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
home-manager.nixosModules.home-manager
{
home-manager = {
useGlobalPkgs = true;
useUserPackages = true;
extraSpecialArgs = { inherit inputs; };
users.USER.imports = [
./home.nix
];
};
}
];
};
};
}
```
=== "NixOS"
```nix title="flake.nix"
{
outputs =
inputs@{ nixpkgs, ... }:
{
nixosConfigurations.HOSTNAME = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
];
};
};
}
```
=== "Home Manager (standalone)"
```nix title="flake.nix"
{
outputs =
inputs@{ home-manager, nixpkgs, ... }:
let
pkgs = import nixpkgs {
system = "x86_64-linux";
};
in
{
homeConfigurations.USER = home-manager.lib.homeManagerConfiguration {
inherit pkgs;
extraSpecialArgs = { inherit inputs; };
modules = [
./home.nix
];
};
};
}
```
<!-- prettier-ignore-end -->
Finally, `inputs` can be used in a module to add the package to your packages list:
<!-- prettier-ignore-start -->
=== "Home Manager module"
```nix title="home.nix"
{ inputs, pkgs, ... }:
{
home.packages = [
inputs.tagstudio.packages.${pkgs.stdenv.hostPlatform.system}.tagstudio
];
}
```
=== "NixOS module"
```nix title="configuration.nix"
{ inputs, pkgs, ... }:
{
environment.systemPackages = [
inputs.tagstudio.packages.${pkgs.stdenv.hostPlatform.system}.tagstudio
];
}
```
<!-- prettier-ignore-end -->
Don't forget to rebuild!
## Third-Party Dependencies
For audio/video thumbnails and playback you'll need [FFmpeg](https://ffmpeg.org/download.html) installed on your system. If you encounter any issues with this, please reference our [FFmpeg Help](./help/ffmpeg.md) guide.
You can check to see if FFmpeg and FFprobe are correctly located by launching TagStudio and going to "About TagStudio" in the menu bar.

50
docs/library/entry.md Normal file
View File

@@ -0,0 +1,50 @@
# File Entries
File entries are the individual representations of your files inside a TagStudio [library](./index.md). Each one corresponds one-to-one to a file on disk, and tracks all of the additional [tags](tag.md) and metadata that you attach to it inside TagStudio.
## Storage
File entry data is stored within the `ts_library.sqlite` file inside each library's `.TagStudio` folder. No modifications are made to your actual files on disk, and nothing like sidecar files are generated for your files.
## Appearance
File entries appear as thumbnails inside the grid display. The preview panel shows a more detailed preview of the file, along with extra file stats and all attached TagStudio tags and fields.
## Unlinked File Entries
If the file that an entry is referencing has been moved, renamed, or deleted on disk, then TagStudio will display its unlinked status with a red chain-link icon instead of its thumbnail image. Certain uncached stats such as the file size and image dimensions will also be unavailable to see in the preview panel.
To fix file entries that have become unlinked, select the "Fix Unlinked Entries" option from the Tools menu. From there, refresh the unlinked entry count and choose whether to search and relink you files, and/or delete the file entries from your library. This will NOT delete or modify any files on disk.
## 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.
### 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`)

View File

@@ -0,0 +1,8 @@
---
tags:
- Upcoming Feature
---
# Entry Groups
Entries can be grouped via tags marked as "groups" which when applied to different entries will signal TagStudio to treat those entries as a single group inside of searches and browsing.

23
docs/library/field.md Normal file
View File

@@ -0,0 +1,23 @@
# Fields
Fields are additional types of metadata that you can attach to [file entries](./entry.md). Like [tags](./tag.md), fields are not stored inside files themselves nor in sidecar files, but rather inside the respective TagStudio [library](./index.md) save file.
## Field Types
### Text Line
A string of text, displayed as a single line.
- 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.
### Datetime [WIP]
A date and time value.
- e.g: Date Published, Date Taken, etc.

5
docs/library/index.md Normal file
View File

@@ -0,0 +1,5 @@
# Library
The library is how TagStudio represents your chosen directory, with every file inside being represented by a [file entry](./entry.md). You can have as many or few libraries as you wish, since each libraries' data is stored within a `.TagStudio` folder at its root. From there the library save file itself is stored as `ts_library.sqlite`, with TagStudio versions 9.4 and below using a the legacy `ts_library.json` format.
Note that this means [tags](./tag.md) you create only exist _per-library_.

View File

@@ -0,0 +1,121 @@
# Library Search
TagStudio provides various methods to search your library, ranging from TagStudio data such as tags to inherent file data such as paths or media types.
## Boolean Operators
TagStudio allows you to use common [Boolean search](https://en.wikipedia.org/wiki/Full-text_search#Boolean_queries) operators when searching your library, along with [grouping](#grouping-and-nesting), [nesting](#grouping-and-nesting), and [character escaping](#escaping-characters). Note that you may need to use grouping in order to get the desired results you're looking for.
### AND
The `AND` operator will only return results that match **both** sides of the operator. `AND` is used implicitly when no Boolean operators are given. To use the `AND` operator explicitly, simply type "and" (case insensitive) in-between items of your search.
<!-- prettier-ignore -->
!!! example
Searching for "Tag1 Tag2" will be treated the same as "Tag1 `AND` Tag2" and will only return results that contain both Tag1 and Tag2.
### OR
The `OR` operator will return results that match **either** the left or right side of the operator. To use the `OR` operator simply type "or" (case insensitive) in-between items of your search.
<!-- prettier-ignore -->
!!! example
Searching for "Tag1 `OR` Tag2" will return results that contain either "Tag1", "Tag2", or both.
### NOT
The `NOT` operator will returns results where the condition on the right is **false.** To use the `NOT` operator simply type "not" (case insensitive) in-between items of your search. You can also begin your search with `NOT` to only view results that do not contain the next term that follows.
<!-- prettier-ignore -->
!!! example
Searching for "Tag1 `NOT` Tag2" will only return results that contain "Tag1" while also not containing "Tag2".
### Grouping and Nesting
Searches can be grouped and nested by using parentheses to surround parts of your search query.
<!-- prettier-ignore -->
!!! example
Searching for "(Tag1 `OR` Tag2) `AND` Tag3" will return any results that contain Tag3, plus one or the other (or both) of Tag1 and Tag2.
### Escaping Characters
Sometimes search queries have ambiguous characters and need to be "escaped". This is most common with tag names which contain spaces, or overlap with existing search keywords such as "[path:](#filename-and-path) of exile". To escape most search terms, surround the section of your search in plain quotes. Alternatively, spaces in tag names can be replaced by underscores.
#### Valid Escaped Tag Searches
- "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".
## Tags
[Tag](#tags) search is the default mode of file entry search in TagStudio. No keyword prefix is required, however using `tag:` will also work. The tag search attempts to match tag [names](./tag.md#name), [shorthands](./tag.md#shorthand), [aliases](./tag.md#aliases), as well as allows for tags to [substitute](./tag.md#intuition-via-substitution) in for any of their [parent tags](./tag.md#parent-tags).
You may also see the `tag_id:` prefix keyword show up when using the right-click "Search for Tag" option on tags. This is meant for internal use, and eventually will not be displayed or accessible to the user.
## Fields
_[Field](./field.md) search is currently not in the program, however is coming in a future version._
## File Entry Search
### Filename and Path
Filename and path search is available via the `path:` keyword and comes in a few different styles. By default, any string that follows the `path:` keyword will be searched as a substring inside a file's complete filepath. This means that given a file `folder/my_file.txt`, searching for `path: my_file` or `path: folder` will both return results for that file.
#### Case Sensitivity
TagStudio uses a "[smartcase](https://neovim.io/doc/user/options.html#'smartcase')"-like system for case sensitivity. This means that a search term typed in `lowercase` will be treated as **case-insensitive**, while a term typed in any `MixedCase` will be treated as **case-sensitive**. This makes it quicker to type searches when case sensitivity isn't required, while also providing a simple option to leverage case sensitivity when desired. Note that this means there's technically no way to currently search for a lowercase term while respecting case sensitivity.
#### Glob Syntax
Optionally, you may use [glob](https://en.wikipedia.org/wiki/Glob_(programming)) syntax to search filepaths.
#### Examples
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`
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)_
## Special Searches
Some predefined searches use the `special:` keyword prefix and give quick results for certain special search queries.
### Untagged
To see all your file entries which don't contain any tags, use the `special: untagged` search.
### Empty
**_NOTE:_** _Currently unavailable in v9.5.0_
To see all your file entries which don't contain any tags _and_ any fields, use the `special: empty` search.

131
docs/library/tag.md Normal file
View File

@@ -0,0 +1,131 @@
# Tags
Tags are discrete objects that represent some attribute. This could be a person, place, object, concept, and more. Unlike most tagging systems, TagStudio tags are not solely represented by a line of text or a hashtag. Tags in TagStudio consist of several properties and relationships that give extra customization, searching power, and ease of tagging that cannot be achieved by string-based tags alone. TagStudio tags are designed to be as simple or as complex as you'd like, giving options to users of all skill levels and use cases.
## Naming Tags
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
### Name
This is the base name of a tag. It does not have to be unique, and can use any characters you wish. If your tag can go by multiple names, for example if it's the name of a person or something that's commonly shortened or abbreviated, then it's recommended that you put the full tag name here.
### Shorthand
This is a special type of alias that's used for shortening the tag name under special circumstances, mostly when screen space is limited. Tag shorthands can be searched for just like tag names and tag aliases.
### Aliases
Aliases are alternate names that the tag can go by. This may include individual first names for people, alternate spellings, shortened names, and more. If there's a common abbreviation or shortened name for your tag, it's recommended to use the [shorthand](#shorthand) field for this instead.
When searching for a tag, aliases (including the shorthand) can also be used to find the tag. This not only includes searching for tags themselves, but for tagged [file entries](./entry.md) as well!
### Automatic Disambiguation
Just as in real life, sometimes there are different attributes that share the same name with one another. The process of adding specificity to something in order to not confuse it with something similar is known as [disambiguation](https://en.wikipedia.org/wiki/Word-sense_disambiguation). In TagStudio we give the option to automatically disambiguate tag names based on a specially marked [Parent Tag](#parent-tags). Parent tags are explained in further detail below, but for the purposes of tag names they can lend themselves to clarifying the name of a tag without the user needing to manually change the name or add complicated aliases.
Given a tag named "Freddy", we may confuse it with other "Freddy" tags in our library. There are lots of Freddys in the world, after all. If we're talking about Freddy from "Five Nights at Freddy's", then we may already (and likely should) have a separate "Five Nights at Freddy's" tag added as a parent tag. When the disambiguation box next to a parent tag is selected (see image below) then our tag name will automatically display its name with that parent tag's name (or shorthand if available) in parentheses.
![Tag Disambiguation Example](../assets/tag_disambiguation_example.png)
So if the "Five Nights at Freddy's" tag is added as a parent tag on the "Freddy" tag, and the disambiguation box next to it is checked, then our tag name will automatically be displayed as "Freddy (Five Nights at Freddy's)". Better yet, if the "Five Nights at Freddy's" tag has a shorthand such as "FNAF", then our "Freddy" tag will be displayed as "Freddy (FNAF)". This process preserves our base tag name ("Freddy") and provides an option to get a clean and consistent method to display disambiguating parent categories, rather than having to type this information in manually for each applicable tag.
## Tag Relationships
One of the core properties of tags in TagStudio is their ability to form relationships with other tags, just as attributes have relationships with each other in real life. A rectangle is a square, but a square isn't a rectangle. A certain plumber with a red hat and blue overalls might be part of a well-known media franchise, developed by an equally well-known company. But how do representing these relationships help with tagging images and files? With tag relationships, we can leverage the following principles:
1. [Simplicity via Deduplication](#simplicity-via-deduplication)
2. [Intuition via Substitution](#intuition-via-substitution)
3. [Rediscovery via Linking](#rediscovery-via-linking)
### Parent Tags
#### Simplicity via Deduplication
In a system where tags have no relationships, you're required to add as many tags as you possibly can to describe every last element of an image or file. If you want to tag an image of Shrek, you need to add a tag for `Shrek` himself, a `Character` tag since he's a character, a `Movie` and perhaps `Dreamworks` tag since he's a character from a movie, or perhaps a `Book` tag if we're talking about the original character, and then of course tags for every other attribute of Shrek shown or implied. By allowing tags to have inheritance relationships, we can have a single `Shrek` tag inherit from `Character` (Shrek IS a character) as well as from a separate `Shrek (Movie Franchise)` tag that itself inherits from `Movie Franchise` and `Dreamworks`. Now by simply adding the `Shrek` tag to an image, we've effectively also added the `Character`, `Shrek (Move Franchise)`, `Movie Franchise`, and `Dreamworks` attributes all in one go. On the image entry itself we only see `Shrek`, but the rest of the attributes are implied.
![Shrek Tag Details](../assets/built_tag_shrek.png)
#### Intuition via Substitution
Now when searching for images that have `Dreamworks` and `Character`, any images or files originally just tagged with `Shrek` will appear as you would expect. A little bit of tag setup goes a long way not only saving so much time during tagging, but also to ensure an intuitive way to search your files!
#### Rediscovery via Linking
Lastly, when searching your files with broader categories such as `Character` or `Dreamworks` you may rediscover images and files that you had simply tagged with tags such as `Barbatus` or `Tulio`, since you didn't need to manually tag those files with `Character` or `Dreamworks`, but had forgotten that they are both in fact Dreamworks characters. While you focus on tagging your files with seemingly surface level attributes, your TagStudio library is building rich connections between tags and files that may not be fully apparent until being discovered through various search queries. While you were simply tagging images with `Shrek` and `Tulio`, you may have unlocked an easy way to search for "2D Dreamworks Characters" without having to explicitly tag for that!
### Component Tags
**_[Coming in version 9.6](../updates/roadmap.md#v96)_**
Component tags will be built from a composition-based, or "HAS" type relationship between tags. This takes care of instances where an attribute may "have" another attribute, but doesn't inherit from it. Shrek may be an `Ogre`, he may be a `Character`, but he is NOT a `Leather Vest` - even if he's commonly seen _with_ it. Component tags, along with the upcoming [Tag Override](./tag_overrides.md) feature, are built to handle these cases in a way that still simplifies the tagging process without adding too much undue complexity for the user.
## Tag Appearance
### Color
Tags use a default uncolored appearance by default, however can take on a number of built-in and user-created colors and color palettes! Tag color palettes can be based on a single color value (see: TagStudio Standard, TagStudio Shades, TagStudio Pastels) or use an optional secondary color use for the text and optionally the tag border (e.g. TagStudio Neon).
![Tag Color Selection](../assets/tag_color_selection.png)
#### User-Created Colors
Custom palettes and colors can be created via the [Tag Color Manager](./tag_color.md). These colors will display alongside the built-in colors inside the tag selection window and are separated by their namespace names. Colors which use the secondary color for the tag border will be outlined in that color, otherwise they will only display the secondary color on the bottom of the swatch to indicate at a glance that the text colors are different.
![Custom Tag Color Selection](../assets/custom_tag_color_selection.png)
### Icon
**_[Coming in version 9.6](../updates/roadmap.md#v96)_**
## Tag Properties
Properties are special attributes of tags that change their behavior in some way.
#### Is Category
When the "Is Category" property is checked, this tag now acts as a category separator inside the preview panel. If this tag or any tags inheriting from this tag (i.e. tags that have this tag as a "[Parent Tag](#parent-tags)"), then these tags will appear under a separated group that's named after this tag. Tags inheriting from multiple "category tags" will still show up under any applicable category. _Read more under: [Tag Categories](../library/tag_categories.md)._
![Tag Category Example](../assets/tag_categories_example.png)
#### Is Hidden
**_[Coming in version 9.6](../updates/roadmap.md#v96)_**
When the "Is Hidden" property is checked, any file entries tagged with this tag will not show up in searches by default. This property comes by default with the built-in "Archived" tag.
## Tag Search Examples
The following are examples of how a set of given tags will respond to various search queries.
| Tag | Name | Shorthand | Aliases | Parent Tags |
| ------------------- | ------------------- | --------- | ---------------------- | -------------------------------------------- |
| _League of Legends_ | "League of Legends" | "LoL" | ["League"] | ["Game", "Fantasy"] |
| _Arcane_ | "Arcane" | "" | [] | ["League of Legends", "Cartoon"] |
| _Jinx (LoL)_ | "Jinx Piltover" | "Jinx" | ["Jinxy", "Jinxy Poo"] | ["League of Legends", "Arcane", "Character"] |
| _Zander (Arcane)_ | "Zander Zanderson" | "Zander" | [] | ["Arcane", "Character"] |
| _Mr. Legend (LoL)_ | "Mr. Legend" | "" | [] | ["League of Legends", "Character"] |
**The query "Arcane" will display results tagged with:**
| Tag | Cause of Inclusion | Tag Tree Lineage |
| --------------- | -------------------------------- | -------------------------- |
| Arcane | Direct match of tag name | "Arcane" |
| Jinx (LoL) | Search term is set as parent tag | "Jinx (LoL) > Arcane" |
| Zander (Arcane) | Search term is set as parent tag | "Zander (Arcane) > Arcane" |
**The query "League of Legends" will display results tagged with:**
| Tag | Cause of Inclusion | Tag Tree Lineage |
| ----------------- | ------------------------------------------------------ | ---------------------------------------------- |
| League of Legends | Direct match of tag name | "League of Legends" |
| Arcane | Search term is set as parent tag | "Arcane > League of Legends" |
| Jinx (LoL) | Search term is set as parent tag | "Jinx (LoL) > League of Legends" |
| Mr. Legend (LoL) | Search term is set as parent tag | "Mr. Legend (LoL) > League of Legends" |
| Zander (Arcane) | Search term is a parent tag of a tag set as parent tag | "Zander (Arcane) > Arcane > League of Legends" |
Note: The query "LoL" will display the same results as the above example since "LoL" is the shorthand for "League of Legends".

View File

@@ -0,0 +1,17 @@
---
tags:
---
# Tag Categories
The "Is Category" property of tags determines if a tag should be treated as a category itself when being organized inside the preview panel. Tags marked as categories will show themselves and all tags inheriting from it (including recursively) underneath a field-like section with the tag's name. This means that duplicates of tags can appear on entries if the tag inherits from multiple parent categories, however this is by design and reflects the nature of multiple inheritance. Any tags not inheriting from a category tag will simply show under a default "Tag" section.
![Tag Categories Example](../assets/tag_categories_example.png)
### Built-In Tags and Categories
The built-in tags "Favorite" and "Archived" inherit from the built-in "Meta Tags" category which is marked as a category by default. This behavior of default tags can be fully customized by disabling the category option and/or by adding/removing the tags' Parent Tags.
### Migrating from v9.4 Libraries
Due to the nature of how tags and Tag Felids operated prior to v9.5, the organization style of Tag Categories vs Tag Fields is not 1:1. Instead of tags being organized into fields on a per-entry basis, tags themselves determine their organizational layout via the "Is Property" flag. Any tags _(not currently inheriting from either the "Favorite" or "Archived" tags)_ will be shown under the default "Tags" header upon migrating to the v9.5+ library format. Similar organization to Tag Fields can be achieved by using the built-in "Meta Tags" tag or any other marked with "Is Category" and then setting those tags as parents for other tags to inherit from.

71
docs/library/tag_color.md Normal file
View File

@@ -0,0 +1,71 @@
# Tag Colors
TagStudio features a variety of built-in tag colors, alongside the ability for users to create their own custom tag color palettes.
## Tag Color Manager
The Tag Color Manager is where you can create and manage your custom tag colors and associated namespaces. You can access the Tag Color Manager from the "File -> Manage Tag Colors" option in the menu bar.
![Tag Color Manager](../assets/tag_color_manager.png)
## Creating a Namespace
TagStudio uses namespaces to group colors into palettes. Namespaces are a way for you to use the same color name across multiple palettes without having to worry about [name collision](https://en.wikipedia.org/wiki/Name_collision) with other palettes. This is especially useful when sharing your color palettes with others!\*
_\* Color pack sharing coming in a future update_
To create your first namespace, either click the "New Namespace" button or the large button prompt underneath the built-in colors.
![Create Namespace](../assets/create_namespace.png)
### Name
The display name of the namespace, used for presentation.
### ID Slug
An internal ID for the namespace which is automatically derived from the namespace name.
Namespaces beginning with "tagstudio" are reserved by TagStudio and will automatically have their text changed.
<!-- prettier-ignore -->
!!! note
It's currently not possible to manually edit the Namespace ID Slug. This will be possible once sharable color packs are added.
## Creating a Color
Once you've created your first namespace, click the "+" button inside the namespace section to create a color. To edit a color that you've previously created, either click on the color name or right click and select "Edit Color" from the context menu.
![Create Color (Primary Color)](../assets/custom_color_primary_only.png)
### Name
The display name for the color, used for presentation. You may occasionally see the color name followed by the [namespace name](#name) in parentheses to disambiguate it from other colors with the same name.
### ID Slug
Similar to [Namespace ID Slugs](#id-slug), the ID Slug is used as an internal ID and is automatically derived from the tag color name.
<!-- prettier-ignore -->
!!! note
It's currently not possible to manually edit the Color ID Slug. This will be possible once sharable color packs are added.
### Primary Color
The primary color is used as the main tag color and by default is used as the background color with the text and border colors being derived from this color.
### Secondary Color
By default, the secondary color is only used as an optional override for the tag text color. This color can be cleared by clicking the adjacent "Reset" button.
![Create Color (Secondary Color)](../assets/custom_color_no_border.png)
The secondary color can also be used as the tag border color by checking the "Use Secondary Color for Border" box.
![Create Color (Use Secondary for Border)](../assets/custom_color_border.png)
## Using Colors
When editing a tag, click the tag color button to bring up the tag color selection panel. From here you can choose any built-in TagStudio color as well as any of your custom colors.
![Tag Color Selection](../assets/tag_color_selection.png)

View File

@@ -0,0 +1,20 @@
---
tags:
- Upcoming Feature
---
# Tag Overrides
Tag overrides are the ability to add or remove [parent tags](./tag.md#parent-tags) from a [tag](./tag.md) on a per-[entry](./entry.md) basis.
## Examples
<figure markdown="span">
![Example 1](../assets/tag_override_ex-1.png){ height="300" }
<figcaption>Ex. 1 - Comparing standard tag composition vs additive and subtractive inheritance overrides.</figcaption>
</figure>
<figure markdown="span">
![Example 2](../assets/tag_override_ex-2.png){ height="300" }
<figcaption>Ex. 2 - Parent tag swap using tag overrides.</figcaption>
</figure>

View File

@@ -0,0 +1,3 @@
th, td {
padding: 0.5em 1em 0.5em 1em !important;
}

View File

@@ -0,0 +1,4 @@
---
title: Changelog
---
--8<-- "CHANGELOG.md"

217
docs/updates/roadmap.md Normal file
View File

@@ -0,0 +1,217 @@
# Feature Roadmap
This checklist details the current and remaining features required at a minimum for TagStudio to be considered "Feature Complete". This list is _not_ a definitive list for additional feature requests and PRs as they come in, but rather an outline of my personal core feature set intended for TagStudio.
## Priorities
Features are broken up into the following priority levels, with nested priorities referencing their relative priority for the overall feature (i.e. A [LOW] priority feature can have a [HIGH] priority element but it otherwise still a [LOW] priority item overall):
- [HIGH] - Core feature
- [MEDIUM] - Important but not necessary
- [LOW] - Just nice to have
## Version Milestones
These version milestones are rough estimations for when the previous core features will be added. For a more definitive idea for when features are coming, please reference the current GitHub [milestones](https://github.com/TagStudioDev/TagStudio/milestones).
<!-- prettier-ignore -->
!!! note
This list was created after the release of version 9.4
### v9.5
#### Core
- [x] SQL backend [HIGH]
#### Tags
- [x] Deleting Tags [HIGH]
- [ ] User-defined tag colors [HIGH]
- [x] ID based, not string or hex [HIGH]
- [x] Color name [HIGH]
- [x] Color value (hex) [HIGH]
- [x] Existing colors are now a set of base colors [HIGH]
- [x] [Tag Categories](../library/tag_categories.md) [HIGH]
- [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 [HIGH]
#### Search
- [x] Boolean operators [HIGH]
- [x] Filename search [HIGH]
- [x] File type search [HIGH]
- [x] Search by extension (e.g. ".jpg", ".png") [HIGH]
- [x] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") [LOW]
- [x] Search by media type (e.g. "image", "video", "document") [MEDIUM]
- [x] Sort by date added [HIGH]
#### UI
- [ ] Translations _(Any applicable)_ [MEDIUM]
#### Performance
- [x] Thumbnail caching [HIGH]
### v9.6
#### Core
- [ ] Cached file property table (media duration, word count, dimensions, etc.) [MEDIUM]
#### Library
- [ ] Multiple Root Directories per Library [HIGH]
- [ ] `.ts_ignore` (`.gitignore`-style glob ignoring) [HIGH]
- [ ] Sharable Color Packs [MEDIUM]
- [ ] Human-readable (TOML) files containing tag data [HIGH]
- [ ] Importable [HIGH]
- [ ] Exportable [HIGH]
#### Tags
- [ ] Merging Tags [HIGH]
- [ ] [Component/HAS](../library/tag.md#component-tags) subtags [HIGH]
- [ ] Tag Icons [HIGH]
- [ ] Small Icons [HIGH]
- [ ] Large Icons for Profiles [MEDIUM]
- [ ] Built-in Icon Packs (i.e. Boxicons) [HIGH]
- [ ] User Defined Icons [HIGH]
- [ ] Multiple Languages for Tag Strings [MEDIUM]
- [ ] Title is tag name [HIGH]
- [ ] Title has tag color [MEDIUM]
- [ ] Tag marked as category does not display as a tag itself [HIGH]
- [ ] [Tag Overrides](../library/tag_overrides.md) [MEDIUM]
- [ ] Per-file overrides of subtags [HIGH]
#### Fields
- [ ] Datetime fields [HIGH]
- [ ] Custom field names [HIGH]
#### Search
- [ ] Field content search [HIGH]
- [ ] Sort by date created [HIGH]
- [ ] Sort by date modified [HIGH]
- [x] Sort by filename [HIGH]
- [ ] HAS operator for composition tags [HIGH]
- [ ] Search bar rework
- [ ] Improved tag autocomplete [HIGH]
- [ ] Tags appear as widgets in search bar [HIGH]
#### UI
- [ ] File duration on video thumbnails [HIGH]
- [ ] 3D Model Previews [MEDIUM]
- [ ] STL Previews [HIGH]
- [ ] Word count/line count on text thumbnails [LOW]
- [x] Settings Menu [HIGH]
- [x] Application Settings [HIGH]
- [x] Stored in system user folder/designated folder [HIGH]
- [ ] Library Settings [HIGH]
- [ ] Stored in `.TagStudio` folder [HIGH]
- [ ] Tagging Panel [HIGH]
Toggleable persistent main window panel or pop-out. Replaces the current tag manager.
- [ ] Top Tags [HIGH]
- [ ] Recent Tags [HIGH]
- [ ] Tag Search [HIGH]
- [ ] Pinned Tags [HIGH]
- [ ] New tabbed tag building UI to support the new tag features [HIGH]
### v9.7
#### Library
- [ ] [Entry groups](../library/entry_groups.md) [HIGH]
- [ ] Groups for files/entries where the same entry can be in multiple groups [HIGH]
- [ ] Ability to number entries within group [HIGH]
- [ ] Ability to set sorting method for group [HIGH]
- [ ] Ability to set custom thumbnail for group [HIGH]
- [ ] Group is treated as entry with tags and metadata [HIGH]
- [ ] Nested groups [MEDIUM]
#### Search
- [ ] Sort by relevance [HIGH]
- [ ] Sort by date taken (photos) [MEDIUM]
- [ ] Sort by file size [HIGH]
- [ ] Sort by file dimension (images/video) [LOW]
#### [Macros](../utilities/macro.md)
- [ ] Sharable Macros [MEDIUM]
- [ ] Standard notation format (TOML) contacting macro instructions [HIGH]
- [ ] Exportable [HIGH]
- [ ] Importable [HIGH]
- [ ] Triggers [HIGH]
- [ ] On new file [HIGH]
- [ ] On library refresh [HIGH]
- [ ] [...]
- [ ] Actions [HIGH]
- [ ] Add tag(s) [HIGH]
- [ ] Add field(s) [HIGH]
- [ ] Set field content [HIGH]
- [ ] [...]
#### UI
- [ ] Custom thumbnail overrides [MEDIUM]
- [ ] Toggle File Extension Label [MEDIUM]
- [ ] Toggle Duration Label [MEDIUM]
- [ ] Custom Tag Badges [LOW]
- [ ] Unified Media Player [HIGH]
- [ ] Auto-hiding player controls
- [x] Play/Pause [HIGH]
- [x] Loop [HIGH]
- [x] Toggle Autoplay [MEDIUM]
- [ ] Volume Control [HIGH]
- [x] Toggle Mute [HIGH]
- [ ] Timeline scrubber [HIGH]
- [ ] Fullscreen [MEDIUM]
- [ ] Library list view [HIGH]
- [ ] Configurable page size [HIGH]
### v9.8
#### Library
- [ ] Automatic Entry Relinking [HIGH]
- [ ] Detect Renames [HIGH]
- [ ] Detect Moves [HIGH]
- [ ] Detect Deletions [HIGH]
#### Search
- [ ] OCR search [LOW]
- [ ] Fuzzy Search [LOW]
### v9.9
#### Library
- [ ] Exportable Library Data [HIGH]
- [ ] Standard notation format (i.e. JSON) contacting all library data [HIGH]
#### Tags
- [ ] Tag Packs [MEDIUM]
- [ ] Human-readable (TOML) files containing tag data [HIGH]
- [ ] Multiple Languages for Tag Strings [MEDIUM]
- [ ] Importable [HIGH]
- [ ] Exportable [HIGH]
- [ ] Conflict resolution [HIGH]
### v10.0
- [ ] All remaining [HIGH] and optional [MEDIUM] features
### Post v10.0
#### Core
- [ ] Core Library/API
- [ ] Plugin Support

View File

@@ -0,0 +1,60 @@
# Save Format Changes
This page outlines the various changes made to the TagStudio save file format over time, sometimes referred to as the "database" or "database file".
---
## JSON
| Used From | Used Until | Format | Location |
| --------- | ----------------------------------------------------------------------- | ------ | --------------------------------------------- |
| v1.0.0 | [v9.4.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.4.2) | JSON | `<Library Folder>`/.TagStudio/ts_library.json |
The legacy database format for public TagStudio releases [v9.1](https://github.com/TagStudioDev/TagStudio/tree/Alpha-v9.1) through [v9.4.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.4.2). Variations of this format had been used privately since v1.0.0.
Replaced by the new SQLite format introduced in TagStudio [v9.5.0 Pre-Release 1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1).
---
## DB_VERSION 6
| Used From | Used Until | Format | Location |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [v9.5.0-PR1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) | [v9.5.0-PR1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
The first public version of the SQLite save file format.
Migration from the legacy JSON format is provided via a walkthrough when opening a legacy library in TagStudio [v9.5.0 Pre-Release 1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1) or later.
---
## DB_VERSION 7
| Used From | Used Until | Format | Location |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [v9.5.0-PR2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr2) | [v9.5.0-PR3](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr3) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Repairs "Description" fields to use a TEXT_LINE key instead of a TEXT_BOX key.
- Repairs tags that may have a disambiguation_id pointing towards a deleted tag.
---
## DB_VERSION 8
| Used From | Used Until | Format | Location |
| ------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [v9.5.0-PR4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr4) | [v9.5.1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.1) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Adds the `color_border` column to the `tag_colors` table. Used for instructing the [secondary color](../library/tag_color.md#secondary-color) to apply to a tag's border as a new optional behavior.
- Adds three new default colors: "Burgundy (TagStudio Shades)", "Dark Teal (TagStudio Shades)", and "Dark Lavender (TagStudio Shades)".
- Updates Neon colors to use the new `color_border` property.
---
## DB_VERSION 9
| Used From | Used Until | Format | Location |
| ----------------------------------------------------------------------- | ---------- | ------ | ----------------------------------------------- |
| [v9.5.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.2) | _Current_ | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Adds the `filename` column to the `entries` table. Used for sorting entries by filename in search results.

73
docs/usage.md Normal file
View File

@@ -0,0 +1,73 @@
# Usage
## Creating/Opening a Library
With TagStudio opened, start by creating a new library or opening an existing one using File -> Open/Create Library from the menu bar. TagStudio will automatically create a new library from the chosen directory if one does not already exist. Upon creating a new library, TagStudio will automatically scan your folders for files and add those to your library (no files are moved during this process!).
## Refreshing the Library
Libraries under 10,000 files automatically scan for new or modified files when opened. In order to refresh the library manually, select "Refresh Directories" under the File menu.
## Adding Tags to File Entries
Access the "Add Tag" search box by either clicking on the "Add Tag" button at the bottom of the right sidebar, accessing the "Add Tags to Selected" option from the File menu, or by pressing <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>T</kbd>.
From here you can search for existing tags or create a new one if the one you're looking for doesn't exist. Click the "+" button next to any tags you want to the currently selected file entries. To quickly add the top result, press the <kbd>Enter</kbd>/<kbd>Return</kbd> key to add the topmost tag and reset the tag search. Press <kbd>Enter</kbd>/<kbd>Return</kbd> once more to close the dialog box. By using this method, you can quickly add various tags in quick succession just by using the keyboard!
To remove a tag from a file entry, hover over the tag in the preview panel and click on the "-" icon that appears.
## Adding Metadata to File Entries
To add a metadata field to a file entry, start by clicking the "Add Field" button at the bottom of the preview panel. From the dropdown menu, select the type of metadata field youd like to add to the entry
## Editing Metadata Fields
### Text Line / Text Box
Hover over the field and click the pencil icon. From there, add or edit text in the dialog box popup.
## Creating Tags
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.
### Tag Manager
You can manage your library of tags by opening the "Tag Manager" panel from Edit -> "Manage Tags". From here you can create, search for, edit, and permanently delete any tags you've created in your library.
## Editing Tags
To edit a tag, click on it inside the preview panel or right-click the tag and select "Edit Tag" from the context menu.
## Relinking Moved Files
Inevitably some of the files inside your library will be renamed, moved, or deleted. If a file has been renamed or moved, TagStudio will display the thumbnail as a red broken chain link. To relink moved files or delete these entries, select the "Manage Unlinked Entries" option under the Tools menu. Click the "Refresh" button to scan your library for unlinked entries. Once complete, you can attempt to "Search & Relink" any unlinked file entries to their respective files, or "Delete Unlinked Entries" in the event the original files have been deleted and you no longer wish to keep their entries inside your library.
<!-- prettier-ignore -->
!!! warning
There is currently no method to relink entries to files that have been renamed - only moved or deleted. This is a high priority for future releases.
<!-- prettier-ignore -->
!!! warning
If multiple matches for a moved file are found (matches are currently defined as files with a matching filename as the original), TagStudio will currently ignore the match groups. Adding a GUI for manual selection, as well as smarter automated relinking, are high priorities for future versions.
### Saving the Library
As of version 9.5, libraries are saved automatically as you go. To save a backup of your library, select File -> Save Library Backup from the menu bar.
## Launch Arguments
There are a handful of launch arguments you can pass to TagStudio via the command line or a desktop shortcut.
| Argument | Short | Description |
| ---------------------- | ----- | ---------------------------------------------------- |
| `--open <path>` | `-o` | Path to a TagStudio Library folder to open on start. |
| `--config-file <path>` | `-c` | Path to the TagStudio config file to load. |

46
docs/utilities/macro.md Normal file
View File

@@ -0,0 +1,46 @@
# Tools & Macros
Tools and macros are features that serve to create a more fluid [library](../library/index.md)-managing process, or provide some extra functionality. Please note that some are still in active development and will be fleshed out in future updates.
## Tools
### Fix Unlinked Entries
This tool displays the number of unlinked [entries](../library/entry.md), and some options for their resolution.
Refresh
: Scans through the library and updates the unlinked entry count.
Search & Relink
: Attempts to automatically find and reassign missing files.
Delete Unlinked Entries
: Displays a confirmation prompt containing the list of all missing files to be deleted before committing to or cancelling the operation.
### Fix Duplicate Files
This tool allows for management of duplicate files in the library using a [DupeGuru](https://dupeguru.voltaicideas.net/) file.
Load DupeGuru File
: load the "results" file created from a DupeGuru scan
Mirror Entries
: Duplicate entries will have their contents mirrored across all instances. This allows for duplicate files to then be deleted with DupeGuru as desired, without losing the [field](../library/field.md) data that has been assigned to either. (Once deleted, the "Fix Unlinked Entries" tool can be used to clean up the duplicates)
### Create Collage
This tool is a preview of an upcoming feature. When selected, TagStudio will generate a collage of all the contents in a Library, which can be found in the Library folder ("/your-folder/.TagStudio/collages/"). Note that this feature is still in early development, and doesn't yet offer any customization options.
## Macros
### Auto-fill [WIP]
Tool is in development and will be documented in a future update.
### Sort fields
Tool is in development. Will allow for user-defined sorting of [fields](../library/field.md).
### Folders to Tags
Creates tags from the existing folder structure in the library, which are previewed in a hierarchy view for the user to confirm. A tag will be created for each folder and applied to all entries, with each subfolder being linked to the parent folder as a [parent tag](../library/tag.md#parent-tags). Tags will initially be named after the folders, but can be fully edited and customized afterwards.

509
flake.lock generated
View File

@@ -1,132 +1,5 @@
{
"nodes": {
"cachix": {
"inputs": {
"devenv": "devenv_2",
"flake-compat": [
"devenv",
"flake-compat"
],
"nixpkgs": [
"devenv",
"nixpkgs"
],
"pre-commit-hooks": [
"devenv",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1712055811,
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
"owner": "cachix",
"repo": "cachix",
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "cachix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat_2",
"nix": "nix_2",
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks": "pre-commit-hooks"
},
"locked": {
"lastModified": 1724763216,
"narHash": "sha256-oW2bwCrJpIzibCNK6zfIDaIQw765yMAuMSG2gyZfGv0=",
"owner": "cachix",
"repo": "devenv",
"rev": "1e4ef61205b9aa20fe04bf1c468b6a316281c4f1",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"devenv-root": {
"flake": false,
"locked": {
"narHash": "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY=",
"type": "file",
"url": "file:///dev/null"
},
"original": {
"type": "file",
"url": "file:///dev/null"
}
},
"devenv_2": {
"inputs": {
"flake-compat": [
"devenv",
"cachix",
"flake-compat"
],
"nix": "nix",
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix",
"pre-commit-hooks": [
"devenv",
"cachix",
"pre-commit-hooks"
]
},
"locked": {
"lastModified": 1708704632,
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
"owner": "cachix",
"repo": "devenv",
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "python-rewrite",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
@@ -147,354 +20,27 @@
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1689068808,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"devenv",
"cachix",
"devenv",
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression"
},
"locked": {
"lastModified": 1712911606,
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"devenv",
"cachix",
"devenv",
"poetry2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688870561,
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nix2container": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1724996935,
"narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=",
"owner": "nlewo",
"repo": "nix2container",
"rev": "fa6bb0a1159f55d071ba99331355955ae30b3401",
"type": "github"
},
"original": {
"owner": "nlewo",
"repo": "nix2container",
"type": "github"
}
},
"nix_2": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-regression": "nixpkgs-regression_2"
},
"locked": {
"lastModified": 1712911606,
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
"owner": "domenkozar",
"repo": "nix",
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.21",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1692808169,
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
"lastModified": 1741173522,
"narHash": "sha256-k7VSqvv0r1r53nUI/IfPHCppkUAddeXn843YlAC5DR0=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
"rev": "d69ab0d71b22fa1ce3dbeff666e6deb4917db049",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-qt6": {
"locked": {
"lastModified": 1718428119,
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
"type": "github"
}
},
"nixpkgs-regression": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-regression_2": {
"locked": {
"lastModified": 1643052045,
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1710695816,
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1713361204,
"narHash": "sha256-TA6EDunWTkc5FvDCqU3W2T3SFn0gRZqh6D/hJnM02MM=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1724819573,
"narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "71e91c409d1e654808b2621f28a327acfdad8dc2",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"devenv",
"cachix",
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1692876271,
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": [
"devenv",
"flake-compat"
],
"flake-utils": "flake-utils_2",
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1713775815,
"narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"devenv-root": "devenv-root",
"flake-parts": "flake-parts",
"nix2container": "nix2container",
"nixpkgs": "nixpkgs_3",
"nixpkgs-qt6": "nixpkgs-qt6",
"systems": "systems_4"
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
@@ -511,51 +57,6 @@
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_4": {
"locked": {
"lastModified": 1689347949,
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
"owner": "nix-systems",
"repo": "default-linux",
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default-linux",
"type": "github"
}
}
},
"root": "root",

222
flake.nix
View File

@@ -2,207 +2,67 @@
description = "TagStudio";
inputs = {
devenv.url = "github:cachix/devenv";
devenv-root = {
url = "file+file:///dev/null";
flake = false;
};
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
nix2container = {
url = "github:nlewo/nix2container";
inputs.nixpkgs.follows = "nixpkgs";
};
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
# Pinned to Qt version 6.7.1
nixpkgs-qt6.url = "github:NixOS/nixpkgs/e6cea36f83499eb4e9cd184c8a8e823296b50ad5";
systems.url = "github:nix-systems/default-linux";
systems.url = "github:nix-systems/default";
};
outputs =
{
inputs@{
flake-parts,
nixpkgs,
nixpkgs-qt6,
self,
systems,
...
}@inputs:
}:
let
inherit (nixpkgs) lib;
in
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [ inputs.devenv.flakeModule ];
systems = import systems;
systems = import inputs.systems;
perSystem =
{ pkgs, ... }:
{
config,
pkgs,
system,
...
}:
let
inherit (nixpkgs) lib;
packages =
let
pythonPackages = pkgs.python312Packages;
qt6Pkgs = import nixpkgs-qt6 { inherit system; };
in
{
formatter = pkgs.nixfmt-rfc-style;
devenv.shells = rec {
default = tagstudio;
tagstudio =
let
cfg = config.devenv.shells.tagstudio;
in
{
# NOTE: many things were simply transferred over from previous,
# there must be additional work in ensuring all relevant dependencies
# are in place (and no extraneous). I have already spent much
# work making this in the first place and just need to get it out
# there, especially after my promises. Would appreciate any help
# (possibly PRs!) on taking care of this. Otherwise, just expect
# this to get ironed out over time.
#
# Thank you! -Xarvex
devenv.root =
let
devenvRoot = builtins.readFile inputs.devenv-root.outPath;
in
# If not overriden (/dev/null), --impure is necessary.
pkgs.lib.mkIf (devenvRoot != "") devenvRoot;
name = "TagStudio";
# Derived from previous flake iteration.
packages =
(with pkgs; [
cmake
binutils
coreutils
dbus
fontconfig
freetype
gdb
glib
libGL
libGLU
libgcc
libxkbcommon
mypy
ruff
xorg.libxcb
zstd
])
++ (with qt6Pkgs; [
qt6.full
qt6.qtbase
qt6.qtwayland
qtcreator
]);
enterShell =
let
setQtEnv =
pkgs.runCommand "set-qt-env"
{
buildInputs = with qt6Pkgs.qt6; [
qtbase
];
nativeBuildInputs =
(with pkgs; [
makeShellWrapper
])
++ (with qt6Pkgs.qt6; [
wrapQtAppsHook
]);
}
''
makeShellWrapper "$(type -p sh)" "$out" "''${qtWrapperArgs[@]}"
sed "/^exec/d" -i "$out"
'';
in
''
source ${setQtEnv}
'';
scripts.tagstudio.exec = ''
python ${cfg.devenv.root}/tagstudio/tag_studio.py
'';
env = {
QT_QPA_PLATFORM = "wayland;xcb";
# Derived from previous flake iteration.
# Not desired given LD_LIBRARY_PATH pollution.
# See supposed alternative below, further research required.
LD_LIBRARY_PATH = lib.makeLibraryPath (
(with pkgs; [
dbus
fontconfig
freetype
gcc-unwrapped
glib
libglvnd
libkrb5
libpulseaudio
libva
libxkbcommon
openssl
stdenv.cc.cc.lib
wayland
xorg.libxcb
xorg.libXrandr
zlib
zstd
])
++ (with qt6Pkgs.qt6; [
qtbase
qtwayland
full
])
);
};
languages.python = {
enable = true;
venv = {
enable = true;
quiet = true;
requirements =
let
excludeDeps =
req: deps:
builtins.concatStringsSep "\n" (
builtins.filter (line: !(lib.any (elem: lib.hasPrefix elem line) deps)) (lib.splitString "\n" req)
);
in
''
${builtins.readFile ./requirements.txt}
${excludeDeps (builtins.readFile ./requirements-dev.txt) [
"mypy"
"ruff"
]}
'';
};
# Should be able to replace LD_LIBRARY_PATH?
# Was not quite able to get working,
# will be consulting cachix community. -Xarvex
# libraries = with pkgs; [ ];
};
pillow-jxl-plugin = pythonPackages.callPackage ./nix/package/pillow-jxl-plugin.nix {
inherit (pkgs) cmake;
inherit pyexiv2;
inherit (pkgs) rustPlatform;
};
pyexiv2 = pythonPackages.callPackage ./nix/package/pyexiv2.nix { inherit (pkgs) exiv2; };
vtf2img = pythonPackages.callPackage ./nix/package/vtf2img.nix { };
in
rec {
default = tagstudio;
tagstudio = pythonPackages.callPackage ./nix/package {
inherit pillow-jxl-plugin vtf2img;
};
tagstudio-jxl = tagstudio.override { withJXLSupport = true; };
inherit pillow-jxl-plugin pyexiv2 vtf2img;
};
devShells = rec {
default = tagstudio;
tagstudio = import ./nix/shell.nix {
inherit
inputs
lib
pkgs
self
;
};
};
formatter = pkgs.nixfmt-rfc-style;
};
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 KiB

140
mkdocs.yml Normal file
View File

@@ -0,0 +1,140 @@
# yaml-language-server: $schema=https://squidfunk.github.io/mkdocs-material/schema.json
# MkDocs: https://www.mkdocs.org/
# Material for MkDocs: https://squidfunk.github.io/mkdocs-material/
# To install:
# pip install mkdocs-material
# To run the preview server:
# mkdocs serve
---
site_name: TagStudio
site_description: "A User-Focused Photo & File Management System"
site_url: https://docs.tagstud.io/
repo_url: https://github.com/TagStudioDev/TagStudio
edit_uri: blob/main/docs/
repo_name: TagStudioDev/TagStudio
extra:
social:
- icon: fontawesome/brands/github
link: https://github.com/TagStudioDev
- icon: fontawesome/brands/discord
link: https://discord.gg/hRNnVKhF2G
tags:
Upcoming Feature: upcoming
nav:
- Home:
- index.md
- install.md
- usage.md
- develop.md
- Help:
- help/ffmpeg.md
- Library:
- library/index.md
- library/entry.md
- library/entry_groups.md
- library/field.md
- library/library_search.md
- library/tag.md
- library/tag_categories.md
- library/tag_color.md
- library/tag_overrides.md
- Utilities:
- utilities/macro.md
- Updates:
- updates/changelog.md
- updates/roadmap.md
- updates/schema_changes.md
theme:
name: material
palette:
# Palette toggle for automatic mode
- media: "(prefers-color-scheme)"
toggle:
icon: material/brightness-auto
name: Switch to light mode
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: deep purple
accent: deep purple
toggle:
icon: material/brightness-7
name: Switch to dark mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: deep purple
accent: deep purple
toggle:
icon: material/brightness-4
name: SSwitch to system preference
logo: assets/icon.png
favicon: assets/icon.ico
font: false # use system fonts
language: en
features:
- navigation.instant
- navigation.indexes
- navigation.tracking
- navigation.expand
- navigation.sections
- search.suggest
- content.code.annotate
- content.code.copy
- content.action.edit
icon:
repo: fontawesome/brands/github
tag:
upcoming: material/flask-outline
markdown_extensions:
# Python Markdown
- abbr
- admonition
- attr_list
- def_list
- footnotes
- md_in_html
- toc:
permalink: true
# Python Markdown Extensions
- pymdownx.arithmatex:
generic: true
- pymdownx.betterem:
smart_enable: all
- pymdownx.caret
- pymdownx.details
- pymdownx.highlight
- pymdownx.inlinehilite
- pymdownx.keys
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.tilde
- pymdownx.snippets
plugins:
- search
- tags
- social: # social embed cards
enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions)
extra_css:
- stylesheets/extra.css

135
nix/package/default.nix Normal file
View File

@@ -0,0 +1,135 @@
{
buildPythonApplication,
chardet,
ffmpeg-headless,
ffmpeg-python,
hatchling,
humanfriendly,
lib,
mutagen,
numpy,
opencv-python,
pillow,
pillow-heif,
pillow-jxl-plugin,
pipewire,
pydantic,
pydub,
pyside6,
pytest-qt,
pytest-xdist,
pytestCheckHook,
pythonRelaxDepsHook,
qt6,
rawpy,
send2trash,
sqlalchemy,
stdenv,
structlog,
syrupy,
toml,
ujson,
vtf2img,
wrapGAppsHook,
withJXLSupport ? false,
}:
let
pyproject = (lib.importTOML ../../pyproject.toml).project;
in
buildPythonApplication {
pname = pyproject.name;
inherit (pyproject) version;
pyproject = true;
src = ../../.;
nativeBuildInputs = [
pythonRelaxDepsHook
qt6.wrapQtAppsHook
# INFO: Should be unnecessary once PR is pulled.
# PR: https://github.com/NixOS/nixpkgs/pull/271037
# Issue: https://github.com/NixOS/nixpkgs/issues/149812
wrapGAppsHook
];
buildInputs = [
qt6.qtbase
qt6.qtmultimedia
];
nativeCheckInputs = [
pytest-qt
pytest-xdist
pytestCheckHook
syrupy
];
# TODO: Install more icon resolutions when available.
preInstall = ''
mkdir -p $out/share/applications $out/share/icons/hicolor/512x512/apps
cp $src/src/tagstudio/resources/tagstudio.desktop $out/share/applications
cp $src/src/tagstudio/resources/icon.png $out/share/icons/hicolor/512x512/apps/tagstudio.png
'';
makeWrapperArgs =
[ "--prefix PATH : ${lib.makeBinPath [ ffmpeg-headless ]}" ]
++ lib.optional stdenv.hostPlatform.isLinux "--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath [ pipewire ]
}";
pythonRemoveDeps = true;
pythonImportsCheck = [ "tagstudio" ];
build-system = [ hatchling ];
dependencies = [
chardet
ffmpeg-python
humanfriendly
mutagen
numpy
opencv-python
pillow
pillow-heif
pydantic
pydub
pyside6
rawpy
send2trash
sqlalchemy
structlog
toml
ujson
vtf2img
] ++ lib.optional withJXLSupport pillow-jxl-plugin;
disabledTests = [
# INFO: These tests require modifications to a library, which does not work
# in a read-only environment.
"test_build_tag_panel_add_alias_callback"
"test_build_tag_panel_add_aliases"
"test_build_tag_panel_add_sub_tag_callback"
"test_build_tag_panel_build_tag"
"test_build_tag_panel_remove_alias_callback"
"test_build_tag_panel_remove_subtag_callback"
"test_build_tag_panel_set_aliases"
"test_build_tag_panel_set_parent_tags"
"test_build_tag_panel_set_tag"
"test_json_migration"
"test_library_migrations"
# INFO: This test requires modification of a configuration file.
"test_filepath_setting"
];
meta = {
inherit (pyproject) description;
homepage = "https://docs.tagstud.io/";
license = lib.licenses.gpl3Only;
maintainers = with lib.maintainers; [ xarvex ];
mainProgram = "tagstudio";
platforms = lib.platforms.unix;
};
}

View File

@@ -0,0 +1,67 @@
{
buildPythonPackage,
cmake,
fetchPypi,
lib,
numpy,
packaging,
pillow,
pyexiv2,
pytestCheckHook,
rustPlatform,
}:
buildPythonPackage rec {
pname = "pillow-jxl-plugin";
version = "1.3.2";
pyproject = true;
src = fetchPypi {
pname = builtins.replaceStrings [ "-" ] [ "_" ] pname;
inherit version;
hash = "sha256-efBoek8yUFR+ArhS55lm9F2XhkZ7/I3GsScQEe8U/2I=";
};
cargoDeps = rustPlatform.fetchCargoVendor {
inherit src;
hash = "sha256-vZHrwGfgo3fIIOY7p0vy4XIKiHoddPDdJggkBen+w/A=";
};
nativeBuildInputs = [
cmake
rustPlatform.cargoSetupHook
rustPlatform.maturinBuildHook
];
nativeCheckInputs = [
numpy
pyexiv2
pytestCheckHook
];
# INFO: Working directory takes precedence in the Python path. Remove
# `pillow_jxl` to prevent it from being loaded during pytest, rather than the
# built module, as it includes a `pillow_jxl.pillow_jxl` .so that is imported.
# See: https://github.com/NixOS/nixpkgs/issues/255262
# See: https://github.com/NixOS/nixpkgs/pull/255471
preCheck = ''
rm -r pillow_jxl
'';
dontUseCmakeConfigure = true;
pythonImportsCheck = [ "pillow_jxl" ];
dependencies = [
packaging
pillow
];
meta = {
description = "Pillow plugin for JPEG-XL, using Rust for bindings.";
homepage = "https://github.com/Isotr0py/pillow-jpegxl-plugin";
license = lib.licenses.gpl3;
maintainers = with lib.maintainers; [ xarvex ];
platforms = lib.platforms.unix;
};
}

32
nix/package/pyexiv2.nix Normal file
View File

@@ -0,0 +1,32 @@
{
autoPatchelfHook,
buildPythonPackage,
exiv2,
fetchFromGitHub,
lib,
}:
buildPythonPackage rec {
pname = "pyexiv2";
version = "2.15.3";
src = fetchFromGitHub {
owner = "LeoHsiao1";
repo = pname;
rev = "v${version}";
hash = "sha256-83bFMaoXncvhRJNcCgkkC7B29wR5pjuLO/EdkQdqxxo=";
};
nativeBuildInputs = [ autoPatchelfHook ];
buildInputs = [ exiv2.lib ];
pythonImportsCheck = [ "pyexiv2" ];
meta = {
description = "Read and write image metadata, including EXIF, IPTC, XMP, ICC Profile.";
homepage = "https://github.com/LeoHsiao1/pyexiv2";
license = lib.licenses.gpl3;
maintainers = with lib.maintainers; [ xarvex ];
platforms = with lib.platforms; darwin ++ linux ++ windows;
};
}

29
nix/package/vtf2img.nix Normal file
View File

@@ -0,0 +1,29 @@
{
buildPythonPackage,
fetchPypi,
lib,
pillow,
}:
buildPythonPackage rec {
pname = "vtf2img";
version = "0.1.0";
src = fetchPypi {
inherit pname version;
hash = "sha256-YmWs8673d72wH4nTOXP4AFGs2grIETln4s1MD5PfE0A=";
};
pythonImportsCheck = [ "vtf2img" ];
dependencies = [ pillow ];
meta = {
description = "A Python library to convert Valve Texture Format (VTF) files to images.";
homepage = "https://github.com/julienc91/vtf2img";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ xarvex ];
mainProgram = "vtf2img";
platforms = lib.platforms.unix;
};
}

104
nix/shell.nix Normal file
View File

@@ -0,0 +1,104 @@
{ lib, pkgs, ... }:
let
# INFO: PySide6 from PIP is compiled for 6.8.0 of Qt, must pin to match version.
# Long term this can be solved with an alternative like uv2nix for the devshell.
qt6Pkgs = import (builtins.fetchTarball {
name = "nixos-unstable-qt6-pinned";
url = "https://github.com/NixOS/nixpkgs/archive/93ff48c9be84a76319dac293733df09bbbe3f25c.tar.gz";
sha256 = "038m7932v4z0zn2lk7k7mbqbank30q84r1viyhchw9pcm3aq3q23";
}) { inherit (pkgs) system; };
pythonLibraryPath = lib.makeLibraryPath (
(with pkgs; [
fontconfig.lib
freetype
glib
stdenv.cc.cc.lib
zstd
])
++ (with qt6Pkgs.qt6; [ qtbase ])
++ lib.optionals (!pkgs.stdenv.isDarwin) (
(with pkgs; [
dbus.lib
libGL
libdrm
libpulseaudio
libva
libxkbcommon
pipewire
xorg.libX11
xorg.libXrandr
])
++ (with qt6Pkgs.qt6; [ qtwayland ])
)
);
libraryPath = "${lib.optionalString pkgs.stdenv.isDarwin "DY"}LD_LIBRARY_PATH";
python = pkgs.python312;
pythonWrapped = pkgs.symlinkJoin {
inherit (python)
name
pname
version
meta
;
paths = [ python ];
nativeBuildInputs = (with pkgs; [ makeWrapper ]) ++ (with qt6Pkgs.qt6; [ wrapQtAppsHook ]);
buildInputs = with qt6Pkgs.qt6; [ qtbase ];
postBuild = ''
wrapProgram $out/bin/python3.12 \
--prefix ${libraryPath} : ${pythonLibraryPath} \
"''${qtWrapperArgs[@]}"
'';
};
in
pkgs.mkShellNoCC {
nativeBuildInputs = with pkgs; [
coreutils
uv
ruff
];
buildInputs = [ pythonWrapped ] ++ (with pkgs; [ ffmpeg-headless ]);
env = {
QT_QPA_PLATFORM = "wayland;xcb";
UV_NO_SYNC = "1";
UV_PYTHON_DOWNLOADS = "never";
};
shellHook =
let
python = lib.getExe pythonWrapped;
in
# bash
''
venv="''${UV_PROJECT_ENVIRONMENT:-.venv}"
if [ ! -f "''${venv}"/bin/activate ] || [ "$(readlink -f "''${venv}"/bin/python)" != "$(readlink -f ${python})" ]; then
printf '%s\n' 'Regenerating virtual environment, Python interpreter changed...' >&2
rm -rf "''${venv}"
uv venv --python ${python} "''${venv}"
fi
source "''${venv}"/bin/activate
if [ ! -f "''${venv}"/pyproject.toml ] || ! diff --brief pyproject.toml "''${venv}"/pyproject.toml >/dev/null; then
printf '%s\n' 'Installing dependencies, pyproject.toml changed...' >&2
uv pip install --quiet --editable '.[mkdocs,mypy,pre-commit,pytest]'
cp pyproject.toml "''${venv}"/pyproject.toml
fi
pre-commit install
'';
meta = {
maintainers = with lib.maintainers; [ xarvex ];
platforms = lib.platforms.unix;
};
}

View File

@@ -1,9 +1,111 @@
[tool.ruff]
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py", "**/vendored/"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "TagStudio"
description = "A User-Focused Photo & File Management System."
version = "9.5.2"
license = "GPL-3.0-only"
readme = "README.md"
dependencies = [
"chardet==5.2.0",
"ffmpeg-python==0.2.0",
"humanfriendly==10.0",
"mutagen==1.47.0",
"numpy==2.1.0",
"opencv_python==4.10.0.84",
"Pillow==10.3.0",
"pillow-heif==0.16.0",
"pillow-jxl-plugin==1.3.0",
"pydub==0.25.1",
"PySide6==6.8.0.1",
"rawpy==0.22.0",
"Send2Trash==1.8.3",
"SQLAlchemy==2.0.34",
"structlog==24.4.0",
"typing_extensions>=3.10.0.0,<4.11.0",
"ujson>=5.8.0,<5.9.0",
"vtf2img==0.1.0",
"toml==0.10.2",
"pydantic==2.9.2",
]
[project.optional-dependencies]
dev = ["tagstudio[mkdocs,mypy,pre-commit,pyinstaller,pytest,ruff]"]
mkdocs = ["mkdocs-material[imaging]==9.*"]
mypy = ["mypy==1.11.2", "mypy-extensions==1.*", "types-ujson>=5.8.0,<5.9.0"]
pre-commit = ["pre-commit==3.7.0"]
pyinstaller = ["Pyinstaller==6.6.0"]
pytest = [
"pytest==8.2.0",
"pytest-cov==5.0.0",
"pytest-qt==4.4.0",
"syrupy==4.7.1",
]
ruff = ["ruff==0.8.1"]
[project.gui-scripts]
tagstudio = "tagstudio.main:main"
[tool.hatch.build.targets.wheel]
packages = ["src/tagstudio"]
[tool.mypy]
strict_optional = false
disable_error_code = ["union-attr", "annotation-unchecked", "import-untyped"]
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 = ['tests', 'src/qt/helpers/vendored']
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'"
qt_api = "pyside6"
[tool.pyright]
ignore = [".venv/**"]
include = ["src/tagstudio/**"]
reportAny = false
reportIgnoreCommentWithoutRule = false
reportImplicitStringConcatenation = false
reportMissingTypeArgument = false
# reportOptionalMemberAccess = false
reportUnannotatedClassAttribute = false
reportUnknownArgumentType = false
reportUnknownLambdaType = false
reportUnknownMemberType = false
reportUnusedCallResult = false
[tool.ruff]
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]
line-length = 100
[tool.ruff.lint]
select = ["B", "D", "E", "F", "FBT003", "I", "N", "SIM", "T20", "UP"]
ignore = ["D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107"]
[tool.ruff.lint.per-file-ignores]
"tests/**" = ["D", "E402"]
"src/tagstudio/qt/helpers/vendored/**" = ["B", "E", "N", "UP", "SIM115"]
[tool.ruff.lint.pydocstyle]
convention = "google"

View File

@@ -1,6 +0,0 @@
ruff==0.4.2
pre-commit==3.7.0
pytest==8.2.0
Pyinstaller==6.6.0
mypy==1.10.0
syrupy==4.6.1

View File

@@ -1,18 +0,0 @@
humanfriendly==10.0
opencv_python>=4.8.0.74,<=4.9.0.80
Pillow==10.3.0
PySide6==6.7.1
PySide6_Addons==6.7.1
PySide6_Essentials==6.7.1
typing_extensions>=3.10.0.0,<=4.11.0
ujson>=5.8.0,<=5.9.0
numpy==1.26.4
rawpy==0.21.0
pillow-heif==0.16.0
chardet==5.2.0
pydub==0.25.1
mutagen==1.47.0
numpy==1.26.4
ffmpeg-python==0.2.0
Send2Trash==1.8.3
vtf2img==0.1.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 KiB

View File

@@ -0,0 +1,29 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
VERSION: str = "9.5.2" # Major.Minor.Patch
VERSION_BRANCH: str = "" # Usually "" or "Pre-Release"
# The folder & file names where TagStudio keeps its data relative to a library.
TS_FOLDER_NAME: str = ".TagStudio"
BACKUP_FOLDER_NAME: str = "backups"
COLLAGE_FOLDER_NAME: str = "collages"
THUMB_CACHE_NAME: str = "thumbs"
FONT_SAMPLE_TEXT: str = (
"""ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?@$%(){}[]"""
)
FONT_SAMPLE_SIZES: list[int] = [10, 15, 20]
# NOTE: These were the field IDs used for the "Tags", "Content Tags", and "Meta Tags" fields inside
# the legacy JSON database. These are used to help migrate libraries from JSON to SQLite.
LEGACY_TAG_FIELD_IDS: set[int] = {6, 7, 8}
TAG_ARCHIVED = 0
TAG_FAVORITE = 1
TAG_META = 2
RESERVED_TAG_START = 0
RESERVED_TAG_END = 999
RESERVED_NAMESPACE_PREFIX = "tagstudio"

View File

@@ -0,0 +1,43 @@
from pathlib import Path
import structlog
from PySide6.QtCore import QSettings
from tagstudio.core.constants import TS_FOLDER_NAME
from tagstudio.core.enums import SettingItems
from tagstudio.core.global_settings import GlobalSettings
from tagstudio.core.library.alchemy.library import LibraryStatus
logger = structlog.get_logger(__name__)
class DriverMixin:
cached_values: QSettings
settings: GlobalSettings
def evaluate_path(self, open_path: str | None) -> LibraryStatus:
"""Check if the path of library is valid."""
library_path: Path | None = None
if open_path:
library_path = Path(open_path).expanduser()
if not library_path.exists():
logger.error("Path does not exist.", open_path=open_path)
return LibraryStatus(success=False, message="Path does not exist.")
elif self.settings.open_last_loaded_on_startup and self.cached_values.value(
SettingItems.LAST_LIBRARY
):
library_path = Path(str(self.cached_values.value(SettingItems.LAST_LIBRARY)))
if not (library_path / TS_FOLDER_NAME).exists():
logger.error(
"TagStudio folder does not exist.",
library_path=library_path,
ts_folder=TS_FOLDER_NAME,
)
self.cached_values.setValue(SettingItems.LAST_LIBRARY, "")
# dont consider this a fatal error, just skip opening the library
library_path = None
return LibraryStatus(
success=True,
library_path=library_path,
)

View File

@@ -0,0 +1,78 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import enum
from typing import Any
from uuid import uuid4
class SettingItems(str, enum.Enum):
"""List of setting item names."""
LAST_LIBRARY = "last_library"
LIBS_LIST = "libs_list"
THUMB_CACHE_SIZE_LIMIT = "thumb_cache_size_limit"
class ShowFilepathOption(int, enum.Enum):
"""Values representing the options for the "show_filenames" setting."""
SHOW_FULL_PATHS = 0
SHOW_RELATIVE_PATHS = 1
SHOW_FILENAMES_ONLY = 2
DEFAULT = SHOW_RELATIVE_PATHS
class Theme(str, enum.Enum):
COLOR_BG_DARK = "#65000000"
COLOR_BG_LIGHT = "#22000000"
COLOR_DARK_LABEL = "#DD000000"
COLOR_BG = "#65000000"
COLOR_HOVER = "#65444444"
COLOR_PRESSED = "#65777777"
COLOR_DISABLED_BG = "#30000000"
COLOR_FORBIDDEN = "#65F39CAA"
COLOR_FORBIDDEN_BG = "#65440D12"
class OpenStatus(enum.IntEnum):
NOT_FOUND = 0
SUCCESS = 1
CORRUPTED = 2
class MacroID(enum.Enum):
AUTOFILL = "autofill"
SIDECAR = "sidecar"
BUILD_URL = "build_url"
MATCH = "match"
CLEAN_URL = "clean_url"
class DefaultEnum(enum.Enum):
"""Allow saving multiple identical values in property called .default."""
default: Any
def __new__(cls, value):
# Create the enum instance
obj = object.__new__(cls)
# make value random
obj._value_ = uuid4()
# assign the actual value into .default property
obj.default = value
return obj
@property
def value(self):
raise AttributeError("access the value via .default property instead")
class LibraryPrefs(DefaultEnum):
"""Library preferences with default value accessible via .default property."""
IS_EXCLUDE_LIST = True
EXTENSION_LIST = [".json", ".xmp", ".aae"]
DB_VERSION = 9

View File

@@ -0,0 +1,6 @@
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
class NoRendererError(Exception): ...

View File

@@ -0,0 +1,71 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import platform
from enum import Enum
from pathlib import Path
from typing import override
import structlog
import toml
from pydantic import BaseModel, Field
from tagstudio.core.enums import ShowFilepathOption
if platform.system() == "Windows":
DEFAULT_GLOBAL_SETTINGS_PATH = (
Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml"
)
else:
DEFAULT_GLOBAL_SETTINGS_PATH = Path.home() / ".config" / "TagStudio" / "settings.toml"
logger = structlog.get_logger(__name__)
class TomlEnumEncoder(toml.TomlEncoder):
@override
def dump_value(self, v):
if isinstance(v, Enum):
return super().dump_value(v.value)
return super().dump_value(v)
class Theme(Enum):
DARK = 0
LIGHT = 1
SYSTEM = 2
DEFAULT = SYSTEM
# NOTE: pydantic also has a BaseSettings class (from pydantic-settings) that allows any settings
# properties to be overwritten with environment variables. as tagstudio is not currently using
# environment variables, i did not base it on that, but that may be useful in the future.
class GlobalSettings(BaseModel):
language: str = Field(default="en")
open_last_loaded_on_startup: bool = Field(default=True)
autoplay: bool = Field(default=True)
loop: bool = Field(default=True)
show_filenames_in_grid: bool = Field(default=True)
page_size: int = Field(default=100)
show_filepath: ShowFilepathOption = Field(default=ShowFilepathOption.DEFAULT)
theme: Theme = Field(default=Theme.SYSTEM)
@staticmethod
def read_settings(path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> "GlobalSettings":
if path.exists():
with open(path) as file:
filecontents = file.read()
if len(filecontents.strip()) != 0:
logger.info("[Settings] Reading Global Settings File", path=path)
settings_data = toml.loads(filecontents)
settings = GlobalSettings(**settings_data)
return settings
return GlobalSettings()
def save(self, path: Path = DEFAULT_GLOBAL_SETTINGS_PATH) -> None:
if not path.parent.exists():
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
toml.dump(dict(self), f, encoder=TomlEnumEncoder())

View File

@@ -0,0 +1,70 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from pathlib import Path
import structlog
from sqlalchemy import Dialect, Engine, String, TypeDecorator, create_engine, text
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import DeclarativeBase
from tagstudio.core.constants import RESERVED_TAG_END
logger = structlog.getLogger(__name__)
class PathType(TypeDecorator):
impl = String
cache_ok = True
def process_bind_param(self, value: Path, dialect: Dialect):
if value is not None:
return Path(value).as_posix()
return None
def process_result_value(self, value: str, dialect: Dialect):
if value is not None:
return Path(value)
return None
class Base(DeclarativeBase):
type_annotation_map = {Path: PathType}
def make_engine(connection_string: str) -> Engine:
return create_engine(connection_string)
def make_tables(engine: Engine) -> None:
logger.info("[Library] Creating DB tables...")
Base.metadata.create_all(engine)
# tag IDs < 1000 are reserved
# create tag and delete it to bump the autoincrement sequence
# TODO - find a better way
# is this the better way?
with engine.connect() as conn:
result = conn.execute(text("SELECT SEQ FROM sqlite_sequence WHERE name='tags'"))
autoincrement_val = result.scalar()
if not autoincrement_val or autoincrement_val <= RESERVED_TAG_END:
try:
conn.execute(
text(
"INSERT INTO tags "
"(id, name, color_namespace, color_slug, is_category) VALUES "
f"({RESERVED_TAG_END}, 'temp', NULL, NULL, false)"
)
)
conn.execute(text(f"DELETE FROM tags WHERE id = {RESERVED_TAG_END}"))
conn.commit()
except OperationalError as e:
logger.error("Could not initialize built-in tags", error=e)
conn.rollback()
def drop_tables(engine: Engine) -> None:
logger.info("dropping db tables")
Base.metadata.drop_all(engine)

View File

@@ -0,0 +1,572 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import structlog
from tagstudio.core.library.alchemy.models import Namespace, TagColorGroup
logger = structlog.get_logger(__name__)
def namespaces() -> list[Namespace]:
tagstudio_standard = Namespace("tagstudio-standard", "TagStudio Standard")
tagstudio_pastels = Namespace("tagstudio-pastels", "TagStudio Pastels")
tagstudio_shades = Namespace("tagstudio-shades", "TagStudio Shades")
tagstudio_earth_tones = Namespace("tagstudio-earth-tones", "TagStudio Earth Tones")
tagstudio_grayscale = Namespace("tagstudio-grayscale", "TagStudio Grayscale")
tagstudio_neon = Namespace("tagstudio-neon", "TagStudio Neon")
return [
tagstudio_standard,
tagstudio_pastels,
tagstudio_shades,
tagstudio_earth_tones,
tagstudio_grayscale,
tagstudio_neon,
]
def json_to_sql_color(json_color: str) -> tuple[str | None, str | None]:
"""Convert a color string from a <=9.4 JSON library to a 9.5+ (namespace, slug) tuple."""
json_color_ = json_color.lower()
match json_color_:
case "black":
return ("tagstudio-grayscale", "black")
case "dark gray":
return ("tagstudio-grayscale", "dark-gray")
case "gray":
return ("tagstudio-grayscale", "gray")
case "light gray":
return ("tagstudio-grayscale", "light-gray")
case "white":
return ("tagstudio-grayscale", "white")
case "light pink":
return ("tagstudio-pastels", "light-pink")
case "pink":
return ("tagstudio-standard", "pink")
case "magenta":
return ("tagstudio-standard", "magenta")
case "red":
return ("tagstudio-standard", "red")
case "red orange":
return ("tagstudio-standard", "red-orange")
case "salmon":
return ("tagstudio-pastels", "salmon")
case "orange":
return ("tagstudio-standard", "orange")
case "yellow orange":
return ("tagstudio-standard", "amber")
case "yellow":
return ("tagstudio-standard", "yellow")
case "mint":
return ("tagstudio-pastels", "mint")
case "lime":
return ("tagstudio-standard", "lime")
case "light green":
return ("tagstudio-pastels", "light-green")
case "green":
return ("tagstudio-standard", "green")
case "teal":
return ("tagstudio-standard", "teal")
case "cyan":
return ("tagstudio-standard", "cyan")
case "light blue":
return ("tagstudio-pastels", "light-blue")
case "blue":
return ("tagstudio-standard", "blue")
case "blue violet":
return ("tagstudio-shades", "navy")
case "violet":
return ("tagstudio-standard", "indigo")
case "purple":
return ("tagstudio-standard", "purple")
case "peach":
return ("tagstudio-earth-tones", "peach")
case "brown":
return ("tagstudio-earth-tones", "brown")
case "lavender":
return ("tagstudio-pastels", "lavender")
case "blonde":
return ("tagstudio-earth-tones", "blonde")
case "auburn":
return ("tagstudio-shades", "auburn")
case "light brown":
return ("tagstudio-earth-tones", "light-brown")
case "dark brown":
return ("tagstudio-earth-tones", "dark-brown")
case "cool gray":
return ("tagstudio-earth-tones", "cool-gray")
case "warm gray":
return ("tagstudio-earth-tones", "warm-gray")
case "olive":
return ("tagstudio-shades", "olive")
case "berry":
return ("tagstudio-shades", "berry")
case _:
return (None, None)
def standard() -> list[TagColorGroup]:
red = TagColorGroup(
slug="red",
namespace="tagstudio-standard",
name="Red",
primary="#E22C3C",
)
red_orange = TagColorGroup(
slug="red-orange",
namespace="tagstudio-standard",
name="Red Orange",
primary="#E83726",
)
orange = TagColorGroup(
slug="orange",
namespace="tagstudio-standard",
name="Orange",
primary="#ED6022",
)
amber = TagColorGroup(
slug="amber",
namespace="tagstudio-standard",
name="Amber",
primary="#FA9A2C",
)
yellow = TagColorGroup(
slug="yellow",
namespace="tagstudio-standard",
name="Yellow",
primary="#FFD63D",
)
lime = TagColorGroup(
slug="lime",
namespace="tagstudio-standard",
name="Lime",
primary="#92E649",
)
green = TagColorGroup(
slug="green",
namespace="tagstudio-standard",
name="Green",
primary="#45D649",
)
teal = TagColorGroup(
slug="teal",
namespace="tagstudio-standard",
name="Teal",
primary="#22D589",
)
cyan = TagColorGroup(
slug="cyan",
namespace="tagstudio-standard",
name="Cyan",
primary="#3DDBDB",
)
blue = TagColorGroup(
slug="blue",
namespace="tagstudio-standard",
name="Blue",
primary="#3B87F0",
)
indigo = TagColorGroup(
slug="indigo",
namespace="tagstudio-standard",
name="Indigo",
primary="#874FF5",
)
purple = TagColorGroup(
slug="purple",
namespace="tagstudio-standard",
name="Purple",
primary="#BB4FF0",
)
magenta = TagColorGroup(
slug="magenta",
namespace="tagstudio-standard",
name="Magenta",
primary="#F64680",
)
pink = TagColorGroup(
slug="pink",
namespace="tagstudio-standard",
name="Pink",
primary="#FF62AF",
)
return [
red,
red_orange,
orange,
amber,
yellow,
lime,
green,
teal,
cyan,
blue,
indigo,
purple,
pink,
magenta,
]
def pastels() -> list[TagColorGroup]:
coral = TagColorGroup(
slug="coral",
namespace="tagstudio-pastels",
name="Coral",
primary="#F2525F",
)
salmon = TagColorGroup(
slug="salmon",
namespace="tagstudio-pastels",
name="Salmon",
primary="#F66348",
)
light_orange = TagColorGroup(
slug="light-orange",
namespace="tagstudio-pastels",
name="Light Orange",
primary="#FF9450",
)
light_amber = TagColorGroup(
slug="light-amber",
namespace="tagstudio-pastels",
name="Light Amber",
primary="#FFBA57",
)
light_yellow = TagColorGroup(
slug="light-yellow",
namespace="tagstudio-pastels",
name="Light Yellow",
primary="#FFE173",
)
light_lime = TagColorGroup(
slug="light-lime",
namespace="tagstudio-pastels",
name="Light Lime",
primary="#C9FF7A",
)
light_green = TagColorGroup(
slug="light-green",
namespace="tagstudio-pastels",
name="Light Green",
primary="#81FF76",
)
mint = TagColorGroup(
slug="mint",
namespace="tagstudio-pastels",
name="Mint",
primary="#68FFB4",
)
sky_blue = TagColorGroup(
slug="sky-blue",
namespace="tagstudio-pastels",
name="Sky Blue",
primary="#8EFFF4",
)
light_blue = TagColorGroup(
slug="light-blue",
namespace="tagstudio-pastels",
name="Light Blue",
primary="#64C6FF",
)
lavender = TagColorGroup(
slug="lavender",
namespace="tagstudio-pastels",
name="Lavender",
primary="#908AF6",
)
lilac = TagColorGroup(
slug="lilac",
namespace="tagstudio-pastels",
name="Lilac",
primary="#DF95FF",
)
light_pink = TagColorGroup(
slug="light-pink",
namespace="tagstudio-pastels",
name="Light Pink",
primary="#FF87BA",
)
return [
coral,
salmon,
light_orange,
light_amber,
light_yellow,
light_lime,
light_green,
mint,
sky_blue,
light_blue,
lavender,
lilac,
light_pink,
]
def shades() -> list[TagColorGroup]:
burgundy = TagColorGroup(
slug="burgundy",
namespace="tagstudio-shades",
name="Burgundy",
primary="#6E1C24",
)
auburn = TagColorGroup(
slug="auburn",
namespace="tagstudio-shades",
name="Auburn",
primary="#A13220",
)
olive = TagColorGroup(
slug="olive",
namespace="tagstudio-shades",
name="Olive",
primary="#4C652E",
)
dark_teal = TagColorGroup(
slug="dark-teal",
namespace="tagstudio-shades",
name="Dark Teal",
primary="#1F5E47",
)
navy = TagColorGroup(
slug="navy",
namespace="tagstudio-shades",
name="Navy",
primary="#104B98",
)
dark_lavender = TagColorGroup(
slug="dark_lavender",
namespace="tagstudio-shades",
name="Dark Lavender",
primary="#3D3B6C",
)
berry = TagColorGroup(
slug="berry",
namespace="tagstudio-shades",
name="Berry",
primary="#9F2AA7",
)
return [burgundy, auburn, olive, dark_teal, navy, dark_lavender, berry]
def earth_tones() -> list[TagColorGroup]:
dark_brown = TagColorGroup(
slug="dark-brown",
namespace="tagstudio-earth-tones",
name="Dark Brown",
primary="#4C2315",
)
brown = TagColorGroup(
slug="brown",
namespace="tagstudio-earth-tones",
name="Brown",
primary="#823216",
)
light_brown = TagColorGroup(
slug="light-brown",
namespace="tagstudio-earth-tones",
name="Light Brown",
primary="#BE5B2D",
)
blonde = TagColorGroup(
slug="blonde",
namespace="tagstudio-earth-tones",
name="Blonde",
primary="#EFC664",
)
peach = TagColorGroup(
slug="peach",
namespace="tagstudio-earth-tones",
name="Peach",
primary="#F1C69C",
)
warm_gray = TagColorGroup(
slug="warm-gray",
namespace="tagstudio-earth-tones",
name="Warm Gray",
primary="#625550",
)
cool_gray = TagColorGroup(
slug="cool-gray",
namespace="tagstudio-earth-tones",
name="Cool Gray",
primary="#515768",
)
return [dark_brown, brown, light_brown, blonde, peach, warm_gray, cool_gray]
def grayscale() -> list[TagColorGroup]:
black = TagColorGroup(
slug="black",
namespace="tagstudio-grayscale",
name="Black",
primary="#111018",
)
dark_gray = TagColorGroup(
slug="dark-gray",
namespace="tagstudio-grayscale",
name="Dark Gray",
primary="#242424",
)
gray = TagColorGroup(
slug="gray",
namespace="tagstudio-grayscale",
name="Gray",
primary="#53525A",
)
light_gray = TagColorGroup(
slug="light-gray",
namespace="tagstudio-grayscale",
name="Light Gray",
primary="#AAAAAA",
)
white = TagColorGroup(
slug="white",
namespace="tagstudio-grayscale",
name="White",
primary="#F2F1F8",
)
return [black, dark_gray, gray, light_gray, white]
def neon() -> list[TagColorGroup]:
neon_red = TagColorGroup(
slug="neon-red",
namespace="tagstudio-neon",
name="Neon Red",
primary="#180607",
secondary="#E22C3C",
color_border=True,
)
neon_red_orange = TagColorGroup(
slug="neon-red-orange",
namespace="tagstudio-neon",
name="Neon Red Orange",
primary="#220905",
secondary="#E83726",
color_border=True,
)
neon_orange = TagColorGroup(
slug="neon-orange",
namespace="tagstudio-neon",
name="Neon Orange",
primary="#1F0D05",
secondary="#ED6022",
color_border=True,
)
neon_amber = TagColorGroup(
slug="neon-amber",
namespace="tagstudio-neon",
name="Neon Amber",
primary="#251507",
secondary="#FA9A2C",
color_border=True,
)
neon_yellow = TagColorGroup(
slug="neon-yellow",
namespace="tagstudio-neon",
name="Neon Yellow",
primary="#2B1C0B",
secondary="#FFD63D",
color_border=True,
)
neon_lime = TagColorGroup(
slug="neon-lime",
namespace="tagstudio-neon",
name="Neon Lime",
primary="#1B220C",
secondary="#92E649",
color_border=True,
)
neon_green = TagColorGroup(
slug="neon-green",
namespace="tagstudio-neon",
name="Neon Green",
primary="#091610",
secondary="#45D649",
color_border=True,
)
neon_teal = TagColorGroup(
slug="neon-teal",
namespace="tagstudio-neon",
name="Neon Teal",
primary="#09191D",
secondary="#22D589",
color_border=True,
)
neon_cyan = TagColorGroup(
slug="neon-cyan",
namespace="tagstudio-neon",
name="Neon Cyan",
primary="#0B191C",
secondary="#3DDBDB",
color_border=True,
)
neon_blue = TagColorGroup(
slug="neon-blue",
namespace="tagstudio-neon",
name="Neon Blue",
primary="#09101C",
secondary="#3B87F0",
color_border=True,
)
neon_indigo = TagColorGroup(
slug="neon-indigo",
namespace="tagstudio-neon",
name="Neon Indigo",
primary="#150B24",
secondary="#874FF5",
color_border=True,
)
neon_purple = TagColorGroup(
slug="neon-purple",
namespace="tagstudio-neon",
name="Neon Purple",
primary="#1E0B26",
secondary="#BB4FF0",
color_border=True,
)
neon_magenta = TagColorGroup(
slug="neon-magenta",
namespace="tagstudio-neon",
name="Neon Magenta",
primary="#220A13",
secondary="#F64680",
color_border=True,
)
neon_pink = TagColorGroup(
slug="neon-pink",
namespace="tagstudio-neon",
name="Neon Pink",
primary="#210E15",
secondary="#FF62AF",
color_border=True,
)
neon_white = TagColorGroup(
slug="neon-white",
namespace="tagstudio-neon",
name="Neon White",
primary="#131315",
secondary="#F2F1F8",
color_border=True,
)
return [
neon_red,
neon_red_orange,
neon_orange,
neon_amber,
neon_yellow,
neon_lime,
neon_green,
neon_teal,
neon_cyan,
neon_blue,
neon_indigo,
neon_purple,
neon_pink,
neon_magenta,
neon_white,
]

View File

@@ -0,0 +1,136 @@
import enum
from dataclasses import dataclass, replace
from pathlib import Path
import structlog
from tagstudio.core.query_lang.ast import AST, Constraint, ConstraintType
from tagstudio.core.query_lang.parser import Parser
MAX_SQL_VARIABLES = 32766 # 32766 is the max sql bind parameter count as defined here: https://github.com/sqlite/sqlite/blob/master/src/sqliteLimit.h#L140
logger = structlog.get_logger(__name__)
class TagColorEnum(enum.IntEnum):
DEFAULT = 1
BLACK = 2
DARK_GRAY = 3
GRAY = 4
LIGHT_GRAY = 5
WHITE = 6
LIGHT_PINK = 7
PINK = 8
RED = 9
RED_ORANGE = 10
ORANGE = 11
YELLOW_ORANGE = 12
YELLOW = 13
LIME = 14
LIGHT_GREEN = 15
MINT = 16
GREEN = 17
TEAL = 18
CYAN = 19
LIGHT_BLUE = 20
BLUE = 21
BLUE_VIOLET = 22
VIOLET = 23
PURPLE = 24
LAVENDER = 25
BERRY = 26
MAGENTA = 27
SALMON = 28
AUBURN = 29
DARK_BROWN = 30
BROWN = 31
LIGHT_BROWN = 32
BLONDE = 33
PEACH = 34
WARM_GRAY = 35
COOL_GRAY = 36
OLIVE = 37
@staticmethod
def get_color_from_str(color_name: str) -> "TagColorEnum":
for color in TagColorEnum:
if color.name == color_name.upper().replace(" ", "_"):
return color
return TagColorEnum.DEFAULT
class ItemType(enum.Enum):
ENTRY = 0
COLLATION = 1
TAG_GROUP = 2
class SortingModeEnum(enum.Enum):
DATE_ADDED = "file.date_added"
FILE_NAME = "generic.filename"
PATH = "file.path"
@dataclass
class FilterState:
"""Represent a state of the Library grid view."""
# these should remain
page_size: int
page_index: int = 0
sorting_mode: SortingModeEnum = SortingModeEnum.DATE_ADDED
ascending: bool = True
# these should be erased on update
# Abstract Syntax Tree Of the current Search Query
ast: AST | None = None
@property
def limit(self):
return self.page_size
@property
def offset(self):
return self.page_size * self.page_index
@classmethod
def show_all(cls, page_size: int) -> "FilterState":
return FilterState(page_size=page_size)
@classmethod
def from_search_query(cls, search_query: str, page_size: int) -> "FilterState":
return cls(ast=Parser(search_query).parse(), page_size=page_size)
@classmethod
def from_tag_id(cls, tag_id: int | str, page_size: int) -> "FilterState":
return cls(ast=Constraint(ConstraintType.TagID, str(tag_id), []), page_size=page_size)
@classmethod
def from_path(cls, path: Path | str, page_size: int) -> "FilterState":
return cls(ast=Constraint(ConstraintType.Path, str(path).strip(), []), page_size=page_size)
@classmethod
def from_mediatype(cls, mediatype: str, page_size: int) -> "FilterState":
return cls(ast=Constraint(ConstraintType.MediaType, mediatype, []), page_size=page_size)
@classmethod
def from_filetype(cls, filetype: str, page_size: int) -> "FilterState":
return cls(ast=Constraint(ConstraintType.FileType, filetype, []), page_size=page_size)
@classmethod
def from_tag_name(cls, tag_name: str, page_size: int) -> "FilterState":
return cls(ast=Constraint(ConstraintType.Tag, tag_name, []), page_size=page_size)
def with_sorting_mode(self, mode: SortingModeEnum) -> "FilterState":
return replace(self, sorting_mode=mode)
def with_sorting_direction(self, ascending: bool) -> "FilterState":
return replace(self, ascending=ascending)
class FieldTypeEnum(enum.Enum):
TEXT_LINE = "Text Line"
TEXT_BOX = "Text Box"
TAGS = "Tags"
DATETIME = "Datetime"
BOOLEAN = "Checkbox"

View File

@@ -0,0 +1,140 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import TYPE_CHECKING, Any
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, declared_attr, mapped_column, relationship
from tagstudio.core.library.alchemy.db import Base
from tagstudio.core.library.alchemy.enums import FieldTypeEnum
if TYPE_CHECKING:
from tagstudio.core.library.alchemy.models import Entry, ValueType
class BaseField(Base):
__abstract__ = True
@declared_attr
def id(self) -> Mapped[int]:
return mapped_column(primary_key=True, autoincrement=True)
@declared_attr
def type_key(self) -> Mapped[str]:
return mapped_column(ForeignKey("value_type.key"))
@declared_attr
def type(self) -> Mapped[ValueType]:
return relationship(foreign_keys=[self.type_key], lazy=False) # type: ignore
@declared_attr
def entry_id(self) -> Mapped[int]:
return mapped_column(ForeignKey("entries.id"))
@declared_attr
def entry(self) -> Mapped[Entry]:
return relationship(foreign_keys=[self.entry_id]) # type: ignore
@declared_attr
def position(self) -> Mapped[int]:
return mapped_column(default=0)
def __hash__(self):
return hash(self.__key())
def __key(self):
raise NotImplementedError
value: Any
class BooleanField(BaseField):
__tablename__ = "boolean_fields"
value: Mapped[bool]
def __key(self):
return (self.type, self.value)
def __eq__(self, value) -> bool:
if isinstance(value, BooleanField):
return self.__key() == value.__key()
raise NotImplementedError
class TextField(BaseField):
__tablename__ = "text_fields"
value: Mapped[str | None]
def __key(self) -> tuple:
return self.type, self.value
def __eq__(self, value) -> bool:
if isinstance(value, TextField):
return self.__key() == value.__key()
elif isinstance(value, DatetimeField):
return False
raise NotImplementedError
class DatetimeField(BaseField):
__tablename__ = "datetime_fields"
value: Mapped[str | None]
def __key(self):
return (self.type, self.value)
def __eq__(self, value) -> bool:
if isinstance(value, DatetimeField):
return self.__key() == value.__key()
raise NotImplementedError
@dataclass
class DefaultField:
id: int
name: str
type: FieldTypeEnum
is_default: bool = field(default=False)
class _FieldID(Enum):
"""Only for bootstrapping content of DB table."""
TITLE = DefaultField(id=0, name="Title", type=FieldTypeEnum.TEXT_LINE, is_default=True)
AUTHOR = DefaultField(id=1, name="Author", type=FieldTypeEnum.TEXT_LINE)
ARTIST = DefaultField(id=2, name="Artist", type=FieldTypeEnum.TEXT_LINE)
URL = DefaultField(id=3, name="URL", type=FieldTypeEnum.TEXT_LINE)
DESCRIPTION = DefaultField(id=4, name="Description", type=FieldTypeEnum.TEXT_BOX)
NOTES = DefaultField(id=5, name="Notes", type=FieldTypeEnum.TEXT_BOX)
COLLATION = DefaultField(id=9, name="Collation", type=FieldTypeEnum.TEXT_LINE)
DATE = DefaultField(id=10, name="Date", type=FieldTypeEnum.DATETIME)
DATE_CREATED = DefaultField(id=11, name="Date Created", type=FieldTypeEnum.DATETIME)
DATE_MODIFIED = DefaultField(id=12, name="Date Modified", type=FieldTypeEnum.DATETIME)
DATE_TAKEN = DefaultField(id=13, name="Date Taken", type=FieldTypeEnum.DATETIME)
DATE_PUBLISHED = DefaultField(id=14, name="Date Published", type=FieldTypeEnum.DATETIME)
# ARCHIVED = DefaultField(id=15, name="Archived", type=CheckboxField.checkbox)
# FAVORITE = DefaultField(id=16, name="Favorite", type=CheckboxField.checkbox)
BOOK = DefaultField(id=17, name="Book", type=FieldTypeEnum.TEXT_LINE)
COMIC = DefaultField(id=18, name="Comic", type=FieldTypeEnum.TEXT_LINE)
SERIES = DefaultField(id=19, name="Series", type=FieldTypeEnum.TEXT_LINE)
MANGA = DefaultField(id=20, name="Manga", type=FieldTypeEnum.TEXT_LINE)
SOURCE = DefaultField(id=21, name="Source", type=FieldTypeEnum.TEXT_LINE)
DATE_UPLOADED = DefaultField(id=22, name="Date Uploaded", type=FieldTypeEnum.DATETIME)
DATE_RELEASED = DefaultField(id=23, name="Date Released", type=FieldTypeEnum.DATETIME)
VOLUME = DefaultField(id=24, name="Volume", type=FieldTypeEnum.TEXT_LINE)
ANTHOLOGY = DefaultField(id=25, name="Anthology", type=FieldTypeEnum.TEXT_LINE)
MAGAZINE = DefaultField(id=26, name="Magazine", type=FieldTypeEnum.TEXT_LINE)
PUBLISHER = DefaultField(id=27, name="Publisher", type=FieldTypeEnum.TEXT_LINE)
GUEST_ARTIST = DefaultField(id=28, name="Guest Artist", type=FieldTypeEnum.TEXT_LINE)
COMPOSER = DefaultField(id=29, name="Composer", type=FieldTypeEnum.TEXT_LINE)
COMMENTS = DefaultField(id=30, name="Comments", type=FieldTypeEnum.TEXT_LINE)

View File

@@ -0,0 +1,23 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from sqlalchemy import ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from tagstudio.core.library.alchemy.db import Base
class TagParent(Base):
__tablename__ = "tag_parents"
parent_id: Mapped[int] = mapped_column(ForeignKey("tags.id"), primary_key=True)
child_id: Mapped[int] = mapped_column(ForeignKey("tags.id"), primary_key=True)
class TagEntry(Base):
__tablename__ = "tag_entries"
tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id"), primary_key=True)
entry_id: Mapped[int] = mapped_column(ForeignKey("entries.id"), primary_key=True)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,318 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from datetime import datetime as dt
from pathlib import Path
from sqlalchemy import JSON, ForeignKey, ForeignKeyConstraint, Integer, event
from sqlalchemy.orm import Mapped, mapped_column, relationship
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE
from tagstudio.core.library.alchemy.db import Base, PathType
from tagstudio.core.library.alchemy.enums import FieldTypeEnum
from tagstudio.core.library.alchemy.fields import (
BaseField,
BooleanField,
DatetimeField,
TextField,
)
from tagstudio.core.library.alchemy.joins import TagParent
class Namespace(Base):
__tablename__ = "namespaces"
namespace: Mapped[str] = mapped_column(primary_key=True, nullable=False)
name: Mapped[str] = mapped_column(nullable=False)
def __init__(
self,
namespace: str,
name: str,
):
self.namespace = namespace
self.name = name
super().__init__()
class TagAlias(Base):
__tablename__ = "tag_aliases"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False)
tag_id: Mapped[int] = mapped_column(ForeignKey("tags.id"))
tag: Mapped["Tag"] = relationship(back_populates="aliases")
def __init__(self, name: str, tag_id: int | None = None):
self.name = name
if tag_id is not None:
self.tag_id = tag_id
super().__init__()
class TagColorGroup(Base):
__tablename__ = "tag_colors"
slug: Mapped[str] = mapped_column(primary_key=True, nullable=False)
namespace: Mapped[str] = mapped_column(
ForeignKey("namespaces.namespace"), primary_key=True, nullable=False
)
name: Mapped[str] = mapped_column()
primary: Mapped[str] = mapped_column(nullable=False)
secondary: Mapped[str | None]
color_border: Mapped[bool] = mapped_column(nullable=False, default=False)
# TODO: Determine if slug and namespace can be optional and generated/added here if needed.
def __init__(
self,
slug: str,
namespace: str,
name: str,
primary: str,
secondary: str | None = None,
color_border: bool = False,
):
self.slug = slug
self.namespace = namespace
self.name = name
self.primary = primary
if secondary:
self.secondary = secondary
self.color_border = color_border
super().__init__()
class Tag(Base):
__tablename__ = "tags"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
name: Mapped[str]
shorthand: Mapped[str | None]
color_namespace: Mapped[str | None] = mapped_column()
color_slug: Mapped[str | None] = mapped_column()
color: Mapped[TagColorGroup | None] = relationship(lazy="joined")
is_category: Mapped[bool]
icon: Mapped[str | None]
aliases: Mapped[set[TagAlias]] = relationship(back_populates="tag")
parent_tags: Mapped[set["Tag"]] = relationship(
secondary=TagParent.__tablename__,
primaryjoin="Tag.id == TagParent.parent_id",
secondaryjoin="Tag.id == TagParent.child_id",
back_populates="parent_tags",
)
disambiguation_id: Mapped[int | None]
__table_args__ = (
ForeignKeyConstraint(
[color_namespace, color_slug], [TagColorGroup.namespace, TagColorGroup.slug]
),
{"sqlite_autoincrement": True},
)
@property
def parent_ids(self) -> list[int]:
return [tag.id for tag in self.parent_tags]
@property
def alias_strings(self) -> list[str]:
return [alias.name for alias in self.aliases]
@property
def alias_ids(self) -> list[int]:
return [tag.id for tag in self.aliases]
def __init__(
self,
id: int | None = None,
name: str | None = None,
shorthand: str | None = None,
aliases: set[TagAlias] | None = None,
parent_tags: set["Tag"] | None = None,
icon: str | None = None,
color_namespace: str | None = None,
color_slug: str | None = None,
disambiguation_id: int | None = None,
is_category: bool = False,
):
self.name = name
self.aliases = aliases or set()
self.parent_tags = parent_tags or set()
self.color_namespace = color_namespace
self.color_slug = color_slug
self.icon = icon
self.shorthand = shorthand
self.disambiguation_id = disambiguation_id
self.is_category = is_category
assert not self.id
self.id = id
super().__init__()
def __str__(self) -> str:
return f"<Tag ID: {self.id} Name: {self.name}>"
def __repr__(self) -> str:
return self.__str__()
def __lt__(self, other) -> bool:
return self.name < other.name
def __le__(self, other) -> bool:
return self.name <= other.name
def __gt__(self, other) -> bool:
return self.name > other.name
def __ge__(self, other) -> bool:
return self.name >= other.name
class Folder(Base):
__tablename__ = "folders"
# TODO - implement this
id: Mapped[int] = mapped_column(primary_key=True)
path: Mapped[Path] = mapped_column(PathType, unique=True)
uuid: Mapped[str] = mapped_column(unique=True)
class Entry(Base):
__tablename__ = "entries"
id: Mapped[int] = mapped_column(primary_key=True)
folder_id: Mapped[int] = mapped_column(ForeignKey("folders.id"))
folder: Mapped[Folder] = relationship("Folder")
path: Mapped[Path] = mapped_column(PathType, unique=True)
filename: Mapped[str] = mapped_column()
suffix: Mapped[str] = mapped_column()
date_created: Mapped[dt | None]
date_modified: Mapped[dt | None]
date_added: Mapped[dt | None]
tags: Mapped[set[Tag]] = relationship(secondary="tag_entries")
text_fields: Mapped[list[TextField]] = relationship(
back_populates="entry",
cascade="all, delete",
)
datetime_fields: Mapped[list[DatetimeField]] = relationship(
back_populates="entry",
cascade="all, delete",
)
@property
def fields(self) -> list[BaseField]:
fields: list[BaseField] = []
fields.extend(self.text_fields)
fields.extend(self.datetime_fields)
fields = sorted(fields, key=lambda field: field.type.position)
return fields
@property
def is_favorite(self) -> bool:
return any(tag.id == TAG_FAVORITE for tag in self.tags)
@property
def is_archived(self) -> bool:
return any(tag.id == TAG_ARCHIVED for tag in self.tags)
def __init__(
self,
path: Path,
folder: Folder,
fields: list[BaseField],
id: int | None = None,
date_created: dt | None = None,
date_modified: dt | None = None,
date_added: dt | None = None,
) -> None:
self.path = path
self.folder = folder
self.id = id
self.filename = path.name
self.suffix = path.suffix.lstrip(".").lower()
# The date the file associated with this entry was created.
# st_birthtime on Windows and Mac, st_ctime on Linux.
self.date_created = date_created
# The date the file associated with this entry was last modified: st_mtime.
self.date_modified = date_modified
# The date this entry was added to the library.
self.date_added = date_added
for field in fields:
if isinstance(field, TextField):
self.text_fields.append(field)
elif isinstance(field, DatetimeField):
self.datetime_fields.append(field)
else:
raise ValueError(f"Invalid field type: {field}")
def has_tag(self, tag: Tag) -> bool:
return tag in self.tags
def remove_tag(self, tag: Tag) -> None:
"""Removes a Tag from the Entry."""
self.tags.remove(tag)
class ValueType(Base):
"""Define Field Types in the Library.
Example:
key: content_tags (this field is slugified `name`)
name: Content Tags (this field is human readable name)
kind: type of content (Text Line, Text Box, Tags, Datetime, Checkbox)
is_default: Should the field be present in new Entry?
order: position of the field widget in the Entry form
"""
__tablename__ = "value_type"
key: Mapped[str] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(nullable=False)
type: Mapped[FieldTypeEnum] = mapped_column(default=FieldTypeEnum.TEXT_LINE)
is_default: Mapped[bool]
position: Mapped[int]
# add relations to other tables
text_fields: Mapped[list[TextField]] = relationship("TextField", back_populates="type")
datetime_fields: Mapped[list[DatetimeField]] = relationship(
"DatetimeField", back_populates="type"
)
boolean_fields: Mapped[list[BooleanField]] = relationship("BooleanField", back_populates="type")
@property
def as_field(self) -> BaseField:
FieldClass = { # noqa: N806
FieldTypeEnum.TEXT_LINE: TextField,
FieldTypeEnum.TEXT_BOX: TextField,
FieldTypeEnum.DATETIME: DatetimeField,
FieldTypeEnum.BOOLEAN: BooleanField,
}
return FieldClass[self.type](
type_key=self.key,
position=self.position,
)
@event.listens_for(ValueType, "before_insert")
def slugify_field_key(mapper, connection, target):
"""Slugify the field key before inserting into the database."""
if not target.key:
from tagstudio.core.library.alchemy.library import slugify
target.key = slugify(target.tag)
class Preferences(Base):
__tablename__ = "preferences"
key: Mapped[str] = mapped_column(primary_key=True)
value: Mapped[dict] = mapped_column(JSON, nullable=False)

View File

@@ -0,0 +1,203 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import re
from typing import TYPE_CHECKING
import structlog
from sqlalchemy import ColumnElement, and_, distinct, func, or_, select, text
from sqlalchemy.orm import Session
from sqlalchemy.sql.operators import ilike_op
from tagstudio.core.library.alchemy.joins import TagEntry
from tagstudio.core.library.alchemy.models import Entry, Tag, TagAlias
from tagstudio.core.media_types import FILETYPE_EQUIVALENTS, MediaCategories
from tagstudio.core.query_lang.ast import (
ANDList,
BaseVisitor,
Constraint,
ConstraintType,
Not,
ORList,
Property,
)
# Only import for type checking/autocompletion, will not be imported at runtime.
if TYPE_CHECKING:
from tagstudio.core.library.alchemy.library import Library
else:
Library = None # don't import library because of circular imports
logger = structlog.get_logger(__name__)
# TODO: Reevaluate after subtags -> parent tags name change
TAG_CHILDREN_ID_QUERY = text("""
-- Note for this entire query that tag_parents.child_id is the parent id and tag_parents.parent_id is the child id due to bad naming
WITH RECURSIVE ChildTags AS (
SELECT :tag_id AS child_id
UNION
SELECT tp.parent_id AS child_id
FROM tag_parents tp
INNER JOIN ChildTags c ON tp.child_id = c.child_id
)
SELECT child_id FROM ChildTags;
""") # noqa: E501
def get_filetype_equivalency_list(item: str) -> list[str] | set[str]:
for s in FILETYPE_EQUIVALENTS:
if item in s:
return s
return [item]
class SQLBoolExpressionBuilder(BaseVisitor[ColumnElement[bool]]):
def __init__(self, lib: Library) -> None:
super().__init__()
self.lib = lib
def visit_or_list(self, node: ORList) -> ColumnElement[bool]:
return or_(*[self.visit(element) for element in node.elements])
def visit_and_list(self, node: ANDList) -> ColumnElement[bool]:
tag_ids: list[int] = []
bool_expressions: list[ColumnElement[bool]] = []
# Search for TagID / unambiguous Tag Constraints and store the respective tag ids separately
for term in node.terms:
if isinstance(term, Constraint) and len(term.properties) == 0:
match term.type:
case ConstraintType.TagID:
try:
tag_ids.append(int(term.value))
except ValueError:
logger.error(
"[SQLBoolExpressionBuilder] Could not cast value to an int Tag ID",
value=term.value,
)
continue
case ConstraintType.Tag:
if len(ids := self.__get_tag_ids(term.value)) == 1:
tag_ids.append(ids[0])
continue
bool_expressions.append(self.visit(term))
# If there are at least two tag ids use a relational division query
# to efficiently check all of them
if len(tag_ids) > 1:
bool_expressions.append(self.__entry_has_all_tags(tag_ids))
# If there is just one tag id, check the normal way
elif len(tag_ids) == 1:
bool_expressions.append(
self.__entry_satisfies_expression(TagEntry.tag_id == tag_ids[0])
)
return and_(*bool_expressions)
def visit_constraint(self, node: Constraint) -> ColumnElement[bool]:
"""Returns a Boolean Expression that is true, if the Entry satisfies the constraint."""
if len(node.properties) != 0:
raise NotImplementedError("Properties are not implemented yet") # TODO TSQLANG
if node.type == ConstraintType.Tag:
return self.__entry_matches_tag_ids(self.__get_tag_ids(node.value))
elif node.type == ConstraintType.TagID:
return self.__entry_matches_tag_ids([int(node.value)])
elif node.type == ConstraintType.Path:
ilike = False
glob = False
# Smartcase check
if node.value == node.value.lower():
ilike = True
if node.value.startswith("*") or node.value.endswith("*"):
glob = True
if ilike and glob:
logger.info("ConstraintType.Path", ilike=True, glob=True)
return func.lower(Entry.path).op("GLOB")(f"{node.value.lower()}")
elif ilike:
logger.info("ConstraintType.Path", ilike=True, glob=False)
return ilike_op(Entry.path, f"%{node.value}%")
elif glob:
logger.info("ConstraintType.Path", ilike=False, glob=True)
return Entry.path.op("GLOB")(node.value)
else:
logger.info(
"ConstraintType.Path", ilike=False, glob=False, re=re.escape(node.value)
)
return Entry.path.regexp_match(re.escape(node.value))
elif node.type == ConstraintType.MediaType:
extensions: set[str] = set[str]()
for media_cat in MediaCategories.ALL_CATEGORIES:
if node.value == media_cat.name:
extensions = extensions | media_cat.extensions
break
return Entry.suffix.in_(map(lambda x: x.replace(".", ""), extensions))
elif node.type == ConstraintType.FileType:
return or_(
*[Entry.suffix.ilike(ft) for ft in get_filetype_equivalency_list(node.value)]
)
elif node.type == ConstraintType.Special: # noqa: SIM102 unnecessary once there is a second special constraint
if node.value.lower() == "untagged":
return ~Entry.id.in_(select(Entry.id).join(TagEntry))
# raise exception if Constraint stays unhandled
raise NotImplementedError("This type of constraint is not implemented yet")
def visit_property(self, node: Property) -> None:
raise NotImplementedError("This should never be reached!")
def visit_not(self, node: Not) -> ColumnElement[bool]:
return ~self.visit(node.child)
def __entry_matches_tag_ids(self, tag_ids: list[int]) -> ColumnElement[bool]:
"""Returns a boolean expression that is true if the entry has at least one of the supplied tags.""" # noqa: E501
return (
select(1)
.correlate(Entry)
.where(and_(TagEntry.entry_id == Entry.id, TagEntry.tag_id.in_(tag_ids)))
.exists()
)
def __get_tag_ids(self, tag_name: str, include_children: bool = True) -> list[int]:
"""Given a tag name find the ids of all tags that this name could refer to."""
with Session(self.lib.engine) as session:
tag_ids = list(
session.scalars(
select(Tag.id)
.where(or_(Tag.name.ilike(tag_name), Tag.shorthand.ilike(tag_name)))
.union(select(TagAlias.tag_id).where(TagAlias.name.ilike(tag_name)))
)
)
if len(tag_ids) > 1:
logger.debug(
f'Tag Constraint "{tag_name}" is ambiguous, {len(tag_ids)} matching tags found',
tag_ids=tag_ids,
include_children=include_children,
)
if not include_children:
return tag_ids
outp = []
for tag_id in tag_ids:
outp.extend(list(session.scalars(TAG_CHILDREN_ID_QUERY, {"tag_id": tag_id})))
return outp
def __entry_has_all_tags(self, tag_ids: list[int]) -> ColumnElement[bool]:
"""Returns Binary Expression that is true if the Entry has all provided tag ids."""
# Relational Division Query
return Entry.id.in_(
select(TagEntry.entry_id)
.where(TagEntry.tag_id.in_(tag_ids))
.group_by(TagEntry.entry_id)
.having(func.count(distinct(TagEntry.tag_id)) == len(tag_ids))
)
def __entry_satisfies_expression(self, expr: ColumnElement[bool]) -> ColumnElement[bool]:
"""Returns Binary Expression that is true if the Entry satisfies the column expression.
Executed on: Entry ⟕ TagEntry (Entry LEFT OUTER JOIN TagEntry).
"""
return Entry.id.in_(select(Entry.id).outerjoin(TagEntry).where(expr))

View File

@@ -0,0 +1,38 @@
BOX_FIELDS = ["tag_box", "text_box"]
TEXT_FIELDS = ["text_line", "text_box"]
DATE_FIELDS = ["datetime"]
DEFAULT_FIELDS: list[dict] = [
{"id": 0, "name": "Title", "type": "text_line"},
{"id": 1, "name": "Author", "type": "text_line"},
{"id": 2, "name": "Artist", "type": "text_line"},
{"id": 3, "name": "URL", "type": "text_line"},
{"id": 4, "name": "Description", "type": "text_box"},
{"id": 5, "name": "Notes", "type": "text_box"},
{"id": 6, "name": "Tags", "type": "tag_box"},
{"id": 7, "name": "Content Tags", "type": "tag_box"},
{"id": 8, "name": "Meta Tags", "type": "tag_box"},
{"id": 9, "name": "Collation", "type": "collation"},
{"id": 10, "name": "Date", "type": "datetime"},
{"id": 11, "name": "Date Created", "type": "datetime"},
{"id": 12, "name": "Date Modified", "type": "datetime"},
{"id": 13, "name": "Date Taken", "type": "datetime"},
{"id": 14, "name": "Date Published", "type": "datetime"},
{"id": 15, "name": "Archived", "type": "checkbox"},
{"id": 16, "name": "Favorite", "type": "checkbox"},
{"id": 17, "name": "Book", "type": "collation"},
{"id": 18, "name": "Comic", "type": "collation"},
{"id": 19, "name": "Series", "type": "collation"},
{"id": 20, "name": "Manga", "type": "collation"},
{"id": 21, "name": "Source", "type": "text_line"},
{"id": 22, "name": "Date Uploaded", "type": "datetime"},
{"id": 23, "name": "Date Released", "type": "datetime"},
{"id": 24, "name": "Volume", "type": "collation"},
{"id": 25, "name": "Anthology", "type": "collation"},
{"id": 26, "name": "Magazine", "type": "collation"},
{"id": 27, "name": "Publisher", "type": "text_line"},
{"id": 28, "name": "Guest Artist", "type": "text_line"},
{"id": 29, "name": "Composer", "type": "text_line"},
{"id": 30, "name": "Comments", "type": "text_box"},
]

Some files were not shown because too many files have changed in this diff Show More