diff --git a/api/src/models/video.ts b/api/src/models/video.ts index c6257c28..c0c1298e 100644 --- a/api/src/models/video.ts +++ b/api/src/models/video.ts @@ -1,37 +1,37 @@ -import { PatternStringExact } from "@sinclair/typebox"; +import { PatternStringExact, type TSchema } from "@sinclair/typebox"; import { t } from "elysia"; import { type Prettify, comment } from "~/utils"; import { ExtraType } from "./entry/extra"; import { bubble, bubbleVideo, registerExamples } from "./examples"; import { DbMetadata, EpisodeId, ExternalId, Resource } from "./utils"; +const Opt = (schema: TSchema) => t.Optional(t.Nullable(schema)); + export const Guess = t.Recursive((Self) => t.Object( { title: t.String(), - kind: t.Optional(t.UnionEnum(["episode", "movie", "extra"])), - extraKind: t.Optional(ExtraType), - years: t.Optional(t.Array(t.Integer(), { default: [] })), - episodes: t.Optional( + kind: Opt(t.UnionEnum(["episode", "movie", "extra"])), + extraKind: Opt(ExtraType), + years: Opt(t.Array(t.Integer(), { default: [] })), + episodes: Opt( t.Array( t.Object({ season: t.Nullable(t.Integer()), episode: t.Integer() }), { default: [] }, ), ), - externalId: t.Optional(t.Record(t.String(), t.String())), + externalId: Opt(t.Record(t.String(), t.String())), from: t.String({ description: "Name of the tool that made the guess", }), - history: t.Optional( - t.Array(t.Omit(Self, ["history"]), { - default: [], - description: comment` - When another tool refines the guess or a user manually edit it, the history of the guesses - are kept in this \`history\` value. - `, - }), - ), + history: t.Array(t.Omit(Self, ["history"]), { + default: [], + description: comment` + When another tool refines the guess or a user manually edit it, the history of the guesses + are kept in this \`history\` value. + `, + }), }, { additionalProperties: true, diff --git a/scanner/README.md b/scanner/README.md index 75bd5ea6..5532b1c1 100644 --- a/scanner/README.md +++ b/scanner/README.md @@ -1,6 +1,6 @@ # Scanner -## Workflow (for v5, not current) +## Workflow In order of action: diff --git a/scanner/scanner/client.py b/scanner/scanner/client.py index f497f5b7..654f7e65 100644 --- a/scanner/scanner/client.py +++ b/scanner/scanner/client.py @@ -3,6 +3,7 @@ from logging import getLogger from types import TracebackType from aiohttp import ClientSession +from pydantic import TypeAdapter from scanner.utils import Singleton @@ -44,15 +45,15 @@ class KyooClient(metaclass=Singleton): async def create_videos(self, videos: list[Video]) -> list[VideoCreated]: async with self._client.post( "videos", - json=[x.model_dump_json() for x in videos], + data=TypeAdapter(list[Video]).dump_json(videos, by_alias=True), ) as r: r.raise_for_status() - return list[VideoCreated](**await r.json()) + return TypeAdapter(list[VideoCreated]).validate_json(await r.text()) async def delete_videos(self, videos: list[str] | set[str]): async with self._client.delete( "videos", - json=videos, + data=TypeAdapter(list[str] | set[str]).dump_json(videos, by_alias=True), ) as r: r.raise_for_status() diff --git a/scanner/scanner/identifiers/identify.py b/scanner/scanner/identifiers/identify.py index b9fe8c56..393efcc7 100644 --- a/scanner/scanner/identifiers/identify.py +++ b/scanner/scanner/identifiers/identify.py @@ -4,6 +4,8 @@ from itertools import zip_longest from logging import getLogger from typing import Callable, Literal, cast +from rebulk.match import Match + from ..models.videos import Guess, Video from .guess.guess import guessit @@ -49,12 +51,15 @@ async def identify(path: str) -> Video: for s, e in zip_longest( seasons, episodes, - fillvalue=seasons[-1] if len(seasons) < len(episodes) else episodes[-1], + fillvalue=seasons[-1] if any(seasons) else Match(0, 0, value=1), ) ], external_id={}, from_="guessit", - raw={k: [x.value for x in v] for k, v in raw.items()}, + raw={ + k: [x.value if x.value is int else str(x.value) for x in v] + for k, v in raw.items() + }, ) for step in pipeline: diff --git a/scanner/scanner/models/videos.py b/scanner/scanner/models/videos.py index 459723e8..041df4f7 100644 --- a/scanner/scanner/models/videos.py +++ b/scanner/scanner/models/videos.py @@ -73,6 +73,8 @@ class Video(Model): ] = [] -class VideoCreated(Resource): +class VideoCreated(Model): + id: str + path: str guess: Guess entries: list[Resource] diff --git a/scanner/scanner/utils.py b/scanner/scanner/utils.py index 6bf6ae91..9e71e83d 100644 --- a/scanner/scanner/utils.py +++ b/scanner/scanner/utils.py @@ -2,7 +2,7 @@ from abc import ABCMeta from typing import Annotated, Any, Callable, override from langcodes import Language as BaseLanguage -from pydantic import AliasGenerator, BaseModel, ConfigDict, GetJsonSchemaHandler +from pydantic import BaseModel, ConfigDict, GetJsonSchemaHandler from pydantic.alias_generators import to_camel from pydantic.json_schema import JsonSchemaValue from pydantic_core import core_schema @@ -29,9 +29,8 @@ class Singleton(ABCMeta, type): class Model(BaseModel): model_config = ConfigDict( use_enum_values=True, - alias_generator=AliasGenerator( - serialization_alias=lambda x: to_camel(x[:-1] if x[-1] == "_" else x), - ), + validate_by_name=True, + alias_generator=lambda x: to_camel(x[:-1] if x[-1] == "_" else x), )