Compare commits

...

458 Commits

Author SHA1 Message Date
Xarvex
aa9917de9e fix(nix): add forgotten audioop-lts 2025-08-24 18:12:48 -05:00
Xarvex
3d62e592f7 chore(nix): use Python 3.13 2025-08-24 18:10:44 -05:00
Xarvex
70fa866b8d fix(pyproject): allow Python 3.13 to function 2025-08-24 18:09:48 -05:00
Xarvex
899c534467 fix(nix): fixup and rework, always use nixpkgs PySide/Qt (#1048) 2025-08-24 18:08:14 -05:00
Travis Abendshien
74383e3c3c feat: swap IDs in tag_parents table; bump DB to v100
commit c1346e7df36b137cf88be284a96329fee9605a6a
Author: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Date:   Sat Aug 23 18:04:58 2025 -0700

    docs: update DB v100 with tag_parents flip

commit 7e5d9381759b000533c809df9d9bc4f9d984e363
Author: HeikoWasTaken <heikowastaken@protonmail.com>
Date:   Sun Aug 24 00:31:21 2025 +0100

    fix: swap IDs in parent_tags DB table (#998)

    * fix: reorder child and parent IDs in TagParent constructor call

    * feat: add db10 migration

    * fix: SQL query returning parent IDs instead of children IDs

    * fix: stop assigning child tags as parents

    * fix: select and remove parent tags, instead of child tags

    * test/fix: correctly reorder child/parent args in broken test

    * fix: migrate json subtags as parent tags, instead of child tags (I see where it went wrong now lol)

    * fix: query parent tags instead of children

    * refactor: scooching this down below db9 migrations

    * test: add DB10 migration test

    ---------

    Co-authored-by: heiko <heiko_was_taken@protonmail.com>

commit 1ce02699ad9798800f9d98832b2a6377e3d79ed4
Author: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Date:   Sat Aug 23 14:47:39 2025 -0700

    feat: add db minor versioning, bump to 100
2025-08-23 18:17:19 -07:00
TheBobBobs
660a87bb94 feat: add random sorting (#1029)
* feat: add random sorting

* fix: improve randomness of random sort
2025-08-23 13:42:36 -07:00
Travis Abendshien
89cf2b22e4 fix(ui): fix vector previews not rendering 2025-08-22 15:33:58 -07:00
Travis Abendshien
133092cd05 fix(ui): fix clear square behind ignored icon 2025-08-21 17:31:26 -07:00
Travis Abendshien
94ac83768a docs: add sidebar icon for ignore page 2025-08-21 16:13:20 -07:00
Travis Abendshien
61ca3cb32a feat(ui): add exr thumbnail support (#1035) 2025-08-21 15:51:30 -07:00
Travis Abendshien
0e7a2dfd3d feat: add .ts_ignore pattern ignoring system (#897)
* feat: add `.ts_ignore` pattern ignoring system

* fix: add wcmatch dependency

* search: add ".TemporaryItems" to GLOBAL_IGNORE

* add `desktop.ini` and `.localized` to global ignore

* add ".fhdx" and ".ts" filetypes

* chore: remove logging statement

* chore: format with ruff

* feat: use ripgrep for scanning if available

* docs: add ignore.md

* search: remove ts_ignore filtering on queries

* feat: detect if files are added but ignored

* fix: render edges on all unlinked thumbs

* perf: don't search for cached unlinked thumbs

* fix(ui): ensure newlines in file stats

* fix: use ignore_to_glob for wcmatch

* fix(tests): remove inconsistent test

The test hinged on the timing of refresh_dir()'s yield's rather than actual values

* ui: change ignored icon and color
2025-08-21 15:50:59 -07:00
Travis Abendshien
d00546d5fe docs(roadmap): add infinite scrolling to roadmap 2025-08-19 17:33:44 -07:00
Travis Abendshien
969b1674f0 fix(search): pass current BrowsingState to from_tag_id() (#1038)
* fix(search): pass current BrowsingState to from_tag_id()

* chore: fix docstring

* fix: use existing query to build tag_id query

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

---------

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>
2025-08-19 12:58:09 -07:00
Travis Abendshien
00001bbf0b docs: update header style for mobile 2025-08-18 08:26:41 -07:00
Travis Abendshien
df064ad104 feat: add missing raw image, text, and audio types 2025-08-17 16:57:20 -07:00
Travis Abendshien
31d205a869 docs: update home page 2025-08-17 16:55:06 -07:00
Travis Abendshien
a55d9a6a67 docs: add preview support section to library page 2025-08-17 16:54:10 -07:00
Travis Abendshien
2c5c98c86c docs: update formatting of schema_changes page 2025-08-16 09:37:21 -07:00
Travis Abendshien
8cef5e5749 docs: change accent color to "purple" 2025-08-16 09:04:17 -07:00
Travis Abendshien
32a2b47c4c docs: remove minimal/unfinished pages 2025-08-15 20:45:04 -07:00
Travis Abendshien
7a44ef156d docs: add icons to sidebar and titles 2025-08-15 20:33:32 -07:00
Travis Abendshien
f2454c4a9a docs: rework roadmap (#1023)
* docs: rework core features

* docs: rework tag features

* docs: rework library features

* docs: rework search features

* docs: rework application features

* docs: rework macro features

* refactor(docs): remove card formatting for roadmap

* docs: roadmap tweaks
2025-08-12 11:53:26 -07:00
Jann Stute
537ecb2a55 fix: add tag to selected entries in bulk not individually (#1028) 2025-08-11 12:08:52 -07:00
Travis Abendshien
62f1b7ca55 ci: bump windows runner image to 2022 2025-08-07 19:46:29 -07:00
Travis Abendshien
8aec8ca11a chore: bump version to v9.5.3 2025-08-07 18:45:02 -07:00
Travis Abendshien
c235d4f727 ui: tweak media player style and behavior (#1025)
* ui: tweak media player style and behavior

* ui: change player overlay to gradient

* ui: add slight outline to player bar

* feat: realtime playback seeking

* fix(ui): click to jump to unbuffered media

* chore: organize imports

* refactor: remove unused code
2025-08-07 14:23:20 -07:00
Travis Abendshien
78e29a9a69 fix(ui): show correct thumb labels (#1010)
* fix(ui): show correct thumb labels

* fix(ui): use timestamp cutoff for thumb filename and ext
2025-08-07 12:33:38 -07:00
Trigam
c71032ff51 feat(ui): clickable links in text fields (#924)
* feat: links in text fields are clickable

* style: fix linting errors

* style: remove unnecessary comment

* feat: better url pattern

* chore: disable Ruff error

* style: add trailing line

* style: maybe format it? that'd be smart

* ui: chance text_label from RichText to Markdown

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-08-07 12:29:09 -07:00
Travis Abendshien
77cfa697fc fix: use srctools instead of vtf2img for vtf files (#1014) 2025-08-06 18:34:12 -07:00
Travis Abendshien
89fc8a7280 fix(docs): load non-local fonts 2025-08-06 13:40:20 -07:00
Travis Abendshien
809885e8b5 docs: add git clone step to develop page 2025-08-06 12:42:34 -07:00
Travis Abendshien
424ee3bc2c ci: relax pillow version, upgrade mkdocs-material 2025-08-06 12:29:50 -07:00
Travis Abendshien
278a6de8a6 feat(docs): vauxhall theme for docs site 2025-08-06 11:48:33 -07:00
Travis Abendshien
142e464f54 fix(ci): restore imaging extra for mkdocs-material 2025-08-05 11:40:36 -07:00
Jann Stute
fa0b82c004 fix: don't fail when posix env var is not present (#1018)
* fix: don't set MacOS specific values when not on MacOS

* refactor: use dict.get instead
2025-08-05 11:24:41 -07:00
Travis Abendshien
0d1311557a refactor: replace remaining instances of logging with structlog (#1012) 2025-08-05 10:38:51 -07:00
Travis Abendshien
4105e178bd fix: use np.frombuffer instead of np.fromstring for numpy 2.3 (#1013) 2025-08-05 10:37:15 -07:00
Travis Abendshien
4d4a4874d7 ui: add arrow icons for navigation buttons (#1016)
* ui: add arrow icons for nav buttons

* ui: use arrow icons for pagination buttons
2025-08-05 10:32:47 -07:00
Travis Abendshien
40f555edc7 fix(ui): hide empty ProgressWidget cancel button (#1011) 2025-08-05 10:32:28 -07:00
Travis Abendshien
7bef302f90 fix: restore item_thumb delete action 2025-08-04 16:06:52 -07:00
TheBobBobs
e115443811 perf: optimize page loading by referencing entries by ID (#954)
* refactor: search_library now returns ids instead of entries

* perf: optimize update_thumbs
2025-08-04 16:04:07 -07:00
Jann Stute
c2261d5b83 refactor(tag_box): mvc split (#1003)
* refactor: split into view and controller

* refactor: controller simplifications

* Update src/tagstudio/qt/controller/components/tag_box_controller.py

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

* refactor: split method

* refactor: add override specifiers

* fix: shutup mypy

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-08-04 15:06:22 -07:00
Thibault Maekelbergh
1459f79b23 build: add Nix path to ffmpeg locations on macOS (#990)
* Add Nix path to ffmpeg locations on macOS

* Add 'os' import
2025-08-04 15:02:14 -07:00
Jann Stute
1ee1ccbe8d fix: preview thumbnails don't scale as large as they could (#1005)
* fix: preview thumbnails don't scale as large as they could

* fix: only scale as large as necessary

* fix: explicitly declare vars
2025-08-04 14:39:17 -07:00
Travis Abendshien
4653eb2a04 fix: ensure initial browsing state uses UI values (#1008) 2025-08-04 12:43:37 -07:00
Jann Stute
940617cd63 fix(library): get_tag_by_name (#1006) 2025-08-03 13:03:36 -07:00
ugurozturk
9b287cb5af docs: fix incorrect release dates (#966)
* Wrong Date on Changelog fixed

* docs: fix remaining release dates

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-08-02 21:48:18 -07:00
VasigaranAndAngel
6b96ee79e1 refactor: type improvements for main_window.py (#957) 2025-08-02 21:37:11 -07:00
Travis Abendshien
7d9480e3cf ui: reorder thumbnail zoom actions 2025-08-02 21:28:27 -07:00
purpletennisball
45943ba00d feat: zoom keyboard shortcut (#956)
* feat: add increase/decrease zoom menu items

* fix: increase thumbnail size not working on windows

* fix: duplicated menu items
2025-08-02 21:22:44 -07:00
ngivanyh
d86fc5cdd0 translations: update Traditional Chinese (#974)
* translation - almost done

* almost done

* small stuft left

* finish, the only untranslated text  "The \"Favorite\" and \"Archived\" tags now inherit from a new 「Meta Tags」 tag which is marked as a category by default." of json_migration.info.description

* small changes - done
2025-08-01 17:00:51 -07:00
Weblate (bot)
ed142203aa translations: update from Hosted Weblate (#947)
* Added translation using Weblate (Thai)

Co-authored-by: Anucha Hlownonkor <tony.chompoo@gmail.com>

* Translated using Weblate (Filipino)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (319 of 319 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% (323 of 323 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (German)

Currently translated at 100.0% (323 of 323 strings)

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

* Translated using Weblate (Russian)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (323 of 323 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Maxim Truhonin <maxim.truhonin@gmail.com>
Co-authored-by: Utof <vip.ilyabondar@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/ru/
Translation: TagStudio/Strings

* Translated using Weblate (Japanese)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (319 of 319 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Hungarian)

Currently translated at 100.0% (319 of 319 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 38.0% (123 of 323 strings)

Translated using Weblate (Dutch)

Currently translated at 38.0% (123 of 323 strings)

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 99.6% (322 of 323 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (French)

Currently translated at 100.0% (319 of 319 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 (Chinese (Simplified Han script))

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (323 of 323 strings)

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

* Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (323 of 323 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (319 of 319 strings)

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

* Translated using Weblate (Toki Pona)

Currently translated at 95.3% (308 of 323 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: Anucha Hlownonkor <tony.chompoo@gmail.com>
Co-authored-by: searinminecraft <kitakita@disroot.org>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: Stereo157E <pascal-reinartz157e@gmx.de>
Co-authored-by: Maxim Truhonin <maxim.truhonin@gmail.com>
Co-authored-by: Utof <vip.ilyabondar@gmail.com>
Co-authored-by: wany-oh <wany-oh@users.noreply.hosted.weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Pheubel <luca.guagliardo@live.nl>
Co-authored-by: danpg94 <danpalma94@gmail.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Luoyu <tkiuvvv2333@gmail.com>
Co-authored-by: Neemek <sindodeg@gmail.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
2025-08-01 16:34:44 -07:00
mashed5894
fd7d1f1e95 feat(ui): krita/open raster thumbs (#985)
* added kra/krz media categories and thumb rendering logic

* added .ora support (follows open doc standard)

---------

Co-authored-by: fj92hg <>
2025-07-31 23:53:35 -07:00
Jann Stute
a7d98e765b refactor(preview_thumb): mvc split (#978)
* refactor: basic split

* fix: renaming and usage test didn't work for the tests

* fix: tests

* refactor: restructuring

* refactor: further separation and lots of related changes

* refactor: remove last reference to a widget from controller

* refactor: address todo

* fix: failing tests and mypy compaint

* refactor: move control logic to controller

* refactor: more readable button style

* refactor: move existing code to view

* refactor: move existing code to controller

* fix: imports

* refactor: make methods private by default

* refactor: privatise fields

* refactor: reduce code duplication

* refactor: consolidate and sort display methods

* refactor: remove needless setting of delete action text

* refactor: extract control logic from _display_file

* refactor: use MediaType for __switch_preview

* fix: import in preview_panel_view.py

* refactor: remove unnecessary wrapper on view side

* refactor: move image data retrieval to control side

* refactor: move audio / video specific code to the respective method

* refactor: remove superfluos methods

* refactor: this and that

* refactor: use proper type instead of dict for file stats

* refactor: extract gif parsing to controller

* refactor: extract video size extraction to controller

* doc: add rule of thumb to Qt MVC Style Guide

* doc: change rule of thumb from note to tip

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-07-31 23:46:46 -07:00
Jann Stute
192af25f6f refactor(preview_panel): mvc split (#952)
* refactor: basic split

* fix: renaming and usage test didn't work for the tests

* fix: tests

* refactor: restructuring

* refactor: further separation and lots of related changes

* refactor: remove last reference to a widget from controller

* refactor: address todo

* fix: failing tests and mypy compaint

* refactor: move control logic to controller

* refactor: more readable button style

* fix: set_selection was called with invalid argument
2025-07-31 22:53:32 -07:00
Jann Stute
7176908274 docs: Add QT MVC structure to style guide (#950)
* doc: first draft

* doc: highlight key aspects of example

* doc: address feedback

* doc: add comment on making methods private by default

* doc: fix typo

* doc: fix typos
2025-07-05 11:26:14 -07:00
VasigaranAndAngel
29154babf8 refactor: type improvements to landing.py and panel.py (#960)
* landing.py

* panel.py and a method's name change

* remove # noqa

* remove set_title method and pass title to the PanelModal constructor

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

* send keyPressEvent only if it's not used

* pass window_title to constructor

use `title` if `window_title` is `None`

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

---------

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>
2025-07-03 11:10:20 -07:00
VasigaranAndAngel
001ff14799 recator: fixes to preview_panel.py, progress.py, tag.py and tag_box.py (#961)
* preview_panel.py

* progress.py

* tag.py

* tag_box.py

* Revert "preview_panel.py"

This reverts commit bcb4515834.
2025-07-03 09:03:22 -07:00
VasigaranAndAngel
57ab919d1b refactor: type fixes to folders_to_tags.py, collage_icon.py and item_thumb.py (#959)
* folders_to_tags.py type fixes

* collage_icon.py

* item_thumb.py

* fix test and mypy errors

* naming fix

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

---------

Co-authored-by: Jann Stute <46534683+Computerdores@users.noreply.github.com>
2025-07-03 08:39:33 -07:00
VasigaranAndAngel
4e722278d8 refactor: resource_manager.py (#958)
* refactor

* describe return types in get method

* make ResourceManager singleton

* match case statement
2025-06-27 14:11:29 -07:00
Travis Abendshien
b8313c3f15 ui: enable Chinese (Simplified) and Viossa language options 2025-06-07 14:49:00 -07:00
Jann Stute
c100babd9f feat(ui): option to change tag primary click behavior (#945)
* feat: add settings field

* feat: change click behaviour based on settings value

* fix: make ignore comment pyright specific to shut up mypy

* fix: add german and english translations for new strings

* fix: settings dropdowns were always english not matter the selected language
2025-06-06 12:45:30 -07:00
Jann Stute
3999d5d39b feat(ui): improved datetime field modal using QDateTimeEdit (#946)
* feat: custom modal making use of QDateTimeEdit

* fix: add back license notice

* refactor: remove unnecessary line

* feat: use date and hour format from settings for date time picker
2025-06-06 12:41:52 -07:00
Jann Stute
14d1c2b618 refactor: split QtDriver into View and Controller to follow MVC model (#935)
* fix: incorrect qt enum accesses

* refactor: clean up main_window.py and reenable ruff checks for it

* refactor: completely reorganise main_window.py

* refactor: factor out regions into separate methods

* refactor: move preview_panel to MainWindow

* refactor: move file menu construction to MainMenuBar

* refactor: move edit menu construction to MainMenuBar

* refactor: move view menu construction to MainMenuBar

* refactor: move tools menu construction to MainMenuBar

* refactor: move macros menu construction to MainMenuBar

* refactor: move help menu construction to MainMenuBar

* refactor: move ui construction code out of QtDriver and into MainWindow

* fix: remove unnecessary imports

* fix: tests were failing
2025-06-05 16:02:03 -07:00
Weblate (bot)
da18bd0dd5 translations: update from Hosted Weblate (#907)
* Translated using Weblate (Filipino)

Currently translated at 100.0% (313 of 313 strings)

Translated using Weblate (Filipino)

Currently translated at 100.0% (313 of 313 strings)

Translated using Weblate (Filipino)

Currently translated at 74.7% (234 of 313 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
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% (313 of 313 strings)

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

* Translated using Weblate (German)

Currently translated at 100.0% (313 of 313 strings)

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

* Translated using Weblate (Russian)

Currently translated at 91.3% (286 of 313 strings)

Translated using Weblate (Russian)

Currently translated at 91.3% (286 of 313 strings)

Translated using Weblate (Russian)

Currently translated at 89.1% (279 of 313 strings)

Translated using Weblate (Russian)

Currently translated at 88.8% (278 of 313 strings)

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

* Translated using Weblate (Japanese)

Currently translated at 100.0% (313 of 313 strings)

Translated using Weblate (Japanese)

Currently translated at 63.2% (198 of 313 strings)

Translated using Weblate (Japanese)

Currently translated at 52.3% (164 of 313 strings)

Update translation files

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

Translated using Weblate (Japanese)

Currently translated at 52.0% (163 of 313 strings)

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

* Translated using Weblate (Hungarian)

Currently translated at 100.0% (313 of 313 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 92.6% (290 of 313 strings)

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

* Translated using Weblate (Spanish)

Currently translated at 99.3% (311 of 313 strings)

Translated using Weblate (Spanish)

Currently translated at 96.4% (302 of 313 strings)

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

* Translated using Weblate (French)

Currently translated at 100.0% (313 of 313 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 (Chinese (Simplified Han script))

Currently translated at 100.0% (313 of 313 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (312 of 312 strings)

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

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Luoyu <rainluoyu@outlook.com>
Co-authored-by: Luoyu <tkiuvvv2333@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/zh_Hans/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 95.8% (300 of 313 strings)

Translated using Weblate (Toki Pona)

Currently translated at 94.8% (297 of 313 strings)

Translated using Weblate (Toki Pona)

Currently translated at 90.4% (283 of 313 strings)

Translated using Weblate (Toki Pona)

Currently translated at 90.0% (282 of 313 strings)

Translated using Weblate (Toki Pona)

Currently translated at 83.0% (260 of 313 strings)

Translated using Weblate (Toki Pona)

Currently translated at 80.7% (252 of 312 strings)

Translated using Weblate (Toki Pona)

Currently translated at 80.7% (252 of 312 strings)

Translated using Weblate (Toki Pona)

Currently translated at 80.7% (252 of 312 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

* Translated using Weblate (Viossa)

Currently translated at 96.1% (301 of 313 strings)

Translated using Weblate (Viossa)

Currently translated at 96.1% (301 of 313 strings)

Translated using Weblate (Viossa)

Currently translated at 96.1% (301 of 313 strings)

Translated using Weblate (Viossa)

Currently translated at 46.3% (145 of 313 strings)

Translated using Weblate (Viossa)

Currently translated at 45.0% (141 of 313 strings)

Translated using Weblate (Viossa)

Currently translated at 45.1% (141 of 312 strings)

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

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Co-authored-by: searinminecraft <kitakita@disroot.org>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: Liveside <livesi5e@gmail.com>
Co-authored-by: Dott-rus <antonamelin8@gmail.com>
Co-authored-by: Utof <vip.ilyabondar@gmail.com>
Co-authored-by: wany-oh <wany-oh@users.noreply.hosted.weblate.org>
Co-authored-by: Szíjártó Levente Pál <szijartoleventepal@gmail.com>
Co-authored-by: Feather <featherprinceyt@gmail.com>
Co-authored-by: Joan <joancanalscrehuet@gmail.com>
Co-authored-by: Sunny <0t2sruto@pwall.anonaddy.com>
Co-authored-by: Med <45147847+kitsumed@users.noreply.github.com>
Co-authored-by: Luoyu <rainluoyu@outlook.com>
Co-authored-by: Luoyu <tkiuvvv2333@gmail.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Co-authored-by: Nginearing <142851004+Nginearing@users.noreply.github.com>
2025-06-04 02:14:32 -07:00
Travis Abendshien
9b0eb90d06 Merge branch 'Alpha-v9.5.3' 2025-06-04 01:35:25 -07:00
rsazra
e4bb07e852 fix: add parent tags to folders_to_tags macro and start tagging at root folder (#940)
* start tagging from root and add parent tags

* update snapshot to pass test
2025-06-04 01:22:17 -07:00
purpletennisball
e1117bf1a1 fix: creating new tag now refreshes the menu using the current search text (#939) 2025-06-04 00:43:09 -07:00
rsazra
c21e2d657d fix(ui): add selected field on return (#941) 2025-06-04 00:36:27 -07:00
Jann Stute
25fb6883c1 fix: proper error on unterminated quoted string (#936)
* fix: minor warnings

* fix: return ParsingError on unterminated quoted string
2025-06-04 00:33:33 -07:00
Jann Stute
cf6c56c9d2 fix: restore page navigation state (#933)
* refactor: store browsing history for navigation purposes

* refactor: remove page_size from FilterState

* refactor: move on from the term "filter" in favor of "BrowsingState"

* fix: refactors didn't propagate to the tests

* fix: ruff complaints

* fix: remaing refactoring errors

* fix: navigation works again

* fix: also store and restore query
2025-06-04 00:29:07 -07:00
Bee
b0a9d045fd docs: update CHANGELOG.md (#914)
* Update CHANGELOG.md

* Update CHANGELOG.md again

add the more recent changelog parts
2025-06-03 23:32:03 -07:00
Xarvex
1e783a5e3c fix(nix/package): override PySide6 if later version is being used (#917) 2025-05-06 16:42:21 -05:00
Travis Abendshien
6517ef560f Merge branch 'main' into Alpha-v9.5.3 2025-05-05 14:25:38 -07:00
Jann Stute
d061e2e866 fix: tests were overwriting the settings.toml (#928)
* fix: tests were overwriting the settings.toml

* fix(GlobalSettings): add default value for _loaded_from

* fix: a case in read_settings didn't set loaded_from correctly

* fix(GlobalSettings): proper serialisation
2025-05-05 14:04:48 -07:00
Travis Abendshien
ee56f3e2bc feat(ui): show stems for extension-less files (#899)
* feat(ui): show stems for extension-less files

* chore: remove unnecessary call and comment

* fix: only access filepath if not none
2025-05-05 13:09:26 -07:00
Xarvex
702ecd4118 chore(pyproject): version bumping/relaxing (#886)
* chore(pyproject): version bumping/relaxing

* fix(pyproject): remove imaging extra for mkdocs-material

* fix: ruff formatting

* fix: mypy

* fix(pyproject): PySide violates SemVer

* chore(pyproject): set Python version, bump pydantic

* fix(ci): up Ruff to consistent version

* fix(pyproject): fixup requires-python

* fix: ruff checks

* chore(pyproject): bump dependencies

* fix(ci): up Ruff to consistent version

* fix(tests): strip out non-reproducible tests
2025-05-05 12:47:57 -07:00
Travis Abendshien
c14734d25e chore: remove unused "dev/null" check 2025-05-04 17:29:07 -07:00
Tony
36f3b45e66 fix(tests): fix tests failing on Windows (#903)
* create and delete tmp directory for tests

* Update tests/conftest.py

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

* Update tests/conftest.py

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

* remove unused import

* cleanup and added type hints

* fix mypy assignment error

* swap gettempdir() with TemporaryDirectory()

* reuse temp library for more tests + cleanup

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2025-05-04 17:23:36 -07:00
VasigaranAndAngel
efb062034d refactor: remove placeholder video, fix type hints in preview_thumb.py (#906)
* type fixes

* remove `stop_file_use()` method and release the file properly.

* remove "placeholder_mp4" from resources.json
2025-05-04 15:13:34 -07:00
Jann Stute
6598630984 feat: datetime fields settings integration (#926)
* refactor: move datetime formatting to global settings

* feat: datetime fields settings integration
2025-05-04 11:49:52 -07:00
Jann Stute
98c4b1c359 feat(ui): temporary modal to edit datetime fields and small refactors (#921)
* refactor: simplify datetime widget creation

* refactor: fix warnings

* feat: text modal to enter date

* chore: change ignore comment to only apply to pyright not mypy
2025-05-04 11:13:58 -07:00
Travis Abendshien
525b382803 chore: remove unused import 2025-05-04 10:50:57 -07:00
JCC1998
f770614c4e feat: add date_format and hour_format settings (#904)
* feat: add date_format and hour_format settings

* fix: fix ruff validation errors

* fix: use ruff format command

* fix: Refactor code and improve some logic

* fix: remove unused import

* Added zero padding setting and implement some comments

* Add test assert property

* fix: Unclutter selector and clarify zero-padding literal

* fix: Use static strings

Co-authored-by: Tony <1414927+zfbx@users.noreply.github.com>

---------

Co-authored-by: Tony <1414927+zfbx@users.noreply.github.com>
2025-05-04 10:46:26 -07:00
Tony
97ee43c875 feat: add ability to invert selection (#909)
* implement selection inversing

* Rename Select Inverse to Invert Selection
2025-04-05 19:59:44 -05:00
Xarvex
a72c2c4ba8 docs(develop): use double quotes for pip
Some shells, notably Zsh, try parsing the brackets.

Fixes: #910
2025-04-05 19:11:46 -05:00
Xarvex
29d3d64a32 nix(nix/package): ignore incompatible tests 2025-03-31 23:53:46 -05:00
Travis Abendshien
b0bfc10a67 docs: update roadmap 2025-03-31 20:36:08 -07:00
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
xarvex
3e2fb1282c feat(flake): expose formatter
Fixes: #433
2024-09-03 00:12:03 -05:00
xarvex
3e950a0cbe fix(direnv)!: gitignore .envrc
A common workflow is to have a local .envrc, allow contributors to do
so. Still provide the recommended Nix-based setup, for those who wish to
use it.
That file can then be copied to or symlinked to `.envrc`.
2024-09-01 21:28:58 -05:00
xarvex
38626ac690 chore(direnv): update .envrc
- Cleanup direnv-root file
- Set filetype for vi-like editors
- Format and lint according to shellcheck and shfmt
2024-08-31 13:16:30 -05:00
xarvex
bc38e568fa feat(flake): remove impurity, update nix-direnv
Use path as an input that can be overriden automatically when direnv is
in use.
nix-direnv version present in .envrc has been updated, using watch_file on the flake is already handled.
2024-08-30 14:44:22 -05:00
Florian Zier
4b35df0924 fix(flake): GPU hardware acceleration
* Enable hardware acceleration for Nix devenv

Section "nativeBuildInputs" needs the dependency "qt6.full" to provide hardware acceleration in QT.
Library "wayland" is needed to create a proper OpenGL context under Wayland as well.

Provide a check for open AMD driver and use it for VDPAU video hardware acceleration.

Move "wrapQtAppsHook" to it's correct place under "nativeBuildInputs".

* Add xorg.libXrandr for hardware accelerated video playback.

Using libva and openssl libraries eliminates the need for a dependency on the qt6.full library.
2024-08-29 23:49:00 -05:00
xarvex
750cbf0f9c fix(flake): add missing media dependencies
Fixes: #417

Did not opt for setting VDPAU_DRIVER, should be done on user side
See: https://wiki.archlinux.org/title/Hardware_video_acceleration#Configuring_VA-API
2024-08-29 19:38:23 -05:00
xarvex
cb4798b715 feat(flake): complete revamp with devenv/direnv
Not perfect, and mostly a port of the previous edition. Hours have
already been sunk into this, and need to get this out for consumption,
and for ironing out.
For more information see: https://devenv.sh

NOTE: impure is used only because of the devenv-managed state, do not be
alarmed!
2024-08-24 22:57:00 -05:00
Xarvex
e1b1ef9888 Merge pull request #229 from seakrueger/flake-setup-venv
ci(flake): create and activate venv via Nix Flake
2024-08-22 20:09:24 -05:00
UnusualEgg
3fcf6022b9 refactor: combine open launch args (#364)
Combine the `--open` and `-o` launch arguments into a single argument option.
2024-08-22 18:02:03 -07:00
Travis Abendshien
ec960f2372 (fix): use .get() to avoid KeyError (#347) 2024-08-11 19:01:08 -07:00
Sam
ce87b11fbd Fix #2 for Add Library Tags panel (#328)
The "Add Tags" panel displays all tags when no search has been performed. Modifies the "Add Library Tags panel" to be the same.
2024-07-22 06:59:43 -07:00
Travis Abendshien
c79086f715 Fix collation data not clearing on library close 2024-07-04 17:40:19 -07:00
Travis Abendshien
9ce07bd369 Bump version to 9.3.2 2024-07-03 17:41:21 -07:00
Theasacraft
33ee27a84f Fix small bug (#306) 2024-07-03 17:02:59 -07:00
Travis Abendshien
1204d2b7b5 Fix search ignoring case of extension list 2024-06-21 11:28:12 -07:00
Sean Krueger
15ee13c7b4 Bump Qt6 version to 6.7.1 2024-06-17 13:05:17 -05:00
Xarvex
49ad8117ef fix(flake): resolve mypy access to libraries 2024-06-17 13:05:17 -05:00
Sean Krueger
4f193613de Update Contributing documentation for dev on Nix
Moves the previous updated blurb from the README to the new CONTRIBUTING
file. Also reworks some wording to link to the Flake nix wiki page for
nix users who haven't enable flakes yet.
2024-06-17 13:05:17 -05:00
Sean Krueger
31038711f2 Install ruff via nixpkgs 2024-06-17 13:05:17 -05:00
Sean Krueger
cc827108ef Add xcb as fallback when wayland fails to load
Mostly as a fallback for xserver.
2024-06-17 13:05:17 -05:00
Sean Krueger
9fec4822c1 Setup and activate virtual environment via flake
When using the nix flake to generate a development shell, the python
virtual environment will now automatically be created and dependecies
from both requirements.txt and requirements-dev.txt will be installed.
This removes the need for using the setup script after entering the
dev shell. Exec bash must be the last thing called, as any other
commands past it will not get executed by the shell hook. Also removes
some duplicate dependencies that I found.
2024-06-17 13:05:17 -05:00
Travis Abendshien
501ab1f977 Update issue templates to ask for title 2024-06-16 19:54:56 -07:00
Theasacraft
b3c01e180a Update to pyside6 version 6.7.1 (#223)
* Update to pyside6.7.1

* Fix Ruff

* Fix MyPy

* Update mypy job to also use PySide6 6.7.1

* Remove unused imports

* Add Description to class

* Ruff format

* Fix Warning in pagination.py

* Probably fix Pyside app test

* Rename CustomQPushButton to QPushButtonWrapper
also renamed custom_qbutton.py to qbutton_wrapper.py
2024-06-16 16:53:38 -07:00
Jiri
4c6ebec529 refactoring: centralize field IDs (#157)
* use enum with named fields instead of ad-hoc numbers

* move tag ids into constants file
2024-06-16 14:24:48 -07:00
Travis Abendshien
5c25666e67 Fix TypeError in folders_to_tags.py
- Additionally use proper comparison syntax
2024-06-15 13:19:28 -07:00
Lennart S
fae65bd9e9 docs: Fixed broken markdown doc link (#291) 2024-06-15 13:10:55 -07:00
Jiri
8e065ca8ac create testing library files ad-hoc (#292) 2024-06-14 10:11:00 -07:00
Travis Abendshien
82946cb0b8 Update CONTRIBUTING.md with PyTest info 2024-06-13 15:39:13 -07:00
Jiri
aa2925cde0 add pytest to CI pipeline (#286) 2024-06-13 15:29:22 -07:00
Travis Abendshien
5bc80a043f Update tagstudio.spec 2024-06-13 09:17:14 -07:00
Travis Abendshien
65d88b9987 Refactor video_player.py (Fix #270) (#274)
* Refactor video_player.py

- Move icons files to qt/images folder, some being renamed
- Reduce icon loading to single initial import
- Tweak icon dimensions and animation timings
- Remove unnecessary commented code
- Remove unused/duplicate imports
- Add license info to file

* Add basic ResourceManager, use in video_player.py

* Revert tagstudio.spec changes

* Change tuple usage to dicts

* Move ResourceManager initialization steps

* Fix errant list notation
2024-06-12 23:20:17 -07:00
Travis Abendshien
37ff35fcf6 Set mouse event transparency on ItemThumbs (#279) 2024-06-12 02:00:16 -07:00
Xarvex
9b13e338bb Use bug report and feature request forms for issues (#277)
* Initial bug report

* Images can be attached in description

* Enclose label in quotes

* Wikis are no longer being used

* Use footnote to clarify what is up-to-date

* Footnotes not supported in checklist

* Initial feature request form

* Fixup grammar/wording
2024-06-11 19:32:18 -07:00
Travis Abendshien
a47b0adb6e Update icon.ico 2024-06-11 17:29:30 -07:00
Travis Abendshien
9f39bf6fdc Update README: Correct version number in figure 2024-06-11 01:27:21 -07:00
Andrew Arneson
e375166bfe Raise error if video file has 0 frames or is in valid. (#275)
video.get(cv2.CAP_PROP_FRAME_COUNT) returns 0 or -1
2024-06-10 18:10:57 -07:00
Sean Krueger
7054ffd227 Separately pin QT nixpkg version (#244)
* Add missing libraries for video player

* Pin qt6 package version to 6.6.3

Currently, this succesfully launches the program. Pinning qt seperatly
allows the rest of unstable nixpkgs to be updated even after the qt
package version has been bumped. This fixes vim failing to launch in the
nix shell because of a bad gcc version. Bumping the package version to
qt6.7.1 also will require bumping PySide to 6.7.1, otherwise it will
fail to find qt. Qt 6.7.1 nixpkg commit is 47da0aee5616a063015f10ea593688646f2377e4

* fixup: Pin Qtcreator also

QtCreator was still against nixpkgs not the specific qt variant.
2024-06-10 14:52:13 -07:00
Travis Abendshien
6d283d1f2d Add CONTRIBUTING.md, update README 2024-06-10 02:30:16 -07:00
Travis Abendshien
a0baf015db Bump version to v9.3.1 Pre-Release 2024-06-08 15:34:29 -07:00
Travis Abendshien
58be4cdb4b Bump version to v9.3.0 2024-06-08 15:21:37 -07:00
Travis Abendshien
08761d5f8a Add Landing Page When No Library Is Opened (#258)
* Add landing page when no library is open

- Add landing page when no library is open
- Add linear_gradient method
- Reformat main_window.py with spaces instead of tabs because apparently it wasn't formatted already?

* Add color_overlay methods, ClickableLabel widget

- Add color_overlay helper methods
- Add clickable_label widget
- Add docstrings to landing.py methods
- Add logo easter egg
- Refactor landing.py content

* Fix redefinition

* Fix macOS shortcut text
2024-06-08 15:18:40 -07:00
Travis Abendshien
6a680ad3d1 Increase shown tag limit from 29 to 100 (#227) 2024-06-08 12:40:37 -07:00
PencilVoid
b5ec3598e1 Add "Clear Selection" button (#259)
* Add "Clear Selection" button

* Change clear select keybind to Esc
2024-06-08 10:51:39 -07:00
Theasacraft
926dfffebe Add option to use a allowed extensions instead of ignored extensions (#251)
* Add option to use a whitelist instead of a blacklist

* maybe fix mypy?

* Fix Mypy and rename ignored_extensions

* This should fix mypy

* Update checkbox text

* Update window title

* shorten if statment and update text

* update variable names

* Fix Mypy

* hopefully fix mypy

* Fix mypy

* deprecate ignored_extensions

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

* polishing

* polishing

* Fix mypy

* finishing touches

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

* Fix boolean loading

* UI/UX + ext list loading tweaks

- Change extension list mode setting from Checkbox to ComboBox to help better convey its purpose
- Change and simplify wording
- Add type hints to extension variables and change loading to use `get()` with default values
- Sanitize older extension lists that don't use extensions with a leading "."
- Misc. code organization and docstrings

---------

Co-authored-by: Jiri <yedpodtrzitko@users.noreply.github.com>
Co-authored-by: Travis Abendshien <lvnvtravis@gmail.com>
2024-06-07 18:02:28 -07:00
Xarvex
461906c349 Workflows: attempt fix for mismatched hashes in pip and bump MacOS version (#255)
* Attempted fix at mismatched hashes
Due to the seemingly random nature of the bug, this cannot be tested

* macos-11 runner has been deprecated, bump to 12
2024-06-06 12:51:08 -07:00
Travis Abendshien
2dc5197fbd Add upcoming feature documentation 2024-06-04 15:59:39 -07:00
PossiblePanda
11f0c7f9b8 Added various file formats to constants.py (#231)
* Added various file formats to constants.py

* Update tagstudio/src/core/constants.py

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

* Update tagstudio/src/core/constants.py

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

---------

Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
2024-06-04 14:13:57 -07:00
Andrew Arneson
fb445e6ab0 Fix Default Ignored File Extension (#245)
Add item delegate for Ignored File Extension to add leading `.` if left off extension
2024-06-03 21:47:56 -07:00
Giochino Danilo Ramos Silva
6e96a0ff61 Multi mode search system (#232)
* multi search mode system

A way to change the search from requiring all tags to and of the tags

* better wording

* Update start_win.bat

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

* Fix home_ui.py using PySide6 instead of PyQt5

* Refresh search on mode change

* Search mode selections naming fix

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

* converted SearchMode from constants to enums
2024-06-03 15:37:56 -07:00
Travis Abendshien
c75aff4db3 Rename "Subtags" to "Parent Tags"
Mentioned change in #211
2024-06-03 13:30:15 -07:00
Travis Abendshien
84a4b2f0cf Merge pull request #240 from Loran425/bugfix/cancel_library_dialog
Bugfix Open Library Dialog
2024-06-02 22:54:51 -07:00
Andrew Arneson
10b90dcc74 Bugfix for recent library re-creating a library at the last library location. (#238)
* Prevent Automatic opening of a Library if the ".TagStudio" folder has been deleted.
If the library no longer has a `.TagStudio` folder clear the Last_Library value

* Add disabling recent libraries that are missing or have missing `.TagStudio` folders

* Fix bug where this would crash if an empty library was passed

* Grabbed the wrong color
2024-06-02 22:53:42 -07:00
Andrew Arneson
2d89df620e Fix Open Library Dialog
Resolve issues where the open library dialog will try to open `.` if no path is returned from the dialog
2024-06-02 22:43:10 -06:00
Travis Abendshien
0646508c24 Fix Raw Image Handling and Improve Text File Encoding Compatibility (#233)
* Fix text and RAW image handling

- Fix RAW images not being loaded correctly in the preview panel
- Fix trying to read size data from null images
- Refactor `os.stat` to `<Path object>.stat()`
- Remove unnecessary upper/lower conversions
- Improve encoding compatibility beyond UTF-8 when reading text files
- Code cleanup

* Use chardet for character encoding detection
2024-06-02 20:18:40 -07:00
Travis Abendshien
0137ed5be8 Update README.md 2024-06-01 21:09:55 -07:00
Theasacraft
779a251c09 Fix small bugs (#228) 2024-05-31 16:45:13 -07:00
Travis Abendshien
868b553670 Duplicate Entry Handling (Fixes #179) (#204)
* Reapply "Add duplicate entry handling (Fix #179)"

This reverts commit 66ec0913b6.

* Reapply "Fix create library + type checks"

This reverts commit 57e27bb51f.

* Type and hint changes

* Remove object cast

* MyPy wrestling

* Remove type: ignore, change __eq__ cast

- Remove `type: ignore` comments from `Entry`'s `__eq__` method
- Change the cast in this method from `__value = cast(Self, object)` to `__value = cast(Self, __value)`

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

* Fix formatting + mypy

---------

Co-authored-by: Jiri <yedpodtrzitko@users.noreply.github.com>
2024-05-29 15:47:37 -07:00
DrRetro
9f630fe315 Video Player (#149)
* Basic Video Player

* Fixes and Comments

* Fixed Bug Where Video's Audio did not stop when switching to a Image.

* Redo on VideoPlayer. Now with rounded corners.

* Fixed size not being correct when first starting video player.

* Ruff Check Fix

* Fixed Sizing Issue, and added Autoplay option in right click menu.

* Autoplay Toggle and Fixed Issue with video not stoping after closing library.

* Ruff Format

* Suggested Changes Done

* Commented out useless code that cause first warning.

* Fixed Album Art Error

* Might have found solution to Autoplay Inconsistency

* Ruff Format

* Finally Fixed Autoplay Inconsistency

* Fixed Merge Conficts

* Requested Changes and Ruff Format

* Test for new check

* Fix for PySide App Test

* More typing fixes and a few other changes.

* Ruff Format

* MyPy Fix

* MyPy Fix

* Ruff Format

* MyPy Fix

* MyPy and Ruff Fix

* Code Clean-Up and Requests completed.

* Conflict Fixes

* MyPy Fix

* Confict Fix

It appears one of the commits from main fixed the autoplay issue.
2024-05-29 13:58:09 -07:00
Icosahunter
6798ffd0a7 Replace use of os.path with pathlib (#156)
* Replace usage of os.path with usage of pathlib.Path in ts_cli.py

* Replace use of os.path with pathlib in Library.py

* Replace use of os.path with pathlib in ts_core.py

* resolve entry path/filename on creation/update

* Fix errors and bugs related to move from os.path to pathlib.

* Remove most uses of '.resolve()' as it didn't do what I thought it did

* Fix filtering in refresh directories to not need to cast to string.

* Some work on ts_qt, thumbnails don't load...

* Fixed the thumbnail issue, things seem to be working.

* Fix some bugs

* Replace some isfile with is_file ts_cli.py

* Update tagstudio/src/core/library.py

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

* Update library.py

* Update library.py

* Update library.py

* Update ts_cli.py

* Update library.py

* Update ts_qt

* Fix path display in delete unlinked entries modal

* Ruff formatting

* Builds and opens/creates library now

* Fix errors

* Fix ruff and mypy issues (hopefully)

* Fixed some things, broke some things

* Fixed the thumbnails not working

* Fix some new os.path instances in qt files

* Fix MyPy issues

* Fix ruff and mypy issues

* Fix some issues

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

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

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

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

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

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

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

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

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

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

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

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

* Fix refresh_dupe_files issue

* Ruff format

* Tweak filepaths

- Suffix comparisons are now case-insensitive
- Restore original thumbnail extension label behavior
- Fix preview panel trying to read file size from missing files

---------

Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com>
Co-authored-by: Travis Abendshien <lvnvtravis@gmail.com>
2024-05-26 16:17:05 -07:00
Jiri
2e8678414b feat: add select all hotkey (#217)
* add select all hotkey

* add item to selected
2024-05-26 01:00:56 -07:00
Gawi
e1cd46d010 Wiki/Docs Updates (#194)
* split file + link fix

* Cleanup & Minimum Fill

* polish & link

* Update doc/Tag.md

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

---------
2024-05-25 13:49:12 -07:00
Travis Abendshien
9879697c95 Bump version to v9.2.2 2024-05-23 00:42:00 -07:00
380 changed files with 37380 additions and 29398 deletions

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

65
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
---
name: Bug Report
description: File a bug or issue report.
title: '[Bug]: '
labels: ['Type: Bug']
body:
- type: markdown
attributes:
value: |
*Please add an appropriate title for this issue.*
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.
[^1]: This can mean latest release, pre-release, or git commit.
- type: checkboxes
attributes:
label: Checklist
description: Make sure you've checked (and actually did) all of the below.
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/docs/index.md).
required: true
- label: I have searched existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
required: true
- type: input
attributes:
label: TagStudio Version
placeholder: Alpha 9.1.0
validations:
required: true
- type: input
attributes:
label: Operating System & Version
placeholder: TagOS 3.14
validations:
required: true
- type: textarea
attributes:
label: Description
description: Clear and concise description of the problem. Attach screenshots if needed, and include any errors you see.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: Clear and concise description of what you think should happen.
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: Minimal steps needed for the problem to occur.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
attributes:
label: Logs
description: Provide any logs, if applicable.

View File

@@ -0,0 +1,41 @@
---
name: Feature Request
description: Suggest a new feature.
title: '[Feature Request]: '
labels: ['Type: Enhancement']
body:
- type: markdown
attributes:
value: |
*Please add an appropriate title for this feature request.*
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.
[^1]: This can mean latest release, pre-release, or git commit.
- type: checkboxes
attributes:
label: Checklist
description: Make sure you've checked (and actually did) all of the below.
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/docs/index.md).
required: true
- label: I have searched existing [issues](https://github.com/TagStudioDev/TagStudio/issues).
required: true
- type: textarea
attributes:
label: Description
description: Clear and concise description of the problem or missing capability. Attach screenshots if needed, and explain your own use case.
validations:
required: true
- type: textarea
attributes:
label: Solution
description: Clear and concise description of what you think should happen.
- type: textarea
attributes:
label: Alternatives
description: Any considered alternative solutions or workarounds. If undesirable, why?

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,50 +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
- name: Install dependencies
run: |
pip install -r 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,35 +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
- name: Install dependencies
run: |
python -m pip install --upgrade pip
# pyside 6.6.3 has some issue in their .pyi files
pip install PySide6==6.6.2
pip install -r requirements.txt
pip install mypy==1.10.0
mkdir tagstudio/.mypy_cache
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: pip
- uses: tsuyoshicho/action-mypy@v4
- name: Install Python dependencies
run: |
python -m pip install --upgrade uv
uv pip install --system .[mypy]
- name: Execute MyPy
uses: tsuyoshicho/action-mypy@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
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

74
.github/workflows/pytest.yaml vendored Normal file
View File

@@ -0,0 +1,74 @@
---
name: pytest
on: [push, pull_request]
jobs:
pytest:
name: Run pytest
runs-on: ubuntu-24.04
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 .[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,14 +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'
- 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
@@ -34,24 +48,41 @@ jobs:
macos:
strategy:
matrix:
os-version: ['11', '14']
os-version: ['13', '14']
include:
- os-version: '11'
- os-version: '13'
arch: x86_64
- os-version: '14'
arch: aarch64
runs-on: macos-${{ matrix.os-version }}
env:
# 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'
- 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
@@ -69,29 +100,51 @@ jobs:
build-flag: --portable
suffix: _portable
file-end: .exe
runs-on: windows-2019
runs-on: windows-2022
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'
- 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: astral-sh/ruff-action@v3
with:
version: 0.4.2
args: 'format --check'
version: 0.11.0
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: astral-sh/ruff-action@v3
with:
version: 0.11.8
args: check

36
.gitignore vendored
View File

@@ -88,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.
@@ -140,6 +138,9 @@ venv.bak/
# Rope project settings
.ropeproject
# macoS
*.DS_Store
# mkdocs documentation
/site
@@ -162,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
@@ -174,6 +175,10 @@ poetry.toml
# LSP config files
pyrightconfig.json
# Syncthing
.stfolder/
.stignore
### Qt ###
# C++ objects and libs
*.slo
@@ -231,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/
@@ -247,8 +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

@@ -5,6 +5,805 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [9.5.2] - 2025-03-31
### Added
#### Search
- feat(ui): add setting to not display full filepaths by [@HermanKassler](https://github.com/HermanKassler) in [#841](https://github.com/TagStudioDev/TagStudio/pull/841)
- feat: add filename and path sorting by [@Computerdores](https://github.com/Computerdores) in [#842](https://github.com/TagStudioDev/TagStudio/pull/842)
#### Settings
- feat: new settings menu + settings backend by [@Computerdores](https://github.com/Computerdores) in [#859](https://github.com/TagStudioDev/TagStudio/pull/859)
#### UI
- feat(ui): merge media controls by [@csponge](https://github.com/csponge) in [#805](https://github.com/TagStudioDev/TagStudio/pull/805)
- fix: Remove border from video preview top and left by [@zfbx](https://github.com/zfbx) in [#900](https://github.com/TagStudioDev/TagStudio/pull/900)
- feat(ui): add more default icons and file type equivalencies by [@CyanVoxel](https://github.com/CyanVoxel) in [#882](https://github.com/TagStudioDev/TagStudio/pull/882)
- ui: recent libraries list improvements by [@CyanVoxel](https://github.com/CyanVoxel) in [#881](https://github.com/TagStudioDev/TagStudio/pull/881)
#### Misc
- feat: provide a .desktop file by [@xarvex](https://github.com/xarvex) in [#870](https://github.com/TagStudioDev/TagStudio/pull/870)
### Fixed
- fix: catch NotImplementedError for Float16 JPEG-XL files by [@CyanVoxel](https://github.com/CyanVoxel) in [#849](https://github.com/TagStudioDev/TagStudio/pull/849)
- fix(nix/package): account for GTK platform by [@xarvex](https://github.com/xarvex) in [#868](https://github.com/TagStudioDev/TagStudio/pull/868)
- fix: do not set palette for Linux-like systems that offer theming by [@xarvex](https://github.com/xarvex) in [#869](https://github.com/TagStudioDev/TagStudio/pull/869)
- fix(flake): remove pinned input, only consume in Nix shell by [@xarvex](https://github.com/xarvex) in [#872](https://github.com/TagStudioDev/TagStudio/pull/872)
- fix: stop ffmpeg cmd windows, refactor ffmpeg_checker by [@CyanVoxel](https://github.com/CyanVoxel) in [#855](https://github.com/TagStudioDev/TagStudio/pull/855)
- fix: hide mnemonics on macOS by [@CyanVoxel](https://github.com/CyanVoxel) in [#856](https://github.com/TagStudioDev/TagStudio/pull/856)
- fix: use UNION instead of UNION ALL by [@CyanVoxel](https://github.com/CyanVoxel) in [#877](https://github.com/TagStudioDev/TagStudio/pull/877)
- fix: remove unescaped ampersand from "about.description" by [@CyanVoxel](https://github.com/CyanVoxel) in [#885](https://github.com/TagStudioDev/TagStudio/pull/885)
- fix(ui): display 0 frame webp files in preview panel by [@CyanVoxel](https://github.com/CyanVoxel) in [64dc88a](https://github.com/TagStudioDev/TagStudio/commit/64dc88afa90bb11f3c9b74a2522f947370ce21db)
- fix: close pdf file object in thumb renderer by [@Computerdores](https://github.com/Computerdores) in [#893](https://github.com/TagStudioDev/TagStudio/pull/893)
- perf: improve responsiveness of GIF entries by [@Computerdores](https://github.com/Computerdores) in [#894](https://github.com/TagStudioDev/TagStudio/pull/894)
- fix(ui): seamlessly loop videos by [@CyanVoxel](https://github.com/CyanVoxel) in [#902](https://github.com/TagStudioDev/TagStudio/pull/902)
### Internal Changes
- refactor!: change layout; import and build change by [@xarvex](https://github.com/xarvex) and [@CyanVoxel](https://github.com/CyanVoxel) in [#844](https://github.com/TagStudioDev/TagStudio/pull/844)
- fix: log all problems in translation test by [@Computerdores](https://github.com/Computerdores) in [#839](https://github.com/TagStudioDev/TagStudio/pull/839)
- refactor: split translation keys for about screen by [@CyanVoxel](https://github.com/CyanVoxel) in [#845](https://github.com/TagStudioDev/TagStudio/pull/845)
- feat(ci): development tooling refresh and split documentation by [@xarvex](https://github.com/xarvex) in [#867](https://github.com/TagStudioDev/TagStudio/pull/867)
- refactor: type hints and improvements in file_opener.py by [@VasigaranAndAngel](https://github.com/VasigaranAndAngel) in [#876](https://github.com/TagStudioDev/TagStudio/pull/876)
- build: update spec file to use proper pathex and datas paths by [@Leonard2](https://github.com/Leonard2) in [#895](https://github.com/TagStudioDev/TagStudio/pull/895)
- refactor: fix various missing and broken type hints@VasigaranAndAngel in [#901](https://github.com/TagStudioDev/TagStudio/pull/901)
- refactor: fix type hints and overrides in flowlayout.py by [@VasigaranAndAngel](https://github.com/VasigaranAndAngel) in [#880](https://github.com/TagStudioDev/TagStudio/pull/880)
### Documentation
- docs: fix typos and grammar by [@Gawidev](https://github.com/Gawidev) in [#879](https://github.com/TagStudioDev/TagStudio/pull/879)
- docs: update `ThumbRenderer` source by [@emmanuel-ferdman](https://github.com/emmanuel-ferdman) in [#896](https://github.com/TagStudioDev/TagStudio/pull/896)
### Translations
- Added Japanese (50%)
- [@needledetector](https://github.com/needledetector)
- Updated Turkish (93%)
- [@Nyghl](https://github.com/Nyghl)
- Updated Filipino (57%)
- [@searinminecraft](https://github.com/searinminecraft)
- Updated Tamil (92%)
- [@TamilNeram](https://github.com/TamilNeram)
- Updated Portuguese (Brazil) (83%)
- [@viniciushelder](https://github.com/viniciushelder)
- Updated German (95%)
- [@DontBlameMe99](https://github.com/DontBlameMe99), [@Computerdores](https://github.com/Computerdores)
- Updated Russian (85%)
- werdi, [@Dott-rus](https://github.com/Dott-rus)
- Updated Hungarian (100%)
- Szíjártó Levente Pál
- Updated Spanish (96%)
- Joan, [@Nginearing](https://github.com/Nginearing)
- Updated French (100%)
- [@kitsumed](https://github.com/kitsumed)
- Updated Toki Pona (80%)
- [@Math-Bee](https://github.com/Math-Bee)
## [9.5.1] - 2025-03-06
### Fixed
- Fixed translations crashing the program and preventing it from being reopened ([#827](https://github.com/TagStudioDev/TagStudio/issues/827))
- fix: restore `translate_formatted()` method as `format()` by [@CyanVoxel](https://github.com/CyanVoxel) in [#830](https://github.com/TagStudioDev/TagStudio/pull/830)
- tests: add tests for translations by [@Computerdores](https://github.com/Computerdores) in [#833](https://github.com/TagStudioDev/TagStudio/pull/833)
- fix(translations): fix invalid placeholders by [@CyanVoxel](https://github.com/CyanVoxel) in [#835](https://github.com/TagStudioDev/TagStudio/pull/835)
- Removed empty parentheses from the "About" screen title
- fix: separate about screen title from translations by [@CyanVoxel](https://github.com/CyanVoxel) in [#836](https://github.com/TagStudioDev/TagStudio/pull/836)
### Translations
- Updated French (99%)
- [@alessdangelo](https://github.com/alessdangelo), [@Bamowen](https://github.com/Bamowen), [@kitsumed](https://github.com/kitsumed)
- Updated German (98%)
- [@Thesacraft](https://github.com/Thesacraft)
- Updated Portuguese (Brazil) (88%)
- [@viniciushelder](https://github.com/viniciushelder)
- Updated Russian (73%)
- werdei
- Updated Spanish (95%)
- [@JCC1998](https://github.com/JCC1998)
### Documentation
- docs: fix category typo by [@salem404](https://github.com/salem404) in [#834](https://github.com/TagStudioDev/TagStudio/pull/834)
## [9.5.0] - 2025-03-03
### Added
#### Overhauled Search Engine
##### Boolean Operators
- feat: implement query language by [@Computerdores](https://github.com/Computerdores) in [#606](https://github.com/TagStudioDev/TagStudio/pull/606)
- feat: optimize AND queries by [@Computerdores](https://github.com/Computerdores) in [#679](https://github.com/TagStudioDev/TagStudio/pull/679)
##### Filetype, Mediatype, and Glob Path + Smartcase Searches
- fix: remove wildcard requirement for tags by [@Tyrannicodin](https://github.com/Tyrannicodin) in [#481](https://github.com/TagStudioDev/TagStudio/pull/481)
- feat: add filetype and mediatype searches by [@python357-1](https://github.com/python357-1) in [#575](https://github.com/TagStudioDev/TagStudio/pull/575)
- feat: make path search use globs by [@python357-1](https://github.com/python357-1) in [#582](https://github.com/TagStudioDev/TagStudio/pull/582)
- feat: implement search equivalence of "jpg" and "jpeg" filetypes by [@Computerdores](https://github.com/Computerdores) in [#649](https://github.com/TagStudioDev/TagStudio/pull/649)
- feat: add smartcase and globless path searches by [@CyanVoxel](https://github.com/CyanVoxel) in [#743](https://github.com/TagStudioDev/TagStudio/pull/743)
##### Sortable Results
- feat: sort by "date added" in library by [@Computerdores](https://github.com/Computerdores) in [#674](https://github.com/TagStudioDev/TagStudio/pull/674)
##### Autocomplete
- feat: add autocomplete for search engine by [@python357-1](https://github.com/python357-1) in [#586](https://github.com/TagStudioDev/TagStudio/pull/586)
#### Replaced "Tag Fields" with Tag Categories
Instead of tags needing to be added to a tag field type such as "Meta Tags", "Content Tags", or just the "Tags" field, tags are now added directly to file entries with no intermediary step. While tag field types offered a way to further organize tags, it was cumbersome, inflexible, and simply not fully fleshed out. Tag Categories offer all of the previous (intentional) functionality while greatly increasing the ease of use and customization.
- feat!: tag categories by [@CyanVoxel](https://github.com/CyanVoxel) in [#655](https://github.com/TagStudioDev/TagStudio/pull/655)
[![Screenshot 2025-01-04 at 04 23 43](https://private-user-images.githubusercontent.com/46939827/400138597-0b92eca5-db8f-4e3e-954b-1b4f3795f073.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MDAxMzg1OTctMGI5MmVjYTUtZGI4Zi00ZTNlLTk1NGItMWI0ZjM3OTVmMDczLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWY4ZWEzOWRkZjkwOGZmYjZmZDUzMjU1MjJhNDNkNzYzZmM4YjZkMTUyNWIzMjNhMGY1NWNhYmU4ODNiNzlhMzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.cE_WO9AHsigusAbtaQV0QtN4FjYJz0lyHLLwFFDBO-0)](https://private-user-images.githubusercontent.com/46939827/400138597-0b92eca5-db8f-4e3e-954b-1b4f3795f073.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MDAxMzg1OTctMGI5MmVjYTUtZGI4Zi00ZTNlLTk1NGItMWI0ZjM3OTVmMDczLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWY4ZWEzOWRkZjkwOGZmYjZmZDUzMjU1MjJhNDNkNzYzZmM4YjZkMTUyNWIzMjNhMGY1NWNhYmU4ODNiNzlhMzYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.cE_WO9AHsigusAbtaQV0QtN4FjYJz0lyHLLwFFDBO-0)
#### Thumbnails and File Previews
##### New Thumbnail Support
- feat: add svg thumbnail support (port [#442](https://github.com/TagStudioDev/TagStudio/pull/442)) by [@Tyrannicodin](https://github.com/Tyrannicodin) and [@CyanVoxel](https://github.com/CyanVoxel) in [#540](https://github.com/TagStudioDev/TagStudio/pull/540)
- feat: add pdf thumbnail support (port [#378](https://github.com/TagStudioDev/TagStudio/pull/378)) by [@Heiholf](https://github.com/Heiholf) and [@CyanVoxel](https://github.com/CyanVoxel) in [#543](https://github.com/TagStudioDev/TagStudio/pull/543)
- feat: add ePub thumbnail support (port [#387](https://github.com/TagStudioDev/TagStudio/pull/387)) by [@Heiholf](https://github.com/Heiholf) and [@CyanVoxel](https://github.com/CyanVoxel) in [#539](https://github.com/TagStudioDev/TagStudio/pull/539)
- feat: add OpenDocument thumbnail support (port [#366](https://github.com/TagStudioDev/TagStudio/pull/366)) by [@Joshua-Beatty](https://github.com/Joshua-Beatty) and [@CyanVoxel](https://github.com/CyanVoxel) in [#545](https://github.com/TagStudioDev/TagStudio/pull/545)
- feat: add JXL thumbnail and animated APNG + WEBP support (port [#344](https://github.com/TagStudioDev/TagStudio/pull/344) and partially port [#357](https://github.com/TagStudioDev/TagStudio/pull/357)) by [@BPplays](https://github.com/BPplays) and [@CyanVoxel](https://github.com/CyanVoxel) in [#549](https://github.com/TagStudioDev/TagStudio/pull/549)
- fix: catch ImportError for pillow_jxl module by [@CyanVoxel](https://github.com/CyanVoxel) in [a2f9685](https://github.com/TagStudioDev/TagStudio/commit/a2f9685bc0d744ea6f5334c6d2926aad3f6d375a)
##### Audio Playback
- feat: audio playback by [@csponge](https://github.com/csponge) in [#576](https://github.com/TagStudioDev/TagStudio/pull/576)
- feat(ui): add audio volume slider by [@SkeleyM](https://github.com/SkeleyM) in [#691](https://github.com/TagStudioDev/TagStudio/pull/691)
##### Thumbnail Caching
- feat(ui): add thumbnail caching by [@CyanVoxel](https://github.com/CyanVoxel) in [#694](https://github.com/TagStudioDev/TagStudio/pull/694)
#### Tags
##### Delete Tags _(Finally!)_
- feat: remove and create tags from tag database panel by [@DandyDev01](https://github.com/DandyDev01) in [#569](https://github.com/TagStudioDev/TagStudio/pull/569)
##### Custom User-Created Tag Colors
Create your own custom tag colors via the new Tag Color Manager! Tag colors are assigned a namespace (group) and include a name, primary color, and optional secondary color. By default the secondary color is used for the tag text color, but this can also be toggled to apply to the border color as well!
- feat(ui)!: user-created tag colors@CyanVoxel in [#801](https://github.com/TagStudioDev/TagStudio/pull/801)
[![](https://private-user-images.githubusercontent.com/46939827/413668576-b591f1fe-1c44-4d82-b6e5-d166590aeab1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg1NzYtYjU5MWYxZmUtMWM0NC00ZDgyLWI2ZTUtZDE2NjU5MGFlYWIxLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjM4NzczYTFhYmFhNjgwMWJlYjgwNTkzYjA3ZWFlNTkwNzBiYTlhNTAzM2Y0MWM1MWQ0MzY1YmEyNmE4NjAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.EOEVWLDMx5CT-Gg5UhBmdMIYT49IZPKrrA9VL7N-pBQ)](https://private-user-images.githubusercontent.com/46939827/413668576-b591f1fe-1c44-4d82-b6e5-d166590aeab1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg1NzYtYjU5MWYxZmUtMWM0NC00ZDgyLWI2ZTUtZDE2NjU5MGFlYWIxLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTdiZjM4NzczYTFhYmFhNjgwMWJlYjgwNTkzYjA3ZWFlNTkwNzBiYTlhNTAzM2Y0MWM1MWQ0MzY1YmEyNmE4NjAmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.EOEVWLDMx5CT-Gg5UhBmdMIYT49IZPKrrA9VL7N-pBQ) [![](https://private-user-images.githubusercontent.com/46939827/413668612-96e81b08-6993-4a5e-96d0-3b05b50fbe44.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg2MTItOTZlODFiMDgtNjk5My00YTVlLTk2ZDAtM2IwNWI1MGZiZTQ0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWJkNTUxODJiMDZhN2I2MDAxYzZlNzIyOTAzYTgwZDg3ZDFlYWM3ODM1YWY0Mzg5MDJjNDY0NzJhYjU4ZTAzMmEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.rqM3YOwrYdBCiwbBbEvalj7Tsfl-XWqgD1K9PeE46tI)](https://private-user-images.githubusercontent.com/46939827/413668612-96e81b08-6993-4a5e-96d0-3b05b50fbe44.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg2MTItOTZlODFiMDgtNjk5My00YTVlLTk2ZDAtM2IwNWI1MGZiZTQ0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWJkNTUxODJiMDZhN2I2MDAxYzZlNzIyOTAzYTgwZDg3ZDFlYWM3ODM1YWY0Mzg5MDJjNDY0NzJhYjU4ZTAzMmEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.rqM3YOwrYdBCiwbBbEvalj7Tsfl-XWqgD1K9PeE46tI)
##### New Tag Colors + UI
- feat: expanded tag color system by [@CyanVoxel](https://github.com/CyanVoxel) in [#709](https://github.com/TagStudioDev/TagStudio/pull/709)
- fix(ui): use correct pink tag color by [@CyanVoxel](https://github.com/CyanVoxel) in [431efe4](https://github.com/TagStudioDev/TagStudio/commit/431efe4fe93213141c763e59ca9887215766fd42)
- fix(ui): use consistent tag outline colors by [@CyanVoxel](https://github.com/CyanVoxel) in [020a73d](https://github.com/TagStudioDev/TagStudio/commit/020a73d095c74283d6c80426d3c3db8874409952)
[![Screenshot 2025-01-04 at 04 23 43](https://private-user-images.githubusercontent.com/46939827/408753168-c8f82d89-ad7e-4be6-830e-b91cdc58e4c6.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MDg3NTMxNjgtYzhmODJkODktYWQ3ZS00YmU2LTgzMGUtYjkxY2RjNThlNGM2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTk1OWNhZGNkOTRiZGJhNGQxNGU1MjJhYTViYTc0OTNiNTA4NDUxODA4OTYxZDUwNzYxZDhmYWZkNzM4NTE3N2QmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.HLnwHyp3BYg8vXo3BtvqBoOqtpQTI1eykqa-L3chLUk)](https://private-user-images.githubusercontent.com/46939827/408753168-c8f82d89-ad7e-4be6-830e-b91cdc58e4c6.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTU5ODIsIm5iZiI6MTc0NjY1NTY4MiwicGF0aCI6Ii80NjkzOTgyNy80MDg3NTMxNjgtYzhmODJkODktYWQ3ZS00YmU2LTgzMGUtYjkxY2RjNThlNGM2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIyMDgwMlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTk1OWNhZGNkOTRiZGJhNGQxNGU1MjJhYTViYTc0OTNiNTA4NDUxODA4OTYxZDUwNzYxZDhmYWZkNzM4NTE3N2QmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.HLnwHyp3BYg8vXo3BtvqBoOqtpQTI1eykqa-L3chLUk)
##### New Tag Alias UI
- fix: preview panel aliases not staying up to date with database by [@DandyDev01](https://github.com/DandyDev01) in [#641](https://github.com/TagStudioDev/TagStudio/pull/641)
- fix: subtags/parent tags & aliases update the UI for building a tag by [@DandyDev01](https://github.com/DandyDev01) in [#534](https://github.com/TagStudioDev/TagStudio/pull/534)
#### Translations
TagStudio now has official translation support! Head to the new settings panel and select from one of the initial languages included. Note that many languages currently have incomplete translations.
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! Thank you to everyone who's helped contribute to the translations so far!
- translations: add string tokens for en.json by [@Bamowen](https://github.com/Bamowen) in [#507](https://github.com/TagStudioDev/TagStudio/pull/507)
- feat: translations by [@Computerdores](https://github.com/Computerdores) in [#662](https://github.com/TagStudioDev/TagStudio/pull/662)
- feat(ui): add language setting by [@CyanVoxel](https://github.com/CyanVoxel) in [#803](https://github.com/TagStudioDev/TagStudio/pull/803)
Initial Languages:
- Chinese (Traditional) (68%)
- [@brisu](https://github.com/brisu)
- Dutch (35%)
- [@Pheubel](https://github.com/Pheubel)
- Filipino (43%)
- [@searinminecraft](https://github.com/searinminecraft)
- French (100%)
- [@Bamowen](https://github.com/Bamowen), [@alessdangelo](https://github.com/alessdangelo), [@kitsumed](https://github.com/kitsumed), Obscaeris
- German (98%)
- [@Ryussei](https://github.com/Ryussei), [@Computerdores](https://github.com/Computerdores), Aaron M, [@JoeJoeTV](https://github.com/JoeJoeTV), [@Kurty00](https://github.com/Kurty00)
- Hungarian (100%)
- [@smileyhead](https://github.com/smileyhead)
- Norwegian Bokmål (16%)
- [@comradekingu](https://github.com/comradekingu)
- Polish (97%)
- Anonymous
- Portuguese (Brazil) (64%)
- [@LoboMetalurgico](https://github.com/LoboMetalurgico), [@SpaceFox1](https://github.com/SpaceFox1), [@DaviMarquezeli](https://github.com/DaviMarquezeli), [@viniciushelder](https://github.com/viniciushelder), Alexander Lennart Formiga Johnsson
- Russian (22%)
- [@The-Stolas](https://github.com/The-Stolas)
- Spanish (57%)
- [@gallegonovato](https://github.com/gallegonovato), [@Nginearing](https://github.com/Nginearing), [@noceno](https://github.com/noceno)
- Swedish (24%)
- [@adampawelec](https://github.com/adampawelec), [@mashed5894](https://github.com/mashed5894)
- Tamil (22%)
- [@VasigaranAndAngel](https://github.com/VasigaranAndAngel)
- Toki Pona (32%)
- [@goldstargloww](https://github.com/goldstargloww)
- Turkish (22%)
- [@Nyghl](https://github.com/Nyghl)
#### Miscellaneous
- feat: about section by [@mashed5894](https://github.com/mashed5894) in [#712](https://github.com/TagStudioDev/TagStudio/pull/712)
- feat(ui): add configurable splash screens by [@CyanVoxel](https://github.com/CyanVoxel) in [#703](https://github.com/TagStudioDev/TagStudio/pull/703)
- feat(ui): show filenames in thumbnail grid by [@CyanVoxel](https://github.com/CyanVoxel) in [#633](https://github.com/TagStudioDev/TagStudio/pull/633)
- feat(about): clickable links to docs/discord/etc in about modal by [@SkeleyM](https://github.com/SkeleyM) in [#799](https://github.com/TagStudioDev/TagStudio/pull/799)
### Fixed
- fix(ui): display all tags in panel during empty search by [@samuellieberman](https://github.com/samuellieberman) in [#328](https://github.com/TagStudioDev/TagStudio/pull/328)
- fix: avoid `KeyError` in `add_folders_to_tree()` (fix [#346](https://github.com/TagStudioDev/TagStudio/issues/346)) by [@CyanVoxel](https://github.com/CyanVoxel) in [#347](https://github.com/TagStudioDev/TagStudio/pull/347)
- fix: error on closing library by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#484](https://github.com/TagStudioDev/TagStudio/pull/484)
- fix: resolution info [#550](https://github.com/TagStudioDev/TagStudio/issues/550) by [@Roc25](https://github.com/Roc25) in [#551](https://github.com/TagStudioDev/TagStudio/pull/551)
- fix: remove queued thumnail jobs when closing library by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#583](https://github.com/TagStudioDev/TagStudio/pull/583)
- fix: use absolute ffprobe path on macos (Fix [#511](https://github.com/TagStudioDev/TagStudio/issues/511)) by [@CyanVoxel](https://github.com/CyanVoxel) in [#629](https://github.com/TagStudioDev/TagStudio/pull/629)
- fix(ui): prevent duplicate parent tags in UI by [@SkeleyM](https://github.com/SkeleyM) in [#665](https://github.com/TagStudioDev/TagStudio/pull/665)
- fix: fix -o flag not working if path has whitespace around it by [@python357-1](https://github.com/python357-1) in [#670](https://github.com/TagStudioDev/TagStudio/pull/670)
- fix: better file opening compatibility with non-ascii filenames by [@SkeleyM](https://github.com/SkeleyM) in [#667](https://github.com/TagStudioDev/TagStudio/pull/667)
- fix: restore environment before launching external programs by [@mashed5894](https://github.com/mashed5894) in [#707](https://github.com/TagStudioDev/TagStudio/pull/707)
- fix: have pydub use known ffmpeg + ffprobe locations by [@CyanVoxel](https://github.com/CyanVoxel) in [#724](https://github.com/TagStudioDev/TagStudio/pull/724)
- fix: add ".DS_Store" to `GLOBAL_IGNORE_SET` by [@CyanVoxel](https://github.com/CyanVoxel) in [b72a2f2](https://github.com/TagStudioDev/TagStudio/commit/b72a2f233141db4db6aa6be8796b626ebd3f0756)
- fix: don't add "._" files to libraries by [@CyanVoxel](https://github.com/CyanVoxel) in [eb1f634](https://github.com/TagStudioDev/TagStudio/commit/eb1f634d386cd8a5ecee1e6ff6a0b7d8811550fa)
### Changed
#### SQLite Save File Format
This was the main focus of this update, and where the majority of development time and resources have been spent since v9.4. These changes include everything that was done to migrate from the JSON format to SQLite starting from the initial SQLite PR, while re-implementing every feature from v9.4 as the initial SQLite PR was based on v9.3.x at the time.
- refactor!: use SQLite and SQLAlchemy for database backend by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#332](https://github.com/TagStudioDev/TagStudio/pull/332)
- feat: make search results more ergonomic by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#498](https://github.com/TagStudioDev/TagStudio/pull/498)
- feat: store `Entry` suffix separately by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#503](https://github.com/TagStudioDev/TagStudio/pull/503)
- feat: port thumbnail ([#390](https://github.com/TagStudioDev/TagStudio/pull/390)) and related features to v9.5 by [@CyanVoxel](https://github.com/CyanVoxel) in [#522](https://github.com/TagStudioDev/TagStudio/pull/522)
- fix: don't check db version with new library by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#536](https://github.com/TagStudioDev/TagStudio/pull/536)
- fix(ui): update ui when removing fields by [@DandyDev01](https://github.com/DandyDev01) in [#560](https://github.com/TagStudioDev/TagStudio/pull/560)
- feat(parity): backend for aliases and parent tags by [@DandyDev01](https://github.com/DandyDev01) in [#596](https://github.com/TagStudioDev/TagStudio/pull/596)
- fix: "open in explorer" opens correct folder by [@KirilBourakov](https://github.com/KirilBourakov) in [#603](https://github.com/TagStudioDev/TagStudio/pull/603)
- fix: ui/ux parity fixes for thumbnails and files by [@CyanVoxel](https://github.com/CyanVoxel) in [#608](https://github.com/TagStudioDev/TagStudio/pull/608)
- feat(parity): migrate json libraries to sqlite by [@CyanVoxel](https://github.com/CyanVoxel) in [#604](https://github.com/TagStudioDev/TagStudio/pull/604)
- fix: clear all setting values when opening a library by [@VasigaranAndAngel](https://github.com/VasigaranAndAngel) in [#622](https://github.com/TagStudioDev/TagStudio/pull/622)
- fix: remove/rework windows path tests by [@VasigaranAndAngel](https://github.com/VasigaranAndAngel) in [#625](https://github.com/TagStudioDev/TagStudio/pull/625)
- fix: add check to see if library is loaded in filter_items by [@Roc25](https://github.com/Roc25) in [#547](https://github.com/TagStudioDev/TagStudio/pull/547)
- fix: multiple macro errors by [@Computerdores](https://github.com/Computerdores) in [#612](https://github.com/TagStudioDev/TagStudio/pull/612)
- fix: don't allow blank tag alias values in db by [@CyanVoxel](https://github.com/CyanVoxel) in [#628](https://github.com/TagStudioDev/TagStudio/pull/628)
- feat: Reimplement drag drop files on sql migration by [@seakrueger](https://github.com/seakrueger) in [#528](https://github.com/TagStudioDev/TagStudio/pull/528)
- fix: stop sqlite db from being updated while running tests by [@python357-1](https://github.com/python357-1) in [#648](https://github.com/TagStudioDev/TagStudio/pull/648)
- fix: enter/return adds top result tag by [@SkeleyM](https://github.com/SkeleyM) in [#651](https://github.com/TagStudioDev/TagStudio/pull/651)
- fix: show correct unlinked files count by [@SkeleyM](https://github.com/SkeleyM) in [#653](https://github.com/TagStudioDev/TagStudio/pull/653)
- feat: implement parent tag search by [@Computerdores](https://github.com/Computerdores) in [#673](https://github.com/TagStudioDev/TagStudio/pull/673)
- fix: only close add tag menu with no search by [@SkeleyM](https://github.com/SkeleyM) in [#685](https://github.com/TagStudioDev/TagStudio/pull/685)
- fix: drag and drop no longer resets by [@SkeleyM](https://github.com/SkeleyM) in [#710](https://github.com/TagStudioDev/TagStudio/pull/710)
- feat(ui): port "create and add tag" to main branch by [@SkeleyM](https://github.com/SkeleyM) in [#711](https://github.com/TagStudioDev/TagStudio/pull/711)
- fix: don't add default title field, use proper phrasing for adding files by [@CyanVoxel](https://github.com/CyanVoxel) in [#701](https://github.com/TagStudioDev/TagStudio/pull/701)
- fix: preview panel + main window fixes and optimizations by [@CyanVoxel](https://github.com/CyanVoxel) in [#700](https://github.com/TagStudioDev/TagStudio/pull/700)
- fix: sort tag results by [@mashed5894](https://github.com/mashed5894) in [#721](https://github.com/TagStudioDev/TagStudio/pull/721)
- fix: restore opening last library on startup by [@SkeleyM](https://github.com/SkeleyM) in [#729](https://github.com/TagStudioDev/TagStudio/pull/729)
- fix(ui): don't always create tag on enter by [@SkeleyM](https://github.com/SkeleyM) in [#731](https://github.com/TagStudioDev/TagStudio/pull/731)
- fix: use tag aliases in tag search by [@CyanVoxel](https://github.com/CyanVoxel) in [#726](https://github.com/TagStudioDev/TagStudio/pull/726)
- fix: keep initial id order in `get_entries_full()` by [@CyanVoxel](https://github.com/CyanVoxel) in [#736](https://github.com/TagStudioDev/TagStudio/pull/736)
- fix: always catch db mismatch by [@CyanVoxel](https://github.com/CyanVoxel) in [#738](https://github.com/TagStudioDev/TagStudio/pull/738)
- fix: relink unlinked entry to existing entry without sql error by [@mashed5894](https://github.com/mashed5894) in [#730](https://github.com/TagStudioDev/TagStudio/issues/730)
- fix: refactor and fix bugs with missing_files.py by [@CyanVoxel](https://github.com/CyanVoxel) in [#739](https://github.com/TagStudioDev/TagStudio/pull/739)
- fix: dragging files references correct entry IDs [@CyanVoxel](https://github.com/CyanVoxel) in [44ff17c](https://github.com/TagStudioDev/TagStudio/commit/44ff17c0b3f05570e356c112f005dbc14c7cc05d)
- ui: port splash screen from Alpha-v9.4 by [@CyanVoxel](https://github.com/CyanVoxel) in [af760ee](https://github.com/TagStudioDev/TagStudio/commit/af760ee61a523c84bab0fb03a68d7465866d0e05)
- fix: tags created from tag database now add aliases by [@CyanVoxel](https://github.com/CyanVoxel) in [2903dd2](https://github.com/TagStudioDev/TagStudio/commit/2903dd22c45c02498687073d075bb88886de6b62)
- fix: check for tag name parity during JSON migration by [@CyanVoxel](https://github.com/CyanVoxel) in [#748](https://github.com/TagStudioDev/TagStudio/pull/748)
- feat(ui): re-implement tag display names on sql by [@CyanVoxel](https://github.com/CyanVoxel) in [#747](https://github.com/TagStudioDev/TagStudio/pull/747)
- fix(ui): restore Windows accent color on PySide 6.8.0.1 by [@CyanVoxel](https://github.com/CyanVoxel) in [#755](https://github.com/TagStudioDev/TagStudio/pull/755)
- fix(ui): (mostly) fix right-click search option on tags by [@CyanVoxel](https://github.com/CyanVoxel) in [#756](https://github.com/TagStudioDev/TagStudio/pull/756)
- feat: copy/paste fields and tags by [@mashed5894](https://github.com/mashed5894) in [#722](https://github.com/TagStudioDev/TagStudio/pull/722)
- perf: optimize query methods and reduce preview panel updates by [@CyanVoxel](https://github.com/CyanVoxel) in [#794](https://github.com/TagStudioDev/TagStudio/pull/794)
- feat: port file trashing ([#409](https://github.com/TagStudioDev/TagStudio/pull/409)) to v9.5 by [@CyanVoxel](https://github.com/CyanVoxel) in [#792](https://github.com/TagStudioDev/TagStudio/pull/792)
- fix: prevent future library versions from being opened by [@CyanVoxel](https://github.com/CyanVoxel) in [bcf3b2f](https://github.com/TagStudioDev/TagStudio/commit/bcf3b2f96bc8b876ca4b0c1d1882ce14a190f249)
#### UI/UX
- feat(ui): pre-select default tag name in `BuildTagPanel` by [@Cool-Game-Dev](https://github.com/Cool-Game-Dev) in [#592](https://github.com/TagStudioDev/TagStudio/pull/592)
- feat(ui): keyboard navigation for editing tags by [@Computerdores](https://github.com/Computerdores) in [#407](https://github.com/TagStudioDev/TagStudio/pull/407)
- feat(ui): use tag query as default new tag name by [@CyanVoxel](https://github.com/CyanVoxel) in [29c0dfd](https://github.com/TagStudioDev/TagStudio/commit/29c0dfdb2d88e8f473e27c7f1fe7ede6e5bd0feb)
- feat(ui): shortcut to add tags to selected entries; change click behavior of tags to edit by [@CyanVoxel](https://github.com/CyanVoxel) in [#749](https://github.com/TagStudioDev/TagStudio/pull/749)
- fix(ui): use consistent dark mode colors for all systems by [@CyanVoxel](https://github.com/CyanVoxel) in [#752](https://github.com/TagStudioDev/TagStudio/pull/752)
- fix(ui): use camera white balance for raw images by [@CyanVoxel](https://github.com/CyanVoxel) in [6ee5304](https://github.com/TagStudioDev/TagStudio/commit/6ee5304b52f217af0f5df543fcb389649203d6b2)
- Mixed field editing has been limited due to various bugs in both the JSON and SQL implementations. This will be re-implemented in a future release.
- fix(ui): improve tagging ux by [@CyanVoxel](https://github.com/CyanVoxel) in [#633](https://github.com/TagStudioDev/TagStudio/pull/633)
- fix(ui): hide library actions when no library is open by [@CyanVoxel](https://github.com/CyanVoxel) in [#787](https://github.com/TagStudioDev/TagStudio/pull/787)
- refactor(ui): recycle tag list in TagSearchPanel by [@CyanVoxel](https://github.com/CyanVoxel) in [#788](https://github.com/TagStudioDev/TagStudio/pull/788)
- feat(ui): add tag view limit dropdown
- fix(ui): expand usage of esc and enter for modals by [@CyanVoxel](https://github.com/CyanVoxel) in [#793](https://github.com/TagStudioDev/TagStudio/pull/793)
#### Performance
- feat: improve performance of "Delete Missing Entries" by [@Toby222](https://github.com/Toby222) and [@Computerdores](https://github.com/Computerdores) in [#696](https://github.com/TagStudioDev/TagStudio/pull/696)
#### Internal Changes
- refactor: combine open launch args by [@UnusualEgg](https://github.com/UnusualEgg) in [#364](https://github.com/TagStudioDev/TagStudio/pull/364)
- feat: add date_created, date_modified, and date_added columns to entries table by [@CyanVoxel](https://github.com/CyanVoxel) in [#740](https://github.com/TagStudioDev/TagStudio/pull/740)
## [9.5.0 Pre-Release 4] - 2025-02-17
### Added
#### Custom User-Created Tag Colors ([@CyanVoxel](https://github.com/CyanVoxel) in [#801](https://github.com/TagStudioDev/TagStudio/pull/801))
Create your own custom tag colors via the new Tag Color Manager! Tag colors are assigned a namespace (group) and include a name, primary color, and optional secondary color. By default the secondary color is used for the tag text color, but this can also be toggled to apply to the border color as well!
[![Screenshot 2025-02-16 at 17 34 22](https://private-user-images.githubusercontent.com/46939827/413668576-b591f1fe-1c44-4d82-b6e5-d166590aeab1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTQ3MTYsIm5iZiI6MTc0NjY1NDQxNiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg1NzYtYjU5MWYxZmUtMWM0NC00ZDgyLWI2ZTUtZDE2NjU5MGFlYWIxLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNDY1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBhYzUwZmExZjRlMWI4YzJjZTZmNDRiMjFiN2ZlZjg4ZjE3MWM4NzBkNmJlZWNjMzg2OWU5YTE2OWVmZTA2YTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.nEs6bk1euKTEkIqDipNJBrXHbHegb3PHWoW3tKI02_8)](https://private-user-images.githubusercontent.com/46939827/413668576-b591f1fe-1c44-4d82-b6e5-d166590aeab1.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTQ3MTYsIm5iZiI6MTc0NjY1NDQxNiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg1NzYtYjU5MWYxZmUtMWM0NC00ZDgyLWI2ZTUtZDE2NjU5MGFlYWIxLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNDY1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTBhYzUwZmExZjRlMWI4YzJjZTZmNDRiMjFiN2ZlZjg4ZjE3MWM4NzBkNmJlZWNjMzg2OWU5YTE2OWVmZTA2YTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.nEs6bk1euKTEkIqDipNJBrXHbHegb3PHWoW3tKI02_8)
[![Screenshot 2025-02-16 at 17 32 56](https://private-user-images.githubusercontent.com/46939827/413668612-96e81b08-6993-4a5e-96d0-3b05b50fbe44.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTQ3MTYsIm5iZiI6MTc0NjY1NDQxNiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg2MTItOTZlODFiMDgtNjk5My00YTVlLTk2ZDAtM2IwNWI1MGZiZTQ0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNDY1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTlhOGU2NjA2ZjRhMjNjZGYxZDE1ZWYzZmVjN2RjM2Q0YzA1NTcwZGE5OGFkMjc2MDIzMTk1YTFlYjY2NTQxNmImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.VD2trGcVKQVKUpzVog1UhZUM0JRcEHUhwGCiHpZ8zF0)](https://private-user-images.githubusercontent.com/46939827/413668612-96e81b08-6993-4a5e-96d0-3b05b50fbe44.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTQ3MTYsIm5iZiI6MTc0NjY1NDQxNiwicGF0aCI6Ii80NjkzOTgyNy80MTM2Njg2MTItOTZlODFiMDgtNjk5My00YTVlLTk2ZDAtM2IwNWI1MGZiZTQ0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNDY1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTlhOGU2NjA2ZjRhMjNjZGYxZDE1ZWYzZmVjN2RjM2Q0YzA1NTcwZGE5OGFkMjc2MDIzMTk1YTFlYjY2NTQxNmImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.VD2trGcVKQVKUpzVog1UhZUM0JRcEHUhwGCiHpZ8zF0)
#### Translations
TagStudio now has official translation support! Head to the new settings panel and select from one of the initial languages included. Note that many languages currently have incomplete translations.
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! Thank you to everyone who's helped contribute to the translations so far!
- translations: add string tokens for en.json by [@Bamowen](https://github.com/Bamowen) in [#507](https://github.com/TagStudioDev/TagStudio/pull/507)
- feat: translations by [@Computerdores](https://github.com/Computerdores) in [#662](https://github.com/TagStudioDev/TagStudio/pull/662)
- feat(ui): add language setting by [@CyanVoxel](https://github.com/CyanVoxel) in [#803](https://github.com/TagStudioDev/TagStudio/pull/803)
Initial Languages:
- Chinese (Traditional) (68%)
- [@brisu](https://github.com/brisu)
- Dutch (35%)
- [@Pheubel](https://github.com/Pheubel)
- Filipino (15%)
- [@searinminecraft](https://github.com/searinminecraft)
- French (89%)
- [@Bamowen](https://github.com/Bamowen), [@alessdangelo](https://github.com/alessdangelo), [@kitsumed](https://github.com/kitsumed), Obscaeris
- German (73%)
- [@Ryussei](https://github.com/Ryussei), [@Computerdores](https://github.com/Computerdores), Aaron M
- Hungarian (89%)
- [@smileyhead](https://github.com/smileyhead)
- Norwegian Bokmål (16%)
- [@comradekingu](https://github.com/comradekingu)
- Polish (76%)
- Anonymous
- Portuguese (Brazil) (22%)
- [@LoboMetalurgico](https://github.com/LoboMetalurgico), [@SpaceFox1](https://github.com/SpaceFox1)
- Russian (22%)
- [@The-Stolas](https://github.com/The-Stolas)
- Spanish (46%)
- [@gallegonovato](https://github.com/gallegonovato), [@Nginearing](https://github.com/Nginearing), [@noceno](https://github.com/noceno)
- Swedish (24%)
- [@adampawelec](https://github.com/adampawelec), [@mashed5894](https://github.com/mashed5894)
- Tamil (22%)
- [@VasigaranAndAngel](https://github.com/VasigaranAndAngel)
- Toki Pona (32%)
- [@goldstargloww](https://github.com/goldstargloww)
- Turkish (22%)
- [@Nyghl](https://github.com/Nyghl)
### Fixed
- feat(about): clickable links to docs/discord/etc in about modal by [@SkeleyM](https://github.com/SkeleyM) in [#799](https://github.com/TagStudioDev/TagStudio/pull/799)
### Internal Changes
This release increases the internal `DB_VERSION` to 8. Libraries created with this version of TagStudio can still be opened in earlier v9.5.0 pre-release versions, however the behavior of custom color borders will not be identical to the behavior in this PR. Otherwise it should still be possible to use any custom colors created in this version in these earlier pre-releases (but not really recommended).
## [9.5.0 Pre-Release 3] - 2025-02-10
### Added
##### [#743](https://github.com/TagStudioDev/TagStudio/pull/743) by [@CyanVoxel](https://github.com/CyanVoxel)
Added "Smartcase" and Globless Path Search
- `path: temp`: Returns all paths that have "temp" **(Case insensitive)** somewhere in the name.
- `path: Temp`: Returns all paths that have "Temp" **(Case sensitive)** somewhere in the name.
Glob Patterns w/ Smartcase
- `path: *temp*`: Returns all paths that have "temp" **(Case insensitive)** somewhere in the name.
- `path: *Temp*`: Returns all paths that have "Temp" **(Case sensitive)** somewhere in the name.
- `path: temp*`: Returns all paths that start with "temp" **(Case insensitive)** somewhere in the name.
- `path: Temp*`: Returns all paths that start with "Temp" **(Case sensitive)** somewhere in the name.
- `path: *temp`: Returns all paths that end with "temp" **(Case insensitive)** somewhere in the name.
- `path: *TEmP`: Returns all paths that end with "TEmP" **(Case sensitive)** somewhere in the name.
##### [#788](https://github.com/TagStudioDev/TagStudio/pull/788) by [@CyanVoxel](https://github.com/CyanVoxel)
- Added a "View Limit" dropdown to tag search boxes to limit the number of on-screen tags. Previously this limit was hardcoded to 100, but now options range from 25 to unlimited.
[![](https://private-user-images.githubusercontent.com/46939827/411701461-7f7da065-888d-4fe5-a4e7-f99447bcce98.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTQ3MTYsIm5iZiI6MTc0NjY1NDQxNiwicGF0aCI6Ii80NjkzOTgyNy80MTE3MDE0NjEtN2Y3ZGEwNjUtODg4ZC00ZmU1LWE0ZTctZjk5NDQ3YmNjZTk4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNDY1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWVlZjVhZjYyNWQwY2FmMjhmMWI1MzI5ZDdjYWMxMmM0N2M0Nzc4MmY1YjE0NWY4MjVhZmIyMDI1NzQzY2M0YmQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.a3xUaH5r3HsDgOb6-lo3T-xSRRSy7dDOrln5i62KFP8)](https://private-user-images.githubusercontent.com/46939827/411701461-7f7da065-888d-4fe5-a4e7-f99447bcce98.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTQ3MTYsIm5iZiI6MTc0NjY1NDQxNiwicGF0aCI6Ii80NjkzOTgyNy80MTE3MDE0NjEtN2Y3ZGEwNjUtODg4ZC00ZmU1LWE0ZTctZjk5NDQ3YmNjZTk4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNDY1NlomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWVlZjVhZjYyNWQwY2FmMjhmMWI1MzI5ZDdjYWMxMmM0N2M0Nzc4MmY1YjE0NWY4MjVhZmIyMDI1NzQzY2M0YmQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.a3xUaH5r3HsDgOb6-lo3T-xSRRSy7dDOrln5i62KFP8)
### Changed
- fix(ui): expand usage of esc and enter for modals by [@CyanVoxel](https://github.com/CyanVoxel) in [#793](https://github.com/TagStudioDev/TagStudio/pull/793)
- perf: optimize query methods and reduce preview panel updates by [@CyanVoxel](https://github.com/CyanVoxel) in [#794](https://github.com/TagStudioDev/TagStudio/pull/794)
##### [#788](https://github.com/TagStudioDev/TagStudio/pull/788) by [@CyanVoxel](https://github.com/CyanVoxel)
- Improved performance of tag search boxes, including the tag manager
### Fixed
- fix(ui): hide library actions when no library is open by [@CyanVoxel](https://github.com/CyanVoxel) in [#787](https://github.com/TagStudioDev/TagStudio/pull/787)
- feat: port file trashing ([#409](https://github.com/TagStudioDev/TagStudio/pull/409)) to v9.5 by [@CyanVoxel](https://github.com/CyanVoxel) in [#792](https://github.com/TagStudioDev/TagStudio/pull/792)
### Docs
- Added references to alternative POSIX shells, as well as pyenv to CONTRIBUTING.md by [@ChloeZamorano](https://github.com/ChloeZamorano) in [#791](https://github.com/TagStudioDev/TagStudio/pull/791)
## [9.5.0 Pre-Release 2] - 2025-02-03
### Added
##### [#784](https://github.com/TagStudioDev/TagStudio/pull/784) by [@CyanVoxel](https://github.com/CyanVoxel)
- Add Ctrl+M shortcut to open the "Tag Manager"
### Fixed
- fix: don't wrap field names too early by [@CyanVoxel](https://github.com/CyanVoxel) in [2215403](https://github.com/TagStudioDev/TagStudio/commit/2215403201e3b416a43ead0a322688180af6d71b) and [90a826d](https://github.com/TagStudioDev/TagStudio/commit/90a826d12804b3386a0b9003abb20f23f88ab3be)
- fix: save all tag attributes from "Create & Add" modal by [@SkeleyM](https://github.com/SkeleyM) in [#762](https://github.com/TagStudioDev/TagStudio/pull/762)
- fix: allow tag names with colons in search by [@SkeleyM](https://github.com/SkeleyM) in [#765](https://github.com/TagStudioDev/TagStudio/pull/765)
- fix: catch `ParsingError` by [@CyanVoxel](https://github.com/CyanVoxel) in [#779](https://github.com/TagStudioDev/TagStudio/pull/779)
- fix: patch incorrect description type & invalid disambiguation_id refs by [@CyanVoxel](https://github.com/CyanVoxel) in [#782](https://github.com/TagStudioDev/TagStudio/pull/782)
##### [#784](https://github.com/TagStudioDev/TagStudio/pull/784) by [@CyanVoxel](https://github.com/CyanVoxel)
- Reset tag search box and focus each time a tag search panel is opened
- Include tag parents in tag search results (v9.4 parity)
- Lowercase tag names now get properly sorted with uppercase ones
- Don't include tag display names in "closeness" factor when searching
- Escape "&" characters inside tag names so Qt doesn't treat them as mnemonics
- Set minimum tag width
- Fix "Add Tags" panel missing its window title when accessing from the keyboard shortcut
### Changed
##### [#784](https://github.com/TagStudioDev/TagStudio/pull/784) by [@CyanVoxel](https://github.com/CyanVoxel)
- The "use for disambiguation" button has been moved to the right-hand side of parent tags in order to prevent accidental clicks involving the left-hand "remove tag" button
- Add "Create & Add" button to the bottom of all non-whitespace searches, even if they return some tags
- The awkward "+" button next to tags in the "Add Tags" panel has been removed in favor of clicking on tags themselves
- Improved visual feedback for highlighting, keyboard focusing, and clicking tags
- The clickable area of the "-" button on tags has been increased and has visual feedback when you hover and click it
- You can now tab into the tag search list and add tags with a spacebar press (previously possible but very janky)
- In tag search panels, pressing the Esc key will return your focus to the search bar and highlight your previous query. If the search box is already highlighted, pressing Esc will close the modal
- In modals such as the "Add Tag" and "Edit Tag" panels, pressing Esc will cancel the operation and close the modal
### Internal Changes
- refactor: wrap migration_iterator lambda in a try/except block by [@CyanVoxel](https://github.com/CyanVoxel) in [#773](https://github.com/TagStudioDev/TagStudio/pull/773)
### Docs
- docs: update field and library pages by [@CyanVoxel](https://github.com/CyanVoxel) in [f5ff4d7](https://github.com/TagStudioDev/TagStudio/commit/f5ff4d78c1ad53134e9c64698886aee68c0f1dc1)
- docs: add information about "tag manager" by [@CyanVoxel](https://github.com/CyanVoxel) in [9bdbafa](https://github.com/TagStudioDev/TagStudio/commit/9bdbafa40c4274922f6533b5b5fcee9a4fe43030)
- docs: add note about glob searching in the readme by [@CyanVoxel](https://github.com/CyanVoxel) in [6e402ac](https://github.com/TagStudioDev/TagStudio/commit/6e402ac34d2d60e71fbd36ad234fe3914d5eb8e0)
- docs: add library_search page by [@CyanVoxel](https://github.com/CyanVoxel) in [5be7dfc](https://github.com/TagStudioDev/TagStudio/commit/5be7dfc314b21042c18b2f08893f2b452d12394a)
- docs: docs: add more links to index.md by [@CyanVoxel](https://github.com/CyanVoxel) in [d795889](https://github.com/TagStudioDev/TagStudio/commit/d7958892b7762586837204d686a6a2a993e3c26e)
- docs: fix typo for "category" in usage.md by [@pinheadtf2](https://github.com/pinheadtf2) in [#760](https://github.com/TagStudioDev/TagStudio/pull/760)
- fix(docs): fix screenshot sometimes not rendering by [@SkeleyM](https://github.com/SkeleyM) in [#775](https://github.com/TagStudioDev/TagStudio/pull/775)
## [9.5.0 Pre-Release 1] - 2025-01-31
### Added
#### Overhauled Search Engine
##### Boolean Operators
- feat: implement query language by [@Computerdores](https://github.com/Computerdores) in [#606](https://github.com/TagStudioDev/TagStudio/pull/606)
- feat: optimize AND queries by [@Computerdores](https://github.com/Computerdores) in [#679](https://github.com/TagStudioDev/TagStudio/pull/679)
##### Filetype, Mediatype, and Glob Path Searches
- fix: remove wildcard requirement for tags by [@Tyrannicodin](https://github.com/Tyrannicodin) in [#481](https://github.com/TagStudioDev/TagStudio/pull/481)
- feat: add filetype and mediatype searches by [@python357-1](https://github.com/python357-1) in [#575](https://github.com/TagStudioDev/TagStudio/pull/575)
- feat: make path search use globs by [@python357-1](https://github.com/python357-1) in [#582](https://github.com/TagStudioDev/TagStudio/pull/582)
- feat: implement search equivalence of "jpg" and "jpeg" filetypes by [@Computerdores](https://github.com/Computerdores) in [#649](https://github.com/TagStudioDev/TagStudio/pull/649)
##### Sortable Results
- feat: sort by "date added" in library by [@Computerdores](https://github.com/Computerdores) in [#674](https://github.com/TagStudioDev/TagStudio/pull/674)
##### Autocomplete
- feat: add autocomplete for search engine by [@python357-1](https://github.com/python357-1) in [#586](https://github.com/TagStudioDev/TagStudio/pull/586)
#### Replaced "Tag Fields" with Tag Categories
Instead of tags needing to be added to a tag field type such as "Meta Tags", "Content Tags", or just the "Tags" field, tags are now added directly to file entries with no intermediary step. While tag field types offered a way to further organize tags, it was cumbersome, inflexible, and simply not fully fleshed out. Tag Categories offer all of the previous (intentional) functionality while greatly increasing the ease of use and customization.
- feat!: tag categories by [@CyanVoxel](https://github.com/CyanVoxel) in [#655](https://github.com/TagStudioDev/TagStudio/pull/655)
[![Screenshot 2025-01-04 at 04 23 43](https://private-user-images.githubusercontent.com/46939827/400138597-0b92eca5-db8f-4e3e-954b-1b4f3795f073.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTUwODcsIm5iZiI6MTc0NjY1NDc4NywicGF0aCI6Ii80NjkzOTgyNy80MDAxMzg1OTctMGI5MmVjYTUtZGI4Zi00ZTNlLTk1NGItMWI0ZjM3OTVmMDczLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNTMwN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTM1M2EwNGFjYmJiM2QwZjg2YzNmNTVjMGYwZDJkZGJkZTg5NjZjNDFhNTYyNDE0OGNlOWFhNzRkMzE3MjBkNzEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.B5NRZmHygdHlMy2ZnZtHjfOs83jjEliwfxoe3eMBnEQ)](https://private-user-images.githubusercontent.com/46939827/400138597-0b92eca5-db8f-4e3e-954b-1b4f3795f073.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTUwODcsIm5iZiI6MTc0NjY1NDc4NywicGF0aCI6Ii80NjkzOTgyNy80MDAxMzg1OTctMGI5MmVjYTUtZGI4Zi00ZTNlLTk1NGItMWI0ZjM3OTVmMDczLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNTMwN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTM1M2EwNGFjYmJiM2QwZjg2YzNmNTVjMGYwZDJkZGJkZTg5NjZjNDFhNTYyNDE0OGNlOWFhNzRkMzE3MjBkNzEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.B5NRZmHygdHlMy2ZnZtHjfOs83jjEliwfxoe3eMBnEQ)
#### Thumbnails and File Previews
##### New Thumbnail Support
- feat: add svg thumbnail support (port [#442](https://github.com/TagStudioDev/TagStudio/pull/442)) by [@Tyrannicodin](https://github.com/Tyrannicodin) and [@CyanVoxel](https://github.com/CyanVoxel) in [#540](https://github.com/TagStudioDev/TagStudio/pull/540)
- feat: add pdf thumbnail support (port [#378](https://github.com/TagStudioDev/TagStudio/pull/378)) by [@Heiholf](https://github.com/Heiholf) and [@CyanVoxel](https://github.com/CyanVoxel) in [#543](https://github.com/TagStudioDev/TagStudio/pull/543)
- feat: add ePub thumbnail support (port [#387](https://github.com/TagStudioDev/TagStudio/pull/387)) by [@Heiholf](https://github.com/Heiholf) and [@CyanVoxel](https://github.com/CyanVoxel) in [#539](https://github.com/TagStudioDev/TagStudio/pull/539)
- feat: add OpenDocument thumbnail support (port [#366](https://github.com/TagStudioDev/TagStudio/pull/366)) by [@Joshua-Beatty](https://github.com/Joshua-Beatty) and [@CyanVoxel](https://github.com/CyanVoxel) in [#545](https://github.com/TagStudioDev/TagStudio/pull/545)
- feat: add JXL thumbnail and animated APNG + WEBP support (port [#344](https://github.com/TagStudioDev/TagStudio/pull/344) and partially port [#357](https://github.com/TagStudioDev/TagStudio/pull/357)) by [@BPplays](https://github.com/BPplays) and [@CyanVoxel](https://github.com/CyanVoxel) in [#549](https://github.com/TagStudioDev/TagStudio/pull/549)
- fix: catch ImportError for pillow_jxl module by [@CyanVoxel](https://github.com/CyanVoxel) in [a2f9685](https://github.com/TagStudioDev/TagStudio/commit/a2f9685bc0d744ea6f5334c6d2926aad3f6d375a)
##### Audio Playback
- feat: audio playback by [@csponge](https://github.com/csponge) in [#576](https://github.com/TagStudioDev/TagStudio/pull/576)
- feat(ui): add audio volume slider by [@SkeleyM](https://github.com/SkeleyM) in [#691](https://github.com/TagStudioDev/TagStudio/pull/691)
##### Thumbnail Caching
- feat(ui): add thumbnail caching by [@CyanVoxel](https://github.com/CyanVoxel) in [#694](https://github.com/TagStudioDev/TagStudio/pull/694)
#### Tags
##### Delete Tags _(Finally!)_
- feat: remove and create tags from tag database panel by [@DandyDev01](https://github.com/DandyDev01) in [#569](https://github.com/TagStudioDev/TagStudio/pull/569)
##### New Tag Colors + UI
- feat: expanded tag color system by [@CyanVoxel](https://github.com/CyanVoxel) in [#709](https://github.com/TagStudioDev/TagStudio/pull/709)
- fix(ui): use correct pink tag color by [@CyanVoxel](https://github.com/CyanVoxel) in [431efe4](https://github.com/TagStudioDev/TagStudio/commit/431efe4fe93213141c763e59ca9887215766fd42)
- fix(ui): use consistent tag outline colors by [@CyanVoxel](https://github.com/CyanVoxel) in [020a73d](https://github.com/TagStudioDev/TagStudio/commit/020a73d095c74283d6c80426d3c3db8874409952)
[![Screenshot 2025-01-04 at 04 23 43](https://private-user-images.githubusercontent.com/46939827/408753168-c8f82d89-ad7e-4be6-830e-b91cdc58e4c6.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTUwODcsIm5iZiI6MTc0NjY1NDc4NywicGF0aCI6Ii80NjkzOTgyNy80MDg3NTMxNjgtYzhmODJkODktYWQ3ZS00YmU2LTgzMGUtYjkxY2RjNThlNGM2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNTMwN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI3ZDk2MDg5ODVjYzA2ZGU2Njc1ODE5M2U4OWU4ZGMwY2MxNzUzM2M2YmM2ZmRiMTdlOTkyNGM3MjlhMWNiOWUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.0RYUWgVW8VFvu2unzdWHtDCE4USYM77OcMvWeZkd8Hs)](https://private-user-images.githubusercontent.com/46939827/408753168-c8f82d89-ad7e-4be6-830e-b91cdc58e4c6.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDY2NTUwODcsIm5iZiI6MTc0NjY1NDc4NywicGF0aCI6Ii80NjkzOTgyNy80MDg3NTMxNjgtYzhmODJkODktYWQ3ZS00YmU2LTgzMGUtYjkxY2RjNThlNGM2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNTA1MDclMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjUwNTA3VDIxNTMwN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI3ZDk2MDg5ODVjYzA2ZGU2Njc1ODE5M2U4OWU4ZGMwY2MxNzUzM2M2YmM2ZmRiMTdlOTkyNGM3MjlhMWNiOWUmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.0RYUWgVW8VFvu2unzdWHtDCE4USYM77OcMvWeZkd8Hs)
##### New Tag Alias UI
- fix: preview panel aliases not staying up to date with database by [@DandyDev01](https://github.com/DandyDev01) in [#641](https://github.com/TagStudioDev/TagStudio/pull/641)
- fix: subtags/parent tags & aliases update the UI for building a tag by [@DandyDev01](https://github.com/DandyDev01) in [#534](https://github.com/TagStudioDev/TagStudio/pull/534)
#### Miscellaneous
- feat: about section by [@mashed5894](https://github.com/mashed5894) in [#712](https://github.com/TagStudioDev/TagStudio/pull/712)
- feat(ui): add configurable splash screens by [@CyanVoxel](https://github.com/CyanVoxel) in [#703](https://github.com/TagStudioDev/TagStudio/pull/703)
- feat(ui): show filenames in thumbnail grid by [@CyanVoxel](https://github.com/CyanVoxel) in [#633](https://github.com/TagStudioDev/TagStudio/pull/633)
### Fixed
- fix(ui): display all tags in panel during empty search by [@samuellieberman](https://github.com/samuellieberman) in [#328](https://github.com/TagStudioDev/TagStudio/pull/328)
- fix: avoid `KeyError` in `add_folders_to_tree()` (fix [#346](https://github.com/TagStudioDev/TagStudio/issues/346)) by [@CyanVoxel](https://github.com/CyanVoxel) in [#347](https://github.com/TagStudioDev/TagStudio/pull/347)
- fix: error on closing library by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#484](https://github.com/TagStudioDev/TagStudio/pull/484)
- fix: resolution info [#550](https://github.com/TagStudioDev/TagStudio/issues/550) by [@Roc25](https://github.com/Roc25) in [#551](https://github.com/TagStudioDev/TagStudio/pull/551)
- fix: remove queued thumnail jobs when closing library by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#583](https://github.com/TagStudioDev/TagStudio/pull/583)
- fix: use absolute ffprobe path on macos (Fix [#511](https://github.com/TagStudioDev/TagStudio/issues/511)) by [@CyanVoxel](https://github.com/CyanVoxel) in [#629](https://github.com/TagStudioDev/TagStudio/pull/629)
- fix(ui): prevent duplicate parent tags in UI by [@SkeleyM](https://github.com/SkeleyM) in [#665](https://github.com/TagStudioDev/TagStudio/pull/665)
- fix: fix -o flag not working if path has whitespace around it by [@python357-1](https://github.com/python357-1) in [#670](https://github.com/TagStudioDev/TagStudio/pull/670)
- fix: better file opening compatibility with non-ascii filenames by [@SkeleyM](https://github.com/SkeleyM) in [#667](https://github.com/TagStudioDev/TagStudio/pull/667)
- fix: restore environment before launching external programs by [@mashed5894](https://github.com/mashed5894) in [#707](https://github.com/TagStudioDev/TagStudio/pull/707)
- fix: have pydub use known ffmpeg + ffprobe locations by [@CyanVoxel](https://github.com/CyanVoxel) in [#724](https://github.com/TagStudioDev/TagStudio/pull/724)
- fix: add ".DS_Store" to `GLOBAL_IGNORE_SET` by [@CyanVoxel](https://github.com/CyanVoxel) in [b72a2f2](https://github.com/TagStudioDev/TagStudio/commit/b72a2f233141db4db6aa6be8796b626ebd3f0756)
- fix: don't add "._" files to libraries by [@CyanVoxel](https://github.com/CyanVoxel) in [eb1f634](https://github.com/TagStudioDev/TagStudio/commit/eb1f634d386cd8a5ecee1e6ff6a0b7d8811550fa)
### Changed
#### SQLite Save File Format
This was the main focus of this update, and where the majority of development time and resources have been spent since v9.4. These changes include everything that was done to migrate from the JSON format to SQLite starting from the initial SQLite PR, while re-implementing every feature from v9.4 as the initial SQLite PR was based on v9.3.x at the time.
- refactor!: use SQLite and SQLAlchemy for database backend by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#332](https://github.com/TagStudioDev/TagStudio/pull/332)
- feat: make search results more ergonomic by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#498](https://github.com/TagStudioDev/TagStudio/pull/498)
- feat: store `Entry` suffix separately by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#503](https://github.com/TagStudioDev/TagStudio/pull/503)
- feat: port thumbnail ([#390](https://github.com/TagStudioDev/TagStudio/pull/390)) and related features to v9.5 by [@CyanVoxel](https://github.com/CyanVoxel) in [#522](https://github.com/TagStudioDev/TagStudio/pull/522)
- fix: don't check db version with new library by [@yedpodtrzitko](https://github.com/yedpodtrzitko) in [#536](https://github.com/TagStudioDev/TagStudio/pull/536)
- fix(ui): update ui when removing fields by [@DandyDev01](https://github.com/DandyDev01) in [#560](https://github.com/TagStudioDev/TagStudio/pull/560)
- feat(parity): backend for aliases and parent tags by [@DandyDev01](https://github.com/DandyDev01) in [#596](https://github.com/TagStudioDev/TagStudio/pull/596)
- fix: "open in explorer" opens correct folder by [@KirilBourakov](https://github.com/KirilBourakov) in [#603](https://github.com/TagStudioDev/TagStudio/pull/603)
- fix: ui/ux parity fixes for thumbnails and files by [@CyanVoxel](https://github.com/CyanVoxel) in [#608](https://github.com/TagStudioDev/TagStudio/pull/608)
- feat(parity): migrate json libraries to sqlite by [@CyanVoxel](https://github.com/CyanVoxel) in [#604](https://github.com/TagStudioDev/TagStudio/pull/604)
- fix: clear all setting values when opening a library by [@VasigaranAndAngel](https://github.com/VasigaranAndAngel) in [#622](https://github.com/TagStudioDev/TagStudio/pull/622)
- fix: remove/rework windows path tests by [@VasigaranAndAngel](https://github.com/VasigaranAndAngel) in [#625](https://github.com/TagStudioDev/TagStudio/pull/625)
- fix: add check to see if library is loaded in filter_items by [@Roc25](https://github.com/Roc25) in [#547](https://github.com/TagStudioDev/TagStudio/pull/547)
- fix: multiple macro errors by [@Computerdores](https://github.com/Computerdores) in [#612](https://github.com/TagStudioDev/TagStudio/pull/612)
- fix: don't allow blank tag alias values in db by [@CyanVoxel](https://github.com/CyanVoxel) in [#628](https://github.com/TagStudioDev/TagStudio/pull/628)
- feat: Reimplement drag drop files on sql migration by [@seakrueger](https://github.com/seakrueger) in [#528](https://github.com/TagStudioDev/TagStudio/pull/528)
- fix: stop sqlite db from being updated while running tests by [@python357-1](https://github.com/python357-1) in [#648](https://github.com/TagStudioDev/TagStudio/pull/648)
- fix: enter/return adds top result tag by [@SkeleyM](https://github.com/SkeleyM) in [#651](https://github.com/TagStudioDev/TagStudio/pull/651)
- fix: show correct unlinked files count by [@SkeleyM](https://github.com/SkeleyM) in [#653](https://github.com/TagStudioDev/TagStudio/pull/653)
- feat: implement parent tag search by [@Computerdores](https://github.com/Computerdores) in [#673](https://github.com/TagStudioDev/TagStudio/pull/673)
- fix: only close add tag menu with no search by [@SkeleyM](https://github.com/SkeleyM) in [#685](https://github.com/TagStudioDev/TagStudio/pull/685)
- fix: drag and drop no longer resets by [@SkeleyM](https://github.com/SkeleyM) in [#710](https://github.com/TagStudioDev/TagStudio/pull/710)
- feat(ui): port "create and add tag" to main branch by [@SkeleyM](https://github.com/SkeleyM) in [#711](https://github.com/TagStudioDev/TagStudio/pull/711)
- fix: don't add default title field, use proper phrasing for adding files by [@CyanVoxel](https://github.com/CyanVoxel) in [#701](https://github.com/TagStudioDev/TagStudio/pull/701)
- fix: preview panel + main window fixes and optimizations by [@CyanVoxel](https://github.com/CyanVoxel) in [#700](https://github.com/TagStudioDev/TagStudio/pull/700)
- fix: sort tag results by [@mashed5894](https://github.com/mashed5894) in [#721](https://github.com/TagStudioDev/TagStudio/pull/721)
- fix: restore opening last library on startup by [@SkeleyM](https://github.com/SkeleyM) in [#729](https://github.com/TagStudioDev/TagStudio/pull/729)
- fix(ui): don't always create tag on enter by [@SkeleyM](https://github.com/SkeleyM) in [#731](https://github.com/TagStudioDev/TagStudio/pull/731)
- fix: use tag aliases in tag search by [@CyanVoxel](https://github.com/CyanVoxel) in [#726](https://github.com/TagStudioDev/TagStudio/pull/726)
- fix: keep initial id order in `get_entries_full()` by [@CyanVoxel](https://github.com/CyanVoxel) in [#736](https://github.com/TagStudioDev/TagStudio/pull/736)
- fix: always catch db mismatch by [@CyanVoxel](https://github.com/CyanVoxel) in [#738](https://github.com/TagStudioDev/TagStudio/pull/738)
- fix: relink unlinked entry to existing entry without sql error by [@mashed5894](https://github.com/mashed5894) in [#730](https://github.com/TagStudioDev/TagStudio/issues/730)
- fix: refactor and fix bugs with missing_files.py by [@CyanVoxel](https://github.com/CyanVoxel) in [#739](https://github.com/TagStudioDev/TagStudio/pull/739)
- fix: dragging files references correct entry IDs [@CyanVoxel](https://github.com/CyanVoxel) in [44ff17c](https://github.com/TagStudioDev/TagStudio/commit/44ff17c0b3f05570e356c112f005dbc14c7cc05d)
- ui: port splash screen from Alpha-v9.4 by [@CyanVoxel](https://github.com/CyanVoxel) in [af760ee](https://github.com/TagStudioDev/TagStudio/commit/af760ee61a523c84bab0fb03a68d7465866d0e05)
- fix: tags created from tag database now add aliases by [@CyanVoxel](https://github.com/CyanVoxel) in [2903dd2](https://github.com/TagStudioDev/TagStudio/commit/2903dd22c45c02498687073d075bb88886de6b62)
- fix: check for tag name parity during JSON migration by [@CyanVoxel](https://github.com/CyanVoxel) in [#748](https://github.com/TagStudioDev/TagStudio/pull/748)
- feat(ui): re-implement tag display names on sql by [@CyanVoxel](https://github.com/CyanVoxel) in [#747](https://github.com/TagStudioDev/TagStudio/pull/747)
- fix(ui): restore Windows accent color on PySide 6.8.0.1 by [@CyanVoxel](https://github.com/CyanVoxel) in [#755](https://github.com/TagStudioDev/TagStudio/pull/755)
- fix(ui): (mostly) fix right-click search option on tags by [@CyanVoxel](https://github.com/CyanVoxel) in [#756](https://github.com/TagStudioDev/TagStudio/pull/756)
- feat: copy/paste fields and tags by [@mashed5894](https://github.com/mashed5894) in [#722](https://github.com/TagStudioDev/TagStudio/pull/722)
#### UI/UX
- feat(ui): pre-select default tag name in `BuildTagPanel` by [@Cool-Game-Dev](https://github.com/Cool-Game-Dev) in [#592](https://github.com/TagStudioDev/TagStudio/pull/592)
- feat(ui): keyboard navigation for editing tags by [@Computerdores](https://github.com/Computerdores) in [#407](https://github.com/TagStudioDev/TagStudio/pull/407)
- feat(ui): use tag query as default new tag name by [@CyanVoxel](https://github.com/CyanVoxel) in [29c0dfd](https://github.com/TagStudioDev/TagStudio/commit/29c0dfdb2d88e8f473e27c7f1fe7ede6e5bd0feb)
- feat(ui): shortcut to add tags to selected entries; change click behavior of tags to edit by [@CyanVoxel](https://github.com/CyanVoxel) in [#749](https://github.com/TagStudioDev/TagStudio/pull/749)
- fix(ui): use consistent dark mode colors for all systems by [@CyanVoxel](https://github.com/CyanVoxel) in [#752](https://github.com/TagStudioDev/TagStudio/pull/752)
- fix(ui): use camera white balance for raw images by [@CyanVoxel](https://github.com/CyanVoxel) in [6ee5304](https://github.com/TagStudioDev/TagStudio/commit/6ee5304b52f217af0f5df543fcb389649203d6b2)
- Mixed field editing has been limited due to various bugs in both the JSON and SQL implementations. This will be re-implemented in a future release.
#### Performance
- feat: improve performance of "Delete Missing Entries" by [@Toby222](https://github.com/Toby222) and [@Computerdores](https://github.com/Computerdores) in [#696](https://github.com/TagStudioDev/TagStudio/pull/696)
#### Internal Changes
- refactor: combine open launch args by [@UnusualEgg](https://github.com/UnusualEgg) in [#364](https://github.com/TagStudioDev/TagStudio/pull/364)
- feat: add date_created, date_modified, and date_added columns to entries table by [@CyanVoxel](https://github.com/CyanVoxel) in [#740](https://github.com/TagStudioDev/TagStudio/pull/740)
## [9.4.2] - 2024-12-01
### Added/Fixed
- Create auto-backup of library for use in save failures (Fix [#343](https://github.com/TagStudioDev/TagStudio/issues/343)) by [@CyanVoxel](https://github.com/CyanVoxel) in [#554](https://github.com/TagStudioDev/TagStudio/pull/554)
## [9.4.1] - 2024-09-13
### Added
- Warn user if FFmpeg is not installed
- Support for `.raf` and `.orf` raw image thumbnails and previews
### Fixed
- Use `birthtime` for file creation time on Mac & Windows
- Use audio icon fallback when FFmpeg is not detected
- Retain search query upon directory refresh
### Changed
- Significantly improve file re-scanning performance
## [9.4.0] - 2024-09-03
### Added
- Copy and paste fields
- Add multiple fields at once
- Drag and drop files in/out of the program
- Files can be shared by dragging them from the thumbnail grid to other programs
- Files can be added to library folder by dragging them into the program
- Manage Python virtual environment in Nix flake
- Ability to create tag when adding tags
- Blender preview thumbnail support
- File deletion/trashing
- Added right-click option on thumbnails and preview panel to delete files
- Added Edit Menu option for deleting files
- Added <kbd>Delete</kbd> key shortcut for deleting files
- Font preview thumbnail support
- Short "Aa" previews for thumbnails
- Full alphabet preview for the preview pane
- Sort tags by alphabetical/color
- File explorer action follows OS naming
- Preview Source Engine files
- Expanded thumbnail and preview features
- Add album cover art thumbnails
- Add audio waveform thumbnails for audio files without embedded cover art
- Add new default file thumbnails, both for generic and specific file types
- Change the unlinked file icon to better convey its meaning
- Add dropdown for different thumbnail sizes
- Show File Creation and Modified dates; Restyle file path label
### Fixed
- Backslashes in f-string on file dupe widget
- Tags not shown when none searched
- Avoid error from eagerly grabbing data values
- Correct behavior for tag search options
- Load Gallery-DL sidecar files correctly
- Correct duplicate file matching
- GPU hardware acceleration in Nix flake
- Suppress command prompt windows for FFmpeg in builds
### Internal Changes
- Move type constants to media classes
- Combine open launch arguments
- Revamp Nix flake with devenv/direnv in cb4798b
- Remove impurity of Nix flake when used with direnv in bc38e56
## [9.3.2] - 2024-07-18
### Fixed
- Fix signal log warning
- Fix "Folders to Tags" feature
- Fix search ignoring case of extension list
### Internal Changes
- Add tests into CI by
- Create testing library files ad-hoc
- Refactoring: centralize field IDs
- Update to pyside6 version 6.7.1
## [9.3.1] - 2024-06-13
### Fixed
- Separately pin QT nixpkg version
- Bugfix for #252, don't attempt to read video file if invalid or 0 frames long
- Toggle Mouse Event Transparency on ItemThumbs
- Refactor `video_player.py`
## [9.3.0] - 2024-06-08
### Added
- Added playback previews for video files
- Added Boolean "and/or" search mode selection
- Added ability to scan and fix duplicate entries (not to be confused with duplicate files) from the "Fix Unlinked Entries" menu
- Added “Select All” (<kbd>Ctrl</kbd>+<kbd>A</kbd> / <kbd>⌘ Command</kbd>+<kbd>A</kbd>) hotkey for the library grid view
- Added "Clear Selection" hotkey (<kbd>Esc</kbd>) for the library grid view
- Added the ability to invert the file extension inclusion list into an exclusion list
- Added default landing page when no library is open
### Fixed
- TagStudio will no longer attempt to or allow you to reopen a library from a missing location
- Fixed `PermissionError` when attempting to access files with a higher permission level upon scanning the library directory
- Fixed RAW image previews sometimes not loadingand
- Fixed most non-UTF-8 encoded text files from not being able to be previewed
- Fixed "Refresh Directories"/"Fix Unlinked Entries" creating duplicate entries
- Other miscellaneous fixes
### Changed
- Renamed "Subtags" to "Parent Tags" to help better describe their function
- Increased number of tags shown by default in the "Add Tag" modal from 29 to 100
- Documentation is now split into individual linked files and updated to include future features
- Replaced use of `os.path` with `pathlib`
- `.cr2` files are now included in the list of RAW image file types
- Minimum supported macOS version raised to 12.0
## [9.2.1] - 2024-05-23
### Added
- Basic thumbnail/preview support for RAW images (currently `.raw`, `.dng`, `.rw2`, `.nef`, `.arw`, `.crw`, `.cr3`)
- NOTE: These previews are currently slow to load given the nature of rendering them. In the future once thumbnail caching is added, this process should only happen once.
- Thumbnail/preview support for HEIF images
### Fixed
- Fixed sidebar not expanding horizontally
- Fixed "Recent Library" list not updating when creating a new library
- Fixed palletized images not loading with alpha channels
- Low resolution images (such as pixel art) now render with crisp edges in thumbnails and previews
- Fixed visual bug where the edit icon would show for incorrect fields
## [9.2.0] - 2024-05-14
### Added
@@ -48,6 +847,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- New application icons
### Known Issues
- Using and editing multiple entry fields of the same type may result in incorrect field(s) being updated
- Adding Favorite or Archived tags via the thumbnail badges may apply the tag(s) to incorrect fields
- Searching for tag names with spaces does not currently function as intended

150
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,150 @@
# Contributing to TagStudio
_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 [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 [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
If you wish to develop for TagStudio, you'll need to create a development environment by installing the required dependencies. You have a number of options depending on your level of experience and familiarly with existing Python toolchains.
If you know what you're doing and have developed for Python projects in the past, you can get started quickly with the "Brief Instructions" below. Otherwise, please see the full instructions on the documentation website for "[Creating a Development Environment](https://docs.tagstud.io/install/#creating-a-development-environment)".
### Brief Instructions
1. Have [Python 3.12](https://www.python.org/downloads/) and PIP installed. Also have [FFmpeg](https://ffmpeg.org/download.html) installed if you wish to have audio/video playback and thumbnails.
2. Clone the repository to the folder of your choosing:
```
git clone https://github.com/TagStudioDev/TagStudio.git
```
3. Use a dependency manager such as [uv](https://docs.astral.sh/uv/) or [Poetry 2.0](https://python-poetry.org/blog/category/releases/) to install the required dependencies, or alternatively create and activate a [virtual environment](https://docs.tagstud.io/install/#manual-installation) with `venv`.
4. If using a virtual environment instead of a dependency manager, install an editable version of the program and development dependencies with the following PIP command:
```
pip install -e ".[dev]"
```
Otherwise, modify the command above for use with your dependency manager of choice. For example if using uv, you may use this:
```
uv pip install -e ".[dev]"
```
## Workflow Checks
When pushing your code, several automated workflows will check it against predefined tests and style checks. It's _highly recommended_ that you run these checks locally beforehand to avoid having to fight back-and-forth with the workflow checks inside your pull requests.
> [!TIP]
> To format the code automatically before each commit, there's a configured action available for the `pre-commit` hook. Install it by running `pre-commit install`. The hook will be executed each time on running `git commit`.
### [Ruff](https://github.com/astral-sh/ruff)
A Python linter and code formatter. Ruff uses the `pyproject.toml` as its config file and runs whenever code is pushed or pulled into the project.
#### Running Locally
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/).
### [Mypy](https://github.com/python/mypy)
Mypy is a static type checker for Python. It sure has a lot to say sometimes, but we recommend you take its advice when possible. Mypy also uses the `pyproject.toml` as its config file and runs whenever code is pushed or pulled into the project.
#### Running Locally
- **(First time only)** Run the following:
- `mkdir -p .mypy_cache`
- `mypy --install-types --non-interactive`
- You can now check code by running `mypy --config-file pyproject.toml .` in the repository root. _(Don't forget the "." at the end!)_
Mypy is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=matangover.mypy), PyCharm [plugin](https://plugins.jetbrains.com/plugin/11086-mypy), and [more](https://plugins.jetbrains.com/plugin/11086-mypy).
### PyTest
- Run all tests by running `pytest tests/` in the repository root.
## Code Style
See the [Style Guide](/STYLE.md)
### Modules & Implementations
- **Do not** modify legacy library code in the `src/core/library/json/` directory
- Avoid direct calls to `os`
- Use `Pathlib` library instead of `os.path`
- Use `platform.system()` instead of `os.name` and `sys.platform`
- Don't prepend local imports with `tagstudio`, stick to `src`
- Use the `logger` system instead of `print` statements
- Avoid nested f-strings
- Use HTML-like tags inside Qt widgets over stylesheets where possible
### Commit and Pull Request Style
- 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.
> [!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.
> [!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 `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
## Translation Guidelines
Translations are performed on the TagStudio [Weblate project](https://hosted.weblate.org/projects/tagstudio/).
_Translation guidelines coming soon._

324
README.md
View File

@@ -1,73 +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!
> [!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.
<p align="center">
<img width="80%" src="screenshot.jpg">
<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)
- [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.
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
- 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`.
### 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 and other docs.
> 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. 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.
> [!NOTE]
> 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.
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.
#### Optional Arguments
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 to pass to the program.
<!-- 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!
> `--open <path>` / `-o <path>`
> Path to a TagStudio Library folder to open on start.
### Third-Party Dependencies
> `--config-file <path>` / `-c <path>`
> Path to a TagStudio Library folder to open on start.
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
@@ -77,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
@@ -92,59 +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 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.
> [!WARNING]
> There is currently no method to view all tags that youve created in your library. This is a top priority for future releases.
### Relinking Renamed/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.
> 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]
> 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.
> [!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.
#### 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.
@@ -159,140 +205,28 @@ Import JSON sidecar data generated by [gallery-dl](https://github.com/mikf/galle
> [!CAUTION]
> This feature is not supported or documented in any official capacity whatsoever. It will likely be rolled-in to a larger and more generalized sidecar importing feature in the future.
## Creating a Development Environment
## Launching/Building From Source
If you're interested in contributing to TagStudio or just wish to poke around the live codebase, here are instructions for setting up the Python project.
### Prerequisites
- Python 3.12
### Creating a Python Virtual Environment
> [!NOTE]
> Depending on your system, Python may be called `python`, `py`, `python3`, or `py3`. These instructions use the alias `python3`. You can check to see which alias you system uses and if it's for the correct Python version by typing `python3 --version` (or whichever alias) into your terminal.
_Skip these steps if launching from the .sh script on Linux/macOS._
1. In the root repository directory, create a python virtual environment:
`python3 -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`
3. Install the required packages:
- required to run the app: `pip install -r requirements.txt`
- required to develop: `pip install -r requirements-dev.txt`
To run all the tests use `python -m pytest tests/` from the `tagstudio` folder.
_Learn more about setting up a virtual environment [here](https://docs.python.org/3/tutorial/venv.html)._
### Launching Development Environment
- **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** (TagStudio.sh)
- Use the provided `flake.nix` file to create and enter a working environment by running `nix develop`. Then, run the `TagStudio.sh` script.
- **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`.
See instructions in the "[Creating Development Environment](/CONTRIBUTING.md/#creating-a-development-environment)" section from the [contribution documentation](/CONTRIBUTING.md).
## FAQ
### What State Is the Project Currently In?
As of writing (Alpha v9.2.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?
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.
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.
#### Priority Features
### Features That Will NOT Be Added
- 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"
- 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.
#### Future Features
### Why Is this Already Version 9?
- 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...)._
### Can I Contribute?
**Yes!!** I recommend taking a look at the [Priority Features](#priority-features), [Future Features](#future-features), and [Features I Won't Pull](#features-i-likely-wont-addpull) lists, as well as the project issues to see whats currently being worked on. Please do not submit pull requests with new feature additions without opening up an issue with a feature request first.
Code formatting is automatically checked via [Ruff](https://docs.astral.sh/ruff/).
To format the code manually, install ruff via `pip install -r requirements-dev.txt` and then run `ruff format`
To format the code automatically before each commit, there's a configured action available for `pre-commit` hook. Install it by running `pre-commit install`. The hook will be executed each time on running `git commit`.
More structured documentation on contribution requirements is on its way, but for now:
- Use `pathlib` in favor of `os.path`
- Try to make new UI additions match the existing style of the application
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.

84
STYLE.md Normal file
View File

@@ -0,0 +1,84 @@
# 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.
- This should include making methods private by default (e.g. `__method()`)
- Methods should only be protected (e.g. `_method()`) or public (e.g. `method()`) when needed and warranted
- Keep a maximum column width of no more than **100** characters.
- Code comments should be used to help describe sections of code that can't speak for themselves.
- Use [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings for any classes and functions you add.
- If you're modifying an existing function that does _not_ have docstrings, you don't _have_ to add docstrings to it... but it would be pretty cool if you did ;)
- Imports should be ordered alphabetically.
- Lists of values should be ordered using their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order).
- Some files have their methods ordered alphabetically as well (i.e. [`thumb_renderer`](https://github.com/TagStudioDev/TagStudio/blob/main/src/tagstudio/qt/widgets/thumb_renderer.py)). If you're working in a file and notice this, please try and keep to the pattern.
- When writing text for window titles or form titles, use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" capitalization. Your IDE may have a command to format this for you automatically, although some may incorrectly capitalize short prepositions. In a pinch you can use a website such as [capitalizemytitle.com](https://capitalizemytitle.com/) to check.
- If it wasn't mentioned above, then stick to [**PEP-8**](https://peps.python.org/pep-0008/)!
## QT
As of writing this section, the QT part of the code base is quite unstructured and the View and Controller parts are completely intermixed[^1]. This makes maintenance, fixes and general understanding of the code base quite challenging, because the interesting parts you are looking for are entangled in a bunch of repetitive UI setup code. To address this we are aiming to more strictly separate the view and controller aspects of the QT frontend.
The general structure of the QT code base should look like this:
```
qt
├── controller
│ ├── widgets
│ │ └── preview_panel_controller.py
│ └── main_window_controller.py
├── view
│ ├── widgets
│ │ └── preview_panel_view.py
│ └── main_window_view.py
├── ts_qt.py
└── mixed.py
```
In this structure there are the `view` and `controller` sub-directories. They have the exact same structure and for every `<component>_view.py` there is a `<component>_controller.py` at the same location in the other subdirectory and vice versa.
Typically the classes should look like this:
```py
# my_cool_widget_view.py
class MyCoolWidgetView(QWidget):
def __init__(self):
super().__init__()
self.__button = QPushButton()
self.__color_dropdown = QComboBox()
# ...
self.__connect_callbacks()
def __connect_callbacks(self):
self.__button.clicked.connect(self._button_click_callback)
self.__color_dropdown.currentIndexChanged.connect(
lambda idx: self._color_dropdown_callback(self.__color_dropdown.itemData(idx))
)
def _button_click_callback(self):
raise NotImplementedError()
```
```py
# my_cool_widget_controller.py
class MyCoolWidget(MyCoolWidgetView):
def __init__(self):
super().__init__()
def _button_click_callback(self):
print("Button was clicked!")
def _color_dropdown_callback(self, color: Color):
print(f"The selected color is now: {color}")
```
Observe the following key aspects of this example:
- The Controller is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code
- The UI elements are in private variables
- This enforces that the controller shouldn't directly access UI elements
- Instead the view should provide a protected API (e.g. `_get_color()`) for things like setting/getting the value of a dropdown, etc.
- Instead of `_get_color()` there could also be a `_color` method marked with `@property`
- The callback methods are already defined as protected methods with NotImplementedErrors
- Defines the interface the callbacks
- Enforces that UI events be handled
> [!TIP]
> A good (non-exhaustive) rule of thumb is: If it requires a non-UI import, then it doesn't belong in the `*_view.py` file.
[^1]: For an explanation of the Model-View-Controller (MVC) Model, checkout this article: [MVC Framework Introduction](https://www.geeksforgeeks.org/mvc-framework-introduction/).

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"
]
}
]
}

View File

@@ -1,189 +0,0 @@
# TagStudio Documentation (Alpha v9.1.0)
## _A User-Focused Document Management System_
> [!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.
## Contents
- [Library](#library)
- [Fields](#fields)
- [Entries](#entries)
- [Tags](#tags)
- [Retrieving Entries](#retrieving-entries-based-on-tag-cluster)
- [Missing File Resolution](#missing-file-resolution)
## Library
The Library is how TagStudio represents your chosen directory. In this Library or Vault system, all files within this directory are represented by Entries, which then contain metadata Fields. All TagStudio data for a Library is stored within a `.TagStudio` folder at the root of the Library's directory. Internal Library objects include:
- Fields (v9+)
- Text Line (Title, Author, Artist, URL)
- Text Box (Description, Notes)
- Tag Box (Tags, Content Tags, Meta Tags)
- Datetime (Date Created, Date Modified, Date Taken) [WIP]
- Collation (Collation) [WIP]
- `name: str`: Collation Name
- `page: int`: Page #
- Checkbox (Archive, Favorite) [WIP]
- Drop Down (Group of Tags to select one from) [WIP]
- Entries (v1+)
- Tags (v7+)
- Macros (v9/10+)
## Fields
Fields are the building blocks of metadata stored in Entires. Fields have several base types for representing different types of information, including:
- `text_line`
- A string of text, displayed as a single line.
- Useful for Titles, Authors, URLs, etc.
- `text_box`
- A long string of text displayed as a box of text.
- Useful for descriptions, notes, etc.
- `datetime` [WIP]
- A date and time value.
- `tag_box`
- A box of tags added by the user.
- Multiple tag boxes can be used to separate classifications of tags, ex. 'Content Tags' and 'Meta Tags'.
- `checkbox` [WIP]
- A two-state checkbox.
- Can be associated with a tag for quick organization.
- `collation` [WIP]
- A collation is a collection of files that are intended to be displayed and retrieved together. Examples may include pages of a book or document that are spread out across several individual files. If you're intention is to associate files across multiple 'collations', use Tags instead!
## Entries
Entries are the representations of your files within the Library. They consist of a reference to the file on your drive, as well as the metadata associated with it.
### Entry Object Structure (v9):
- `id`:
- ID for the Entry.
- Int, Unique, Required
- Used for internal processing
- `filename`:
- The filename with extension of the referenced media file.
- String, Required
- `path`:
- The folder path in which the media file is located in.
- String, Required, OS Agnostic
- `fields`:
- A list of Field ID/Value dicts.
- List of dicts, Optional
NOTE: _Entries currently have several unused optional fields intended for later features._
## Tags
**Tags** are small data objects that represent an attribute of something. A person, place, thing, concept, you name it! Tags in TagStudio allow for more sophisticated Entry organization and searching thanks to their ability to contain alternate names and spellings via `aliases`, relational organization thanks to inherent `subtags`, and more! Tags can be as simple or as powerful as you want to make them, and TagStudio aims to provide as much power to you as possible.
### Tag Object Structure (v9):
- `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
- Each word analyzed individually
- Used for display, searching, and storing
- `shorthand`:
- The shorthand name for the Tag.
- String, Optional
- Doesn't have to be unique
- Entire string analyzed as-is
- Used for display and searching
- `aliases`:
- Alternate names for the Tag.
- List of Strings, Optional
- Recommended to be unique to this Tag
- Entire string analyzed as-is
- Used for searching
- `subtags`:
- Other Tags that make up properties of this Tag.
- List of Strings, Optional
- Used for display (first subtag only) and searching.
- `color`:
- A hex code value for customizing the Tag's display color
- String, Optional
- Used for display
### Tag Examples:
#### League of Legends
- `name`: "League of Legends"
- `shorthand`: "LoL"
- `aliases`: ["League"]
- `subtags`: ["Game", "Fantasy"]
#### Arcane
- `name`: "Arcane"
- `shorthand`: ""
- `aliases`: []
- `subtags`: ["League of Legends", "Cartoon"]
#### Jinx (LoL)
- `name`: "Jinx Piltover"
- `shorthand`: "Jinx"
- `aliases`: ["Jinxy", "Jinxy Poo"]
- `subtags`: ["League of Legends", "Arcane", "Character"]
#### Zander (Arcane)
- `name`: "Zander Zanderson"
- `shorthand`: "Zander"
- `aliases`: []
- `subtags`: ["Arcane", "Character"]
#### Mr. Legend (LoL)
- `name`: "Mr. Legend"
- `shorthand`: ""
- `aliases`: []
- `subtags`: ["League of Legends", "Character"]
### Query "League of Legends" returns results for:
- League of Legends [because of "League of Legend"'s name]
- Arcane [because of "Arcane"'s subtag]
- Jinx (LoL) [because of "Jinx Piltover"'s subtag]
- Mr. Legend (LoL) [because of "Mr. Legned (LoL)'s subtag"]
- Zander (Arcane) [because of "Zander Zanderson"'s subtag ("Arcane")'s subtag]
### Query "LoL" returns results for:
- League of Legends [because of "League of Legend"'s shorthand]
- LoL [because of "League of Legend"'s shorthand]
- Arcane [because of "Arcane"'s subtag]
- Jinx (LoL) [because of "Jinx Piltover"'s subtag]
- Mr. Legend (LoL) [because of "Mr. Legned (LoL)'s subtag"]
- Zander (Arcane) [because of "Zander Zanderson"'s subtag ("Arcane")'s subtag]
### Query "Arcane" returns results for:
- Arcane [because of "Arcane"'s name]
- Jinx (LoL) [because of "Jinx Piltover"'s subtag "Arcane"]
- Zander (Arcane) [because of "Zander Zanderson"'s subtag]
## Retrieving Entries based on Tag 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.
## Missing File Resolution
1. Refresh missing file list (`refresh missing`) (Automatically run if library has few entries)
2. Fix missing files screen (`fix missing`)
### Fix Missing Files Screen
0. **Match Search** (Determines if entries can be fixed) Scans for filename in library directory
1. **Quick Fixes** (one match found, no existing entry)
2. **Match Selection** (multiple matches found)
3. **Merge Conflict Resolution** (match has existing entry)
Any remaining missing files can be listed, but they probably really are missing at this point. You can update the path and filename to point to new files if you know where they should actually be pointing to.

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

BIN
docs/assets/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View File

Before

Width:  |  Height:  |  Size: 677 KiB

After

Width:  |  Height:  |  Size: 677 KiB

10
docs/assets/icon_mono.svg Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 739 739" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.986683,0,0,0.986683,-136.081,-136.081)">
<path d="M535.939,863.161C515.931,843.153 203.505,529.713 183.497,509.705C169.086,495.294 161.469,476.645 160.649,457.754C160.046,443.845 138.078,230.102 137.923,217.139C137.681,196.785 145.323,176.356 160.839,160.839C177.115,144.564 198.795,136.951 220.125,138.016C232.439,138.63 447.036,159.52 461.817,160.931C479.3,162.6 496.329,170.12 509.705,183.497C523.113,196.904 849.753,522.531 863.161,535.939C893.716,566.494 893.716,616.108 863.161,646.663L646.663,863.161C616.108,893.716 566.494,893.716 535.939,863.161ZM321.355,223.613C296.045,198.303 254.947,198.303 229.636,223.613C204.326,248.924 204.326,290.022 229.636,315.332C254.947,340.643 296.045,340.643 321.355,315.332C346.666,290.022 346.666,248.924 321.355,223.613ZM362.109,606.786C409.476,654.152 424.103,598.401 454.027,606.786C468.584,610.865 453.72,642.505 443.028,673.551C425.086,725.641 484.094,757.817 516.601,720.743C545.603,687.667 503.579,655.692 520.581,632.527C537.795,609.074 563.542,633.319 565.542,665.527C568.921,719.955 535.825,735.585 543.999,774.591C553.59,820.348 624.181,827.565 638,774.591C647.736,737.269 603.102,705.31 628.352,644.476C636.209,625.545 662.786,619.154 669.759,644.476C673.976,659.791 660.264,670.152 666.759,693.55C674.41,721.114 725.088,732.96 740.374,693.55C746.873,676.793 734.853,651.273 731.597,640.406C714.283,582.611 826.807,582.426 762.374,517.789C703.034,458.263 493.6,249.017 493.6,249.017C479.164,234.58 457.464,234.58 443.028,249.017L249.017,443.028C234.58,457.464 234.58,479.164 249.017,493.6C249.017,493.6 340.149,584.825 362.109,606.786Z" style="fill:white;"/>
</g>
<g transform="matrix(0.986683,0,0,0.986683,-136.081,-136.081)">
<path d="M733.962,560.164C740.987,553.139 740.987,541.733 733.962,534.708L482.173,282.92C475.148,275.895 463.742,275.895 456.717,282.92L431.261,308.376C424.237,315.4 424.237,326.807 431.261,333.831L683.05,585.62C690.075,592.645 701.481,592.645 708.506,585.62L733.962,560.164ZM439.639,559.207C446.664,552.182 446.664,540.776 439.639,533.751L335.491,429.602C328.466,422.578 317.059,422.578 310.035,429.602L284.579,455.058C277.554,462.083 277.554,473.489 284.579,480.514L388.728,584.663C395.752,591.688 407.159,591.688 414.184,584.663L439.639,559.207ZM584.306,556.624C591.331,549.599 591.331,538.192 584.306,531.168L409.115,355.977C402.091,348.953 390.684,348.953 383.66,355.977L358.204,381.433C351.179,388.458 351.179,399.864 358.204,406.889L533.394,582.079C540.419,589.104 551.825,589.104 558.85,582.079L584.306,556.624ZM298.425,246.543C311.081,259.198 311.081,279.747 298.425,292.402C285.77,305.058 265.221,305.058 252.566,292.402C239.911,279.747 239.911,259.198 252.566,246.543C265.221,233.888 285.77,233.888 298.425,246.543Z" style="fill:white;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
docs/assets/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
docs/assets/tag_bubbles.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

192
docs/develop.md Normal file
View File

@@ -0,0 +1,192 @@
---
icon: material/code-braces
---
# :material-code-braces: 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)!
## Installing 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.
---
## Cloning from GitHub
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
```
## 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".

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

@@ -0,0 +1,58 @@
---
icon: material/movie-open-cog
---
# :material-movie-open-cog: 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)

110
docs/index.md Normal file
View File

@@ -0,0 +1,110 @@
---
title: Home
hide:
- toc
---
#
<link rel="stylesheet" href="stylesheets/home.css">
<figure markdown="span">
![TagStudio](./assets/ts-9-3_logo_text.png){ width=80% }<h2>A User-Focused Photo & File Management System</h2>
</figure>
<br>
<figure markdown="span">
![TagStudio screenshot](./assets/screenshot.png){ width=80% }
<figcaption>TagStudio Alpha v9.5.0 running on macOS Sequoia.</figcaption>
</figure>
<div class="grid" markdown>
![TagStudio screenshot](./assets/tag_bubbles.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.
</div>
<figure markdown="span">
[:material-download: Download Latest Release](https://github.com/TagStudioDev/TagStudio/releases){ .md-button .md-button--primary }
</figure>
## :material-star: Core Features
<div class="grid cards" markdown>
- :material-file-multiple:{ .lg .middle } **[All Files](./library/entry.md) Welcome**
***
TagStudio works with photos, videos, music, documents, and more! **All file types** are recognized by TagStudio, with most common ones having built-in preview support.
[:material-arrow-right: See Full Preview Support](./library/index.md#preview-support)
- :material-tag-text:{ .lg .middle } **Create [Tags](./library/tag.md) Your Way**
***
- :material-format-font: No character restrictions
- :material-form-textbox: Add aliases/alternate names
- :material-palette: Customize colors and styles
- :material-tag-multiple: Tags can be tagged with other tags!
- :material-star-four-points: And more!
- :material-magnify:{ .lg .middle } **Powerful [Search](./library/library_search.md)**
***
- Full [Boolean operator](./library/library_search.md) support
- Filenames, paths, and extensions with [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) syntax
- General media types (e.g. "Photo", "Video", "Document")
- Special searches (e.g. "Untagged")
- "[Smartcase](./library/library_search.md#case-sensitivity)" case sensitivity
- :material-text-box:{ .lg .middle } **Text and Date [Fields](./library/field.md)**
***
Along with tags, add custom metadata fields such as text and dates to your files!
This is useful for adding notes and descriptions, titling files, and keeping track of extra dates and times.
</div>
## :material-toolbox: Built Different
<div class="grid cards" markdown>
- :material-scale-balance:{ .lg .middle } **Open Source**
***
TagStudio is licensed under the GPL-3.0 license with the source code and executable releases available on [GitHub](https://github.com/TagStudioDev/TagStudio).
[:material-arrow-right: View License](https://github.com/TagStudioDev/TagStudio/blob/main/LICENSE)
[:material-arrow-right: Roadmap to MIT Core Library License](./updates/roadmap.md#core-library-api)
- :material-database:{ .lg .middle } **Central Save File**
***
Apposed to filling your drives with [sidecar files](https://en.wikipedia.org/wiki/Sidecar_file), TagStudio uses a project-like [library](./library/index.md) system that stores your tags and metadata inside a single save file per-library.
[:material-arrow-right: Learn About the Format](./library/index.md)
</div>
---
## :material-layers-triple: More Than an Application
TagStudio aims to create an **open** and **robust** format for file tagging that isn't burdened by the limitations of traditional tagging and file metadata systems. **TagStudio** is the first proof-of-concept implementation of this system.
<div class="grid cards" markdown>
- :material-map-check:{ .lg .middle } See the [**Roadmap**](./updates/roadmap.md) for future features and updates
</div>

224
docs/install.md Normal file
View File

@@ -0,0 +1,224 @@
---
icon: material/download
---
# :material-download: 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
<!-- prettier-ignore -->
!!! tip
You can check to see if any of these dependencies are correctly located by launching TagStudio and going to "About TagStudio" in the menu bar.
### FFmpeg/FFprobe
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.
### ripgrep
A recommended tool to improve the performance of directory scanning is [`ripgrep`](https://github.com/BurntSushi/ripgrep), a Rust-based directory walker that natively integrates with our [`.ts_ignore`](./utilities/ignore.md) (`.gitignore`-style) pattern matching system for excluding files and directories. Ripgrep is already pre-installed on some Linux distributions and also available from several package managers.

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

@@ -0,0 +1,54 @@
---
icon: material/file
---
# :material-file: Entries
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 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`)

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

@@ -0,0 +1,27 @@
---
icon: material/text-box
---
# :material-text-box: 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.

155
docs/library/index.md Normal file
View File

@@ -0,0 +1,155 @@
# :material-database: Library
<!-- prettier-ignore -->
!!! info
This page is a work in progress and needs to be updated with additional information.
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_. Global tags along with other library structure updates are planned for future releases on the [roadmap](../updates/roadmap.md#library).
---
## Preview Support
TagStudio offers built-in preview and thumbnail support for a wide variety of file types. Files that don't have explicit support can still be added to your library like normal, they will just show a default icon for thumbnails and previews. TagStudio also references the file's [MIME](https://en.wikipedia.org/wiki/Media_type) type in an attempt to render previews for file types that haven't gained explicit support yet.
### :material-image-outline: Images
Images will generate thumbnails the first time they are viewed or since the last time they were modified. Thumbnails are used in the grid view, but not in the Preview Panel. Animated images will play in the Preview Panel.
| Filetype | Extensions | Animation |
| -------------------- | -------------------------------------------------- | :---------------------------------: |
| Animated PNG | `.apng` | :material-check-circle:{.lg .green} |
| Apple Icon Image | `.icns` | :material-minus-circle:{.lg .gray} |
| AVIF | `.avif` | :material-minus-circle:{.lg .gray} |
| Bitmap | `.bmp` | :material-minus-circle:{.lg .gray} |
| GIF | `.gif` | :material-check-circle:{.lg .green} |
| HEIF | `.heif`, `.heic` | :material-minus-circle:{.lg .gray} |
| JPEG | `.jpeg`, `.jpg`, `.jfif`, `.jif`, `.jpg_large`[^1] | :material-minus-circle:{.lg .gray} |
| JPEG-XL | `.jxl` | :material-close-circle:{.lg .red} |
| OpenEXR | `.exr` | :material-minus-circle:{.lg .gray} |
| OpenRaster | `.ora` | :material-minus-circle:{.lg .gray} |
| PNG | `.png` | :material-minus-circle:{.lg .gray} |
| SVG | `.svg` | :material-minus-circle:{.lg .gray} |
| TIFF | `.tiff`, `.tif` | :material-minus-circle:{.lg .gray} |
| Valve Texture Format | `.vtf` | :material-close-circle:{.lg .red} |
| WebP | `.webp` | :material-check-circle:{.lg .green} |
| Windows Icon | `.ico` | :material-minus-circle:{.lg .gray} |
#### :material-image-outline: RAW Images
| Filetype | Extensions |
| -------------------------------- | ---------------------- |
| Camera Image File Format (Canon) | `.crw`, `.cr2`, `.cr3` |
| Digital Negative | `.dng` |
| Fuji RAW | `.raf` |
| Nikon RAW | `.nef`, `.nrw` |
| Olympus RAW | `.orf` |
| Panasonic RAW | `.raw`, `.rw2` |
| Sony RAW | `.arw` |
### :material-movie-open: Videos
Video thumbnails will default to the closest viable frame from the middle of the video. Both thumbnail generation and video playback in the Preview Panel requires [FFmpeg](../install.md#third-party-dependencies) installed on your system.
| Filetype | Extensions | Dependencies |
| --------------------- | ----------------------- | :----------: |
| 3GP | `.3gp` | FFmpeg |
| AVI | `.avi` | FFmpeg |
| AVIF | `.avif` | FFmpeg |
| FLV | `.flv` | FFmpeg |
| HEVC | `.hevc` | FFmpeg |
| Matroska | `.mkv` | FFmpeg |
| MP4 | `.mp4` , `.m4p` | FFmpeg |
| MPEG Transport Stream | `.ts` | FFmpeg |
| QuickTime | `.mov`, `.movie`, `.qt` | FFmpeg |
| WebM | `.webm` | FFmpeg |
| WMV | `.wmv` | FFmpeg |
### :material-sine-wave: Audio
Audio thumbnails will default to embedded cover art (if any) andfallback to generated waveform thumbnails. Audio file playback is supported in the Preview Panel if you have [FFmpeg](../install.md#third-party-dependencies) installed on your system. Audio waveforms are currently not cached.
| Filetype | Extensions | Dependencies |
| ------------------- | ------------------------ | :----------: |
| AAC | `.aac`, `.m4a` | FFmpeg |
| AIFF | `.aiff`, `.aif`, `.aifc` | FFmpeg |
| Apple Lossless[^2] | `.alac`, `.aac` | FFmpeg |
| FLAC | `.flac` | FFmpeg |
| MP3 | `.mp3`, | FFmpeg |
| Ogg | `.ogg` | FFmpeg |
| WAVE | `.wav`, `.wave` | FFmpeg |
| Windows Media Audio | `.wma` | FFmpeg |
### :material-file-chart: Documents
Preview support for office documents or well-known project file formats varies by the format and whether or not embedded thumbnails are available to be read from. OpenDocument-based files are typically supported.
| Filetype | Extensions | Preview Type |
| ----------------------------- | --------------------- | -------------------------------------------------------------------------- |
| Blender | `.blend`, `.blend<#>` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Keynote (Apple iWork) | `.key` | Embedded thumbnail |
| Krita[^3] | `.kra`, `.krz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| MuseScore | `.mscz` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
| Numbers (Apple iWork) | `.numbers` | Embedded thumbnail |
| OpenDocument Presentation | `.odp`, `.fodp` | Embedded thumbnail |
| OpenDocument Spreadsheet | `.ods`, `.fods` | Embedded thumbnail |
| OpenDocument Text | `.odt`, `.fodt` | Embedded thumbnail |
| Pages (Apple iWork) | `.pages` | Embedded thumbnail |
| PDF | `.pdf` | First page render |
| Photoshop | `.psd` | Flattened image render |
| PowerPoint (Microsoft Office) | `.pptx`, `.ppt` | Embedded thumbnail :material-alert-circle:{ title="If available in file" } |
### 3D Models
<!-- prettier-ignore -->
!!! failure "3D Model Support"
TagStudio does not currently support previews for 3D model files *(outside of Blender project embedded thumbnails)*. This is on our [roadmap](../updates/roadmap.md#uiux) for future features.
### :material-format-font: Fonts
Font thumbnails will use a "Aa" example preview of the font, with a full alphanumeric of the font available in the Preview Panel.
| Filetype | Extensions |
| -------------------- | ----------------- |
| OpenType Font | `.otf`, `.otc` |
| TrueType Font | `.ttf`, `.ttc` |
| Web Open Font Format | `.woff`, `.woff2` |
### :material-text-box: Text
<!-- prettier-ignore -->
!!! info "Plain Text Support"
TagStudio supports the *vast* majority of files considered to be "[plain text](https://en.wikipedia.org/wiki/Plain_text)". If an extension or format is not listed here, odds are it's still supported anyway.
Text files render the first 256 bytes of text information to an image preview for thumbnails and the Preview Panel. Improved thumbnails, full scrollable text, and syntax highlighting are on our [roadmap](../updates/roadmap.md#uiux) for future features.
| Filetype | Extensions | Syntax Highlighting |
| ---------- | --------------------------------------------- | :--------------------------------: |
| CSV | `.csv` | :material-close-circle:{.lg .red} |
| HTML | `.html`, `.htm`, `.xhtml`, `.shtml`, `.dhtml` | :material-close-circle:{.lg .red} |
| JSON | `.json`, `.jsonc`, `.json5` | :material-close-circle:{.lg .red} |
| Markdown | `.md`, `.markdown`, `.mkd`, `.rmd` | :material-close-circle:{.lg .red} |
| Plain Text | `.txt`, `.text` | :material-minus-circle:{.lg .gray} |
| TOML | `.toml` | :material-close-circle:{.lg .red} |
| XML | `.xml`, `.xul` | :material-close-circle:{.lg .red} |
| YAML | `.yaml`, `.yml` | :material-close-circle:{.lg .red} |
### :material-file: Other
| Filetype | Extensions | Preview Type |
| -------- | ---------- | -------------------- |
| EPUB | `.epub` | Embedded ebook cover |
<!-- prettier-ignore-start -->
[^1]:
The `.jpg_large` extension is unofficial and instead the byproduct of how [Google Chrome used to download images from Twitter](https://fileinfo.com/extension/jpg_large). Since this mangled extension is still in circulation, TagStudio supports it.
[^2]:
Apple Lossless traditionally uses `.m4a` and `.caf` containers, but may unofficially use the `.alac` extension. The `.m4a` container is also used for separate compressed audio codecs.
[^3]:
Krita also supports saving projects as OpenRaster `.ora` files. Support for these is listed in the "[Images](#images)" section.
<!-- prettier-ignore-end -->

View File

@@ -0,0 +1,125 @@
---
icon: material/magnify
---
# :material-magnify: 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.

135
docs/library/tag.md Normal file
View File

@@ -0,0 +1,135 @@
---
icon: material/tag-text
---
# :material-tag-text: 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_**
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" 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_**
## 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_**
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 @@
---
icon: material/shape-plus
---
# :material-shape-plus: 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.

75
docs/library/tag_color.md Normal file
View File

@@ -0,0 +1,75 @@
---
icon: material/palette
---
# :material-palette: 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)

155
docs/stylesheets/extra.css Normal file
View File

@@ -0,0 +1,155 @@
/* Dark Theme */
[data-md-color-scheme="slate"] {
--md-default-bg-color: #060617;
--md-default-fg-color: #eae1ff;
--md-default-fg-color--light: #b898ff;
--md-code-fg-color: #eae1ffcc;
--md-code-hl-string-color: rgb(92, 255, 228);
--md-code-hl-keyword-color: rgb(61, 155, 255);
--md-code-hl-constant-color: rgb(205, 78, 255);
--md-footer-bg-color--dark: #03030c;
--md-code-bg-color: #090a26;
}
/* Light Theme */
[data-md-color-scheme="default"] {
--md-default-fg-color--light: #090a26;
}
.md-header {
background: linear-gradient(
60deg,
rgb(205, 78, 255) 10%,
rgb(116, 123, 255) 50%,
rgb(72, 145, 255) 75%,
rgb(98, 242, 255) 100%
);
}
/* Mobile Nav Header */
.md-nav__source {
background: linear-gradient(
60deg,
rgb(205, 78, 255) 0%,
rgb(116, 123, 255) 100%
);
border-style: solid;
border-width: 0 0 2px 0;
border-color: #ffffff33;
}
th,
td {
padding: 0.5em 1em 0.5em 1em !important;
}
.md-header {
border-style: solid;
border-width: 0 0 2px 0;
border-color: #ffffff33;
}
.md-header__title {
font-family: "Bai Jamjuree", Roboto, sans-serif;
font-weight: 500 !important;
font-size: 1.1rem;
letter-spacing: -0.05rem !important;
}
.md-nav__title,
h1,
h2,
.md-nav__item--section > .md-nav__link {
font-family: "Bai Jamjuree", Roboto, sans-serif;
font-weight: 500 !important;
font-size: 0.8rem;
letter-spacing: -0.05rem !important;
}
/* Add padding to stop text from cutting off early due to negative letter spacing */
.md-nav__item--section > .md-nav__link > .md-ellipsis {
padding-right: 0.5rem;
}
.md-header__title .md-header__topic .md-ellipsis {
padding-right: 0.5rem;
}
.md-header__button.md-logo {
padding-right: 0;
padding-bottom: 0.3rem;
margin-right: -0.8rem;
}
figcaption {
margin-top: 0 !important;
}
.md-search__form {
height: 1.5rem;
background-color: #00004444;
border-radius: 0.3rem !important;
border-style: solid;
border-width: 2px 1px 0 1px;
border-color: #00004422;
padding-bottom: 2px;
}
.md-search__form > .md-icon {
margin-top: -0.2rem;
}
h1 > .twemoji {
margin-top: 0.14rem;
}
h2 > .twemoji {
margin-top: 0.08rem;
}
/* Matches the palette used by mkdocs-material */
.priority-high {
color: #f1185a;
}
.priority-med {
color: #7c4eff;
}
.priority-low {
color: #28afff;
}
.red {
color: rgb(245, 0, 87);
}
.amber {
color: rgb(255, 145, 0);
}
.green {
color: rgb(0, 191, 165);
}
.gray {
color: rgb(158, 158, 158);
}
@media screen and (max-width: 76.234375em) {
/* Always show image logo on mobile */
.md-header__button.md-logo {
display: block;
}
label[for="__drawer"].md-header__button.md-icon {
order: -1;
}
.md-header {
background: linear-gradient(
60deg,
rgb(205, 78, 255) 10%,
rgb(116, 123, 255) 70%,
rgb(72, 179, 255) 100%
);
}
}

14
docs/stylesheets/home.css Normal file
View File

@@ -0,0 +1,14 @@
h2 {
margin: 1rem 0 0 0 !important;
}
.md-content .md-typeset h1 {
/* display: none; */
font-size: 0;
}
.grid > p {
align-content: center;
font-size: 1rem !important;
line-height: 1.25rem;
}

View File

@@ -0,0 +1,6 @@
---
title: Changelog
icon: material/list-status
---
--8<-- "CHANGELOG.md"

272
docs/updates/roadmap.md Normal file
View File

@@ -0,0 +1,272 @@
---
icon: material/map-check
---
# :material-map-check: Roadmap
This page outlines the current and planned features required for TagStudio to be considered "feature complete" (v10.0.0). Features and changes are broken up by group in order to better assess the overall state of those features. [Priority levels](#priority-levels) and [version estimates](#version-estimates) are provided in order to give a rough idea of what's planned and when it may release.
This roadmap will update as new features are planned or completed. If there's a feature you'd like to see but is not listed on this page, please check the GitHub [Issues](https://github.com/TagStudioDev/TagStudio/issues) page and submit a feature request if one does not already exist!
## Priority Levels
Planned features and changes are assigned **priority levels** to signify how important they are to the feature-complete version of TagStudio and to serve as a general guide for what should be worked on first, along with [version estimates](#version-estimates). When features are completed, their priority level icons are removed.
<!-- prettier-ignore -->
!!! info "Priority Level Icons"
- :material-chevron-triple-up:{ .priority-high title="High Priority" } **High Priority** - Core features
- :material-chevron-double-up:{ .priority-med title="Medium Priority" } **Medium Priority** - Important, but not necessary
- :material-chevron-up:{ .priority-low title="Low Priority" } **Low Priority** - Just nice to have
## Version Estimates
Features are given rough estimations for which version they will be completed in, and are listed next to their names (e.g. Feature **[v9.0.0]**). They are eventually replaced with links to the version changelog in which they were completed in, if applicable.
<!-- prettier-ignore -->
!!! tip
For a more definitive and up-to-date list of features planned for near-future updates, please reference the current GitHub [Milestones](https://github.com/TagStudioDev/TagStudio/milestones)!
---
## Core
### :material-database: SQL Library Database
An improved SQLite-based library save file format in which legacy JSON libraries are be migrated to.
Must be finalized or deemed "feature complete" before other core features are developed or finalized.
<!-- prettier-ignore -->
!!! note
See the "[Library](#library)" section for features related to the library database rather than the underlying schema.
- [x] A SQLite-based library save file format **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [ ] Cached File Properties Table :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Date Entry Added to Library :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Date File Created :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Date File Modified :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Date Photo Taken :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Media Duration :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Media Dimensions :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Word Count :material-chevron-up:{ .priority-low title="Low Priority" }
### :material-database-cog: Core Library + API
A separated, UI agnostic core library that would be used to interface with the TagStudio library format. Would host an API for communication from outside the program. This would be licensed under the more permissive [MIT](https://en.wikipedia.org/wiki/MIT_License) license to foster wider adoption compared to the TagStudio application source code.
- [ ] Core Library :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- [ ] Core Library API :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- [ ] MIT License :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
### :material-clipboard-text: Format Specification
A detailed written specification for the TagStudio tag and/or library format. Intended for used by third-parties to build alternative cores or protocols that can remain interoperable.
- [ ] Format Specification Established :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
---
## Application
### :material-button-cursor: UI/UX
- [x] Library Grid View
- [ ] Explore Filesystem in Grid View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Infinite Scrolling (No Pagination) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Library List View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Explore Filesystem in List View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Lightbox View :material-chevron-triple-up:{ .priority-high title="High Priority" }
- Similar to List View in concept, but displays one large preview that can cycle back/forth between entries.
- [ ] Smaller thumbnails of immediate adjacent entries below :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [x] Translations
- [ ] Search Bar Rework :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.0]**
- [ ] Improved Tag Autocomplete :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Tags appear as widgets in search bar :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [x] Unified Media Player
- [x] Auto-Hiding Player Controls
- [x] Play/Pause
- [x] Loop
- [x] Toggle Autoplay
- [x] Volume Control
- [x] Toggle Mute
- [x] Timeline scrubber
- [ ] Fullscreen :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Fine-Tuned UI/UX :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.4]**
- [ ] 3D Model Thumbnails/Previews :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] STL File Support
- [ ] OBJ File Support
- [ ] Plaintext Thumbnails/Previews :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [x] Basic Support
- [ ] Full File Preview :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Syntax Highlighting :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Toggleable Persistent Tagging Panel :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Top Tags
- [ ] Recent Tags
- [ ] Tag Search
- [ ] Pinned Tags
- [ ] New Tabbed Tag Building UI to Support New Tag Features :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Custom Thumbnail Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Media Duration Labels :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Word/Line Count Labels :material-chevron-up:{ .priority-low title="Low Priority" }
- [ ] Custom Tag Badges :material-chevron-up:{ .priority-low title="Low Priority" }
- Would serve as an addition/alternative to the Favorite and Archived badges.
### :material-cog: Settings
- [x] Application Settings
- [x] Stored in System User Folder/Designated Folder
- [x] Language
- [x] Date and Time Format
- [x] Theme
- [x] Configurable Page Size
- [ ] Library Settings :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Stored in `.TagStudio` folder :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Toggle File Extension Label :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Toggle Duration Label :material-chevron-double-up:{ .priority-med title="Medium Priority" }
### :material-puzzle: Plugin Support
Some form of official plugin support for TagStudio, likely with its own API that may connect to or encapsulate part of the the [core library API](#core-library-api).
- [ ] Plugin Support :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
---
## [Library](../library/index.md)
### :material-wrench: Library Mechanics
- [x] Per-Library Tags
- [ ] Global Tags :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Multiple Root Directories :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Automatic Entry Relinking :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.0]**
- [ ] Detect Renames :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Detect Moves :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Detect Deletions :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Performant :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Background File Scanning :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.0]**
- [x] Thumbnail Caching **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [ ] Audio Waveform Caching :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.0]**
### :material-grid: [Entries](../library/entry.md)
Library representations of files or file-like objects.
- [x] File Entries **[v1.0.0]**
- [ ] Folder Entries :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] URL Entries / Bookmarks :material-chevron-up:{ .priority-low title="Low Priority" }
- [x] Fields
- [x] Text Lines
- [x] Text Boxes
- [x] Datetimes **[v9.5.4]**
- [ ] User-Titled Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Removal of Deprecated Fields :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Entry Groups :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.7.0]**
- [ ] Non-exclusive; Entries can be in multiple groups :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Ability to number entries within group :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Ability to set sorting method for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Ability to set custom thumbnail for group :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Group is treated as entry with tags and metadata :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Nested groups :material-chevron-double-up:{ .priority-med title="Medium Priority" }
### :material-tag-text: [Tags](../library/tag.md)
Discrete library objects representing [attributes](<https://en.wikipedia.org/wiki/Property_(philosophy)>). Can be applied to library [entries](../library/entry.md), or applied to other tags to build traversable relationships.
- [x] Tag Name **[v8.0.0]**
- [x] Tag Shorthand Name **[v8.0.0]**
- [x] Tag Aliases List **[v8.0.0]**
- [x] Tag Color **[v8.0.0]**
- [ ] Tag Description :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.6.0]**
- [x] Tag Colors
- [x] Built-in Color Palette **[v8.0.0]**
- [x] User-Defined Colors **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] Primary and Secondary Colors **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [ ] Tag Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Small Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Large Icons for Profiles :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.6.0]**
- [ ] Built-in Icon Packs (i.e. Boxicons) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] User-Defined Icons :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [x] [Category Property](../library/tag_categories.md) **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] Property available for tags that allow the tag and any inheriting from it to be displayed separately in the preview panel under a title
- [ ] Fine-tuned exclusion from categories :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Hidden Property :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Built-in "Archived" tag has this property by default :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Checkbox near search bar to show hidden tags in search :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Tag Relationships
- [x] [Parent Tags](../library/tag.md#parent-tags) ([Inheritance](<https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)>) Relationship) **[v9.0.0]**
- [ ] [Component Tags](../library/tag.md#component-tags) ([Composition](https://en.wikipedia.org/wiki/Object_composition) Relationship) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Multiple Language Support :material-chevron-up:{ .priority-low title="Low Priority" } **[v9.9.0]**
- [ ] Tag Overrides :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Tag Merging :material-chevron-double-up:{ .priority-med title="Medium Priority" }
### :material-magnify: [Search](../library/library_search.md)
- [x] Tag Search **[v8.0.0]**
- [x] Filename Search **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] Glob Search **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] Filetype Search **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] Search by Extension (e.g. ".jpg", ".png") **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] Search by media type (e.g. "image", "video", "document") **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [ ] Field Content Search :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [x] [Boolean Operators](../library/library_search.md) **[[v9.5.0](./changelog.md#950-2025-03-03)]**
- [x] `AND` Operator
- [x] `OR` Operator
- [x] `NOT` Operator
- [x] Parenthesis Grouping
- [x] Character Escaping
- [ ] `HAS` Operator (for [Component Tags](../library/tag.md#component-tags)) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Conditional Search :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.7.0]**
- [ ] Compare Dates :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Compare Durations :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Compare File Sizes :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Compare Dimensions :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [x] Smartcase Search [[v9.5.0](./changelog.md#950-2025-03-03)]
- [ ] Search Result Sorting
- [x] Sort by Filename **[[v9.5.2](./changelog.md#952-2025-03-31)]**
- [x] Sort by Date Entry Added to Library **[[v9.5.2](./changelog.md#952-2025-03-31)]**
- [ ] Sort by File Creation Date :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Sort by File Modification Date :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Sort by File Modification Date :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Sort by Date Taken (Photos) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] OCR Search :material-chevron-up:{ .priority-low title="Low Priority" }
- [ ] Fuzzy Search :material-chevron-up:{ .priority-low title="Low Priority" }
### :material-file-cog: [Macros](../utilities/macro.md)
- [ ] Standard, Human Readable Format (TOML) :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.4]**
- [ ] Versioning System :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.5.4]**
- [ ] Triggers **[v9.5.4]**
- [ ] On File Added :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] On Library Refresh :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] [...]
- [ ] Actions **[v9.5.4]**
- [ ] Add Tag(s) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Add Field(s) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Set Field Content :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] [...]
### :material-table-arrow-right: Sharable Data
Sharable TagStudio library data in the form of data packs (tags, colors, etc.) or other formats.
Packs are intended as an easy way to import and export specific data between libraries and users, while export-only formats are intended to be imported by other programs.
- [ ] Color Packs :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Importable
- [ ] Exportable
- [x] UUIDs + Namespaces :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [x] Standard, Human Readable Format (TOML) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Versioning System :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Tag Packs :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.9.0]**
- [ ] Importable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Exportable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] UUIDs + Namespaces :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Standard, Human Readable Format (TOML) :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Versioning System :material-chevron-double-up:{ .priority-med title="Medium Priority" }
- [ ] Macro Sharing :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v9.6.0]**
- [ ] Importable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Exportable :material-chevron-triple-up:{ .priority-high title="High Priority" }
- [ ] Sharable Entry Data :material-chevron-double-up:{ .priority-med title="Medium Priority" } **[v9.9.0]**
- _Specifics of this are yet to be determined_
- [ ] Export Library to Human Readable Format :material-chevron-triple-up:{ .priority-high title="High Priority" } **[v10.0.0]**
- Intended to give users more flexible options with their data if they wish to migrate away from TagStudio

View File

@@ -0,0 +1,91 @@
---
icon: material/database-edit
---
# :material-database-edit: Save Format Changes
This page outlines the various changes made to the TagStudio library save file format over time, sometimes referred to as the "database" or "database file".
---
## JSON
Legacy (JSON) library save format versions were tied to the release version of the program itself. This number was stored in a `version` key inside the JSON file.
### Versions 1.0.0 - 9.4.2
| 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).
---
## SQLite
Starting with TagStudio [v9.5.0-pr1](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.0-pr1), the library save format has been moved to a [SQLite](https://sqlite.org) format. Legacy JSON libraries are migrated (with the user's consent) to the new format when opening in current versions of the program. The save format versioning is now separate from the program's versioning number and stored inside a `DB_VERSION` attribute inside the SQLite file.
### Versions 1 - 5
These versions were used while developing the new SQLite file format, outside any official or recommended release. These versions **were never supported** in any official capacity and were actively warned against using for real libraries.
---
### 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.
---
### 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.
---
### 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.
---
### Version 9
| Used From | Used Until | Format | Location |
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------- | ------ | ----------------------------------------------- |
| [v9.5.2](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.2) | [v9.5.3](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.3) | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Adds the `filename` column to the `entries` table. Used for sorting entries by filename in search results.
---
### Version 100
| Used From | Used Until | Format | Location |
| ----------------------------------------------------------------------- | ---------- | ------ | ----------------------------------------------- |
| [v9.5.4](https://github.com/TagStudioDev/TagStudio/releases/tag/v9.5.4) | _Current_ | SQLite | `<Library Folder>`/.TagStudio/ts_library.sqlite |
- Introduces built-in minor versioning
- The version number divided by 100 (and floored) constitutes the **major** version. Major version indicate breaking changes that prevent libraries from being opened in TagStudio versions older than the ones they were created in.
- Values more precise than this ("ones" through "tens" columns) constitute the **minor** version. These indicate minor changes that don't prevent a newer library from being opened in an older version of TagStudio, as long as the major version is not also increased.
- Swaps `parent_id` and `child_id` values in the `tag_parents` table, which have erroneously been flipped since the first SQLite DB version.

77
docs/usage.md Normal file
View File

@@ -0,0 +1,77 @@
---
icon: material/mouse
---
# :material-mouse: 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. |

322
docs/utilities/ignore.md Normal file
View File

@@ -0,0 +1,322 @@
---
title: Ignore Files
icon: material/file-document-remove
---
# :material-file-document-remove: Ignore Files & Directories
<!-- prettier-ignore -->
!!! warning "Legacy File Extension Ignoring"
TagStudio versions prior to v9.5.4 use a different, more limited method to exclude or include file extensions from your library and subsequent searches. Opening a pre-exiting library in v9.5.4 or later will non-destructively convert this to the newer, more extensive `.ts_ignore` format.
If you're still running an older version of TagStudio in the meantime, you can access the legacy system by going to "Edit -> Manage File Extensions" in the menubar.
TagStudio offers the ability to ignore specific files and directories via a `.ts_ignore` file located inside your [library's](../library/index.md) `.TagStudio` folder. This file is designed to use very similar [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>)-style pattern matching as the [`.gitignore`](https://git-scm.com/docs/gitignore) file used by Git™[^1]. It can be edited within TagStudio or opened to edit with an external program by going to the "Edit -> Ignore Files" option in the menubar.
This file is only referenced when scanning directories for new files to add to your library, and does not apply to files that have already been added to your library.
<!-- prettier-ignore -->
!!! tip
If you just want some specific examples of how to achieve common tasks with the ignore patterns (e.g. ignoring a single file type, ignoring a specific folder) then jump to the "[Use Cases](#use-cases)" section!
<!-- prettier-ignore-start -->
=== "Example .ts_ignore file"
```toml title="My Library/.TagStudio/.ts_ignore"
# TagStudio .ts_ignore file.
# Code
__pycache__
.pytest_cache
.venv
.vs
# Projects
Minecraft/**/Metadata
Minecraft/Website
!Minecraft/Website/*.png
!Minecraft/Website/*.css
# Documents
*.doc
*.docx
*.ppt
*.pptx
*.xls
*.xlsx
```
<!-- prettier-ignore-end -->
## Pattern Format
<!-- prettier-ignore -->
!!! note ""
_This section sourced and adapted from Git's[^1] `.gitignore` [documentation](https://git-scm.com/docs/gitignore)._
### Internal Processes
When scanning your library directories, the `.ts_ignore` file is read by either the [`wcmatch`](https://facelessuser.github.io/wcmatch/glob/) library or [`ripgrep`](https://github.com/BurntSushi/ripgrep) in glob mode depending if you have the later installed on your system and it's detected by TagStudio. Ripgrep is the preferred method for scanning directories due to its improved performance and identical pattern matching to `.gitignore`. This mixture of tools may lead to slight inconsistencies if not using `ripgrep`.
---
### Comments ( `#` )
A `#` symbol at the start of a line indicates that this line is a comment, and match no items. Blank lines are used to enhance readability and also match no items.
- Can be escaped by putting a backslash ("`\`") in front of the `#` symbol.
<!-- prettier-ignore-start -->
=== "Example comment"
```toml
# This is a comment! I can say whatever I want on this line.
file_that_is_being_matched.txt
# file_that_is_NOT_being_matched.png
file_that_is_being_matched.png
```
=== "Organizing with comments"
```toml
# TagStudio .ts_ignore file.
# Minecraft Stuff
Minecraft/**/Metadata
Minecraft/Website
!Minecraft/Website/*.png
!Minecraft/Website/*.css
# Microsoft Office
*.doc
*.docx
*.ppt
*.pptx
*.xls
*.xlsx
```
=== "Escape a # symbol"
```toml
# To ensure a file named '#hashtag.jpg' is ignored:
\#hashtag.jpg
```
<!-- prettier-ignore-end -->
---
### Directories ( `/` )
The forward slash "`/`" is used as the directory separator. Separators may occur at the beginning, middle or end of the `.ts_ignore` search pattern.
- If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular `.TagStudio` library folder itself. Otherwise the pattern may also match at any level below the `.TagStudio` folder level.
- If there is a separator at the end of the pattern then the pattern will only match directories, otherwise the pattern can match both files and directories.
<!-- prettier-ignore-start -->
=== "Example folder pattern"
```toml
# Matches "frotz" and "a/frotz" if they are directories.
frotz/
```
=== "Example nested folder pattern"
```toml
# Matches "doc/frotz" but not "a/doc/frotz".
doc/frotz/
```
<!-- prettier-ignore-end -->
---
### Negation ( `!` )
A `!` prefix before a pattern negates the pattern, allowing any files matched matched by previous patterns to be un-matched.
- Any matching file excluded by a previous pattern will become included again.
- **It is not possible to re-include a file if a parent directory of that file is excluded.**
<!-- prettier-ignore-start -->
=== "Example negation"
```toml
# All .jpg files will be ignored, except any located in the 'Photos' folder.
*.jpg
Photos/!*.jpg
```
=== "Escape a ! Symbol"
```toml
# To ensure a file named '!wowee.jpg' is ignored:
\!wowee.jpg
```
<!-- prettier-ignore-end -->
---
### Wildcards
#### Single Asterisks ( `*` )
An asterisk "`*`" matches anything except a slash.
<!-- prettier-ignore-start -->
=== "File examples"
```toml
# Matches all .png files in the "Images" folder.
Images/*.png
# Matches all .png files in all folders
*.png
```
=== "Folder examples"
```toml
# Matches any files or folders directly in "Images/" but not deeper levels.
# Matches file "Images/mario.jpg"
# Matches folder "Images/Mario"
# Does not match file "Images/Mario/cat.jpg"
Images/*
```
<!-- prettier-ignore-end -->
#### Question Marks ( `?` )
The character "`?`" matches any one character except "`/`".
<!-- prettier-ignore-start -->
=== "File examples"
```toml
# Matches any .png file starting with "IMG_" and ending in any four characters.
# Matches "IMG_0001.png"
# Matches "Photos/IMG_1234.png"
# Does not match "IMG_1.png"
IMG_????.png
# Same as above, except matches any file extension instead of only .png
IMG_????.*
```
=== "Folder examples"
```toml
# Matches all files in any direct subfolder of "Photos" beginning in "20".
# Matches "Photos/2000"
# Matches "Photos/2024"
# Matches "Photos/2099"
# Does not match "Photos/1995"
Photos/20??/
```
<!-- prettier-ignore-end -->
#### Double Asterisks ( `**` )
Two consecutive asterisks ("`**`") in patterns matched against full pathname may have special meaning:
- A leading "`**`" followed by a slash means matches in all directories.
- A trailing "`/**`" matches everything inside.
- A slash followed by two consecutive asterisks then a slash ("`/**/`") matches zero or more directories.
- Other consecutive asterisks are considered regular asterisks and will match according to the previous rules.
<!-- prettier-ignore-start -->
=== "Leading **"
```toml
# Both match file or directory "foo" anywhere
**/foo
foo
# Matches file or directory "bar" anywhere that is directly under directory "foo"
**/foo/bar
```
=== "Trailing /**"
```toml
# Matches all files inside directory "abc" with infinite depth.
abc/**
```
=== "Middle /**/"
```toml
# Matches "a/b", "a/x/b", "a/x/y/b" and so on.
a/**/b
```
<!-- prettier-ignore-end -->
#### Square Brackets ( `[a-Z]` )
Character sets and ranges are specific and powerful forms of wildcards that use characters inside of brackets (`[]`) to leverage very specific matching. The range notation, e.g. `[a-zA-Z]`, can be used to match one of the characters in a range.
<!-- prettier-ignore -->
!!! tip
For more in-depth examples and explanations on how to use ranges, please reference the [`glob`](https://man7.org/linux/man-pages/man7/glob.7.html) man page.
<!-- prettier-ignore-start -->
=== "Range examples"
```toml
# Matches all files that start with "IMG_" and end in a single numeric character.
# Matches "IMG_0.jpg", "IMG_7.png"
# Does not match "IMG_10.jpg", "IMG_A.jpg"
IMG_[0-9]
# Matches all files that start with "IMG_" and end in a single alphabetic character
IMG_[a-z]
```
=== "Set examples"
```toml
# Matches all files that start with "IMG_" and in any character in the set.
# Matches "draft_a.docx", "draft_b.docx", "draft_c.docx"
# Does not match "draft_d.docx"
draft_[abc]
# Matches all files that start with "IMG_" and end in a single alphabetic character
IMG_[a-z]
```
<!-- prettier-ignore-end -->
---
## Use Cases
### Ignoring Files by Extension
<!-- prettier-ignore -->
=== "Ignore all .jpg files"
```toml
*.jpg
```
=== "Ignore all files EXCEPT .jpg files"
```toml
*
!*.jpg
```
=== "Ignore all .jpg files in specific folders"
```toml
./Photos/Worst Vacation/*.jpg
Music/Artwork Art/*.jpg
```
<!-- prettier-ignore -->
!!! tip "Ensuring Complete Extension Matches"
For some filetypes, it may be nessisary to specify different casing and alternative spellings in order to match with all possible variations of an extension in your library.
```toml title="Ignore (Most) Possible JPEG File Extensions"
# The JPEG Cinematic Universe
*.jpg
*.jpeg
*.jfif
*.jpeg_large
*.JPG
*.JPEG
*.JFIF
*.JPEG_LARGE
```
### Ignoring a Folder
<!-- prettier-ignore -->
=== "Ignore all "Cache" folders"
```toml
# Matches any folder called "Cache" no matter where it is in your library.
cache/
```
=== "Ignore a "Downloads" folder"
```toml
# "Downloads" must be a folder on the same level as your ".TagStudio" folder.
# Does not match with folders name "Downloads" elsewhere in your library
# Does not match with a file called "Downloads"
/Downloads/
```
=== "Ignore .jpg files in specific folders"
```toml
Photos/Worst Vacation/*.jpg
/Music/Artwork Art/*.jpg
```
[^1]: The term "Git" is a licensed trademark of "The Git Project", a member of the Software Freedom Conservancy. Git is released under the [GNU General Public License version 2.0](https://opensource.org/license/GPL-2.0), an open source license. TagStudio is not associated with the Git Project, only including systems based on some therein.

50
docs/utilities/macro.md Normal file
View File

@@ -0,0 +1,50 @@
---
icon: material/script-text
---
# :material-script-text: 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.

53
flake.lock generated
View File

@@ -1,16 +1,36 @@
{
"nodes": {
"nixpkgs": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1712473363,
"narHash": "sha256-TIScFAVdI2yuybMxxNjC4YZ/j++c64wwuKbpnZnGiyU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e89cf1c932006531f454de7d652163a9a5c86668",
"lastModified": 1754487366,
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
"type": "github"
},
"original": {
"owner": "nixos",
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1755615617,
"narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "20075955deac2583bb12f07151c2df830ef346b4",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
@@ -18,7 +38,24 @@
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"systems": "systems"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},

133
flake.nix
View File

@@ -1,70 +1,73 @@
{
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
description = "TagStudio";
outputs = { self, nixpkgs, }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
devShells.x86_64-linux.default = pkgs.mkShell {
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
pkgs.gcc-unwrapped
pkgs.zlib
pkgs.libglvnd
pkgs.glib
pkgs.stdenv.cc.cc
pkgs.fontconfig
pkgs.libxkbcommon
pkgs.xorg.libxcb
pkgs.freetype
pkgs.dbus
pkgs.qt6.qtwayland
pkgs.qt6.full
pkgs.qt6.qtbase
pkgs.zstd
];
buildInputs = with pkgs; [
cmake
gdb
zstd
qt6.qtbase
qt6.full
qt6.qtwayland
qtcreator
python312Packages.pip
python312Full
python312Packages.virtualenv # run virtualenv .
python312Packages.pyusb # fixes the pyusb 'No backend available' when installed directly via pip
libgcc
makeWrapper
bashInteractive
glib
libxkbcommon
freetype
binutils
dbus
coreutils
libGL
libGLU
fontconfig
xorg.libxcb
# this is for the shellhook portion
qt6.wrapQtAppsHook
makeWrapper
bashInteractive
];
# set the environment variables that Qt apps expect
shellHook = ''
export QT_QPA_PLATFORM=wayland
export LIBRARY_PATH=/usr/lib:/usr/lib64:$LIBRARY_PATH
# export LD_LIBRARY_PATH=${pkgs.stdenv.cc.cc.lib}/lib/:/run/opengl-driver/lib/
export QT_PLUGIN_PATH=${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}
bashdir=$(mktemp -d)
makeWrapper "$(type -p bash)" "$bashdir/bash" "''${qtWrapperArgs[@]}"
exec "$bashdir/bash"
'';
inputs = {
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default";
};
outputs =
inputs@{
flake-parts,
nixpkgs,
self,
...
}:
let
inherit (nixpkgs) lib;
in
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import inputs.systems;
perSystem =
{ pkgs, ... }:
let
python3 = pkgs.python313;
in
{
packages =
let
python3Packages = python3.pkgs;
pillow-jxl-plugin = python3Packages.callPackage ./nix/package/pillow-jxl-plugin.nix {
inherit (pkgs) cmake;
inherit pyexiv2;
};
pyexiv2 = python3Packages.callPackage ./nix/package/pyexiv2.nix { inherit (pkgs) exiv2; };
in
rec {
default = tagstudio;
tagstudio = pkgs.callPackage ./nix/package {
inherit python3Packages;
inherit pillow-jxl-plugin;
};
tagstudio-jxl = tagstudio.override { withJXLSupport = true; };
inherit pillow-jxl-plugin pyexiv2;
};
devShells = rec {
default = tagstudio;
tagstudio = import ./nix/shell.nix {
inherit
inputs
lib
pkgs
self
python3
;
};
};
formatter = pkgs.nixfmt-rfc-style;
};
};
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 KiB

148
mkdocs.yml Normal file
View File

@@ -0,0 +1,148 @@
# 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/library_search.md
- library/entry.md
- library/field.md
- library/tag.md
- library/tag_categories.md
- library/tag_color.md
- Utilities:
- utilities/ignore.md
- utilities/macro.md
- Updates:
- updates/changelog.md
- updates/roadmap.md
- updates/schema_changes.md
theme:
name: material
custom_dir: overrides
palette:
# Palette toggle for automatic mode
- media: "(prefers-color-scheme)"
toggle:
icon: material/lightbulb-auto-outline
name: Switch to Light Mode
# Palette toggle for light mode
- media: "(prefers-color-scheme: light)"
scheme: default
primary: purple
accent: purple
toggle:
icon: material/lightbulb
name: Switch to Dark Mode
# Palette toggle for dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
primary: purple
accent: purple
toggle:
icon: material/lightbulb-night-outline
name: Switch to System Preference
logo: assets/icon_mono.svg
favicon: assets/icon.ico
font:
code: Jetbrains Mono
language: en
features:
- navigation.instant
- navigation.indexes
- navigation.tracking
- navigation.expand
- navigation.sections
- search.suggest
- content.code.annotate
- content.code.copy
- content.action.edit
- content.tooltips
icon:
repo: fontawesome/brands/github
tag:
upcoming: material/flask-outline
markdown_extensions:
# Python Markdown
- abbr
- admonition
- attr_list
- def_list
- footnotes
- md_in_html
- tables
- toc:
permalink: true
# Python Markdown Extensions
- pymdownx.arithmatex:
generic: true
- pymdownx.betterem:
smart_enable: all
- pymdownx.caret
- pymdownx.details
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
- pymdownx.highlight
- pymdownx.inlinehilite
- pymdownx.keys
- pymdownx.mark
- pymdownx.smartsymbols
- pymdownx.snippets
- 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
- https://fonts.googleapis.com/css2?family=Bai+Jamjuree:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;1,200;1,300;1,400;1,500;1,600;1,700&display=swap

150
nix/package/default.nix Normal file
View File

@@ -0,0 +1,150 @@
{
ffmpeg-headless,
lib,
pipewire,
python3Packages,
qt6,
ripgrep,
stdenv,
wrapGAppsHook,
pillow-jxl-plugin,
withJXLSupport ? false,
}:
let
pyproject = (lib.importTOML ../../pyproject.toml).project;
in
python3Packages.buildPythonApplication {
pname = pyproject.name;
inherit (pyproject) version;
pyproject = true;
src = ../../.;
nativeBuildInputs = [
python3Packages.pythonRelaxDepsHook
qt6.wrapQtAppsHook
# 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 = with python3Packages; [
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
'';
dontWrapGApps = true;
dontWrapQtApps = true;
makeWrapperArgs = [
"--prefix PATH : ${
lib.makeBinPath [
ffmpeg-headless
ripgrep
]
}"
]
++ lib.optional stdenv.hostPlatform.isLinux "--prefix LD_LIBRARY_PATH : ${
lib.makeLibraryPath [ pipewire ]
}"
++ [
"\${gappsWrapperArgs[@]}"
"\${qtWrapperArgs[@]}"
];
pythonRemoveDeps = lib.optional (!withJXLSupport) [ "pillow_jxl" ];
pythonRelaxDeps = [
"numpy"
"pillow"
"pillow-heif"
"pillow-jxl-plugin"
"pyside6"
"structlog"
"typing-extensions"
];
pythonImportsCheck = [ "tagstudio" ];
build-system = with python3Packages; [ hatchling ];
dependencies =
with python3Packages;
[
audioop-lts
chardet
ffmpeg-python
humanfriendly
mutagen
numpy
opencv-python
pillow
pillow-heif
pydantic
pydub
pyside6
rawpy
send2trash
sqlalchemy
srctools
structlog
toml
ujson
wcmatch
]
++ lib.optional withJXLSupport pillow-jxl-plugin;
disabledTests = [
# 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"
"test_add_same_tag_to_selection_single"
"test_add_tag_to_selection_multiple"
"test_add_tag_to_selection_single"
"test_custom_tag_category"
"test_file_path_display"
"test_meta_tag_category"
"test_update_selection_empty"
"test_update_selection_empty"
"test_update_selection_multiple"
"test_update_selection_single"
# 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,68 @@
{
buildPythonPackage,
cmake,
fetchPypi,
lib,
numpy,
packaging,
pillow,
pyexiv2,
pytestCheckHook,
rustPlatform,
}:
buildPythonPackage rec {
pname = "pillow-jxl-plugin";
version = "1.3.4";
pyproject = true;
src = fetchPypi {
pname = "pillow_jxl_plugin";
inherit version;
hash = "sha256-jqWJ/FWep8XfzLQq9NgUj121CPX01FGDKLq1ox/LJo4=";
};
cargoDeps = rustPlatform.fetchCargoVendor {
inherit src;
hash = "sha256-7j+sCn+P6q6tsm2MJ/cM7hF2KEjILJNA6SDb35tecPg=";
};
nativeBuildInputs = [
cmake
rustPlatform.cargoSetupHook
rustPlatform.maturinBuildHook
];
nativeCheckInputs = [
numpy
pyexiv2
pytestCheckHook
];
# 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";
changelog = "https://github.com/Isotr0py/pillow-jpegxl-plugin/releases/tag/v${version}";
license = lib.licenses.gpl3;
maintainers = with lib.maintainers; [ xarvex ];
platforms = lib.platforms.unix;
};
}

37
nix/package/pyexiv2.nix Normal file
View File

@@ -0,0 +1,37 @@
{
autoPatchelfHook,
buildPythonPackage,
exiv2,
fetchFromGitHub,
lib,
setuptools,
}:
buildPythonPackage rec {
pname = "pyexiv2";
version = "2.15.3";
pyproject = true;
src = fetchFromGitHub {
owner = "LeoHsiao1";
repo = "pyexiv2";
tag = "v${version}";
hash = "sha256-83bFMaoXncvhRJNcCgkkC7B29wR5pjuLO/EdkQdqxxo=";
};
nativeBuildInputs = [ autoPatchelfHook ];
buildInputs = [ exiv2.lib ];
pythonImportsCheck = [ "pyexiv2" ];
build-system = [ setuptools ];
meta = {
description = "Read and write image metadata, including EXIF, IPTC, XMP, ICC Profile";
homepage = "https://github.com/LeoHsiao1/pyexiv2";
changelog = "https://github.com/LeoHsiao1/pyexiv2/releases/tag/v${version}";
license = lib.licenses.gpl3;
maintainers = with lib.maintainers; [ xarvex ];
platforms = with lib.platforms; darwin ++ linux ++ windows;
};
}

129
nix/shell.nix Normal file
View File

@@ -0,0 +1,129 @@
{
lib,
pkgs,
python3,
...
}:
let
pythonLibraryPath =
with pkgs;
lib.makeLibraryPath (
[
fontconfig
freetype
glib
qt6.qtbase
stdenv.cc.cc
zstd
]
++ lib.optionals (!stdenv.isDarwin) [
dbus
libGL
libdrm
libpulseaudio
libva
libxkbcommon
pipewire
qt6.qtwayland
xorg.libX11
xorg.libXrandr
]
);
libraryPath = "${lib.optionalString pkgs.stdenv.isDarwin "DY"}LD_LIBRARY_PATH";
python3Wrapped = pkgs.symlinkJoin {
inherit (python3)
name
pname
version
meta
;
paths = [ python3 ];
nativeBuildInputs = with pkgs; [
makeWrapper
qt6.wrapQtAppsHook
# 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 = with pkgs.qt6; [
qtbase
qtmultimedia
];
postBuild = ''
wrapProgram $out/bin/${python3.meta.mainProgram} \
--prefix ${libraryPath} : ${pythonLibraryPath} \
"''${gappsWrapperArgs[@]}" \
"''${qtWrapperArgs[@]}"
'';
dontWrapGApps = true;
dontWrapQtApps = true;
};
in
pkgs.mkShellNoCC {
nativeBuildInputs = with pkgs; [
coreutils
uv
ruff
];
buildInputs = [
python3Wrapped
]
++ (with pkgs; [
ffmpeg-headless
ripgrep
]);
env = {
QT_QPA_PLATFORM = "wayland;xcb";
UV_NO_SYNC = "1";
UV_PYTHON_DOWNLOADS = "never";
};
shellHook =
let
python = lib.getExe python3Wrapped;
# PySide/Qt are very particular about matching versions. Override with nixpkgs package.
pythonPath = lib.makeSearchPathOutput "lib" python3.sitePackages (
with python3.pkgs; [ pyside6 ] ++ pyside6.propagatedBuildInputs or [ ]
);
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
PYTHONPATH=${pythonPath}''${PYTHONPATH:+:}''${PYTHONPATH:-}
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

@@ -0,0 +1,122 @@
<!--
Copyright (c) 2016-2025 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
<!-- Determine classes -->
{% set class = "md-header" %}
{% if "navigation.tabs.sticky" in features %}
{% set class = class ~ " md-header--shadow md-header--lifted" %}
{% elif "navigation.tabs" not in features %}
{% set class = class ~ " md-header--shadow" %}
{% endif %}
<!-- Header -->
<header class="{{ class }}" data-md-component="header">
<nav
class="md-header__inner md-grid"
aria-label="{{ lang.t('header') }}"
>
<!-- Link to home -->
<a
href="{{ config.extra.homepage | d(nav.homepage.url, true) | url }}"
title="{{ config.site_name | e }}"
class="md-header__button md-logo"
aria-label="{{ config.site_name }}"
data-md-component="logo"
>
{% include "partials/logo.html" %}
</a>
<!-- Button to open drawer -->
<label class="md-header__button md-icon" for="__drawer">
{% set icon = config.theme.icon.menu or "material/menu" %}
{% include ".icons/" ~ icon ~ ".svg" %}
</label>
<!-- Header title -->
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
<a style="font-weight: 900;">Tag</a><a style="font-weight: 500;"><i>Studio</i></a>
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
{% if page.meta and page.meta.title %}
{{ page.meta.title }}
{% else %}
{{ page.title }}
{% endif %}
</span>
</div>
</div>
</div>
<!-- Color palette toggle -->
{% if config.theme.palette %}
{% if not config.theme.palette is mapping %}
{% include "partials/palette.html" %}
{% endif %}
{% endif %}
<!-- User preference: color palette -->
{% if not config.theme.palette is mapping %}
{% include "partials/javascripts/palette.html" %}
{% endif %}
<!-- Site language selector -->
{% if config.extra.alternate %}
{% include "partials/alternate.html" %}
{% endif %}
<!-- Button to open search modal -->
{% if "material/search" in config.plugins %}
{% set search = config.plugins["material/search"] | attr("config") %}
<!-- Check if search is actually enabled - see https://t.ly/DT_0V -->
{% if search.enabled %}
<label class="md-header__button md-icon" for="__search">
{% set icon = config.theme.icon.search or "material/magnify" %}
{% include ".icons/" ~ icon ~ ".svg" %}
</label>
<!-- Search interface -->
{% include "partials/search.html" %}
{% endif %}
{% endif %}
<!-- Repository information -->
{% if config.repo_url %}
<div class="md-header__source">
{% include "partials/source.html" %}
</div>
{% endif %}
</nav>
<!-- Navigation tabs (sticky) -->
{% if "navigation.tabs.sticky" in features %}
{% if "navigation.tabs" in features %}
{% include "partials/tabs.html" %}
{% endif %}
{% endif %}
</header>

View File

@@ -0,0 +1,69 @@
<!--
Copyright (c) 2016-2025 Martin Donath <martin.donath@squidfunk.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-->
{% import "partials/nav-item.html" as item with context %}
<!-- Determine classes -->
{% set class = "md-nav md-nav--primary" %}
{% if "navigation.tabs" in features %}
{% set class = class ~ " md-nav--lifted" %}
{% endif %}
{% if "toc.integrate" in features %}
{% set class = class ~ " md-nav--integrated" %}
{% endif %}
<!-- Navigation -->
<nav
class="{{ class }}"
aria-label="{{ lang.t('nav') }}"
data-md-level="0"
>
<!-- Site title -->
<!-- <label class="md-nav__title" for="__drawer">
<a
href="{{ config.extra.homepage | d(nav.homepage.url, true) | url }}"
title="{{ config.site_name | e }}"
class="md-nav__button md-logo"
aria-label="{{ config.site_name }}"
data-md-component="logo"
>
{% include "partials/logo.html" %}
</a>
{{ config.site_name }}
</label> -->
<!-- Repository information -->
{% if config.repo_url %}
<div class="md-nav__source">
{% include "partials/source.html" %}
</div>
{% endif %}
<!-- Navigation list -->
<ul class="md-nav__list" data-md-scrollfix>
{% for nav_item in nav %}
{% set path = "__nav_" ~ loop.index %}
{{ item.render(nav_item, path, 1) }}
{% endfor %}
</ul>
</nav>

View File

@@ -1,8 +1,115 @@
[tool.ruff]
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "TagStudio"
description = "A User-Focused Photo & File Management System."
version = "9.5.3"
license = "GPL-3.0-only"
readme = "README.md"
requires-python = ">=3.12,<3.14"
dependencies = [
"audioop-lts; python_version >= '3.13'",
"chardet~=5.2",
"ffmpeg-python~=0.2",
"humanfriendly==10.*",
"mutagen~=1.47",
"numpy~=2.2",
"opencv_python~=4.11",
"Pillow>=10.2,<=12.0",
"pillow-heif~=0.22",
"pillow-jxl-plugin~=1.3",
"pydantic~=2.10",
"pydub~=0.25",
"PySide6==6.8.0.*",
"rawpy~=0.24",
"Send2Trash~=1.8",
"SQLAlchemy~=2.0",
"srctools~=2.6",
"structlog~=25.3",
"toml~=0.10",
"typing_extensions~=4.13",
"ujson~=5.10",
"wcmatch==10.*",
]
[project.optional-dependencies]
dev = ["tagstudio[mkdocs,mypy,pre-commit,pyinstaller,pytest,ruff]"]
mkdocs = ["mkdocs-material[imaging]>=9.6.14"]
mypy = ["mypy==1.15.0", "mypy-extensions==1.*", "types-ujson~=5.10"]
pre-commit = ["pre-commit~=4.2"]
pyinstaller = ["Pyinstaller~=6.13"]
pytest = [
"pytest==8.3.5",
"pytest-cov==6.1.1",
"pytest-qt==4.4.0",
"syrupy==4.9.1",
]
ruff = ["ruff==0.11.8"]
[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 = ["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", "tests"]
extraPaths = ["src/tagstudio", "tests"]
reportAny = false
reportIgnoreCommentWithoutRule = false
reportImplicitStringConcatenation = false
reportMissingTypeArgument = false
# reportOptionalMemberAccess = false
reportUnannotatedClassAttribute = false
reportUnknownArgumentType = false
reportUnknownLambdaType = false
reportUnknownMemberType = false
reportUnusedCallResult = false
[tool.ruff]
exclude = ["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,5 +0,0 @@
ruff==0.4.2
pre-commit==3.7.0
pytest==8.2.0
Pyinstaller==6.6.0
mypy==1.10.0

View File

@@ -1,10 +0,0 @@
humanfriendly==10.0
opencv_python>=4.8.0.74,<=4.9.0.80
Pillow==10.3.0
PySide6>=6.5.1.1,<=6.6.3.1
PySide6_Addons>=6.5.1.1,<=6.6.3.1
PySide6_Essentials>=6.5.1.1,<=6.6.3.1
typing_extensions>=3.10.0.0,<=4.11.0
ujson>=5.8.0,<=5.9.0
rawpy==0.21.0
pillow-heif==0.16.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 531 KiB

View File

@@ -0,0 +1,30 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
VERSION: str = "9.5.3" # 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"
IGNORE_NAME: str = ".ts_ignore"
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,87 @@
# 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 TagClickActionOption(int, enum.Enum):
"""Values representing the options for the "tag_click_action" setting."""
OPEN_EDIT = 0
SET_SEARCH = 1
ADD_TO_SEARCH = 2
DEFAULT = OPEN_EDIT
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")
# TODO: Remove DefaultEnum and LibraryPrefs classes once remaining values are removed.
class LibraryPrefs(DefaultEnum):
"""Library preferences with default value accessible via .default property."""
IS_EXCLUDE_LIST = True
EXTENSION_LIST = [".json", ".xmp", ".aae"]

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,102 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import platform
from datetime import datetime
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, TagClickActionOption
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)
tag_click_action: TagClickActionOption = Field(default=TagClickActionOption.DEFAULT)
date_format: str = Field(default="%x")
hour_format: bool = Field(default=True)
zero_padding: bool = Field(default=True)
loaded_from: Path = Field(default=DEFAULT_GLOBAL_SETTINGS_PATH, exclude=True)
@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, loaded_from=path)
return settings
return GlobalSettings(loaded_from=path)
def save(self, path: Path | None = None) -> None:
if path is None:
path = self.loaded_from
if not path.parent.exists():
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w") as f:
toml.dump(self.model_dump(), f, encoder=TomlEnumEncoder())
@property
def datetime_format(self) -> str:
date_format = self.date_format
is_24h = self.hour_format
hour_format = "%H:%M:%S" if is_24h else "%I:%M:%S %p"
zero_padding = self.zero_padding
zero_padding_symbol = ""
if not zero_padding:
zero_padding_symbol = "#" if platform.system() == "Windows" else "-"
date_format = date_format.replace("%d", f"%{zero_padding_symbol}d").replace(
"%m", f"%{zero_padding_symbol}m"
)
hour_format = hour_format.replace("%H", f"%{zero_padding_symbol}H").replace(
"%I", f"%{zero_padding_symbol}I"
)
return f"{date_format}, {hour_format}"
def format_datetime(self, dt: datetime) -> str:
return datetime.strftime(dt, self.datetime_format)

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,156 @@
import enum
import random
from dataclasses import dataclass, replace
from pathlib import Path
import structlog
from tagstudio.core.query_lang.ast import AST
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"
RANDOM = "sorting.mode.random"
@dataclass
class BrowsingState:
"""Represent a state of the Library grid view."""
page_index: int = 0
sorting_mode: SortingModeEnum = SortingModeEnum.DATE_ADDED
ascending: bool = True
random_seed: float = 0
query: str | None = None
# Abstract Syntax Tree Of the current Search Query
@property
def ast(self) -> AST | None:
if self.query is None:
return None
return Parser(self.query).parse()
@classmethod
def show_all(cls) -> "BrowsingState":
return BrowsingState()
@classmethod
def from_search_query(cls, search_query: str) -> "BrowsingState":
return cls(query=search_query)
@classmethod
def from_tag_id(
cls, tag_id: int | str, state: "BrowsingState | None" = None
) -> "BrowsingState":
"""Create and return a BrowsingState object given a tag ID.
Args:
tag_id(int): The tag ID to search for.
state(BrowsingState|None): An optional BrowsingState object to use
existing options from, such as sorting options.
"""
logger.warning(state)
if state:
return state.with_search_query(f"tag_id:{str(tag_id)}")
return cls(query=f"tag_id:{str(tag_id)}")
@classmethod
def from_path(cls, path: Path | str) -> "BrowsingState":
return cls(query=f'path:"{str(path).strip()}"')
@classmethod
def from_mediatype(cls, mediatype: str) -> "BrowsingState":
return cls(query=f"mediatype:{mediatype}")
@classmethod
def from_filetype(cls, filetype: str) -> "BrowsingState":
return cls(query=f"filetype:{filetype}")
@classmethod
def from_tag_name(cls, tag_name: str) -> "BrowsingState":
return cls(query=f'tag:"{tag_name}"')
def with_page_index(self, index: int) -> "BrowsingState":
return replace(self, page_index=index)
def with_sorting_mode(self, mode: SortingModeEnum) -> "BrowsingState":
seed = self.random_seed
if mode == SortingModeEnum.RANDOM:
seed = random.random()
return replace(self, sorting_mode=mode, random_seed=seed)
def with_sorting_direction(self, ascending: bool) -> "BrowsingState":
return replace(self, ascending=ascending)
def with_search_query(self, search_query: str) -> "BrowsingState":
return replace(self, query=search_query)
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.child_id",
secondaryjoin="Tag.id == TagParent.parent_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,201 @@
# 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__)
TAG_CHILDREN_ID_QUERY = text("""
WITH RECURSIVE ChildTags AS (
SELECT :tag_id AS tag_id
UNION
SELECT tp.child_id AS tag_id
FROM tag_parents tp
INNER JOIN ChildTags c ON tp.parent_id = c.tag_id
)
SELECT tag_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) -> ColumnElement[bool]:
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,154 @@
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
from copy import deepcopy
from pathlib import Path
import structlog
import wcmatch.fnmatch as fnmatch
from wcmatch import glob, pathlib
from tagstudio.core.constants import IGNORE_NAME, TS_FOLDER_NAME
from tagstudio.core.singleton import Singleton
logger = structlog.get_logger()
PATH_GLOB_FLAGS = glob.GLOBSTARLONG | glob.DOTGLOB | glob.NEGATE | pathlib.MATCHBASE
GLOBAL_IGNORE = [
# TagStudio -------------------
f"{TS_FOLDER_NAME}",
# Trash -----------------------
".Trash-*",
".Trash",
".Trashes",
"$RECYCLE.BIN",
# System ----------------------
"._*",
".DS_Store",
".fseventsd",
".Spotlight-V100",
".TemporaryItems",
"desktop.ini",
"System Volume Information",
".localized",
]
def ignore_to_glob(ignore_patterns: list[str]) -> list[str]:
"""Convert .gitignore-like patterns to explicit glob syntax.
Args:
ignore_patterns (list[str]): The .gitignore-like patterns to convert.
"""
glob_patterns: list[str] = deepcopy(ignore_patterns)
additional_patterns: list[str] = []
# Mimic implicit .gitignore syntax behavior for the SQLite GLOB function.
for pattern in glob_patterns:
# Temporarily remove any exclusion character before processing
exclusion_char = ""
gp = pattern
if pattern.startswith("!"):
gp = pattern[1:]
exclusion_char = "!"
if not gp.startswith("**/") and not gp.startswith("*/") and not gp.startswith("/"):
# Create a version of a prefix-less pattern that starts with "**/"
gp = "**/" + gp
additional_patterns.append(exclusion_char + gp)
gp = gp.removesuffix("/**").removesuffix("/*").removesuffix("/")
additional_patterns.append(exclusion_char + gp)
gp = gp.removeprefix("**/").removeprefix("*/")
additional_patterns.append(exclusion_char + gp)
glob_patterns = glob_patterns + additional_patterns
# Add "/**" suffix to suffix-less patterns to match implicit .gitignore behavior.
for pattern in glob_patterns:
if pattern.endswith("/**"):
continue
glob_patterns.append(pattern.removesuffix("/*").removesuffix("/") + "/**")
glob_patterns = list(set(glob_patterns))
logger.info("[Ignore]", glob_patterns=glob_patterns)
return glob_patterns
class Ignore(metaclass=Singleton):
"""Class for processing and managing glob-like file ignore file patterns."""
_last_loaded: tuple[Path, float] | None = None
_patterns: list[str] = []
compiled_patterns: fnmatch.WcMatcher | None = None
@staticmethod
def get_patterns(library_dir: Path, include_global: bool = True) -> list[str]:
"""Get the ignore patterns for the given library directory.
Args:
library_dir (Path): The path of the library to load patterns from.
include_global (bool): Flag for including the global ignore set.
In most scenarios, this should be True.
"""
patterns = GLOBAL_IGNORE if include_global else []
ts_ignore_path = Path(library_dir / TS_FOLDER_NAME / IGNORE_NAME)
if not ts_ignore_path.exists():
logger.info(
"[Ignore] No .ts_ignore file found",
path=ts_ignore_path,
)
Ignore._last_loaded = None
Ignore._patterns = patterns
return Ignore._patterns
# Process the .ts_ignore file if the previous result is non-existent or outdated.
loaded = (ts_ignore_path, ts_ignore_path.stat().st_mtime)
if not Ignore._last_loaded or (Ignore._last_loaded and Ignore._last_loaded != loaded):
logger.info(
"[Ignore] Processing the .ts_ignore file...",
library=library_dir,
last_mtime=Ignore._last_loaded[1] if Ignore._last_loaded else None,
new_mtime=loaded[1],
)
Ignore._patterns = patterns + Ignore._load_ignore_file(ts_ignore_path)
Ignore.compiled_patterns = fnmatch.compile(
"*", PATH_GLOB_FLAGS, exclude=ignore_to_glob(Ignore._patterns)
)
else:
logger.info(
"[Ignore] No updates to the .ts_ignore detected",
library=library_dir,
last_mtime=Ignore._last_loaded[1],
new_mtime=loaded[1],
)
Ignore._last_loaded = loaded
return Ignore._patterns
@staticmethod
def _load_ignore_file(path: Path) -> list[str]:
"""Load and process the .ts_ignore file into a list of glob patterns.
Args:
path (Path): The path of the .ts_ignore file.
"""
patterns: list[str] = []
if path.exists():
with open(path, encoding="utf8") as f:
for line_raw in f.readlines():
line = line_raw.strip()
# Ignore blank lines and comments
if not line or line.startswith("#"):
continue
patterns.append(line)
return patterns

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"},
]

View File

@@ -0,0 +1,685 @@
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import mimetypes
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
import structlog
logger = structlog.get_logger(__name__)
FILETYPE_EQUIVALENTS = [
set(["aif", "aiff", "aifc"]),
set(["html", "htm", "xhtml", "shtml", "dhtml"]),
set(["jfif", "jpeg_large", "jpeg", "jpg_large", "jpg"]),
set(["json", "jsonc", "json5"]),
set(["md", "markdown", "mkd", "rmd"]),
set(["tar.gz", "tgz"]),
set(["xml", "xul"]),
set(["yaml", "yml"]),
]
class MediaType(str, Enum):
"""Names of media types."""
ADOBE_PHOTOSHOP = "adobe_photoshop"
AFFINITY_PHOTO = "affinity_photo"
ARCHIVE = "archive"
AUDIO_MIDI = "audio_midi"
AUDIO = "audio"
BLENDER = "blender"
CODE = "code"
DATABASE = "database"
DISK_IMAGE = "disk_image"
DOCUMENT = "document"
EBOOK = "ebook"
FONT = "font"
IMAGE_ANIMATED = "image_animated"
IMAGE_RAW = "image_raw"
IMAGE_VECTOR = "image_vector"
IMAGE = "image"
INSTALLER = "installer"
IWORK = "iwork"
MATERIAL = "material"
MODEL = "model"
OPEN_DOCUMENT = "open_document"
PACKAGE = "package"
PDF = "pdf"
PLAINTEXT = "plaintext"
PRESENTATION = "presentation"
PROGRAM = "program"
SHADER = "shader"
SHORTCUT = "shortcut"
SOURCE_ENGINE = "source_engine"
SPREADSHEET = "spreadsheet"
TEXT = "text"
VIDEO = "video"
@dataclass(frozen=True)
class MediaCategory:
"""An object representing a category of media.
Includes a MediaType identifier, extensions set, and IANA status flag.
Args:
media_type (MediaType): The MediaType Enum representing this category.
extensions (set[str]): The set of file extensions associated with this category.
Includes leading ".", all lowercase, and does not need to be unique to this category.
is_iana (bool): Represents whether or not this is an IANA registered category.
"""
media_type: MediaType
extensions: set[str]
name: str
is_iana: bool = False
def contains(self, ext: str, mime_fallback: bool = False) -> bool:
"""Check if an extension is a member of this MediaCategory.
Args:
ext (str): File extension with a leading "." and in all lowercase.
mime_fallback (bool): Flag to guess MIME type if no set matches are made.
"""
if ext in self.extensions:
return True
elif mime_fallback and self.is_iana:
mime_type: str | None = mimetypes.guess_type(Path("x" + ext), strict=False)[0]
if mime_type is not None and mime_type.startswith(self.media_type.value):
return True
return False
class MediaCategories:
"""Contain pre-made MediaCategory objects as well as methods to interact with them."""
# These sets are used either individually or together to form the final sets
# for the MediaCategory(s).
# These sets may be combined and are NOT 1:1 with the final categories.
_ADOBE_PHOTOSHOP_SET: set[str] = {
".pdd",
".psb",
".psd",
}
_AFFINITY_PHOTO_SET: set[str] = {".afphoto"}
_KRITA_SET: set[str] = {".kra", ".krz"}
_ARCHIVE_SET: set[str] = {
".7z",
".gz",
".rar",
".s7z",
".tar",
".tgz",
".zip",
}
_AUDIO_MIDI_SET: set[str] = {
".mid",
".midi",
}
_AUDIO_SET: set[str] = {
".aac",
".aif",
".aifc",
".aiff",
".alac",
".caf",
".flac",
".m4a",
".m4p",
".mp3",
".mpeg4",
".ogg",
".wav",
".wma",
}
_BLENDER_SET: set[str] = {
".blen_tc",
".blend",
".blend1",
".blend2",
".blend3",
".blend4",
".blend5",
".blend6",
".blend7",
".blend8",
".blend9",
".blend10",
".blend11",
".blend12",
".blend13",
".blend14",
".blend15",
".blend16",
".blend17",
".blend18",
".blend19",
".blend20",
".blend21",
".blend22",
".blend23",
".blend24",
".blend25",
".blend26",
".blend27",
".blend28",
".blend29",
".blend30",
".blend31",
".blend32",
}
_CODE_SET: set[str] = {
".bat",
".cfg",
".conf",
".cpp",
".cs",
".csh",
".css",
".d",
".dhtml",
".fgd",
".fish",
".gitignore",
".h",
".hpp",
".htm",
".html",
".inf",
".ini",
".js",
".json",
".json5",
".jsonc",
".jsx",
".kv3",
".lua",
".meta",
".nix",
".nu",
".nut",
".php",
".plist",
".prefs",
".ps1",
".py",
".pyi",
".qml",
".qrc",
".qss",
".rs",
".sh",
".shtml",
".sip",
".spec",
".tcl",
".timestamp",
".toml",
".ts",
".tsx",
".vcfg",
".vdf",
".vmt",
".vqlayout",
".vsc",
".vsnd_template",
".xhtml",
".xml",
".xul",
".yaml",
".yml",
}
_DATABASE_SET: set[str] = {
".accdb",
".mdb",
".pdb",
".sqlite",
".sqlite3",
}
_DISK_IMAGE_SET: set[str] = {".bios", ".dmg", ".fhdx", ".iso"}
_DOCUMENT_SET: set[str] = {
".doc",
".docm",
".docx",
".dot",
".dotm",
".dotx",
".odt",
".pages",
".pdf",
".rtf",
".tex",
".wpd",
".wps",
}
_EBOOK_SET: set[str] = {
".azw",
".azw3",
".cb7",
".cba",
".cbr",
".cbt",
".cbz",
".djvu",
".epub",
".fb2",
".ibook",
".inf",
".kfx",
".lit",
".mobi",
".pdb",
".prc",
}
_FONT_SET: set[str] = {
".fon",
".otf",
".ttc",
".ttf",
".woff",
".woff2",
}
_IMAGE_ANIMATED_SET: set[str] = {
".apng",
".gif",
".webp",
}
_IMAGE_RAW_SET: set[str] = {
".arw",
".cr2",
".cr3",
".crw",
".dng",
".nef",
".nrw",
".orf",
".raf",
".raw",
".rw2",
".srf",
".srf2",
}
_IMAGE_VECTOR_SET: set[str] = {".eps", ".epsf", ".epsi", ".svg", ".svgz"}
_IMAGE_RASTER_SET: set[str] = {
".apng",
".avif",
".bmp",
".exr",
".gif",
".heic",
".heif",
".icns",
".j2k",
".jfif",
".jp2",
".jpeg_large",
".jpeg",
".jpg_large",
".jpg",
".jpg2",
".jxl",
".png",
".psb",
".psd",
".tif",
".tiff",
".webp",
}
_INSTALLER_SET: set[str] = {".appx", ".msi", ".msix"}
_IWORK_SET: set[str] = {".key", ".pages", ".numbers"}
_MATERIAL_SET: set[str] = {".mtl"}
_MODEL_SET: set[str] = {".3ds", ".fbx", ".obj", ".stl"}
_OPEN_DOCUMENT_SET: set[str] = {
".fodg",
".fodp",
".fods",
".fodt",
".mscz",
".odf",
".odg",
".odp",
".ods",
".odt",
".ora",
}
_PACKAGE_SET: set[str] = {
".aab",
".akp",
".apk",
".apkm",
".apks",
".pkg",
".xapk",
}
_PDF_SET: set[str] = {".pdf"}
_PLAINTEXT_SET: set[str] = {
".csv",
".i3u",
".lang",
".lock",
".log",
".markdown",
".md",
".mkd",
".rmd",
".text",
".txt",
"contributing",
"license",
"readme",
}
_PRESENTATION_SET: set[str] = {
".key",
".odp",
".ppt",
".pptx",
}
_PROGRAM_SET: set[str] = {".app", ".bin", ".exe"}
_SOURCE_ENGINE_SET: set[str] = {".vtf"}
_SHADER_SET: set[str] = {
".effect",
".frag",
".fsh",
".glsl",
".shader",
".vert",
".vsh",
}
_SHORTCUT_SET: set[str] = {".desktop", ".lnk", ".url"}
_SPREADSHEET_SET: set[str] = {
".csv",
".numbers",
".ods",
".xls",
".xlsx",
}
_VIDEO_SET: set[str] = {
".3gp",
".avi",
".flv",
".gifv",
".hevc",
".m4p",
".m4v",
".mkv",
".mov",
".mp4",
".webm",
".wmv",
".ts",
}
ADOBE_PHOTOSHOP_TYPES = MediaCategory(
media_type=MediaType.ADOBE_PHOTOSHOP,
extensions=_ADOBE_PHOTOSHOP_SET,
is_iana=False,
name="photoshop",
)
AFFINITY_PHOTO_TYPES = MediaCategory(
media_type=MediaType.AFFINITY_PHOTO,
extensions=_AFFINITY_PHOTO_SET,
is_iana=False,
name="affinity photo",
)
ARCHIVE_TYPES = MediaCategory(
media_type=MediaType.ARCHIVE,
extensions=_ARCHIVE_SET,
is_iana=False,
name="archive",
)
AUDIO_MIDI_TYPES = MediaCategory(
media_type=MediaType.AUDIO_MIDI,
extensions=_AUDIO_MIDI_SET,
is_iana=False,
name="audio midi",
)
AUDIO_TYPES = MediaCategory(
media_type=MediaType.AUDIO,
extensions=_AUDIO_SET | _AUDIO_MIDI_SET,
is_iana=True,
name="audio",
)
BLENDER_TYPES = MediaCategory(
media_type=MediaType.BLENDER,
extensions=_BLENDER_SET,
is_iana=False,
name="blender",
)
CODE_TYPES = MediaCategory(
media_type=MediaType.CODE,
extensions=_CODE_SET,
is_iana=False,
name="code",
)
DATABASE_TYPES = MediaCategory(
media_type=MediaType.DATABASE,
extensions=_DATABASE_SET,
is_iana=False,
name="database",
)
DISK_IMAGE_TYPES = MediaCategory(
media_type=MediaType.DISK_IMAGE,
extensions=_DISK_IMAGE_SET,
is_iana=False,
name="disk image",
)
DOCUMENT_TYPES = MediaCategory(
media_type=MediaType.DOCUMENT,
extensions=_DOCUMENT_SET,
is_iana=False,
name="document",
)
EBOOK_TYPES = MediaCategory(
media_type=MediaType.EBOOK,
extensions=_EBOOK_SET,
is_iana=False,
name="ebook",
)
FONT_TYPES = MediaCategory(
media_type=MediaType.FONT,
extensions=_FONT_SET,
is_iana=True,
name="font",
)
IMAGE_ANIMATED_TYPES = MediaCategory(
media_type=MediaType.IMAGE_ANIMATED,
extensions=_IMAGE_ANIMATED_SET,
is_iana=False,
name="animated image",
)
IMAGE_RAW_TYPES = MediaCategory(
media_type=MediaType.IMAGE_RAW,
extensions=_IMAGE_RAW_SET,
is_iana=False,
name="raw image",
)
IMAGE_VECTOR_TYPES = MediaCategory(
media_type=MediaType.IMAGE_VECTOR,
extensions=_IMAGE_VECTOR_SET,
is_iana=False,
name="vector image",
)
IMAGE_RASTER_TYPES = MediaCategory(
media_type=MediaType.IMAGE,
extensions=_IMAGE_RASTER_SET,
is_iana=False,
name="raster image",
)
IMAGE_TYPES = MediaCategory(
media_type=MediaType.IMAGE,
extensions=_IMAGE_RASTER_SET | _IMAGE_RAW_SET | _IMAGE_VECTOR_SET,
is_iana=True,
name="image",
)
INSTALLER_TYPES = MediaCategory(
media_type=MediaType.INSTALLER,
extensions=_INSTALLER_SET,
is_iana=False,
name="installer",
)
IWORK_TYPES = MediaCategory(
media_type=MediaType.IWORK,
extensions=_IWORK_SET,
is_iana=False,
name="iwork",
)
MATERIAL_TYPES = MediaCategory(
media_type=MediaType.MATERIAL,
extensions=_MATERIAL_SET,
is_iana=False,
name="material",
)
MODEL_TYPES = MediaCategory(
media_type=MediaType.MODEL,
extensions=_MODEL_SET,
is_iana=True,
name="model",
)
OPEN_DOCUMENT_TYPES = MediaCategory(
media_type=MediaType.OPEN_DOCUMENT,
extensions=_OPEN_DOCUMENT_SET,
is_iana=False,
name="open document",
)
PACKAGE_TYPES = MediaCategory(
media_type=MediaType.PACKAGE,
extensions=_PACKAGE_SET,
is_iana=False,
name="package",
)
PDF_TYPES = MediaCategory(
media_type=MediaType.PDF,
extensions=_PDF_SET,
is_iana=False,
name="pdf",
)
PLAINTEXT_TYPES = MediaCategory(
media_type=MediaType.PLAINTEXT,
extensions=_PLAINTEXT_SET | _CODE_SET,
is_iana=False,
name="plaintext",
)
PRESENTATION_TYPES = MediaCategory(
media_type=MediaType.PRESENTATION,
extensions=_PRESENTATION_SET,
is_iana=False,
name="presentation",
)
PROGRAM_TYPES = MediaCategory(
media_type=MediaType.PROGRAM,
extensions=_PROGRAM_SET,
is_iana=False,
name="program",
)
SHADER_TYPES = MediaCategory(
media_type=MediaType.SHADER,
extensions=_SHADER_SET,
is_iana=False,
name="shader",
)
SHORTCUT_TYPES = MediaCategory(
media_type=MediaType.SHORTCUT,
extensions=_SHORTCUT_SET,
is_iana=False,
name="shortcut",
)
SOURCE_ENGINE_TYPES = MediaCategory(
media_type=MediaType.SOURCE_ENGINE,
extensions=_SOURCE_ENGINE_SET,
is_iana=False,
name="source engine",
)
SPREADSHEET_TYPES = MediaCategory(
media_type=MediaType.SPREADSHEET,
extensions=_SPREADSHEET_SET,
is_iana=False,
name="spreadsheet",
)
TEXT_TYPES = MediaCategory(
media_type=MediaType.TEXT,
extensions=_DOCUMENT_SET | _PLAINTEXT_SET,
is_iana=True,
name="text",
)
VIDEO_TYPES = MediaCategory(
media_type=MediaType.VIDEO,
extensions=_VIDEO_SET,
is_iana=True,
name="video",
)
KRITA_TYPES = MediaCategory(
media_type=MediaType.IMAGE,
extensions=_KRITA_SET,
is_iana=False,
name="krita",
)
ALL_CATEGORIES = [
ADOBE_PHOTOSHOP_TYPES,
AFFINITY_PHOTO_TYPES,
ARCHIVE_TYPES,
AUDIO_MIDI_TYPES,
AUDIO_TYPES,
BLENDER_TYPES,
DATABASE_TYPES,
DISK_IMAGE_TYPES,
DOCUMENT_TYPES,
EBOOK_TYPES,
FONT_TYPES,
IMAGE_ANIMATED_TYPES,
IMAGE_RAW_TYPES,
IMAGE_TYPES,
IMAGE_VECTOR_TYPES,
INSTALLER_TYPES,
IWORK_TYPES,
MATERIAL_TYPES,
MODEL_TYPES,
OPEN_DOCUMENT_TYPES,
PACKAGE_TYPES,
PDF_TYPES,
PLAINTEXT_TYPES,
PRESENTATION_TYPES,
PROGRAM_TYPES,
CODE_TYPES,
SHADER_TYPES,
SHORTCUT_TYPES,
SOURCE_ENGINE_TYPES,
SPREADSHEET_TYPES,
TEXT_TYPES,
VIDEO_TYPES,
KRITA_TYPES,
]
@staticmethod
def get_types(ext: str, mime_fallback: bool = False) -> set[MediaType]:
"""Return a set of MediaTypes given a file extension.
Args:
ext (str): File extension with a leading "." and in all lowercase.
mime_fallback (bool): Flag to guess MIME type if no set matches are made.
"""
media_types: set[MediaType] = set()
for cat in MediaCategories.ALL_CATEGORIES:
if cat.contains(ext, mime_fallback):
media_types.add(cat.media_type)
return media_types
@staticmethod
def is_ext_in_category(ext: str, media_cat: MediaCategory, mime_fallback: bool = False) -> bool:
"""Check if an extension is a member of a MediaCategory.
Args:
ext (str): File extension with a leading "." and in all lowercase.
media_cat (MediaCategory): The MediaCategory to to check for extension membership.
mime_fallback (bool): Flag to guess MIME type if no set matches are made.
"""
return media_cat.contains(ext, mime_fallback)

View File

@@ -0,0 +1,141 @@
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
import traceback
from enum import IntEnum
from typing import Any
import structlog
from tagstudio.core.library.alchemy.enums import TagColorEnum
logger = structlog.get_logger(__name__)
class ColorType(IntEnum):
PRIMARY = 0
TEXT = 1
BORDER = 2
LIGHT_ACCENT = 3
DARK_ACCENT = 4
class UiColor(IntEnum):
DEFAULT = 0
THEME_DARK = 1
THEME_LIGHT = 2
RED = 3
ORANGE = 4
AMBER = 5
GREEN = 6
BLUE = 7
PURPLE = 8
TAG_COLORS: dict[TagColorEnum, dict[ColorType, Any]] = {
TagColorEnum.DEFAULT: {
ColorType.PRIMARY: "#1e1e1e",
ColorType.TEXT: ColorType.LIGHT_ACCENT,
ColorType.BORDER: "#333333",
ColorType.LIGHT_ACCENT: "#FFFFFF",
ColorType.DARK_ACCENT: "#222222",
}
}
UI_COLORS: dict[UiColor, dict[ColorType, Any]] = {
UiColor.DEFAULT: {
ColorType.PRIMARY: "#333333",
ColorType.BORDER: "#555555",
ColorType.LIGHT_ACCENT: "#FFFFFF",
ColorType.DARK_ACCENT: "#1e1e1e",
},
UiColor.RED: {
ColorType.PRIMARY: "#e22c3c",
ColorType.BORDER: "#e54252",
ColorType.LIGHT_ACCENT: "#f39caa",
ColorType.DARK_ACCENT: "#440d12",
},
UiColor.ORANGE: {
ColorType.PRIMARY: "#FF8020",
ColorType.BORDER: "#E86919",
ColorType.LIGHT_ACCENT: "#FFECB3",
ColorType.DARK_ACCENT: "#752809",
},
UiColor.AMBER: {
ColorType.PRIMARY: "#FFC107",
ColorType.BORDER: "#FFD54F",
ColorType.LIGHT_ACCENT: "#FFECB3",
ColorType.DARK_ACCENT: "#772505",
},
UiColor.GREEN: {
ColorType.PRIMARY: "#28bb48",
ColorType.BORDER: "#43c568",
ColorType.LIGHT_ACCENT: "#DDFFCC",
ColorType.DARK_ACCENT: "#0d3828",
},
UiColor.BLUE: {
ColorType.PRIMARY: "#3b87f0",
ColorType.BORDER: "#4e95f2",
ColorType.LIGHT_ACCENT: "#aedbfa",
ColorType.DARK_ACCENT: "#122948",
},
UiColor.PURPLE: {
ColorType.PRIMARY: "#C76FF3",
ColorType.BORDER: "#c364f2",
ColorType.LIGHT_ACCENT: "#EFD4FB",
ColorType.DARK_ACCENT: "#3E1555",
},
UiColor.THEME_DARK: {
ColorType.PRIMARY: "#333333",
ColorType.BORDER: "#555555",
ColorType.LIGHT_ACCENT: "#FFFFFF",
ColorType.DARK_ACCENT: "#1e1e1e",
},
UiColor.THEME_LIGHT: {
ColorType.PRIMARY: "#FFFFFF",
ColorType.BORDER: "#333333",
ColorType.LIGHT_ACCENT: "#999999",
ColorType.DARK_ACCENT: "#888888",
},
}
def get_tag_color(color_type: ColorType, color_id: TagColorEnum) -> str:
"""Return a hex value given a tag color name and ColorType.
Args:
color_type (ColorType): The ColorType category to retrieve from.
color_id (ColorType): The color name enum to retrieve from.
Return:
A hex value string representing a color with a leading "#".
"""
try:
if color_type == ColorType.TEXT:
text_account: ColorType = TAG_COLORS[color_id][color_type]
return get_tag_color(text_account, color_id)
return TAG_COLORS[color_id][color_type]
except KeyError:
traceback.print_stack()
logger.error("[PALETTE] Tag color not found.", color_id=color_id)
return "#FF00FF"
def get_ui_color(color_type: ColorType, color_id: UiColor) -> str:
"""Return a hex value given a UI color name and ColorType.
Args:
color_type (ColorType): The ColorType category to retrieve from.
color_id (UiColor): The color name enum to retrieve from.
Return:
A hex value string representing a color with a leading "#".
"""
try:
return UI_COLORS[color_id][color_type]
except KeyError:
traceback.print_stack()
logger.error("[PALETTE] UI color not found", color_id=color_id)
return "#FF00FF"

View File

@@ -0,0 +1,126 @@
from abc import ABC, abstractmethod
from enum import Enum
from typing import Generic, TypeVar, Union
class ConstraintType(Enum):
Tag = 0
TagID = 1
MediaType = 2
FileType = 3
Path = 4
Special = 5
@staticmethod
def from_string(text: str) -> Union["ConstraintType", None]:
return {
"tag": ConstraintType.Tag,
"tag_id": ConstraintType.TagID,
"mediatype": ConstraintType.MediaType,
"filetype": ConstraintType.FileType,
"path": ConstraintType.Path,
"special": ConstraintType.Special,
}.get(text.lower(), None)
class AST:
parent: Union["AST", None] = None
def __str__(self):
class_name = self.__class__.__name__
fields = vars(self) # Get all instance variables as a dictionary
field_str = ", ".join(f"{key}={value}" for key, value in fields.items())
return f"{class_name}({field_str})"
def __repr__(self) -> str:
return self.__str__()
class ANDList(AST):
terms: list[AST]
def __init__(self, terms: list[AST]) -> None:
super().__init__()
for term in terms:
term.parent = self
self.terms = terms
class ORList(AST):
elements: list[AST]
def __init__(self, elements: list[AST]) -> None:
super().__init__()
for element in elements:
element.parent = self
self.elements = elements
class Constraint(AST):
type: ConstraintType
value: str
properties: list["Property"]
def __init__(self, type: ConstraintType, value: str, properties: list["Property"]) -> None:
super().__init__()
for prop in properties:
prop.parent = self
self.type = type
self.value = value
self.properties = properties
class Property(AST):
key: str
value: str
def __init__(self, key: str, value: str) -> None:
super().__init__()
self.key = key
self.value = value
class Not(AST):
child: AST
def __init__(self, child: AST) -> None:
super().__init__()
self.child = child
T = TypeVar("T")
class BaseVisitor(ABC, Generic[T]):
def visit(self, node: AST) -> T:
if isinstance(node, ANDList):
return self.visit_and_list(node)
elif isinstance(node, ORList):
return self.visit_or_list(node)
elif isinstance(node, Constraint):
return self.visit_constraint(node)
elif isinstance(node, Property):
return self.visit_property(node)
elif isinstance(node, Not):
return self.visit_not(node)
raise Exception(f"Unknown Node Type of {node}") # pragma: nocover
@abstractmethod
def visit_and_list(self, node: ANDList) -> T:
raise NotImplementedError() # pragma: nocover
@abstractmethod
def visit_or_list(self, node: ORList) -> T:
raise NotImplementedError() # pragma: nocover
@abstractmethod
def visit_constraint(self, node: Constraint) -> T:
raise NotImplementedError() # pragma: nocover
@abstractmethod
def visit_property(self, node: Property) -> T:
raise NotImplementedError() # pragma: nocover
@abstractmethod
def visit_not(self, node: Not) -> T:
raise NotImplementedError() # pragma: nocover

View File

@@ -0,0 +1,128 @@
from tagstudio.core.query_lang.ast import (
AST,
ANDList,
Constraint,
ConstraintType,
Not,
ORList,
Property,
)
from tagstudio.core.query_lang.tokenizer import Token, Tokenizer, TokenType
from tagstudio.core.query_lang.util import ParsingError
class Parser:
text: str
tokenizer: Tokenizer
next_token: Token
last_constraint_type: ConstraintType = ConstraintType.Tag
def __init__(self, text: str) -> None:
self.text = text
self.tokenizer = Tokenizer(self.text)
self.next_token = self.tokenizer.get_next_token()
def parse(self) -> AST:
if self.next_token.type == TokenType.EOF:
return ORList([])
out = self.__or_list()
if self.next_token.type != TokenType.EOF:
raise ParsingError(self.next_token.start, self.next_token.end, "Syntax Error")
return out
def __or_list(self) -> AST:
terms = [self.__and_list()]
while self.__is_next_or():
self.__eat(TokenType.ULITERAL)
terms.append(self.__and_list())
return ORList(terms) if len(terms) > 1 else terms[0]
def __is_next_or(self) -> bool:
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "OR"
def __and_list(self) -> AST:
elements = [self.__term()]
while (
self.next_token.type
in [
TokenType.QLITERAL,
TokenType.ULITERAL,
TokenType.CONSTRAINTTYPE,
TokenType.RBRACKETO,
]
and not self.__is_next_or()
):
self.__skip_and()
elements.append(self.__term())
return ANDList(elements) if len(elements) > 1 else elements[0]
def __skip_and(self) -> None:
if self.__is_next_and():
self.__eat(TokenType.ULITERAL)
if self.__is_next_and():
raise self.__syntax_error("Unexpected AND")
def __is_next_and(self) -> bool:
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "AND"
def __term(self) -> AST:
if self.__is_next_not():
self.__eat(TokenType.ULITERAL)
term = self.__term()
if isinstance(term, Not): # instead of Not(Not(child)) return child
return term.child
return Not(term)
if self.next_token.type == TokenType.RBRACKETO:
self.__eat(TokenType.RBRACKETO)
out = self.__or_list()
self.__eat(TokenType.RBRACKETC)
return out
else:
return self.__constraint()
def __is_next_not(self) -> bool:
return self.next_token.type == TokenType.ULITERAL and self.next_token.value.upper() == "NOT"
def __constraint(self) -> Constraint:
if self.next_token.type == TokenType.CONSTRAINTTYPE:
self.last_constraint_type = self.__eat(TokenType.CONSTRAINTTYPE).value
value = self.__literal()
properties = []
if self.next_token.type == TokenType.SBRACKETO:
self.__eat(TokenType.SBRACKETO)
properties.append(self.__property())
while self.next_token.type == TokenType.COMMA:
self.__eat(TokenType.COMMA)
properties.append(self.__property())
self.__eat(TokenType.SBRACKETC)
return Constraint(self.last_constraint_type, value, properties)
def __property(self) -> Property:
key = self.__eat(TokenType.ULITERAL).value
self.__eat(TokenType.EQUALS)
value = self.__literal()
return Property(key, value)
def __literal(self) -> str:
if self.next_token.type in [TokenType.QLITERAL, TokenType.ULITERAL]:
return self.__eat(self.next_token.type).value
raise self.__syntax_error()
def __eat(self, type: TokenType) -> Token:
if self.next_token.type != type:
raise self.__syntax_error(f"expected {type} found {self.next_token.type}")
out = self.next_token
self.next_token = self.tokenizer.get_next_token()
return out
def __syntax_error(self, msg: str = "Syntax Error") -> ParsingError:
return ParsingError(self.next_token.start, self.next_token.end, msg)

View File

@@ -0,0 +1,150 @@
from enum import Enum
from typing import Any
from tagstudio.core.query_lang.ast import ConstraintType
from tagstudio.core.query_lang.util import ParsingError
class TokenType(Enum):
EOF = -1
QLITERAL = 0 # Quoted Literal
ULITERAL = 1 # Unquoted Literal (does not contain ":", " ", "[", "]", "(", ")", "=", ",")
RBRACKETO = 2 # Round Bracket Open
RBRACKETC = 3 # Round Bracket Close
SBRACKETO = 4 # Square Bracket Open
SBRACKETC = 5 # Square Bracket Close
CONSTRAINTTYPE = 6
COLON = 10
COMMA = 11
EQUALS = 12
class Token:
type: TokenType
value: Any
start: int
end: int
def __init__(self, type: TokenType, value: Any, start: int, end: int) -> None:
self.type = type
self.value = value
self.start = start
self.end = end
@staticmethod
def from_type(type: TokenType, pos: int) -> "Token":
return Token(type, None, pos, pos)
@staticmethod
def EOF(pos: int) -> "Token": # noqa: N802
return Token.from_type(TokenType.EOF, pos)
def __str__(self) -> str:
return f"Token({self.type}, {self.value}, {self.start}, {self.end})" # pragma: nocover
def __repr__(self) -> str:
return self.__str__() # pragma: nocover
class Tokenizer:
text: str
pos: int
current_char: str | None
ESCAPABLE_CHARS = ["\\", '"', '"']
NOT_IN_ULITERAL = [":", " ", "[", "]", "(", ")", "=", ","]
def __init__(self, text: str) -> None:
self.text = text
self.pos = 0
self.current_char = self.text[self.pos] if len(text) > 0 else None
def get_next_token(self) -> Token:
self.__skip_whitespace()
if self.current_char is None:
return Token.EOF(self.pos)
if self.current_char in ("'", '"'):
return self.__quoted_string()
elif self.current_char == "(":
self.__advance()
return Token.from_type(TokenType.RBRACKETO, self.pos - 1)
elif self.current_char == ")":
self.__advance()
return Token.from_type(TokenType.RBRACKETC, self.pos - 1)
elif self.current_char == "[":
self.__advance()
return Token.from_type(TokenType.SBRACKETO, self.pos - 1)
elif self.current_char == "]":
self.__advance()
return Token.from_type(TokenType.SBRACKETC, self.pos - 1)
elif self.current_char == ",":
self.__advance()
return Token.from_type(TokenType.COMMA, self.pos - 1)
elif self.current_char == "=":
self.__advance()
return Token.from_type(TokenType.EQUALS, self.pos - 1)
else:
return self.__unquoted_string_or_constraint_type()
def __unquoted_string_or_constraint_type(self) -> Token:
out = ""
start = self.pos
while self.current_char is not None:
if self.current_char in self.NOT_IN_ULITERAL:
if self.current_char == ":":
if len(out) == 0:
raise ParsingError(self.pos, self.pos)
constraint_type = ConstraintType.from_string(out)
if constraint_type is not None:
self.__advance()
return Token(TokenType.CONSTRAINTTYPE, constraint_type, start, self.pos)
else:
break
out += self.current_char
self.__advance()
end = self.pos - 1
return Token(TokenType.ULITERAL, out, start, end)
def __quoted_string(self) -> Token:
start = self.pos
quote = self.current_char
self.__advance()
escape = False
out = ""
while escape or self.current_char != quote:
if self.current_char is None:
raise ParsingError(start, self.pos, "Unterminated quoted string")
if escape:
escape = False
if self.current_char not in Tokenizer.ESCAPABLE_CHARS:
out += "\\"
else:
out += self.current_char
self.__advance()
continue
if self.current_char == "\\":
escape = True
else:
out += self.current_char
self.__advance()
end = self.pos
self.__advance()
return Token(TokenType.QLITERAL, out, start, end)
def __advance(self) -> None:
if self.pos < len(self.text) - 1:
self.pos += 1
self.current_char = self.text[self.pos]
else:
self.current_char = None
def __skip_whitespace(self) -> None:
while self.current_char is not None and self.current_char.isspace():
self.__advance()

View File

@@ -0,0 +1,15 @@
class ParsingError(BaseException):
start: int
end: int
msg: str
def __init__(self, start: int, end: int, msg: str = "Syntax Error") -> None:
self.start = start
self.end = end
self.msg = msg
def __str__(self) -> str:
return f"Syntax Error {self.start}->{self.end}: {self.msg}" # pragma: nocover
def __repr__(self) -> str:
return self.__str__() # pragma: nocover

Some files were not shown because too many files have changed in this diff Show More