Add episode and show types

This commit is contained in:
Zoe Roux 2023-03-24 14:04:00 +09:00
parent 8ac5f02d93
commit a9ce596381
8 changed files with 169 additions and 13 deletions

View File

@ -9,7 +9,7 @@ from providers.types.metadataid import MetadataID
from ..provider import Provider
from ..types.movie import Movie, MovieTranslation
from ..types.status import Status
from ..types.episode import Episode
from ..types.studio import Studio
@ -138,3 +138,15 @@ class TheMovieDatabase(Provider):
movie = movies[0]
movie.translations = {k: v.translations[k] for k, v in zip(language, movies)}
return movie
async def identify_episode(
self,
name: str,
season: Optional[int],
episode: Optional[int],
absolute: Optional[int],
*,
language: list[str]
) -> Episode:
raise NotImplementedError

View File

@ -3,6 +3,7 @@ from aiohttp import ClientSession
from abc import abstractmethod
from typing import Optional, TypeVar
from .types.episode import Episode
from .types.movie import Movie
@ -27,3 +28,15 @@ class Provider:
self, name: str, year: Optional[int], *, language: list[str]
) -> Movie:
raise NotImplementedError
@abstractmethod
async def identify_episode(
self,
name: str,
season: Optional[int],
episode: Optional[int],
absolute: Optional[int],
*,
language: list[str]
) -> Episode:
raise NotImplementedError

View File

@ -0,0 +1,26 @@
from datetime import date
from dataclasses import dataclass, field
from typing import Optional
from .show import Show
from .season import Season
from .metadataid import MetadataID
@dataclass
class EpisodeTranslation:
name: str
overview: Optional[str]
thumbnails: list[str]
@dataclass
class Episode:
show: Show | dict[str, MetadataID]
season: Optional[Season]
episode_number: Optional[int]
absolute_number: Optional[int]
release_date: Optional[date | int]
path: Optional[str]
external_id: dict[str, MetadataID]
translations: dict[str, EpisodeTranslation] = field(default_factory=dict)

View File

@ -2,14 +2,19 @@ import os
from dataclasses import asdict, dataclass, field
from datetime import date
from typing import Optional
from enum import Enum
from .genre import Genre
from .status import Status
from .studio import Studio
from .metadataid import MetadataID
class Status(str, Enum):
UNKNOWN = "unknown"
FINISHED = "finished"
PLANNED = "planned"
@dataclass
class MovieTranslation:
name: str

View File

@ -0,0 +1,25 @@
from datetime import date
from dataclasses import dataclass, field
from typing import Optional
from .show import Show
from .metadataid import MetadataID
@dataclass
class SeasonTranslation:
name: Optional[str]
overview: Optional[str]
poster: list[str]
thumbnails: list[str]
@dataclass
class Season:
show: Show | dict[str, MetadataID]
season_number: int
start_date: Optional[date | int]
end_date: Optional[date | int]
external_id: dict[str, MetadataID]
translations: dict[str, SeasonTranslation] = field(default_factory=dict)

View File

@ -0,0 +1,71 @@
import os
from dataclasses import asdict, dataclass, field
from datetime import date
from typing import Optional
from enum import Enum
from .genre import Genre
from .studio import Studio
from .metadataid import MetadataID
class Status(str, Enum):
UNKNOWN = "unknown"
FINISHED = "finished"
AIRING = "airing"
PLANNED = "planned"
@dataclass
class ShowTranslation:
name: str
tagline: Optional[str] = None
keywords: list[str] = field(default_factory=list)
overview: Optional[str] = None
posters: list[str] = field(default_factory=list)
logos: list[str] = field(default_factory=list)
trailers: list[str] = field(default_factory=list)
thumbnails: list[str] = field(default_factory=list)
@dataclass
class Show:
original_language: Optional[str] = None
aliases: list[str] = field(default_factory=list)
start_air: Optional[date | int] = None
end_air: Optional[date | int] = None
status: Status = Status.UNKNOWN
studios: list[Studio] = field(default_factory=list)
genres: list[Genre] = field(default_factory=list)
# TODO: handle staff
# staff: list[Staff]
external_id: dict[str, MetadataID] = field(default_factory=dict)
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]
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),
"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),
"title": self.translations[default_language].name,
"genres": [x.to_kyoo() for x in self.genres],
}

View File

@ -1,8 +0,0 @@
from enum import Enum
class Status(str, Enum):
UNKNOWN = "unknown"
FINISHED = "finished"
AIRING = "airing"
PLANNED = "planned"

View File

@ -35,12 +35,14 @@ class Scanner:
@log_errors
async def identify(self, path: Path):
raw = guessit(path)
raw = guessit(path, "--episode-prefer-number")
logging.info("Identied %s: %s", path, raw)
# TODO: check if episode/movie already exists in kyoo and skip if it does.
# TODO: keep a list of processing shows to only fetch metadata once even if
# multiples identify of the same show run on the same time
# TODO: Add collections support
if raw["type"] == "movie":
movie = await self.provider.identify_movie(
raw["title"], raw.get("year"), language=self.languages
@ -49,7 +51,17 @@ class Scanner:
logging.debug("Got movie: %s", movie)
await self.post("movies", data=movie.to_kyoo())
elif raw["type"] == "episode":
pass
# TODO: Identify shows & seasons too.
episode = await self.provider.identify_episode(
raw["title"],
season=raw.get("season"),
episode=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)
await self.post("episodes", data=episode.to_kyoo())
else:
logging.warn("Unknown video file type: %s", raw["type"])