31 KiB
PROJECT.md
This file is project-specific. Only include information directly related to the concrete project — goals, current state, architecture decisions, known issues, and tasks.
Origin
Curator is a fork of the former Tagger project. The tagging, filtering and hardlink-tree parts are inherited and keep working as before. On top of that, Curator becomes a full movie library manager (Filmotéka).
Core idea
Curator manages a personal movie library based on two folders:
- Pool — the managed repository of video files. This is the single source of truth. Curator manages the pool itself (insert/remove file), so files are never moved by hand. The pool has exactly two top-level folders: Filmy (movies — tag-based tree) and Seriály (series — a "copy-as-is" folder mirrored 1:1 into the output; see Design decisions). Every file lives here exactly once.
- Filmotéka (output) — a generated, browsable directory tree made only of hardlinks into the pool (the same mechanism as today's hardlink manager). It is fully disposable: deleting the Filmotéka folder loses nothing, because it can always be regenerated from the pool.
Workflow
- The user configures two folders: the pool and the Filmotéka output.
- The user picks a video file via "Open file".
- Curator opens a dialog to fill in basic info — at minimum the title/name and a ČSFD link.
- Curator renames the file and moves it into the managed pool, and writes a metadata file describing it.
- From the pool, Curator generates the Filmotéka — a complex tree of hardlinks built from each file's tags/metadata (like the current hardlink manager, but driven by the pool).
- Deleting the Filmotéka has no effect on the pool; the tree is regenerated on demand.
Current state
- Inherited from Tagger:
Tag,TagManager,File(sidecar metadata),FileManager(folder scan, filtering, ignore patterns), 3-level config,HardlinkManager(create/sync/cleanup), pytest suite. - Rename Tagger → Curator done across code, spec, config filenames
(
.Curator.!gtag/.Curator.!ftag) and tests. - PySide6 GUI (
src/ui/qt_app.py) reframed around the Filmotéka workflow is the entry point; the old tkintersrc/ui/gui.pyis retained for reference. - Pool + Filmotéka wired up: global config holds
pool_dir/filmoteka_dir;FileManagercreatesFilmy/Seriály, imports movies (copy →Title.ext), loads the pool, and the GUI generates the Filmotéka tree viaHardlinkManager. Filecarriestitle+csfd_link. Pool metadata lives in a unified index (<pool>/.Curator.!index, seepool_index.py);Filewrites there when an index is injected. Without an index aFileis an in-memory object only — the old per-file.!tagsidecars and the per-folder.!ftagconfig have been removed.
GUI decision
The GUI was reframed around the Filmotéka (not kept as a generic tagger) and rewritten in PySide6: Pool/Filmotéka setup, Import movie, tag-filter sidebar, movie table, and one-click Filmotéka generation.
Design decisions
- Metadata storage: one unified metadata file for the whole pool (a central index), not per-file sidecars. Justified because Curator owns the pool and files are never moved manually, so it is not exposed to path drift.
- The record is the movie, files are its children (index v2,
movie.py). Keyed by identity (tmdb-<id>, else aname-<slug>fallback), each record holds the metadata and amedialist of its files with a freelabelper version. Chosen after the v1 path-keyed shape made multi-version movies unworkable: metadata rode on one file, so it had to be rescued when that file went, and retitling a grouped movie had to be forbidden. With identity as the key,Movie.desired_layout()is a pure function of the record — one video → flat, two or more → own folder — so promote, demote and rename are the same operation, and an import can recognise "this tmdbid is already in the pool" instead of silently creating a duplicate. - Config split by scope: the global config (
.Curator.!gtag) holds app data only — window state, MRU folders, and thepool_dir/filmoteka_dirpointers (which must stay global: the index is found through them). Everything that describes a specific library —tag_schemaandcopyasis_folders— lives in the pool index under asettingssection, so it travels with the pool.FileManagermigrates any pre-existing global values into the index on first open. The old per-folder.!ftagconfig and per-file.!tagsidecars were removed (they were unused Tagger leftovers), and the orphaned tkintersrc/ui/gui.pywent with them. - Import dialog: multi-file — pick several videos at once and give each
its own Title + ČSFD link (one row per file, more can be added from the
dialog), or auto-filled with "Najít ČSFD odkazy" (cleans each filename into
a query and fills the first ČSFD search hit; existing links are kept). A single
copy/move toggle decides whether the sources are copied (default) or moved
into the pool. Each file is renamed to
Title.ext. When a ČSFD link is given, Curator fetches the movie and assigns Žánr / Rok / Země původu / Hodnocení (ten-point band) tags automatically; further tags can be added via the UI. Directors and the first 10 actors are fetched and cached too, but deliberately not turned into tags/folders (there would be too many). - Genres / countries: a movie can have multiple genres and, for a co-production, multiple countries of origin (ČSFD writes them slash-separated, e.g. "USA / Velká Británie"). Each becomes its own tag, so the film appears under every matching genre and country branch in the Filmotéka (multiple hardlinks).
- Pool layout: two top-level folders — Filmy and Seriály. Movies are the first target; the Seriály branch follows the "copy-as-is" rule below.
- Copy-as-is folders (Seriály): a subfolder inside the pool can be marked as
copy / as-is. For such a folder Curator does not build a tag-based tree;
instead it mirrors the exact directory hierarchy from the pool into the
Filmotéka output, with the files materialized as hardlinks into the pool.
So
pool/Seriály/...is cloned 1:1 intooutput/Seriály/...(same structure, hardlinked files). This is how Seriály work. - File naming: pooled movies follow the convention
Title (YYYY).ext— a clean title plus a four-digit year in parentheses (naming.py:canonical_pool_stem/parse_pool_stem). The year lets two same-named films coexist and makes the filename self-describing (File.name_contextrecovers the year from the filename when no ČSFD/tag year is present). The GUI import requires a four-digit year (per-row Rok field, pre-filled from the source filename and fillable from ČSFD — a ČSFD-suggested year is tinted blue to be verified, a name colliding with the pool is tinted red). Filmy → "Přejmenovat dle ČSFD (rok)…" (FileManager.rename_all_to_canonical) brings olderTitle.extfiles up to the convention from their ČSFD metadata; files without a known year are skipped.File.titlealways stays the clean title (no year). - Import copy vs move: by default the original file is copied into the pool (non-destructive); the import dialog also offers a move option that relocates the source into the pool instead.
- Filmotéka tree layout: driven by a category → root-folder map
(
FILMOTEKA_CATEGORY_ROOTS). At the output root sit the genre folders directly (output/Akční/film, …), next to the copy-as-is mirrors (Seriály), plus two grouping folders:Dle roku(output/Dle roku/<rok>/film) andDle země původu(output/Dle země původu/<země>/film), plusDle hodnocení. Each is a hardlink.HardlinkManagersupports an empty root (tag folders placed directly at the output root) and restricts obsolete cleanup to the tag-tree's own top-level folders so mirrors are never touched. - Tag schema (config-driven, not hard-coded): the categories, their ČSFD
source field + transform, and their Filmotéka folder mapping all live in
tag_schemain the global config (defaultconfig.DEFAULT_TAG_SCHEMA, edited via Nastavení → Tag schéma…). Bothapply_csfd_tags(which fields → tags) and the Filmotéka layout (FileManager.filmoteka_category_roots) read from it, so adding a category or changing a folder rule needs no code change. A category can be made filter-only (no folders) by setting itsfilmoteka_rootto null. Thetransform(e.g.decade_band) shapes only the folder name — tags keep the exact value (rating → tagHodnocení/90, folderDle hodnocení/90–100 %); it is applied at Filmotéka generation viafilmoteka_category_transforms. - Filmotéka link naming (clean title, year on collision): the default
hardlink name in the tag tree is the movie's clean title —
Title.ext, not the pooledTitle (YYYY).ext. When two different films would collide on that name within one folder, the colliding ones keep the disambiguating year (Title (YYYY).ext); a residual same-title-and-year clash gets a stable numeric suffix.HardlinkManager._plan_linksresolves the whole tag tree to a collision-free(file, path)plan that both link creation and obsolete-detection consume, so they can never disagree on a name (the previous churn source). Copy-as-is mirrors (Seriály) are unaffected — they mirror 1:1. - Per-category filename template (
filename_templatein a schema entry): overrides the default naming inside that category's folders only, rendered from the movie's metadata (File.name_context: title/year/rating/ext/stem/ filename plus any free-form attributes), e.g. a Kolekce with"{collection_sort} - {title}{ext}". Applied viafilmoteka_category_filename_templates. - Free-form per-movie attributes (
File.attributes, set in the GUI): arbitrarykey → valuemetadata stored in the index and merged intoname_context, so custom fields likecollection_sortcan drive filename templates. - Tag provenance (ČSFD vs user): each file records which tags came from ČSFD
(
csfd_tags). Re-fetching regenerates only those; user-added tags are kept, so changing a movie's ČSFD link refreshes ČSFD tags without losing manual ones. - Special folders + "- " prefix: besides the tag tree, generation produces
- Tipy dne(15 random) and- Nově přidané(10 newest byFile.added, mtime fallback — ctime is bumped by hardlinking). All grouping folders and these specials are prefixed with "- " so DLNA/TV browsers sort them before the genre folders at the output root. Specials arereserved_subfoldersso cleanup never touches them; renaming old unprefixed folders is handled automatically (they become stale root-level folders and are swept on the next generation). - Date added:
import_moviestampsFile.added(ISO); older items without it fall back to the file's mtime for the "recently added" ordering (ctime is unreliable — creating the Filmotéka hardlinks bumps it).
Direction — Jellyfin pivot (in progress)
Curator is being refocused from a ČSFD-tagging tool into a Jellyfin-first library manager. Agreed shape:
- Pool = the Jellyfin library set. Files are named
Title (rok) [tmdbid-N].ext— the form Jellyfin matches on. Libraries are physical top-level pool folders, each with a kind (librariessetting; managed in Pool → Knihovny…):movie— single-file movies, TMDb naming (done)series— series/season/episode tree, mirrored 1:1 (done)mixed— movies and series in one folder (Jellyfin "Mixed" type; supported-but-discouraged by Jellyfin, but low-risk here thanks to the strict structure, and fully reversible — see decisions). Scanning TBD (phase 9).homevideo— files with no TMDb (Jellyfin "Home videos" type). TBD (ph. 8).
- Target library layout (the user's plan):
Hlavní(mixed, incl. kids),Dokumenty(mixed, mostly no-TMDb YT series),Anime(series),Chajda(homevideo). Kids are not a separate folder — see decisions. - Naming convention:
- Movie:
{CZ název} (rok) [tmdbid-N].ext— the exact Jellyfin form. A 3D movie carries an extra.3D.<FORMAT>tag before the extension (Jellyfin's own convention;FORMAT∈HSBS/FSBS/HTAB/FTAB/MVC), set via the edit dialog and preserved across every future rename like the tmdbid is. - Multiple movie versions (2D+3D, 4K+1080p, …): identity is
(title, rok, tmdbid), stays a flat file at one version. A second version promotes it to Jellyfin's required per-movie folder — every file prefixed with the exact folder name —Title (rok) [tmdbid]/Title (rok) [tmdbid] - {popisek}.ext; a 3D version's popisek doubles as its 3D tag (3D HSBS, scanned by Jellyfin the same as the flat.3D.HSBSform). Curator does the promotion/demotion itself (Upravit film → Verze), and a version's popisek is renamable too (rename_movie_version) — a flat movie always shows one placeholder row ("Výchozí") in the list even though it has no on-disk label yet, but renaming only applies once it's grouped. A popisek left empty (a plain "2D" file with nothing else to call it) isDEFAULT_VERSION_LABEL("Výchozí"), not an error — everywhere a popisek is entered (add_movie_version,rename_movie_version,merge_as_version, the import dialog's version link). Promoting/demoting always carries the file's own current 3D formát over automatically when no popisek is given for it (existing_label/keep_labeldefault toNone→ derived vianaming.parse_3d_format) — a movie can freely gain and lose versions in any order (3D-only → +2D → -3D → 3D-only again, …) without a formát ever getting silently dropped along the way. The default (plain) version of a grouped movie carries no label:Folder/Folder.ext(file_manager._version_filename); "Výchozí" is the UI's name for it. Identity — what decides "is this film already in the pool" — is the folder name for a grouped movie and the stem minus any.3D.<FORMAT>for a flat one (FileManager._identity_stem,pooled_with_stem). Importing a film that's already there defaults to adding a version, never a root-level duplicate, and "keep both as separate films" is not offered for same-TMDb-id films (they'd share one index record). Parts (one film split across files, Jellyfin "stacking") are versions with apartNpopisek, written asFolder-partN.extinstead ofFolder - popisek.ext(file_manager._version_filename,movie.normalize_part_label); a film with parts always lives in its own folder. Parts and versions in one folder are not combined yet. Upravit film presents this as one "Média filmu" section: flat ("Samostatný film") until a second medium arrives, then a folder whose mode is switched between versions and parts (movie_group_mode,set_movie_group_mode); parts are reordered with ↑/↓ (move_movie_part). Renames after a relabel go throughFile.save_location, never a plainsave_metadata, so a stale sibling can't overwrite shared metadata. Custom per-movie attributes are no longer editable in the GUI; stored ones still feed the miniDLNA filename templates. The table always shows one row per movie (visible_movies), marked▣/Film · N verzewhen it has several; shared metadata lives on the movie record (index v2), so every version reads the same. Accidental duplicates (same tmdbid / title+year / file size as separate movies) are found by Nástroje → Kontrola duplicit and folded in viamerge_as_version. Not yet supported: renaming/moving/retitling a grouped movie (remove the version or edit files directly). Works inmixedlibraries too, disambiguated from a series by shape: a movie's version folder and a series folder are bothTitle (rok) [tmdbid]/, but a series always nests episodes in aSeason NN/Specialsfolder and a movie's version folder never does (series.looks_like_series_folder) —load_pool_movies/is_movie_group_memberread the shape andscan_series(skip_movie_folders=Trueformixed) skips what it recognises as a movie, so both kinds can share one identity-named folder pair without either leaking into the other's listing. Aseries-kind library (which never holds movies) keeps reading every subfolder as a series regardless of shape, so a raw not-yet-renamed import can still have loose episode files at the series root beforeSeason NNexists. Assignable at import time too, not just after the fact: picking several files for the same film in one batch (Importovat filmy…), a row's "🔗 Přiřadit jako verzi…" marks it a version of another row instead of importing it as its own movie — the earlier attempt at this (a 3D formát combo per row) was rejected because a lone row has nothing to attach the version to and just created a duplicate; picking the target row explicitly is what that was missing. The popisek is pre-filled from the filename (naming.three_d_format_from_filename) the same wayyear_from_filenamepre-fills the year. Import runs the target rows first, then folds each linked row onto its target viaadd_movie_version. The reverse case — a film whose only file is 3D, with nothing to link it to — gets its own 3D formát combo per row instead (import_movie(..., format_3d=…), same as Upravit film); disabled the moment the row is linked as a version or gains one as a follower, since a grouped movie's 3D-ness lives on the version's popisek, not on the row that started the group. - Series:
{název série} (rok) [tmdbid]/Season NN/S{ss}E{ee} - {série}.ext(season 0 →Specials). The episode filename is the code + the series title only — the release string is dropped (identity is the code; Jellyfin fetches episode metadata from TMDb via the code +[tmdbid]). Episode numbers are recognised from the filename and editable at import / in the series GUI; Seriály → "Napravit názvy souborů…" bulk-fixes the whole pool. - Episodes split into physical parts (an old recording cut into
several files) get Jellyfin's own
.ptNstacking suffix —SxxEyy - {série}.ptN.ext— recognised from cd/dvd/part/pt/disc/disk + a number in the source filename (Show.S01E02.pt2.mkv,Show S01E01-part-1.mkv, …) or typed by hand at import asS01E02P3(src.core.series.parse_episode_code/parse_episode_part). Distinct from a multi-episode file (E01-E02, one file = several episodes) — a part is the opposite: several files = one episode. - Removing a series is scoped to the tree selection: Přehled seriálů →
"Odebrat z poolu…" deletes the series, a single season, or a single
episode, whichever is selected (
FileManager.remove_series/remove_season/remove_episodes) — series still carry no index metadata, so this is always just deleting files/folders from disk. - CZ title source: TMDb (cs-CZ) primarily — reliable when matched by
tmdbid(yearless title search picks the wrong film, hence the earlier "weird names"); ČSFD as fallback/override (a ČSFD link at import, or when TMDb has no cs title); last fallback = the original filename. The filename is always editable in the GUI.
- Movie:
- Metadata is Jellyfin's job: Jellyfin fetches from TMDb and (configured to)
writes
.nfo+ artwork into the folders → survives a Jellyfin reinstall. Curator resolves a TMDb id → title + year for naming (tmdb.py). ČSFD is kept only as a title source (not for tags/matching — it exposes no TMDb/IMDb id, verified): it feeds the ČSFD → TMDb bridge (csfd.py,FileManager.resolve_via_csfd) — filename → ČSFD search → ČSFD original title + year → TMDb search → tmdbid, more reliable than a raw Czech-title TMDb search. Used by the import dialog ("Dohledat přes ČSFD") and by migration for any movie with a storedcsfd_url. - Import: mandatory TMDb id (matching + CZ title), optional ČSFD link
(drives the ČSFD→TMDb bridge, and stored as a pointer); filename hand-editable
before/after import. The import row is pre-filled on open (no network) from
metadata already next to the source file: Curator's pre-index layouts —
a
.{filename}.!tagsidecar or an old.Curator.!indexup the tree (legacy_meta.read_legacy_meta, authoritative for the ČSFD link) — then a Kodi/Jellyfin.nfo(nfo.read_nfo, also reads acsfduniqueid/<csfdid>, and supplies the TMDb id). - miniDLNA: keep a genre-split hardlink tree for a dumb DLNA browser,
but drive it from the NFO Jellyfin wrote (repurposing
HardlinkManager+naming.py), not from ČSFD tags. Scoped per library (miniDLNA → "Knihovny pro generování…",minidlna_librariesin the pool index, default = all) — lets a library (e.g. Kids) be excluded from the dumb-DLNA output entirely; a movie library's genre-tree links are swept as obsolete the normal way when it's excluded. Aseriesorhomevideo-kind library has no genre tags to build a tree from (homevideo has no TMDb id either), so both hardcopy whole intooutput/<jejich vlastní název>(mirror_as_is_libraries) — this works today independent of phase 8 (homevideo needs no GUI import/browse support to be mirrored). Mirrors have no obsolete-link mechanism of their own, so excluding one is pruned explicitly (FileManager.minidlna_prune_targets+HardlinkManager.remove_mirror) — including the sharedoutput/Seriálybucket a mixed-library series and a same-named real library can both write into, reconciled per-entry so pruning one never erases the other's still-valid content. - Jellyfin API (thin, hybrid): trigger scans, verify, manage collections;
key + URL in
.env. NFO on disk stays authoritative. - Subtitles stay external, never muxed (
subtitles.py): a.srt/.ass/.ssa/.sub/.vttsharing the video's stem (+ optional.lang/.forced/.sdh/.default) plays natively in Jellyfin — no re-encode, multiple languages side by side, easy to replace. Presence is read live from disk (like NFO), not cached in the index. The one thing Curator owns: a subtitle follows its video through every rename/move so it never silently detaches (every rename/move path callssubtitles.rename_subtitles_with).
Decisions (locked)
- Kids = Jellyfin parental rating, not a folder (variant B). Kids content stays in the normal libraries; a child Jellyfin account gets a max-rating limit with "block unrated" ON. Ratings come from TMDb via Jellyfin. Caveat: Czech certifications are sparse — set the library metadata country to one with fuller ratings (US) and fix odd cases in Jellyfin (Identify + lock).
- Mixed libraries are OK and reversible. Jellyfin discourages them (unreliable
metadata) but our disciplined structure (series =
Season/SxxEyy, movie = single file +[tmdbid]) gives the scanner clear signals. Because metadata is NFO on disk + tmdbid in the name, switching mixed↔separate later is just moving folders (Jellyfin re-reads NFO, no re-fetch, no loss). - Home videos (Chajda) use Jellyfin's "Home videos" content type — no online lookup, just serves the files.
- Always match by tmdbid, not by yearless title. TMDb cs-CZ titles are good when the right film is matched (lookup by id, or search with a year); a bare title search trips on remakes/same-name films (verified: Home Alone → 2021 reboot, Vejška → 2006). Import supplies the id; migration searches with a year.
Phasing
(1) TMDb identity + tmdbid naming + import rework [done]; (2) movie libraries as
physical partitions + import-into / move-between [done]; (3) Jellyfin API — scan +
verify + status [done]; (4) miniDLNA genre tree from NFO + ČSFD removed [done];
(5) series model — SxxEyy layout, import, rename [done]; (6) migrate the existing
pool — title+year → TMDb search → tmdbid [done]; (7) consolidated edit dialogs +
library organizer (unified libraries model with kinds) [done]; (9) mixed-library
scanning + generation (movies + series in one folder) [done].
Remaining: (8) import without TMDb/year (homevideo, no-TMDb docs).
Note: libraries hold flat files for now (pool/<Library>/Title (rok) [tmdbid].ext); per-movie folders are a later refinement.
Tasks
Phase 8 — import without TMDb / without year (homevideo, no-TMDb docs)
- Relax the import dialog: allow a row with no year and no tmdbid (currently
a 4-digit year is mandatory). Pool name then falls back to
Title.ext(movies) /Series/Season NN/SxxEyy.ext(series), no[tmdbid]. - Drive this by library kind:
homevideo(and no-TMDb rows inmixed) skip the TMDb requirement;moviekeeps requiring a year (as today). homevideolibrary scanning: load its files as items (flat), no TMDb naming, no genre tags. Consider a dedicated import path or a per-import "bez TMDb" toggle. miniDLNA generation forhomevideoneeds none of this — it hardcopies the whole library folder as-is (mirror_as_is_libraries[done]); this task is only about GUI import/browsing.- Tests: import with no year/id lands correctly; homevideo library loads.
Phase 9 — mixed-library scanning + generation
- Scanning [done]: a
mixedlibrary's top-level files load as movies (load_pool_movies), its subfolders as series (load_series/series_libraries= kindseriesormixed, reusingscan_series) — except a canonical-shaped subfolder with noSeason NN/Specialsinside it, which is a movie's version folder instead and loads as a movie too (see "Multiple movie versions" above). Both are kind-aware over the unifiedlibrarieslist. The library filter / table already surfaces mixed libraries. - miniDLNA generation [done]: a mixed library's movies are genre-tagged as
usual; series in its subfolders have no genre tags, so they're hardcopied
straight into
output/Seriály/<název>/…instead (FileManager.minidlna_scope→ the mixed-library series list; the mirror loop ingenerate_filmoteka). NFO refresh needs no separate handling — it only ever touches movies (self.filelist), series never go through NFO/TMDb tagging, mixed or not. - Tests: a mixed folder with both a movie and a series loads/generates
correctly [done] (
TestMinidlnaScopeintest_file_manager.py).
Later / optional
- ČSFD → TMDb title bridge (done). A direct TMDb search on a Czech title was
matching the wrong film too often, so a lean
csfd.pywas revived (Anubis PoW solver, film-page + search parsing,original_titleextraction — the entry in.film-nameswhose flag matches the country of origin).resolve_via_csfddrives it from the import dialog and from migration. Not a return of ČSFD tags — genres still come from TMDb via Jellyfin/NFO. Open follow-up: batch "look up ČSFD links for the whole pool" as a migration pre-pass (today thecsfd_urlis per-movie, set at import / in the edit dialog). - Per-movie folders (
pool/<Lib>/Title (rok) [tmdbid]/…) instead of flat files — partially done: a movie only gets its own folder once it has a second playable version (see "Multiple movie versions" below); a single-file movie stays flat as before. - Collections / BoxSets (Bond, Marvel) via Jellyfin API or
<set>in NFO. - Optional common umbrella folder above libraries (
pool/Média/<Lib>) — needs path-awarelibrary_of(currently single-segment).
Done
- Jellyfin pivot phases 1–7 (see Phasing): TMDb identity, tmdbid naming, movie libraries + move, Jellyfin API (scan/verify), NFO-driven miniDLNA + ČSFD removed, series model (SxxEyy), pool→TMDb migration, consolidated edit dialogs
- Phase 9 — mixed-library scanning + generation: a
mixedlibrary's top-level movies genre-tag as usual, series in its subfolders hardcopy intooutput/Seriály/<název>/…(no genre tags apply to a series) - Library organizer (
librariesunified model with kinds movie/series/mixed/ homevideo; Pool → Knihovny…);movie_libraries/copyasis_foldersderived - Pool naming convention
Title (YYYY).ext(naming.py): year required at import (per-row Rok field, blue = ČSFD-suggested, red = pool collision), plus "Přejmenovat dle ČSFD (rok)" to bring older files up to the convention - Video integrity check (
integrity.py, Testy → "Kontrola integrity videa"): FFmpeg-based scan for corrupted/unreadable video data, deep (full decode) or quick (ffprobe), over selected movies or the whole pool - Pool-root and Filmotéka-output folder settings in the global config
- Filmy / Seriály top-level folder handling in the pool
- "Import movie" dialog (Title + ČSFD link), copy into pool/Filmy as Title.ext
- Rename a pooled movie from the app (
FileManager.rename_movie): renames the file in pool/Filmy and moves its metadata to the new index key - Remove-from-pool (delete file + its metadata)
- Generate the Filmotéka hardlink tree from the pool (Rok / Žánr / Země původu / Hodnocení)
- Filmotéka fully regenerable from the pool alone (delete output = no loss)
- GUI reframed around the Filmotéka and rewritten in PySide6
- Seriály "copy-as-is" mirror: pool/Seriály cloned 1:1 into the output as
hardlinks (
HardlinkManager.mirror_as_is), wired into Filmotéka generation - Fixed
media_utilsmissingsubprocessimport - Unified pool metadata index (
pool_index.py): one.Curator.!indexper pool;Filereads/writes it when injected,FileManageruses it for the pool - Configurable copy-as-is folders (
copyasis_foldersin global config, editable from the GUI); each is mirrored 1:1 during Filmotéka generation (Seriály default) - README.md written (overview, concepts, workflow, run/build instructions)
- ČSFD scraping (
csfd.py, ported from Tagger devel):File.apply_csfd_tagsfetches a movie and assigns Žánr / Rok / Země původu tags (cached in metadata); wired into the GUI (auto-fetch on import with a ČSFD link, plus "Načíst tagy z ČSFD"). Parsing updated for current ČSFD HTML and verified live against Matrix (film/9499); HTTPS uses the OS cert store viatruststore(corporate SSL) - ČSFD Anubis anti-bot wall handled:
csfd.pydetects the proof-of-work challenge page, solves it (SHA-256 PoW matching the bundled worker JS) and replays via a sharedrequests.Session, so Žánr / Rok / Země původu tags load again (the "nalezeno 1 film, načteno 0 tagů" symptom). Verified live (Matrix 1999) - Removed the inherited Tagger predefined tags:
DEFAULT_TAGSis now empty (no Hodnocení ⭐ / Barva categories) and new files no longer get an automaticStav/Novétag. Tags now come from ČSFD (Žánr / Rok / Země původu) and manual edits. Note:Hodnoceníis still listed inFILMOTEKA_CATEGORIES, so that branch is simply empty until something assigns a Hodnocení tag again - Fixed template cruft:
src/constants.pymade consistent (Curator values,get_version/get_debug_modeAPI) andtest_constants.pyaligned; removed the importedtagger/devel dump