Use jsons for serialization

This commit is contained in:
Zoe Roux 2023-03-25 13:20:53 +09:00
parent c3b8595cd7
commit 31a349704b
11 changed files with 65 additions and 31 deletions

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
from datetime import datetime
import logging import logging
from aiohttp import ClientSession from aiohttp import ClientSession
from datetime import datetime
from typing import Awaitable, Callable, Dict, Optional, Any, TypeVar from typing import Awaitable, Callable, Dict, Optional, Any, TypeVar
from ..provider import Provider from ..provider import Provider
@ -175,7 +175,7 @@ class TheMovieDatabase(Provider):
f"/tv/{show_id}", f"/tv/{show_id}",
params={ params={
"language": lng, "language": lng,
"append_to_response": "alternative_titles,videos,credits,keywords,images", "append_to_response": "alternative_titles,videos,credits,keywords,images,external_ids",
}, },
) )
logging.debug("TMDb responded: %s", show) logging.debug("TMDb responded: %s", show)
@ -183,7 +183,7 @@ class TheMovieDatabase(Provider):
ret = Show( ret = Show(
original_language=show["original_language"], original_language=show["original_language"],
aliases=[x["title"] for x in show["alternative_titles"]["titles"]], aliases=[x["title"] for x in show["alternative_titles"]["results"]],
start_air=datetime.strptime(show["first_air_date"], "%Y-%m-%d").date(), start_air=datetime.strptime(show["first_air_date"], "%Y-%m-%d").date(),
end_air=datetime.strptime(show["last_air_date"], "%Y-%m-%d").date(), end_air=datetime.strptime(show["last_air_date"], "%Y-%m-%d").date(),
status=ShowStatus.FINISHED status=ShowStatus.FINISHED
@ -202,9 +202,10 @@ class TheMovieDatabase(Provider):
show["id"], f"https://www.themoviedb.org/tv/{show['id']}" show["id"], f"https://www.themoviedb.org/tv/{show['id']}"
), ),
"imdb": MetadataID( "imdb": MetadataID(
show["imdb_id"], show["external_ids"]["imdb_id"],
f"https://www.imdb.com/title/{show['imdb_id']}", f"https://www.imdb.com/title/{show['external_ids']['imdb_id']}",
), ),
"tvdb": MetadataID(show["external_ids"]["tvdb_id"], link=None),
}, },
seasons=[ seasons=[
self.to_season(x, language=lng, show_id=show["id"]) self.to_season(x, language=lng, show_id=show["id"])
@ -215,7 +216,7 @@ class TheMovieDatabase(Provider):
translation = ShowTranslation( translation = ShowTranslation(
name=show["name"], name=show["name"],
tagline=show["tagline"], tagline=show["tagline"],
keywords=list(map(lambda x: x["name"], show["keywords"]["keywords"])), keywords=list(map(lambda x: x["name"], show["keywords"]["results"])),
overview=show["overview"], overview=show["overview"],
posters=self.get_image(show["images"]["posters"]), posters=self.get_image(show["images"]["posters"]),
logos=self.get_image(show["images"]["logos"]), logos=self.get_image(show["images"]["logos"]),
@ -257,8 +258,8 @@ class TheMovieDatabase(Provider):
) -> Season: ) -> Season:
return Season( return Season(
season_number=season["season_number"], season_number=season["season_number"],
start_date=datetime.strptime(season["air_date"], "%Y-%m-%d").date(), start_air=datetime.strptime(season["air_date"], "%Y-%m-%d").date(),
end_date=None, end_air=None,
external_id={ external_id={
self.name: MetadataID( self.name: MetadataID(
season["id"], season["id"],
@ -269,10 +270,10 @@ class TheMovieDatabase(Provider):
language: SeasonTranslation( language: SeasonTranslation(
name=season["name"], name=season["name"],
overview=season["overview"], overview=season["overview"],
poster=[ posters=[
f"https://image.tmdb.org/t/p/original{season['poster_path']}" f"https://image.tmdb.org/t/p/original{season['poster_path']}"
] ]
if "poster_path" in season if season["poster_path"] is not None
else [], else [],
thumbnails=[], thumbnails=[],
) )
@ -295,7 +296,9 @@ class TheMovieDatabase(Provider):
# TODO: Handle absolute episodes # TODO: Handle absolute episodes
if not season or not episode_nbr: if not season or not episode_nbr:
raise NotImplementedError("Absolute order episodes not implemented for the movie database") raise NotImplementedError(
"Absolute order episodes not implemented for the movie database"
)
async def for_language(lng: str) -> Episode: async def for_language(lng: str) -> Episode:
episode = await self.get( episode = await self.get(
@ -327,7 +330,7 @@ class TheMovieDatabase(Provider):
external_id={ external_id={
self.name: MetadataID( self.name: MetadataID(
episode["id"], episode["id"],
f"https://www.themoviedb.org/movie/{episode['id']}", f"https://www.themoviedb.org/tv/{show_id}/season/{episode['season_number']}/episode/{episode['episode_number']}",
), ),
}, },
) )

View File

@ -28,7 +28,7 @@ class Episode:
season_number: Optional[int] season_number: Optional[int]
episode_number: Optional[int] episode_number: Optional[int]
absolute_number: Optional[int] absolute_number: Optional[int]
release_date: Optional[date] release_date: Optional[date | int]
thumbnail: Optional[str] thumbnail: Optional[str]
external_id: dict[str, MetadataID] external_id: dict[str, MetadataID]
@ -43,5 +43,4 @@ class Episode:
return { return {
**asdict(self), **asdict(self),
**asdict(self.translations[default_language]), **asdict(self.translations[default_language]),
"release_date": format_date(self.release_date),
} }

View File

@ -1,7 +1,8 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
@dataclass @dataclass
class MetadataID: class MetadataID:
id: str id: str
link: str link: Optional[str]

View File

@ -56,7 +56,7 @@ class Movie:
), ),
"logo": next(iter(self.translations[default_language].logos), None), "logo": next(iter(self.translations[default_language].logos), None),
"trailer": next(iter(self.translations[default_language].trailers), None), "trailer": next(iter(self.translations[default_language].trailers), None),
"studio": next(iter(x.to_kyoo() for x in self.studios), None), "studio": next(iter(self.studios), None),
"release_date": None, "release_date": None,
"startAir": format_date(self.release_date), "startAir": format_date(self.release_date),
"title": self.translations[default_language].name, "title": self.translations[default_language].name,

View File

@ -1,7 +1,9 @@
import os
from datetime import date from datetime import date
from dataclasses import dataclass, field from dataclasses import dataclass, field, asdict
from typing import Optional from typing import Optional
from ..utils import format_date
from .metadataid import MetadataID from .metadataid import MetadataID
@ -9,15 +11,27 @@ from .metadataid import MetadataID
class SeasonTranslation: class SeasonTranslation:
name: Optional[str] name: Optional[str]
overview: Optional[str] overview: Optional[str]
poster: list[str] posters: list[str]
thumbnails: list[str] thumbnails: list[str]
@dataclass @dataclass
class Season: class Season:
season_number: int season_number: int
start_date: Optional[date | int] start_air: Optional[date | int]
end_date: Optional[date | int] end_air: Optional[date | int]
external_id: dict[str, MetadataID] external_id: dict[str, MetadataID]
translations: dict[str, SeasonTranslation] = field(default_factory=dict) 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]
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
),
}

View File

@ -10,6 +10,7 @@ from .season import Season
from .metadataid import MetadataID from .metadataid import MetadataID
from ..utils import format_date from ..utils import format_date
class Status(str, Enum): class Status(str, Enum):
UNKNOWN = "unknown" UNKNOWN = "unknown"
FINISHED = "finished" FINISHED = "finished"
@ -58,9 +59,8 @@ class Show:
), ),
"logo": next(iter(self.translations[default_language].logos), None), "logo": next(iter(self.translations[default_language].logos), None),
"trailer": next(iter(self.translations[default_language].trailers), None), "trailer": next(iter(self.translations[default_language].trailers), None),
"studio": next(iter(x.to_kyoo() for x in self.studios), None), "studio": next(iter(self.studios), None),
"startAir": format_date(self.start_air),
"endAir": format_date(self.end_air),
"title": self.translations[default_language].name, "title": self.translations[default_language].name,
"genres": [x.to_kyoo() for x in self.genres], "genres": [x.to_kyoo() for x in self.genres],
"seasons": [x.to_kyoo() for x in self.seasons],
} }

View File

@ -8,6 +8,3 @@ class Studio:
name: str name: str
logos: list[str] = field(default_factory=list) logos: list[str] = field(default_factory=list)
external_id: dict[str, MetadataID] = field(default_factory=dict) external_id: dict[str, MetadataID] = field(default_factory=dict)
def to_kyoo(self):
return asdict(self)

View File

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

View File

@ -1,3 +1,4 @@
guessit guessit
aiohttp aiohttp
jsons
black-with-tabs black-with-tabs

View File

@ -5,7 +5,11 @@ async def main():
import os import os
import logging import logging
import sys import sys
import jsons
from datetime import date
from typing import Optional
from aiohttp import ClientSession from aiohttp import ClientSession
from providers.utils import format_date
path = os.environ.get("LIBRARY_ROOT") path = os.environ.get("LIBRARY_ROOT")
if not path: if not path:
@ -28,7 +32,12 @@ async def main():
if len(sys.argv) > 1 and sys.argv[1] == "-vv": if len(sys.argv) > 1 and sys.argv[1] == "-vv":
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
async with ClientSession() as client: jsons.set_serializer(lambda x, **_: format_date(x), Optional[date | int])
async with ClientSession(
json_serialize=lambda *args, **kwargs: jsons.dumps(
*args, key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE, **kwargs
),
) as client:
await Scanner(client, languages=languages.split(","), api_key=api_key).scan( await Scanner(client, languages=languages.split(","), api_key=api_key).scan(
path path
) )

View File

@ -1,13 +1,14 @@
from functools import wraps from functools import wraps
import json
import os import os
import asyncio import asyncio
import logging import logging
import jsons
from aiohttp import ClientSession from aiohttp import ClientSession
from pathlib import Path from pathlib import Path
from guessit import guessit from guessit import guessit
from providers.provider import Provider from providers.provider import Provider
from providers.types.episode import PartialShow from providers.types.episode import Episode, PartialShow
from providers.types.show import Show
def log_errors(f): def log_errors(f):
@ -81,7 +82,15 @@ class Scanner:
async def post(self, path: str, *, data: object) -> str: async def post(self, path: str, *, data: object) -> str:
url = os.environ.get("KYOO_URL", "http://back:5000") url = os.environ.get("KYOO_URL", "http://back:5000")
print(json.dumps(data, indent=4)) logging.info(
"Sending %s: %s",
path,
jsons.dumps(
data,
key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE,
jdkwargs={"indent": 4},
),
)
async with self._client.post( async with self._client.post(
f"{url}/{path}", json=data, headers={"X-API-Key": self._api_key} f"{url}/{path}", json=data, headers={"X-API-Key": self._api_key}
) as r: ) as r: