191 lines
11 KiB
Markdown
191 lines
11 KiB
Markdown
# Changelog
|
||
|
||
Document all notable changes to the project, grouped by version and release date.
|
||
|
||
## Format
|
||
|
||
Each version entry uses these sections (include only those that apply):
|
||
|
||
- **Added** — new features
|
||
- **Changed** — changes to existing functionality
|
||
- **Fixed** — bug fixes
|
||
- **Removed** — removed features
|
||
- **Dependencies** — added, updated, or removed dependencies
|
||
|
||
## Rules
|
||
|
||
- Follow semantic versioning: `MAJOR.MINOR.PATCH`
|
||
- Newest version goes at the top
|
||
- Always update this file before bumping the version in `pyproject.toml`
|
||
- Document changes as they are made, not all at once at release time
|
||
|
||
## Unreleased
|
||
|
||
## 1.5.0 — 2026-06-16
|
||
|
||
### Added
|
||
- **Free-form per-movie attributes** (`attributes` in the index, set via *Filmy →
|
||
Nastavit atribut…* / context menu): arbitrary `key → value` pairs (e.g.
|
||
`collection_sort`) that are exposed to Filmotéka filename templates through
|
||
`File.name_context`. So a Kolekce template `"{collection_sort} - {title}{ext}"`
|
||
orders the folder as `01 - Dr. No.mkv`, `02 - …`. Empty value removes the attr.
|
||
- **Per-category filename template** in the tag schema (`filename_template`,
|
||
editable in *Nastavení → Tag schéma…*): the hardlink inside that category's
|
||
folders is named from the template (fields `title`/`year`/`rating`/`ext`/
|
||
`stem`/`filename`), e.g. a *Kolekce* with `"{year} - {title}{ext}"` yields
|
||
`Dle kolekce/James Bond/1962 - Dr. No.mkv` while every other folder (and the
|
||
pool) keeps the plain filename. `HardlinkManager` takes
|
||
`category_filename_templates`; `File.name_context()` supplies the fields.
|
||
- The "Přiřadit štítky" dialog now lists **all tag-schema categories** (including
|
||
empty user ones like *Kolekce*) and has a **"➕ Nový štítek…"** button to create
|
||
a tag value (e.g. *Kolekce/James Bond*) on the spot and assign it. Tag values
|
||
are created on first assignment — the schema editor defines categories + rules,
|
||
this dialog defines/assigns the individual values.
|
||
|
||
### Changed
|
||
- Tag schema `transform` now shapes only the **Filmotéka folder name**, not the
|
||
tag value. ČSFD tags keep the **exact value** (rating → `Hodnocení/90`) while
|
||
the grouping transform is applied when building folders (→ `Dle hodnocení/
|
||
90–100 %`). `csfd_field_values` no longer transforms; `HardlinkManager` takes a
|
||
`category_transforms` map (`FileManager.filmoteka_category_transforms`) and
|
||
`apply_transform`/`decade_band` run at folder-generation time.
|
||
|
||
### Fixed
|
||
- Sidebar filter tree no longer re-expands every category on each check/uncheck:
|
||
the expanded/collapsed state is preserved across the rebuild (new categories
|
||
still default to expanded).
|
||
|
||
## 1.3.0 — 2026-06-16
|
||
|
||
### Added
|
||
- **Configurable tag schema** (`tag_schema` in the global config, edited via
|
||
*Nastavení → Tag schéma…*): a single source of truth for which tag categories
|
||
exist, which ČSFD field feeds each (with an optional value transform, e.g.
|
||
`decade_band` for the rating), and how each maps into the Filmotéka tree
|
||
(`""` = output root, `"Dle …"` = grouping folder, none = filter-only, no
|
||
folders). Replaces the hard-coded `FILMOTEKA_CATEGORY_ROOTS` and the fixed
|
||
category list in `apply_csfd_tags`; `HardlinkManager` roots are now derived
|
||
from the schema (`FileManager.filmoteka_category_roots`).
|
||
- **Tag provenance (ČSFD vs user)**: each file tracks which tags came from ČSFD
|
||
(`csfd_tags` in the index). `apply_csfd_tags` now **regenerates only the ČSFD
|
||
tags** from the schema and leaves user-added tags untouched — so changing a
|
||
movie's ČSFD link refreshes its ČSFD tags without wiping your own.
|
||
|
||
## 1.2.0 — 2026-06-16
|
||
|
||
### Added
|
||
- Fork of the former **Tagger** project as **Curator**, a movie-library manager.
|
||
- **Pool** concept (single source of truth) with `Filmy` / `Seriály` folders and
|
||
a configurable **Filmotéka** output folder, stored in the global config.
|
||
- **Multi-file "Import movies" flow**: pick several videos at once and give each
|
||
its own Title + ČSFD link (one row per file, more addable in the dialog); a
|
||
copy/move toggle chooses whether sources are copied (default, non-destructive)
|
||
or moved into `pool/Filmy` as `Title.ext`. Imported movies are indexed and, if
|
||
a ČSFD link is set, enriched with tags right away.
|
||
- **Auto-find ČSFD links** in the import dialog ("🔎 Najít ČSFD odkazy"): for
|
||
every row without a link it cleans the filename into a query
|
||
(`clean_filename_to_query` strips resolution/codec/source/group, keeps the
|
||
year) and fills in the first ČSFD search hit (`find_csfd_url` →
|
||
`search_movies`, reusing one Anubis session). Existing links are never
|
||
overwritten; results are a suggestion the user can review before importing.
|
||
- Import dialog now **highlights in red any title that already exists in the
|
||
pool** (live as you type) and lets you **remove a single row** (✕ per row), so
|
||
you can drop one file without discarding the whole batch and re-picking.
|
||
- **Conflict resolution on import**: when a row is left red (name already in the
|
||
pool), import asks whether to **replace** the existing movie(s) with the new
|
||
file, **keep both** (numeric suffix), or **cancel**. `FileManager.import_movie`
|
||
gained an `on_conflict` policy (`suffix` / `replace` / `skip`); `replace`
|
||
evicts the existing same-named movie (file + index metadata) first.
|
||
- `File` now stores `title` and `csfd_link`.
|
||
- New **PySide6** GUI reframed around the Filmotéka workflow (pool setup, import,
|
||
tag filter sidebar, movie table, one-click Filmotéka generation), replacing the
|
||
tkinter GUI as the entry point.
|
||
- Seriály **copy-as-is mirror**: `pool/Seriály` is cloned 1:1 into the Filmotéka
|
||
output as hardlinks (`HardlinkManager.mirror_as_is`), generated alongside the
|
||
movie tree.
|
||
- **Unified pool metadata index** (`pool_index.py`): the whole pool's metadata
|
||
lives in a single `<pool>/.Curator.!index` JSON keyed by pool-relative path.
|
||
`File` reads/writes the index when one is injected and otherwise keeps using
|
||
per-file `.!tag` sidecars; `FileManager` uses the index for the pool.
|
||
- **Configurable copy-as-is folders**: `copyasis_folders` in the global config
|
||
(editable from the GUI) lists pool subfolders mirrored 1:1 during generation;
|
||
`Seriály` is the default. Generalizes the previously hardcoded Seriály mirror.
|
||
- Project `README.md` (overview, concepts, workflow, run/build instructions).
|
||
- **ČSFD scraping** (`csfd.py`, ported from the Tagger devel branch): fetches
|
||
movie data from a ČSFD link (JSON-LD + HTML parsing). `File.apply_csfd_tags`
|
||
assigns **Žánr / Rok / Země původu / Hodnocení** tags and caches the fetched
|
||
data (incl. directors and the first 10 actors) in the metadata. The rating is
|
||
bucketed into ten-point bands (`rating_band`, e.g. `80–89 %`, `90–100 %`).
|
||
**Directors and actors are collected but intentionally not turned into tags or
|
||
Filmotéka folders** — there would be far too many. The GUI auto-fetches on
|
||
import when a link is given and offers "Načíst tagy z ČSFD" for selected movies.
|
||
- **Rename a pooled movie** from the app ("Přejmenovat…" in the Movie menu /
|
||
context menu, F2): `FileManager.rename_movie` renames the physical file in
|
||
pool/Filmy to `<new name>.<ext>` (extension preserved), moves its metadata to
|
||
the new index key, and syncs `title`/`filename`. Refuses empty names, names
|
||
with path separators, and collisions with an existing pooled file.
|
||
- App startup injects `truststore` so HTTPS uses the OS certificate store —
|
||
ČSFD fetching works behind corporate SSL inspection (where certifi's bundle
|
||
lacks the proxy root CA).
|
||
|
||
### Fixed
|
||
- `media_utils.add_video_resolution_tag` referenced `subprocess` without importing it.
|
||
- ČSFD parsing updated for the current site HTML: year is read from JSON-LD
|
||
`dateCreated`, and the origin line (now bullet-separated, no commas) is
|
||
tokenized so country / year / duration are extracted correctly.
|
||
- **ČSFD anti-bot wall (Anubis):** ČSFD now serves a proof-of-work challenge
|
||
page instead of the movie, so fetches returned a film with no genres/year
|
||
("načteno 0 tagů"). `csfd.py` now detects the Anubis challenge, solves the
|
||
SHA-256 proof-of-work the way the bundled worker JS does, and replays the
|
||
request through a `requests.Session` (reused across a batch so only the first
|
||
fetch pays the PoW cost). Žánr / Rok / Země původu tags load again.
|
||
- "Assign tags" dialog crashed on PySide6/Qt6 — `Qt.ItemIsTristate` was renamed
|
||
to `Qt.ItemIsAutoTristate`.
|
||
- Sidebar tag-filter checkboxes never appeared checked: every toggle triggered a
|
||
table refresh that rebuilt the tree from scratch (all unchecked), wiping the
|
||
click. The active filter is now kept in a separate model (`_active_filter`) and
|
||
restored on rebuild. The count after each tag is also now filter-aware — it
|
||
shows how many of the currently filtered movies carry that tag (i.e. how many
|
||
would remain if it were checked), instead of always the pool-wide total. The
|
||
refresh is deferred via `QTimer.singleShot` so the tree is not rebuilt inside
|
||
its own `itemChanged` signal (which deleted the item Qt was still processing
|
||
and crashed the app with SIGSEGV on a real click).
|
||
|
||
### Changed
|
||
- ČSFD country tag category renamed **Země → Země původu**. Added
|
||
`scripts/migrate_tag_category.py` to rewrite the category in an existing pool
|
||
index (backs up `.Curator.!index` first); run against the live pool.
|
||
- Filmotéka tree **relaid out**: genre folders now sit **directly at the output
|
||
root** (next to the copy-as-is Seriály mirror), with year tags grouped under a
|
||
**`Dle roku`** folder and country tags under **`Dle země původu`**.
|
||
`HardlinkManager` gained a category → root-folder map (`category_roots`,
|
||
empty root = tag folders at the output root) and now restricts obsolete-link
|
||
cleanup to the tag-tree's own top-level folders, so copy-as-is mirrors are
|
||
never touched. The tree also groups the ČSFD rating under `Dle hodnocení`.
|
||
- ČSFD origin is now parsed as **multiple countries**: a co-production like
|
||
"USA / Velká Británie" becomes a separate **Země původu** tag per country
|
||
(so the film is filed under each), instead of one combined tag. `CSFDMovie`
|
||
gained `countries: list[str]` (replacing the single `country`); the csfd cache
|
||
schema bumped to v2 (legacy single-country caches are split on read).
|
||
- Movie table trimmed to **Název / Štítky / Velikost** — the Datum and ČSFD
|
||
columns were dropped (a ČSFD link is a prerequisite, so its indicator was
|
||
always the same).
|
||
- All references to "Tagger" renamed to "Curator" (code, spec, config filenames
|
||
`.Curator.!gtag` / `.Curator.!ftag`, tests).
|
||
- `requires-python` narrowed to `>=3.14,<3.15` (PySide6 compatibility).
|
||
|
||
### Removed
|
||
- Duplicate `src/core/constants.py` (hard-coded stale `VERSION = "v1.0.3"`). The
|
||
GUI window title imported from it, so it showed the wrong version. All imports
|
||
now use `src/constants.py` (version derived from `pyproject.toml`), which also
|
||
absorbed `APP_VIEWPORT`; the window title uses `APP_TITLE` → "Curator vX.Y.Z".
|
||
- Legacy Tagger predefined tags: the always-available **Hodnocení** (⭐ rating)
|
||
and **Barva** (color) categories in `TagManager`, and the automatic
|
||
**Stav/Nové** tag assigned to every newly imported file. `DEFAULT_TAGS` is now
|
||
empty; the pool is driven by ČSFD-derived tags (Žánr / Rok / Země původu).
|
||
|
||
### Dependencies
|
||
- Added `pyside6` (GUI), `requests` + `beautifulsoup4` (ČSFD scraping),
|
||
`truststore` (OS cert store for HTTPS). Declared `python-dotenv`, `pillow`,
|
||
`loguru` (already imported by the inherited code).
|