video discover: lazy collection-id backfill so 'complete your collections' lights up
Already-matched movies predate the tmdb_collection_id column, so the collection gap rails were empty (only newly-enriched movies got the id). Add a self-healing backfill: each /discover/gaps load fills the franchise id for up to 20 owned movies missing it (eng.movie_collection reuses the matcher's belongs_to_collection read), recording 0 for movies with no franchise so they're not re-checked. The 'no-franchise' 0 is excluded from the rails. Backfill is wrapped/isolated so it can never break the gap response. Over a few Discover visits the whole library fills in and 'Complete the <franchise>' rails populate.
This commit is contained in:
parent
8236b95db0
commit
a68d9755a9
3 changed files with 60 additions and 1 deletions
|
|
@ -100,6 +100,17 @@ def register_routes(bp):
|
|||
db = get_video_db()
|
||||
eng = get_video_enrichment_engine()
|
||||
try:
|
||||
# Lazy collection-id backfill: movies matched before the collection column
|
||||
# exists have no franchise id. Fill a small batch each load (self-healing);
|
||||
# isolated so a backfill hiccup never breaks the gap rails.
|
||||
try:
|
||||
for mv in db.movies_missing_collection(srv, limit=20):
|
||||
coll = eng.movie_collection(mv["tmdb_id"])
|
||||
if coll is not None:
|
||||
db.set_movie_collection(mv["id"], coll.get("id"), coll.get("name"))
|
||||
except Exception:
|
||||
logger.exception("collection-id backfill batch failed")
|
||||
|
||||
owned = db.owned_movie_tmdb_ids(srv)
|
||||
rails = []
|
||||
# Complete your collections — top franchises you've started, missing entries.
|
||||
|
|
|
|||
|
|
@ -436,6 +436,20 @@ class VideoEnrichmentEngine:
|
|||
return []
|
||||
return self._stamp_owned(items)
|
||||
|
||||
def movie_collection(self, tmdb_id) -> dict | None:
|
||||
"""A movie's TMDB franchise membership {id, name} (belongs_to_collection), or
|
||||
{id: None} when it belongs to no collection. Used to backfill already-matched
|
||||
movies that predate the collection column."""
|
||||
w = self.workers.get("tmdb")
|
||||
if not w or not w.enabled or not tmdb_id:
|
||||
return None
|
||||
try:
|
||||
meta = w.client.match("movie", None, None, known_id=tmdb_id) or {}
|
||||
return {"id": meta.get("tmdb_collection_id"), "name": meta.get("tmdb_collection_name")}
|
||||
except Exception:
|
||||
logger.exception("movie_collection backfill failed for %s", tmdb_id)
|
||||
return None
|
||||
|
||||
def trailer(self, kind, tmdb_id) -> dict | None:
|
||||
"""Best YouTube trailer for a title (cached a day — trailers don't move)."""
|
||||
w = self.workers.get("tmdb")
|
||||
|
|
|
|||
|
|
@ -815,13 +815,47 @@ class VideoDatabase:
|
|||
finally:
|
||||
conn.close()
|
||||
|
||||
def movies_missing_collection(self, server_source=None, limit: int = 20) -> list:
|
||||
"""Owned, TMDB-matched movies whose franchise (tmdb_collection_id) hasn't been
|
||||
backfilled yet — drives the lazy collection-id backfill. Returns [{id, tmdb_id}]."""
|
||||
sql = ("SELECT id, tmdb_id FROM movies WHERE has_file=1 AND tmdb_id IS NOT NULL "
|
||||
"AND tmdb_collection_id IS NULL")
|
||||
args: list = []
|
||||
if server_source:
|
||||
sql += " AND server_source=?"
|
||||
args.append(server_source)
|
||||
sql += " LIMIT ?"
|
||||
args.append(int(limit))
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
return [{"id": r["id"], "tmdb_id": r["tmdb_id"]} for r in conn.execute(sql, args)]
|
||||
except sqlite3.Error:
|
||||
return []
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def set_movie_collection(self, movie_id: int, collection_id, name) -> None:
|
||||
"""Persist a movie's TMDB collection id/name (backfill). collection_id may be
|
||||
None — recorded as 0 so the row isn't re-checked forever (a movie with no
|
||||
franchise). 0 is excluded from the gap rails."""
|
||||
conn = self._get_connection()
|
||||
try:
|
||||
conn.execute("UPDATE movies SET tmdb_collection_id=?, tmdb_collection_name=? WHERE id=?",
|
||||
(int(collection_id) if collection_id else 0, name, int(movie_id)))
|
||||
conn.commit()
|
||||
except sqlite3.Error:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def owned_movie_collections(self, server_source=None, limit: int = 12) -> list:
|
||||
"""Franchises the user has STARTED (owns >=1 movie in), most-invested first —
|
||||
drives the 'Complete your collections' gap rails. Returns
|
||||
[{collection_id, name, owned_count}]."""
|
||||
sql = ("SELECT tmdb_collection_id AS cid, "
|
||||
"MAX(tmdb_collection_name) AS name, COUNT(*) AS c "
|
||||
"FROM movies WHERE has_file=1 AND tmdb_collection_id IS NOT NULL")
|
||||
"FROM movies WHERE has_file=1 AND tmdb_collection_id IS NOT NULL "
|
||||
"AND tmdb_collection_id != 0")
|
||||
args: list = []
|
||||
if server_source:
|
||||
sql += " AND server_source=?"
|
||||
|
|
|
|||
Loading…
Reference in a new issue