Example #1
0
def dlna_handle_notify_last_change(state_var: UpnpStateVariable) -> None:
    """
    Handle changes to LastChange state variable.

    This expands all changed state variables in the LastChange state variable.
    Note that the callback is called twice:
    - for the original event;
    - for the expanded event, via this function.
    """
    if state_var.name != "LastChange":
        raise UpnpError("Call this only on state variable LastChange")

    event_data: Optional[str] = state_var.value
    if not event_data:
        _LOGGER.debug("No event data on state_variable")
        return

    changes = _parse_last_change_event(event_data)
    if "0" not in changes:
        _LOGGER.warning("Only InstanceID 0 is supported")
        return

    service = state_var.service
    changes_0 = changes["0"]
    service.notify_changed_state_variables(changes_0)
Example #2
0
    async def async_set_transport_uri(self,
                                      media_url: str,
                                      media_title: str,
                                      meta_data: Optional[str] = None) -> None:
        """Play a piece of media."""
        # pylint: disable=too-many-arguments
        # escape media_url
        _LOGGER.debug("Set transport uri: %s", media_url)
        media_url_parts = urlparse(media_url)
        media_url = urlunparse([
            media_url_parts.scheme,
            media_url_parts.netloc,
            media_url_parts.path,
            "",
            quote_plus(media_url_parts.query),
            "",
        ])

        # queue media
        if not meta_data:
            meta_data = await self.construct_play_media_metadata(
                media_url, media_title)
        action = self._action("AVT", "SetAVTransportURI")
        if not action:
            raise UpnpError("Missing action AVT/SetAVTransportURI")
        await action.async_call(InstanceID=0,
                                CurrentURI=media_url,
                                CurrentURIMetaData=meta_data)
Example #3
0
def dlna_handle_notify_last_change(state_var: UpnpStateVariable):
    """
    Handle changes to LastChange state variable.

    This expands all changed state variables in the LastChange state variable.
    Note that the callback is called twice:
    - for the original event;
    - for the expanded event, via this function.
    """
    if state_var.name != 'LastChange':
        raise UpnpError('Call this only on state variable LastChange')

    service = state_var.service
    changes = {}

    el_event = ET.fromstring(state_var.value)
    for el_instance in el_event:
        if not el_instance.tag.endswith("}InstanceID"):
            continue

        if el_instance.attrib['val'] != '0':
            _LOGGER.warning('Only InstanceID 0 is supported')
            continue

        for el_state_var in el_instance:
            name = el_state_var.tag.split('}')[1]
            value = el_state_var.attrib['val']
            changes[name] = value

    service.notify_changed_state_variables(changes)
Example #4
0
    async def async_set_transport_uri(
            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) -> None:
        """Play a piece of media."""
        # pylint: disable=too-many-arguments
        # escape media_url
        _LOGGER.debug('Set transport uri: %s', media_url)
        media_url_parts = urlparse(media_url)
        media_url = urlunparse([
            media_url_parts.scheme, media_url_parts.netloc,
            media_url_parts.path, '',
            quote_plus(media_url_parts.query), ''
        ])

        # queue media
        meta_data = await self._construct_play_media_metadata(
            media_url,
            media_title,
            mime_type,
            upnp_class,
            override_mime_type=override_mime_type,
            override_dlna_features=override_dlna_features)
        action = self._action('AVT', 'SetAVTransportURI')
        if not action:
            raise UpnpError('Missing action AVT/SetAVTransportURI')
        await action.async_call(InstanceID=0,
                                CurrentURI=media_url,
                                CurrentURIMetaData=meta_data)
Example #5
0
 async def async_mute_volume(self, mute: bool) -> None:
     """Mute the volume."""
     action = self._action("RC", "SetMute")
     if not action:
         raise UpnpError("Missing action RC/SetMute")
     desired_mute = bool(mute)
     await action.async_call(InstanceID=0,
                             Channel="Master",
                             DesiredMute=desired_mute)
Example #6
0
 async def async_mute_volume(self, mute: bool) -> None:
     """Mute the volume."""
     action = self._action('RC', 'SetMute')
     if not action:
         raise UpnpError('Missing action RC/SetMute')
     desired_mute = bool(mute)
     await action.async_call(InstanceID=0,
                             Channel='Master',
                             DesiredMute=desired_mute)
Example #7
0
    async def async_stop(self) -> None:
        """Send stop command."""
        if not self._can_transport_action("stop"):
            _LOGGER.debug("Cannot do Stop")
            return

        action = self._action("AVT", "Stop")
        if not action:
            raise UpnpError("Missing action AVT/Stop")
        await action.async_call(InstanceID=0)
Example #8
0
    async def async_play(self) -> None:
        """Send play command."""
        if not self._can_transport_action("play"):
            _LOGGER.debug("Cannot do Play")
            return

        action = self._action("AVT", "Play")
        if not action:
            raise UpnpError("Missing action AVT/Play")
        await action.async_call(InstanceID=0, Speed="1")
Example #9
0
    async def async_next(self) -> None:
        """Send next track command."""
        if not self._can_transport_action("next"):
            _LOGGER.debug("Cannot do Next")
            return

        action = self._action("AVT", "Next")
        if not action:
            raise UpnpError("Missing action AVT/Next")
        await action.async_call(InstanceID=0)
Example #10
0
    async def async_play(self) -> None:
        """Send play command."""
        if not self._can_transport_action('play'):
            _LOGGER.debug('Cannot do Play')
            return

        action = self._action('AVT', 'Play')
        if not action:
            raise UpnpError('Missing action AVT/Play')
        await action.async_call(InstanceID=0, Speed='1')
Example #11
0
    async def async_next(self) -> None:
        """Send next track command."""
        if not self._can_transport_action('next'):
            _LOGGER.debug('Cannot do Next')
            return

        action = self._action('AVT', 'Next')
        if not action:
            raise UpnpError('Missing action AVT/Next')
        await action.async_call(InstanceID=0)
Example #12
0
    async def async_previous(self) -> None:
        """Send previous track command."""
        if not self._can_transport_action('previous'):
            _LOGGER.debug('Cannot do Previous')
            return

        action = self._action('AVT', 'Previous')
        if not action:
            raise UpnpError('Missing action AVT/Previous')
        await action.async_call(InstanceID=0)
Example #13
0
    async def async_stop(self) -> None:
        """Send stop command."""
        if not self._can_transport_action('stop'):
            _LOGGER.debug('Cannot do Stop')
            return

        action = self._action('AVT', 'Stop')
        if not action:
            raise UpnpError('Missing action AVT/Stop')
        await action.async_call(InstanceID=0)
Example #14
0
    async def async_previous(self) -> None:
        """Send previous track command."""
        if not self._can_transport_action("previous"):
            _LOGGER.debug("Cannot do Previous")
            return

        action = self._action("AVT", "Previous")
        if not action:
            raise UpnpError("Missing action AVT/Previous")
        await action.async_call(InstanceID=0)
Example #15
0
    async def async_seek_rel_time(self, time: timedelta) -> None:
        """Send seek command with REL_TIME."""
        if not self._can_transport_action("seek"):
            _LOGGER.debug("Cannot do Seek by REL_TIME")
            return

        action = self._action("AVT", "Seek")
        if not action:
            raise UpnpError("Missing action AVT/Seek")
        target = time_to_str(time)
        await action.async_call(InstanceID=0, Unit="REL_TIME", Target=target)
Example #16
0
    async def async_seek_abs_time(self, time: timedelta) -> None:
        """Send seek command with ABS_TIME."""
        if not self._can_transport_action('seek'):
            _LOGGER.debug('Cannot do Seek by ABS_TIME')
            return

        action = self._action('AVT', 'Seek')
        if not action:
            raise UpnpError('Missing action AVT/Seek')
        target = time_to_str(time)
        await action.async_call(InstanceID=0, Unit='ABS_TIME', Target=target)
Example #17
0
    def _level(self, var_name: str) -> Optional[float]:
        state_var = self._state_variable("RC", var_name)
        if state_var is None:
            raise UpnpError("Missing StateVariable RC/%s" % (var_name, ))

        value: Optional[float] = state_var.value
        if value is None:
            _LOGGER.debug("Got no value for %s", var_name)
            return None

        max_value = state_var.max_value or 100.0
        return min(value / max_value, 1.0)
Example #18
0
    async def _async_set_level(self, var_name: str, level: float,
                               **kwargs: Any) -> None:
        action = self._action("RC", "Set%s" % var_name)
        if not action:
            raise UpnpError("Missing Action RC/Set%s" % (var_name, ))

        arg_name = "Desired%s" % var_name
        argument = action.argument(arg_name)
        if not argument:
            raise UpnpError("Missing Argument %s for Action RC/Set%s" % (
                arg_name,
                var_name,
            ))
        state_variable = argument.related_state_variable

        min_ = state_variable.min_value or 0
        max_ = state_variable.max_value or 100
        desired_level = int(min_ + level * (max_ - min_))

        args = kwargs.copy()
        args[arg_name] = desired_level
        await action.async_call(InstanceID=0, **args)
Example #19
0
def dlna_handle_notify_last_change(state_var):
    """
    Handle changes to LastChange state variable.

    This expands all changed state variables in the LastChange state variable.
    Note that the callback is called twice:
    - for the original event;
    - for the expanded event, via this function.
    """
    if state_var.name != 'LastChange':
        raise UpnpError('Call this only on state variable LastChange')

    service = state_var.service
    changed_state_variables = []

    el_event = ET.fromstring(state_var.value)
    _LOGGER.debug("Event payload: %s" % state_var.value)
    for el_instance in el_event:
        if not el_instance.tag.endswith("}InstanceID"):
            continue

        if el_instance.attrib['val'] != '0':
            _LOGGER.warning('Only InstanceID 0 is supported')
            continue

        for el_state_var in el_instance:
            name = el_state_var.tag.split('}')[1]
            state_var = service.state_variable(name)
            if state_var is None:
                _LOGGER.debug("State variable %s does not exist, ignoring",
                              name)
                continue

            value = el_state_var.attrib['val']
            try:
                state_var.upnp_value = value
            except vol.error.MultipleInvalid:
                _LOGGER.error('Got invalid value for %s: %s', state_var, value)
            changed_state_variables.append(state_var)

    service.notify_changed_state_variables(changed_state_variables)
Example #20
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")
Example #21
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")