Rework Tagger fork into Curator movie-library manager (PySide6 GUI, pool index, ČSFD import)
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
from pathlib import Path
|
||||
import json
|
||||
from .tag import Tag
|
||||
|
||||
# Bump this when the csfd_cache schema changes to force re-fetch on next open.
|
||||
CSFD_CACHE_VERSION = 1
|
||||
|
||||
|
||||
class File:
|
||||
def __init__(self, file_path: Path, tagmanager=None, index=None) -> None:
|
||||
self.file_path = file_path
|
||||
self.filename = file_path.name
|
||||
self.metadata_filename = self.file_path.parent / f".{self.filename}.!tag"
|
||||
# Optional unified pool index; when set, metadata lives there instead of
|
||||
# in the sidecar file (see PoolIndex).
|
||||
self.index = index
|
||||
self.new = True
|
||||
self.ignored = False
|
||||
self.tags: list[Tag] = []
|
||||
self.tagmanager = tagmanager
|
||||
# new: optional date string "YYYY-MM-DD" (assigned manually)
|
||||
self.date: str | None = None
|
||||
# movie-library fields
|
||||
self.title: str | None = None
|
||||
self.csfd_link: str | None = None
|
||||
# Cached CSFD data — avoids re-fetching on every open
|
||||
self.csfd_cache: dict | None = None
|
||||
self.get_metadata()
|
||||
|
||||
def get_metadata(self) -> None:
|
||||
if self.index is not None:
|
||||
record = self.index.get(self.file_path)
|
||||
if record is None:
|
||||
self._init_new_metadata()
|
||||
self.save_metadata()
|
||||
else:
|
||||
self._apply_record(record)
|
||||
return
|
||||
|
||||
if not self.metadata_filename.exists():
|
||||
self._init_new_metadata()
|
||||
self.save_metadata()
|
||||
else:
|
||||
self.load_metadata()
|
||||
|
||||
def _init_new_metadata(self) -> None:
|
||||
self.new = True
|
||||
self.ignored = False
|
||||
self.tags = []
|
||||
self.date = None
|
||||
self.title = None
|
||||
self.csfd_link = None
|
||||
self.csfd_cache = None
|
||||
if self.tagmanager:
|
||||
tag = self.tagmanager.add_tag("Stav", "Nové")
|
||||
self.tags.append(tag)
|
||||
|
||||
def _build_record(self) -> dict:
|
||||
data = {
|
||||
"new": self.new,
|
||||
"ignored": self.ignored,
|
||||
# ukládáme full_path tagů
|
||||
"tags": [tag.full_path if isinstance(tag, Tag) else tag for tag in self.tags],
|
||||
# date může být None
|
||||
"date": self.date,
|
||||
"title": self.title,
|
||||
"csfd_link": self.csfd_link,
|
||||
}
|
||||
if self.csfd_cache is not None:
|
||||
data["csfd_cache"] = {"version": CSFD_CACHE_VERSION, **self.csfd_cache}
|
||||
return data
|
||||
|
||||
def _apply_record(self, data: dict) -> None:
|
||||
self.new = data.get("new", True)
|
||||
self.ignored = data.get("ignored", False)
|
||||
self.tags = []
|
||||
self.date = data.get("date", None)
|
||||
self.title = data.get("title", None)
|
||||
self.csfd_link = data.get("csfd_link", None)
|
||||
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"}
|
||||
else:
|
||||
self.csfd_cache = None
|
||||
|
||||
if not self.tagmanager:
|
||||
return
|
||||
|
||||
for tag_str in data.get("tags", []):
|
||||
if "/" in tag_str:
|
||||
category, name = tag_str.split("/", 1)
|
||||
tag = self.tagmanager.add_tag(category, name)
|
||||
self.tags.append(tag)
|
||||
|
||||
def save_metadata(self):
|
||||
data = self._build_record()
|
||||
if self.index is not None:
|
||||
self.index.set(self.file_path, data)
|
||||
return
|
||||
with open(self.metadata_filename, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
def load_metadata(self) -> None:
|
||||
with open(self.metadata_filename, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
self._apply_record(data)
|
||||
|
||||
def delete_metadata(self) -> None:
|
||||
"""Remove this file's metadata (from the index, or its sidecar file)."""
|
||||
if self.index is not None:
|
||||
self.index.delete(self.file_path)
|
||||
elif self.metadata_filename.exists():
|
||||
self.metadata_filename.unlink()
|
||||
|
||||
def set_date(self, date_str: str | None):
|
||||
"""Nastaví datum (např. '2025-09-25') nebo None pro smazání."""
|
||||
if date_str is None or date_str == "":
|
||||
self.date = None
|
||||
else:
|
||||
# neprvádíme složitou validaci zde; očekáváme 'YYYY-MM-DD'
|
||||
self.date = date_str
|
||||
self.save_metadata()
|
||||
|
||||
def set_csfd_link(self, url: str | None) -> None:
|
||||
"""Nastaví CSFD odkaz nebo None. Invaliduje cache při změně odkazu."""
|
||||
new_url = url if url else None
|
||||
if new_url != self.csfd_link:
|
||||
self.csfd_cache = None # odkaz se změnil — stará cache je neplatná
|
||||
self.csfd_link = new_url
|
||||
self.save_metadata()
|
||||
|
||||
def get_cached_movie(self):
|
||||
"""Vrátí CSFDMovie z cache, nebo None. Nevyžaduje síť."""
|
||||
if self.csfd_cache is None:
|
||||
return None
|
||||
try:
|
||||
from .csfd import CSFDMovie
|
||||
return CSFDMovie.from_dict(self.csfd_cache)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def apply_csfd_tags(
|
||||
self, add_genres: bool = True, add_year: bool = True, add_country: bool = True
|
||||
) -> dict:
|
||||
"""Načte informace z CSFD a přiřadí tagy (Žánr, Rok, Země); cachuje data.
|
||||
|
||||
Returns:
|
||||
dict s klíči 'success', 'movie'/'error', 'tags_added'
|
||||
"""
|
||||
if not self.csfd_link:
|
||||
return {"success": False, "error": "CSFD odkaz není nastaven", "tags_added": []}
|
||||
|
||||
try:
|
||||
from .csfd import fetch_movie
|
||||
movie = fetch_movie(self.csfd_link)
|
||||
self.csfd_cache = movie.to_dict()
|
||||
except ImportError as e:
|
||||
return {"success": False, "error": f"Chybí závislosti: {e}", "tags_added": []}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"Chyba při načítání CSFD: {e}", "tags_added": []}
|
||||
|
||||
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 and movie.country:
|
||||
_add("Země", movie.country)
|
||||
|
||||
# Use the CSFD title if we don't have one yet
|
||||
if movie.title and not self.title:
|
||||
self.title = movie.title
|
||||
|
||||
self.save_metadata()
|
||||
return {"success": True, "movie": movie, "tags_added": tags_added}
|
||||
|
||||
def add_tag(self, tag):
|
||||
# tag může být Tag nebo string
|
||||
from .tag import Tag as TagClass
|
||||
if isinstance(tag, str):
|
||||
if "/" in tag and self.tagmanager:
|
||||
category, name = tag.split("/", 1)
|
||||
tag_obj = self.tagmanager.add_tag(category, name)
|
||||
else:
|
||||
tag_obj = TagClass("default", tag)
|
||||
elif isinstance(tag, TagClass):
|
||||
tag_obj = tag
|
||||
else:
|
||||
return
|
||||
if tag_obj not in self.tags:
|
||||
self.tags.append(tag_obj)
|
||||
self.save_metadata()
|
||||
|
||||
def remove_tag(self, tag):
|
||||
# tag může být Tag nebo string (full_path)
|
||||
if isinstance(tag, str):
|
||||
if "/" in tag:
|
||||
category, name = tag.split("/", 1)
|
||||
tag_obj = Tag(category, name)
|
||||
else:
|
||||
tag_obj = Tag("default", tag)
|
||||
elif isinstance(tag, Tag):
|
||||
tag_obj = tag
|
||||
else:
|
||||
return
|
||||
if tag_obj in self.tags:
|
||||
self.tags.remove(tag_obj)
|
||||
self.save_metadata()
|
||||
Reference in New Issue
Block a user