def get_queue(self, start=0, max_items=100): """ Get information about the queue. Returns: A list containing a dictionary for each track in the queue. The track dictionary contains the following information about the track: title, artist, album, album_art, uri If we're unable to return data for a field, we'll return an empty list. This can happen for all kinds of reasons so be sure to check values. This method is heavly based on Sam Soffes (aka soffes) ruby implementation """ queue = [] body = GET_QUEUE_BODY_TEMPLATE.format(start, max_items) response = self.__send_command(CONTENT_DIRECTORY_ENDPOINT, BROWSE_ACTION, body) try: dom = XML.fromstring(really_utf8(response)) resultText = dom.findtext('.//Result') if not resultText: return queue resultDom = XML.fromstring(really_utf8(resultText)) for element in resultDom.findall( './/{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}item'): try: item = { 'title': None, 'artist': None, 'album': None, 'album_art': None, 'uri': None } item['title'] = element.findtext( '{http://purl.org/dc/elements/1.1/}title') item['artist'] = element.findtext( '{http://purl.org/dc/elements/1.1/}creator') item['album'] = element.findtext( '{urn:schemas-upnp-org:metadata-1-0/upnp/}album') item['album_art'] = element.findtext( '{urn:schemas-upnp-org:metadata-1-0/upnp/}albumArtURI') item['uri'] = element.findtext( '{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}res') queue.append(item) except: logger.warning('Could not handle item: %s', element) logger.error(traceback.format_exc()) except: logger.error('Could not handle result from Sonos') logger.error(traceback.format_exc()) return queue
def get_queue(self, start = 0, max_items = 100): """ Get information about the queue. Returns: A list containing a dictionary for each track in the queue. The track dictionary contains the following information about the track: title, artist, album, album_art, uri If we're unable to return data for a field, we'll return an empty list. This can happen for all kinds of reasons so be sure to check values. This method is heavly based on Sam Soffes (aka soffes) ruby implementation """ queue = [] body = GET_QUEUE_BODY_TEMPLATE.format(start, max_items) response = self.__send_command(CONTENT_DIRECTORY_ENDPOINT, BROWSE_ACTION, body) try: dom = XML.fromstring(really_utf8(response)) resultText = dom.findtext('.//Result') if not resultText: return queue resultDom = XML.fromstring(really_utf8(resultText)) for element in resultDom.findall('.//{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}item'): try: item = { 'title': None, 'artist': None, 'album': None, 'album_art': None, 'uri': None } item['title'] = element.findtext('{http://purl.org/dc/elements/1.1/}title') item['artist'] = element.findtext('{http://purl.org/dc/elements/1.1/}creator') item['album'] = element.findtext('{urn:schemas-upnp-org:metadata-1-0/upnp/}album') item['album_art'] = element.findtext('{urn:schemas-upnp-org:metadata-1-0/upnp/}albumArtURI') item['uri'] = element.findtext('{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}res') queue.append(item) except: logger.warning('Could not handle item: %s', element) logger.error(traceback.format_exc()) except: logger.error('Could not handle result from Sonos') logger.error(traceback.format_exc()) return queue
def get_current_transport_info(self): """ Get the current playback state Returns: A dictionary containing the following information about the speakers playing state current_transport_state (PLAYING, PAUSED_PLAYBACK, STOPPED), current_trasnport_status (OK, ?), current_speed(1,?) This allows us to know if speaker is playing or not. Don't know other states of CurrentTransportStatus and CurrentSpeed. """ response = self.__send_command(TRANSPORT_ENDPOINT, GET_CUR_TRANSPORT_ACTION, GET_CUR_TRANSPORT_BODY) dom = XML.fromstring(really_utf8(response)) playstate = { 'current_transport_status': '', 'current_transport_state': '', 'current_transport_speed': '' } playstate['current_transport_state'] = dom.findtext('.//CurrentTransportState') playstate['current_transport_status'] = dom.findtext('.//CurrentTransportStatus') playstate['current_transport_speed'] = dom.findtext('.//CurrentSpeed') return playstate
def get_speaker_info(self, refresh=False): """ Get information about the Sonos speaker. Arguments: refresh -- Refresh the speaker info cache. Returns: Information about the Sonos speaker, such as the UID, MAC Address, and Zone Name. """ if self.speaker_info and refresh is False: return self.speaker_info else: response = requests.get('http://' + self.speaker_ip + ':1400/status/zp') dom = XML.fromstring(response.content) self.speaker_info['zone_name'] = really_utf8(dom.findtext('.//ZoneName')) self.speaker_info['zone_icon'] = dom.findtext('.//ZoneIcon') self.speaker_info['uid'] = dom.findtext('.//LocalUID') self.speaker_info['serial_number'] = dom.findtext('.//SerialNumber') self.speaker_info['software_version'] = dom.findtext('.//SoftwareVersion') self.speaker_info['hardware_version'] = dom.findtext('.//HardwareVersion') self.speaker_info['mac_address'] = dom.findtext('.//MACAddress') return self.speaker_info
def get_current_transport_info(self): """ Get the current playback state Returns: A dictionary containing the following information about the speakers playing state current_transport_state (PLAYING, PAUSED_PLAYBACK, STOPPED), current_trasnport_status (OK, ?), current_speed(1,?) This allows us to know if speaker is playing or not. Don't know other states of CurrentTransportStatus and CurrentSpeed. """ response = self.__send_command(TRANSPORT_ENDPOINT, GET_CUR_TRANSPORT_ACTION, GET_CUR_TRANSPORT_BODY) dom = XML.fromstring(really_utf8(response)) playstate = { 'current_transport_status': '', 'current_transport_state': '', 'current_transport_speed': '' } playstate['current_transport_state'] = dom.findtext( './/CurrentTransportState') playstate['current_transport_status'] = dom.findtext( './/CurrentTransportStatus') playstate['current_transport_speed'] = dom.findtext('.//CurrentSpeed') return playstate
def get_speaker_info(self, refresh=False): """ Get information about the Sonos speaker. Arguments: refresh -- Refresh the speaker info cache. Returns: Information about the Sonos speaker, such as the UID, MAC Address, and Zone Name. """ if self.speaker_info and refresh is False: return self.speaker_info else: response = requests.get('http://' + self.speaker_ip + ':1400/status/zp') dom = XML.fromstring(response.content) self.speaker_info['zone_name'] = really_utf8( dom.findtext('.//ZoneName')) self.speaker_info['zone_icon'] = dom.findtext('.//ZoneIcon') self.speaker_info['uid'] = dom.findtext('.//LocalUID') self.speaker_info['serial_number'] = dom.findtext( './/SerialNumber') self.speaker_info['software_version'] = dom.findtext( './/SoftwareVersion') self.speaker_info['hardware_version'] = dom.findtext( './/HardwareVersion') self.speaker_info['mac_address'] = dom.findtext('.//MACAddress') return self.speaker_info
def __get_radio_favorites(self, favorite_type, start=0, max_items=100): """ Helper method for `get_favorite_radio_*` methods. Arguments: favorite_type -- Specify either `RADIO_STATIONS` or `RADIO_SHOWS`. start -- Which number to start the retrieval from. Used for paging. max_items -- The total number of results to return. """ if favorite_type != RADIO_SHOWS or RADIO_STATIONS: favorite_type = RADIO_STATIONS body = GET_RADIO_FAVORITES_BODY_TEMPLATE.format( favorite_type, start, max_items) response = self.__send_command(CONTENT_DIRECTORY_ENDPOINT, BROWSE_ACTION, body) dom = XML.fromstring(really_utf8(response)) result = {} favorites = [] d = dom.findtext('.//Result') if d != '': # Favorites are returned in DIDL-Lite format metadata = XML.fromstring(really_utf8(d)) for item in metadata.findall( './/{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}item'): favorite = {} favorite['title'] = really_utf8( item.findtext( './/{http://purl.org/dc/elements/1.1/}title')) favorite['uri'] = item.findtext( './/{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}res') favorites.append(favorite) result['total'] = dom.findtext('.//TotalMatches', 0) result['returned'] = len(favorites) result['favorites'] = favorites return result
def __get_radio_favorites(self, favorite_type, start=0, max_items=100): """ Helper method for `get_favorite_radio_*` methods. Arguments: favorite_type -- Specify either `RADIO_STATIONS` or `RADIO_SHOWS`. start -- Which number to start the retrieval from. Used for paging. max_items -- The total number of results to return. """ if favorite_type != RADIO_SHOWS or RADIO_STATIONS: favorite_type = RADIO_STATIONS body = GET_RADIO_FAVORITES_BODY_TEMPLATE.format(favorite_type, start, max_items) response = self.__send_command(CONTENT_DIRECTORY_ENDPOINT, BROWSE_ACTION, body) dom = XML.fromstring(really_utf8(response)) result = {} favorites = [] d = dom.findtext('.//Result') if d != '': # Favorites are returned in DIDL-Lite format metadata = XML.fromstring(really_utf8(d)) for item in metadata.findall('.//{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}item'): favorite = {} favorite['title'] = really_utf8(item.findtext('.//{http://purl.org/dc/elements/1.1/}title')) favorite['uri'] = item.findtext('.//{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}res') favorites.append(favorite) result['total'] = dom.findtext('.//TotalMatches', 0) result['returned'] = len(favorites) result['favorites'] = favorites return result
def __get_topology(self, refresh=False): """ Gets the topology if it is not already available or if refresh=True Code contributed by Aaron Daubman ([email protected]) Arguments: refresh -- Refresh the topology cache """ if not self.topology or refresh: self.topology = {} response = requests.get('http://' + self.speaker_ip + ':1400/status/topology') dom = XML.fromstring(really_utf8(response.content)) for player in dom.find('ZonePlayers'): if player.text not in self.topology: self.topology[player.text] = {} self.topology[player.text]['group'] = player.attrib.get('group') self.topology[player.text]['uuid'] = player.attrib.get('uuid') self.topology[player.text]['coordinator'] = (player.attrib.get('coordinator') == 'true') # Split the IP out of the URL returned in location - e.g. return '10.1.1.1' from 'http://10.1.1.1:1400/...' self.topology[player.text]['ip'] = player.attrib.get('location').split('//')[1].split(':')[0]
def _set_topology(self, zp): ''' improved (!)version of SoCo get_toplogy: - uses uPnP call to one zone player - excludes invisible zones (e.g. in stereo pair) - builds dictionary with key of ip and a dictionary of zone details Note: uses master = True v.s. SoCo Coordinator = True! ''' # get group state from one zone via direct uPnP call using SoCo zgroups = zp.zoneGroupTopology.GetZoneGroupState()['ZoneGroupState'] tree = XML.fromstring(really_utf8(zgroups)) for grp in tree: for zp in grp: # ignore zones marked as Invisible (e.g. one of a stereo pair) if 'Invisible' in zp.attrib: # Invisible info not always there if zp.attrib['Invisible']: # Invisible ==1 (True) continue ip_key = zp.attrib['Location'].split('//')[1].split(':')[0] self.topology[ip_key] = {} self.topology[ip_key]['uuid'] = zp.attrib['UUID'] self.topology[ip_key]['zone_name'] = zp.attrib['ZoneName'] #not needed if this is the key!!!!!!! self.topology[ip_key]['master'] = (zp.attrib['UUID'] == grp.attrib['Coordinator']) self.topology[ip_key]['group'] = grp.attrib['Coordinator'] return len(self.topology)
def __get_topology(self, refresh=False): """ Gets the topology if it is not already available or if refresh=True Code contributed by Aaron Daubman ([email protected]) Arguments: refresh -- Refresh the topology cache """ if not self.topology or refresh: self.topology = {} response = requests.get('http://' + self.speaker_ip + ':1400/status/topology') dom = XML.fromstring(really_utf8(response.content)) for player in dom.find('ZonePlayers'): if player.text not in self.topology: self.topology[player.text] = {} self.topology[player.text]['group'] = player.attrib.get( 'group') self.topology[player.text]['uuid'] = player.attrib.get('uuid') self.topology[player.text]['coordinator'] = ( player.attrib.get('coordinator') == 'true') # Split the IP out of the URL returned in location - e.g. return '10.1.1.1' from 'http://10.1.1.1:1400/...' self.topology[player.text]['ip'] = player.attrib.get( 'location').split('//')[1].split(':')[0]
def get_current_track_info(self): """ Get information about the currently playing track. Returns: A dictionary containing the following information about the currently playing track: playlist_position, duration, title, artist, album, position and a link to the album art. If we're unable to return data for a field, we'll return an empty string. This can happen for all kinds of reasons so be sure to check values. For example, a track may not have complete metadata and be missing an album name. In this case track['album'] will be an empty string. """ response = self.__send_command(TRANSPORT_ENDPOINT, GET_CUR_TRACK_ACTION, GET_CUR_TRACK_BODY) dom = XML.fromstring(really_utf8(response)) track = {'title': '', 'artist': '', 'album': '', 'album_art': '', 'position': ''} track['playlist_position'] = dom.findtext('.//Track') track['duration'] = dom.findtext('.//TrackDuration') track['uri'] = dom.findtext('.//TrackURI') track['position'] = dom.findtext('.//RelTime') d = dom.findtext('.//TrackMetaData') # Duration seems to be '0:00:00' when listening to radio if d != '' and track['duration'] == '0:00:00': metadata = XML.fromstring(really_utf8(d)) #Try parse trackinfo trackinfo = metadata.findtext('.//{urn:schemas-rinconnetworks-com:metadata-1-0/}streamContent') index = trackinfo.find(' - ') if index > -1: track['artist'] = trackinfo[:index] track['title'] = trackinfo[index+3:] else: logger.warning('Could not handle track info: "%s"', trackinfo) logger.warning(traceback.format_exc()) track['title'] = really_utf8(trackinfo) # If the speaker is playing from the line-in source, querying for track # metadata will return "NOT_IMPLEMENTED". elif d != '' and d != 'NOT_IMPLEMENTED': # Track metadata is returned in DIDL-Lite format metadata = XML.fromstring(really_utf8(d)) md_title = metadata.findtext('.//{http://purl.org/dc/elements/1.1/}title') md_artist = metadata.findtext('.//{http://purl.org/dc/elements/1.1/}creator') md_album = metadata.findtext('.//{urn:schemas-upnp-org:metadata-1-0/upnp/}album') track['title'] = "" if (md_title): track['title'] = really_utf8(md_title) track['artist'] = "" if (md_artist): track['artist'] = really_utf8(md_artist) track['album'] = "" if (md_album): track['album'] = really_utf8(md_album) album_art = metadata.findtext('.//{urn:schemas-upnp-org:metadata-1-0/upnp/}albumArtURI') if album_art is not None: track['album_art'] = 'http://' + self.speaker_ip + ':1400' + metadata.findtext('.//{urn:schemas-upnp-org:metadata-1-0/upnp/}albumArtURI') return track
def get_current_track_info(self): """ Get information about the currently playing track. Returns: A dictionary containing the following information about the currently playing track: playlist_position, duration, title, artist, album, position and a link to the album art. If we're unable to return data for a field, we'll return an empty string. This can happen for all kinds of reasons so be sure to check values. For example, a track may not have complete metadata and be missing an album name. In this case track['album'] will be an empty string. """ response = self.__send_command(TRANSPORT_ENDPOINT, GET_CUR_TRACK_ACTION, GET_CUR_TRACK_BODY) dom = XML.fromstring(really_utf8(response)) track = { 'title': '', 'artist': '', 'album': '', 'album_art': '', 'position': '' } track['playlist_position'] = dom.findtext('.//Track') track['duration'] = dom.findtext('.//TrackDuration') track['uri'] = dom.findtext('.//TrackURI') track['position'] = dom.findtext('.//RelTime') d = dom.findtext('.//TrackMetaData') # Duration seems to be '0:00:00' when listening to radio if d != '' and track['duration'] == '0:00:00': metadata = XML.fromstring(really_utf8(d)) #Try parse trackinfo trackinfo = metadata.findtext( './/{urn:schemas-rinconnetworks-com:metadata-1-0/}streamContent' ) index = trackinfo.find(' - ') if index > -1: track['artist'] = trackinfo[:index] track['title'] = trackinfo[index + 3:] else: logger.warning('Could not handle track info: "%s"', trackinfo) logger.warning(traceback.format_exc()) track['title'] = really_utf8(trackinfo) # If the speaker is playing from the line-in source, querying for track # metadata will return "NOT_IMPLEMENTED". elif d != '' and d != 'NOT_IMPLEMENTED': # Track metadata is returned in DIDL-Lite format metadata = XML.fromstring(really_utf8(d)) md_title = metadata.findtext( './/{http://purl.org/dc/elements/1.1/}title') md_artist = metadata.findtext( './/{http://purl.org/dc/elements/1.1/}creator') md_album = metadata.findtext( './/{urn:schemas-upnp-org:metadata-1-0/upnp/}album') track['title'] = "" if (md_title): track['title'] = really_utf8(md_title) track['artist'] = "" if (md_artist): track['artist'] = really_utf8(md_artist) track['album'] = "" if (md_album): track['album'] = really_utf8(md_album) album_art = metadata.findtext( './/{urn:schemas-upnp-org:metadata-1-0/upnp/}albumArtURI') if album_art is not None: track[ 'album_art'] = 'http://' + self.speaker_ip + ':1400' + metadata.findtext( './/{urn:schemas-upnp-org:metadata-1-0/upnp/}albumArtURI' ) return track