diff --git a/server/entertainment_decider/extractors/all/tvmaze.py b/server/entertainment_decider/extractors/all/tvmaze.py index cca1c15..95f3d70 100644 --- a/server/entertainment_decider/extractors/all/tvmaze.py +++ b/server/entertainment_decider/extractors/all/tvmaze.py @@ -3,14 +3,32 @@ from __future__ import annotations from typing import ( Any, Dict, + Iterable, List, Literal, + NewType, Optional, TypeVar, TypedDict, Union, ) +from ...models import Tag, TagKey +from ...models.localization import ( + get_country_tag as get_country_tag_by_code, + get_language_tag, +) + + +EXTRACTOR_KEY = ".extractor/com.tvmaze" +EXTRACTOR_NAME = "TVMaze" + + +GenreName = NewType("GenreName", str) +NetworkId = NewType("NetworkId", int) +ShowTypeName = NewType("ShowTypeName", str) +WebChannelId = NewType("WebChannelId", int) + Weekdays = Union[ Literal["Monday"], @@ -76,7 +94,7 @@ def select_best_image(*image_list: TvmazeImage) -> Optional[str]: class TvmazeNetwork(TypedDict): - id: int + id: NetworkId name: str country: TvmazeCountry webChannel: Optional[Any] @@ -100,8 +118,8 @@ class TvmazeSeason(TypedDict): episodeOrder: int premiereDate: str endDate: str - network: TvmazeNetwork - webChannel: Optional[Any] + network: Optional[TvmazeNetwork] + webChannel: Optional[TvmazeWebChannel] image: TvmazeImage summary: str @@ -110,9 +128,9 @@ class TvmazeShow(TypedDict): id: int url: str name: str - type: str + type: ShowTypeName language: str - genres: List[str] + genres: List[GenreName] status: str runtime: int averageRuntime: int @@ -122,6 +140,9 @@ class TvmazeShow(TypedDict): schedule: TvmazeSchedule rating: TvmazeRating weight: int + network: Optional[TvmazeNetwork] + webChannel: Optional[TvmazeWebChannel] + dvdCountry: Optional[TvmazeCountry] externals: TvmazeExternalIds image: TvmazeImage summary: str @@ -132,6 +153,13 @@ class TvmazeShowEmbedded(TvmazeShow): _embedded: TvmazeEmbeddings +class TvmazeWebChannel(TypedDict): + id: WebChannelId + name: str + country: TvmazeCountry + dvdCountry: Optional[TvmazeCountry] + + T = TypeVar("T", bound=Dict) @@ -140,3 +168,110 @@ def add_embedding(object: T, key: str, value: Any, parent_key: str = "_embedded" object[parent_key] = {} object[parent_key][key] = value return object + + +# Tag related stuff + + +GENRE_PREFIX = f"{EXTRACTOR_KEY}/genre" +NETWORK_PREFIX = f"{EXTRACTOR_KEY}/network" +SHOW_TYPE_PREFIX = f"{EXTRACTOR_KEY}/show_type" +WEB_CHANNEL_PREFIX = f"{EXTRACTOR_KEY}/web_channel" + + +## multi tag generators + + +def get_show_tags(show: TvmazeShow) -> Iterable[Tag]: + general_video_tag = TagKey.get_tag(".kind/video") + if general_video_tag is not None: + yield general_video_tag + yield get_show_type_tag(show["type"]) + yield get_language_tag(show["language"]) + for genre in show["genres"]: + yield get_genre_tag(genre) + network = show["network"] + if network is not None: + yield from get_all_network_tags(network) + web_channel = show["webChannel"] + if web_channel is not None: + yield from get_all_web_channel_tags(web_channel) + + +def get_all_network_tags(network: TvmazeNetwork) -> Iterable[Tag]: + country = network["country"] + if country is not None: + yield get_country_tag(country) + yield get_network_tag(network) + + +def get_all_web_channel_tags(web_channel: TvmazeWebChannel) -> Iterable[Tag]: + country = web_channel["country"] + if country is not None: + yield get_country_tag(country) + yield get_web_channel_tag(web_channel) + + +## single tag generators + + +def get_country_tag(country: TvmazeCountry) -> Tag: + return get_country_tag_by_code(country["code"]) + + +def get_genre_tag(genre_name: GenreName) -> Tag: + return get_any_tag( + category_key=GENRE_PREFIX, + category_name="Genre", + element_key=genre_name.lower(), + element_name=genre_name, + ) + + +def get_network_tag(network: TvmazeNetwork) -> Tag: + return get_any_tag( + category_key=NETWORK_PREFIX, + category_name="Network", + element_key=str(network["id"]), + element_name=network["name"], + ) + + +def get_show_type_tag(show_type_name: ShowTypeName) -> Tag: + return get_any_tag( + category_key=SHOW_TYPE_PREFIX, + category_name="Show Type", + element_key=show_type_name.lower(), + element_name=show_type_name, + ) + + +def get_web_channel_tag(web_channel: TvmazeWebChannel) -> Tag: + return get_any_tag( + category_key=WEB_CHANNEL_PREFIX, + category_name="Web Channel", + element_key=str(web_channel["id"]), + element_name=web_channel["name"], + ) + + +def get_any_tag( + *, + category_key: str, + category_name: str, + element_key: str, + element_name: str, +) -> Tag: + TagKey.get_or_create_tag( + tag_key=category_key, + title=f"[{EXTRACTOR_NAME}] {category_name}", + use_for_preferences=False, + ) + return TagKey.get_or_create_tag( + tag_key=f"{category_key}/{element_key}", + title=f"[{EXTRACTOR_NAME}] [{category_name}] {element_name}", + use_for_preferences=True, + super_tags=[ + category_key, + ], + ) diff --git a/server/entertainment_decider/extractors/collection/tvmaze.py b/server/entertainment_decider/extractors/collection/tvmaze.py index c9b6e91..a49e5aa 100644 --- a/server/entertainment_decider/extractors/collection/tvmaze.py +++ b/server/entertainment_decider/extractors/collection/tvmaze.py @@ -1,21 +1,21 @@ from __future__ import annotations from datetime import datetime -import itertools import re -from typing import List, Optional +from typing import Optional from pony import orm # TODO remove import requests from ...models import ( MediaCollection, - Tag, + MediaElement, ) from ..all.tvmaze import ( TvmazeEpisodeEmbedded, TvmazeShowEmbedded, add_embedding, + get_show_tags, ) from ..generic import ( ChangedReport, @@ -125,12 +125,9 @@ class TvmazeCollectionExtractor(CollectionExtractor[TvmazeShowEmbedded]): self.__get_show_custom_uri(data["id"]), ) ) - for genre in itertools.chain(["Video", data["type"]], data["genres"]): - tag_list: List[Tag] = list( - orm.select(tag for tag in Tag if tag.title == genre) - ) - if len(tag_list) == 1: - object.tag_list.add(tag_list[0]) + for tag in get_show_tags(data): + object.tag_list.add(tag) + elem_set = set[MediaElement]() for episode in data["_embedded"]["episodes"]: if episode["airstamp"] is not None: add_embedding(episode, "show", data) diff --git a/server/entertainment_decider/extractors/media/tvmaze.py b/server/entertainment_decider/extractors/media/tvmaze.py index 3d00ed3..2eda15f 100644 --- a/server/entertainment_decider/extractors/media/tvmaze.py +++ b/server/entertainment_decider/extractors/media/tvmaze.py @@ -9,7 +9,6 @@ import requests from ...models import MediaElement, MediaThumbnail from ..all.tvmaze import ( TvmazeEpisodeEmbedded, - TvmazeShowEmbedded, select_best_image, ) from ..generic import (