diff --git a/scanner/providers/implementations/themoviedatabase.py b/scanner/providers/implementations/themoviedatabase.py index 343dc9e0..9aee85ad 100644 --- a/scanner/providers/implementations/themoviedatabase.py +++ b/scanner/providers/implementations/themoviedatabase.py @@ -384,8 +384,11 @@ class TheMovieDatabase(Provider): if search["original_language"] not in language: language.append(search["original_language"]) - if season is None: - season = await self._xem.get_season_override("tvdb", tvdbid, name) + # Handle weird season names overrides from thexem. + # For example when name is "Jojo's bizzare adventure - Stone Ocean", with season None, + # We want something like season 6 ep 3. + if season is None and absolute is not None: + (season, episode, absolute) = await self._xem.get_episode_override("tvdb", tvdbid, name, absolute) if not show_id in self.absolute_episode_cache: await self.get_absolute_order(show_id) diff --git a/scanner/providers/implementations/thexem.py b/scanner/providers/implementations/thexem.py index 268216fb..ca4b6989 100644 --- a/scanner/providers/implementations/thexem.py +++ b/scanner/providers/implementations/thexem.py @@ -1,11 +1,9 @@ -import asyncio import logging -from typing import Dict, List, Literal, Tuple +from typing import Dict, List, Literal from aiohttp import ClientSession from datetime import timedelta, datetime from functools import wraps -from providers.types.episode import Episode from providers.utils import ProviderError @@ -47,12 +45,36 @@ class TheXem: }, ) as r: r.raise_for_status() - ret = (await r.json()) + ret = await r.json() if "data" not in ret or ret["result"] == "failure": logging.error("Could not fetch xem metadata. Error: %s", ret["message"]) raise ProviderError("Could not fetch xem metadata") return ret["data"] + @cache(ttl=timedelta(days=1)) + async def get_show_map( + self, provider: Literal["tvdb"] | Literal["anidb"], id: str + ) -> List[ + Dict[ + Literal["scene"] | Literal["tvdb"] | Literal["anidb"], + Dict[Literal["season"] | Literal["episode"] | Literal["absolute"], int], + ] + ]: + logging.info("Fetching from thexem the map of %s (%s)", id, provider) + async with self._client.get( + f"{self.base}/map/all", + params={ + "id": id, + "origin": provider, + }, + ) as r: + r.raise_for_status() + ret = await r.json() + if "data" not in ret or ret["result"] == "failure": + logging.error("Could not fetch xem mapping. Error: %s", ret["message"]) + raise ProviderError("Could not fetch xem mapping") + return ret["data"] + async def get_season_override( self, provider: Literal["tvdb"] | Literal["anidb"], id: str, show_name: str ): @@ -65,3 +87,32 @@ class TheXem: if show_name.lower() == name.lower(): return season return None + + async def get_episode_override( + self, + provider: Literal["tvdb"] | Literal["anidb"], + id: str, + show_name: str, + episode: int, + ): + master_season = await self.get_season_override(provider, id, show_name) + # master season is not always a direct translation with a tvdb season, we need to translate that back + map = await self.get_show_map(provider, id) + ep = next( + ( + x + for x in map + if x["scene"]["season"] == master_season + and x["scene"]["episode"] == episode + ), + None, + ) + if ep is None: + logging.warning( + "Could not get xem mapping for show %s, falling back to identifier mapping.", + show_name, + ) + return [master_season, episode, None] + + # Only tvdb has a proper absolute handling so we always use this one. + return (ep[provider]["season"], ep[provider]["episode"], ep["tvdb"]["absolute"])