core/homeassistant/components/media_player/browse_media.py

113 lines
3.5 KiB
Python

"""Browse media features for media player."""
from __future__ import annotations
from datetime import timedelta
import logging
from urllib.parse import quote
import yarl
from homeassistant.components.http.auth import async_sign_path
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.network import get_url, is_hass_url
from .const import CONTENT_AUTH_EXPIRY_TIME, MEDIA_CLASS_DIRECTORY
@callback
def async_process_play_media_url(hass: HomeAssistant, media_content_id: str) -> str:
"""Update a media URL with authentication if it points at Home Assistant."""
if media_content_id[0] != "/" and not is_hass_url(hass, media_content_id):
return media_content_id
parsed = yarl.URL(media_content_id)
if parsed.query:
logging.getLogger(__name__).debug(
"Not signing path for content with query param"
)
else:
signed_path = async_sign_path(
hass,
quote(parsed.path),
timedelta(seconds=CONTENT_AUTH_EXPIRY_TIME),
)
media_content_id = str(parsed.join(yarl.URL(signed_path)))
# prepend external URL
if media_content_id[0] == "/":
media_content_id = f"{get_url(hass)}{media_content_id}"
return media_content_id
class BrowseMedia:
"""Represent a browsable media file."""
def __init__(
self,
*,
media_class: str,
media_content_id: str,
media_content_type: str,
title: str,
can_play: bool,
can_expand: bool,
children: list[BrowseMedia] | None = None,
children_media_class: str | None = None,
thumbnail: str | None = None,
) -> None:
"""Initialize browse media item."""
self.media_class = media_class
self.media_content_id = media_content_id
self.media_content_type = media_content_type
self.title = title
self.can_play = can_play
self.can_expand = can_expand
self.children = children
self.children_media_class = children_media_class
self.thumbnail = thumbnail
def as_dict(self, *, parent: bool = True) -> dict:
"""Convert Media class to browse media dictionary."""
if self.children_media_class is None:
self.calculate_children_class()
response = {
"title": self.title,
"media_class": self.media_class,
"media_content_type": self.media_content_type,
"media_content_id": self.media_content_id,
"can_play": self.can_play,
"can_expand": self.can_expand,
"children_media_class": self.children_media_class,
"thumbnail": self.thumbnail,
}
if not parent:
return response
if self.children:
response["children"] = [
child.as_dict(parent=False) for child in self.children
]
else:
response["children"] = []
return response
def calculate_children_class(self) -> None:
"""Count the children media classes and calculate the correct class."""
if self.children is None or len(self.children) == 0:
return
self.children_media_class = MEDIA_CLASS_DIRECTORY
proposed_class = self.children[0].media_class
if all(child.media_class == proposed_class for child in self.children):
self.children_media_class = proposed_class
def __repr__(self) -> str:
"""Return representation of browse media."""
return f"<BrowseMedia {self.title} ({self.media_class})>"