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"
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
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')
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 ]
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")
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")