SoCo/SoCo — pull request #192 by KennethNielsen (2014-07-07 06:13:56+00:00)
Title: Ml browse

Hi everyone

Here it is, finally, the much talked about browse method for music library items. This PR is based of work in #191, so it should not be merged before that is in. Only the last two commits pertain to this PR.

The implementation is based on the research done by @robwebset as [discussed on google groups](https://groups.google.com/forum/#!topic/python-soco/tFnNlGTp9TU) and in #186.

The browse method allows you to get the sub-elements of any ml item like so::

```python
import soco
soc = soco.SoCo('...ipaddress..')
some_album = soc.get_albums()['item_list'][0]
tracks_in_that_album = soc.browse(some_album)
```

Calling the browse method without a ml_item will give the top level items which for the music library tree are the categories.

The only exception is that the playlists category (which contains imported playlists) cannot be browsed. Browsing a track returns nothing.

As previously discussed, I think this makes for a more intuitive interface than the one @robwebset implemented in #186 in that; you don't have to handle sub-category strings yourself, if you want the sub-elements of an item you just ask for them.

Making this browse method also makes the entire music lib implementation symmetric to that way the Wimp plugin is implemented (and I guess others could be implemented) in that it has a browse function and a few search function specific types of data.

This PR also introduces a new ML class based of a new DIDL Lite class for "object.container.playlistContainer.sameArtist". This structure is (among other items) returned when browsing an artist or composer.

If you want to test it more thoroughly you can use this code:

```python
from soco import SoCo
soc = SoCo('192.168.0.12')

def recursive_browse(item, cat, level=0):
"""Recursively browse items in the music lib"""
print '{0}: {1}{2}'.format(cat, '+' * level, item)
level += 1
for new_item in soc.browse(item)['item_list']:
recursive_browse(new_item, cat, level)

def browse_all():
"""Browse the entire tree recursively"""
cats = soc.browse()['item_list']
for index, cat in enumerate(cats):
# The Playlists category cannot for some reason not be browsed, since
# it does not have an id.
if hasattr(cat, 'item_id'):
recursive_browse(cat, index)
print 'DONE'

def test_browse_single(search_type):
"""Test browsing a single type"""
main = soc.get_music_library_information(search_type)['item_list'][0]
print '\nThe {0} \'{1}\' contains the following items:'.\
format(search_type[:-1], main)
for item in soc.browse(main)['item_list']:
print item

def test_browse():
"""Test the browse fucntionality of all supported types"""
for search_type in ['artists', 'album_artists', 'albums', 'genres',
'composers', 'tracks', 'categories']:
test_browse_single(search_type)
print 'DONE'

if __name__ == '__main__':
#test_browse()
#browse_all()
```

The ``test_browse()`` function tries to browse one item of each of the searches you can do with get_music_library_information and the ``browse_all()`` runs through the entire tree category by category.

Let me know what you think.