Add a basic id mapper to retrive tvdb id from a tmdb id

This commit is contained in:
Zoe Roux 2024-01-05 12:40:28 +01:00
parent 91c88e7f43
commit b3ddac60f6
5 changed files with 99 additions and 28 deletions

View File

@ -0,0 +1,36 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from providers.implementations.themoviedatabase import TheMovieDatabase
from typing import List, Optional
from providers.types.metadataid import MetadataID
class IdMapper:
def init(self, *, language: str, tmdb: Optional[TheMovieDatabase]):
self.language = language
self._tmdb = tmdb
async def get_show(
self, show: dict[str, MetadataID], *, required: Optional[List[str]] = None
):
ids = show
# Only fetch using tmdb if one of the required ids is not already known.
should_fetch = required is not None and any((x not in ids for x in required))
if self._tmdb and self._tmdb.name in ids and should_fetch:
tmdb_info = await self._tmdb.identify_show(
ids[self._tmdb.name].data_id,
original_language=None,
language=[self.language],
)
return {**ids, **tmdb_info.external_id}
return ids
async def get_movie(
self, movie: dict[str, MetadataID], *, required: Optional[List[str]] = None
):
# TODO: actually do something here
return movie

View File

@ -3,6 +3,7 @@ import logging
from aiohttp import ClientSession from aiohttp import ClientSession
from datetime import datetime from datetime import datetime
from typing import Awaitable, Callable, Dict, List, Optional, Any, TypeVar from typing import Awaitable, Callable, Dict, List, Optional, Any, TypeVar
from providers.idmapper import IdMapper
from providers.implementations.thexem import TheXem from providers.implementations.thexem import TheXem
from providers.utils import ProviderError from providers.utils import ProviderError
@ -19,10 +20,13 @@ from ..types.collection import Collection, CollectionTranslation
class TheMovieDatabase(Provider): class TheMovieDatabase(Provider):
def __init__(self, client: ClientSession, api_key: str, xem: TheXem) -> None: def __init__(
self, client: ClientSession, api_key: str, xem: TheXem, idmapper: IdMapper
) -> None:
super().__init__() super().__init__()
self._client = client self._client = client
self._xem = xem self._xem = xem
self._idmapper = idmapper
self.base = "https://api.themoviedb.org/3" self.base = "https://api.themoviedb.org/3"
self.api_key = api_key self.api_key = api_key
self.genre_map = { self.genre_map = {
@ -212,17 +216,20 @@ class TheMovieDatabase(Provider):
ret.translations = {lng: translation} ret.translations = {lng: translation}
return ret return ret
return await self.process_translations(for_language, language) ret = await self.process_translations(for_language, language)
# If we have more external_ids freely available, add them.
ret.external_id = await self._idmapper.get_movie(ret.external_id)
return ret
async def identify_show( async def identify_show(
self, self,
pshow: PartialShow, show_id: str,
*, *,
original_language: Optional[str],
language: list[str], language: list[str],
) -> Show: ) -> Show:
show_id = pshow.external_id[self.name].data_id if original_language and original_language not in language:
if pshow.original_language not in language: language.append(original_language)
language.append(pshow.original_language)
async def for_language(lng: str) -> Show: async def for_language(lng: str) -> Show:
show = await self.get( show = await self.get(
@ -290,7 +297,7 @@ class TheMovieDatabase(Provider):
show["images"]["posters"] show["images"]["posters"]
+ ( + (
[{"file_path": show["poster_path"]}] [{"file_path": show["poster_path"]}]
if lng == pshow.original_language if lng == show["original_language"]
else [] else []
) )
), ),
@ -299,7 +306,7 @@ class TheMovieDatabase(Provider):
show["images"]["backdrops"] show["images"]["backdrops"]
+ ( + (
[{"file_path": show["backdrop_path"]}] [{"file_path": show["backdrop_path"]}]
if lng == pshow.original_language if lng == show["original_language"]
else [] else []
) )
), ),
@ -333,6 +340,8 @@ class TheMovieDatabase(Provider):
ret = await self.process_translations( ret = await self.process_translations(
for_language, language, merge_seasons_translations for_language, language, merge_seasons_translations
) )
# If we have more external_ids freely available, add them.
ret.external_id = await self._idmapper.get_show(ret.external_id)
return ret return ret
def to_season( def to_season(
@ -381,6 +390,14 @@ class TheMovieDatabase(Provider):
raise ProviderError(f"No result for a tv show named: {name}") raise ProviderError(f"No result for a tv show named: {name}")
search = self.get_best_result(search_results, name, year) search = self.get_best_result(search_results, name, year)
show_id = search["id"] show_id = search["id"]
show = PartialShow(
original_language=search["original_language"],
external_id={
self.name: MetadataID(
show_id, f"https://www.themoviedb.org/tv/{show_id}"
)
},
)
if search["original_language"] not in language: if search["original_language"] not in language:
language.append(search["original_language"]) language.append(search["original_language"])
@ -388,10 +405,18 @@ class TheMovieDatabase(Provider):
# For example when name is "Jojo's bizzare adventure - Stone Ocean", with season None, # For example when name is "Jojo's bizzare adventure - Stone Ocean", with season None,
# We want something like season 6 ep 3. # We want something like season 6 ep 3.
if season is None and absolute is not None: if season is None and absolute is not None:
(tvdb_season, tvdb_episode, absolute) = await self._xem.get_episode_override("tvdb", tvdbid, name, absolute) ids = await self._idmapper.get_show(show.external_id, required=["tmdbid"])
# Most of the time, tvdb absolute and tmdb absolute are in think so we use that as our souce of truth. if ids["tvdb"] is not None:
# tvdb_season/episode are not in sync with tmdb so we discard those and use our usual absolute order fetching. (
(_, _) = tvdb_season, tvdb_episode tvdb_season,
tvdb_episode,
absolute,
) = await self._xem.get_episode_override(
"tvdb", ids["tvdb"].data_id, name, absolute
)
# Most of the time, tvdb absolute and tmdb absolute are in think so we use that as our souce of truth.
# tvdb_season/episode are not in sync with tmdb so we discard those and use our usual absolute order fetching.
(_, _) = tvdb_season, tvdb_episode
if not show_id in self.absolute_episode_cache: if not show_id in self.absolute_episode_cache:
await self.get_absolute_order(show_id) await self.get_absolute_order(show_id)
@ -441,15 +466,7 @@ class TheMovieDatabase(Provider):
logging.debug("TMDb responded: %s", episode) logging.debug("TMDb responded: %s", episode)
ret = Episode( ret = Episode(
show=PartialShow( show=show,
name=search["name"],
original_language=search["original_language"],
external_id={
self.name: MetadataID(
show_id, f"https://www.themoviedb.org/tv/{show_id}"
)
},
),
season_number=episode["season_number"], season_number=episode["season_number"],
episode_number=episode["episode_number"], episode_number=episode["episode_number"],
absolute_number=absolute, absolute_number=absolute,

View File

@ -5,7 +5,7 @@ from typing import Optional, TypeVar
from providers.utils import ProviderError from providers.utils import ProviderError
from .types.episode import Episode, PartialShow from .types.episode import Episode
from .types.show import Show from .types.show import Show
from .types.movie import Movie from .types.movie import Movie
from .types.collection import Collection from .types.collection import Collection
@ -16,22 +16,35 @@ Self = TypeVar("Self", bound="Provider")
class Provider: class Provider:
@classmethod @classmethod
def get_all(cls: type[Self], client: ClientSession) -> list[Self]: def get_all(
cls: type[Self], client: ClientSession, languages: list[str]
) -> list[Self]:
providers = [] providers = []
from providers.idmapper import IdMapper
idmapper = IdMapper()
from providers.implementations.thexem import TheXem from providers.implementations.thexem import TheXem
xem = TheXem(client) xem = TheXem(client)
from providers.implementations.themoviedatabase import TheMovieDatabase from providers.implementations.themoviedatabase import TheMovieDatabase
tmdb = os.environ.get("THEMOVIEDB_APIKEY") tmdb = os.environ.get("THEMOVIEDB_APIKEY")
if tmdb: if tmdb:
providers.append(TheMovieDatabase(client, tmdb, xem)) tmdb = TheMovieDatabase(client, tmdb, xem, idmapper)
providers.append(tmdb)
else:
tmdb = None
if not any(providers): if not any(providers):
raise ProviderError( raise ProviderError(
"No provider configured. You probably forgot to specify an API Key" "No provider configured. You probably forgot to specify an API Key"
) )
idmapper.init(tmdb=tmdb, language=languages[0])
return providers return providers
@abstractproperty @abstractproperty
@ -45,7 +58,9 @@ class Provider:
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod
async def identify_show(self, show: PartialShow, *, language: list[str]) -> Show: async def identify_show(
self, show_id: str, *, original_language: Optional[str], language: list[str]
) -> Show:
raise NotImplementedError raise NotImplementedError
@abstractmethod @abstractmethod

View File

@ -9,7 +9,6 @@ from .metadataid import MetadataID
@dataclass @dataclass
class PartialShow: class PartialShow:
name: str
original_language: str original_language: str
external_id: dict[str, MetadataID] external_id: dict[str, MetadataID]

View File

@ -28,7 +28,7 @@ class Scanner:
except Exception as e: except Exception as e:
self._ignore_pattern = re.compile("") self._ignore_pattern = re.compile("")
logging.error(f"Invalid ignore pattern. Ignoring. Error: {e}") logging.error(f"Invalid ignore pattern. Ignoring. Error: {e}")
self.provider = Provider.get_all(client)[0] self.provider = Provider.get_all(client, languages)[0]
self.cache = {"shows": {}, "seasons": {}, "collections": {}} self.cache = {"shows": {}, "seasons": {}, "collections": {}}
self.languages = languages self.languages = languages
@ -156,7 +156,11 @@ class Scanner:
async def create_show(_: str): async def create_show(_: str):
# TODO: Check if a show with the same metadata id exists already on kyoo. # TODO: Check if a show with the same metadata id exists already on kyoo.
show = ( show = (
await self.provider.identify_show(episode.show, language=self.languages) await self.provider.identify_show(
episode.show.external_id[self.provider.name].data_id,
original_language=episode.show.original_language,
language=self.languages,
)
if isinstance(episode.show, PartialShow) if isinstance(episode.show, PartialShow)
else episode.show else episode.show
) )