Add show and seasons for themoviedatabase

This commit is contained in:
Zoe Roux 2023-03-24 16:31:29 +09:00
parent a9ce596381
commit 20f7f87072
4 changed files with 221 additions and 50 deletions

View File

@ -2,15 +2,16 @@ import asyncio
from datetime import datetime from datetime import datetime
import logging import logging
from aiohttp import ClientSession from aiohttp import ClientSession
from typing import Callable, Dict, Optional, Any from typing import Awaitable, Callable, Coroutine, Dict, Optional, Any, TypeVar
from providers.types.genre import Genre
from providers.types.metadataid import MetadataID
from ..provider import Provider from ..provider import Provider
from ..types.movie import Movie, MovieTranslation from ..types.movie import Movie, MovieTranslation, Status as MovieStatus
from ..types.episode import Episode from ..types.season import Season, SeasonTranslation
from ..types.episode import Episode, PartialShow
from ..types.studio import Studio from ..types.studio import Studio
from ..types.genre import Genre
from ..types.metadataid import MetadataID
from ..types.show import Show, ShowTranslation, Status as ShowStatus
class TheMovieDatabase(Provider): class TheMovieDatabase(Provider):
@ -48,6 +49,17 @@ class TheMovieDatabase(Provider):
r.raise_for_status() r.raise_for_status()
return await r.json() return await r.json()
T = TypeVar("T")
async def process_translations(
self, for_language: Callable[[str], Awaitable[T]], languages: list[str]
) -> T:
tasks = map(lambda lng: for_language(lng), languages)
items: list[Any] = await asyncio.gather(*tasks)
item = items[0]
item.translations = {k: v.translations[k] for k, v in zip(languages, items)}
return item
def get_image(self, images: list[Dict[str, Any]]) -> list[str]: def get_image(self, images: list[Dict[str, Any]]) -> list[str]:
return [ return [
f"https://image.tmdb.org/t/p/original{x['file_path']}" f"https://image.tmdb.org/t/p/original{x['file_path']}"
@ -55,6 +67,19 @@ class TheMovieDatabase(Provider):
if x["file_path"] if x["file_path"]
] ]
def to_studio(self, company: dict[str, Any]) -> Studio:
return Studio(
name=company["name"],
logos=[f"https://image.tmdb.org/t/p/original{company['logo_path']}"]
if "logo_path" in company
else [],
external_id={
"themoviedatabase": MetadataID(
company["id"], f"https://www.themoviedb.org/company/{company['id']}"
)
},
)
async def identify_movie( async def identify_movie(
self, name: str, year: Optional[int], *, language: list[str] self, name: str, year: Optional[int], *, language: list[str]
) -> Movie: ) -> Movie:
@ -82,23 +107,10 @@ class TheMovieDatabase(Provider):
release_date=datetime.strptime( release_date=datetime.strptime(
movie["release_date"], "%Y-%m-%d" movie["release_date"], "%Y-%m-%d"
).date(), ).date(),
status=Status.FINISHED status=MovieStatus.FINISHED
if movie["status"] == "Released" if movie["status"] == "Released"
else Status.PLANNED, else MovieStatus.PLANNED,
studios=[ studios=[self.to_studio(x) for x in movie["production_companies"]],
Studio(
name=x["name"],
logos=[f"https://image.tmdb.org/t/p/original{x['logo_path']}"]
if "logo_path" in x
else [],
external_id={
"themoviedatabase": MetadataID(
x["id"], f"https://www.themoviedb.org/company/{x['id']}"
)
},
)
for x in movie["production_companies"]
],
genres=[ genres=[
self.genre_map[x["id"]] self.genre_map[x["id"]]
for x in movie["genres"] for x in movie["genres"]
@ -132,13 +144,106 @@ class TheMovieDatabase(Provider):
ret.translations = {lng: translation} ret.translations = {lng: translation}
return ret return ret
# TODO: make the folllowing generic return await self.process_translations(for_language, language)
tasks = map(lambda lng: for_language(lng), language)
movies: list[Movie] = await asyncio.gather(*tasks)
movie = movies[0]
movie.translations = {k: v.translations[k] for k, v in zip(language, movies)}
return movie
async def identify_show(
self,
show: PartialShow,
*,
language: list[str],
) -> Show:
show_id = show.external_id["themoviedatabase"].id
if show.original_language not in language:
language.append(show.original_language)
async def for_language(lng: str) -> Show:
show = await self.get(
f"/tv/{show_id}",
params={
"language": lng,
"append_to_response": "alternative_titles,videos,credits,keywords,images",
},
)
logging.debug("TMDb responded: %s", show)
# TODO: Use collection data
ret = Show(
original_language=show["original_language"],
aliases=[x["title"] for x in show["alternative_titles"]["titles"]],
start_air=datetime.strptime(show["first_air_date"], "%Y-%m-%d").date(),
end_air=datetime.strptime(show["last_air_date"], "%Y-%m-%d").date(),
status=ShowStatus.FINISHED
if show["status"] == "Released"
else ShowStatus.AIRING
if show["in_production"]
else ShowStatus.FINISHED,
studios=[self.to_studio(x) for x in show["production_companies"]],
genres=[
self.genre_map[x["id"]]
for x in show["genres"]
if x["id"] in self.genre_map
],
external_id={
"themoviedatabase": MetadataID(
show["id"], f"https://www.themoviedb.org/tv/{show['id']}"
),
"imdb": MetadataID(
show["imdb_id"],
f"https://www.imdb.com/title/{show['imdb_id']}",
),
},
seasons=[
self.to_season(x, language=lng, show_id=show["id"])
for x in show["seasons"]
],
# TODO: Add cast information
)
translation = ShowTranslation(
name=show["name"],
tagline=show["tagline"],
keywords=list(map(lambda x: x["name"], show["keywords"]["keywords"])),
overview=show["overview"],
posters=self.get_image(show["images"]["posters"]),
logos=self.get_image(show["images"]["logos"]),
thumbnails=self.get_image(show["images"]["backdrops"]),
trailers=[
f"https://www.youtube.com/watch?v{x['key']}"
for x in show["videos"]["results"]
if x["type"] == "Trailer" and x["site"] == "YouTube"
],
)
ret.translations = {lng: translation}
return ret
ret = await self.process_translations(for_language, language)
return ret
def to_season(
self, season: dict[str, Any], *, language: str, show_id: str
) -> Season:
return Season(
season_number=season["season_number"],
start_date=datetime.strptime(season["air_date"], "%Y-%m-%d").date(),
end_date=None,
external_id={
"themoviedatabase": MetadataID(
season["id"],
f"https://www.themoviedb.org/tv/{show_id}/season/{season['season_number']}",
)
},
translations={
language: SeasonTranslation(
name=season["name"],
overview=season["overview"],
poster=[
f"https://image.tmdb.org/t/p/original{season['poster_path']}"
]
if "poster_path" in season
else [],
thumbnails=[],
)
},
)
async def identify_episode( async def identify_episode(
self, self,
@ -147,6 +252,65 @@ class TheMovieDatabase(Provider):
episode: Optional[int], episode: Optional[int],
absolute: Optional[int], absolute: Optional[int],
*, *,
language: list[str] language: list[str],
) -> Episode: ) -> Episode:
raise NotImplementedError search = (await self.get("search/tv", params={"query": name}))["results"][0]
show_id = search["id"]
if search["original_language"] not in language:
language.append(search["original_language"])
async def for_language(lng: str) -> Episode:
movie = await self.get(
f"/movie/{show_id}",
params={
"language": lng,
"append_to_response": "alternative_titles,videos,credits,keywords,images",
},
)
logging.debug("TMDb responded: %s", movie)
# TODO: Use collection data
ret = Movie(
original_language=movie["original_language"],
aliases=[x["title"] for x in movie["alternative_titles"]["titles"]],
release_date=datetime.strptime(
movie["release_date"], "%Y-%m-%d"
).date(),
status=MovieStatus.FINISHED
if movie["status"] == "Released"
else MovieStatus.PLANNED,
studios=[self.to_studio(x) for x in movie["production_companies"]],
genres=[
self.genre_map[x["id"]]
for x in movie["genres"]
if x["id"] in self.genre_map
],
external_id={
"themoviedatabase": MetadataID(
movie["id"], f"https://www.themoviedb.org/movie/{movie['id']}"
),
"imdb": MetadataID(
movie["imdb_id"],
f"https://www.imdb.com/title/{movie['imdb_id']}",
),
}
# TODO: Add cast information
)
translation = MovieTranslation(
name=movie["title"],
tagline=movie["tagline"],
keywords=list(map(lambda x: x["name"], movie["keywords"]["keywords"])),
overview=movie["overview"],
posters=self.get_image(movie["images"]["posters"]),
logos=self.get_image(movie["images"]["logos"]),
thumbnails=self.get_image(movie["images"]["backdrops"]),
trailers=[
f"https://www.youtube.com/watch?v{x['key']}"
for x in movie["videos"]["results"]
if x["type"] == "Trailer" and x["site"] == "YouTube"
],
)
ret.translations = {lng: translation}
return ret
return self.process_translations(for_language, language)

View File

@ -7,16 +7,24 @@ from .season import Season
from .metadataid import MetadataID from .metadataid import MetadataID
@dataclass
class PartialShow:
name: str
original_language: str
external_id: dict[str, MetadataID]
@dataclass @dataclass
class EpisodeTranslation: class EpisodeTranslation:
name: str name: str
overview: Optional[str] overview: Optional[str]
thumbnails: list[str] thumbnails: list[str]
@dataclass @dataclass
class Episode: class Episode:
show: Show | dict[str, MetadataID] show: Show | PartialShow
season: Optional[Season] season_number: Optional[int]
episode_number: Optional[int] episode_number: Optional[int]
absolute_number: Optional[int] absolute_number: Optional[int]
release_date: Optional[date | int] release_date: Optional[date | int]

View File

@ -2,7 +2,6 @@ from datetime import date
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional from typing import Optional
from .show import Show
from .metadataid import MetadataID from .metadataid import MetadataID
@ -16,7 +15,6 @@ class SeasonTranslation:
@dataclass @dataclass
class Season: class Season:
show: Show | dict[str, MetadataID]
season_number: int season_number: int
start_date: Optional[date | int] start_date: Optional[date | int]
end_date: Optional[date | int] end_date: Optional[date | int]

View File

@ -4,9 +4,9 @@ from datetime import date
from typing import Optional from typing import Optional
from enum import Enum from enum import Enum
from .genre import Genre from .genre import Genre
from .studio import Studio from .studio import Studio
from .season import Season
from .metadataid import MetadataID from .metadataid import MetadataID
class Status(str, Enum): class Status(str, Enum):
@ -19,28 +19,29 @@ class Status(str, Enum):
@dataclass @dataclass
class ShowTranslation: class ShowTranslation:
name: str name: str
tagline: Optional[str] = None tagline: Optional[str]
keywords: list[str] = field(default_factory=list) keywords: list[str]
overview: Optional[str] = None overview: Optional[str]
posters: list[str] = field(default_factory=list) posters: list[str]
logos: list[str] = field(default_factory=list) logos: list[str]
trailers: list[str] = field(default_factory=list) trailers: list[str]
thumbnails: list[str] = field(default_factory=list) thumbnails: list[str]
@dataclass @dataclass
class Show: class Show:
original_language: Optional[str] = None original_language: Optional[str]
aliases: list[str] = field(default_factory=list) aliases: list[str]
start_air: Optional[date | int] = None start_air: Optional[date | int]
end_air: Optional[date | int] = None end_air: Optional[date | int]
status: Status = Status.UNKNOWN status: Status
studios: list[Studio] = field(default_factory=list) studios: list[Studio]
genres: list[Genre] = field(default_factory=list) genres: list[Genre]
seasons: list[Season]
# TODO: handle staff # TODO: handle staff
# staff: list[Staff] # staff: list[Staff]
external_id: dict[str, MetadataID] = field(default_factory=dict) external_id: dict[str, MetadataID]
translations: dict[str, ShowTranslation] = field(default_factory=dict) translations: dict[str, ShowTranslation] = field(default_factory=dict)