20 KiB
20 KiB
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.11.0 — 2026-07-27
Added
- Pool naming convention
Title (YYYY).ext(src/core/naming.py). Pooled movies now carry a four-digit year in parentheses, so two same-named films coexist and the filename is self-describing.- Import requires a year: the import dialog has a per-row Rok field
(pre-filled from the source filename), and "Najít ČSFD odkazy" now also
fills the year from the top ČSFD hit — a suggested year is tinted blue
(verify it), a name colliding with the pool stays red. The import is
blocked until every row has a four-digit year, and the file lands in the pool
as
Title (rok).ext. Same title + different year no longer collide. - Rename to the convention: Filmy → "Přejmenovat dle ČSFD (rok)…"
(
FileManager.rename_all_to_canonical) renames selected movies (or the whole pool) toTitle (rok).extusing the year from their ČSFD metadata, with a cancelable progress dialog; files with no known year are skipped and name collisions reported.File.titlekeeps the clean title (without the year);File.name_contextrecovers the year from a canonical filename when no ČSFD/tag year exists.
- Import requires a year: the import dialog has a per-row Rok field
(pre-filled from the source filename), and "Najít ČSFD odkazy" now also
fills the year from the top ČSFD hit — a suggested year is tinted blue
(verify it), a name colliding with the pool stays red. The import is
blocked until every row has a four-digit year, and the file lands in the pool
as
Changed
- Filmotéka hardlinks are named by the clean title (no year). In the tag
tree a movie now links as
Title.extinstead of the pooledTitle (YYYY).ext. When two different films would collide on that name in the same folder, the colliding ones keep the year (Title (YYYY).ext) to tell them apart; a residual clash (same title and year) gets a stable numeric suffix. Copy-as-is mirrors (Seriály) are untouched — they stay 1:1. A per-categoryfilename_template(Tag schéma dialog) still overrides the default inside its folders. Link creation and obsolete-detection now share one collision-resolved plan (HardlinkManager._plan_links), so they can't disagree on a name — the first generation after upgrading re-lays the affected links once, then it is stable. File.name_context'syearfield is now always a string (was an int when it came straight from the ČSFD cache) — uniform for filename templates.
1.10.0 — 2026-07-07
Added
- Video integrity check (
src/core/integrity.py, Testy → "Kontrola integrity videa…"): scans movies for corrupted/unreadable video data via FFmpeg. Two depths — Důkladná (fullffmpegdecode to-f null, catches mid-file data corruption, slow) and Rychlá (ffprobecontainer/stream probe, catches truncated/unreadable files, fast). Runs on the selected movies or the whole pool, with a cancelable progress dialog, and reports the bad files (with the first decoder error) separately from those that couldn't be checked.FileManager.scan_video_integritydrives the scan;check_video_integritychecks one file and degrades gracefully when FFmpeg isn't installed.
1.9.0 — 2026-07-07
Fixed
- Endless Filmotéka churn (
vytvořeno: N, odebráno: Nevery run). When a movie was re-imported/replaced (new inode), the old hardlink kept the correct name but pointed at the dead inode. Obsolete detection matched links by name only, so it spared that orphan and instead forced the freshly created, correct link to a_1suffix — which, having an unexpected name, was swept on the next run and recreated again, forever.HardlinkManager.find_obsolete_linksnow matches both the path and the inode: an orphan squatting the expected name is removed and the base name is restored, so generation is stable after one cleanup run.
Changed
- Status bar now shows the current Pool and Filmotéka paths as permanent widgets (right-aligned), so they stay visible while the transient area shows the movie count or the current selection — previously the paths were part of the transient message and were wiped as soon as a selection updated the status.
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 thepool_dir/filmoteka_dirpointers. Everything describing a particular library —tag_schemaandcopyasis_folders— moved into the pool index (.Curator.!index, under a newsettingssection), so it travels with the pool.FileManagerreads/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_configetc.) — inherited from Tagger and unused by the Filmotéka workflow. - Per-file metadata sidecars (
.{filename}.!tag).Filemetadata is now stored only in the pool index; an index-lessFileis 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
addedtimestamp 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.pyto 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/--outputor the global config. Ships with a systemd oneshottipy-dne.service+ dailytipy-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 dneand- 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 (
attributesin the index, set via Filmy → Nastavit atribut… / context menu): arbitrarykey → valuepairs (e.g.collection_sort) that are exposed to Filmotéka filename templates throughFile.name_context. So a Kolekce template"{collection_sort} - {title}{ext}"orders the folder as01 - 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 (fieldstitle/year/rating/ext/stem/filename), e.g. a Kolekce with"{year} - {title}{ext}"yieldsDle kolekce/James Bond/1962 - Dr. No.mkvwhile every other folder (and the pool) keeps the plain filename.HardlinkManagertakescategory_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
transformnow 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_valuesno longer transforms;HardlinkManagertakes acategory_transformsmap (FileManager.filmoteka_category_transforms) andapply_transform/decade_bandrun 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_schemain 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_bandfor the rating), and how each maps into the Filmotéka tree (""= output root,"Dle …"= grouping folder, none = filter-only, no folders). Replaces the hard-codedFILMOTEKA_CATEGORY_ROOTSand the fixed category list inapply_csfd_tags;HardlinkManagerroots are now derived from the schema (FileManager.filmoteka_category_roots). - Tag provenance (ČSFD vs user): each file tracks which tags came from ČSFD
(
csfd_tagsin the index).apply_csfd_tagsnow 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ályfolders 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/FilmyasTitle.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_querystrips 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_moviegained anon_conflictpolicy (suffix/replace/skip);replaceevicts the existing same-named movie (file + index metadata) first. Filenow storestitleandcsfd_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ályis 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.!indexJSON keyed by pool-relative path.Filereads/writes the index when one is injected and otherwise keeps using per-file.!tagsidecars;FileManageruses the index for the pool. - Configurable copy-as-is folders:
copyasis_foldersin the global config (editable from the GUI) lists pool subfolders mirrored 1:1 during generation;Seriályis 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_tagsassigns Žá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_movierenames the physical file in pool/Filmy to<new name>.<ext>(extension preserved), moves its metadata to the new index key, and syncstitle/filename. Refuses empty names, names with path separators, and collisions with an existing pooled file. - App startup injects
truststoreso 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_tagreferencedsubprocesswithout 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.pynow detects the Anubis challenge, solves the SHA-256 proof-of-work the way the bundled worker JS does, and replays the request through arequests.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.ItemIsTristatewas renamed toQt.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 viaQTimer.singleShotso the tree is not rebuilt inside its ownitemChangedsignal (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.pyto rewrite the category in an existing pool index (backs up.Curator.!indexfirst); 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 rokufolder and country tags underDle země původu.HardlinkManagergained 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 underDle 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.
CSFDMoviegainedcountries: list[str](replacing the singlecountry); 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-pythonnarrowed to>=3.14,<3.15(PySide6 compatibility).
Removed
- Duplicate
src/core/constants.py(hard-coded staleVERSION = "v1.0.3"). The GUI window title imported from it, so it showed the wrong version. All imports now usesrc/constants.py(version derived frompyproject.toml), which also absorbedAPP_VIEWPORT; the window title usesAPP_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_TAGSis 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). Declaredpython-dotenv,pillow,loguru(already imported by the inherited code).