mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-06-01 12:44:45 -04:00
Add episode and show types
This commit is contained in:
parent
8ac5f02d93
commit
a9ce596381
@ -9,7 +9,7 @@ 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
|
||||||
from ..types.status import Status
|
from ..types.episode import Episode
|
||||||
from ..types.studio import Studio
|
from ..types.studio import Studio
|
||||||
|
|
||||||
|
|
||||||
@ -138,3 +138,15 @@ class TheMovieDatabase(Provider):
|
|||||||
movie = movies[0]
|
movie = movies[0]
|
||||||
movie.translations = {k: v.translations[k] for k, v in zip(language, movies)}
|
movie.translations = {k: v.translations[k] for k, v in zip(language, movies)}
|
||||||
return movie
|
return movie
|
||||||
|
|
||||||
|
|
||||||
|
async def identify_episode(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
season: Optional[int],
|
||||||
|
episode: Optional[int],
|
||||||
|
absolute: Optional[int],
|
||||||
|
*,
|
||||||
|
language: list[str]
|
||||||
|
) -> Episode:
|
||||||
|
raise NotImplementedError
|
||||||
|
@ -3,6 +3,7 @@ from aiohttp import ClientSession
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from typing import Optional, TypeVar
|
from typing import Optional, TypeVar
|
||||||
|
|
||||||
|
from .types.episode import Episode
|
||||||
from .types.movie import Movie
|
from .types.movie import Movie
|
||||||
|
|
||||||
|
|
||||||
@ -27,3 +28,15 @@ class Provider:
|
|||||||
self, name: str, year: Optional[int], *, language: list[str]
|
self, name: str, year: Optional[int], *, language: list[str]
|
||||||
) -> Movie:
|
) -> Movie:
|
||||||
raise NotImplementedError
|
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
|
||||||
|
26
scanner/providers/types/episode.py
Normal file
26
scanner/providers/types/episode.py
Normal 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)
|
@ -2,14 +2,19 @@ import os
|
|||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from .genre import Genre
|
from .genre import Genre
|
||||||
from .status import Status
|
|
||||||
from .studio import Studio
|
from .studio import Studio
|
||||||
from .metadataid import MetadataID
|
from .metadataid import MetadataID
|
||||||
|
|
||||||
|
|
||||||
|
class Status(str, Enum):
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
FINISHED = "finished"
|
||||||
|
PLANNED = "planned"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MovieTranslation:
|
class MovieTranslation:
|
||||||
name: str
|
name: str
|
||||||
|
25
scanner/providers/types/season.py
Normal file
25
scanner/providers/types/season.py
Normal 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)
|
71
scanner/providers/types/show.py
Normal file
71
scanner/providers/types/show.py
Normal 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],
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class Status(str, Enum):
|
|
||||||
UNKNOWN = "unknown"
|
|
||||||
FINISHED = "finished"
|
|
||||||
AIRING = "airing"
|
|
||||||
PLANNED = "planned"
|
|
@ -35,12 +35,14 @@ class Scanner:
|
|||||||
|
|
||||||
@log_errors
|
@log_errors
|
||||||
async def identify(self, path: Path):
|
async def identify(self, path: Path):
|
||||||
raw = guessit(path)
|
raw = guessit(path, "--episode-prefer-number")
|
||||||
logging.info("Identied %s: %s", path, raw)
|
logging.info("Identied %s: %s", path, raw)
|
||||||
|
|
||||||
# TODO: check if episode/movie already exists in kyoo and skip if it does.
|
# 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
|
# 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
|
# multiples identify of the same show run on the same time
|
||||||
|
|
||||||
|
# TODO: Add collections support
|
||||||
if raw["type"] == "movie":
|
if raw["type"] == "movie":
|
||||||
movie = await self.provider.identify_movie(
|
movie = await self.provider.identify_movie(
|
||||||
raw["title"], raw.get("year"), language=self.languages
|
raw["title"], raw.get("year"), language=self.languages
|
||||||
@ -49,7 +51,17 @@ class Scanner:
|
|||||||
logging.debug("Got movie: %s", movie)
|
logging.debug("Got movie: %s", movie)
|
||||||
await self.post("movies", data=movie.to_kyoo())
|
await self.post("movies", data=movie.to_kyoo())
|
||||||
elif raw["type"] == "episode":
|
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:
|
else:
|
||||||
logging.warn("Unknown video file type: %s", raw["type"])
|
logging.warn("Unknown video file type: %s", raw["type"])
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user