Proxy Plex thumbnail images in media browser (#66702)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
88b7a9fccc
commit
69ce03465d
6 changed files with 97 additions and 34 deletions
|
@ -919,6 +919,7 @@ omit =
|
|||
homeassistant/components/plaato/entity.py
|
||||
homeassistant/components/plaato/sensor.py
|
||||
homeassistant/components/plex/media_player.py
|
||||
homeassistant/components/plex/view.py
|
||||
homeassistant/components/plum_lightpad/light.py
|
||||
homeassistant/components/pocketcasts/sensor.py
|
||||
homeassistant/components/point/__init__.py
|
||||
|
|
|
@ -999,25 +999,7 @@ class MediaPlayerEntity(Entity):
|
|||
|
||||
async def _async_fetch_image(self, url: str) -> tuple[bytes | None, str | None]:
|
||||
"""Retrieve an image."""
|
||||
content, content_type = (None, None)
|
||||
websession = async_get_clientsession(self.hass)
|
||||
with suppress(asyncio.TimeoutError), async_timeout.timeout(10):
|
||||
response = await websession.get(url)
|
||||
if response.status == HTTPStatus.OK:
|
||||
content = await response.read()
|
||||
if content_type := response.headers.get(CONTENT_TYPE):
|
||||
content_type = content_type.split(";")[0]
|
||||
|
||||
if content is None:
|
||||
url_parts = URL(url)
|
||||
if url_parts.user is not None:
|
||||
url_parts = url_parts.with_user("xxxx")
|
||||
if url_parts.password is not None:
|
||||
url_parts = url_parts.with_password("xxxxxxxx")
|
||||
url = str(url_parts)
|
||||
_LOGGER.warning("Error retrieving proxied image from %s", url)
|
||||
|
||||
return content, content_type
|
||||
return await async_fetch_image(_LOGGER, self.hass, url)
|
||||
|
||||
def get_browse_image_url(
|
||||
self,
|
||||
|
@ -1205,3 +1187,28 @@ async def websocket_browse_media(hass, connection, msg):
|
|||
_LOGGER.warning("Browse Media should use new BrowseMedia class")
|
||||
|
||||
connection.send_result(msg["id"], payload)
|
||||
|
||||
|
||||
async def async_fetch_image(
|
||||
logger: logging.Logger, hass: HomeAssistant, url: str
|
||||
) -> tuple[bytes | None, str | None]:
|
||||
"""Retrieve an image."""
|
||||
content, content_type = (None, None)
|
||||
websession = async_get_clientsession(hass)
|
||||
with suppress(asyncio.TimeoutError), async_timeout.timeout(10):
|
||||
response = await websession.get(url)
|
||||
if response.status == HTTPStatus.OK:
|
||||
content = await response.read()
|
||||
if content_type := response.headers.get(CONTENT_TYPE):
|
||||
content_type = content_type.split(";")[0]
|
||||
|
||||
if content is None:
|
||||
url_parts = URL(url)
|
||||
if url_parts.user is not None:
|
||||
url_parts = url_parts.with_user("xxxx")
|
||||
if url_parts.password is not None:
|
||||
url_parts = url_parts.with_password("xxxxxxxx")
|
||||
url = str(url_parts)
|
||||
logger.warning("Error retrieving proxied image from %s", url)
|
||||
|
||||
return content, content_type
|
||||
|
|
|
@ -48,6 +48,7 @@ from .errors import ShouldUpdateConfigEntry
|
|||
from .media_browser import browse_media
|
||||
from .server import PlexServer
|
||||
from .services import async_setup_services
|
||||
from .view import PlexImageView
|
||||
|
||||
_LOGGER = logging.getLogger(__package__)
|
||||
|
||||
|
@ -84,6 +85,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
|
||||
await async_setup_services(hass)
|
||||
|
||||
hass.http.register_view(PlexImageView())
|
||||
|
||||
gdm = hass.data[PLEX_DOMAIN][GDM_SCANNER] = GDM()
|
||||
|
||||
def gdm_scan():
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
"""Support to interface with the Plex API."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from homeassistant.components.media_player import BrowseMedia
|
||||
|
@ -73,7 +75,15 @@ def browse_media( # noqa: C901
|
|||
"can_expand": item.type in EXPANDABLES,
|
||||
}
|
||||
if hasattr(item, "thumbUrl"):
|
||||
payload["thumbnail"] = item.thumbUrl
|
||||
plex_server.thumbnail_cache.setdefault(str(item.ratingKey), item.thumbUrl)
|
||||
if is_internal:
|
||||
thumbnail = item.thumbUrl
|
||||
else:
|
||||
thumbnail = get_proxy_image_url(
|
||||
plex_server.machine_identifier,
|
||||
item.ratingKey,
|
||||
)
|
||||
payload["thumbnail"] = thumbnail
|
||||
|
||||
return BrowseMedia(**payload)
|
||||
|
||||
|
@ -321,3 +331,11 @@ def station_payload(station):
|
|||
can_play=True,
|
||||
can_expand=False,
|
||||
)
|
||||
|
||||
|
||||
def get_proxy_image_url(
|
||||
server_id: str,
|
||||
media_content_id: str,
|
||||
) -> str:
|
||||
"""Generate an url for a Plex media browser image."""
|
||||
return f"/api/plex_image_proxy/{server_id}/{media_content_id}"
|
||||
|
|
|
@ -585,17 +585,3 @@ class PlexMediaPlayer(MediaPlayerEntity):
|
|||
media_content_type,
|
||||
media_content_id,
|
||||
)
|
||||
|
||||
async def async_get_browse_image(
|
||||
self,
|
||||
media_content_type: str,
|
||||
media_content_id: str,
|
||||
media_image_id: str | None = None,
|
||||
) -> tuple[bytes | None, str | None]:
|
||||
"""Get media image from Plex server."""
|
||||
image_url = self.plex_server.thumbnail_cache.get(media_content_id)
|
||||
if image_url:
|
||||
result = await self._async_fetch_image(image_url)
|
||||
return result
|
||||
|
||||
return (None, None)
|
||||
|
|
48
homeassistant/components/plex/view.py
Normal file
48
homeassistant/components/plex/view.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
"""Implement a view to provide proxied Plex thumbnails to the media browser."""
|
||||
from __future__ import annotations
|
||||
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp.hdrs import CACHE_CONTROL
|
||||
from aiohttp.typedefs import LooseHeaders
|
||||
|
||||
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
|
||||
from homeassistant.components.media_player import async_fetch_image
|
||||
|
||||
from .const import DOMAIN as PLEX_DOMAIN, SERVERS
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PlexImageView(HomeAssistantView):
|
||||
"""Media player view to serve a Plex image."""
|
||||
|
||||
name = "api:plex:image"
|
||||
url = "/api/plex_image_proxy/{server_id}/{media_content_id}"
|
||||
|
||||
async def get( # pylint: disable=no-self-use
|
||||
self,
|
||||
request: web.Request,
|
||||
server_id: str,
|
||||
media_content_id: str,
|
||||
) -> web.Response:
|
||||
"""Start a get request."""
|
||||
if not request[KEY_AUTHENTICATED]:
|
||||
return web.Response(status=HTTPStatus.UNAUTHORIZED)
|
||||
|
||||
hass = request.app["hass"]
|
||||
if (server := hass.data[PLEX_DOMAIN][SERVERS].get(server_id)) is None:
|
||||
return web.Response(status=HTTPStatus.NOT_FOUND)
|
||||
|
||||
if (image_url := server.thumbnail_cache.get(media_content_id)) is None:
|
||||
return web.Response(status=HTTPStatus.NOT_FOUND)
|
||||
|
||||
data, content_type = await async_fetch_image(_LOGGER, hass, image_url)
|
||||
|
||||
if data is None:
|
||||
return web.Response(status=HTTPStatus.SERVICE_UNAVAILABLE)
|
||||
|
||||
headers: LooseHeaders = {CACHE_CONTROL: "max-age=3600"}
|
||||
return web.Response(body=data, content_type=content_type, headers=headers)
|
Loading…
Add table
Reference in a new issue