Add live progress status to bulk TMDB movie enrichment

Was a single synchronous request with no visibility into progress —
large libraries gave no indication whether it was working or just slow.
Now runs as a background job (matching the existing music-enrich pattern)
with GET /api/tmdb/enrich-all/status polled every 2s; Settings shows
"Enriching... N/total" while running and a summary once done.
This commit is contained in:
Myron Blair
2026-07-26 22:01:12 -05:00
parent a313008440
commit 9af505b45c
2 changed files with 88 additions and 31 deletions
+53 -26
View File
@@ -1798,37 +1798,64 @@ async def opensubs_download(payload: dict, user: dict = Depends(require_admin)):
# ============ BULK TMDB ENRICHMENT ============
_movie_enrich_status = {"running": False, "total": 0, "done": 0, "enriched": 0, "skipped": 0, "failed": 0, "error": ""}
async def _run_movie_enrich(key: str):
_movie_enrich_status.update(running=True, total=0, done=0, enriched=0, skipped=0, failed=0, error="")
try:
movies = await db.movies.find({}, {"_id": 0}).to_list(20000)
_movie_enrich_status["total"] = len(movies)
for m in movies:
needs = not m.get("cast") or not m.get("director") or not m.get("description") or not m.get("poster_url")
if not needs:
_movie_enrich_status["skipped"] += 1
_movie_enrich_status["done"] += 1
continue
try:
if m.get("tmdb_id"):
data = await asyncio.to_thread(tmdb_client.get_movie, key, m["tmdb_id"])
else:
results = await asyncio.to_thread(tmdb_client.search_movies, key, m["title"])
if not results:
_movie_enrich_status["failed"] += 1
_movie_enrich_status["done"] += 1
continue
data = await asyncio.to_thread(tmdb_client.get_movie, key, results[0]["tmdb_id"])
updates = {}
for field in ("description", "poster_url", "backdrop_url", "director", "duration_minutes", "rating"):
if data.get(field) and not m.get(field): updates[field] = data[field]
if data.get("cast") and not m.get("cast"): updates["cast"] = data["cast"]
if data.get("genres") and not m.get("genres"): updates["genres"] = data["genres"]
if data.get("tmdb_id"): updates["tmdb_id"] = data["tmdb_id"]
if updates:
await db.movies.update_one({"id": m["id"]}, {"$set": updates})
_movie_enrich_status["enriched"] += 1
except Exception as e:
logger.warning(f"Enrich failed for {m.get('title')}: {e}")
_movie_enrich_status["failed"] += 1
_movie_enrich_status["done"] += 1
except Exception as e:
_movie_enrich_status["error"] = str(e)
finally:
_movie_enrich_status["running"] = False
@api.post("/tmdb/enrich-all")
async def tmdb_enrich_all(user: dict = Depends(require_admin)):
"""Sweep movies with missing metadata and fill from TMDB (by title match)."""
"""Kicks off a background sweep of movies with missing metadata, filled from TMDB (by title match)."""
if _movie_enrich_status["running"]:
raise HTTPException(status_code=409, detail="Enrichment already running")
s = await get_settings()
key = s.get("tmdb_api_key", "")
if not key: raise HTTPException(status_code=400, detail="TMDB not configured")
enriched = 0; skipped = 0; failed = 0
async for m in db.movies.find({}, {"_id": 0}):
needs = not m.get("cast") or not m.get("director") or not m.get("description") or not m.get("poster_url")
if not needs:
skipped += 1; continue
try:
if m.get("tmdb_id"):
data = await asyncio.to_thread(tmdb_client.get_movie, key, m["tmdb_id"])
else:
results = await asyncio.to_thread(tmdb_client.search_movies, key, m["title"])
if not results: failed += 1; continue
data = await asyncio.to_thread(tmdb_client.get_movie, key, results[0]["tmdb_id"])
updates = {}
for field in ("description", "poster_url", "backdrop_url", "director", "duration_minutes", "rating"):
if data.get(field) and not m.get(field): updates[field] = data[field]
if data.get("cast") and not m.get("cast"): updates["cast"] = data["cast"]
if data.get("genres") and not m.get("genres"): updates["genres"] = data["genres"]
if data.get("tmdb_id"): updates["tmdb_id"] = data["tmdb_id"]
if updates:
await db.movies.update_one({"id": m["id"]}, {"$set": updates})
enriched += 1
except Exception as e:
logger.warning(f"Enrich failed for {m.get('title')}: {e}")
failed += 1
return {"ok": True, "enriched": enriched, "skipped": skipped, "failed": failed}
asyncio.create_task(_run_movie_enrich(key))
return {"ok": True}
@api.get("/tmdb/enrich-all/status")
async def tmdb_enrich_all_status(user: dict = Depends(require_admin)):
return _movie_enrich_status
async def _auto_fetch_subtitle(content_type: str, content_id: str, tmdb_id: Optional[int],