video: cache person pages, lazy preview seasons + trending (close the gaps)
The owned-item extras and preview detail payloads were already cached (30-min TTL); person_detail, tmdb_season, and trending were not — so person pages and lazy preview seasons re-hit TMDB each view. Now cached too (trending 1h). Library ownership is re-annotated fresh on each call so 'In Library' badges stay current while the expensive TMDB fetch is reused. Tests for person + season caching.
This commit is contained in:
parent
e5724c6faa
commit
b567eb4808
2 changed files with 50 additions and 16 deletions
|
|
@ -278,15 +278,19 @@ class VideoEnrichmentEngine:
|
|||
w = self.workers.get("tmdb")
|
||||
if not w or not w.enabled:
|
||||
return []
|
||||
try:
|
||||
results = w.client.trending() or []
|
||||
except Exception:
|
||||
logger.exception("video trending failed")
|
||||
return []
|
||||
for r in results:
|
||||
cached = self._cache_get(("trending",))
|
||||
if cached is None:
|
||||
try:
|
||||
cached = w.client.trending() or []
|
||||
self._cache_put(("trending",), cached, ttl=3600)
|
||||
except Exception:
|
||||
logger.exception("video trending failed")
|
||||
return []
|
||||
# Re-annotate ownership fresh each call (cheap) so it tracks the library.
|
||||
for r in cached:
|
||||
if r.get("tmdb_id"):
|
||||
r["library_id"] = self.db.library_id_for_tmdb(r["kind"], r["tmdb_id"])
|
||||
return results
|
||||
return cached
|
||||
|
||||
def tmdb_detail(self, kind, tmdb_id) -> dict | None:
|
||||
"""Full detail for a TMDB title not in the library — same shape as the
|
||||
|
|
@ -352,6 +356,10 @@ class VideoEnrichmentEngine:
|
|||
w = self.workers.get("tmdb")
|
||||
if not w or not w.enabled:
|
||||
return None
|
||||
key = ("season", tv_id, season_number)
|
||||
cached = self._cache_get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
try:
|
||||
se = w.client.season_episodes(tv_id, season_number)
|
||||
except Exception:
|
||||
|
|
@ -365,8 +373,10 @@ class VideoEnrichmentEngine:
|
|||
"still_url": e.get("still_url"), "has_still": bool(e.get("still_url")),
|
||||
"owned": False}
|
||||
for e in (se.get("episodes") or []) if e.get("episode_number") is not None]
|
||||
return {"season_number": season_number, "overview": se.get("overview"),
|
||||
"poster_url": se.get("poster_url"), "episodes": eps}
|
||||
out = {"season_number": season_number, "overview": se.get("overview"),
|
||||
"poster_url": se.get("poster_url"), "episodes": eps}
|
||||
self._cache_put(key, out)
|
||||
return out
|
||||
|
||||
def person_detail(self, tmdb_id) -> dict | None:
|
||||
"""A person (actor/director) page — bio + filmography, each credit
|
||||
|
|
@ -374,13 +384,17 @@ class VideoEnrichmentEngine:
|
|||
w = self.workers.get("tmdb")
|
||||
if not w or not w.enabled:
|
||||
return None
|
||||
try:
|
||||
p = w.client.person(tmdb_id)
|
||||
except Exception:
|
||||
logger.exception("person_detail failed for %s", tmdb_id)
|
||||
return None
|
||||
if not p:
|
||||
return None
|
||||
p = self._cache_get(("person", tmdb_id))
|
||||
if p is None:
|
||||
try:
|
||||
p = w.client.person(tmdb_id)
|
||||
except Exception:
|
||||
logger.exception("person_detail failed for %s", tmdb_id)
|
||||
return None
|
||||
if not p:
|
||||
return None
|
||||
self._cache_put(("person", tmdb_id), p)
|
||||
# Re-annotate ownership fresh each call (cheap) so it tracks the library.
|
||||
for c in p.get("credits") or []:
|
||||
if c.get("tmdb_id"):
|
||||
c["library_id"] = self.db.library_id_for_tmdb(c["kind"], c["tmdb_id"])
|
||||
|
|
|
|||
|
|
@ -257,6 +257,26 @@ def test_engine_search_annotates_library(db):
|
|||
assert "library_id" not in res[2] # people aren't library-matched
|
||||
|
||||
|
||||
def test_person_detail_caches_tmdb_call(db):
|
||||
calls = []
|
||||
class Tmdb:
|
||||
enabled = True
|
||||
def person(self, tid): calls.append(tid); return {"tmdb_id": tid, "name": "P", "credits": []}
|
||||
eng = VideoEnrichmentEngine(db, {"tmdb": Tmdb()})
|
||||
eng.person_detail(7); eng.person_detail(7)
|
||||
assert calls == [7] # second view served from cache
|
||||
|
||||
|
||||
def test_tmdb_season_caches_tmdb_call(db):
|
||||
calls = []
|
||||
class Tmdb:
|
||||
enabled = True
|
||||
def season_episodes(self, tv, sn): calls.append((tv, sn)); return {"overview": "S", "episodes": []}
|
||||
eng = VideoEnrichmentEngine(db, {"tmdb": Tmdb()})
|
||||
eng.tmdb_season(9, 1); eng.tmdb_season(9, 1)
|
||||
assert calls == [(9, 1)]
|
||||
|
||||
|
||||
def test_engine_trending_annotates_library(db):
|
||||
mid = db.upsert_movie("plex", {"server_id": "m1", "title": "Owned", "tmdb_id": 1})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue