mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 16:27:08 +00:00
Add image proxy to Kodi media browser (#47315)
This commit is contained in:
parent
09c51da3a2
commit
467bd91c80
@ -1,5 +1,5 @@
|
|||||||
"""Support for media browsing."""
|
"""Support for media browsing."""
|
||||||
from contextlib import suppress
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from homeassistant.components.media_player import BrowseError, BrowseMedia
|
from homeassistant.components.media_player import BrowseError, BrowseMedia
|
||||||
@ -59,121 +59,20 @@ class UnknownMediaType(BrowseError):
|
|||||||
"""Unknown media type."""
|
"""Unknown media type."""
|
||||||
|
|
||||||
|
|
||||||
async def build_item_response(media_library, payload):
|
async def build_item_response(media_library, payload, get_thumbnail_url=None):
|
||||||
"""Create response payload for the provided media query."""
|
"""Create response payload for the provided media query."""
|
||||||
search_id = payload["search_id"]
|
search_id = payload["search_id"]
|
||||||
search_type = payload["search_type"]
|
search_type = payload["search_type"]
|
||||||
|
|
||||||
thumbnail = None
|
_, title, media = await get_media_info(media_library, search_id, search_type)
|
||||||
title = None
|
thumbnail = await get_thumbnail_url(search_type, search_id)
|
||||||
media = None
|
|
||||||
|
|
||||||
properties = ["thumbnail"]
|
|
||||||
if search_type == MEDIA_TYPE_ALBUM:
|
|
||||||
if search_id:
|
|
||||||
album = await media_library.get_album_details(
|
|
||||||
album_id=int(search_id), properties=properties
|
|
||||||
)
|
|
||||||
thumbnail = media_library.thumbnail_url(
|
|
||||||
album["albumdetails"].get("thumbnail")
|
|
||||||
)
|
|
||||||
title = album["albumdetails"]["label"]
|
|
||||||
media = await media_library.get_songs(
|
|
||||||
album_id=int(search_id),
|
|
||||||
properties=[
|
|
||||||
"albumid",
|
|
||||||
"artist",
|
|
||||||
"duration",
|
|
||||||
"album",
|
|
||||||
"thumbnail",
|
|
||||||
"track",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
media = media.get("songs")
|
|
||||||
else:
|
|
||||||
media = await media_library.get_albums(properties=properties)
|
|
||||||
media = media.get("albums")
|
|
||||||
title = "Albums"
|
|
||||||
|
|
||||||
elif search_type == MEDIA_TYPE_ARTIST:
|
|
||||||
if search_id:
|
|
||||||
media = await media_library.get_albums(
|
|
||||||
artist_id=int(search_id), properties=properties
|
|
||||||
)
|
|
||||||
media = media.get("albums")
|
|
||||||
artist = await media_library.get_artist_details(
|
|
||||||
artist_id=int(search_id), properties=properties
|
|
||||||
)
|
|
||||||
thumbnail = media_library.thumbnail_url(
|
|
||||||
artist["artistdetails"].get("thumbnail")
|
|
||||||
)
|
|
||||||
title = artist["artistdetails"]["label"]
|
|
||||||
else:
|
|
||||||
media = await media_library.get_artists(properties)
|
|
||||||
media = media.get("artists")
|
|
||||||
title = "Artists"
|
|
||||||
|
|
||||||
elif search_type == "library_music":
|
|
||||||
library = {MEDIA_TYPE_ALBUM: "Albums", MEDIA_TYPE_ARTIST: "Artists"}
|
|
||||||
media = [{"label": name, "type": type_} for type_, name in library.items()]
|
|
||||||
title = "Music Library"
|
|
||||||
|
|
||||||
elif search_type == MEDIA_TYPE_MOVIE:
|
|
||||||
media = await media_library.get_movies(properties)
|
|
||||||
media = media.get("movies")
|
|
||||||
title = "Movies"
|
|
||||||
|
|
||||||
elif search_type == MEDIA_TYPE_TVSHOW:
|
|
||||||
if search_id:
|
|
||||||
media = await media_library.get_seasons(
|
|
||||||
tv_show_id=int(search_id),
|
|
||||||
properties=["thumbnail", "season", "tvshowid"],
|
|
||||||
)
|
|
||||||
media = media.get("seasons")
|
|
||||||
tvshow = await media_library.get_tv_show_details(
|
|
||||||
tv_show_id=int(search_id), properties=properties
|
|
||||||
)
|
|
||||||
thumbnail = media_library.thumbnail_url(
|
|
||||||
tvshow["tvshowdetails"].get("thumbnail")
|
|
||||||
)
|
|
||||||
title = tvshow["tvshowdetails"]["label"]
|
|
||||||
else:
|
|
||||||
media = await media_library.get_tv_shows(properties)
|
|
||||||
media = media.get("tvshows")
|
|
||||||
title = "TV Shows"
|
|
||||||
|
|
||||||
elif search_type == MEDIA_TYPE_SEASON:
|
|
||||||
tv_show_id, season_id = search_id.split("/", 1)
|
|
||||||
media = await media_library.get_episodes(
|
|
||||||
tv_show_id=int(tv_show_id),
|
|
||||||
season_id=int(season_id),
|
|
||||||
properties=["thumbnail", "tvshowid", "seasonid"],
|
|
||||||
)
|
|
||||||
media = media.get("episodes")
|
|
||||||
if media:
|
|
||||||
season = await media_library.get_season_details(
|
|
||||||
season_id=int(media[0]["seasonid"]), properties=properties
|
|
||||||
)
|
|
||||||
thumbnail = media_library.thumbnail_url(
|
|
||||||
season["seasondetails"].get("thumbnail")
|
|
||||||
)
|
|
||||||
title = season["seasondetails"]["label"]
|
|
||||||
|
|
||||||
elif search_type == MEDIA_TYPE_CHANNEL:
|
|
||||||
media = await media_library.get_channels(
|
|
||||||
channel_group_id="alltv",
|
|
||||||
properties=["thumbnail", "channeltype", "channel", "broadcastnow"],
|
|
||||||
)
|
|
||||||
media = media.get("channels")
|
|
||||||
title = "Channels"
|
|
||||||
|
|
||||||
if media is None:
|
if media is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
children = []
|
children = await asyncio.gather(
|
||||||
for item in media:
|
*[item_payload(item, get_thumbnail_url) for item in media]
|
||||||
with suppress(UnknownMediaType):
|
)
|
||||||
children.append(item_payload(item, media_library))
|
|
||||||
|
|
||||||
if search_type in (MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE) and search_id == "":
|
if search_type in (MEDIA_TYPE_TVSHOW, MEDIA_TYPE_MOVIE) and search_id == "":
|
||||||
children.sort(key=lambda x: x.title.replace("The ", "", 1), reverse=False)
|
children.sort(key=lambda x: x.title.replace("The ", "", 1), reverse=False)
|
||||||
@ -199,16 +98,13 @@ async def build_item_response(media_library, payload):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def item_payload(item, media_library):
|
async def item_payload(item, get_thumbnail_url=None):
|
||||||
"""
|
"""
|
||||||
Create response payload for a single media item.
|
Create response payload for a single media item.
|
||||||
|
|
||||||
Used by async_browse_media.
|
Used by async_browse_media.
|
||||||
"""
|
"""
|
||||||
title = item["label"]
|
title = item["label"]
|
||||||
thumbnail = item.get("thumbnail")
|
|
||||||
if thumbnail:
|
|
||||||
thumbnail = media_library.thumbnail_url(thumbnail)
|
|
||||||
|
|
||||||
media_class = None
|
media_class = None
|
||||||
|
|
||||||
@ -272,6 +168,12 @@ def item_payload(item, media_library):
|
|||||||
_LOGGER.debug("Unknown media type received: %s", media_content_type)
|
_LOGGER.debug("Unknown media type received: %s", media_content_type)
|
||||||
raise UnknownMediaType from err
|
raise UnknownMediaType from err
|
||||||
|
|
||||||
|
thumbnail = item.get("thumbnail")
|
||||||
|
if thumbnail is not None and get_thumbnail_url is not None:
|
||||||
|
thumbnail = await get_thumbnail_url(
|
||||||
|
media_content_type, media_content_id, thumbnail_url=thumbnail
|
||||||
|
)
|
||||||
|
|
||||||
return BrowseMedia(
|
return BrowseMedia(
|
||||||
title=title,
|
title=title,
|
||||||
media_class=media_class,
|
media_class=media_class,
|
||||||
@ -283,7 +185,7 @@ def item_payload(item, media_library):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def library_payload(media_library):
|
async def library_payload():
|
||||||
"""
|
"""
|
||||||
Create response payload to describe contents of a specific library.
|
Create response payload to describe contents of a specific library.
|
||||||
|
|
||||||
@ -305,12 +207,137 @@ def library_payload(media_library):
|
|||||||
MEDIA_TYPE_TVSHOW: "TV shows",
|
MEDIA_TYPE_TVSHOW: "TV shows",
|
||||||
MEDIA_TYPE_CHANNEL: "Channels",
|
MEDIA_TYPE_CHANNEL: "Channels",
|
||||||
}
|
}
|
||||||
for item in [{"label": name, "type": type_} for type_, name in library.items()]:
|
|
||||||
library_info.children.append(
|
library_info.children = await asyncio.gather(
|
||||||
|
*[
|
||||||
item_payload(
|
item_payload(
|
||||||
{"label": item["label"], "type": item["type"], "uri": item["type"]},
|
{
|
||||||
media_library,
|
"label": item["label"],
|
||||||
|
"type": item["type"],
|
||||||
|
"uri": item["type"],
|
||||||
|
},
|
||||||
)
|
)
|
||||||
)
|
for item in [
|
||||||
|
{"label": name, "type": type_} for type_, name in library.items()
|
||||||
|
]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
return library_info
|
return library_info
|
||||||
|
|
||||||
|
|
||||||
|
async def get_media_info(media_library, search_id, search_type):
|
||||||
|
"""Fetch media/album."""
|
||||||
|
thumbnail = None
|
||||||
|
title = None
|
||||||
|
media = None
|
||||||
|
|
||||||
|
properties = ["thumbnail"]
|
||||||
|
if search_type == MEDIA_TYPE_ALBUM:
|
||||||
|
if search_id:
|
||||||
|
album = await media_library.get_album_details(
|
||||||
|
album_id=int(search_id), properties=properties
|
||||||
|
)
|
||||||
|
thumbnail = media_library.thumbnail_url(
|
||||||
|
album["albumdetails"].get("thumbnail")
|
||||||
|
)
|
||||||
|
title = album["albumdetails"]["label"]
|
||||||
|
media = await media_library.get_songs(
|
||||||
|
album_id=int(search_id),
|
||||||
|
properties=[
|
||||||
|
"albumid",
|
||||||
|
"artist",
|
||||||
|
"duration",
|
||||||
|
"album",
|
||||||
|
"thumbnail",
|
||||||
|
"track",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
media = media.get("songs")
|
||||||
|
else:
|
||||||
|
media = await media_library.get_albums(properties=properties)
|
||||||
|
media = media.get("albums")
|
||||||
|
title = "Albums"
|
||||||
|
|
||||||
|
elif search_type == MEDIA_TYPE_ARTIST:
|
||||||
|
if search_id:
|
||||||
|
media = await media_library.get_albums(
|
||||||
|
artist_id=int(search_id), properties=properties
|
||||||
|
)
|
||||||
|
media = media.get("albums")
|
||||||
|
artist = await media_library.get_artist_details(
|
||||||
|
artist_id=int(search_id), properties=properties
|
||||||
|
)
|
||||||
|
thumbnail = media_library.thumbnail_url(
|
||||||
|
artist["artistdetails"].get("thumbnail")
|
||||||
|
)
|
||||||
|
title = artist["artistdetails"]["label"]
|
||||||
|
else:
|
||||||
|
media = await media_library.get_artists(properties)
|
||||||
|
media = media.get("artists")
|
||||||
|
title = "Artists"
|
||||||
|
|
||||||
|
elif search_type == "library_music":
|
||||||
|
library = {MEDIA_TYPE_ALBUM: "Albums", MEDIA_TYPE_ARTIST: "Artists"}
|
||||||
|
media = [{"label": name, "type": type_} for type_, name in library.items()]
|
||||||
|
title = "Music Library"
|
||||||
|
|
||||||
|
elif search_type == MEDIA_TYPE_MOVIE:
|
||||||
|
if search_id:
|
||||||
|
movie = await media_library.get_movie_details(
|
||||||
|
movie_id=int(search_id), properties=properties
|
||||||
|
)
|
||||||
|
thumbnail = media_library.thumbnail_url(
|
||||||
|
movie["moviedetails"].get("thumbnail")
|
||||||
|
)
|
||||||
|
title = movie["moviedetails"]["label"]
|
||||||
|
else:
|
||||||
|
media = await media_library.get_movies(properties)
|
||||||
|
media = media.get("movies")
|
||||||
|
title = "Movies"
|
||||||
|
|
||||||
|
elif search_type == MEDIA_TYPE_TVSHOW:
|
||||||
|
if search_id:
|
||||||
|
media = await media_library.get_seasons(
|
||||||
|
tv_show_id=int(search_id),
|
||||||
|
properties=["thumbnail", "season", "tvshowid"],
|
||||||
|
)
|
||||||
|
media = media.get("seasons")
|
||||||
|
tvshow = await media_library.get_tv_show_details(
|
||||||
|
tv_show_id=int(search_id), properties=properties
|
||||||
|
)
|
||||||
|
thumbnail = media_library.thumbnail_url(
|
||||||
|
tvshow["tvshowdetails"].get("thumbnail")
|
||||||
|
)
|
||||||
|
title = tvshow["tvshowdetails"]["label"]
|
||||||
|
else:
|
||||||
|
media = await media_library.get_tv_shows(properties)
|
||||||
|
media = media.get("tvshows")
|
||||||
|
title = "TV Shows"
|
||||||
|
|
||||||
|
elif search_type == MEDIA_TYPE_SEASON:
|
||||||
|
tv_show_id, season_id = search_id.split("/", 1)
|
||||||
|
media = await media_library.get_episodes(
|
||||||
|
tv_show_id=int(tv_show_id),
|
||||||
|
season_id=int(season_id),
|
||||||
|
properties=["thumbnail", "tvshowid", "seasonid"],
|
||||||
|
)
|
||||||
|
media = media.get("episodes")
|
||||||
|
if media:
|
||||||
|
season = await media_library.get_season_details(
|
||||||
|
season_id=int(media[0]["seasonid"]), properties=properties
|
||||||
|
)
|
||||||
|
thumbnail = media_library.thumbnail_url(
|
||||||
|
season["seasondetails"].get("thumbnail")
|
||||||
|
)
|
||||||
|
title = season["seasondetails"]["label"]
|
||||||
|
|
||||||
|
elif search_type == MEDIA_TYPE_CHANNEL:
|
||||||
|
media = await media_library.get_channels(
|
||||||
|
channel_group_id="alltv",
|
||||||
|
properties=["thumbnail", "channeltype", "channel", "broadcastnow"],
|
||||||
|
)
|
||||||
|
media = media.get("channels")
|
||||||
|
title = "Channels"
|
||||||
|
|
||||||
|
return thumbnail, title, media
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Kodi",
|
"name": "Kodi",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/kodi",
|
"documentation": "https://www.home-assistant.io/integrations/kodi",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"pykodi==0.2.1"
|
"pykodi==0.2.3"
|
||||||
],
|
],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@OnFreund",
|
"@OnFreund",
|
||||||
|
@ -3,8 +3,10 @@ from datetime import timedelta
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
import jsonrpc_base
|
import jsonrpc_base
|
||||||
|
from jsonrpc_base.jsonrpc import ProtocolError, TransportError
|
||||||
from pykodi import CannotConnectError
|
from pykodi import CannotConnectError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -61,9 +63,10 @@ from homeassistant.helpers import (
|
|||||||
entity_platform,
|
entity_platform,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.event import async_track_time_interval
|
from homeassistant.helpers.event import async_track_time_interval
|
||||||
|
from homeassistant.helpers.network import is_internal_request
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from .browse_media import build_item_response, library_payload
|
from .browse_media import build_item_response, get_media_info, library_payload
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_WS_PORT,
|
CONF_WS_PORT,
|
||||||
DATA_CONNECTION,
|
DATA_CONNECTION,
|
||||||
@ -871,16 +874,50 @@ class KodiEntity(MediaPlayerEntity):
|
|||||||
|
|
||||||
async def async_browse_media(self, media_content_type=None, media_content_id=None):
|
async def async_browse_media(self, media_content_type=None, media_content_id=None):
|
||||||
"""Implement the websocket media browsing helper."""
|
"""Implement the websocket media browsing helper."""
|
||||||
|
is_internal = is_internal_request(self.hass)
|
||||||
|
|
||||||
|
async def _get_thumbnail_url(
|
||||||
|
media_content_type,
|
||||||
|
media_content_id,
|
||||||
|
media_image_id=None,
|
||||||
|
thumbnail_url=None,
|
||||||
|
):
|
||||||
|
if is_internal:
|
||||||
|
return self._kodi.thumbnail_url(thumbnail_url)
|
||||||
|
|
||||||
|
return self.get_browse_image_url(
|
||||||
|
media_content_type,
|
||||||
|
urllib.parse.quote_plus(media_content_id),
|
||||||
|
media_image_id,
|
||||||
|
)
|
||||||
|
|
||||||
if media_content_type in [None, "library"]:
|
if media_content_type in [None, "library"]:
|
||||||
return await self.hass.async_add_executor_job(library_payload, self._kodi)
|
return await library_payload()
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"search_type": media_content_type,
|
"search_type": media_content_type,
|
||||||
"search_id": media_content_id,
|
"search_id": media_content_id,
|
||||||
}
|
}
|
||||||
response = await build_item_response(self._kodi, payload)
|
|
||||||
|
response = await build_item_response(self._kodi, payload, _get_thumbnail_url)
|
||||||
if response is None:
|
if response is None:
|
||||||
raise BrowseError(
|
raise BrowseError(
|
||||||
f"Media not found: {media_content_type} / {media_content_id}"
|
f"Media not found: {media_content_type} / {media_content_id}"
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
async def async_get_browse_image(
|
||||||
|
self, media_content_type, media_content_id, media_image_id=None
|
||||||
|
):
|
||||||
|
"""Get media image from kodi server."""
|
||||||
|
try:
|
||||||
|
image_url, _, _ = await get_media_info(
|
||||||
|
self._kodi, media_content_id, media_content_type
|
||||||
|
)
|
||||||
|
except (ProtocolError, TransportError):
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
if image_url:
|
||||||
|
return await self._async_fetch_image(image_url)
|
||||||
|
|
||||||
|
return (None, None)
|
||||||
|
@ -1476,7 +1476,7 @@ pykira==0.1.1
|
|||||||
pykmtronic==0.0.3
|
pykmtronic==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.kodi
|
# homeassistant.components.kodi
|
||||||
pykodi==0.2.1
|
pykodi==0.2.3
|
||||||
|
|
||||||
# homeassistant.components.kulersky
|
# homeassistant.components.kulersky
|
||||||
pykulersky==0.5.2
|
pykulersky==0.5.2
|
||||||
|
@ -784,7 +784,7 @@ pykira==0.1.1
|
|||||||
pykmtronic==0.0.3
|
pykmtronic==0.0.3
|
||||||
|
|
||||||
# homeassistant.components.kodi
|
# homeassistant.components.kodi
|
||||||
pykodi==0.2.1
|
pykodi==0.2.3
|
||||||
|
|
||||||
# homeassistant.components.kulersky
|
# homeassistant.components.kulersky
|
||||||
pykulersky==0.5.2
|
pykulersky==0.5.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user