Add per-movie attributes and per-category filename templates

This commit is contained in:
2026-06-16 17:39:39 +02:00
parent b3a61f9e86
commit a71b209539
19 changed files with 1064 additions and 111 deletions
+81 -30
View File
@@ -26,6 +26,12 @@ class File:
self.csfd_link: str | None = None
# Cached CSFD data — avoids re-fetching on every open
self.csfd_cache: dict | None = None
# full_paths of tags that came from ČSFD (vs. user-added); only these are
# regenerated when the ČSFD link changes — user tags are preserved.
self.csfd_tag_paths: set[str] = set()
# free-form per-movie attributes (e.g. "collection_sort"); usable in
# Filmotéka filename templates via name_context.
self.attributes: dict[str, str] = {}
self.get_metadata()
def get_metadata(self) -> None:
@@ -52,6 +58,8 @@ class File:
self.title = None
self.csfd_link = None
self.csfd_cache = None
self.csfd_tag_paths = set()
self.attributes = {}
def _build_record(self) -> dict:
data = {
@@ -59,10 +67,13 @@ class File:
"ignored": self.ignored,
# ukládáme full_path tagů
"tags": [tag.full_path if isinstance(tag, Tag) else tag for tag in self.tags],
# which of the tags came from ČSFD (provenance)
"csfd_tags": sorted(self.csfd_tag_paths),
# date může být None
"date": self.date,
"title": self.title,
"csfd_link": self.csfd_link,
"attributes": self.attributes,
}
if self.csfd_cache is not None:
data["csfd_cache"] = {"version": CSFD_CACHE_VERSION, **self.csfd_cache}
@@ -75,6 +86,9 @@ class File:
self.date = data.get("date", None)
self.title = data.get("title", None)
self.csfd_link = data.get("csfd_link", None)
# Legacy records have no provenance → treated as all-user (empty set)
self.csfd_tag_paths = set(data.get("csfd_tags", []))
self.attributes = dict(data.get("attributes", {}))
raw_cache = data.get("csfd_cache")
if raw_cache and raw_cache.get("version") == CSFD_CACHE_VERSION:
self.csfd_cache = {k: v for k, v in raw_cache.items() if k != "version"}
@@ -103,6 +117,44 @@ class File:
data = json.load(f)
self._apply_record(data)
def name_context(self) -> dict:
"""Fields for a Filmotéka filename template (see config tag schema).
Provides title / year / rating / ext / stem / filename from the movie's
metadata (year & rating come from the ČSFD cache, year falling back to a
``Rok`` tag). Missing values render as an empty string.
"""
cache = self.csfd_cache or {}
year = cache.get("year")
if year is None:
for t in self.tags:
if isinstance(t, Tag) and t.category == "Rok" and t.name.isdigit():
year = t.name
break
rating = cache.get("rating")
# user attributes first; core fields take precedence over same-named keys
context = dict(self.attributes)
context.update({
"title": self.title or self.file_path.stem,
"year": "" if year is None else year,
"rating": "" if rating is None else rating,
"ext": self.file_path.suffix,
"stem": self.file_path.stem,
"filename": self.filename,
})
return context
def set_attribute(self, key: str, value: str | None) -> None:
"""Set a free-form attribute (e.g. 'collection_sort'); None/'' removes it."""
key = key.strip()
if not key:
return
if value:
self.attributes[key] = value
else:
self.attributes.pop(key, None)
self.save_metadata()
def delete_metadata(self) -> None:
"""Remove this file's metadata (from the index, or its sidecar file)."""
if self.index is not None:
@@ -153,19 +205,14 @@ class File:
except Exception:
return None
def apply_csfd_tags(
self,
add_genres: bool = True,
add_year: bool = True,
add_country: bool = True,
add_rating: bool = True,
) -> dict:
"""Načte informace z CSFD a přiřadí tagy; cachuje data.
def apply_csfd_tags(self, schema: list[dict] | None = None) -> dict:
"""Načte data z ČSFD a přegeneruje **jen ČSFD tagy** podle tag schématu.
Tagy: Žánr, Rok, Země původu a Hodnocení (procenta zařazená do desítkového
pásma, např. ``80–89 %``). Režie a herci se z ČSFD **stahují a cachují**
(``csfd_cache``), ale záměrně se z nich netvoří tagy ani složky — bylo by
jich příliš mnoho.
``schema`` je seznam definic kategorií (viz ``DEFAULT_TAG_SCHEMA``); pro
každý záznam s ``csfd_field`` vytvoří tagy z příslušného pole filmu
(s volitelným ``transform``). Tagy přidané uživatelem (mimo provenance
ČSFD) zůstávají nedotčené — proto změna ČSFD odkazu nepřepíše vlastní tagy.
Režie/herci se stále cachují, tag se z nich tvoří jen je-li v schématu.
Returns:
dict s klíči 'success', 'movie'/'error', 'tags_added'
@@ -173,6 +220,10 @@ class File:
if not self.csfd_link:
return {"success": False, "error": "CSFD odkaz není nastaven", "tags_added": []}
if schema is None:
from .config import DEFAULT_TAG_SCHEMA
schema = DEFAULT_TAG_SCHEMA
try:
from .csfd import fetch_movie
movie = fetch_movie(self.csfd_link)
@@ -182,25 +233,25 @@ class File:
except Exception as e:
return {"success": False, "error": f"Chyba při načítání CSFD: {e}", "tags_added": []}
from .csfd import csfd_field_values
# Drop previous ČSFD-sourced tags; keep user-added ones.
self.tags = [t for t in self.tags if t.full_path not in self.csfd_tag_paths]
self.csfd_tag_paths = set()
tags_added: list[str] = []
def _add(category: str, name: str) -> None:
tag_obj = self.tagmanager.add_tag(category, name) if self.tagmanager else Tag(category, name)
if tag_obj not in self.tags:
self.tags.append(tag_obj)
tags_added.append(f"{category}/{name}")
if add_genres and movie.genres:
for genre in movie.genres:
_add("Žánr", genre)
if add_year and movie.year:
_add("Rok", str(movie.year))
if add_country:
for country in movie.countries:
_add("Země původu", country)
if add_rating and movie.rating is not None:
from .csfd import rating_band
_add("Hodnocení", rating_band(movie.rating))
for entry in schema:
field = entry.get("csfd_field")
if not field:
continue # user-only category
category = entry["category"]
for value in csfd_field_values(movie, field):
tag_obj = (self.tagmanager.add_tag(category, value)
if self.tagmanager else Tag(category, value))
if tag_obj not in self.tags:
self.tags.append(tag_obj)
tags_added.append(tag_obj.full_path)
self.csfd_tag_paths.add(tag_obj.full_path)
# Use the CSFD title if we don't have one yet
if movie.title and not self.title: