mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-28 22:01:24 +00:00
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>
This commit is contained in:
@@ -1,16 +1,18 @@
|
||||
# vi: ft=bash
|
||||
#
|
||||
# If you wish to use this file, copy or symlink it to `.envrc` for direnv to read it.
|
||||
# This will use the flake development shell.
|
||||
|
||||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.5; then
|
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.5/direnvrc" "sha256-RuwIS+QKFj/T9M2TFXScjBsLR6V3A17YVoEW/Q6AZ1w="
|
||||
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
|
||||
|
||||
devenv_root_file="$(mktemp -t devenv-root-XXXXXXXX)"
|
||||
printf %s "${PWD}" >"${devenv_root_file}"
|
||||
if ! use flake . --override-input devenv-root "file+file://${devenv_root_file}"; then
|
||||
printf '%s\n' "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
|
||||
if [ -f .venv/bin/activate ]; then
|
||||
# If the file is watched when it does not exist,
|
||||
# direnv will execute again when it gets created.
|
||||
watch_file .venv/bin/activate
|
||||
fi
|
||||
rm "${devenv_root_file}"
|
||||
unset devenv_root_file
|
||||
watch_file nix/shell.nix
|
||||
watch_file pyproject.toml
|
||||
|
||||
use flake
|
||||
|
||||
# vi: ft=bash
|
||||
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +1,2 @@
|
||||
---
|
||||
patreon: cyanvoxel
|
||||
|
||||
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: Bug Report
|
||||
description: File a bug or issue report.
|
||||
title: '[Bug]: '
|
||||
@@ -51,7 +52,7 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Minimal steps neded for the problem to occur.
|
||||
description: Minimal steps needed for the problem to occur.
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: Feature Request
|
||||
description: Suggest a new feature.
|
||||
title: '[Feature Request]: '
|
||||
|
||||
28
.github/workflows/mypy.yaml
vendored
28
.github/workflows/mypy.yaml
vendored
@@ -1,38 +1,34 @@
|
||||
---
|
||||
name: MyPy
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
mypy:
|
||||
name: Run MyPy
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: reviewdog/action-setup@v1
|
||||
- name: Setup reviewdog
|
||||
uses: reviewdog/action-setup@v1
|
||||
with:
|
||||
reviewdog_version: latest
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
cache: pip
|
||||
|
||||
- name: Install dependencies
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system -r requirements.txt
|
||||
uv pip install --system mypy==1.11.2
|
||||
mkdir tagstudio/.mypy_cache
|
||||
uv pip install --system .[mypy]
|
||||
|
||||
- uses: tsuyoshicho/action-mypy@v4
|
||||
- name: Execute MyPy
|
||||
uses: tsuyoshicho/action-mypy@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
reporter: github-check
|
||||
fail_on_error: true
|
||||
workdir: tagstudio
|
||||
level: error
|
||||
mypy_flags: --config-file ../pyproject.toml
|
||||
|
||||
37
.github/workflows/publish_docs.yaml
vendored
37
.github/workflows/publish_docs.yaml
vendored
@@ -1,19 +1,20 @@
|
||||
name: Publish Docs
|
||||
---
|
||||
name: Publish Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- 'mkdocs.yml'
|
||||
- 'CHANGELOG.md'
|
||||
- '.github/workflows/publish_docs.yaml'
|
||||
- .github/workflows/publish_docs.yaml
|
||||
- docs/**
|
||||
- mkdocs.yml
|
||||
- CHANGELOG.md
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
concurrency:
|
||||
group: publish-docs
|
||||
cancel-in-progress: true
|
||||
|
||||
@@ -21,18 +22,28 @@ jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
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.x
|
||||
- run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
|
||||
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-
|
||||
- run: pip install mkdocs-material mkdocs-material[imaging]
|
||||
- run: mkdocs gh-deploy --force
|
||||
|
||||
|
||||
- name: Execute mkdocs
|
||||
run: mkdocs gh-deploy --force
|
||||
|
||||
49
.github/workflows/pytest.yaml
vendored
49
.github/workflows/pytest.yaml
vendored
@@ -1,10 +1,11 @@
|
||||
---
|
||||
name: pytest
|
||||
|
||||
on: [ push, pull_request ]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
pytest:
|
||||
name: Run tests
|
||||
name: Run pytest
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
@@ -15,56 +16,54 @@ jobs:
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system .[pytest]
|
||||
|
||||
- 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 \
|
||||
libgl1 \
|
||||
libegl1 \
|
||||
libgl1 \
|
||||
libopengl0 \
|
||||
libpulse0 \
|
||||
libxcb-cursor0 \
|
||||
libxcb-icccm4 \
|
||||
libxcb-image0 \
|
||||
libxcb-keysyms1 \
|
||||
libxcb-randr0 \
|
||||
libxcb-render-util0 \
|
||||
libxcb-xinerama0 \
|
||||
libopengl0 \
|
||||
libxcb-cursor0 \
|
||||
libpulse0
|
||||
libxkbcommon-x11-0 \
|
||||
libyaml-dev \
|
||||
x11-utils
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system -r requirements.txt
|
||||
uv pip install --system -r requirements-dev.txt
|
||||
|
||||
- name: Run pytest
|
||||
- name: Execute pytest
|
||||
run: |
|
||||
xvfb-run pytest --cov-report xml --cov=tagstudio
|
||||
|
||||
- name: Store coverage
|
||||
- name: Upload coverage
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'coverage'
|
||||
path: 'coverage.xml'
|
||||
name: coverage
|
||||
path: coverage.xml
|
||||
|
||||
coverage:
|
||||
name: Check Code Coverage
|
||||
name: Check coverage
|
||||
runs-on: ubuntu-latest
|
||||
needs: pytest
|
||||
|
||||
steps:
|
||||
- name: Load coverage
|
||||
- name: Fetch coverage
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: 'coverage'
|
||||
name: coverage
|
||||
|
||||
- name: Check Code Coverage
|
||||
- name: Check coverage
|
||||
uses: yedpodtrzitko/coverage@main
|
||||
with:
|
||||
thresholdAll: 0.4
|
||||
|
||||
99
.github/workflows/release.yml
vendored
99
.github/workflows/release.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: Release
|
||||
|
||||
on:
|
||||
@@ -19,15 +20,27 @@ jobs:
|
||||
suffix: _portable
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
- run: pip install -Ur requirements.txt pyinstaller
|
||||
- run: pyinstaller tagstudio.spec -- ${{ matrix.build-flag }}
|
||||
- run: tar czfC dist/tagstudio_linux_x86_64${{ matrix.suffix }}.tar.gz dist tagstudio
|
||||
- uses: actions/upload-artifact@v4
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system .[pyinstaller]
|
||||
|
||||
- name: Execute PyInstaller
|
||||
run: |
|
||||
pyinstaller tagstudio.spec -- ${{ matrix.build-flag }}
|
||||
tar czfC dist/tagstudio_linux_x86_64${{ matrix.suffix }}.tar.gz dist tagstudio
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tagstudio_linux_x86_64${{ matrix.suffix }}
|
||||
path: dist/tagstudio_linux_x86_64${{ matrix.suffix }}.tar.gz
|
||||
@@ -41,20 +54,35 @@ jobs:
|
||||
arch: x86_64
|
||||
- os-version: '14'
|
||||
arch: aarch64
|
||||
|
||||
runs-on: macos-${{ matrix.os-version }}
|
||||
|
||||
env:
|
||||
# even though we run on 12, target towards compatibility
|
||||
# INFO: Even though we run on 13, target towards compatibility
|
||||
MACOSX_DEPLOYMENT_TARGET: '11.0'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
- run: pip install -Ur requirements.txt pyinstaller
|
||||
- run: pyinstaller tagstudio.spec
|
||||
- run: tar czfC dist/tagstudio_macos_${{ matrix.arch }}.tar.gz dist TagStudio.app
|
||||
- uses: actions/upload-artifact@v4
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system .[pyinstaller]
|
||||
|
||||
|
||||
- name: Execute PyInstaller
|
||||
run: |
|
||||
pyinstaller tagstudio.spec
|
||||
tar czfC dist/tagstudio_macos_${{ matrix.arch }}.tar.gz dist TagStudio.app
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tagstudio_macos_${{ matrix.arch }}
|
||||
path: dist/tagstudio_macos_${{ matrix.arch }}.tar.gz
|
||||
@@ -72,30 +100,51 @@ jobs:
|
||||
build-flag: --portable
|
||||
suffix: _portable
|
||||
file-end: .exe
|
||||
|
||||
runs-on: windows-2019
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
cache: 'pip'
|
||||
- run: pip install -Ur requirements.txt pyinstaller
|
||||
- run: PyInstaller tagstudio.spec -- ${{ matrix.build-flag }}
|
||||
- run: Compress-Archive -Path dist/TagStudio${{ matrix.file-end }} -DestinationPath dist/tagstudio_windows_x86_64${{ matrix.suffix }}.zip
|
||||
- uses: actions/upload-artifact@v4
|
||||
cache: pip
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade uv
|
||||
uv pip install --system .[pyinstaller]
|
||||
|
||||
- name: Execute PyInstaller
|
||||
run: |
|
||||
PyInstaller tagstudio.spec -- ${{ matrix.build-flag }}
|
||||
Compress-Archive -Path dist/TagStudio${{ matrix.file-end }} -DestinationPath dist/tagstudio_windows_x86_64${{ matrix.suffix }}.zip
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: tagstudio_windows_x86_64${{ matrix.suffix }}
|
||||
path: dist/tagstudio_windows_x86_64${{ matrix.suffix }}.zip
|
||||
|
||||
publish:
|
||||
needs: [linux, macos, windows]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: softprops/action-gh-release@v2
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
tagstudio_linux_x86_64/*
|
||||
|
||||
29
.github/workflows/ruff.yaml
vendored
29
.github/workflows/ruff.yaml
vendored
@@ -1,20 +1,31 @@
|
||||
---
|
||||
name: Ruff
|
||||
on: [ push, pull_request ]
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ruff-format:
|
||||
name: Run Ruff format
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: chartboost/ruff-action@v1
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Execute Ruff format
|
||||
uses: chartboost/ruff-action@v1
|
||||
with:
|
||||
version: 0.6.4
|
||||
args: 'format --check'
|
||||
version: 0.8.1
|
||||
args: format --check
|
||||
|
||||
ruff-check:
|
||||
name: Run Ruff check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: chartboost/ruff-action@v1
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Execute Ruff check
|
||||
uses: chartboost/ruff-action@v1
|
||||
with:
|
||||
version: 0.6.4
|
||||
args: 'check'
|
||||
version: 0.8.1
|
||||
args: check
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -265,3 +265,6 @@ TagStudio.ini
|
||||
.envrc
|
||||
.direnv
|
||||
.devenv
|
||||
|
||||
result
|
||||
result-*
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -1,100 +1,56 @@
|
||||
# Contributing to TagStudio
|
||||
|
||||
_Last Updated: January 30th, 2025_
|
||||
_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 open [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls).
|
||||
- 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)
|
||||
- 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 open [Issues](https://github.com/TagStudioDev/TagStudio/issues) and [Pull Requests](https://github.com/TagStudioDev/TagStudio/pulls)
|
||||
- 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 involved for everyone involved.
|
||||
> If the fix is small and self-explanatory (i.e. a typo), then it doesn't require an issue to be opened first. Issue tracking is supposed to make our lives easier, not harder. Please use your best judgement to minimize the amount of work for everyone involved.
|
||||
|
||||
## Creating a Development Environment
|
||||
|
||||
### Prerequisites
|
||||
If you wish to develop for TagStudio, you'll need to create a development environment by installing the required dependencies. You have a number of options depending on your level of experience and familiarly with existing Python toolchains.
|
||||
|
||||
- [Python](https://www.python.org/downloads/) 3.12
|
||||
- [Ruff](https://github.com/astral-sh/ruff) (Included in `requirements-dev.txt`)
|
||||
- [Mypy](https://github.com/python/mypy) (Included in `requirements-dev.txt`)
|
||||
- [PyTest](https://docs.pytest.org) (Included in `requirements-dev.txt`)
|
||||
If you know what you're doing and have developed for Python projects in the past, you can get started quickly with the "Brief Instructions" below. Otherwise, please see the full instructions on the documentation website for "[Creating a Development Environment](https://docs.tagstud.io/install/#creating-a-development-environment)".
|
||||
|
||||
### Creating a Python Virtual Environment
|
||||
### Brief Instructions
|
||||
|
||||
If you wish to launch the source version of TagStudio outside of your IDE:
|
||||
1. Have [Python 3.12](https://www.python.org/downloads/) and PIP installed. Also have [FFmpeg](https://ffmpeg.org/download.html) installed if you wish to have audio/video playback and thumbnails.
|
||||
2. Clone the repository to the folder of your choosing:
|
||||
```
|
||||
git clone https://github.com/TagStudioDev/TagStudio.git
|
||||
```
|
||||
3. Use a dependency manager such as [uv](https://docs.astral.sh/uv/) or [Poetry 2.0](https://python-poetry.org/blog/category/releases/) to install the required dependencies, or alternatively create and activate a [virtual environment](https://docs.tagstud.io/install/#manual-installation) with `venv`.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Depending on your system, Python may be called `python`, `py`, `python3`, or `py3`. These instructions use the alias `python3` for consistency. You can check to see which alias your system uses and if it's for the correct Python version by typing `python3 --version` (or whichever alias) into your terminal.
|
||||
4. If using a virtual environment instead of a dependency manager, install an editable version of the program and development dependencies with the following PIP command:
|
||||
|
||||
> [!TIP]
|
||||
> On Linux and macOS, you can launch the `tagstudio.sh` script to skip the following process, minus the `requirements-dev.txt` installation step. _Using the script is fine if you just want to launch the program from source._
|
||||
```
|
||||
pip install -e .[dev]
|
||||
```
|
||||
|
||||
1. Make sure you're using the correct Python version:
|
||||
- If the output matches `Python 3.12.x` (where the x is any number) then you're using the correct Python version and can skip to step 2. Otherwise, you can install the correct Python version from the [Python](https://www.python.org/downloads/) website, or you can use a tool like [pyenv](https://github.com/pyenv/pyenv/) to install the correct version without changes to your system:
|
||||
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.
|
||||
Otherwise, modify the command above for use with your dependency manager of choice. For example if using uv, you may use this:
|
||||
|
||||
2. In the root repository directory, create a python virtual environment:
|
||||
`python3 -m venv .venv`
|
||||
3. Activate your environment:
|
||||
|
||||
- Windows w/Powershell: `.venv\Scripts\Activate.ps1`
|
||||
- Windows w/Command Prompt: `.venv\Scripts\activate.bat`
|
||||
- Linux/macOS: `source .venv/bin/activate`
|
||||
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` |
|
||||
|PWSH |`.venv/bin/activate.ps1` |
|
||||
|
||||
|
||||
4. Install the required packages:
|
||||
|
||||
- `pip install -r requirements.txt`
|
||||
- If developing (includes Ruff and Mypy): `pip install -r requirements-dev.txt`
|
||||
|
||||
_Learn more about setting up a virtual environment [here](https://docs.python.org/3/tutorial/venv.html)._
|
||||
|
||||
### Manually Launching (Outside of an IDE)
|
||||
|
||||
If you encounter errors about the Python version, or seemingly vague script errors, [pyenv](https://github.com/pyenv/pyenv/) may solve your issue. See step 1 of [Creating a Python Virtual Environment](#creating-a-python-virtual-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** (Nix Flake)
|
||||
- Use the provided [Flake](https://nixos.wiki/wiki/Flakes) to create and enter a working environment by running `nix develop`. Then, run the program via `python3 tagstudio/tag_studio.py` from the root directory.
|
||||
|
||||
> [!WARNING]
|
||||
> Support for NixOS is still a work in progress.
|
||||
|
||||
- **Any** (No Scripts)
|
||||
|
||||
- Alternatively, with the virtual environment loaded, run the python file at `tagstudio\tag_studio.py` from your terminal. If you're in the project's root directory, simply run `python3 tagstudio/tag_studio.py`.
|
||||
```
|
||||
uv pip install -e .[dev]
|
||||
```
|
||||
|
||||
## Workflow Checks
|
||||
|
||||
@@ -125,16 +81,16 @@ Mypy is a static type checker for Python. It sure has a lot to say sometimes, bu
|
||||
|
||||
#### Running Locally
|
||||
|
||||
- **First time only:** Move into the `/tagstudio` directory with `cd tagstudio` and run the following:
|
||||
- **(First time only)** Run the following:
|
||||
- `mkdir -p .mypy_cache`
|
||||
- `mypy --install-types --non-interactive`
|
||||
- Check code by moving into the `/tagstudio` directory with `cd tagstudio` _(if you aren't already inside)_ and running `mypy --config-file ../pyproject.toml .`. _(Don't forget the `.` at the end!)_
|
||||
- You can now check code by running `mypy --config-file pyproject.toml .` in the repository root. _(Don't forget the "." at the end!)_
|
||||
|
||||
Mypy is also available as a VS Code [extension](https://marketplace.visualstudio.com/items?itemName=matangover.mypy), PyCharm [plugin](https://plugins.jetbrains.com/plugin/11086-mypy), and [more](https://plugins.jetbrains.com/plugin/11086-mypy).
|
||||
|
||||
### PyTest
|
||||
|
||||
- Run all tests by moving into the `/tagstudio` directory with `cd tagstudio` and running `pytest tests/`.
|
||||
- Run all tests by running `pytest tests/` in the repository root.
|
||||
|
||||
## Code Style
|
||||
|
||||
@@ -173,7 +129,7 @@ Most of the style guidelines can be checked, fixed, and enforced via Ruff. Older
|
||||
> [!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 rereview all the already reviewed code, which is a lot of unnecessary work for reviewers.
|
||||
> 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?"_
|
||||
@@ -182,7 +138,7 @@ Most of the style guidelines can be checked, fixed, and enforced via Ruff. Older
|
||||
|
||||
- Final code must function on supported versions of Windows, macOS, and Linux:
|
||||
- Windows: 10, 11
|
||||
- macOS: 12.0+
|
||||
- macOS: 13.0+
|
||||
- Linux: _Varies_
|
||||
- Final code must **_NOT:_**
|
||||
- Contain superfluous or unnecessary logging statements
|
||||
|
||||
40
README.md
40
README.md
@@ -44,7 +44,7 @@ TagStudio is a photo & file organization application with an underlying tag-base
|
||||
## Goals
|
||||
|
||||
- To achieve a portable, private, extensible, open-format, and feature-rich system of organizing and rediscovering files.
|
||||
- To provide powerful methods for organization, notably the concept of tag inheritance, or “taggable tags” _(and in the near future, the combination of composition-based tags)._
|
||||
- To provide powerful methods for organization, notably the concept of tag inheritance, or "taggable tags" _(and in the near future, the combination of composition-based tags)._
|
||||
- To create an implementation of such a system that is resilient against a user’s actions outside the program (modifying, moving, or renaming files) while also not burdening the user with mandatory sidecar files or requiring them to change their existing file structures and workflows.
|
||||
- To support a wide range of users spanning across different platforms, multi-user setups, and those with large (several terabyte) libraries.
|
||||
- To make the dang thing look nice, too. It’s 2025, not 1995.
|
||||
@@ -75,7 +75,7 @@ Translation hosting generously provided by [Weblate](https://weblate.org/en/). C
|
||||
- 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.
|
||||
- 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)
|
||||
@@ -98,29 +98,21 @@ Translation hosting generously provided by [Weblate](https://weblate.org/en/). C
|
||||
|
||||
## Installation
|
||||
|
||||
To download TagStudio, visit the [Releases](https://github.com/TagStudioDev/TagStudio/releases) section of the GitHub repository and download the latest release for your system under the "Assets" section. TagStudio is available for **Windows**, **macOS** _(Apple Silicon & Intel)_, and **Linux**. Windows and Linux builds are also available in portable versions if you want a more self-contained executable to move around.
|
||||
To download executable builds of TagStudio, visit the [Releases](https://github.com/TagStudioDev/TagStudio/releases) page of the GitHub repository and download the latest release for your system under the "Assets" section at the bottom of the release.
|
||||
|
||||
**We do not currently publish TagStudio to any package managers. Any TagStudio distributions outside of the GitHub 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.
|
||||
TagStudio has builds for **Windows**, **macOS** _(Apple Silicon & Intel)_, and **Linux**. We also offer portable releases for Windows and Linux which are self-contained and easier to move around.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> On macOS, you may be met with a message saying _""TagStudio" can't be opened because Apple cannot check it for malicious software."_ If you encounter this, then you'll need to go to the "Settings" app, navigate to "Privacy & Security", and scroll down to a section that says _""TagStudio" was blocked from use because it is not from an identified developer."_ Click the "Open Anyway" button to allow TagStudio to run. You should only have to do this once after downloading the application.
|
||||
For detailed instructions, installation help, and instructions for developing for TagStudio, please see the "[Installation](https://docs.tagstud.io/install/)" page on our documentation website.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> On Linux with non-Qt based Desktop Environments you may be unable to open TagStudio. You need to make sure that "xcb-cursor0" or "libxcb-cursor0" packages are installed. For more info check [Missing linux dependencies](https://github.com/TagStudioDev/TagStudio/discussions/182#discussioncomment-9452896)
|
||||
<!-- 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!
|
||||
|
||||
### Third-Party Dependencies
|
||||
|
||||
- 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.
|
||||
|
||||
### Optional Arguments
|
||||
|
||||
Arguments available to pass to the program, either via the command line or a shortcut.
|
||||
|
||||
> `--open <path>` / `-o <path>`
|
||||
> Path to a TagStudio Library folder to open on start.
|
||||
>
|
||||
> `--config-file <path>` / `-c <path>`
|
||||
> Path to the TagStudio config file to load.
|
||||
For video thumbnails and playback, you'll also need [FFmpeg](https://ffmpeg.org/download.html) installed on your system. If you encounter any issues with this, please reference our [FFmpeg Help](/docs/help/ffmpeg.md) guide.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -136,13 +128,13 @@ Libraries under 10,000 files automatically scan for new or modified files when o
|
||||
|
||||
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 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!
|
||||
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 you’d like to add to the entry
|
||||
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 you’d like to add to the entry
|
||||
|
||||
### Editing Metadata Fields
|
||||
|
||||
@@ -172,11 +164,11 @@ You can manage your library of tags from opening the "Tag Manager" panel from Ed
|
||||
|
||||
### 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.
|
||||
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.
|
||||
Inevitably some of the files inside your library will be renamed, moved, or deleted. If a file has been renamed or moved, TagStudio will display the thumbnail as a red broken chain link. To relink moved files or delete these entries, select the "Manage Unlinked Entries" option under the Tools menu. Click the "Refresh" button to scan your library for unlinked entries. Once complete, you can attempt to "Search & Relink" any unlinked file entries to their respective files, or "Delete Unlinked Entries" in the event the original files have been deleted and you no longer wish to keep their entries inside your library.
|
||||
|
||||
> [!WARNING]
|
||||
> There is currently no method to relink entries to files that have been renamed - only moved or deleted. This is a high priority for future releases.
|
||||
@@ -194,7 +186,7 @@ These features were present in pre-public versions of TagStudio (9.0 and below)
|
||||
|
||||
#### 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, it’s a pretty roundabout process and can be streamlined in the future.
|
||||
|
||||
@@ -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
|
||||
@@ -43,7 +43,7 @@ FFmpeg is available under the macOS section of the [FFmpeg website](https://www.
|
||||
|
||||
### Package Managers
|
||||
|
||||
FFmpeg may be installed by default on some Linux distributions, but if not, it is available via your distro's package manager of choice:
|
||||
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`)
|
||||
|
||||
@@ -4,21 +4,21 @@ title: Home
|
||||
|
||||
# Welcome to the TagStudio Documentation!
|
||||
|
||||

|
||||

|
||||
|
||||
TagStudio is a photo & file organization application with an underlying tag-based system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure.
|
||||
|
||||
<figure width="60%" markdown="span">
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
<figcaption>TagStudio Alpha v9.5.0 running on macOS Sequoia.</figcaption>
|
||||
|
||||
</figure>
|
||||
|
||||
## Feature Roadmap
|
||||
|
||||
The [Feature Roadmap](updates/roadmap.md) lists all of the planned core features for TagStudio to be considered "feature complete" along with estimated release milestones. The development and testing of these features takes priority over all other requested or submitted features unless they are later added to this roadmap. This helps ensure that TagStudio eventually sees a full release and becomes more usable by more people more quickly.
|
||||
The [Feature Roadmap](./updates/roadmap.md) lists all of the planned core features for TagStudio to be considered "feature complete" along with estimated release milestones. The development and testing of these features takes priority over all other requested or submitted features unless they are later added to this roadmap. This helps ensure that TagStudio eventually sees a full release and becomes more usable by more people more quickly.
|
||||
|
||||
## Current Features
|
||||
|
||||
@@ -32,8 +32,8 @@ The [Feature Roadmap](updates/roadmap.md) lists all of the planned core features
|
||||
- Add custom powerful [tags](./library/tag.md) to your library entries
|
||||
- Add [metadata fields](./library/field.md) to your library entries, including:
|
||||
- Name, Author, Artist (Single-Line Text Fields)
|
||||
- Description, Notes (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.
|
||||
- Description, Notes (Multi-Line Text Fields)
|
||||
- Create rich tags composed of a name, color, a list of aliases, and a list of "parent tags" - these being tags in which these tags inherit values from.
|
||||
- Copy and paste tags and fields across file entries
|
||||
- Automatically organize tags into groups based on parent tags marked as "categories"
|
||||
- Generate tags from your existing folder structure with the "Folders to Tags" macro (NOTE: these tags do NOT sync with folders after they are created)
|
||||
@@ -41,7 +41,7 @@ The [Feature Roadmap](updates/roadmap.md) lists all of the planned core features
|
||||
### Search
|
||||
|
||||
- [Search](./library/library_search.md) for file entries based on tags, file path (`path:`), file types (`filetype:`), and even media types! (`mediatype:`)
|
||||
- Use and combine boolean operators (`AND`, `OR`, `NOT`) along with parentheses groups, quotation escaping, and underscore substitution to create detailed search queries
|
||||
- Use and combine Boolean operators (`AND`, `OR`, `NOT`) along with parentheses groups, quotation escaping, and underscore substitution to create detailed search queries
|
||||
- Use special search conditions (`special:untagged`) to find file entries without tags or fields, respectively
|
||||
|
||||
### File Entries
|
||||
|
||||
370
docs/install.md
370
docs/install.md
@@ -1,27 +1,373 @@
|
||||
# Installation
|
||||
|
||||
To download TagStudio, visit the [Releases](https://github.com/TagStudioDev/TagStudio/releases) section of the GitHub repository and download the latest release for your system under the "Assets" section. TagStudio is available for **Windows**, **macOS** _(Apple Silicon & Intel)_, and **Linux**. Windows and Linux builds are also available in portable versions if you want a more self-contained executable to move around.
|
||||
## Releases
|
||||
|
||||
**We do not currently publish TagStudio to any package managers. Any TagStudio distributions outside of the GitHub 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.
|
||||
TagStudio provides executable [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.
|
||||
|
||||
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 "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.
|
||||
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 -->
|
||||
!!! info "For Linux Users"
|
||||
On Linux with non-Qt based Desktop Environments you may be unable to open TagStudio. You need to make sure that "xcb-cursor0" or "libxcb-cursor0" packages are installed. For more info check [Missing linux dependencies](https://github.com/TagStudioDev/TagStudio/discussions/182#discussioncomment-9452896)
|
||||
!!! 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 "[Creating a Development Environment](#creating-a-development-environment)"_
|
||||
|
||||
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
|
||||
];
|
||||
};
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
=== "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
|
||||
];
|
||||
}
|
||||
```
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
=== "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!
|
||||
|
||||
## 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.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! tip "Contributing"
|
||||
If you wish to contribute to TagStudio's development, please read our [CONTRIBUTING.md](https://github.com/TagStudioDev/TagStudio/blob/main/CONTRIBUTING.md)!
|
||||
|
||||
### Install Python
|
||||
|
||||
Python [3.12](https://www.python.org/downloads/) is required to develop for TagStudio. Any version matching "Python 3.12.x" should work, with "x" being any number. Alternatively you can use a tool such as [pyenv](https://github.com/pyenv/pyenv/) to install this version of Python without affecting any existing Python installations on your system.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! info "Python Aliases"
|
||||
Depending on your system, Python may be called `python`, `py`, `python3`, or `py3`. These instructions use the alias `python` for consistency.
|
||||
|
||||
If you already have Python installed on your system, you can check the version by running the following command:
|
||||
|
||||
```sh
|
||||
python --version
|
||||
```
|
||||
|
||||
#### Installing with pyenv
|
||||
|
||||
If you choose to install Python using pyenv, please refer to the following instructions:
|
||||
|
||||
1. Follow pyenv's [install instructions](https://github.com/pyenv/pyenv/?tab=readme-ov-file#installation) for your system.
|
||||
2. Install the appropriate Python version with pyenv by running `pyenv install 3.12` (This will **not** mess with your existing Python installation).
|
||||
3. Navigate to the repository root folder in your terminal and run `pyenv local 3.12`. You could alternatively use `pyenv shell 3.12` or `pyenv global 3.12` instead to set the Python version for the current terminal session or the entire system respectively, however using `local` is recommended.
|
||||
|
||||
### Installing Dependencies
|
||||
|
||||
To install the required dependencies, you can use a dependency manager such as [uv](https://docs.astral.sh/uv) or [Poetry 2.0](https://python-poetry.org). Alternatively you can create a virtual environment and manually install the dependencies yourself.
|
||||
|
||||
#### Installing with uv
|
||||
|
||||
If using [uv](https://docs.astral.sh/uv), you can install the dependencies for TagStudio with the following command:
|
||||
|
||||
```sh
|
||||
uv pip install -e .[dev]
|
||||
```
|
||||
|
||||
#### 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
|
||||
```
|
||||
|
||||
#### Installing with Nix
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
You can automatically enter this development shell, and keep your user shell, with a tool like [direnv](https://direnv.net/). A reference `.envrc` is provided in the repository; to use it:
|
||||
|
||||
```sh
|
||||
ln -s .envrc.recommended .envrc
|
||||
```
|
||||
|
||||
You will have to allow usage of it.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
!!! warning "`.envrc` Security"
|
||||
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
|
||||
```
|
||||
|
||||
#### 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]
|
||||
```
|
||||
|
||||
### Launching
|
||||
|
||||
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](#launch-arguments) to pass your own test [libraries](./library/index.md) to use while developing.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
=== "VS Code"
|
||||
```json title=".vscode/launch.json"
|
||||
{
|
||||
// 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}/src/tagstudio/main.py",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true,
|
||||
"args": ["-o", "~/Documents/Example"]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To build your own executables of TagStudio, first follow the steps in "[Installing with PIP](#installing-with-pip)" including the developer dependencies step. 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".
|
||||
|
||||
## Third-Party Dependencies
|
||||
|
||||
- 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](./help/ffmpeg.md) guide.
|
||||
For audio/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](./help/ffmpeg.md) guide.
|
||||
|
||||
## Optional Arguments
|
||||
You can check to see if FFmpeg and FFprobe are correctly located by launching TagStudio and going to "About TagStudio" in the menu bar.
|
||||
|
||||
Optional arguments to pass to the program:
|
||||
## Launch Arguments
|
||||
|
||||
`--open <path>` / `-o <path>`
|
||||
: Path to a TagStudio Library folder to open on start.
|
||||
There are a handful of launch arguments you can pass to TagStudio via the command line or a desktop shortcut.
|
||||
|
||||
`--config-file <path>` / `-c <path>`
|
||||
: Path to the TagStudio config file to load.
|
||||
| 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. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File Entries
|
||||
|
||||
File entries are the individual representations of your files inside a TagStudio [library](index.md). Each one corresponds one-to-one to a file on disk, and tracks all of the additional [tags](tag.md) and metadata that you attach to it inside TagStudio.
|
||||
File entries are the individual representations of your files inside a TagStudio [library](./index.md). Each one corresponds one-to-one to a file on disk, and tracks all of the additional [tags](tag.md) and metadata that you attach to it inside TagStudio.
|
||||
|
||||
## Storage
|
||||
|
||||
@@ -14,7 +14,7 @@ File entries appear as file previews both inside the thumbnail grid. The preview
|
||||
|
||||
If the file that an entry is referencing has been moved, renamed, or deleted on disk, then TagStudio will display a red chain-link icon for the thumbnail image. Certain uncached stats such as the file size and image dimensions will also be unavailable to see in the preview panel when a file becomes unlinked.
|
||||
|
||||
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 entires from your library. This will NOT delete or modify any files on disk.
|
||||
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
|
||||
|
||||
|
||||
@@ -5,4 +5,4 @@ tags:
|
||||
|
||||
# Entry Groups
|
||||
|
||||
Entries can be grouped via tags marked as “groups” which when applied to different entries will signal TagStudio to treat those entries as a single group inside of searches and browsing.
|
||||
Entries can be grouped via tags marked as "groups" which when applied to different entries will signal TagStudio to treat those entries as a single group inside of searches and browsing.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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.
|
||||
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
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Library
|
||||
|
||||
The library is how TagStudio represents your chosen directory, with every file inside being represented by a [file entry](entry.md). You can have as many or few libraries as you wish, since each libraries' data is stored within a `.TagStudio` folder at its root. From there the library save file itself is stored as `ts_library.sqlite`, with TagStudio versions 9.4 and below using a the legacy `ts_library.json` format.
|
||||
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_.
|
||||
Note that this means [tags](./tag.md) you create only exist _per-library_.
|
||||
|
||||
@@ -4,11 +4,11 @@ TagStudio provides various methods to search your library, ranging from TagStudi
|
||||
|
||||
## 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.
|
||||
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.
|
||||
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
|
||||
@@ -74,7 +74,7 @@ TagStudio uses a "[smartcase](https://neovim.io/doc/user/options.html#'smartcase
|
||||
|
||||
#### Glob Syntax
|
||||
|
||||
Optionally, you may use [glob](<https://en.wikipedia.org/wiki/Glob_(programming)>) syntax to search filepaths.
|
||||
Optionally, you may use [glob](https://en.wikipedia.org/wiki/Glob_(programming)) syntax to search filepaths.
|
||||
|
||||
#### Examples
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ In a system where tags have no relationships, you're required to add as many tag
|
||||
|
||||
#### Intuition via Substitution
|
||||
|
||||
Now when searching for 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!
|
||||
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
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ tags:
|
||||
|
||||
# Tag Overrides
|
||||
|
||||
Tag overrides are the ability to add or remove [parent tags](tag.md#parent-tags) from a [tag](tag.md) on a per- [entry](entry.md) basis.
|
||||
Tag overrides are the ability to add or remove [parent tags](./tag.md#parent-tags) from a [tag](./tag.md) on a per-[entry](./entry.md) basis.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
3
docs/stylesheets/extra.css
Normal file
3
docs/stylesheets/extra.css
Normal file
@@ -0,0 +1,3 @@
|
||||
th, td {
|
||||
padding: 0.5em 1em 0.5em 1em !important;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
# Feature Roadmap
|
||||
|
||||
This checklist details the current and remaining features required at a minimum for TagStudio to be considered “Feature Complete”. This list is _not_ a definitive list for additional feature requests and PRs as they come in, but rather an outline of my personal core feature set intended for TagStudio.
|
||||
This checklist details the current and remaining features required at a minimum for TagStudio to be considered "Feature Complete". This list is _not_ a definitive list for additional feature requests and PRs as they come in, but rather an outline of my personal core feature set intended for TagStudio.
|
||||
|
||||
## Priorities
|
||||
|
||||
@@ -39,7 +39,7 @@ These version milestones are rough estimations for when the previous core featur
|
||||
|
||||
- [x] Boolean operators [HIGH]
|
||||
- [x] Filename search [HIGH]
|
||||
- [x] Filetype search [HIGH]
|
||||
- [x] File type search [HIGH]
|
||||
- [x] Search by extension (e.g. ".jpg", ".png") [HIGH]
|
||||
- [x] Optional consolidation of extension synonyms (i.e. ".jpg" can equal ".jpeg") [LOW]
|
||||
- [x] Search by media type (e.g. "image", "video", "document") [MEDIUM]
|
||||
|
||||
@@ -43,4 +43,4 @@ Migration from the legacy JSON format is provided via a walkthrough when opening
|
||||
|
||||
- Adds the `color_border` column to `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 the new `color_border` property.
|
||||
- Updates Neon colors to use the new `color_border` property.
|
||||
|
||||
@@ -12,13 +12,13 @@ Libraries under 10,000 files automatically scan for new or modified files when o
|
||||
|
||||
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 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!
|
||||
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 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 you’d like to add to the entry
|
||||
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 you’d like to add to the entry
|
||||
|
||||
## Editing Metadata Fields
|
||||
|
||||
@@ -33,7 +33,7 @@ Create a new tag by accessing the "New Tag" option from the Edit menu or by pres
|
||||
- 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** 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.
|
||||
@@ -45,11 +45,11 @@ You can manage your library of tags from opening the "Tag Manager" panel from Ed
|
||||
|
||||
## 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.
|
||||
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.
|
||||
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
|
||||
|
||||
502
flake.lock
generated
502
flake.lock
generated
@@ -1,132 +1,5 @@
|
||||
{
|
||||
"nodes": {
|
||||
"cachix": {
|
||||
"inputs": {
|
||||
"devenv": "devenv_2",
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"pre-commit-hooks": [
|
||||
"devenv",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712055811,
|
||||
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"cachix": "cachix",
|
||||
"flake-compat": "flake-compat_2",
|
||||
"nix": "nix_2",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"pre-commit-hooks": "pre-commit-hooks"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724763216,
|
||||
"narHash": "sha256-oW2bwCrJpIzibCNK6zfIDaIQw765yMAuMSG2gyZfGv0=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "1e4ef61205b9aa20fe04bf1c468b6a316281c4f1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv-root": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"narHash": "sha256-d6xi4mKdjkX2JFicDIv5niSzpyI0m/Hnm8GGAIU04kY=",
|
||||
"type": "file",
|
||||
"url": "file:///dev/null"
|
||||
},
|
||||
"original": {
|
||||
"type": "file",
|
||||
"url": "file:///dev/null"
|
||||
}
|
||||
},
|
||||
"devenv_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"flake-compat"
|
||||
],
|
||||
"nix": "nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"poetry2nix": "poetry2nix",
|
||||
"pre-commit-hooks": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"pre-commit-hooks"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1708704632,
|
||||
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "python-rewrite",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1673956053,
|
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat_2": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
@@ -147,354 +20,44 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1689068808,
|
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_3": {
|
||||
"inputs": {
|
||||
"systems": "systems_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710146030,
|
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"pre-commit-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-github-actions": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"poetry2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1688870561,
|
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-github-actions",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix2container": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_3",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1724996935,
|
||||
"narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "fa6bb0a1159f55d071ba99331355955ae30b3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix_2": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-regression": "nixpkgs-regression_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1712911606,
|
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=",
|
||||
"owner": "domenkozar",
|
||||
"repo": "nix",
|
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "domenkozar",
|
||||
"ref": "devenv-2.21",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1692808169,
|
||||
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=",
|
||||
"lastModified": 1741173522,
|
||||
"narHash": "sha256-k7VSqvv0r1r53nUI/IfPHCppkUAddeXn843YlAC5DR0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8",
|
||||
"rev": "d69ab0d71b22fa1ce3dbeff666e6deb4917db049",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-qt6": {
|
||||
"locked": {
|
||||
"lastModified": 1718428119,
|
||||
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
||||
"lastModified": 1734856068,
|
||||
"narHash": "sha256-Q+CB1ajsJg4Z9HGHTBAGY1q18KpnnkmF/eCTLUY6FQ0=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
||||
"rev": "93ff48c9be84a76319dac293733df09bbbe3f25c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-regression_2": {
|
||||
"locked": {
|
||||
"lastModified": 1643052045,
|
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1710695816,
|
||||
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "614b4613980a522ba49f0d194531beddbb7220d3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-23.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1713361204,
|
||||
"narHash": "sha256-TA6EDunWTkc5FvDCqU3W2T3SFn0gRZqh6D/hJnM02MM=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1724819573,
|
||||
"narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "71e91c409d1e654808b2621f28a327acfdad8dc2",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"poetry2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-github-actions": "nix-github-actions",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"cachix",
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692876271,
|
||||
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "poetry2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"pre-commit-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-utils": "flake-utils_2",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1713775815,
|
||||
"narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "93ff48c9be84a76319dac293733df09bbbe3f25c",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"devenv-root": "devenv-root",
|
||||
"flake-parts": "flake-parts",
|
||||
"nix2container": "nix2container",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs-qt6": "nixpkgs-qt6",
|
||||
"systems": "systems_4"
|
||||
"systems": "systems"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
@@ -511,51 +74,6 @@
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_3": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_4": {
|
||||
"locked": {
|
||||
"lastModified": 1689347949,
|
||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default-linux",
|
||||
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default-linux",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
||||
224
flake.nix
224
flake.nix
@@ -2,209 +2,69 @@
|
||||
description = "TagStudio";
|
||||
|
||||
inputs = {
|
||||
devenv.url = "github:cachix/devenv";
|
||||
|
||||
devenv-root = {
|
||||
url = "file+file:///dev/null";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
flake-parts = {
|
||||
url = "github:hercules-ci/flake-parts";
|
||||
inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
nix2container = {
|
||||
url = "github:nlewo/nix2container";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
nixpkgs-qt6.url = "github:NixOS/nixpkgs/93ff48c9be84a76319dac293733df09bbbe3f25c";
|
||||
|
||||
# Pinned to Qt version 6.7.1
|
||||
nixpkgs-qt6.url = "github:NixOS/nixpkgs/e6cea36f83499eb4e9cd184c8a8e823296b50ad5";
|
||||
|
||||
systems.url = "github:nix-systems/default-linux";
|
||||
systems.url = "github:nix-systems/default";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
inputs@{
|
||||
flake-parts,
|
||||
nixpkgs,
|
||||
nixpkgs-qt6,
|
||||
self,
|
||||
systems,
|
||||
...
|
||||
}@inputs:
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
in
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
imports = [ inputs.devenv.flakeModule ];
|
||||
|
||||
systems = import systems;
|
||||
systems = import inputs.systems;
|
||||
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
packages =
|
||||
let
|
||||
python = pkgs.python312Packages;
|
||||
|
||||
qt6Pkgs = import nixpkgs-qt6 { inherit system; };
|
||||
in
|
||||
{
|
||||
formatter = pkgs.nixfmt-rfc-style;
|
||||
|
||||
devenv.shells = rec {
|
||||
default = tagstudio;
|
||||
|
||||
tagstudio =
|
||||
let
|
||||
cfg = config.devenv.shells.tagstudio;
|
||||
in
|
||||
{
|
||||
# NOTE: many things were simply transferred over from previous,
|
||||
# there must be additional work in ensuring all relevant dependencies
|
||||
# are in place (and no extraneous). I have already spent much
|
||||
# work making this in the first place and just need to get it out
|
||||
# there, especially after my promises. Would appreciate any help
|
||||
# (possibly PRs!) on taking care of this. Otherwise, just expect
|
||||
# this to get ironed out over time.
|
||||
#
|
||||
# Thank you! -Xarvex
|
||||
|
||||
devenv.root =
|
||||
let
|
||||
devenvRoot = builtins.readFile inputs.devenv-root.outPath;
|
||||
in
|
||||
# If not overriden (/dev/null), --impure is necessary.
|
||||
pkgs.lib.mkIf (devenvRoot != "") devenvRoot;
|
||||
|
||||
name = "TagStudio";
|
||||
|
||||
# Derived from previous flake iteration.
|
||||
packages =
|
||||
(with pkgs; [
|
||||
cmake
|
||||
binutils
|
||||
coreutils
|
||||
dbus
|
||||
fontconfig
|
||||
freetype
|
||||
gdb
|
||||
glib
|
||||
libGL
|
||||
libGLU
|
||||
libgcc
|
||||
libxkbcommon
|
||||
mypy
|
||||
ruff
|
||||
xorg.libxcb
|
||||
xorg.libX11
|
||||
zstd
|
||||
])
|
||||
++ (with qt6Pkgs; [
|
||||
qt6.full
|
||||
qt6.qtbase
|
||||
qt6.qtwayland
|
||||
qtcreator
|
||||
]);
|
||||
|
||||
enterShell =
|
||||
let
|
||||
setQtEnv =
|
||||
pkgs.runCommand "set-qt-env"
|
||||
{
|
||||
buildInputs = with qt6Pkgs.qt6; [
|
||||
qtbase
|
||||
];
|
||||
|
||||
nativeBuildInputs =
|
||||
(with pkgs; [
|
||||
makeShellWrapper
|
||||
])
|
||||
++ (with qt6Pkgs.qt6; [
|
||||
wrapQtAppsHook
|
||||
]);
|
||||
}
|
||||
''
|
||||
makeShellWrapper "$(type -p sh)" "$out" "''${qtWrapperArgs[@]}"
|
||||
sed "/^exec/d" -i "$out"
|
||||
'';
|
||||
in
|
||||
''
|
||||
source ${setQtEnv}
|
||||
'';
|
||||
|
||||
scripts.tagstudio.exec = ''
|
||||
python ${cfg.devenv.root}/tagstudio/tag_studio.py
|
||||
'';
|
||||
|
||||
env = {
|
||||
QT_QPA_PLATFORM = "wayland;xcb";
|
||||
|
||||
# Derived from previous flake iteration.
|
||||
# Not desired given LD_LIBRARY_PATH pollution.
|
||||
# See supposed alternative below, further research required.
|
||||
LD_LIBRARY_PATH = lib.makeLibraryPath (
|
||||
(with pkgs; [
|
||||
dbus
|
||||
fontconfig
|
||||
freetype
|
||||
gcc-unwrapped
|
||||
glib
|
||||
libglvnd
|
||||
libkrb5
|
||||
libpulseaudio
|
||||
libva
|
||||
libxkbcommon
|
||||
openssl
|
||||
stdenv.cc.cc.lib
|
||||
wayland
|
||||
xorg.libxcb
|
||||
xorg.libX11
|
||||
xorg.libXrandr
|
||||
zlib
|
||||
zstd
|
||||
])
|
||||
++ (with qt6Pkgs.qt6; [
|
||||
qtbase
|
||||
qtwayland
|
||||
full
|
||||
])
|
||||
);
|
||||
};
|
||||
|
||||
languages.python = {
|
||||
enable = true;
|
||||
venv = {
|
||||
enable = true;
|
||||
quiet = true;
|
||||
requirements =
|
||||
let
|
||||
excludeDeps =
|
||||
req: deps:
|
||||
builtins.concatStringsSep "\n" (
|
||||
builtins.filter (line: !(lib.any (elem: lib.hasPrefix elem line) deps)) (lib.splitString "\n" req)
|
||||
);
|
||||
in
|
||||
''
|
||||
${builtins.readFile ./requirements.txt}
|
||||
${excludeDeps (builtins.readFile ./requirements-dev.txt) [
|
||||
"mypy"
|
||||
"ruff"
|
||||
]}
|
||||
'';
|
||||
};
|
||||
|
||||
# Should be able to replace LD_LIBRARY_PATH?
|
||||
# Was not quite able to get working,
|
||||
# will be consulting cachix community. -Xarvex
|
||||
# libraries = with pkgs; [ ];
|
||||
};
|
||||
pillow-jxl-plugin = python.callPackage ./nix/package/pillow-jxl-plugin.nix {
|
||||
inherit (pkgs) cmake;
|
||||
inherit pyexiv2;
|
||||
inherit (pkgs) rustPlatform;
|
||||
};
|
||||
pyexiv2 = python.callPackage ./nix/package/pyexiv2.nix { inherit (pkgs) exiv2; };
|
||||
vtf2img = python.callPackage ./nix/package/vtf2img.nix { };
|
||||
in
|
||||
rec {
|
||||
default = tagstudio;
|
||||
tagstudio = pkgs.python312Packages.callPackage ./nix/package {
|
||||
inherit pillow-jxl-plugin vtf2img;
|
||||
};
|
||||
tagstudio-jxl = tagstudio.override { withJXLSupport = true; };
|
||||
|
||||
inherit pillow-jxl-plugin pyexiv2 vtf2img;
|
||||
};
|
||||
|
||||
devShells = rec {
|
||||
default = tagstudio;
|
||||
tagstudio = import ./nix/shell.nix {
|
||||
inherit
|
||||
inputs
|
||||
lib
|
||||
pkgs
|
||||
self
|
||||
;
|
||||
};
|
||||
};
|
||||
|
||||
formatter = pkgs.nixfmt-rfc-style;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ theme:
|
||||
#- navigation.top
|
||||
- search.suggest
|
||||
- content.code.annotate
|
||||
- content.code.copy
|
||||
- content.action.edit
|
||||
icon:
|
||||
repo: fontawesome/brands/github
|
||||
@@ -136,3 +137,6 @@ plugins:
|
||||
- tags
|
||||
- social: # social embed cards
|
||||
enabled: !ENV [CI, false] # enabled only when running in CI (eg GitHub Actions)
|
||||
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
|
||||
114
nix/package/default.nix
Normal file
114
nix/package/default.nix
Normal file
@@ -0,0 +1,114 @@
|
||||
{
|
||||
buildPythonApplication,
|
||||
chardet,
|
||||
ffmpeg-headless,
|
||||
ffmpeg-python,
|
||||
hatchling,
|
||||
humanfriendly,
|
||||
lib,
|
||||
mutagen,
|
||||
numpy,
|
||||
opencv-python,
|
||||
pillow,
|
||||
pillow-heif,
|
||||
pillow-jxl-plugin,
|
||||
pipewire,
|
||||
pydub,
|
||||
pyside6,
|
||||
pytest-qt,
|
||||
pytest-xdist,
|
||||
pytestCheckHook,
|
||||
pythonRelaxDepsHook,
|
||||
qt6,
|
||||
rawpy,
|
||||
send2trash,
|
||||
sqlalchemy,
|
||||
stdenv,
|
||||
structlog,
|
||||
syrupy,
|
||||
ujson,
|
||||
vtf2img,
|
||||
|
||||
withJXLSupport ? false,
|
||||
}:
|
||||
|
||||
let
|
||||
pyproject = (lib.importTOML ../../pyproject.toml).project;
|
||||
in
|
||||
buildPythonApplication {
|
||||
pname = pyproject.name;
|
||||
inherit (pyproject) version;
|
||||
pyproject = true;
|
||||
|
||||
src = ../../.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pythonRelaxDepsHook
|
||||
qt6.wrapQtAppsHook
|
||||
];
|
||||
buildInputs = [
|
||||
qt6.qtbase
|
||||
qt6.qtmultimedia
|
||||
];
|
||||
|
||||
nativeCheckInputs = [
|
||||
pytest-qt
|
||||
pytest-xdist
|
||||
pytestCheckHook
|
||||
syrupy
|
||||
];
|
||||
|
||||
makeWrapperArgs =
|
||||
[ "--prefix PATH : ${lib.makeBinPath [ ffmpeg-headless ]}" ]
|
||||
++ lib.optional stdenv.hostPlatform.isLinux "--prefix LD_LIBRARY_PATH : ${
|
||||
lib.makeLibraryPath [ pipewire ]
|
||||
}";
|
||||
|
||||
pythonRemoveDeps = true;
|
||||
pythonImportsCheck = [ "tagstudio" ];
|
||||
|
||||
build-system = [ hatchling ];
|
||||
dependencies = [
|
||||
chardet
|
||||
ffmpeg-python
|
||||
humanfriendly
|
||||
mutagen
|
||||
numpy
|
||||
opencv-python
|
||||
pillow
|
||||
pillow-heif
|
||||
pydub
|
||||
pyside6
|
||||
rawpy
|
||||
send2trash
|
||||
sqlalchemy
|
||||
structlog
|
||||
ujson
|
||||
vtf2img
|
||||
] ++ lib.optional withJXLSupport pillow-jxl-plugin;
|
||||
|
||||
# INFO: These tests require modifications to a library, which does not work
|
||||
# in a read-only environment.
|
||||
disabledTests = [
|
||||
"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"
|
||||
];
|
||||
|
||||
meta = {
|
||||
inherit (pyproject) description;
|
||||
homepage = "https://docs.tagstud.io/";
|
||||
license = lib.licenses.gpl3Only;
|
||||
maintainers = with lib.maintainers; [ xarvex ];
|
||||
mainProgram = "tagstudio";
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
}
|
||||
67
nix/package/pillow-jxl-plugin.nix
Normal file
67
nix/package/pillow-jxl-plugin.nix
Normal file
@@ -0,0 +1,67 @@
|
||||
{
|
||||
buildPythonPackage,
|
||||
cmake,
|
||||
fetchPypi,
|
||||
lib,
|
||||
numpy,
|
||||
packaging,
|
||||
pillow,
|
||||
pyexiv2,
|
||||
pytestCheckHook,
|
||||
rustPlatform,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "pillow-jxl-plugin";
|
||||
version = "1.3.2";
|
||||
pyproject = true;
|
||||
|
||||
src = fetchPypi {
|
||||
pname = builtins.replaceStrings [ "-" ] [ "_" ] pname;
|
||||
inherit version;
|
||||
hash = "sha256-efBoek8yUFR+ArhS55lm9F2XhkZ7/I3GsScQEe8U/2I=";
|
||||
};
|
||||
|
||||
cargoDeps = rustPlatform.fetchCargoVendor {
|
||||
inherit src;
|
||||
hash = "sha256-vZHrwGfgo3fIIOY7p0vy4XIKiHoddPDdJggkBen+w/A=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
cmake
|
||||
rustPlatform.cargoSetupHook
|
||||
rustPlatform.maturinBuildHook
|
||||
];
|
||||
|
||||
nativeCheckInputs = [
|
||||
numpy
|
||||
pyexiv2
|
||||
pytestCheckHook
|
||||
];
|
||||
|
||||
# INFO: Working directory takes precedence in the Python path. Remove
|
||||
# `pillow_jxl` to prevent it from being loaded during pytest, rather than the
|
||||
# built module, as it includes a `pillow_jxl.pillow_jxl` .so that is imported.
|
||||
# See: https://github.com/NixOS/nixpkgs/issues/255262
|
||||
# See: https://github.com/NixOS/nixpkgs/pull/255471
|
||||
preCheck = ''
|
||||
rm -r pillow_jxl
|
||||
'';
|
||||
|
||||
dontUseCmakeConfigure = true;
|
||||
|
||||
pythonImportsCheck = [ "pillow_jxl" ];
|
||||
|
||||
dependencies = [
|
||||
packaging
|
||||
pillow
|
||||
];
|
||||
|
||||
meta = {
|
||||
description = "Pillow plugin for JPEG-XL, using Rust for bindings.";
|
||||
homepage = "https://github.com/Isotr0py/pillow-jpegxl-plugin";
|
||||
license = lib.licenses.gpl3;
|
||||
maintainers = with lib.maintainers; [ xarvex ];
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
}
|
||||
32
nix/package/pyexiv2.nix
Normal file
32
nix/package/pyexiv2.nix
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
autoPatchelfHook,
|
||||
buildPythonPackage,
|
||||
exiv2,
|
||||
fetchFromGitHub,
|
||||
lib,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "pyexiv2";
|
||||
version = "2.15.3";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "LeoHsiao1";
|
||||
repo = pname;
|
||||
rev = "v${version}";
|
||||
hash = "sha256-83bFMaoXncvhRJNcCgkkC7B29wR5pjuLO/EdkQdqxxo=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ autoPatchelfHook ];
|
||||
buildInputs = [ exiv2.lib ];
|
||||
|
||||
pythonImportsCheck = [ "pyexiv2" ];
|
||||
|
||||
meta = {
|
||||
description = "Read and write image metadata, including EXIF, IPTC, XMP, ICC Profile.";
|
||||
homepage = "https://github.com/LeoHsiao1/pyexiv2";
|
||||
license = lib.licenses.gpl3;
|
||||
maintainers = with lib.maintainers; [ xarvex ];
|
||||
platforms = with lib.platforms; darwin ++ linux ++ windows;
|
||||
};
|
||||
}
|
||||
29
nix/package/vtf2img.nix
Normal file
29
nix/package/vtf2img.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
buildPythonPackage,
|
||||
fetchPypi,
|
||||
lib,
|
||||
pillow,
|
||||
}:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "vtf2img";
|
||||
version = "0.1.0";
|
||||
|
||||
src = fetchPypi {
|
||||
inherit pname version;
|
||||
hash = "sha256-YmWs8673d72wH4nTOXP4AFGs2grIETln4s1MD5PfE0A=";
|
||||
};
|
||||
|
||||
pythonImportsCheck = [ "vtf2img" ];
|
||||
|
||||
dependencies = [ pillow ];
|
||||
|
||||
meta = {
|
||||
description = "A Python library to convert Valve Texture Format (VTF) files to images.";
|
||||
homepage = "https://github.com/julienc91/vtf2img";
|
||||
license = lib.licenses.mit;
|
||||
maintainers = with lib.maintainers; [ xarvex ];
|
||||
mainProgram = "vtf2img";
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
}
|
||||
93
nix/shell.nix
Normal file
93
nix/shell.nix
Normal file
@@ -0,0 +1,93 @@
|
||||
{
|
||||
inputs,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
qt6Pkgs = import inputs.nixpkgs-qt6 { inherit (pkgs) system; };
|
||||
|
||||
pythonLibraryPath = lib.makeLibraryPath (
|
||||
(with pkgs; [
|
||||
fontconfig.lib
|
||||
freetype
|
||||
glib
|
||||
stdenv.cc.cc.lib
|
||||
zstd
|
||||
])
|
||||
++ (with qt6Pkgs.qt6; [ qtbase ])
|
||||
++ lib.optionals (!pkgs.stdenv.isDarwin) (
|
||||
(with pkgs; [
|
||||
dbus.lib
|
||||
libGL
|
||||
libdrm
|
||||
libpulseaudio
|
||||
libva
|
||||
libxkbcommon
|
||||
pipewire
|
||||
xorg.libX11
|
||||
xorg.libXrandr
|
||||
])
|
||||
++ (with qt6Pkgs.qt6; [ qtwayland ])
|
||||
)
|
||||
);
|
||||
libraryPath = "${lib.optionalString pkgs.stdenv.isDarwin "DY"}LD_LIBRARY_PATH";
|
||||
|
||||
python = pkgs.python312;
|
||||
pythonWrapped = pkgs.symlinkJoin {
|
||||
inherit (python)
|
||||
name
|
||||
pname
|
||||
version
|
||||
meta
|
||||
;
|
||||
|
||||
paths = [ python ];
|
||||
|
||||
nativeBuildInputs = (with pkgs; [ makeWrapper ]) ++ (with qt6Pkgs.qt6; [ wrapQtAppsHook ]);
|
||||
buildInputs = with qt6Pkgs.qt6; [ qtbase ];
|
||||
|
||||
postBuild = ''
|
||||
wrapProgram $out/bin/python3.12 \
|
||||
--prefix ${libraryPath} : ${pythonLibraryPath} \
|
||||
"''${qtWrapperArgs[@]}"
|
||||
'';
|
||||
};
|
||||
in
|
||||
pkgs.mkShellNoCC {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
coreutils
|
||||
|
||||
ruff
|
||||
];
|
||||
buildInputs = [ pythonWrapped ] ++ (with pkgs; [ ffmpeg-headless ]);
|
||||
|
||||
env.QT_QPA_PLATFORM = "wayland;xcb";
|
||||
|
||||
shellHook =
|
||||
let
|
||||
python = lib.getExe pythonWrapped;
|
||||
in
|
||||
# bash
|
||||
''
|
||||
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
|
||||
${python} -m venv .venv
|
||||
fi
|
||||
|
||||
source .venv/bin/activate
|
||||
|
||||
if [ ! -f .venv/pyproject.toml ] || [ "$(cat .venv/pyproject.toml)" != "$(cat pyproject.toml)" ]; then
|
||||
printf '%s\n' 'Installing dependencies, pyproject.toml changed...' >&2
|
||||
pip install --quiet --editable '.[mkdocs,mypy,pytest]'
|
||||
cp pyproject.toml .venv/pyproject.toml
|
||||
fi
|
||||
'';
|
||||
|
||||
meta = {
|
||||
maintainers = with lib.maintainers; [ xarvex ];
|
||||
platforms = lib.platforms.unix;
|
||||
};
|
||||
}
|
||||
116
pyproject.toml
116
pyproject.toml
@@ -1,28 +1,85 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "TagStudio"
|
||||
description = "A User-Focused Photo & File Management System."
|
||||
version = "9.5.1"
|
||||
license = "GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
dependencies = [
|
||||
"chardet==5.2.0",
|
||||
"ffmpeg-python==0.2.0",
|
||||
"humanfriendly==10.0",
|
||||
"mutagen==1.47.0",
|
||||
"numpy==2.1.0",
|
||||
"opencv_python==4.10.0.84",
|
||||
"Pillow==10.3.0",
|
||||
"pillow-heif==0.16.0",
|
||||
"pillow-jxl-plugin==1.3.0",
|
||||
"pydub==0.25.1",
|
||||
"PySide6==6.8.0.1",
|
||||
"rawpy==0.22.0",
|
||||
"Send2Trash==1.8.3",
|
||||
"SQLAlchemy==2.0.34",
|
||||
"structlog==24.4.0",
|
||||
"typing_extensions>=3.10.0.0,<4.11.0",
|
||||
"ujson>=5.8.0,<5.9.0",
|
||||
"vtf2img==0.1.0",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]
|
||||
line-length = 100
|
||||
[project.optional-dependencies]
|
||||
dev = ["pre-commit==3.7.0", "tagstudio[mkdocs,mypy,pyinstaller,pytest,ruff]"]
|
||||
mkdocs = ["mkdocs-material[imaging]==9.*"]
|
||||
mypy = ["mypy==1.11.2", "mypy-extensions==1.*", "types-ujson>=5.8.0,<5.9.0"]
|
||||
pyinstaller = ["Pyinstaller==6.6.0"]
|
||||
pytest = [
|
||||
"pytest==8.2.0",
|
||||
"pytest-cov==5.0.0",
|
||||
"pytest-qt==4.4.0",
|
||||
"syrupy==4.7.1",
|
||||
]
|
||||
ruff = ["ruff==0.8.1"]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tagstudio/tests/**" = ["D", "E402"]
|
||||
"tagstudio/src/qt/helpers/vendored/**" = ["B", "E", "N", "UP", "SIM115"]
|
||||
[project.gui-scripts]
|
||||
tagstudio = "tagstudio.main:main"
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/tagstudio"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["B", "D", "E", "F", "FBT003", "I", "N", "SIM", "T20", "UP"]
|
||||
ignore = ["D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107"]
|
||||
[tool.mypy]
|
||||
mypy_path = ["src/tagstudio"]
|
||||
disable_error_code = [
|
||||
"annotation-unchecked",
|
||||
"func-returns-value",
|
||||
"import-untyped",
|
||||
]
|
||||
explicit_package_bases = true
|
||||
ignore_missing_imports = true
|
||||
implicit_optional = true
|
||||
strict_optional = false
|
||||
warn_unused_ignores = true
|
||||
|
||||
[[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 = ["tagstudio/**"]
|
||||
include = ["src/tagstudio/**"]
|
||||
reportAny = false
|
||||
reportImplicitStringConcatenation = false
|
||||
# reportOptionalMemberAccess = false
|
||||
@@ -31,30 +88,17 @@ reportUnknownArgumentType = false
|
||||
reportUnknownMemberType = false
|
||||
reportUnusedCallResult = false
|
||||
|
||||
[tool.mypy]
|
||||
strict_optional = false
|
||||
disable_error_code = ["func-returns-value", "import-untyped"]
|
||||
explicit_package_bases = true
|
||||
warn_unused_ignores = true
|
||||
check_untyped_defs = true
|
||||
mypy_path = ["tagstudio"]
|
||||
[tool.ruff]
|
||||
exclude = ["main_window.py", "home_ui.py", "resources.py", "resources_rc.py"]
|
||||
line-length = 100
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "tests.*"
|
||||
ignore_errors = true
|
||||
[tool.ruff.lint]
|
||||
select = ["B", "D", "E", "F", "FBT003", "I", "N", "SIM", "T20", "UP"]
|
||||
ignore = ["D100", "D101", "D102", "D103", "D104", "D105", "D106", "D107"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "src.qt.main_window"
|
||||
ignore_errors = true
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/**" = ["D", "E402"]
|
||||
"src/tagstudio/qt/helpers/vendored/**" = ["B", "E", "N", "UP", "SIM115"]
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "src.qt.ui.home_ui"
|
||||
ignore_errors = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = "src.core.ts_core"
|
||||
ignore_errors = true
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
#addopts = "-m 'not qt'"
|
||||
qt_api = "pyside6"
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
ruff==0.8.1
|
||||
pre-commit==3.7.0
|
||||
pytest==8.2.0
|
||||
Pyinstaller==6.6.0
|
||||
mypy==1.11.2
|
||||
syrupy==4.7.1
|
||||
pytest-qt==4.4.0
|
||||
pytest-cov==5.0.0
|
||||
@@ -1,20 +0,0 @@
|
||||
chardet==5.2.0
|
||||
ffmpeg-python==0.2.0
|
||||
humanfriendly==10.0
|
||||
mutagen==1.47.0
|
||||
numpy==2.1.0
|
||||
opencv_python==4.10.0.84
|
||||
pillow-heif==0.16.0
|
||||
pillow-jxl-plugin==1.3.0
|
||||
Pillow==10.3.0
|
||||
pydub==0.25.1
|
||||
PySide6_Addons==6.8.0.1
|
||||
PySide6_Essentials==6.8.0.1
|
||||
PySide6==6.8.0.1
|
||||
rawpy==0.22.0
|
||||
Send2Trash==1.8.3
|
||||
SQLAlchemy==2.0.34
|
||||
structlog==24.4.0
|
||||
typing_extensions>=3.10.0.0,<=4.11.0
|
||||
ujson>=5.8.0,<=5.9.0
|
||||
vtf2img==0.1.0
|
||||
@@ -2,9 +2,10 @@ from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import QSettings
|
||||
from src.core.constants import TS_FOLDER_NAME
|
||||
from src.core.enums import SettingItems
|
||||
from src.core.library.alchemy.library import LibraryStatus
|
||||
|
||||
from tagstudio.core.constants import TS_FOLDER_NAME
|
||||
from tagstudio.core.enums import SettingItems
|
||||
from tagstudio.core.library.alchemy.library import LibraryStatus
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -70,6 +70,6 @@ class LibraryPrefs(DefaultEnum):
|
||||
"""Library preferences with default value accessible via .default property."""
|
||||
|
||||
IS_EXCLUDE_LIST = True
|
||||
EXTENSION_LIST: list[str] = [".json", ".xmp", ".aae"]
|
||||
PAGE_SIZE: int = 500
|
||||
DB_VERSION: int = 8
|
||||
EXTENSION_LIST = [".json", ".xmp", ".aae"]
|
||||
PAGE_SIZE = 500
|
||||
DB_VERSION = 8
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from .alchemy import * # noqa
|
||||
@@ -1,5 +0,0 @@
|
||||
from .enums import ItemType
|
||||
from .library import Library
|
||||
from .models import Entry, Tag
|
||||
|
||||
__all__ = ["Entry", "Library", "Tag", "ItemType"]
|
||||
@@ -2,13 +2,15 @@
|
||||
# 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 src.core.constants import RESERVED_TAG_END
|
||||
|
||||
from tagstudio.core.constants import RESERVED_TAG_END
|
||||
|
||||
logger = structlog.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import structlog
|
||||
|
||||
from .models import Namespace, TagColorGroup
|
||||
from tagstudio.core.library.alchemy.models import Namespace, TagColorGroup
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ from dataclasses import dataclass, replace
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from src.core.query_lang import AST as Query # noqa: N811
|
||||
from src.core.query_lang import Constraint, ConstraintType, Parser
|
||||
|
||||
from tagstudio.core.query_lang.ast import AST, Constraint, ConstraintType
|
||||
from tagstudio.core.query_lang.parser import Parser
|
||||
|
||||
MAX_SQL_VARIABLES = 32766 # 32766 is the max sql bind parameter count as defined here: https://github.com/sqlite/sqlite/blob/master/src/sqliteLimit.h#L140
|
||||
|
||||
@@ -80,7 +81,7 @@ class FilterState:
|
||||
|
||||
# these should be erased on update
|
||||
# Abstract Syntax Tree Of the current Search Query
|
||||
ast: Query = None
|
||||
ast: AST = None
|
||||
|
||||
@property
|
||||
def limit(self):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
@@ -11,11 +12,11 @@ from typing import TYPE_CHECKING, Any
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy.orm import Mapped, declared_attr, mapped_column, relationship
|
||||
|
||||
from .db import Base
|
||||
from .enums import FieldTypeEnum
|
||||
from tagstudio.core.library.alchemy.db import Base
|
||||
from tagstudio.core.library.alchemy.enums import FieldTypeEnum
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .models import Entry, ValueType
|
||||
from tagstudio.core.library.alchemy.models import Entry, ValueType
|
||||
|
||||
|
||||
class BaseField(Base):
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
# 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 .db import Base
|
||||
from tagstudio.core.library.alchemy.db import Base
|
||||
|
||||
|
||||
class TagParent(Base):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
@@ -43,10 +44,8 @@ from sqlalchemy.orm import (
|
||||
make_transient,
|
||||
selectinload,
|
||||
)
|
||||
from src.core.library.json.library import Library as JsonLibrary # type: ignore
|
||||
from src.qt.translations import Translations
|
||||
|
||||
from ...constants import (
|
||||
from tagstudio.core.constants import (
|
||||
BACKUP_FOLDER_NAME,
|
||||
LEGACY_TAG_FIELD_IDS,
|
||||
RESERVED_NAMESPACE_PREFIX,
|
||||
@@ -57,19 +56,35 @@ from ...constants import (
|
||||
TAG_META,
|
||||
TS_FOLDER_NAME,
|
||||
)
|
||||
from ...enums import LibraryPrefs
|
||||
from . import default_color_groups
|
||||
from .db import make_tables
|
||||
from .enums import MAX_SQL_VARIABLES, FieldTypeEnum, FilterState, SortingModeEnum
|
||||
from .fields import (
|
||||
from tagstudio.core.enums import LibraryPrefs
|
||||
from tagstudio.core.library.alchemy import default_color_groups
|
||||
from tagstudio.core.library.alchemy.db import make_tables
|
||||
from tagstudio.core.library.alchemy.enums import (
|
||||
MAX_SQL_VARIABLES,
|
||||
FieldTypeEnum,
|
||||
FilterState,
|
||||
SortingModeEnum,
|
||||
)
|
||||
from tagstudio.core.library.alchemy.fields import (
|
||||
BaseField,
|
||||
DatetimeField,
|
||||
TextField,
|
||||
_FieldID,
|
||||
)
|
||||
from .joins import TagEntry, TagParent
|
||||
from .models import Entry, Folder, Namespace, Preferences, Tag, TagAlias, TagColorGroup, ValueType
|
||||
from .visitors import SQLBoolExpressionBuilder
|
||||
from tagstudio.core.library.alchemy.joins import TagEntry, TagParent
|
||||
from tagstudio.core.library.alchemy.models import (
|
||||
Entry,
|
||||
Folder,
|
||||
Namespace,
|
||||
Preferences,
|
||||
Tag,
|
||||
TagAlias,
|
||||
TagColorGroup,
|
||||
ValueType,
|
||||
)
|
||||
from tagstudio.core.library.alchemy.visitors import SQLBoolExpressionBuilder
|
||||
from tagstudio.core.library.json.library import Library as JsonLibrary
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sqlalchemy import Select
|
||||
@@ -192,7 +207,7 @@ class Library:
|
||||
"""Class for the Library object, and all CRUD operations made upon it."""
|
||||
|
||||
library_dir: Path | None = None
|
||||
storage_path: Path | str | None
|
||||
storage_path: Path | None
|
||||
engine: Engine | None = None
|
||||
folder: Folder | None
|
||||
included_files: set[Path] = set()
|
||||
@@ -291,7 +306,7 @@ class Library:
|
||||
self.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, json_lib.is_exclude_list)
|
||||
|
||||
end_time = time.time()
|
||||
logger.info(f"Library Converted! ({format_timespan(end_time-start_time)})")
|
||||
logger.info(f"Library Converted! ({format_timespan(end_time - start_time)})")
|
||||
|
||||
def get_field_name_from_id(self, field_id: int) -> _FieldID:
|
||||
for f in _FieldID:
|
||||
@@ -316,7 +331,7 @@ class Library:
|
||||
else:
|
||||
return tag.name
|
||||
|
||||
def open_library(self, library_dir: Path, storage_path: str | None = None) -> LibraryStatus:
|
||||
def open_library(self, library_dir: Path, storage_path: Path | None = None) -> LibraryStatus:
|
||||
is_new: bool = True
|
||||
if storage_path == ":memory:":
|
||||
self.storage_path = storage_path
|
||||
@@ -324,7 +339,6 @@ class Library:
|
||||
return self.open_sqlite_library(library_dir, is_new)
|
||||
else:
|
||||
self.storage_path = library_dir / TS_FOLDER_NAME / self.SQL_FILENAME
|
||||
|
||||
if self.verify_ts_folder(library_dir) and (is_new := not self.storage_path.exists()):
|
||||
json_path = library_dir / TS_FOLDER_NAME / self.JSON_FILENAME
|
||||
if json_path.exists():
|
||||
@@ -365,7 +379,7 @@ class Library:
|
||||
select(Preferences).where(Preferences.key == LibraryPrefs.DB_VERSION.name)
|
||||
)
|
||||
if db_result:
|
||||
db_version = db_result.value # type: ignore
|
||||
db_version = db_result.value
|
||||
|
||||
# NOTE: DB_VERSION 6 is the first supported SQL DB version.
|
||||
if db_version < 6 or db_version > LibraryPrefs.DB_VERSION.default:
|
||||
@@ -628,7 +642,7 @@ class Library:
|
||||
end_time = time.time()
|
||||
logger.info(
|
||||
f"[Library] Time it took to get entry: "
|
||||
f"{format_timespan(end_time-start_time, max_units=5)}",
|
||||
f"{format_timespan(end_time - start_time, max_units=5)}",
|
||||
with_fields=with_fields,
|
||||
with_tags=with_tags,
|
||||
)
|
||||
@@ -840,7 +854,7 @@ class Library:
|
||||
query_count = select(func.count()).select_from(statement.alias("entries"))
|
||||
count_all: int = session.execute(query_count).scalar()
|
||||
end_time = time.time()
|
||||
logger.info(f"finished counting ({format_timespan(end_time-start_time)})")
|
||||
logger.info(f"finished counting ({format_timespan(end_time - start_time)})")
|
||||
|
||||
sort_on: ColumnExpressionArgument = Entry.id
|
||||
match search.sorting_mode:
|
||||
@@ -1160,7 +1174,7 @@ class Library:
|
||||
if tag:
|
||||
tags.append(tag.id)
|
||||
else:
|
||||
new = session.add(Tag(name=string))
|
||||
new = session.add(Tag(name=string)) # type: ignore
|
||||
if new:
|
||||
tags.append(new.id)
|
||||
session.flush()
|
||||
@@ -1348,7 +1362,7 @@ class Library:
|
||||
assert isinstance(self.library_dir, Path)
|
||||
makedirs(str(self.library_dir / TS_FOLDER_NAME / BACKUP_FOLDER_NAME), exist_ok=True)
|
||||
|
||||
filename = f'ts_library_backup_{datetime.now(UTC).strftime("%Y_%m_%d_%H%M%S")}.sqlite'
|
||||
filename = f"ts_library_backup_{datetime.now(UTC).strftime('%Y_%m_%d_%H%M%S')}.sqlite"
|
||||
|
||||
target_path = self.library_dir / TS_FOLDER_NAME / BACKUP_FOLDER_NAME / filename
|
||||
|
||||
|
||||
@@ -8,16 +8,16 @@ from pathlib import Path
|
||||
from sqlalchemy import JSON, ForeignKey, ForeignKeyConstraint, Integer, event
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from ...constants import TAG_ARCHIVED, TAG_FAVORITE
|
||||
from .db import Base, PathType
|
||||
from .fields import (
|
||||
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,
|
||||
FieldTypeEnum,
|
||||
TextField,
|
||||
)
|
||||
from .joins import TagParent
|
||||
from tagstudio.core.library.alchemy.joins import TagParent
|
||||
|
||||
|
||||
class Namespace(Base):
|
||||
@@ -304,7 +304,7 @@ class ValueType(Base):
|
||||
def slugify_field_key(mapper, connection, target):
|
||||
"""Slugify the field key before inserting into the database."""
|
||||
if not target.key:
|
||||
from .library import slugify
|
||||
from tagstudio.core.library.alchemy.library import slugify
|
||||
|
||||
target.key = slugify(target.tag)
|
||||
|
||||
|
||||
@@ -9,18 +9,25 @@ 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 src.core.media_types import FILETYPE_EQUIVALENTS, MediaCategories
|
||||
from src.core.query_lang import BaseVisitor
|
||||
from src.core.query_lang.ast import ANDList, Constraint, ConstraintType, Not, ORList, Property
|
||||
|
||||
from .joins import TagEntry
|
||||
from .models import Entry, Tag, TagAlias
|
||||
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 .library import Library
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
else:
|
||||
Library = None # don't import .library because of circular imports
|
||||
Library = None # don't import library because of circular imports
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -7,29 +7,28 @@
|
||||
"""The Library object and related methods for TagStudio."""
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
import os
|
||||
from pathlib import Path
|
||||
import time
|
||||
import traceback
|
||||
from typing import Generator, cast
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import structlog
|
||||
from typing_extensions import Self
|
||||
import ujson
|
||||
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import cast, Generator
|
||||
from typing_extensions import Self
|
||||
|
||||
from .fields import DEFAULT_FIELDS, TEXT_FIELDS
|
||||
from src.core.enums import OpenStatus
|
||||
from src.core.utils.str import strip_punctuation
|
||||
from src.core.utils.web import strip_web_protocol
|
||||
from src.core.constants import (
|
||||
from tagstudio.core.constants import (
|
||||
BACKUP_FOLDER_NAME,
|
||||
COLLAGE_FOLDER_NAME,
|
||||
TS_FOLDER_NAME,
|
||||
VERSION,
|
||||
)
|
||||
from tagstudio.core.enums import OpenStatus
|
||||
from tagstudio.core.library.json.fields import DEFAULT_FIELDS, TEXT_FIELDS
|
||||
from tagstudio.core.utils.str import strip_punctuation
|
||||
from tagstudio.core.utils.web import strip_web_protocol
|
||||
|
||||
TYPE = ["file", "meta", "alt", "mask"]
|
||||
|
||||
@@ -128,7 +127,7 @@ class Entry:
|
||||
if field_index >= 0 and field_index == i:
|
||||
t: list[int] = library.get_field_attr(f, "content")
|
||||
logger.info(
|
||||
f't:{tag_id}, i:{i}, idx:{field_index}, c:{library.get_field_attr(f, "content")}'
|
||||
f"t:{tag_id}, i:{i}, idx:{field_index}, c:{library.get_field_attr(f, 'content')}"
|
||||
)
|
||||
t.remove(tag_id)
|
||||
elif field_index < 0:
|
||||
@@ -208,9 +207,9 @@ class Tag:
|
||||
"""Returns a formatted tag name intended for displaying."""
|
||||
if self.subtag_ids:
|
||||
if library.get_tag(self.subtag_ids[0]).shorthand:
|
||||
return f"{self.name}" f" ({library.get_tag(self.subtag_ids[0]).shorthand})"
|
||||
return f"{self.name} ({library.get_tag(self.subtag_ids[0]).shorthand})"
|
||||
else:
|
||||
return f"{self.name}" f" ({library.get_tag(self.subtag_ids[0]).name})"
|
||||
return f"{self.name} ({library.get_tag(self.subtag_ids[0]).name})"
|
||||
else:
|
||||
return f"{self.name}"
|
||||
|
||||
@@ -759,7 +758,7 @@ class Library:
|
||||
|
||||
logger.info(f"[LIBRARY] Saving Library Backup to Disk...")
|
||||
start_time = time.time()
|
||||
filename = f'ts_library_backup_{datetime.datetime.utcnow().strftime("%F_%T").replace(":", "")}.json'
|
||||
filename = f"ts_library_backup_{datetime.datetime.utcnow().strftime('%F_%T').replace(':', '')}.json"
|
||||
|
||||
self.verify_ts_folders()
|
||||
with open(
|
||||
|
||||
@@ -16,35 +16,35 @@ FILETYPE_EQUIVALENTS = [set(["jpg", "jpeg"])]
|
||||
class MediaType(str, Enum):
|
||||
"""Names of media types."""
|
||||
|
||||
ADOBE_PHOTOSHOP: str = "adobe_photoshop"
|
||||
AFFINITY_PHOTO: str = "affinity_photo"
|
||||
ARCHIVE: str = "archive"
|
||||
AUDIO_MIDI: str = "audio_midi"
|
||||
AUDIO: str = "audio"
|
||||
BLENDER: str = "blender"
|
||||
DATABASE: str = "database"
|
||||
DISK_IMAGE: str = "disk_image"
|
||||
DOCUMENT: str = "document"
|
||||
EBOOK: str = "ebook"
|
||||
FONT: str = "font"
|
||||
IMAGE_ANIMATED: str = "image_animated"
|
||||
IMAGE_RAW: str = "image_raw"
|
||||
IMAGE_VECTOR: str = "image_vector"
|
||||
IMAGE: str = "image"
|
||||
INSTALLER: str = "installer"
|
||||
MATERIAL: str = "material"
|
||||
MODEL: str = "model"
|
||||
OPEN_DOCUMENT: str = "open_document"
|
||||
PACKAGE: str = "package"
|
||||
PDF: str = "pdf"
|
||||
PLAINTEXT: str = "plaintext"
|
||||
PRESENTATION: str = "presentation"
|
||||
PROGRAM: str = "program"
|
||||
SHORTCUT: str = "shortcut"
|
||||
SOURCE_ENGINE: str = "source_engine"
|
||||
SPREADSHEET: str = "spreadsheet"
|
||||
TEXT: str = "text"
|
||||
VIDEO: str = "video"
|
||||
ADOBE_PHOTOSHOP = "adobe_photoshop"
|
||||
AFFINITY_PHOTO = "affinity_photo"
|
||||
ARCHIVE = "archive"
|
||||
AUDIO_MIDI = "audio_midi"
|
||||
AUDIO = "audio"
|
||||
BLENDER = "blender"
|
||||
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"
|
||||
MATERIAL = "material"
|
||||
MODEL = "model"
|
||||
OPEN_DOCUMENT = "open_document"
|
||||
PACKAGE = "package"
|
||||
PDF = "pdf"
|
||||
PLAINTEXT = "plaintext"
|
||||
PRESENTATION = "presentation"
|
||||
PROGRAM = "program"
|
||||
SHORTCUT = "shortcut"
|
||||
SOURCE_ENGINE = "source_engine"
|
||||
SPREADSHEET = "spreadsheet"
|
||||
TEXT = "text"
|
||||
VIDEO = "video"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -337,188 +337,188 @@ class MediaCategories:
|
||||
".wmv",
|
||||
}
|
||||
|
||||
ADOBE_PHOTOSHOP_TYPES: MediaCategory = MediaCategory(
|
||||
ADOBE_PHOTOSHOP_TYPES = MediaCategory(
|
||||
media_type=MediaType.ADOBE_PHOTOSHOP,
|
||||
extensions=_ADOBE_PHOTOSHOP_SET,
|
||||
is_iana=False,
|
||||
name="photoshop",
|
||||
)
|
||||
AFFINITY_PHOTO_TYPES: MediaCategory = MediaCategory(
|
||||
AFFINITY_PHOTO_TYPES = MediaCategory(
|
||||
media_type=MediaType.AFFINITY_PHOTO,
|
||||
extensions=_AFFINITY_PHOTO_SET,
|
||||
is_iana=False,
|
||||
name="affinity photo",
|
||||
)
|
||||
ARCHIVE_TYPES: MediaCategory = MediaCategory(
|
||||
ARCHIVE_TYPES = MediaCategory(
|
||||
media_type=MediaType.ARCHIVE,
|
||||
extensions=_ARCHIVE_SET,
|
||||
is_iana=False,
|
||||
name="archive",
|
||||
)
|
||||
AUDIO_MIDI_TYPES: MediaCategory = MediaCategory(
|
||||
AUDIO_MIDI_TYPES = MediaCategory(
|
||||
media_type=MediaType.AUDIO_MIDI,
|
||||
extensions=_AUDIO_MIDI_SET,
|
||||
is_iana=False,
|
||||
name="audio midi",
|
||||
)
|
||||
AUDIO_TYPES: MediaCategory = MediaCategory(
|
||||
AUDIO_TYPES = MediaCategory(
|
||||
media_type=MediaType.AUDIO,
|
||||
extensions=_AUDIO_SET | _AUDIO_MIDI_SET,
|
||||
is_iana=True,
|
||||
name="audio",
|
||||
)
|
||||
BLENDER_TYPES: MediaCategory = MediaCategory(
|
||||
BLENDER_TYPES = MediaCategory(
|
||||
media_type=MediaType.BLENDER,
|
||||
extensions=_BLENDER_SET,
|
||||
is_iana=False,
|
||||
name="blender",
|
||||
)
|
||||
DATABASE_TYPES: MediaCategory = MediaCategory(
|
||||
DATABASE_TYPES = MediaCategory(
|
||||
media_type=MediaType.DATABASE,
|
||||
extensions=_DATABASE_SET,
|
||||
is_iana=False,
|
||||
name="database",
|
||||
)
|
||||
DISK_IMAGE_TYPES: MediaCategory = MediaCategory(
|
||||
DISK_IMAGE_TYPES = MediaCategory(
|
||||
media_type=MediaType.DISK_IMAGE,
|
||||
extensions=_DISK_IMAGE_SET,
|
||||
is_iana=False,
|
||||
name="disk image",
|
||||
)
|
||||
DOCUMENT_TYPES: MediaCategory = MediaCategory(
|
||||
DOCUMENT_TYPES = MediaCategory(
|
||||
media_type=MediaType.DOCUMENT,
|
||||
extensions=_DOCUMENT_SET,
|
||||
is_iana=False,
|
||||
name="document",
|
||||
)
|
||||
EBOOK_TYPES: MediaCategory = MediaCategory(
|
||||
EBOOK_TYPES = MediaCategory(
|
||||
media_type=MediaType.EBOOK,
|
||||
extensions=_EBOOK_SET,
|
||||
is_iana=False,
|
||||
name="ebook",
|
||||
)
|
||||
FONT_TYPES: MediaCategory = MediaCategory(
|
||||
FONT_TYPES = MediaCategory(
|
||||
media_type=MediaType.FONT,
|
||||
extensions=_FONT_SET,
|
||||
is_iana=True,
|
||||
name="font",
|
||||
)
|
||||
IMAGE_ANIMATED_TYPES: MediaCategory = MediaCategory(
|
||||
IMAGE_ANIMATED_TYPES = MediaCategory(
|
||||
media_type=MediaType.IMAGE_ANIMATED,
|
||||
extensions=_IMAGE_ANIMATED_SET,
|
||||
is_iana=False,
|
||||
name="animated image",
|
||||
)
|
||||
IMAGE_RAW_TYPES: MediaCategory = MediaCategory(
|
||||
IMAGE_RAW_TYPES = MediaCategory(
|
||||
media_type=MediaType.IMAGE_RAW,
|
||||
extensions=_IMAGE_RAW_SET,
|
||||
is_iana=False,
|
||||
name="raw image",
|
||||
)
|
||||
IMAGE_VECTOR_TYPES: MediaCategory = MediaCategory(
|
||||
IMAGE_VECTOR_TYPES = MediaCategory(
|
||||
media_type=MediaType.IMAGE_VECTOR,
|
||||
extensions=_IMAGE_VECTOR_SET,
|
||||
is_iana=False,
|
||||
name="vector image",
|
||||
)
|
||||
IMAGE_RASTER_TYPES: MediaCategory = MediaCategory(
|
||||
IMAGE_RASTER_TYPES = MediaCategory(
|
||||
media_type=MediaType.IMAGE,
|
||||
extensions=_IMAGE_RASTER_SET,
|
||||
is_iana=False,
|
||||
name="raster image",
|
||||
)
|
||||
IMAGE_TYPES: MediaCategory = MediaCategory(
|
||||
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 = MediaCategory(
|
||||
INSTALLER_TYPES = MediaCategory(
|
||||
media_type=MediaType.INSTALLER,
|
||||
extensions=_INSTALLER_SET,
|
||||
is_iana=False,
|
||||
name="installer",
|
||||
)
|
||||
MATERIAL_TYPES: MediaCategory = MediaCategory(
|
||||
MATERIAL_TYPES = MediaCategory(
|
||||
media_type=MediaType.MATERIAL,
|
||||
extensions=_MATERIAL_SET,
|
||||
is_iana=False,
|
||||
name="material",
|
||||
)
|
||||
MODEL_TYPES: MediaCategory = MediaCategory(
|
||||
MODEL_TYPES = MediaCategory(
|
||||
media_type=MediaType.MODEL,
|
||||
extensions=_MODEL_SET,
|
||||
is_iana=True,
|
||||
name="model",
|
||||
)
|
||||
OPEN_DOCUMENT_TYPES: MediaCategory = MediaCategory(
|
||||
OPEN_DOCUMENT_TYPES = MediaCategory(
|
||||
media_type=MediaType.OPEN_DOCUMENT,
|
||||
extensions=_OPEN_DOCUMENT_SET,
|
||||
is_iana=False,
|
||||
name="open document",
|
||||
)
|
||||
PACKAGE_TYPES: MediaCategory = MediaCategory(
|
||||
PACKAGE_TYPES = MediaCategory(
|
||||
media_type=MediaType.PACKAGE,
|
||||
extensions=_PACKAGE_SET,
|
||||
is_iana=False,
|
||||
name="package",
|
||||
)
|
||||
PDF_TYPES: MediaCategory = MediaCategory(
|
||||
PDF_TYPES = MediaCategory(
|
||||
media_type=MediaType.PDF,
|
||||
extensions=_PDF_SET,
|
||||
is_iana=False,
|
||||
name="pdf",
|
||||
)
|
||||
PLAINTEXT_TYPES: MediaCategory = MediaCategory(
|
||||
PLAINTEXT_TYPES = MediaCategory(
|
||||
media_type=MediaType.PLAINTEXT,
|
||||
extensions=_PLAINTEXT_SET,
|
||||
is_iana=False,
|
||||
name="plaintext",
|
||||
)
|
||||
PRESENTATION_TYPES: MediaCategory = MediaCategory(
|
||||
PRESENTATION_TYPES = MediaCategory(
|
||||
media_type=MediaType.PRESENTATION,
|
||||
extensions=_PRESENTATION_SET,
|
||||
is_iana=False,
|
||||
name="presentation",
|
||||
)
|
||||
PROGRAM_TYPES: MediaCategory = MediaCategory(
|
||||
PROGRAM_TYPES = MediaCategory(
|
||||
media_type=MediaType.PROGRAM,
|
||||
extensions=_PROGRAM_SET,
|
||||
is_iana=False,
|
||||
name="program",
|
||||
)
|
||||
SHORTCUT_TYPES: MediaCategory = MediaCategory(
|
||||
SHORTCUT_TYPES = MediaCategory(
|
||||
media_type=MediaType.SHORTCUT,
|
||||
extensions=_SHORTCUT_SET,
|
||||
is_iana=False,
|
||||
name="shortcut",
|
||||
)
|
||||
SOURCE_ENGINE_TYPES: MediaCategory = MediaCategory(
|
||||
SOURCE_ENGINE_TYPES = MediaCategory(
|
||||
media_type=MediaType.SOURCE_ENGINE,
|
||||
extensions=_SOURCE_ENGINE_SET,
|
||||
is_iana=False,
|
||||
name="source engine",
|
||||
)
|
||||
SPREADSHEET_TYPES: MediaCategory = MediaCategory(
|
||||
SPREADSHEET_TYPES = MediaCategory(
|
||||
media_type=MediaType.SPREADSHEET,
|
||||
extensions=_SPREADSHEET_SET,
|
||||
is_iana=False,
|
||||
name="spreadsheet",
|
||||
)
|
||||
TEXT_TYPES: MediaCategory = MediaCategory(
|
||||
TEXT_TYPES = MediaCategory(
|
||||
media_type=MediaType.TEXT,
|
||||
extensions=_DOCUMENT_SET | _PLAINTEXT_SET,
|
||||
is_iana=True,
|
||||
name="text",
|
||||
)
|
||||
VIDEO_TYPES: MediaCategory = MediaCategory(
|
||||
VIDEO_TYPES = MediaCategory(
|
||||
media_type=MediaType.VIDEO,
|
||||
extensions=_VIDEO_SET,
|
||||
is_iana=True,
|
||||
name="video",
|
||||
)
|
||||
|
||||
ALL_CATEGORIES: list[MediaCategory] = [
|
||||
ALL_CATEGORIES = [
|
||||
ADOBE_PHOTOSHOP_TYPES,
|
||||
AFFINITY_PHOTO_TYPES,
|
||||
ARCHIVE_TYPES,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
# 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 src.core.library.alchemy.enums import TagColorEnum
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import TagColorEnum
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
from src.core.query_lang.ast import ( # noqa
|
||||
AST,
|
||||
ANDList,
|
||||
BaseVisitor,
|
||||
Constraint,
|
||||
ConstraintType,
|
||||
ORList,
|
||||
Property,
|
||||
)
|
||||
from src.core.query_lang.parser import Parser # noqa
|
||||
from src.core.query_lang.util import ParsingError # noqa
|
||||
@@ -1,6 +1,14 @@
|
||||
from .ast import AST, ANDList, Constraint, Not, ORList, Property
|
||||
from .tokenizer import ConstraintType, Token, Tokenizer, TokenType
|
||||
from .util import ParsingError
|
||||
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:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from .ast import ConstraintType
|
||||
from .util import ParsingError
|
||||
from tagstudio.core.query_lang.ast import ConstraintType
|
||||
from tagstudio.core.query_lang.util import ParsingError
|
||||
|
||||
|
||||
class TokenType(Enum):
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from src.core.constants import TS_FOLDER_NAME
|
||||
from src.core.library import Entry, Library
|
||||
from src.core.library.alchemy.fields import _FieldID
|
||||
from src.core.utils.missing_files import logger
|
||||
from tagstudio.core.constants import TS_FOLDER_NAME
|
||||
from tagstudio.core.library.alchemy.fields import _FieldID
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.core.utils.missing_files import logger
|
||||
|
||||
|
||||
class TagStudioCore:
|
||||
|
||||
@@ -3,8 +3,10 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from src.core.library import Entry, Library
|
||||
from src.core.library.alchemy.enums import FilterState
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import FilterState
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from src.core.library import Entry, Library
|
||||
from src.core.utils.refresh_dir import GLOBAL_IGNORE_SET
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.core.utils.refresh_dir import GLOBAL_IGNORE_SET
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ from pathlib import Path
|
||||
from time import time
|
||||
|
||||
import structlog
|
||||
from src.core.constants import TS_FOLDER_NAME
|
||||
from src.core.library import Entry, Library
|
||||
|
||||
from tagstudio.core.constants import TS_FOLDER_NAME
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
"""TagStudio launcher."""
|
||||
|
||||
import argparse
|
||||
@@ -10,7 +11,8 @@ import logging
|
||||
import traceback
|
||||
|
||||
import structlog
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
structlog.configure(
|
||||
wrapper_class=structlog.make_filtering_bound_logger(logging.INFO),
|
||||
@@ -55,9 +57,8 @@ def main():
|
||||
help="User interface option for TagStudio. Options: qt, cli (Default: qt)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
from src.core.library import alchemy as backend
|
||||
|
||||
driver = QtDriver(backend, args)
|
||||
driver = QtDriver(args)
|
||||
ui_name = "Qt"
|
||||
|
||||
# Run the chosen frontend driver.
|
||||
|
||||
@@ -9,15 +9,14 @@ from datetime import datetime as dt
|
||||
from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PIL import (
|
||||
Image,
|
||||
)
|
||||
from src.core.constants import THUMB_CACHE_NAME, TS_FOLDER_NAME
|
||||
from src.core.singleton import Singleton
|
||||
from PIL import Image
|
||||
|
||||
from tagstudio.core.constants import THUMB_CACHE_NAME, TS_FOLDER_NAME
|
||||
from tagstudio.core.singleton import Singleton
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.core.library import Library
|
||||
from tagstudio.core.library import Library
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
|
||||
"""PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x."""
|
||||
|
||||
from PySide6.QtCore import QMargins, QPoint, QRect, QSize, Qt
|
||||
|
||||
@@ -29,10 +29,7 @@ import os
|
||||
import struct
|
||||
from io import BufferedReader
|
||||
|
||||
from PIL import (
|
||||
Image,
|
||||
ImageOps,
|
||||
)
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
|
||||
def blend_extract_thumb(path):
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
from PIL import Image
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QGuiApplication
|
||||
from src.qt.helpers.gradient import linear_gradient
|
||||
|
||||
from tagstudio.qt.helpers.gradient import linear_gradient
|
||||
|
||||
# TODO: Consolidate the built-in QT theme values with the values
|
||||
# here, in enums.py, and in palette.py.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ from pathlib import Path
|
||||
import structlog
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QLabel
|
||||
from src.qt.helpers.silent_popen import silent_Popen
|
||||
|
||||
from tagstudio.qt.helpers.silent_popen import silent_Popen
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
from pathlib import Path
|
||||
|
||||
import ffmpeg
|
||||
from src.qt.helpers.vendored.ffmpeg import _probe
|
||||
|
||||
from tagstudio.qt.helpers.vendored.ffmpeg import _probe
|
||||
|
||||
|
||||
def is_readable_video(filepath: Path | str):
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from PIL import Image
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from PySide6.QtWidgets import QPushButton
|
||||
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
# https://creativecommons.org/licenses/by-sa/4.0/
|
||||
# Modified for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from PySide6.QtGui import QBrush, QColor, QPainter, QPixmap
|
||||
from PySide6.QtWidgets import (
|
||||
QProxyStyle,
|
||||
)
|
||||
from PySide6.QtWidgets import QProxyStyle
|
||||
|
||||
|
||||
class RoundedPixmapStyle(QProxyStyle):
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ import subprocess
|
||||
|
||||
import ffmpeg
|
||||
import structlog
|
||||
from src.qt.helpers.silent_popen import silent_Popen
|
||||
|
||||
from tagstudio.qt.helpers.silent_popen import silent_Popen
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ from tempfile import NamedTemporaryFile
|
||||
|
||||
from pydub.logging_utils import log_conversion, log_subprocess_output
|
||||
from pydub.utils import fsdecode
|
||||
from src.qt.helpers.vendored.ffmpeg import FFMPEG_CMD
|
||||
|
||||
from tagstudio.qt.helpers.vendored.ffmpeg import FFMPEG_CMD
|
||||
|
||||
try:
|
||||
from itertools import izip
|
||||
@@ -40,8 +41,9 @@ from pydub.utils import (
|
||||
get_array_type,
|
||||
ratio_to_db,
|
||||
)
|
||||
from src.qt.helpers.silent_popen import silent_Popen
|
||||
from src.qt.helpers.vendored.pydub.utils import _mediainfo_json
|
||||
|
||||
from tagstudio.qt.helpers.silent_popen import silent_Popen
|
||||
from tagstudio.qt.helpers.vendored.pydub.utils import _mediainfo_json
|
||||
|
||||
basestring = str
|
||||
xrange = range
|
||||
@@ -361,11 +363,11 @@ class _AudioSegment:
|
||||
"""Permit use of sum() builtin with an iterable of AudioSegments."""
|
||||
if rarg == 0:
|
||||
return self
|
||||
raise TypeError("Gains must be the second addend after the " "AudioSegment")
|
||||
raise TypeError("Gains must be the second addend after the AudioSegment")
|
||||
|
||||
def __sub__(self, arg):
|
||||
if isinstance(arg, _AudioSegment):
|
||||
raise TypeError("AudioSegment objects can't be subtracted from " "each other")
|
||||
raise TypeError("AudioSegment objects can't be subtracted from each other")
|
||||
else:
|
||||
return self.apply_gain(-arg)
|
||||
|
||||
@@ -1345,8 +1347,7 @@ class _AudioSegment:
|
||||
"""
|
||||
if None not in [duration, end, start]:
|
||||
raise TypeError(
|
||||
'Only two of the three arguments, "start", '
|
||||
'"end", and "duration" may be specified'
|
||||
'Only two of the three arguments, "start", "end", and "duration" may be specified'
|
||||
)
|
||||
|
||||
# no fade == the same audio
|
||||
|
||||
@@ -7,8 +7,9 @@ from pydub.utils import (
|
||||
fsdecode,
|
||||
get_extra_info,
|
||||
)
|
||||
from src.qt.helpers.silent_popen import silent_Popen
|
||||
from src.qt.helpers.vendored.ffmpeg import FFPROBE_CMD
|
||||
|
||||
from tagstudio.qt.helpers.silent_popen import silent_Popen
|
||||
from tagstudio.qt.helpers.vendored.ffmpeg import FFPROBE_CMD
|
||||
|
||||
|
||||
def _mediainfo_json(filepath, read_ahead_limit=-1):
|
||||
|
||||
@@ -5,21 +5,34 @@
|
||||
|
||||
import logging
|
||||
import typing
|
||||
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect,QSize, Qt, QStringListModel)
|
||||
from PySide6.QtGui import QFont
|
||||
from PySide6.QtWidgets import (QComboBox, QFrame, QGridLayout,
|
||||
QHBoxLayout, QVBoxLayout, QLayout, QLineEdit, QMainWindow,
|
||||
QPushButton, QScrollArea, QSizePolicy,
|
||||
QStatusBar, QWidget, QSplitter, QCheckBox,
|
||||
QSpacerItem, QCompleter)
|
||||
from src.qt.pagination import Pagination
|
||||
from src.qt.widgets.landing import LandingWidget
|
||||
|
||||
from src.qt.translations import Translations
|
||||
from PySide6.QtCore import QMetaObject, QRect, QSize, QStringListModel, Qt
|
||||
from PySide6.QtWidgets import (
|
||||
QComboBox,
|
||||
QCompleter,
|
||||
QFrame,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLayout,
|
||||
QLineEdit,
|
||||
QMainWindow,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QSpacerItem,
|
||||
QSplitter,
|
||||
QStatusBar,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.qt.pagination import Pagination
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.landing import LandingWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
@@ -192,4 +205,4 @@ class Ui_MainWindow(QMainWindow):
|
||||
self.landing_widget.setHidden(True)
|
||||
self.landing_widget.set_status_label("")
|
||||
self.scrollArea.setHidden(False)
|
||||
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ from PIL import ImageQt
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPixmap
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
from src.core.constants import VERSION, VERSION_BRANCH
|
||||
from src.core.palette import ColorType, UiColor, get_ui_color
|
||||
from src.qt.modals.ffmpeg_checker import FfmpegChecker
|
||||
from src.qt.resource_manager import ResourceManager
|
||||
from src.qt.translations import Translations
|
||||
|
||||
from tagstudio.core.constants import VERSION, VERSION_BRANCH
|
||||
from tagstudio.core.palette import ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.modals.ffmpeg_checker import FfmpegChecker
|
||||
from tagstudio.qt.resource_manager import ResourceManager
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
|
||||
class AboutModal(QWidget):
|
||||
@@ -31,7 +32,7 @@ class AboutModal(QWidget):
|
||||
|
||||
self.logo_widget = QLabel()
|
||||
self.logo_widget.setObjectName("logo")
|
||||
self.logo_pixmap = QPixmap.fromImage(ImageQt.ImageQt(self.rm.get("logo")))
|
||||
self.logo_pixmap = QPixmap.fromImage(ImageQt.ImageQt(self.rm.get("icon")))
|
||||
self.logo_pixmap = self.logo_pixmap.scaledToWidth(
|
||||
128, Qt.TransformationMode.SmoothTransformation
|
||||
)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from typing import override
|
||||
|
||||
import structlog
|
||||
from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
@@ -15,8 +17,9 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.library import Library
|
||||
from src.qt.translations import Translations
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -40,7 +43,7 @@ class AddFieldModal(QWidget):
|
||||
self.title_widget = QLabel(Translations["library.field.add"])
|
||||
self.title_widget.setObjectName("fieldTitle")
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet("font-weight:bold;" "font-size:14px;" "padding-top: 6px;")
|
||||
self.title_widget.setStyleSheet("font-weight:bold;font-size:14px;padding-top: 6px;")
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.list_widget = QListWidget()
|
||||
@@ -80,6 +83,7 @@ class AddFieldModal(QWidget):
|
||||
|
||||
super().show()
|
||||
|
||||
@override
|
||||
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802
|
||||
if event.key() == QtCore.Qt.Key.Key_Escape:
|
||||
self.cancel_button.click()
|
||||
|
||||
@@ -19,20 +19,20 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core import palette
|
||||
from src.core.library import Library
|
||||
from src.core.library.alchemy.enums import TagColorEnum
|
||||
from src.core.library.alchemy.library import slugify
|
||||
from src.core.library.alchemy.models import TagColorGroup
|
||||
from src.core.palette import ColorType, UiColor, get_tag_color, get_ui_color
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
from src.qt.widgets.tag import (
|
||||
|
||||
from tagstudio.core import palette
|
||||
from tagstudio.core.library.alchemy.enums import TagColorEnum
|
||||
from tagstudio.core.library.alchemy.library import Library, slugify
|
||||
from tagstudio.core.library.alchemy.models import TagColorGroup
|
||||
from tagstudio.core.palette import ColorType, UiColor, get_tag_color, get_ui_color
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelWidget
|
||||
from tagstudio.qt.widgets.tag import (
|
||||
get_border_color,
|
||||
get_highlight_color,
|
||||
get_text_color,
|
||||
)
|
||||
from src.qt.widgets.tag_color_preview import TagColorPreview
|
||||
from tagstudio.qt.widgets.tag_color_preview import TagColorPreview
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -317,7 +317,7 @@ class BuildColorPanel(PanelWidget):
|
||||
|
||||
def update_preview_text(self):
|
||||
self.preview_button.button.setText(
|
||||
f"{self.name_field.text().strip() or Translations["color.placeholder"]} "
|
||||
f"{self.name_field.text().strip() or Translations['color.placeholder']} "
|
||||
f"({self.lib.get_namespace_name(self.color_group.namespace)})"
|
||||
)
|
||||
self.preview_button.button.setMaximumWidth(self.preview_button.button.sizeHint().width())
|
||||
@@ -333,7 +333,7 @@ class BuildColorPanel(PanelWidget):
|
||||
if suffix:
|
||||
try:
|
||||
suffix_num: int = int(suffix)
|
||||
return self.no_collide(f"{split_slug[0]}-{suffix_num+1}")
|
||||
return self.no_collide(f"{split_slug[0]}-{suffix_num + 1}")
|
||||
except ValueError:
|
||||
return self.no_collide(f"{slug}-2")
|
||||
else:
|
||||
|
||||
@@ -8,19 +8,14 @@ from uuid import uuid4
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import (
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.constants import RESERVED_NAMESPACE_PREFIX
|
||||
from src.core.library import Library
|
||||
from src.core.library.alchemy.library import ReservedNamespaceError, slugify
|
||||
from src.core.library.alchemy.models import Namespace
|
||||
from src.core.palette import ColorType, UiColor, get_ui_color
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
from PySide6.QtWidgets import QLabel, QLineEdit, QVBoxLayout, QWidget
|
||||
|
||||
from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX
|
||||
from tagstudio.core.library.alchemy.library import Library, ReservedNamespaceError, slugify
|
||||
from tagstudio.core.library.alchemy.models import Namespace
|
||||
from tagstudio.core.palette import ColorType, UiColor, get_ui_color
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelWidget
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -145,7 +140,7 @@ class BuildNamespacePanel(PanelWidget):
|
||||
if suffix:
|
||||
try:
|
||||
suffix_num: int = int(suffix)
|
||||
return self.no_collide(f"{split_slug[0]}-{suffix_num+1}")
|
||||
return self.no_collide(f"{split_slug[0]}-{suffix_num + 1}")
|
||||
except ValueError:
|
||||
return self.no_collide(f"{slug}-2")
|
||||
else:
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
|
||||
import sys
|
||||
from typing import cast
|
||||
from typing import cast, override
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QColor
|
||||
from PySide6.QtGui import QColor, QKeyEvent
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication,
|
||||
QButtonGroup,
|
||||
@@ -24,22 +24,23 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.library import Library, Tag
|
||||
from src.core.library.alchemy.enums import TagColorEnum
|
||||
from src.core.library.alchemy.models import TagColorGroup
|
||||
from src.core.palette import ColorType, UiColor, get_tag_color, get_ui_color
|
||||
from src.qt.modals.tag_color_selection import TagColorSelection
|
||||
from src.qt.modals.tag_search import TagSearchPanel
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from src.qt.widgets.tag import (
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import TagColorEnum
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Tag, TagColorGroup
|
||||
from tagstudio.core.palette import ColorType, UiColor, get_tag_color, get_ui_color
|
||||
from tagstudio.qt.modals.tag_color_selection import TagColorSelection
|
||||
from tagstudio.qt.modals.tag_search import TagSearchPanel
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelModal, PanelWidget
|
||||
from tagstudio.qt.widgets.tag import (
|
||||
TagWidget,
|
||||
get_border_color,
|
||||
get_highlight_color,
|
||||
get_primary_color,
|
||||
get_text_color,
|
||||
)
|
||||
from src.qt.widgets.tag_color_preview import TagColorPreview
|
||||
from tagstudio.qt.widgets.tag_color_preview import TagColorPreview
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -54,19 +55,20 @@ class CustomTableItem(QLineEdit):
|
||||
def set_id(self, id):
|
||||
self.id = id
|
||||
|
||||
def keyPressEvent(self, event): # noqa: N802
|
||||
if event.key() == Qt.Key.Key_Return or event.key() == Qt.Key.Key_Enter:
|
||||
@override
|
||||
def keyPressEvent(self, arg__1: QKeyEvent): # noqa: N802
|
||||
if arg__1.key() == Qt.Key.Key_Return or arg__1.key() == Qt.Key.Key_Enter:
|
||||
self.on_return()
|
||||
elif event.key() == Qt.Key.Key_Backspace and self.text().strip() == "":
|
||||
elif arg__1.key() == Qt.Key.Key_Backspace and self.text().strip() == "":
|
||||
self.on_backspace()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
super().keyPressEvent(arg__1)
|
||||
|
||||
|
||||
class BuildTagPanel(PanelWidget):
|
||||
on_edit = Signal(Tag)
|
||||
|
||||
def __init__(self, library: Library, tag: Tag | None = None):
|
||||
def __init__(self, library: Library, tag: Tag | None = None) -> None:
|
||||
super().__init__()
|
||||
self.lib = library
|
||||
self.tag: Tag # NOTE: This gets set at the end of the init.
|
||||
@@ -469,7 +471,7 @@ class BuildTagPanel(PanelWidget):
|
||||
def _update_new_alias_name_dict(self):
|
||||
for i in range(0, self.aliases_table.rowCount()):
|
||||
widget = self.aliases_table.cellWidget(i, 1)
|
||||
self.new_alias_names[widget.id] = widget.text() # type: ignore
|
||||
self.new_alias_names[widget.id] = widget.text()
|
||||
|
||||
def _set_aliases(self):
|
||||
self._update_new_alias_name_dict()
|
||||
|
||||
@@ -2,20 +2,29 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt, QThreadPool, Signal
|
||||
from PySide6.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QListView, QPushButton, QVBoxLayout, QWidget
|
||||
from src.core.utils.missing_files import MissingRegistry
|
||||
from src.qt.helpers.custom_runnable import CustomRunnable
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
from PySide6.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QListView,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.core.utils.missing_files import MissingRegistry
|
||||
from tagstudio.qt.helpers.custom_runnable import CustomRunnable
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class DeleteUnlinkedEntriesModal(QWidget):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import enum
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
@@ -11,12 +12,20 @@ import structlog
|
||||
from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt, QUrl
|
||||
from PySide6.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QListView, QPushButton, QVBoxLayout, QWidget
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
from PySide6.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QListView,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import structlog
|
||||
from PySide6.QtCore import Qt, QUrl
|
||||
from PySide6.QtGui import QDesktopServices
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from src.qt.helpers.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
|
||||
|
||||
from tagstudio.qt.helpers.vendored.ffmpeg import FFMPEG_CMD, FFPROBE_CMD
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
@@ -16,10 +16,11 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.enums import LibraryPrefs
|
||||
from src.core.library import Library
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
|
||||
from tagstudio.core.enums import LibraryPrefs
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelWidget
|
||||
|
||||
|
||||
class FileExtensionItemDelegate(QStyledItemDelegate):
|
||||
|
||||
@@ -7,15 +7,23 @@ from typing import TYPE_CHECKING, override
|
||||
|
||||
from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QFileDialog, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
from src.core.library import Library
|
||||
from src.core.utils.dupe_files import DupeRegistry
|
||||
from src.qt.modals.mirror_entities import MirrorEntriesModal
|
||||
from src.qt.translations import Translations
|
||||
from PySide6.QtWidgets import (
|
||||
QFileDialog,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.dupe_files import DupeRegistry
|
||||
from tagstudio.qt.modals.mirror_entities import MirrorEntriesModal
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class FixDupeFilesModal(QWidget):
|
||||
@@ -41,7 +49,7 @@ class FixDupeFilesModal(QWidget):
|
||||
|
||||
self.dupe_count = QLabel()
|
||||
self.dupe_count.setObjectName("dupeCountLabel")
|
||||
self.dupe_count.setStyleSheet("font-weight:bold;" "font-size:14px;" "")
|
||||
self.dupe_count.setStyleSheet("font-weight:bold;font-size:14px;")
|
||||
self.dupe_count.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.file_label = QLabel(Translations["file.duplicates.dupeguru.no_file"])
|
||||
|
||||
@@ -8,17 +8,18 @@ from typing import TYPE_CHECKING, override
|
||||
from PySide6 import QtCore, QtGui
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget
|
||||
from src.core.library import Library
|
||||
from src.core.utils.missing_files import MissingRegistry
|
||||
from src.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal
|
||||
from src.qt.modals.merge_dupe_entries import MergeDuplicateEntries
|
||||
from src.qt.modals.relink_unlinked import RelinkUnlinkedEntries
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.missing_files import MissingRegistry
|
||||
from tagstudio.qt.modals.delete_unlinked import DeleteUnlinkedEntriesModal
|
||||
from tagstudio.qt.modals.merge_dupe_entries import MergeDuplicateEntries
|
||||
from tagstudio.qt.modals.relink_unlinked import RelinkUnlinkedEntries
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class FixUnlinkedEntriesModal(QWidget):
|
||||
@@ -44,12 +45,12 @@ class FixUnlinkedEntriesModal(QWidget):
|
||||
|
||||
self.missing_count_label = QLabel()
|
||||
self.missing_count_label.setObjectName("missingCountLabel")
|
||||
self.missing_count_label.setStyleSheet("font-weight:bold;" "font-size:14px;")
|
||||
self.missing_count_label.setStyleSheet("font-weight:bold;font-size:14px;")
|
||||
self.missing_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.dupe_count_label = QLabel()
|
||||
self.dupe_count_label.setObjectName("dupeCountLabel")
|
||||
self.dupe_count_label.setStyleSheet("font-weight:bold;" "font-size:14px;")
|
||||
self.dupe_count_label.setStyleSheet("font-weight:bold;font-size:14px;")
|
||||
self.dupe_count_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.refresh_unlinked_button = QPushButton(Translations["entries.unlinked.refresh_all"])
|
||||
|
||||
@@ -20,15 +20,17 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.constants import TAG_ARCHIVED, TAG_FAVORITE
|
||||
from src.core.library import Library, Tag
|
||||
from src.core.library.alchemy.enums import TagColorEnum
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.translations import Translations
|
||||
|
||||
from tagstudio.core.constants import TAG_ARCHIVED, TAG_FAVORITE
|
||||
from tagstudio.core.library.alchemy.enums import TagColorEnum
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Tag
|
||||
from tagstudio.core.palette import ColorType, get_tag_color
|
||||
from tagstudio.qt.flowlayout import FlowLayout
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
@@ -175,7 +177,7 @@ class FoldersToTagsModal(QWidget):
|
||||
self.title_widget = QLabel(Translations["folders_to_tags.title"])
|
||||
self.title_widget.setObjectName("title")
|
||||
self.title_widget.setWordWrap(True)
|
||||
self.title_widget.setStyleSheet("font-weight:bold;" "font-size:14px;" "padding-top: 6px")
|
||||
self.title_widget.setStyleSheet("font-weight:bold;font-size:14px;padding-top: 6px")
|
||||
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
self.desc_widget = QLabel()
|
||||
|
||||
@@ -2,17 +2,19 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import typing
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from src.core.library import Library
|
||||
from src.core.utils.dupe_files import DupeRegistry
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.dupe_files import DupeRegistry
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class MergeDuplicateEntries(QObject):
|
||||
|
||||
@@ -9,13 +9,14 @@ from time import sleep
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtGui import QStandardItem, QStandardItemModel
|
||||
from PySide6.QtWidgets import QHBoxLayout, QLabel, QListView, QPushButton, QVBoxLayout, QWidget
|
||||
from src.core.utils.dupe_files import DupeRegistry
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
from tagstudio.core.utils.dupe_files import DupeRegistry
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class MirrorEntriesModal(QWidget):
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
|
||||
|
||||
from PySide6.QtCore import QObject, Signal
|
||||
from src.core.utils.missing_files import MissingRegistry
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.progress import ProgressWidget
|
||||
|
||||
from tagstudio.core.utils.missing_files import MissingRegistry
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.progress import ProgressWidget
|
||||
|
||||
|
||||
class RelinkUnlinkedEntries(QObject):
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtWidgets import QComboBox, QFormLayout, QLabel, QVBoxLayout, QWidget
|
||||
from src.core.enums import SettingItems
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
|
||||
from tagstudio.core.enums import SettingItems
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelWidget
|
||||
|
||||
|
||||
class SettingsPanel(PanelWidget):
|
||||
|
||||
@@ -19,19 +19,20 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.constants import RESERVED_NAMESPACE_PREFIX
|
||||
from src.core.enums import Theme
|
||||
from src.qt.modals.build_namespace import BuildNamespacePanel
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.color_box import ColorBoxWidget
|
||||
from src.qt.widgets.fields import FieldContainer
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
|
||||
from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX
|
||||
from tagstudio.core.enums import Theme
|
||||
from tagstudio.qt.modals.build_namespace import BuildNamespacePanel
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.color_box import ColorBoxWidget
|
||||
from tagstudio.qt.widgets.fields import FieldContainer
|
||||
from tagstudio.qt.widgets.panel import PanelModal
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
# Only import for type checking/autocompletion, will not be imported at runtime.
|
||||
if TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
class TagColorManager(QWidget):
|
||||
@@ -60,7 +61,7 @@ class TagColorManager(QWidget):
|
||||
self.title_label = QLabel()
|
||||
self.title_label.setObjectName("titleLabel")
|
||||
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.title_label.setText(f"<h3>{Translations["color_manager.title"]}</h3>")
|
||||
self.title_label.setText(f"<h3>{Translations['color_manager.title']}</h3>")
|
||||
|
||||
self.scroll_layout = QVBoxLayout()
|
||||
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
@@ -81,7 +82,7 @@ class TagColorManager(QWidget):
|
||||
self.scroll_area.setFrameShape(QFrame.Shape.NoFrame)
|
||||
|
||||
self.scroll_area.setStyleSheet(
|
||||
"QWidget#entryScrollContainer{" f"background:{panel_bg_color};" "border-radius:6px;" "}"
|
||||
f"QWidget#entryScrollContainer{{background:{panel_bg_color};border-radius:6px;}}"
|
||||
)
|
||||
self.scroll_area.setWidget(scroll_container)
|
||||
|
||||
@@ -108,7 +109,6 @@ class TagColorManager(QWidget):
|
||||
self.root_layout.addWidget(self.title_label)
|
||||
self.root_layout.addWidget(self.scroll_area)
|
||||
self.root_layout.addWidget(self.button_container)
|
||||
logger.info(self.root_layout.dumpObjectTree())
|
||||
|
||||
def setup_color_groups(self):
|
||||
all_default = True
|
||||
|
||||
@@ -17,14 +17,15 @@ from PySide6.QtWidgets import (
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
from src.core.library import Library
|
||||
from src.core.library.alchemy.enums import TagColorEnum
|
||||
from src.core.library.alchemy.models import TagColorGroup
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.translations import Translations
|
||||
from src.qt.widgets.panel import PanelWidget
|
||||
from src.qt.widgets.tag import (
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import TagColorEnum
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import TagColorGroup
|
||||
from tagstudio.core.palette import ColorType, get_tag_color
|
||||
from tagstudio.qt.flowlayout import FlowLayout
|
||||
from tagstudio.qt.translations import Translations
|
||||
from tagstudio.qt.widgets.panel import PanelWidget
|
||||
from tagstudio.qt.widgets.tag import (
|
||||
get_border_color,
|
||||
get_highlight_color,
|
||||
get_text_color,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user