diff --git a/scanner/matcher/matcher.py b/scanner/matcher/matcher.py index 0fd597d2..808fb7fa 100644 --- a/scanner/matcher/matcher.py +++ b/scanner/matcher/matcher.py @@ -87,6 +87,7 @@ class Matcher: async def search_movie(self, title: str, year: Optional[int], path: str): movie = await self._provider.search_movie(title, year) + movie.file_title = title movie.path = path logger.debug("Got movie: %s", movie) movie_id = await self._client.post("movies", data=movie.to_kyoo()) @@ -116,7 +117,7 @@ class Matcher: ) episode.path = path logger.debug("Got episode: %s", episode) - episode.show_id = await self.create_or_get_show(episode) + episode.show_id = await self.create_or_get_show(episode, title) if episode.season_number is not None: episode.season_id = await self.register_seasons( @@ -140,7 +141,7 @@ class Matcher: provider_id = collection.external_id[self._provider.name].data_id return await create_collection(provider_id) - async def create_or_get_show(self, episode: Episode) -> str: + async def create_or_get_show(self, episode: Episode, fallback_name: str) -> str: @cache(ttl=timedelta(days=1), cache=self._show_cache) async def create_show(_: str): # TODO: Check if a show with the same metadata id exists already on kyoo. @@ -151,6 +152,7 @@ class Matcher: if isinstance(episode.show, PartialShow) else episode.show ) + show.file_title = fallback_name # TODO: collections logger.debug("Got show: %s", episode) ret = await self._client.post("show", data=show.to_kyoo()) diff --git a/scanner/providers/types/collection.py b/scanner/providers/types/collection.py index c103e998..c6d01603 100644 --- a/scanner/providers/types/collection.py +++ b/scanner/providers/types/collection.py @@ -1,7 +1,8 @@ -import os from dataclasses import asdict, dataclass, field from typing import Optional +from providers.utils import ProviderError, select_translation, select_image + from .metadataid import MetadataID @@ -20,14 +21,15 @@ class Collection: 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] + trans = select_translation(self) + if trans is None: + raise ProviderError( + "Could not find translations for the collection. Aborting" + ) 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), + **asdict(trans), + "poster": select_image(self, "posters"), + "thumbnail": select_image(self, "thumbnails"), + "logo": select_image(self, "logos"), } diff --git a/scanner/providers/types/episode.py b/scanner/providers/types/episode.py index 0401d4ee..8da11c44 100644 --- a/scanner/providers/types/episode.py +++ b/scanner/providers/types/episode.py @@ -1,8 +1,9 @@ -import os from datetime import date from dataclasses import dataclass, field, asdict from typing import Optional +from providers.utils import select_translation + from .show import Show from .metadataid import MetadataID @@ -24,8 +25,8 @@ class EpisodeID: @dataclass class EpisodeTranslation: - name: str - overview: Optional[str] + name: Optional[str] + overview: Optional[str] = None @dataclass @@ -45,10 +46,9 @@ class Episode: 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] + trans = select_translation(self) or EpisodeTranslation("") return { **asdict(self), - **asdict(self.translations[default_language]), + **asdict(trans), "show": None, } diff --git a/scanner/providers/types/movie.py b/scanner/providers/types/movie.py index 5ae09ce4..b9f3c63c 100644 --- a/scanner/providers/types/movie.py +++ b/scanner/providers/types/movie.py @@ -1,9 +1,10 @@ -import os from dataclasses import asdict, dataclass, field from datetime import date from typing import Optional from enum import Enum +from providers.utils import select_translation, select_image + from .collection import Collection from .genre import Genre from .studio import Studio @@ -44,22 +45,22 @@ class Movie: external_id: dict[str, MetadataID] path: Optional[str] = None + # The title of this show according to it's filename (None only for ease of use in providers) + file_title: Optional[str] = None collections: list[Collection] = field(default_factory=list) translations: dict[str, MovieTranslation] = field(default_factory=dict) def to_kyoo(self): - from ..utils import select_image - - # For now, the API of kyoo only support one language so we remove the others. - default_language = os.environ["LIBRARY_LANGUAGES"].split(",")[0] + trans = select_translation(self) or MovieTranslation(name=self.file_title or "") return { **asdict(self), - **asdict(self.translations[default_language]), + **asdict(trans), "poster": select_image(self, "posters"), "thumbnail": select_image(self, "thumbnails"), "logo": select_image(self, "logos"), - "trailer": next(iter(self.translations[default_language].trailers), None), + "trailer": next(iter(trans.trailers), None), "studio": next((x.to_kyoo() for x in self.studios), None), "genres": [x.to_kyoo() for x in self.genres], "collections": None, + "file_title": None, } diff --git a/scanner/providers/types/season.py b/scanner/providers/types/season.py index 0c224ece..b553dae0 100644 --- a/scanner/providers/types/season.py +++ b/scanner/providers/types/season.py @@ -1,8 +1,9 @@ -import os from datetime import date from dataclasses import dataclass, field, asdict from typing import Optional +from providers.utils import select_translation, select_image + from .metadataid import MetadataID @@ -28,13 +29,10 @@ class Season: translations: dict[str, SeasonTranslation] = 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] + trans = select_translation(self) or SeasonTranslation() 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 - ), + **asdict(trans), + "poster": select_image(self, "posters"), + "thumbnail": select_image(self, "thumbnails"), } diff --git a/scanner/providers/types/show.py b/scanner/providers/types/show.py index 3556f628..b66bd466 100644 --- a/scanner/providers/types/show.py +++ b/scanner/providers/types/show.py @@ -1,9 +1,10 @@ -import os from dataclasses import asdict, dataclass, field from datetime import date from typing import Optional from enum import Enum +from providers.utils import select_translation, select_image + from .genre import Genre from .studio import Studio from .season import Season @@ -20,14 +21,14 @@ class Status(str, Enum): @dataclass class ShowTranslation: name: str - tagline: Optional[str] - tags: list[str] - overview: Optional[str] + tagline: Optional[str] = None + tags: list[str] = field(default_factory=list) + overview: Optional[str] = None - posters: list[str] - logos: list[str] - trailers: list[str] - thumbnails: list[str] + 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 @@ -46,20 +47,21 @@ class Show: external_id: dict[str, MetadataID] translations: dict[str, ShowTranslation] = field(default_factory=dict) + # The title of this show according to it's filename (None only for ease of use in providers) + file_title: Optional[str] = None def to_kyoo(self): - from providers.utils import select_image - - # For now, the API of kyoo only support one language so we remove the others. - default_language = os.environ["LIBRARY_LANGUAGES"].split(",")[0] + trans = select_translation(self) or ShowTranslation(name=self.file_title or "") return { **asdict(self), - **asdict(self.translations[default_language]), + **asdict(trans), + "rating": self.rating or 0, "studio": next((x.to_kyoo() for x in self.studios), None), "seasons": None, "poster": select_image(self, "posters"), "thumbnail": select_image(self, "thumbnails"), "logo": select_image(self, "logos"), - "trailer": next(iter(self.translations[default_language].trailers), None), + "trailer": next(iter(trans.trailers), None), "genres": [x.to_kyoo() for x in self.genres], + "file_title": None, } diff --git a/scanner/providers/utils.py b/scanner/providers/utils.py index 1024de92..f9bc39c0 100644 --- a/scanner/providers/utils.py +++ b/scanner/providers/utils.py @@ -1,11 +1,16 @@ +from __future__ import annotations + import os from datetime import date from itertools import chain +from typing import TYPE_CHECKING, Literal, Any, Optional -from typing import Literal - -from providers.types.movie import Movie -from providers.types.show import Show +if TYPE_CHECKING: + from providers.types.movie import Movie + from providers.types.show import Show + from providers.types.season import Season + from providers.types.episode import Episode + from providers.types.collection import Collection def format_date(date: date | int | None) -> str | None: @@ -16,21 +21,46 @@ def format_date(date: date | int | None) -> str | None: return date.isoformat() +# For now, the API of kyoo only support one language so we remove the others. +default_languages = os.environ["LIBRARY_LANGUAGES"].split(",") + + +def sort_translations( + value: Movie | Show | Season | Episode | Collection, + *, + prefer_orginal=False, +): + from providers.types.movie import Movie + from providers.types.show import Show + + if ( + prefer_orginal + and (isinstance(value, Movie) or isinstance(value, Show)) + and value.original_language + and value.original_language in value.translations + ): + yield value.translations[value.original_language] + for lang in default_languages: + if lang in value.translations: + yield value.translations[lang] + + +def select_translation( + value: Movie | Show | Season | Episode | Collection, *, prefer_orginal=False +) -> Optional[Any]: + return next(sort_translations(value, prefer_orginal=prefer_orginal), None) + + def select_image( - self: Movie | Show, - type: Literal["posters"] | Literal["thumbnails"] | Literal["logos"], + value: Movie | Show | Season | Collection, + kind: Literal["posters"] | Literal["thumbnails"] | Literal["logos"], ) -> str | None: - # For now, the API of kyoo only support one language so we remove the others. - default_language = os.environ["LIBRARY_LANGUAGES"].split(",")[0] return next( chain( - ( - getattr(self.translations[self.original_language], type) - if self.original_language - else [] - ), - getattr(self.translations[default_language], type), - *(getattr(x, type) for x in self.translations.values()), + *( + getattr(trans, kind) + for trans in sort_translations(value, prefer_orginal=True) + ) ), None, )