Match guessed's entries to tmdb's entries

This commit is contained in:
Zoe Roux 2025-05-09 02:20:06 +02:00
parent 47f6c66435
commit f26dce64ef
No known key found for this signature in database
3 changed files with 67 additions and 97 deletions

View File

@ -3,6 +3,8 @@ from logging import getLogger
from aiohttp import ClientSession from aiohttp import ClientSession
from .models.movie import Movie
from .models.serie import Serie
from .models.videos import Video, VideoCreated, VideoInfo from .models.videos import Video, VideoCreated, VideoInfo
logger = getLogger(__name__) logger = getLogger(__name__)
@ -10,33 +12,32 @@ logger = getLogger(__name__)
class KyooClient: class KyooClient:
def __init__(self) -> None: def __init__(self) -> None:
self._api_key: str = os.environ.get("KYOO_APIKEY") # type: ignore api_key = os.environ.get("KYOO_APIKEY")
if not self._api_key: if not api_key:
print("Missing environment variable 'KYOO_APIKEY'.") print("Missing environment variable 'KYOO_APIKEY'.")
exit(2) exit(2)
self._url = os.environ.get("KYOO_URL", "http://api:3567/api")
async def __aenter__(self):
self._client = ClientSession( self._client = ClientSession(
base_url=os.environ.get("KYOO_URL", "http://api:3567/api"),
headers={ headers={
"User-Agent": "kyoo", "User-Agent": "kyoo scanner v5",
"X-API-KEY": api_key,
}, },
) )
async def __aenter__(self):
return self return self
async def __aexit__(self): async def __aexit__(self):
await self._client.close() await self._client.close()
async def get_videos_info(self) -> VideoInfo: async def get_videos_info(self) -> VideoInfo:
async with self._client.get( async with self._client.get("/videos") as r:
f"{self._url}/videos",
) as r:
r.raise_for_status() r.raise_for_status()
return VideoInfo(**await r.json()) return VideoInfo(**await r.json())
async def create_videos(self, videos: list[Video]) -> list[VideoCreated]: async def create_videos(self, videos: list[Video]) -> list[VideoCreated]:
async with self._client.post( async with self._client.post(
f"{self._url}/videos", "videos",
json=[x.model_dump_json() for x in videos], json=[x.model_dump_json() for x in videos],
) as r: ) as r:
r.raise_for_status() r.raise_for_status()
@ -44,85 +45,21 @@ class KyooClient:
async def delete_videos(self, videos: list[str] | set[str]): async def delete_videos(self, videos: list[str] | set[str]):
async with self._client.delete( async with self._client.delete(
f"{self._url}/videos", "videos",
json=videos, json=videos,
) as r: ) as r:
r.raise_for_status() r.raise_for_status()
# async def link_collection( async def create_movie(self, movie: Movie):
# self, collection: str, type: Literal["movie"] | Literal["show"], id: str async with self._client.post(
# ): "movies",
# async with self.client.put( json=movie.model_dump_json(),
# f"{self._url}/collections/{collection}/{type}/{id}", ) as r:
# headers={"X-API-Key": self._api_key}, r.raise_for_status()
# ) as r:
# # Allow 409 and continue as if it worked. async def create_serie(self, serie: Serie):
# if not r.ok and r.status != 409: async with self._client.post(
# logger.error(f"Request error: {await r.text()}") "series",
# r.raise_for_status() json=serie.model_dump_json(),
# ) as r:
# async def post(self, path: str, *, data: dict[str, Any]) -> str: r.raise_for_status()
# logger.debug(
# "Sending %s: %s",
# path,
# jsons.dumps(
# data,
# key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE,
# jdkwargs={"indent": 4},
# ),
# )
# async with self.client.post(
# f"{self._url}/{path}",
# json=data,
# headers={"X-API-Key": self._api_key},
# ) as r:
# # Allow 409 and continue as if it worked.
# if not r.ok and r.status != 409:
# logger.error(f"Request error: {await r.text()}")
# r.raise_for_status()
# ret = await r.json()
# return ret["id"]
#
# async def delete(
# self,
# path: str,
# ):
# logger.info("Deleting %s", path)
#
# async with self.client.delete(
# f"{self._url}/paths?recursive=true&path={quote(path)}",
# headers={"X-API-Key": self._api_key},
# ) as r:
# if not r.ok:
# logger.error(f"Request error: {await r.text()}")
# r.raise_for_status()
#
# async def get(self, path: str):
# async with self.client.get(
# f"{self._url}/{path}",
# headers={"X-API-Key": self._api_key},
# ) as r:
# if not r.ok:
# logger.error(f"Request error: {await r.text()}")
# r.raise_for_status()
# return await r.json()
#
# async def put(self, path: str, *, data: dict[str, Any]):
# logger.debug(
# "Sending %s: %s",
# path,
# jsons.dumps(
# data,
# key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE,
# jdkwargs={"indent": 4},
# ),
# )
# async with self.client.put(
# f"{self._url}/{path}",
# json=data,
# headers={"X-API-Key": self._api_key},
# ) as r:
# # Allow 409 and continue as if it worked.
# if not r.ok and r.status != 409:
# logger.error(f"Request error: {await r.text()}")
# r.raise_for_status()

View File

@ -36,7 +36,10 @@ class Provider(ABC):
raise NotImplementedError raise NotImplementedError
async def find_movie( async def find_movie(
self, title: str, year: int | None, external_id: dict[str, str] self,
title: str,
year: int | None,
external_id: dict[str, str],
) -> Movie: ) -> Movie:
ret = await self.get_movie(external_id) ret = await self.get_movie(external_id)
if ret is not None: if ret is not None:
@ -54,7 +57,10 @@ class Provider(ABC):
return ret return ret
async def find_serie( async def find_serie(
self, title: str, year: int | None, external_id: dict[str, str] self,
title: str,
year: int | None,
external_id: dict[str, str],
) -> Serie: ) -> Serie:
ret = await self.get_serie(external_id) ret = await self.get_serie(external_id)
if ret is not None: if ret is not None:

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from logging import getLogger
from typing import Literal from typing import Literal
from .client import KyooClient from .client import KyooClient
@ -7,6 +8,8 @@ from .models.videos import Guess
from .providers.composite import CompositeProvider from .providers.composite import CompositeProvider
from .utils import Model from .utils import Model
logger = getLogger(__name__)
class Request(Model): class Request(Model):
kind: Literal["episode", "movie"] kind: Literal["episode", "movie"]
@ -33,21 +36,45 @@ async def enqueue(requests: list[Request]):
class RequestProcessor: class RequestProcessor:
def __init__(self, client: KyooClient, providers: CompositeProvider): def __init__(self, client: KyooClient, providers: CompositeProvider):
self._client = client self._client = client
self._providers = providers
async def process_scan_requests(self): async def process_scan_requests(self):
# select for update skip_locked limit 1 # select for update skip_locked limit 1
request: Request = ... request: Request = ...
if request.kind == "movie": if request.kind == "movie":
movie = await providers.get_movie( movie = await self._providers.find_movie(
request.title, request.year, request.external_id request.title,
request.year,
request.external_id,
) )
movie.videos = request.videos movie.videos = [x.id for x in request.videos]
await self._client.create_movie(movie) await self._client.create_movie(movie)
else: else:
serie = await providers.get_serie(request.title, request.year) serie = await self._providers.find_serie(
# for vid in request.videos: request.title,
# for ep in vid.episodes: request.year,
# entry = next(x for x in series.entries if (ep.season is None or x.season == ep.season), None) request.external_id,
)
for vid in request.videos:
for ep in vid.episodes:
entry = next(
(
x
for x in serie.entries
if (ep.season is None and x.order == ep.episode)
or (
x.season_number == ep.season
and x.episode_number == ep.episode
)
),
None,
)
if entry is None:
logger.warning(
f"Couldn't match entry for {serie.slug} {ep.season or 'abs'}-e{ep.episode}."
)
continue
entry.videos.append(vid.id)
await self._client.create_serie(serie) await self._client.create_serie(serie)
# delete request # delete request