diff --git a/.devcontainer/ui-lovelace.yaml b/.devcontainer/ui-lovelace.yaml index 440bee8..3da9a14 100644 --- a/.devcontainer/ui-lovelace.yaml +++ b/.devcontainer/ui-lovelace.yaml @@ -2,4 +2,6 @@ views: - title: Test environment cards: - type: custom:upcoming-media-card - entity: sensor.trakt_next_to_watch_all \ No newline at end of file + entity: sensor.trakt_next_to_watch_all + enable_tooltips: true + enable_trailers: true \ No newline at end of file diff --git a/custom_components/trakt_tv/apis/tmdb.py b/custom_components/trakt_tv/apis/tmdb.py index 61bd16a..bd836f2 100644 --- a/custom_components/trakt_tv/apis/tmdb.py +++ b/custom_components/trakt_tv/apis/tmdb.py @@ -1,19 +1,22 @@ -from typing import Any, Dict +from typing import Any, Dict, Optional import aiohttp from custom_components.trakt_tv.const import TMDB_HOST, TMDB_TOKEN -async def get_media_data(kind: str, tmbd_id: int, language: str) -> Dict[str, Any]: +async def get_media_data( + kind: str, prefix: str, tmbd_id: int, language: str +) -> Dict[str, Any]: """ Get information from TMDB about a kind of media. :param kind: The kind of media + :param prefix: The route prefix :param tmbd_id: The ID of the media :param language: The favorite language of the user """ - url = f"{TMDB_HOST}/3/{kind}/{tmbd_id}?api_key={TMDB_TOKEN}&language={language}" + url = f"{TMDB_HOST}/3/{kind}/{tmbd_id}{prefix}?api_key={TMDB_TOKEN}&language={language}" async with aiohttp.request("GET", url) as response: return await response.json() @@ -25,7 +28,7 @@ async def get_movie_data(tmbd_id: int, language: str) -> Dict[str, Any]: :param tmbd_id: The ID of the movie :param language: The favorite language of the user """ - return await get_media_data("movie", tmbd_id, language) + return await get_media_data("movie", "", tmbd_id, language) async def get_show_data(tmbd_id: int, language: str) -> Dict[str, Any]: @@ -35,4 +38,43 @@ async def get_show_data(tmbd_id: int, language: str) -> Dict[str, Any]: :param tmbd_id: The ID of the show :param language: The favorite language of the user """ - return await get_media_data("tv", tmbd_id, language) + return await get_media_data("tv", "", tmbd_id, language) + + +def _extract_trailer_from_data(data: Dict[str, Any]) -> Optional[str]: + """ + Extract trailer data from video data. + """ + videos = data.get("results", {}) + + for video in videos: + site = video.get("site") + _type = video.get("type") + key = video.get("key") + + if site == "YouTube" and _type == "Trailer" and key is not None: + return f"https://www.youtube.com/watch?v={key}" + + return None + + +async def get_movie_trailer(tmbd_id: int, language: str) -> Optional[str]: + """ + Get trailer url from TMDB about a movie. + + :param tmbd_id: The ID of the movie + :param language: The favorite language of the user + """ + data = await get_media_data("movie", "/videos", tmbd_id, language) + return _extract_trailer_from_data(data) + + +async def get_show_trailer(tmbd_id: int, language: str) -> Optional[str]: + """ + Get trailer url from TMDB about a show. + + :param tmbd_id: The ID of the show + :param language: The favorite language of the user + """ + data = await get_media_data("tv", "/videos", tmbd_id, language) + return _extract_trailer_from_data(data) diff --git a/custom_components/trakt_tv/models/media.py b/custom_components/trakt_tv/models/media.py index f5b0534..c31bbe5 100644 --- a/custom_components/trakt_tv/models/media.py +++ b/custom_components/trakt_tv/models/media.py @@ -1,9 +1,15 @@ from abc import ABC, abstractmethod, abstractstaticmethod +from asyncio import gather from dataclasses import dataclass, field from datetime import datetime, timezone from typing import Any, Dict, List, Optional -from custom_components.trakt_tv.apis.tmdb import get_movie_data, get_show_data +from custom_components.trakt_tv.apis.tmdb import ( + get_movie_data, + get_movie_trailer, + get_show_data, + get_show_trailer, +) from ..const import UPCOMING_DATA_FORMAT @@ -78,7 +84,7 @@ def common_information(self) -> Dict[str, Any]: return {k: v for k, v in default.items() if v is not None} - async def get_more_information(self, language): + async def get_more_information(self, language: str): """ Get information from other API calls to complete the trakt movie. @@ -93,6 +99,8 @@ class Movie(Media): """ genres: List[str] = field(default_factory=list) + trailer: Optional[str] = None + summary: Optional[str] = None poster: Optional[str] = None fanart: Optional[str] = None rating: Optional[int] = None @@ -119,15 +127,23 @@ def from_trakt(data) -> "Movie": ids=Identifiers.from_trakt(movie), ) - async def get_more_information(self, language): + async def get_more_information(self, language: str): """ Get information from other API calls to complete the trakt movie. :param language: The favorite language of the user """ - data = await get_movie_data(self.ids.tmdb, language) + data, trailer = await gather( + get_movie_data(self.ids.tmdb, language), + get_movie_trailer(self.ids.tmdb, language), + ) + if title := data.get("title"): self.name = title + if trailer: + self.trailer = trailer + if summary := data.get("overview"): + self.summary = summary if poster := data.get("poster_path"): self.poster = f"https://image.tmdb.org/t/p/w500{poster}" if fanart := data.get("backdrop_path"): @@ -165,6 +181,10 @@ def to_homeassistant(self) -> Dict[str, Any]: if self.ids.slug is not None: default["deep_link"] = f"https://trakt.tv/movies/{self.ids.slug}" + if self.trailer is not None: + default["trailer"] = self.trailer + if self.summary is not None: + default["summary"] = self.summary return default @@ -193,6 +213,8 @@ def from_trakt(data) -> "Episode": @dataclass class Show(Media): + trailer: Optional[str] = None + summary: Optional[str] = None poster: Optional[str] = None fanart: Optional[str] = None genres: List[str] = field(default_factory=list) @@ -225,9 +247,23 @@ def from_trakt(data) -> "Show": episode=episode, ) - def update_common_information(self, data: Dict[str, Any]): + async def get_more_information(self, language: str): + """ + Get information from other API calls to complete the trakt movie. + + :param language: The favorite language of the user + """ + data, trailer = await gather( + get_show_data(self.ids.tmdb, language), + get_show_trailer(self.ids.tmdb, language), + ) + if title := data.get("title"): self.name = title + if trailer: + self.trailer = trailer + if summary := data.get("overview"): + self.summary = summary if poster := data.get("poster_path"): self.poster = f"https://image.tmdb.org/t/p/w500{poster}" if fanart := data.get("backdrop_path"): @@ -246,15 +282,6 @@ def update_common_information(self, data: Dict[str, Any]): # If we really can't find the release date, we set it to the minimum date self.released = datetime.min - async def get_more_information(self, language): - """ - Get information from other API calls to complete the trakt movie. - - :param language: The favorite language of the user - """ - data = await get_show_data(self.ids.tmdb, language) - self.update_common_information(data) - def to_homeassistant(self) -> Dict[str, Any]: """ Convert the Show to upcoming data. @@ -283,6 +310,10 @@ def to_homeassistant(self) -> Dict[str, Any]: if self.ids.slug is not None: default["deep_link"] = f"https://trakt.tv/shows/{self.ids.slug}" + if self.trailer is not None: + default["trailer"] = self.trailer + if self.summary is not None: + default["summary"] = self.summary return default