{"action":"created","comment":{"url":"https://api.github.com/repos/SoCo/SoCo/pulls/comments/38270579","id":38270579,"diff_hunk":"@@ -0,0 +1,780 @@\n+# -*- coding: utf-8 -*-\n+\n+\"\"\"Sonos Music Services interface.\n+\n+This module provides the MusicService class and related functionality.\n+\n+\"\"\"\n+# pylint: disable=fixme\n+\n+from __future__ import unicode_literals\n+\n+import logging\n+log = logging.getLogger(__name__)  # pylint: disable=C0103\n+\n+from xmltodict import parse\n+import requests\n+\n+from soco import SoCo\n+from soco.xml import XML\n+from soco.exceptions import MusicServiceException\n+from soco.soap import SoapMessage, SoapFault\n+\n+from soco.compat import urlparse, parse_qs\n+from soco.music_services.accounts import Account\n+\n+\n+# pylint: disable=too-many-instance-attributes, protected-access\n+class MusicServiceSoapClient(object):\n+\n+    \"\"\"A SOAP client for accessing Music Services\n+\n+    This class handles all the necessary authentication for accessing third\n+    party music services. You are unlikely to need to use it yourself.\n+\n+    \"\"\"\n+\n+    def __init__(self, endpoint, timeout, music_service):\n+        \"\"\" Constructor\n+\n+        Args:\n+             endpoint (str): The SOAP endpoint. A url.\n+             timeout (int): Timeout the connection after this number of\n+                 seconds\n+             music_service (MusicService): The MusicService object to which\n+                 this client belongs\n+\n+        \"\"\"\n+\n+        self.endpoint = endpoint\n+        self.timeout = timeout\n+        self.music_service = music_service\n+        self.namespace = 'http://www.sonos.com/Services/1.1'\n+\n+        # Spotify uses gzip. Others may do so as well. Unzipping is handled\n+        # for us by the requests library. We need to set the user agent to\n+        # a genuine Sonos value. Google Play music seems to want this. The\n+        # firmware release number (after 'Sonos/\" is obviously fake, and\n+        # higher than current values, in case Google only offers certain\n+        # services to certain firmware rel eases. \") #","path":"soco/music_services/music_service.py","position":59,"original_position":59,"commit_id":"c89f5e42713f0572a3a1360ffef3ef9cfa11b9df","original_commit_id":"c89f5e42713f0572a3a1360ffef3ef9cfa11b9df","user":{"login":"lawrenceakka","id":5252441,"avatar_url":"https://avatars.githubusercontent.com/u/5252441?v=3","gravatar_id":"","url":"https://api.github.com/users/lawrenceakka","html_url":"https://github.com/lawrenceakka","followers_url":"https://api.github.com/users/lawrenceakka/followers","following_url":"https://api.github.com/users/lawrenceakka/following{/other_user}","gists_url":"https://api.github.com/users/lawrenceakka/gists{/gist_id}","starred_url":"https://api.github.com/users/lawrenceakka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lawrenceakka/subscriptions","organizations_url":"https://api.github.com/users/lawrenceakka/orgs","repos_url":"https://api.github.com/users/lawrenceakka/repos","events_url":"https://api.github.com/users/lawrenceakka/events{/privacy}","received_events_url":"https://api.github.com/users/lawrenceakka/received_events","type":"User","site_admin":false},"body":"I can't find it in info, or from any UPnP calls.  If we can get it easily, I agree it makes sense to use a real value.","created_at":"2015-08-30T10:52:21Z","updated_at":"2015-08-30T10:52:21Z","html_url":"https://github.com/SoCo/SoCo/pull/262#discussion_r38270579","pull_request_url":"https://api.github.com/repos/SoCo/SoCo/pulls/262","_links":{"self":{"href":"https://api.github.com/repos/SoCo/SoCo/pulls/comments/38270579"},"html":{"href":"https://github.com/SoCo/SoCo/pull/262#discussion_r38270579"},"pull_request":{"href":"https://api.github.com/repos/SoCo/SoCo/pulls/262"}}},"pull_request":{"url":"https://api.github.com/repos/SoCo/SoCo/pulls/262","id":24903403,"html_url":"https://github.com/SoCo/SoCo/pull/262","diff_url":"https://github.com/SoCo/SoCo/pull/262.diff","patch_url":"https://github.com/SoCo/SoCo/pull/262.patch","issue_url":"https://api.github.com/repos/SoCo/SoCo/issues/262","number":262,"state":"open","locked":false,"title":"Universal Music services code","user":{"login":"lawrenceakka","id":5252441,"avatar_url":"https://avatars.githubusercontent.com/u/5252441?v=3","gravatar_id":"","url":"https://api.github.com/users/lawrenceakka","html_url":"https://github.com/lawrenceakka","followers_url":"https://api.github.com/users/lawrenceakka/followers","following_url":"https://api.github.com/users/lawrenceakka/following{/other_user}","gists_url":"https://api.github.com/users/lawrenceakka/gists{/gist_id}","starred_url":"https://api.github.com/users/lawrenceakka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lawrenceakka/subscriptions","organizations_url":"https://api.github.com/users/lawrenceakka/orgs","repos_url":"https://api.github.com/users/lawrenceakka/repos","events_url":"https://api.github.com/users/lawrenceakka/events{/privacy}","received_events_url":"https://api.github.com/users/lawrenceakka/received_events","type":"User","site_admin":false},"body":"Continuing the discussion from #247 , with a PR!\r\n\r\nThis implements the MusicService class for access to all the music services known to Sonos.\r\n\r\nIt does not (yet) translate the data returned by the various services to DIDL.\r\n\r\nPlease test with the various services you have access to, and let me know what you think.\r\n\r\nLawrence\r\n\r\nExamples:\r\n\r\nPrint all the services Sonos knows about\r\n\r\n```python\r\n>>> from soco.music_services import MusicService\r\n>>> from pprint import pprint\r\n>>> print (MusicService.get_all_music_services())\r\n['Spotify',\r\n 'The Hype Machine',\r\n 'Saavn',\r\n 'Bandcamp',\r\n 'Stitcher SmartRadio',\r\n 'Concert Vault',\r\n ...\r\n     ]\r\n```\r\n Or just those those to which you are subscribed (and the relevant usernames)\r\n\r\n```python\r\n>>> pprint (MusicService.get_subscribed_music_services())\r\n[('Spotify', u'XXXXXX'), ('radioPup', u''), ('Spreaker', u'')]\r\n```\r\nInteract with TuneIn\r\n\r\n```python\r\n\r\n>>> tunein = MusicService('TuneIn')\r\n>>> print tunein\r\n<MusicService 'TuneIn' at 0x10ad84e10>\r\n>>> pprint (tunein.get_metadata(item_id='root'))\r\n{'count': 5,\r\n 'index': 0,\r\n 'mediaCollection': ({'albumArtURI': u'http://spotify-...',\r\n                      'canEnumerate': True,\r\n                      'canPlay': False,\r\n                      'canScroll': False,\r\n                      'id': u'playlists',\r\n                      'itemType': u'favorites',\r\n                      'title': u'Playlists'},\r\n                     {'albumArtURI': u'http://spotify-...',\r\n                      'canEnumerate': True,\r\n                      'canPlay': True,\r\n                      'canScroll': False,\r\n                      'id': u'starred',\r\n                      'itemType': u'favorites',\r\n                      'title': u'Starred'},\r\n                    ...),\r\n 'total': 5}\r\n# Get some metadata about a track\r\n>>> response =  spotify.get_media_metadata(\r\n... item_id='spotify:track:6NmXV4o6bmp704aPGyTVVG')\r\n>>> print (response)\r\n{'mediaMetadata': [{'id': u'spotify:track:6NmXV4o6bmp704aPGyTVVG'},\r\n                   {'itemType': u'track'},\r\n                   {'title': u'B?n Fra Helvete (Live)'},\r\n                   {'mimeType': u'audio/x-spotify'},\r\n                   {'trackMetadata': {'album': u'Mann Mot Mann (Ep)',\r\n                                      'albumArtURI': u'http://o.s...9',\r\n                                      'albumId': u'spotify:album:...',\r\n                                      'artist': u'Kaizers Orchestra',\r\n                                      'artistId': u'spotify:artist...',\r\n                                      'canAddToFavorites': True,\r\n                                      'canPlay': True,\r\n                                      'canSkip': True,\r\n                                      'duration': 317}}]}\r\n```\r\n\r\nor even a playlist\r\n\r\n```python\r\n\r\n\r\n>>> response =  spotify.get_metadata(\r\n...    item_id='spotify:user:spotify:playlist:0FQk6BADgIIYd3yTLCThjg')\r\n\r\n```\r\nor a URI\r\n\r\n```python\r\n\r\n\r\n>>> response =  spotify.get_media_URI(\r\n...    item_id='spotify:track:6NmXV4o6bmp704aPGyTVVG')\r\n\r\n```\r\nFind the available search categories, and use them\r\n\r\n```python\r\n\r\n>>> pprint (spotify.available_search_categories)\r\n['albums', 'tracks', 'artists']\r\n>>> result =  spotify.search(category='artists', term='miles')\r\n\r\n```\r\n\r\n**Test Result Table**\r\n\r\nMusic Service  | Instantiate | avail. search cats | search | metadata | media meta | media uri\r\n------------------ | -------------- | ---------------------- | -------- | ------------ | --------------- | ------------\r\nDeezer      | OK | OK | OK | OK | OK | OK\r\nWiMP        | OK | OK | OK | OK    | OK | OK\r\nYouSee Play | OK | OK | OK | OK | OK | OK\r\nGoogle Play Music | OK | OK | OK | OK | OK | MIXED\r\n","created_at":"2014-11-23T16:49:15Z","updated_at":"2015-08-30T10:52:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8c09a7ff3c74d5b9b23f54dabc85f401c4a7887e","assignee":{"login":"lawrenceakka","id":5252441,"avatar_url":"https://avatars.githubusercontent.com/u/5252441?v=3","gravatar_id":"","url":"https://api.github.com/users/lawrenceakka","html_url":"https://github.com/lawrenceakka","followers_url":"https://api.github.com/users/lawrenceakka/followers","following_url":"https://api.github.com/users/lawrenceakka/following{/other_user}","gists_url":"https://api.github.com/users/lawrenceakka/gists{/gist_id}","starred_url":"https://api.github.com/users/lawrenceakka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lawrenceakka/subscriptions","organizations_url":"https://api.github.com/users/lawrenceakka/orgs","repos_url":"https://api.github.com/users/lawrenceakka/repos","events_url":"https://api.github.com/users/lawrenceakka/events{/privacy}","received_events_url":"https://api.github.com/users/lawrenceakka/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/SoCo/SoCo/pulls/262/commits","review_comments_url":"https://api.github.com/repos/SoCo/SoCo/pulls/262/comments","review_comment_url":"https://api.github.com/repos/SoCo/SoCo/pulls/comments{/number}","comments_url":"https://api.github.com/repos/SoCo/SoCo/issues/262/comments","statuses_url":"https://api.github.com/repos/SoCo/SoCo/statuses/c89f5e42713f0572a3a1360ffef3ef9cfa11b9df","head":{"label":"lawrenceakka:music_service","ref":"music_service","sha":"c89f5e42713f0572a3a1360ffef3ef9cfa11b9df","user":{"login":"lawrenceakka","id":5252441,"avatar_url":"https://avatars.githubusercontent.com/u/5252441?v=3","gravatar_id":"","url":"https://api.github.com/users/lawrenceakka","html_url":"https://github.com/lawrenceakka","followers_url":"https://api.github.com/users/lawrenceakka/followers","following_url":"https://api.github.com/users/lawrenceakka/following{/other_user}","gists_url":"https://api.github.com/users/lawrenceakka/gists{/gist_id}","starred_url":"https://api.github.com/users/lawrenceakka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lawrenceakka/subscriptions","organizations_url":"https://api.github.com/users/lawrenceakka/orgs","repos_url":"https://api.github.com/users/lawrenceakka/repos","events_url":"https://api.github.com/users/lawrenceakka/events{/privacy}","received_events_url":"https://api.github.com/users/lawrenceakka/received_events","type":"User","site_admin":false},"repo":{"id":14257987,"name":"SoCo","full_name":"lawrenceakka/SoCo","owner":{"login":"lawrenceakka","id":5252441,"avatar_url":"https://avatars.githubusercontent.com/u/5252441?v=3","gravatar_id":"","url":"https://api.github.com/users/lawrenceakka","html_url":"https://github.com/lawrenceakka","followers_url":"https://api.github.com/users/lawrenceakka/followers","following_url":"https://api.github.com/users/lawrenceakka/following{/other_user}","gists_url":"https://api.github.com/users/lawrenceakka/gists{/gist_id}","starred_url":"https://api.github.com/users/lawrenceakka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lawrenceakka/subscriptions","organizations_url":"https://api.github.com/users/lawrenceakka/orgs","repos_url":"https://api.github.com/users/lawrenceakka/repos","events_url":"https://api.github.com/users/lawrenceakka/events{/privacy}","received_events_url":"https://api.github.com/users/lawrenceakka/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/lawrenceakka/SoCo","description":"SoCo (Sonos Controller) is a simple Python class that allows you to programmatically control Sonos speakers.","fork":true,"url":"https://api.github.com/repos/lawrenceakka/SoCo","forks_url":"https://api.github.com/repos/lawrenceakka/SoCo/forks","keys_url":"https://api.github.com/repos/lawrenceakka/SoCo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/lawrenceakka/SoCo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/lawrenceakka/SoCo/teams","hooks_url":"https://api.github.com/repos/lawrenceakka/SoCo/hooks","issue_events_url":"https://api.github.com/repos/lawrenceakka/SoCo/issues/events{/number}","events_url":"https://api.github.com/repos/lawrenceakka/SoCo/events","assignees_url":"https://api.github.com/repos/lawrenceakka/SoCo/assignees{/user}","branches_url":"https://api.github.com/repos/lawrenceakka/SoCo/branches{/branch}","tags_url":"https://api.github.com/repos/lawrenceakka/SoCo/tags","blobs_url":"https://api.github.com/repos/lawrenceakka/SoCo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/lawrenceakka/SoCo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/lawrenceakka/SoCo/git/refs{/sha}","trees_url":"https://api.github.com/repos/lawrenceakka/SoCo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/lawrenceakka/SoCo/statuses/{sha}","languages_url":"https://api.github.com/repos/lawrenceakka/SoCo/languages","stargazers_url":"https://api.github.com/repos/lawrenceakka/SoCo/stargazers","contributors_url":"https://api.github.com/repos/lawrenceakka/SoCo/contributors","subscribers_url":"https://api.github.com/repos/lawrenceakka/SoCo/subscribers","subscription_url":"https://api.github.com/repos/lawrenceakka/SoCo/subscription","commits_url":"https://api.github.com/repos/lawrenceakka/SoCo/commits{/sha}","git_commits_url":"https://api.github.com/repos/lawrenceakka/SoCo/git/commits{/sha}","comments_url":"https://api.github.com/repos/lawrenceakka/SoCo/comments{/number}","issue_comment_url":"https://api.github.com/repos/lawrenceakka/SoCo/issues/comments{/number}","contents_url":"https://api.github.com/repos/lawrenceakka/SoCo/contents/{+path}","compare_url":"https://api.github.com/repos/lawrenceakka/SoCo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/lawrenceakka/SoCo/merges","archive_url":"https://api.github.com/repos/lawrenceakka/SoCo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/lawrenceakka/SoCo/downloads","issues_url":"https://api.github.com/repos/lawrenceakka/SoCo/issues{/number}","pulls_url":"https://api.github.com/repos/lawrenceakka/SoCo/pulls{/number}","milestones_url":"https://api.github.com/repos/lawrenceakka/SoCo/milestones{/number}","notifications_url":"https://api.github.com/repos/lawrenceakka/SoCo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/lawrenceakka/SoCo/labels{/name}","releases_url":"https://api.github.com/repos/lawrenceakka/SoCo/releases{/id}","created_at":"2013-11-09T13:56:22Z","updated_at":"2015-02-10T10:45:57Z","pushed_at":"2015-08-28T17:52:49Z","git_url":"git://github.com/lawrenceakka/SoCo.git","ssh_url":"git@github.com:lawrenceakka/SoCo.git","clone_url":"https://github.com/lawrenceakka/SoCo.git","svn_url":"https://github.com/lawrenceakka/SoCo","homepage":null,"size":2771,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"SoCo:master","ref":"master","sha":"c9e1b84cbb2996948d0c160a2ecb7803690522ff","user":{"login":"SoCo","id":3826649,"avatar_url":"https://avatars.githubusercontent.com/u/3826649?v=3","gravatar_id":"","url":"https://api.github.com/users/SoCo","html_url":"https://github.com/SoCo","followers_url":"https://api.github.com/users/SoCo/followers","following_url":"https://api.github.com/users/SoCo/following{/other_user}","gists_url":"https://api.github.com/users/SoCo/gists{/gist_id}","starred_url":"https://api.github.com/users/SoCo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SoCo/subscriptions","organizations_url":"https://api.github.com/users/SoCo/orgs","repos_url":"https://api.github.com/users/SoCo/repos","events_url":"https://api.github.com/users/SoCo/events{/privacy}","received_events_url":"https://api.github.com/users/SoCo/received_events","type":"Organization","site_admin":false},"repo":{"id":8763405,"name":"SoCo","full_name":"SoCo/SoCo","owner":{"login":"SoCo","id":3826649,"avatar_url":"https://avatars.githubusercontent.com/u/3826649?v=3","gravatar_id":"","url":"https://api.github.com/users/SoCo","html_url":"https://github.com/SoCo","followers_url":"https://api.github.com/users/SoCo/followers","following_url":"https://api.github.com/users/SoCo/following{/other_user}","gists_url":"https://api.github.com/users/SoCo/gists{/gist_id}","starred_url":"https://api.github.com/users/SoCo/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/SoCo/subscriptions","organizations_url":"https://api.github.com/users/SoCo/orgs","repos_url":"https://api.github.com/users/SoCo/repos","events_url":"https://api.github.com/users/SoCo/events{/privacy}","received_events_url":"https://api.github.com/users/SoCo/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/SoCo/SoCo","description":"SoCo (Sonos Controller) is a simple Python class that allows you to programmatically control Sonos speakers.","fork":false,"url":"https://api.github.com/repos/SoCo/SoCo","forks_url":"https://api.github.com/repos/SoCo/SoCo/forks","keys_url":"https://api.github.com/repos/SoCo/SoCo/keys{/key_id}","collaborators_url":"https://api.github.com/repos/SoCo/SoCo/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/SoCo/SoCo/teams","hooks_url":"https://api.github.com/repos/SoCo/SoCo/hooks","issue_events_url":"https://api.github.com/repos/SoCo/SoCo/issues/events{/number}","events_url":"https://api.github.com/repos/SoCo/SoCo/events","assignees_url":"https://api.github.com/repos/SoCo/SoCo/assignees{/user}","branches_url":"https://api.github.com/repos/SoCo/SoCo/branches{/branch}","tags_url":"https://api.github.com/repos/SoCo/SoCo/tags","blobs_url":"https://api.github.com/repos/SoCo/SoCo/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/SoCo/SoCo/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/SoCo/SoCo/git/refs{/sha}","trees_url":"https://api.github.com/repos/SoCo/SoCo/git/trees{/sha}","statuses_url":"https://api.github.com/repos/SoCo/SoCo/statuses/{sha}","languages_url":"https://api.github.com/repos/SoCo/SoCo/languages","stargazers_url":"https://api.github.com/repos/SoCo/SoCo/stargazers","contributors_url":"https://api.github.com/repos/SoCo/SoCo/contributors","subscribers_url":"https://api.github.com/repos/SoCo/SoCo/subscribers","subscription_url":"https://api.github.com/repos/SoCo/SoCo/subscription","commits_url":"https://api.github.com/repos/SoCo/SoCo/commits{/sha}","git_commits_url":"https://api.github.com/repos/SoCo/SoCo/git/commits{/sha}","comments_url":"https://api.github.com/repos/SoCo/SoCo/comments{/number}","issue_comment_url":"https://api.github.com/repos/SoCo/SoCo/issues/comments{/number}","contents_url":"https://api.github.com/repos/SoCo/SoCo/contents/{+path}","compare_url":"https://api.github.com/repos/SoCo/SoCo/compare/{base}...{head}","merges_url":"https://api.github.com/repos/SoCo/SoCo/merges","archive_url":"https://api.github.com/repos/SoCo/SoCo/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/SoCo/SoCo/downloads","issues_url":"https://api.github.com/repos/SoCo/SoCo/issues{/number}","pulls_url":"https://api.github.com/repos/SoCo/SoCo/pulls{/number}","milestones_url":"https://api.github.com/repos/SoCo/SoCo/milestones{/number}","notifications_url":"https://api.github.com/repos/SoCo/SoCo/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/SoCo/SoCo/labels{/name}","releases_url":"https://api.github.com/repos/SoCo/SoCo/releases{/id}","created_at":"2013-03-13T22:56:39Z","updated_at":"2015-08-30T09:51:56Z","pushed_at":"2015-08-30T03:48:57Z","git_url":"git://github.com/SoCo/SoCo.git","ssh_url":"git@github.com:SoCo/SoCo.git","clone_url":"https://github.com/SoCo/SoCo.git","svn_url":"https://github.com/SoCo/SoCo","homepage":"http://python-soco.com/","size":4510,"stargazers_count":374,"watchers_count":374,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":95,"mirror_url":null,"open_issues_count":45,"forks":95,"open_issues":45,"watchers":374,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/SoCo/SoCo/pulls/262"},"html":{"href":"https://github.com/SoCo/SoCo/pull/262"},"issue":{"href":"https://api.github.com/repos/SoCo/SoCo/issues/262"},"comments":{"href":"https://api.github.com/repos/SoCo/SoCo/issues/262/comments"},"review_comments":{"href":"https://api.github.com/repos/SoCo/SoCo/pulls/262/comments"},"review_comment":{"href":"https://api.github.com/repos/SoCo/SoCo/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/SoCo/SoCo/pulls/262/commits"},"statuses":{"href":"https://api.github.com/repos/SoCo/SoCo/statuses/c89f5e42713f0572a3a1360ffef3ef9cfa11b9df"}}}}