Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Didl class for Sonos favorites #478

Merged
merged 9 commits into from Jan 6, 2018
2 changes: 1 addition & 1 deletion pylintrc
Expand Up @@ -6,7 +6,7 @@
# duplicate code check disabled whilst refactoring of wimp plugin is in
# progress
disable=too-many-lines,locally-disabled,duplicate-code,too-few-public-methods,
bad-option-value,no-else-return
bad-option-value,no-else-return,cyclic-import


[REPORTS]
Expand Down
7 changes: 4 additions & 3 deletions soco/core.py
Expand Up @@ -142,9 +142,6 @@ class SoCo(_SocoSingletonBase):
add_multiple_to_queue
remove_from_queue
clear_queue
get_favorite_radio_shows
get_favorite_radio_stations
get_sonos_favorites
create_sonos_playlist
create_sonos_playlist_from_queue
remove_sonos_playlist
Expand Down Expand Up @@ -1450,6 +1447,7 @@ def clear_queue(self):
('InstanceID', 0),
])

@deprecated('0.13', "soco.music_library.get_favorite_radio_shows", '0.15')
def get_favorite_radio_shows(self, start=0, max_items=100):
"""Get favorite radio shows from Sonos' Radio app.

Expand All @@ -1468,6 +1466,8 @@ def get_favorite_radio_shows(self, start=0, max_items=100):
warnings.warn(message, stacklevel=2)
return self.__get_favorites(RADIO_SHOWS, start, max_items)

@deprecated('0.13', "soco.music_library.get_favorite_radio_stations",
'0.15')
def get_favorite_radio_stations(self, start=0, max_items=100):
"""Get favorite radio stations from Sonos' Radio app.

Expand All @@ -1486,6 +1486,7 @@ def get_favorite_radio_stations(self, start=0, max_items=100):
warnings.warn(message, stacklevel=2)
return self.__get_favorites(RADIO_STATIONS, start, max_items)

@deprecated('0.13', "soco.music_library.get_sonos_favorites", '0.15')
def get_sonos_favorites(self, start=0, max_items=100):
"""Get Sonos favorites.

Expand Down
99 changes: 57 additions & 42 deletions soco/data_structures.py
Expand Up @@ -39,6 +39,10 @@
XML, ns_tag
)

# Due to cyclic import problems, we only import from_didl_string at runtime.
# from data_structures_entry import from_didl_string
_FROM_DIDL_STRING_FUNCTION = None


###############################################################################
# MISC HELPER FUNCTIONS #
Expand All @@ -61,6 +65,7 @@ def to_didl_string(*args):
'xmlns': "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/",
'xmlns:dc': "http://purl.org/dc/elements/1.1/",
'xmlns:upnp': "urn:schemas-upnp-org:metadata-1-0/upnp/",
'xmlns:r': "urn:schemas-rinconnetworks-com:metadata-1-0/"
})
for arg in args:
didl.append(arg.to_element())
Expand Down Expand Up @@ -733,47 +738,6 @@ class DidlAudioItem(DidlItem):
}
)

# Browsing Sonos Favorites produces some odd looking DIDL-Lite. The object
# class is 'object.itemobject.item.sonos-favorite', which is probably a typo
# in Sonos' code somewhere.

# Here is an example:
# <?xml version="1.0" ?>
# <DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"
# xmlns:dc="http://purl.org/dc/elements/1.1/"
# xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/"
# xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/">
# <item id="FV:2/13" parentID="FV:2" restricted="false">
# <dc:title>Shake It Off</dc:title>
# <upnp:class>object.itemobject.item.sonos-favorite</upnp:class>
# <r:ordinal>4</r:ordinal>
# <res protocolInfo="sonos.com-spotify:*:audio/x-spotify:*">
# x-sonos-spotify:spotify%3atrack%3a7n.......?sid=9&amp;flags=32</res>
# <upnp:albumArtURI>http://o.scd.....</upnp:albumArtURI>
# <r:type>instantPlay</r:type>
# <r:description>By Taylor Swift</r:description>
# <r:resMD>&lt;DIDL-Lite xmlns:dc=&quot;
# http://purl.org/dc/elements/1.1/&quot;
# xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot;
# xmlns:r=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;
# xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot;&gt;
# &lt;item id=&quot;00030020spotify%3atrack%3a7n9Q6b...74uCtajkddPt&quot;
# parentID=&quot;0006006ctoplist%2ftracks%2fregion%2fGB&quot;
# restricted=&quot;true&quot;&gt;&lt;dc:title&gt;Shake It Off
# &lt;/dc:title&gt;&lt;upnp:class&gt;object.item.audioItem.musicTrack
# &lt;/upnp:class&gt;&lt;desc id=&quot;cdudn&quot;
# nameSpace=&quot;urn:schemas-rinconnetworks-com:metadata-1-0/&quot;&gt;
# SA_RINCON2311_XXXXX&lt;/desc&gt;
# &lt;/item&gt;
# &lt;/DIDL-Lite&gt;
# </r:resMD>
# </item>
# </DIDL-Lite>

# Note the r:ordinal, r:type; r:description, r:resMD elements which are not
# seen (?) anywhere else
# We're ignoring this for the moment!


class DidlMusicTrack(DidlAudioItem):

Expand Down Expand Up @@ -808,7 +772,6 @@ class DidlAudioBroadcast(DidlAudioItem):
'radio_call_sign': ('upnp', 'radioCallSign'),
'radio_station_id': ('upnp', 'radioStationID'),
'channel_nr': ('upnp', 'channelNr'),

}
)

Expand All @@ -825,6 +788,50 @@ class DidlAudioBroadcastFavorite(DidlAudioBroadcast):
item_class = 'object.item.audioItem.audioBroadcast.sonos-favorite'


class DidlFavorite(DidlItem):

"""Class that represents a Sonos favorite."""

# the DIDL Lite class for this object.
item_class = 'object.itemobject.item.sonos-favorite'
_translation = DidlItem._translation.copy()
_translation.update(
{
'type': ('r', 'type'),
'description': ('r', 'description'),
'favorite_nr': ('r', 'ordinal'),
'resource_meta_data': ('r', 'resMD')
}
)

# The resMD tag contains the metadata of the Didl object referenced by this
# favorite. For user convenience, we will parse this metadata and make the
# object available via the 'reference' property.
@property
def reference(self):
"""The Didl object this favorite refers to."""

# Import from_didl_string if it isn't present already. The import
# happens here because it would cause cyclic import errors if the
# import happened at load time.
global _FROM_DIDL_STRING_FUNCTION # pylint: disable=global-statement
if not _FROM_DIDL_STRING_FUNCTION:
from . import data_structures_entry
_FROM_DIDL_STRING_FUNCTION = data_structures_entry.from_didl_string

ref = _FROM_DIDL_STRING_FUNCTION(
getattr(self, 'resource_meta_data'))[0]
# The resMD metadata lacks a <res> tag, so we use the resources from
# the favorite to make 'reference' playable.
ref.resources = self.resources
return ref

@reference.setter
def reference(self, value):
setattr(self, 'resource_meta_data', to_didl_string(value))
self.resources = value.resources


###############################################################################
# OBJECT.CONTAINER HIERARCHY #
###############################################################################
Expand Down Expand Up @@ -1042,6 +1049,14 @@ class DidlMusicGenre(DidlGenre):
tag = 'item'


class DidlRadioShow(DidlContainer):
"""Class that represents a radio show."""

# the DIDL Lite class for this object.
item_class = 'object.container.radioShow'
# A radio show doesn't seem to have any special attributes


###############################################################################
# SPECIAL LISTS #
###############################################################################
Expand Down
32 changes: 31 additions & 1 deletion soco/music_library.py
Expand Up @@ -37,7 +37,10 @@ class MusicLibrary(object):
'playlists': 'A:PLAYLISTS',
'share': 'S:',
'sonos_playlists': 'SQ:',
'categories': 'A:'}
'categories': 'A:',
'sonos_favorites': 'FV:2',
'radio_stations': 'R:0/0',
'radio_shows': 'R:0/1'}

# pylint: disable=invalid-name, protected-access
def __init__(self, soco=None):
Expand Down Expand Up @@ -139,6 +142,33 @@ def get_playlists(self, *args, **kwargs):
args = tuple(['playlists'] + list(args))
return self.get_music_library_information(*args, **kwargs)

def get_sonos_favorites(self, *args, **kwargs):
"""Convenience method for `get_music_library_information`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know the other docstrings in this class are broken too, but can you fix up the new ones to follow the docstring convention of:

def myfunction():
    """Short preferably one-line description

    Longer description

    Args (not applicable here)
    """

it may even have been me that wrote them that way to begin with, anyway, we should get them fixed up.

with ``search_type='sonos_favorites'``. For details of other arguments,
see `that method
<#soco.music_library.MusicLibrary.get_music_library_information>`_.
"""
args = tuple(['sonos_favorites'] + list(args))
return self.get_music_library_information(*args, **kwargs)

def get_favorite_radio_stations(self, *args, **kwargs):
"""Convenience method for `get_music_library_information`
with ``search_type='radio_stations'``. For details of other arguments,
see `that method
<#soco.music_library.MusicLibrary.get_music_library_information>`_.
"""
args = tuple(['radio_stations'] + list(args))
return self.get_music_library_information(*args, **kwargs)

def get_favorite_radio_shows(self, *args, **kwargs):
"""Convenience method for `get_music_library_information`
with ``search_type='radio_stations'``. For details of other arguments,
see `that method
<#soco.music_library.MusicLibrary.get_music_library_information>`_.
"""
args = tuple(['radio_shows'] + list(args))
return self.get_music_library_information(*args, **kwargs)

# pylint: disable=too-many-locals, too-many-arguments,
# too-many-branches

Expand Down