Exemple #1
0
    async def async_browse_media(self, media_content_type=None, media_content_id=None):
        """Implement the websocket media browsing helper."""

        if self._browse_media_domain  == "kodi":
            component = self.hass.data[self._browse_media_domain]

            for item in component:
                if self._browse_media_conf_entry == item:
                    kodi_data = component[item]

                    if media_content_type in [None, "library"]:
                        return await self.hass.async_add_executor_job(library_payload, kodi_data["kodi"])

                    payload = {
                        "search_type": media_content_type,
                        "search_id": media_content_id,
                    }
                    response = await build_item_response(kodi_data["kodi"], payload)
                    if response is None:
                        raise BrowseError(
                            f"Media not found: {media_content_type} / {media_content_id}"
                        )
                    return response
            raise BrowseError(
                f"Entry not found: {self._browse_media_conf_entry}"
            )
        raise BrowseError(
            f"Domain not supported: {self._browse_media_domain}"
        )
    async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
        """Resolve a media item to a playable item."""
        _, camera_id, event_id = async_parse_identifier(item)

        data: dict = self.hass.data[self.domain]
        entry: dict = data.get(camera_id) if camera_id else None
        base: ReolinkBase = entry.get(BASE) if entry else None
        if not base:
            raise BrowseError("Camera does not exist.")

        file = unquote_plus(event_id)
        if not file:
            raise BrowseError("Event does not exist.")
        _LOGGER.debug("file = %s", file)

        url = await base.api.get_vod_source(file)
        _LOGGER.debug("Load VOD %s", url)
        stream = create_stream(self.hass, url)
        stream.add_provider("hls", timeout=3600)
        url: str = stream.endpoint_url("hls")
        # the media browser seems to have a problem with the master_playlist
        # ( it does not load the referenced playlist ) so we will just
        # force the reference playlist instead, this seems to work
        # though technically wrong
        url = url.replace("master_", "")
        _LOGGER.debug("Proxy %s", url)
        return PlayMedia(url, MIME_TYPE)
Exemple #3
0
    async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
        """Resolve selected Radio station to a streaming URL."""
        station = await self.radios.station(uuid=item.identifier)
        if not station:
            raise BrowseError("Radio station is no longer available")

        if not (mime_type := self._async_get_station_mime_type(station)):
            raise BrowseError("Could not determine stream type of radio station")
Exemple #4
0
    def _browse_media(self, source: str, camera_id: str,
                      event_id: int) -> BrowseMediaSource:
        """Browse media."""
        if camera_id and camera_id not in self.events:
            raise BrowseError("Camera does not exist.")

        if event_id and event_id not in self.events[camera_id]:
            raise BrowseError("Event does not exist.")

        return self._build_item_response(source, camera_id, event_id)
Exemple #5
0
    def _browse_media(self, source_dir_id, location):
        """Browse media."""
        full_path = Path(self.hass.config.path("media", location))

        if not full_path.exists():
            if location == "":
                raise BrowseError("Media directory does not exist.")
            raise BrowseError("Path does not exist.")

        if not full_path.is_dir():
            raise BrowseError("Path is not a directory.")

        return self._build_item_response(source_dir_id, full_path)
Exemple #6
0
    async def async_browse_media(self,
                                 item: MediaSourceItem) -> BrowseMediaSource:
        """Browse media."""
        dms_data = get_domain_data(self.hass)
        if not dms_data.sources:
            raise BrowseError("No sources have been configured")

        source_id, media_id = _parse_identifier(item)
        LOGGER.debug("Browsing for %s / %s", source_id, media_id)

        if not source_id and len(dms_data.sources) > 1:
            # Browsing the root of dlna_dms with more than one server, return
            # all known servers.
            base = BrowseMediaSource(
                domain=DOMAIN,
                identifier="",
                media_class=MEDIA_CLASS_DIRECTORY,
                media_content_type=MEDIA_TYPE_CHANNELS,
                title=self.name,
                can_play=False,
                can_expand=True,
                children_media_class=MEDIA_CLASS_CHANNEL,
            )

            base.children = [
                BrowseMediaSource(
                    domain=DOMAIN,
                    identifier=
                    f"{source_id}/{PATH_OBJECT_ID_FLAG}{ROOT_OBJECT_ID}",
                    media_class=MEDIA_CLASS_CHANNEL,
                    media_content_type=MEDIA_TYPE_CHANNEL,
                    title=source.name,
                    can_play=False,
                    can_expand=True,
                    thumbnail=source.icon,
                ) for source_id, source in dms_data.sources.items()
            ]

            return base

        if not source_id:
            # No source specified, default to the first registered
            source_id = next(iter(dms_data.sources))

        try:
            source = dms_data.sources[source_id]
        except KeyError as err:
            raise BrowseError(f"Unknown source ID: {source_id}") from err

        return await source.async_browse_media(media_id)
Exemple #7
0
def _media_mime_type(media_item: dict[str, Any]) -> str:
    """Return the mime type of a media item."""
    if not media_item[ITEM_KEY_MEDIA_SOURCES]:
        raise BrowseError(
            "Unable to determine mime type for item without media source")

    media_source = media_item[ITEM_KEY_MEDIA_SOURCES][0]
    path = media_source[MEDIA_SOURCE_KEY_PATH]
    mime_type, _ = mimetypes.guess_type(path)

    if mime_type is not None:
        return mime_type

    raise BrowseError(f"Unable to determine mime type for path {path}")
    async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
        """Return media for the specified level of the directory tree.

        The top level is the root that contains devices. Inside each device are
        media for events for that device.
        """
        media_id: MediaId | None = parse_media_id(item.identifier)
        _LOGGER.debug(
            "Browsing media for identifier=%s, media_id=%s", item.identifier, media_id
        )
        devices = await self.devices()
        if media_id is None:
            # Browse the root and return child devices
            browse_root = _browse_root()
            browse_root.children = []
            for device_id, child_device in devices.items():
                browse_root.children.append(
                    _browse_device(MediaId(device_id), child_device)
                )
            return browse_root

        # Browse either a device or events within a device
        if not (device := devices.get(media_id.device_id)):
            raise BrowseError(
                "Unable to find device with identiifer: %s" % item.identifier
            )
    def _browse_media(self, iteminfo: ItemInfo):
        # iteminfo = (entry_id, target_dir, path)

        if not iteminfo.path.exists():
            raise BrowseError("Path does not exist.")

        return self._build_item_response(iteminfo, iteminfo.path)
Exemple #10
0
    async def async_browse_media(self,
                                 media_content_type=None,
                                 media_content_id=None):
        """Implement the websocket media browsing helper."""
        is_internal = is_internal_request(self.hass)

        def _get_thumbnail_url(media_content_type,
                               media_content_id,
                               media_image_id=None):
            if is_internal:
                if media_content_type == MEDIA_TYPE_APP and media_content_id:
                    return self.coordinator.roku.app_icon_url(media_content_id)
                return None

            return self.get_browse_image_url(media_content_type,
                                             media_content_id, media_image_id)

        if media_content_type in [None, "library"]:
            return library_payload(self.coordinator, _get_thumbnail_url)

        payload = {
            "search_type": media_content_type,
            "search_id": media_content_id,
        }
        response = build_item_response(self.coordinator, payload,
                                       _get_thumbnail_url)

        if response is None:
            raise BrowseError(
                f"Media not found: {media_content_type} / {media_content_id}")

        return response
Exemple #11
0
    async def async_browse_media(
        self,
        item: MediaSourceItem,
    ) -> BrowseMediaSource:
        """Return media."""
        radios = self.radios

        if radios is None:
            raise BrowseError("Radio Browser not initialized")

        return BrowseMediaSource(
            domain=DOMAIN,
            identifier=None,
            media_class=MEDIA_CLASS_CHANNEL,
            media_content_type=MEDIA_TYPE_MUSIC,
            title=self.entry.title,
            can_play=False,
            can_expand=True,
            children_media_class=MEDIA_CLASS_DIRECTORY,
            children=[
                *await self._async_build_popular(radios, item),
                *await self._async_build_by_tag(radios, item),
                *await self._async_build_by_language(radios, item),
                *await self._async_build_by_country(radios, item),
            ],
        )
Exemple #12
0
def library_payload(media_library, get_thumbnail_url=None):
    """
    Create response payload to describe contents of a specific library.

    Used by async_browse_media.
    """
    if not media_library.browse_by_idstring(
            "tracks",
            "",
            max_items=1,
    ):
        raise BrowseError("Local library not found")

    children = []
    for item in media_library.browse():
        try:
            children.append(item_payload(item, get_thumbnail_url))
        except UnknownMediaType:
            pass

    return BrowseMedia(
        title="Music Library",
        media_class=MEDIA_CLASS_DIRECTORY,
        media_content_id="library",
        media_content_type="library",
        can_play=False,
        can_expand=True,
        children=children,
    )
Exemple #13
0
 def _provider_item(self,
                    provider_domain: str,
                    params: str | None = None) -> BrowseMediaSource:
     """Return provider item."""
     manager: SpeechManager = self.hass.data[DOMAIN]
     if (provider := manager.providers.get(provider_domain)) is None:
         raise BrowseError("Unknown provider")
    async def async_browse_media(self,
                                 hass,
                                 channel_list,
                                 media_content_type=None,
                                 media_content_id=None):
        """Implement the websocket media browsing helper."""
        if media_content_id not in (None, "root", "channels"):
            raise BrowseError(
                f"Media not found: {media_content_type} / {media_content_id}")

        channellist = await self._async_prepareChannels(hass, channel_list)
        channels = [
            BrowseMedia(
                title=channel["title"],
                media_class=MEDIA_CLASS_TV_SHOW,
                media_content_id=channel["channelName"],
                media_content_type=DOMAINBROWSER,
                can_play=True,
                can_expand=False,
                thumbnail=channel["thumbnail"],
            ) for channel in channellist
        ]

        library_info = BrowseMedia(
            title=self._config.name,
            media_content_id="root",
            media_content_type="library",
            media_class=MEDIA_CLASS_DIRECTORY,
            can_play=False,
            can_expand=True,
            children=channels,
        )

        return library_info
    async def async_browse_media(self,
                                 media_content_type=None,
                                 media_content_id=None):
        """Implement the websocket media browsing helper."""
        if media_content_id not in (None, ""):
            raise BrowseError(
                f"Media not found: {media_content_type} / {media_content_id}")

        return BrowseMedia(
            title="Channels",
            media_class=MEDIA_CLASS_DIRECTORY,
            media_content_id="",
            media_content_type=MEDIA_TYPE_CHANNELS,
            can_play=False,
            can_expand=True,
            children=[
                BrowseMedia(
                    title=channel,
                    media_class=MEDIA_CLASS_CHANNEL,
                    media_content_id=channel,
                    media_content_type=MEDIA_TYPE_CHANNEL,
                    can_play=True,
                    can_expand=False,
                ) for channel in self._channels.values()
            ],
        )
Exemple #16
0
    async def async_browse_media(self,
                                 media_content_type=None,
                                 media_content_id=None):
        """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"]:
            return await library_payload()

        payload = {
            "search_type": media_content_type,
            "search_id": media_content_id,
        }

        response = await build_item_response(self._kodi, payload,
                                             _get_thumbnail_url)
        if response is None:
            raise BrowseError(
                f"Media not found: {media_content_type} / {media_content_id}")
        return response
    async def async_browse_media(self, media_content_type=None, media_content_id=None):
        """Implement the websocket media browsing helper."""
        if media_content_id not in (None, "root"):
            raise BrowseError(
                f"Media not found: {media_content_type} / {media_content_id}"
            )

        presets = self._state.get_preset_details()

        radio = [
            BrowseMedia(
                title=preset.name,
                media_class=MEDIA_CLASS_MUSIC,
                media_content_id=f"preset:{preset.index}",
                media_content_type=MEDIA_TYPE_MUSIC,
                can_play=True,
                can_expand=False,
            )
            for preset in presets.values()
        ]

        root = BrowseMedia(
            title="Root",
            media_class=MEDIA_CLASS_DIRECTORY,
            media_content_id="root",
            media_content_type="library",
            can_play=False,
            can_expand=True,
            children=radio,
        )

        return root
    def _get_stream_url(self, media_item: dict[str, Any]) -> str:
        """Return the stream URL for a media item."""
        media_type = media_item[ITEM_KEY_MEDIA_TYPE]

        if media_type == MEDIA_TYPE_AUDIO:
            return self._get_audio_stream_url(media_item)

        raise BrowseError(f"Unsupported media type {media_type}")
Exemple #19
0
    async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
        """Return media."""
        try:
            source, camera_id, event_id = async_parse_identifier(item)
        except Unresolvable as err:
            raise BrowseError(str(err)) from err

        return self._browse_media(source, camera_id, event_id)
    def _browse_media(self, source_dir_id: str | None,
                      location: str) -> BrowseMediaSource:
        """Browse media."""

        # If only one media dir is configured, use that as the local media root
        if source_dir_id is None and len(self.hass.config.media_dirs) == 1:
            source_dir_id = list(self.hass.config.media_dirs)[0]

        # Multiple folder, root is requested
        if source_dir_id is None:
            if location:
                raise BrowseError("Folder not found.")

            base = BrowseMediaSource(
                domain=DOMAIN,
                identifier="",
                media_class=MEDIA_CLASS_DIRECTORY,
                media_content_type=None,
                title=self.name,
                can_play=False,
                can_expand=True,
                children_media_class=MEDIA_CLASS_DIRECTORY,
            )

            base.children = [
                self._browse_media(source_dir_id, "")
                for source_dir_id in self.hass.config.media_dirs
            ]

            return base

        full_path = Path(self.hass.config.media_dirs[source_dir_id], location)

        if not full_path.exists():
            if location == "":
                raise BrowseError("Media directory does not exist.")
            raise BrowseError("Path does not exist.")

        if not full_path.is_dir():
            raise BrowseError("Path is not a directory.")

        result = self._build_item_response(source_dir_id, full_path)
        if not result:
            raise BrowseError("Unknown source directory.")
        return result
    async def _build_library(self, library: dict[str, Any],
                             include_children: bool) -> BrowseMediaSource:
        """Return a single library as a browsable media source."""
        collection_type = library[ITEM_KEY_COLLECTION_TYPE]

        if collection_type == COLLECTION_TYPE_MUSIC:
            return await self._build_music_library(library, include_children)

        raise BrowseError(f"Unsupported collection type {collection_type}")
Exemple #22
0
    async def async_browse_media(self, media_content_type=None, media_content_id=None):
        """Implement the websocket media browsing helper."""
        if not self._tv.on:
            raise BrowseError("Can't browse when tv is turned off")

        if media_content_id in (None, ""):
            return await self.async_browse_media_root()
        path = media_content_id.partition("/")
        if path[0] == "channels":
            return await self.async_browse_media_channels(True)
        if path[0] == "applications":
            return await self.async_browse_media_applications(True)
        if path[0] == "favorite_lists":
            return await self.async_browse_media_favorite_lists(True)
        if path[0] == "favorites":
            return await self.async_browse_media_favorites(path[2], True)

        raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}")
Exemple #23
0
    async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
        """Return media."""
        try:
            source_dir_id, location = self.async_parse_identifier(item)
        except Unresolvable as err:
            raise BrowseError(str(err)) from err

        result = await self.hass.async_add_executor_job(
            self._browse_media, source_dir_id, location
        )
        return result
Exemple #24
0
    def _get_stream_url(self, media_item: dict[str, Any]) -> str:
        """Return the stream URL for a media item."""
        media_type = media_item[ITEM_KEY_MEDIA_TYPE]
        item_id = media_item[ITEM_KEY_ID]

        if media_type == MEDIA_TYPE_AUDIO:
            return self.api.audio_url(item_id)  # type: ignore[no-any-return]
        if media_type == MEDIA_TYPE_VIDEO:
            return self.api.video_url(item_id)  # type: ignore[no-any-return]

        raise BrowseError(f"Unsupported media type {media_type}")
Exemple #25
0
    async def async_browse_media(
            self,
            item: MediaSourceItem,
            media_types: Tuple[str] = MEDIA_MIME_TYPES) -> BrowseMediaSource:
        """Return media."""
        try:
            source_dir_id, location = async_parse_identifier(item)
        except Unresolvable as err:
            raise BrowseError(str(err)) from err

        return await self.hass.async_add_executor_job(self._browse_media,
                                                      source_dir_id, location)
Exemple #26
0
    async def async_browse_media(
        self,
        item: MediaSourceItem,
    ) -> BrowseMediaSource:
        """Return media."""
        if item.identifier:
            raise BrowseError("Unknown item")

        can_stream_hls = "stream" in self.hass.config.components

        # Root. List cameras.
        component: EntityComponent = self.hass.data[DOMAIN]
        children = []
        not_shown = 0
        for camera in component.entities:
            camera = cast(Camera, camera)
            stream_type = camera.frontend_stream_type

            if stream_type is None:
                content_type = camera.content_type

            elif can_stream_hls and stream_type == StreamType.HLS:
                content_type = FORMAT_CONTENT_TYPE[HLS_PROVIDER]

            else:
                not_shown += 1
                continue

            children.append(
                BrowseMediaSource(
                    domain=DOMAIN,
                    identifier=camera.entity_id,
                    media_class=MEDIA_CLASS_VIDEO,
                    media_content_type=content_type,
                    title=camera.name,
                    thumbnail=f"/api/camera_proxy/{camera.entity_id}",
                    can_play=True,
                    can_expand=False,
                ))

        return BrowseMediaSource(
            domain=DOMAIN,
            identifier=None,
            media_class=MEDIA_CLASS_APP,
            media_content_type="",
            title="Camera",
            can_play=False,
            can_expand=True,
            children_media_class=MEDIA_CLASS_VIDEO,
            children=children,
            not_shown=not_shown,
        )
    async def async_browse_media(
            self,
            item: MediaSourceItem,
            media_types: Tuple[str] = MEDIA_MIME_TYPES) -> BrowseMediaSource:
        """Browse media."""

        try:
            source, camera_id, event_id = async_parse_identifier(item)
        except Unresolvable as err:
            raise BrowseError(str(err)) from err

        _LOGGER.debug("Browsing %s, %s, %s", source, camera_id, event_id)

        if camera_id and camera_id not in self.cache:
            raise BrowseError("Camera does not exist.")

        if (event_id and not "/" in event_id
                and event_id not in self.cache[camera_id]["playback_events"]):
            raise BrowseError("Event does not exist.")

        return await self._async_browse_media(source, camera_id, event_id,
                                              False)
 async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
     """Resolve media to a url."""
     media = await async_parse_uri(item.identifier)
     for mass_instance in self.hass.data[DOMAIN].values():
         if mass_instance.server_id != media["mass_server_id"]:
             continue
         if media["media_type"] in ["track", "radio"]:
             url = f"{mass_instance.base_url}/stream_media/"
             url += f'{media["media_type"]}/{media["provider"]}/{media["item_id"]}'
             return PlayMedia(url, "audio/flac")
         else:
             return PlayMedia(item.identifier, "application/musicassistant")
     raise BrowseError("Invalid Music Assistance instance")
Exemple #29
0
async def test_websocket_browse_media(hass, hass_ws_client):
    """Test browse media websocket."""
    assert await async_setup_component(hass, const.DOMAIN, {})
    await hass.async_block_till_done()

    client = await hass_ws_client(hass)

    media = media_source.models.BrowseMediaSource(
        domain=const.DOMAIN,
        identifier="/media",
        title="Local Media",
        media_class=MEDIA_CLASS_DIRECTORY,
        media_content_type="listing",
        can_play=False,
        can_expand=True,
    )

    with patch(
        "homeassistant.components.media_source.async_browse_media",
        return_value=media,
    ):
        await client.send_json(
            {
                "id": 1,
                "type": "media_source/browse_media",
            }
        )

        msg = await client.receive_json()

    assert msg["success"]
    assert msg["id"] == 1
    assert media.as_dict() == msg["result"]

    with patch(
        "homeassistant.components.media_source.async_browse_media",
        side_effect=BrowseError("test"),
    ):
        await client.send_json(
            {
                "id": 2,
                "type": "media_source/browse_media",
                "media_content_id": "invalid",
            }
        )

        msg = await client.receive_json()

    assert not msg["success"]
    assert msg["error"]["code"] == "browse_media_failed"
    assert msg["error"]["message"] == "test"
    async def async_browse_media(self, media_content_type=None, media_content_id=None):
        """Implement the websocket media browsing helper."""
        if media_content_type in [None, "library"]:
            return await self._hass.async_add_executor_job(library_payload, self)

        payload = {
            "search_type": media_content_type,
            "search_id": media_content_id,
        }
        response = await build_item_response(self, payload)
        if response is None:
            raise BrowseError(
                f"Media not found: {media_content_type} / {media_content_id}"
            )
        return response