Make Smart Search library-aware (Emby dedup + TV gap-fill)

New controller/library.js queries Emby by TMDB provider id (resilient: an
unreachable Emby degrades to "unknown" so search still works).

Movies — quality-aware dedup:
- findReleases checks movieInLibrary(tmdbId) and flags each option alreadyOwned
  when its resolution is <= what you already own, so only genuine upgrades are
  offered; the release picker greys owned qualities and shows a library banner.

TV — library-driven "fill the gaps":
- GET /__api/search/seasons compares Emby's episode inventory against TMDB's
  per-season episode counts to mark each season complete/partial/missing.
- POST /__api/search/fill picks the best complete-season pack (LLM) for every
  missing/incomplete season and queues them. Front-end routes TV titles to a
  season plan + "Fill all gaps" instead of the movie release list.

Adds tmdb.tmdbSeasons; shared tmdb module import in search.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 19:11:30 -04:00
parent fd5ef14999
commit 27a6656ac5
5 changed files with 241 additions and 4 deletions
+14 -1
View File
@@ -69,4 +69,17 @@ async function tmdbFindBest(title, year, mediaType){
};
}
module.exports = { fetchJSON, tmdbSearch, tmdbDetails, tmdbFindBest };
// Per-season episode counts for a show( season 0 / specials excluded).
async function tmdbSeasons(tmdbId){
let data = await fetchJSON(`https://api.themoviedb.org/3/tv/${tmdbId}?api_key=${conf.tmdb.apiKey}`);
let date = data.first_air_date || '';
return {
title: data.name,
year: date ? date.slice(0, 4) : '',
seasons: (data.seasons || [])
.filter(s => s.season_number >= 1 && s.episode_count > 0)
.map(s => ({ season: s.season_number, episodeCount: s.episode_count })),
};
}
module.exports = { fetchJSON, tmdbSearch, tmdbDetails, tmdbFindBest, tmdbSeasons };