Proxy Plex thumbnail images in media browser (#66702)

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
jjlawren 2022-02-19 13:25:33 -06:00 committed by GitHub
parent 88b7a9fccc
commit 69ce03465d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 97 additions and 34 deletions

View file

@ -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

View file

@ -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

View file

@ -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():

View file

@ -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}"

View file

@ -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)

View 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)