mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Identify collections from themoviedb
This commit is contained in:
parent
88eb325079
commit
68a83c31be
@ -14,6 +14,7 @@ from ..types.studio import Studio
|
||||
from ..types.genre import Genre
|
||||
from ..types.metadataid import MetadataID
|
||||
from ..types.show import Show, ShowTranslation, Status as ShowStatus
|
||||
from ..types.collection import Collection, CollectionTranslation
|
||||
|
||||
|
||||
class TheMovieDatabase(Provider):
|
||||
@ -163,7 +164,19 @@ class TheMovieDatabase(Provider):
|
||||
if movie["imdb_id"]
|
||||
else {}
|
||||
)
|
||||
)
|
||||
),
|
||||
collections=[
|
||||
Collection(
|
||||
external_id={
|
||||
self.name: MetadataID(
|
||||
movie["belongs_to_collection"]["id"],
|
||||
f"https://www.themoviedb.org/collection/{movie['belongs_to_collection']['id']}",
|
||||
)
|
||||
},
|
||||
)
|
||||
]
|
||||
if movie["belongs_to_collection"] is not None
|
||||
else [],
|
||||
# TODO: Add cast information
|
||||
)
|
||||
translation = MovieTranslation(
|
||||
@ -218,7 +231,6 @@ class TheMovieDatabase(Provider):
|
||||
},
|
||||
)
|
||||
logging.debug("TMDb responded: %s", show)
|
||||
# TODO: Use collection data
|
||||
|
||||
ret = Show(
|
||||
original_language=show["original_language"],
|
||||
@ -509,3 +521,39 @@ class TheMovieDatabase(Provider):
|
||||
logging.exception(
|
||||
"Could not retrieve absolute ordering information", exc_info=e
|
||||
)
|
||||
|
||||
async def identify_collection(
|
||||
self, provider_id: str, *, language: list[str]
|
||||
) -> Collection:
|
||||
async def for_language(lng: str) -> Collection:
|
||||
collection = await self.get(
|
||||
f"collection/{provider_id}",
|
||||
params={
|
||||
"language": lng,
|
||||
},
|
||||
)
|
||||
logging.debug("TMDb responded: %s", collection)
|
||||
|
||||
ret = Collection(
|
||||
external_id={
|
||||
self.name: MetadataID(
|
||||
collection["id"],
|
||||
f"https://www.themoviedb.org/collection/{collection['id']}",
|
||||
)
|
||||
},
|
||||
)
|
||||
translation = CollectionTranslation(
|
||||
name=collection["name"],
|
||||
overview=collection["overview"],
|
||||
posters=[
|
||||
f"https://image.tmdb.org/t/p/original{collection['poster_path']}"
|
||||
],
|
||||
logos=[],
|
||||
thumbnails=[
|
||||
f"https://image.tmdb.org/t/p/original{collection['backdrop_path']}"
|
||||
],
|
||||
)
|
||||
ret.translations = {lng: translation}
|
||||
return ret
|
||||
|
||||
return await self.process_translations(for_language, language)
|
||||
|
@ -8,6 +8,7 @@ from providers.utils import ProviderError
|
||||
from .types.episode import Episode, PartialShow
|
||||
from .types.show import Show
|
||||
from .types.movie import Movie
|
||||
from .types.collection import Collection
|
||||
|
||||
|
||||
Self = TypeVar("Self", bound="Provider")
|
||||
@ -57,3 +58,8 @@ class Provider:
|
||||
language: list[str]
|
||||
) -> Episode:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def identify_collection(self, provider_id: str, *, language: list[str]) -> Collection:
|
||||
raise NotImplementedError
|
||||
|
||||
|
31
scanner/providers/types/collection.py
Normal file
31
scanner/providers/types/collection.py
Normal file
@ -0,0 +1,31 @@
|
||||
import os
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
from .metadataid import MetadataID
|
||||
|
||||
|
||||
@dataclass
|
||||
class CollectionTranslation:
|
||||
name: str
|
||||
overview: Optional[str] = None
|
||||
posters: list[str] = field(default_factory=list)
|
||||
logos: list[str] = field(default_factory=list)
|
||||
thumbnails: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Collection:
|
||||
external_id: dict[str, MetadataID]
|
||||
translations: dict[str, CollectionTranslation] = field(default_factory=dict)
|
||||
|
||||
def to_kyoo(self):
|
||||
# For now, the API of kyoo only support one language so we remove the others.
|
||||
default_language = os.environ["LIBRARY_LANGUAGES"].split(",")[0]
|
||||
return {
|
||||
**asdict(self),
|
||||
**asdict(self.translations[default_language]),
|
||||
"poster": next(iter(self.translations[default_language].posters), None),
|
||||
"thumbnail": next(iter(self.translations[default_language].thumbnails), None),
|
||||
"logo": next(iter(self.translations[default_language].logos), None),
|
||||
}
|
@ -33,6 +33,7 @@ class Episode:
|
||||
|
||||
path: Optional[str] = None
|
||||
show_id: Optional[str] = None
|
||||
season_id: Optional[str] = None
|
||||
translations: dict[str, EpisodeTranslation] = field(default_factory=dict)
|
||||
|
||||
def to_kyoo(self):
|
||||
|
@ -4,6 +4,7 @@ from datetime import date
|
||||
from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
from .collection import Collection
|
||||
from .genre import Genre
|
||||
from .studio import Studio
|
||||
from .metadataid import MetadataID
|
||||
@ -43,6 +44,7 @@ class Movie:
|
||||
external_id: dict[str, MetadataID]
|
||||
|
||||
path: Optional[str] = None
|
||||
collections: list[Collection] = field(default_factory=list)
|
||||
translations: dict[str, MovieTranslation] = field(default_factory=dict)
|
||||
|
||||
def to_kyoo(self):
|
||||
@ -59,4 +61,5 @@ class Movie:
|
||||
"trailer": next(iter(self.translations[default_language].trailers), None),
|
||||
"studio": next((x.to_kyoo() for x in self.studios), None),
|
||||
"genres": [x.to_kyoo() for x in self.genres],
|
||||
"collections": None,
|
||||
}
|
||||
|
@ -6,8 +6,9 @@ import re
|
||||
from aiohttp import ClientSession
|
||||
from pathlib import Path
|
||||
from guessit import guessit
|
||||
from typing import List
|
||||
from typing import List, Literal
|
||||
from providers.provider import Provider
|
||||
from providers.types.collection import Collection
|
||||
from providers.types.episode import Episode, PartialShow
|
||||
from providers.types.season import Season, SeasonTranslation
|
||||
from .utils import batch, log_errors, provider_cache, set_in_cache
|
||||
@ -28,7 +29,7 @@ class Scanner:
|
||||
self._ignore_pattern = re.compile("")
|
||||
logging.error(f"Invalid ignore pattern. Ignoring. Error: {e}")
|
||||
self.provider = Provider.get_all(client)[0]
|
||||
self.cache = {"shows": {}, "seasons": {}}
|
||||
self.cache = {"shows": {}, "seasons": {}, "collections": {}}
|
||||
self.languages = languages
|
||||
|
||||
async def scan(self, path: str):
|
||||
@ -83,14 +84,21 @@ class Scanner:
|
||||
|
||||
logging.info("Identied %s: %s", path, raw)
|
||||
|
||||
# TODO: Add collections support
|
||||
if raw["type"] == "movie":
|
||||
movie = await self.provider.identify_movie(
|
||||
raw["title"], raw.get("year"), language=self.languages
|
||||
)
|
||||
movie.path = str(path)
|
||||
logging.debug("Got movie: %s", movie)
|
||||
await self.post("movies", data=movie.to_kyoo())
|
||||
movie_id = await self.post("movies", data=movie.to_kyoo())
|
||||
|
||||
if any(movie.collections):
|
||||
ids = await asyncio.gather(
|
||||
*(self.create_or_get_collection(x) for x in movie.collections)
|
||||
)
|
||||
await asyncio.gather(
|
||||
*(self.link_collection(x, "movie", movie_id) for x in ids)
|
||||
)
|
||||
elif raw["type"] == "episode":
|
||||
episode = await self.provider.identify_episode(
|
||||
raw["title"],
|
||||
@ -105,7 +113,7 @@ class Scanner:
|
||||
episode.show_id = await self.create_or_get_show(episode)
|
||||
|
||||
if episode.season_number is not None:
|
||||
await self.register_seasons(
|
||||
episode.season_id = await self.register_seasons(
|
||||
show_id=episode.show_id,
|
||||
season_number=episode.season_number,
|
||||
)
|
||||
@ -113,6 +121,36 @@ class Scanner:
|
||||
else:
|
||||
logging.warn("Unknown video file type: %s", raw["type"])
|
||||
|
||||
async def create_or_get_collection(self, collection: Collection) -> str:
|
||||
@provider_cache("collection")
|
||||
async def create_collection(provider_id: str):
|
||||
# TODO: Check if a collection with the same metadata id exists already on kyoo.
|
||||
new_collection = (
|
||||
await self.provider.identify_collection(
|
||||
provider_id, language=self.languages
|
||||
)
|
||||
if not any(collection.translations.keys())
|
||||
else collection
|
||||
)
|
||||
logging.debug("Got collection: %s", new_collection)
|
||||
return await self.post("collection", data=new_collection.to_kyoo())
|
||||
|
||||
# The parameter is only used as a key for the cache.
|
||||
provider_id = collection.external_id[self.provider.name].data_id
|
||||
return await create_collection(provider_id)
|
||||
|
||||
async def link_collection(
|
||||
self, collection: str, type: Literal["movie"] | Literal["show"], id: str
|
||||
):
|
||||
async with self._client.put(
|
||||
f"{self._url}/collections/{collection}/{type}/{id}",
|
||||
headers={"X-API-Key": self._api_key},
|
||||
) as r:
|
||||
# Allow 409 and continue as if it worked.
|
||||
if not r.ok and r.status != 409:
|
||||
logging.error(f"Request error: {await r.text()}")
|
||||
r.raise_for_status()
|
||||
|
||||
async def create_or_get_show(self, episode: Episode) -> str:
|
||||
@provider_cache("shows")
|
||||
async def create_show(_: str):
|
||||
@ -122,6 +160,7 @@ class Scanner:
|
||||
if isinstance(episode.show, PartialShow)
|
||||
else episode.show
|
||||
)
|
||||
# TODO: collections
|
||||
logging.debug("Got show: %s", episode)
|
||||
ret = await self.post("show", data=show.to_kyoo())
|
||||
try:
|
||||
@ -138,14 +177,14 @@ class Scanner:
|
||||
return await create_show(provider_id)
|
||||
|
||||
@provider_cache("seasons")
|
||||
async def register_seasons(self, show_id: str, season_number: int):
|
||||
async def register_seasons(self, show_id: str, season_number: int) -> str:
|
||||
# TODO: fetch season here. this will be useful when a new season of a show is aired after the show has been created on kyoo.
|
||||
season = Season(
|
||||
season_number=season_number,
|
||||
show_id=show_id,
|
||||
translations={lng: SeasonTranslation() for lng in self.languages},
|
||||
)
|
||||
await self.post("seasons", data=season.to_kyoo())
|
||||
return await self.post("seasons", data=season.to_kyoo())
|
||||
|
||||
async def post(self, path: str, *, data: object) -> str:
|
||||
logging.debug(
|
||||
|
Loading…
x
Reference in New Issue
Block a user