# 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.8.0 — 2026-07-03 ### Changed - **Split config responsibilities by scope.** The global config (`.Curator.!gtag`) now carries **application data only** — window state, MRU folders, and the `pool_dir` / `filmoteka_dir` pointers. Everything describing a particular library — **`tag_schema`** and **`copyasis_folders`** — moved into the pool index (`.Curator.!index`, under a new `settings` section), so it travels with the pool. `FileManager` reads/writes those via the index (`PoolIndex.get_setting` / `set_setting`) and **migrates** any values left in an old global config into the index once, on first open (`FileManager._migrate_global_settings`). ### Removed - **Per-folder config (`.Curator.!ftag`)** and the whole legacy folder-scan path (`FileManager.append` / `get_folder_config` / `save_folder_config` / `set_ignore_patterns` / `get_ignore_patterns`, `config.load_folder_config` etc.) — inherited from Tagger and unused by the Filmotéka workflow. - **Per-file metadata sidecars (`.{filename}.!tag`).** `File` metadata is now stored only in the pool index; an index-less `File` is an in-memory object and writes nothing to disk. - **Old tkinter GUI** (`src/ui/gui.py`), orphaned since the PySide6 rewrite and the only remaining consumer of the folder-config/sidecar code. ## 1.7.0 — 2026-07-02 ### Added - **Nastavení → "Statistiky…"**: a library overview dialog — total movies, total size, average ČSFD rating, with/without ČSFD link, untagged count, and a per-category tag breakdown (counts sorted by frequency). `FileManager.statistics()` aggregates it. - **Testy menu → "Kontrola konzistence dat…"**: compares the pool index against the actual files in pool/Filmy and reports **metadata without a file** (movie deleted directly) and **files without metadata** (dropped into the pool without importing). Read-only diagnosis (`FileManager.check_data_consistency`). - **"- Nově přidané"** special folder in the Filmotéka: the **10** most recently added movies as hardlinks (while **"- Tipy dne"** holds **15** random ones). Import now records an **`added`** timestamp on each movie (`File.added`); ordering falls back to the file's **mtime** for older items — not ctime, which the hardlink generation itself bumps (`File.added_timestamp`, `HardlinkManager.generate_recently_added`). - Standalone **`scripts/tipy_dne.py`** to regenerate the "Tipy dne" folder independently of the app (deployable on the server / cron): reads the pool index, picks N random movies (default 15), clears the tips folder and recreates the hardlinks. Stdlib only; paths via `--pool`/`--output` or the global config. Ships with a systemd oneshot `tipy-dne.service` + daily `tipy-dne.timer`. ### Changed - **Special folders are prefixed with "- "** so DLNA/TV browsers sort them ahead of the genre folders at the output root: the grouping roots (now `- Dle roku`, `- Dle země původu`, `- Dle hodnocení`, …) plus `- Tipy dne` and `- Nově přidané`. Genre folders stay at the root, listed after the special ones. ### Fixed - Obsolete-link cleanup now also removes **orphan links** in the tag tree — hardlinks pointing at an inode no longer in the pool (movie re-imported / replaced) — not just links whose current pool inode moved. Within a managed folder, any file that isn't an expected link is swept, so leftovers from a renamed layout (e.g. an old unprefixed `Dle roku`) are cleaned up on the next generation instead of lingering forever. ## 1.6.0 — 2026-06-16 ### Added - Filmotéka generation now also creates a **"Tipy dne"** folder with **10 random pool movies** (hardlinks; copy-as-is folders like Seriály are excluded). The folder is refreshed each run (`HardlinkManager.generate_random_tips`). ### Fixed - Stale **root-level genre folders** were left in the Filmotéka output when the last movie carrying a genre dropped that tag: obsolete-link cleanup only scanned genre folders still present on current movies, so an emptied one was never visited. Cleanup now also sweeps other root-level folders (excluding the grouping roots and `reserved_subfolders` — copy-as-is mirrors, "Tipy dne") and removes the now-empty folder. Grouping folders (Dle roku/…) were already fine. ## 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 `/.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 `.` (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).