Unify movies and series list, add TMDb-sourced tags and detail panel
This commit is contained in:
@@ -826,6 +826,47 @@ class FileManager:
|
||||
self.on_files_changed(self.filelist)
|
||||
return file_obj
|
||||
|
||||
def refresh_tags_from_tmdb(
|
||||
self, files: list[File] | None = None,
|
||||
on_progress: Callable[[int, int, File], None] | None = None,
|
||||
should_cancel: Callable[[], bool] | None = None,
|
||||
) -> dict:
|
||||
"""(Re)derive each movie's tags from **TMDb** (by the tmdbid in its name).
|
||||
|
||||
Fetches genres / year / country / rating from TMDb per the tag schema and
|
||||
regenerates the auto tags (`File.apply_nfo`, which is source-agnostic);
|
||||
user-added tags are kept. Movies with no tmdbid in the filename are skipped.
|
||||
|
||||
Returns ``{ok, updated, without_id, errors}``.
|
||||
"""
|
||||
from . import tmdb
|
||||
if not tmdb.api_available():
|
||||
return {"ok": False, "error": "TMDB_API_KEY není nastaven (.env).",
|
||||
"updated": 0, "without_id": 0, "errors": []}
|
||||
targets = files if files is not None else self.filelist
|
||||
schema = self.tag_schema
|
||||
updated, without_id = 0, 0
|
||||
errors: list[str] = []
|
||||
total = len(targets)
|
||||
for i, f in enumerate(targets, 1):
|
||||
if should_cancel is not None and should_cancel():
|
||||
break
|
||||
tmdb_id = parse_tmdb_id(f.file_path.stem)
|
||||
if tmdb_id is None:
|
||||
without_id += 1
|
||||
else:
|
||||
try:
|
||||
f.apply_nfo(tmdb.movie_details(tmdb_id), schema)
|
||||
updated += 1
|
||||
except tmdb.TmdbError as exc:
|
||||
errors.append(f"{f.filename}: {exc}")
|
||||
if on_progress is not None:
|
||||
on_progress(i, total, f)
|
||||
if updated and self.on_files_changed:
|
||||
self.on_files_changed(self.filelist)
|
||||
return {"ok": True, "updated": updated, "without_id": without_id,
|
||||
"errors": errors}
|
||||
|
||||
def match_pool_to_tmdb(
|
||||
self, files: list[File] | None = None,
|
||||
on_progress: Callable[[int, int, File], None] | None = None,
|
||||
|
||||
@@ -35,6 +35,33 @@ class TmdbResult:
|
||||
kind: str # "movie" | "tv"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TmdbDetails:
|
||||
"""Full TMDb data used to (re)generate a movie's tags (genres etc.).
|
||||
|
||||
Exposes :meth:`field_values` so it plugs into the tag schema the same way the
|
||||
NFO reader does (fields: genres / countries / year / rating).
|
||||
"""
|
||||
|
||||
tmdb_id: int
|
||||
title: str
|
||||
year: Optional[str]
|
||||
genres: list[str]
|
||||
countries: list[str]
|
||||
rating: Optional[int] # 0–100 (TMDb vote_average 0–10 scaled up)
|
||||
|
||||
def field_values(self, name: str) -> list[str]:
|
||||
if name == "genres":
|
||||
return list(self.genres)
|
||||
if name == "countries":
|
||||
return list(self.countries)
|
||||
if name == "year":
|
||||
return [self.year] if self.year else []
|
||||
if name == "rating":
|
||||
return [str(self.rating)] if self.rating is not None else []
|
||||
return []
|
||||
|
||||
|
||||
class TmdbError(RuntimeError):
|
||||
"""A TMDb request could not be completed (no key, network, bad status)."""
|
||||
|
||||
@@ -83,6 +110,20 @@ def lookup_movie(tmdb_id: int) -> Optional[TmdbResult]:
|
||||
return TmdbResult(int(data["id"]), title, _year_of(data.get("release_date")), "movie")
|
||||
|
||||
|
||||
def movie_details(tmdb_id: int) -> TmdbDetails:
|
||||
"""Full movie data from TMDb (genres/countries/rating/year), localized to cs."""
|
||||
data = _get(f"/movie/{int(tmdb_id)}", {})
|
||||
title = data.get("title") or data.get("original_title") or ""
|
||||
genres = [g["name"] for g in (data.get("genres") or []) if g.get("name")]
|
||||
countries = [c["name"] for c in (data.get("production_countries") or [])
|
||||
if c.get("name")]
|
||||
vote = data.get("vote_average")
|
||||
rating = round(float(vote) * 10) if vote else None
|
||||
return TmdbDetails(
|
||||
tmdb_id=int(data["id"]), title=title, year=_year_of(data.get("release_date")),
|
||||
genres=genres, countries=countries, rating=rating)
|
||||
|
||||
|
||||
def lookup_tv(tmdb_id: int) -> Optional[TmdbResult]:
|
||||
"""Resolve a series by its TMDb id → name + first-air year, or None."""
|
||||
data = _get(f"/tv/{int(tmdb_id)}", {})
|
||||
|
||||
Reference in New Issue
Block a user