def test_descriptor_to_xml(self) -> None:
        """Test descriptor to XML."""
        descriptor = didl_lite.Descriptor(id="1",
                                          name_space="ns",
                                          type="type",
                                          text="Text")
        item = didl_lite.AudioItem(
            id="0",
            parent_id="0",
            title="Audio Item Title",
            restricted="1",
            language="English",
            descriptors=[descriptor],
        )
        didl_string = didl_lite.to_xml_string(item).decode("utf-8")
        didl_el = ET.fromstring(didl_string)

        item_el = didl_el.find("./didl_lite:item", NAMESPACES)
        assert item_el is not None

        descriptor_el = item_el.find("./didl_lite:desc", NAMESPACES)
        assert descriptor_el is not None
        assert len(descriptor_el.attrib) == 3
        assert descriptor_el.attrib["id"] == "1"
        assert descriptor_el.attrib["nameSpace"] == "ns"
        assert descriptor_el.attrib["type"] == "type"
        assert descriptor_el.text == "Text"

        descriptor = didl_lite.Descriptor(id="2", name_space="ns2")
        descriptor_el = descriptor.to_xml()
        assert descriptor_el is not None
        assert len(descriptor_el.attrib) == 2
        assert descriptor_el.attrib["id"] == "2"
        assert descriptor_el.attrib["nameSpace"] == "ns2"
    def test_item_to_xml(self) -> None:
        """Test item to XML."""
        resource = didl_lite.Resource("url", "protocol_info")
        items = [
            didl_lite.AudioItem(
                id="0",
                parent_id="0",
                title="Audio Item Title",
                restricted="1",
                resources=[resource],
                language="English",
                longDescription="Long description",
            ),
        ]
        didl_string = didl_lite.to_xml_string(*items).decode("utf-8")

        assert 'xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"' in didl_string
        assert 'xmlns:dc="http://purl.org/dc/elements/1.1/"' in didl_string
        assert 'xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"' in didl_string
        assert 'xmlns:sec="http://www.sec.co.kr/"' in didl_string
        assert 'xmlns:ns1="urn:schemas-upnp-org:metadata-1-0/upnp/"' not in didl_string

        didl_el = ET.fromstring(didl_string)

        item_el = didl_el.find("./didl_lite:item", NAMESPACES)
        assert item_el is not None
        assert item_el.attrib["id"] == "0"
        assert item_el.attrib["parentID"] == "0"
        assert item_el.attrib["restricted"] == "1"

        title_el = item_el.find("./dc:title", NAMESPACES)
        assert title_el is not None
        assert title_el.text == "Audio Item Title"

        class_el = item_el.find("./upnp:class", NAMESPACES)
        assert class_el is not None
        assert class_el.text == "object.item.audioItem"

        language_el = item_el.find("./dc:language", NAMESPACES)
        assert language_el is not None
        assert language_el.text == "English"

        long_description_el = item_el.find("./upnp:longDescription",
                                           NAMESPACES)
        assert long_description_el is not None
        assert long_description_el.text == "Long description"

        res_el = item_el.find("./didl_lite:res", NAMESPACES)
        assert res_el is not None
        assert res_el.attrib["protocolInfo"] == "protocol_info"
        assert res_el.text == "url"
    def test_container_to_xml(self) -> None:
        """Test container to XML."""
        container = didl_lite.Album(id="0",
                                    parent_id="0",
                                    title="Audio Item Title",
                                    restricted="1")
        resource = didl_lite.Resource("url", "protocol_info")
        item = didl_lite.AudioItem(
            id="0",
            parent_id="0",
            title="Audio Item Title",
            restricted="1",
            resources=[resource],
            language="English",
        )
        container.append(item)

        didl_string = didl_lite.to_xml_string(container).decode("utf-8")
        didl_el = ET.fromstring(didl_string)

        container_el = didl_el.find("./didl_lite:container", NAMESPACES)
        assert container_el is not None
        assert container_el.attrib["id"] == "0"
        assert container_el.attrib["parentID"] == "0"
        assert container_el.attrib["restricted"] == "1"

        item_el = container_el.find("./didl_lite:item", NAMESPACES)
        assert item_el is not None
        assert item_el.attrib["id"] == "0"
        assert item_el.attrib["parentID"] == "0"
        assert item_el.attrib["restricted"] == "1"

        title_el = item_el.find("./dc:title", NAMESPACES)
        assert title_el is not None
        assert title_el.text == "Audio Item Title"

        class_el = item_el.find("./upnp:class", NAMESPACES)
        assert class_el is not None
        assert class_el.text == "object.item.audioItem"

        language_el = item_el.find("./dc:language", NAMESPACES)
        assert language_el is not None
        assert language_el.text == "English"

        res_el = item_el.find("./didl_lite:res", NAMESPACES)
        assert res_el is not None
        assert res_el.attrib["protocolInfo"] == "protocol_info"
        assert res_el.text == "url"
    def test_item_property_attribute_to_xml(self) -> None:
        """Test item property to XML."""
        item = didl_lite.VideoItem(
            id="0",
            parent_id="0",
            title="Video Item Title",
            restricted="1",
            genre="Action",
            genre_id="genreId",
        )
        didl_string = didl_lite.to_xml_string(item).decode("utf-8")
        didl_el = ET.fromstring(didl_string)

        item_el = didl_el.find("./didl_lite:item", NAMESPACES)
        assert item_el is not None

        genre_el = item_el.find("./upnp:genre", NAMESPACES)
        assert genre_el is not None
        assert genre_el.text == "Action"
        assert genre_el.attrib["id"] == "genreId"
Пример #5
0
    async def async_play_media(
        self, media_type: str, media_id: str, **kwargs: Any
    ) -> None:
        """Play a piece of media."""
        _LOGGER.debug("Playing media: %s, %s, %s", media_type, media_id, kwargs)
        assert self._device is not None

        didl_metadata: str | None = None
        title: str = ""

        # If media is media_source, resolve it to url and MIME type, and maybe metadata
        if media_source.is_media_source_id(media_id):
            sourced_media = await media_source.async_resolve_media(self.hass, media_id)
            media_type = sourced_media.mime_type
            media_id = sourced_media.url
            _LOGGER.debug("sourced_media is %s", sourced_media)
            if sourced_metadata := getattr(sourced_media, "didl_metadata", None):
                didl_metadata = didl_lite.to_xml_string(sourced_metadata).decode(
                    "utf-8"
                )
                title = sourced_metadata.title
Пример #6
0
    async def _construct_play_media_metadata(self, media_url, media_title,
                                             mime_type, upnp_class):
        """Construct the metadata for play_media command."""
        media_info = {
            'mime_type':
            mime_type,
            'dlna_features':
            'DLNA.ORG_OP=01;DLNA.ORG_CI=0;'
            'DLNA.ORG_FLAGS=00000000000000000000000000000000',
        }

        # do a HEAD/GET, to retrieve content-type/mime-type
        try:
            headers = await self._fetch_headers(
                media_url, {'GetContentFeatures.dlna.org': '1'})
            if headers:
                if 'Content-Type' in headers:
                    media_info['mime_type'] = headers['Content-Type']

                if 'ContentFeatures.dlna.org' in headers:
                    media_info['dlna_features'] = headers[
                        'contentFeatures.dlna.org']
        except Exception:  # pylint: disable=broad-except
            pass

        # build DIDL-Lite item + resource
        protocol_info = "http-get:*:{mime_type}:{dlna_features}".format(
            **media_info)
        resource = didl_lite.Resource(uri=media_url,
                                      protocol_info=protocol_info)
        didl_item_type = didl_lite.type_by_upnp_class(upnp_class)
        item = didl_item_type(id="0",
                              parent_id="0",
                              title=media_title,
                              restricted="1",
                              resources=[resource])

        return didl_lite.to_xml_string(item).decode('utf-8')
Пример #7
0
    async def _get_inputs_upnp(self):
        await self._get_upnp_services()

        content_directory = self._upnp_device.service(
            next(s for s in self._upnp_discovery.upnp_services
                 if "ContentDirectory" in s))

        browse = content_directory.action("Browse")
        filter = (
            "av:BIVL,av:liveType,av:containerClass,dc:title,dc:date,"
            "res,res@duration,res@resolution,upnp:albumArtURI,"
            "upnp:albumArtURI@dlna:profileID,upnp:artist,upnp:album,upnp:genre"
        )
        result = await browse.async_call(
            ObjectID="0",
            BrowseFlag="BrowseDirectChildren",
            Filter=filter,
            StartingIndex=0,
            RequestedCount=25,
            SortCriteria="",
        )

        root_items = didl_lite.from_xml_string(result["Result"])
        input_item = next(
            (i for i in root_items
             if isinstance(i, didl_lite.Container) and i.title == "Input"),
            None,
        )

        result = await browse.async_call(
            ObjectID=input_item.id,
            BrowseFlag="BrowseDirectChildren",
            Filter=filter,
            StartingIndex=0,
            RequestedCount=25,
            SortCriteria="",
        )

        av_transport = self._upnp_renderer.service(
            next(s for s in self._upnp_renderer.services
                 if "AVTransport" in s))

        media_info = await av_transport.action("GetMediaInfo").async_call(
            InstanceID=0)
        current_uri = media_info.get("CurrentURI")

        inputs = didl_lite.from_xml_string(result["Result"])

        def is_input_active(input, current_uri):
            if not current_uri:
                return False

            # when input is switched on device, uri can have file:// format
            if current_uri.startswith("file://"):
                # UPnP 'Bluetooth AUDIO' can be file://Bluetooth
                # UPnP 'AUDIO' can be file://Audio
                return current_uri.lower() in "file://" + input.title.lower()

            if current_uri.startswith("local://"):
                # current uri can have additional query params, such as zone
                return input.resources[0].uri in current_uri

        return [
            Input.make(
                title=i.title,
                uri=i.resources[0].uri,
                active="active" if is_input_active(i, current_uri) else "",
                avTransport=av_transport,
                uriMetadata=didl_lite.to_xml_string(i).decode("utf-8"),
            ) for i in inputs
        ]
Пример #8
0
    async def _construct_play_media_metadata(
        self,
        media_url: str,
        media_title: str,
        mime_type: str,
        upnp_class: str,
        override_mime_type: Optional[str] = None,
        override_dlna_features: Optional[str] = None,
    ) -> str:
        """
        Construct the metadata for play_media command.

        This queries the source and takes mime_type/dlna_features from it.

        :arg media_url URL to media
        :arg media_title
        :arg mime_type Suggested mime type, will be overridden by source if possible
        :arg upnp_class UPnP class
        :arg override_mime_type Enfore mime_type, even if source reports a different mime_type
        :arg override_dlna_features Enforce DLNA features, even if source reports different features
        :return String containing metadata
        """
        # pylint: disable=too-many-arguments, too-many-locals
        media_info = {
            "mime_type": mime_type,
            "dlna_features": "*",
        }

        # do a HEAD/GET, to retrieve content-type/mime-type
        try:
            headers = await self._fetch_headers(
                media_url, {"GetContentFeatures.dlna.org": "1"})
            if headers:
                if "Content-Type" in headers:
                    media_info["mime_type"] = headers["Content-Type"]

                if "ContentFeatures.dlna.org" in headers:
                    media_info["dlna_features"] = headers[
                        "contentFeatures.dlna.org"]
        except Exception:  # pylint: disable=broad-except
            pass

        # use CM/GetProtocolInfo to improve on dlna_features
        if self.has_get_protocol_info:
            protocol_info_entries = (
                await self._async_get_sink_protocol_info_for_mime_type(
                    media_info["mime_type"]))
            for entry in protocol_info_entries:
                if entry[3] == "*":
                    # device accepts anything, send this
                    media_info["dlna_features"] = "*"

        # allow overriding of mime_type/dlna_features
        if override_mime_type:
            media_info["mime_type"] = override_mime_type
        if override_dlna_features:
            media_info["dlna_features"] = override_dlna_features

        # build DIDL-Lite item + resource
        didl_item_type = didl_lite.type_by_upnp_class(upnp_class)
        if not didl_item_type:
            raise UpnpError("Unknown DIDL-lite type")

        protocol_info = "http-get:*:{mime_type}:{dlna_features}".format(
            **media_info)
        resource = didl_lite.Resource(uri=media_url,
                                      protocol_info=protocol_info)
        item = didl_item_type(
            id="0",
            parent_id="-1",
            title=media_title,
            restricted="false",
            resources=[resource],
        )

        xml_string: bytes = didl_lite.to_xml_string(item)
        return xml_string.decode("utf-8")
Пример #9
0
    async def construct_play_media_metadata(
        self,
        media_url: str,
        media_title: str,
        default_mime_type: Optional[str] = None,
        default_upnp_class: Optional[str] = None,
        override_mime_type: Optional[str] = None,
        override_upnp_class: Optional[str] = None,
        override_dlna_features: Optional[str] = None,
    ) -> str:
        """
        Construct the metadata for play_media command.

        This queries the source and takes mime_type/dlna_features from it.

        :arg media_url URL to media
        :arg media_title
        :arg default_mime_type Suggested mime type, will be overridden by source if possible
        :arg default_upnp_class Suggested UPnP class, will be used as fallback for autodetection
        :arg override_mime_type Enforce mime_type, even if source reports a different mime_type
        :arg override_upnp_class Enforce upnp_class, even if autodetection finds something usable
        :arg override_dlna_features Enforce DLNA features, even if source reports different features
        :return String containing metadata
        """
        # pylint: disable=too-many-arguments, too-many-locals, too-many-branches
        mime_type = override_mime_type or ""
        upnp_class = override_upnp_class or ""
        dlna_features = override_dlna_features or "*"

        if None in (override_mime_type, override_dlna_features):
            # do a HEAD/GET, to retrieve content-type/mime-type
            try:
                headers = await self._fetch_headers(
                    media_url, {"GetContentFeatures.dlna.org": "1"})
                if headers:
                    if not override_mime_type and "Content-Type" in headers:
                        mime_type = headers["Content-Type"]
                    if (not override_dlna_features
                            and "ContentFeatures.dlna.org" in headers):
                        dlna_features = headers["contentFeatures.dlna.org"]
            except Exception:  # pylint: disable=broad-except
                pass

            if not mime_type:
                _type = guess_type(media_url.split("?")[0])
                mime_type = _type[0] or ""
                if not mime_type:
                    mime_type = default_mime_type or "application/octet-stream"

            # use CM/GetProtocolInfo to improve on dlna_features
            if (not override_dlna_features and dlna_features != "*"
                    and self.has_get_protocol_info):
                protocol_info_entries = (
                    await
                    self._async_get_sink_protocol_info_for_mime_type(mime_type)
                )
                for entry in protocol_info_entries:
                    if entry[3] == "*":
                        # device accepts anything, send this
                        dlna_features = "*"

        # Try to derive a basic upnp_class from mime_type
        if not override_upnp_class:
            mime_type = mime_type.lower()
            for _mime, _class in MIME_TO_UPNP_CLASS_MAPPING.items():
                if mime_type.startswith(_mime):
                    upnp_class = _class
                    break
            else:
                upnp_class = default_upnp_class or "object.item"

        # build DIDL-Lite item + resource
        didl_item_type = didl_lite.type_by_upnp_class(upnp_class)
        if not didl_item_type:
            raise UpnpError("Unknown DIDL-lite type")

        protocol_info = f"http-get:*:{mime_type}:{dlna_features}"
        resource = didl_lite.Resource(uri=media_url,
                                      protocol_info=protocol_info)
        item = didl_item_type(
            id="0",
            parent_id="-1",
            title=media_title,
            restricted="false",
            resources=[resource],
        )

        xml_string: bytes = didl_lite.to_xml_string(item)
        return xml_string.decode("utf-8")