Add kyoo requests for episodes

This commit is contained in:
Zoe Roux 2023-03-25 00:30:37 +09:00
parent 2334afb3eb
commit c3b8595cd7
7 changed files with 73 additions and 32 deletions

View File

@ -41,6 +41,10 @@ class TheMovieDatabase(Provider):
37: Genre.WESTERN,
}
@property
def name(self) -> str:
return "themoviedatabase"
async def get(self, path: str, *, params: dict[str, Any] = {}):
params = {k: v for k, v in params.items() if v is not None}
async with self._client.get(
@ -84,7 +88,7 @@ class TheMovieDatabase(Provider):
if "logo_path" in company
else [],
external_id={
"themoviedatabase": MetadataID(
self.name: MetadataID(
company["id"], f"https://www.themoviedb.org/company/{company['id']}"
)
},
@ -127,7 +131,7 @@ class TheMovieDatabase(Provider):
if x["id"] in self.genre_map
],
external_id={
"themoviedatabase": MetadataID(
self.name: MetadataID(
movie["id"], f"https://www.themoviedb.org/movie/{movie['id']}"
),
"imdb": MetadataID(
@ -162,7 +166,7 @@ class TheMovieDatabase(Provider):
*,
language: list[str],
) -> Show:
show_id = show.external_id["themoviedatabase"].id
show_id = show.external_id[self.name].id
if show.original_language not in language:
language.append(show.original_language)
@ -194,7 +198,7 @@ class TheMovieDatabase(Provider):
if x["id"] in self.genre_map
],
external_id={
"themoviedatabase": MetadataID(
self.name: MetadataID(
show["id"], f"https://www.themoviedb.org/tv/{show['id']}"
),
"imdb": MetadataID(
@ -256,7 +260,7 @@ class TheMovieDatabase(Provider):
start_date=datetime.strptime(season["air_date"], "%Y-%m-%d").date(),
end_date=None,
external_id={
"themoviedatabase": MetadataID(
self.name: MetadataID(
season["id"],
f"https://www.themoviedb.org/tv/{show_id}/season/{season['season_number']}",
)
@ -290,6 +294,8 @@ class TheMovieDatabase(Provider):
language.append(search["original_language"])
# TODO: Handle absolute episodes
if not season or not episode_nbr:
raise NotImplementedError("Absolute order episodes not implemented for the movie database")
async def for_language(lng: str) -> Episode:
episode = await self.get(
@ -305,7 +311,7 @@ class TheMovieDatabase(Provider):
name=search["name"],
original_language=search["original_language"],
external_id={
"themoviedatabase": MetadataID(
self.name: MetadataID(
show_id, f"https://www.themoviedb.org/tv/{show_id}"
)
},
@ -319,7 +325,7 @@ class TheMovieDatabase(Provider):
if "poster_path" in episode
else None,
external_id={
"themoviedatabase": MetadataID(
self.name: MetadataID(
episode["id"],
f"https://www.themoviedb.org/movie/{episode['id']}",
),

View File

@ -1,9 +1,10 @@
import os
from aiohttp import ClientSession
from abc import abstractmethod
from abc import abstractmethod, abstractproperty
from typing import Optional, TypeVar
from .types.episode import Episode
from .types.episode import Episode, PartialShow
from .types.show import Show
from .types.movie import Movie
@ -23,18 +24,26 @@ class Provider:
return providers
@abstractproperty
def name(self) -> str:
raise NotImplementedError
@abstractmethod
async def identify_movie(
self, name: str, year: Optional[int], *, language: list[str]
) -> Movie:
raise NotImplementedError
@abstractmethod
async def identify_show(self, show: PartialShow, *, language: list[str]) -> Show:
raise NotImplementedError
@abstractmethod
async def identify_episode(
self,
name: str,
season: Optional[int],
episode: Optional[int],
episode_nbr: Optional[int],
absolute: Optional[int],
*,
language: list[str]

View File

@ -1,7 +1,9 @@
import os
from datetime import date
from dataclasses import dataclass, field
from dataclasses import dataclass, field, asdict
from typing import Optional
from ..utils import format_date
from .show import Show
from .season import Season
from .metadataid import MetadataID
@ -26,9 +28,20 @@ class Episode:
season_number: Optional[int]
episode_number: Optional[int]
absolute_number: Optional[int]
release_date: Optional[date | int]
release_date: Optional[date]
thumbnail: Optional[str]
external_id: dict[str, MetadataID]
path: Optional[str] = None
show_id: Optional[str] = None
translations: dict[str, EpisodeTranslation] = 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]),
"release_date": format_date(self.release_date),
}

View File

@ -7,6 +7,7 @@ from enum import Enum
from .genre import Genre
from .studio import Studio
from .metadataid import MetadataID
from ..utils import format_date
class Status(str, Enum):
@ -43,13 +44,6 @@ class Movie:
translations: dict[str, MovieTranslation] = field(default_factory=dict)
def format_date(self, date: date | int | None) -> str | None:
if date is None:
return None
if isinstance(date, int):
return f"{date}-01-01T00:00:00Z"
return date.isoformat()
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]
@ -64,7 +58,7 @@ class Movie:
"trailer": next(iter(self.translations[default_language].trailers), None),
"studio": next(iter(x.to_kyoo() for x in self.studios), None),
"release_date": None,
"startAir": self.format_date(self.release_date),
"startAir": format_date(self.release_date),
"title": self.translations[default_language].name,
"genres": [x.to_kyoo() for x in self.genres],
"isMovie": True,

View File

@ -8,6 +8,7 @@ from .genre import Genre
from .studio import Studio
from .season import Season
from .metadataid import MetadataID
from ..utils import format_date
class Status(str, Enum):
UNKNOWN = "unknown"
@ -45,13 +46,6 @@ class Show:
translations: dict[str, ShowTranslation] = field(default_factory=dict)
def format_date(self, date: date | int | None) -> str | None:
if date is None:
return None
if isinstance(date, int):
return f"{date}-01-01T00:00:00Z"
return date.isoformat()
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]
@ -65,8 +59,8 @@ class Show:
"logo": next(iter(self.translations[default_language].logos), None),
"trailer": next(iter(self.translations[default_language].trailers), None),
"studio": next(iter(x.to_kyoo() for x in self.studios), None),
"startAir": self.format_date(self.start_air),
"endAir": self.format_date(self.end_air),
"startAir": format_date(self.start_air),
"endAir": format_date(self.end_air),
"title": self.translations[default_language].name,
"genres": [x.to_kyoo() for x in self.genres],
}

View File

@ -0,0 +1,8 @@
from datetime import date
def format_date(date: date | int | None) -> str | None:
if date is None:
return None
if isinstance(date, int):
return f"{date}-01-01T00:00:00Z"
return date.isoformat()

View File

@ -7,6 +7,7 @@ from aiohttp import ClientSession
from pathlib import Path
from guessit import guessit
from providers.provider import Provider
from providers.types.episode import PartialShow
def log_errors(f):
@ -27,6 +28,7 @@ class Scanner:
self._client = client
self._api_key = api_key
self.provider = Provider.get_all(client)[0]
self.cache = {"shows": {}}
self.languages = languages
async def scan(self, path: str):
@ -44,6 +46,7 @@ class Scanner:
# TODO: Add collections support
if raw["type"] == "movie":
return
movie = await self.provider.identify_movie(
raw["title"], raw.get("year"), language=self.languages
)
@ -51,21 +54,32 @@ class Scanner:
logging.debug("Got movie: %s", movie)
await self.post("movies", data=movie.to_kyoo())
elif raw["type"] == "episode":
# TODO: Identify shows & seasons too.
episode = await self.provider.identify_episode(
raw["title"],
season=raw.get("season"),
episode=raw.get("episode"),
episode_nbr=raw.get("episode"),
absolute=raw.get("episode") if "season" not in raw else None,
language=self.languages,
)
episode.path = str(path)
logging.debug("Got episode: %s", episode)
show_provider_id = episode.show.external_id[self.provider.name].id
if (
isinstance(episode.show, PartialShow)
and show_provider_id not in self.cache["shows"]
):
show = await self.provider.identify_show(
episode.show, language=self.languages
)
logging.debug("Got show: %s", episode)
self.cache["shows"][show_provider_id] = await self.post("show", data=show.to_kyoo())
episode.show_id = self.cache["shows"][show_provider_id]
await self.post("episodes", data=episode.to_kyoo())
else:
logging.warn("Unknown video file type: %s", raw["type"])
async def post(self, path: str, *, data: object):
async def post(self, path: str, *, data: object) -> str:
url = os.environ.get("KYOO_URL", "http://back:5000")
print(json.dumps(data, indent=4))
async with self._client.post(
@ -74,3 +88,6 @@ class Scanner:
if not r.ok:
print(await r.text())
r.raise_for_status()
ret = await r.json()
return ret["id"]