From 2c4ac7b4c8177ea81729d08b2d6c9f2b3213fa29 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sun, 31 May 2015 16:40:57 +0200 Subject: [PATCH] Initial version of plugin, based on https://github.com/DarkAllMan/SubKodi. --- addon.py | 414 ++++++ addon.xml | 21 + fanart.jpg | Bin 0 -> 119754 bytes icon.png | Bin 0 -> 31311 bytes lib/libsonic/__init__.py | 32 + lib/libsonic/connection.py | 2441 ++++++++++++++++++++++++++++++++ lib/libsonic/errors.py | 59 + lib/libsonic_extra/__init__.py | 231 +++ resources/settings.xml | 9 + 9 files changed, 3207 insertions(+) create mode 100644 addon.py create mode 100644 addon.xml create mode 100644 fanart.jpg create mode 100644 icon.png create mode 100644 lib/libsonic/__init__.py create mode 100644 lib/libsonic/connection.py create mode 100644 lib/libsonic/errors.py create mode 100644 lib/libsonic_extra/__init__.py create mode 100644 resources/settings.xml diff --git a/addon.py b/addon.py new file mode 100644 index 0000000..eefaec6 --- /dev/null +++ b/addon.py @@ -0,0 +1,414 @@ +import os +import sys +import urllib +import urlparse + +import xbmc +import xbmcgui +import xbmcaddon +import xbmcplugin + +# Make sure library folder is on the path +addon = xbmcaddon.Addon("plugin.audio.subsonic") +sys.path.append(xbmc.translatePath(os.path.join( + addon.getAddonInfo("path"), "lib"))) + +import libsonic_extra + + +class Plugin(object): + """ + Plugin container + """ + + def __init__(self, addon_url, addon_handle, addon_args): + self.addon_url = addon_url + self.addon_handle = addon_handle + self.addon_args = addon_args + + # Retrieve plugin settings + self.url = addon.getSetting("subsonic_url") + self.username = addon.getSetting("username") + self.password = addon.getSetting("password") + + self.random_count = addon.getSetting("random_count") + self.bitrate = addon.getSetting("bitrate") + self.trans_format = addon.getSetting("trans_format") + + # Create connection + self.connection = libsonic_extra.Connection( + self.url, self.username, self.password) + + def build_url(self, query): + """ + """ + + parts = list(urlparse.urlparse(self.addon_url)) + parts[4] = urllib.urlencode(query) + + return urlparse.urlunparse(parts) + + def walk_genres(self): + """ + Request Subsonic's genres list and iterate each item. + """ + + response = self.connection.getGenres() + + for genre in response["genres"]["genre"]: + yield genre + + def walk_artists(self): + """ + Request SubSonic's index and iterate each item. + """ + + response = self.connection.getArtists() + + for index in response["artists"]["index"]: + for artist in index["artist"]: + yield artist + + def walk_album_list2_genre(self, genre): + """ + """ + + offset = 0 + + while True: + response = self.connection.getAlbumList2( + ltype="byGenre", genre=genre, size=500, offset=offset) + + if not response["albumList2"]["album"]: + break + + for album in response["albumList2"]["album"]: + yield album + + offset += 500 + + def walk_album(self, album_id): + """ + Request Album and iterate each song. + """ + + response = self.connection.getAlbum(album_id) + + for song in response["album"]["song"]: + yield song + + def walk_playlists(self): + """ + Request SubSonic's playlists and iterate over each item. + """ + + response = self.connection.getPlaylists() + + for child in response["playlists"]["playlist"]: + yield child + + def walk_playlist(self, playlist_id): + """ + Request SubSonic's playlist items and iterate over each item. + """ + + response = self.connection.getPlaylist(playlist_id) + + for order, child in enumerate(response["playlist"]["entry"], start=1): + child["order"] = order + yield child + + def walk_directory(self, directory_id): + """ + Request a SubSonic music directory and iterate over each item. + """ + + response = self.connection.getMusicDirectory(directory_id) + + for child in response["directory"]["child"]: + if child.get("isDir"): + for child in self.walk_directory(child["id"]): + yield child + else: + yield child + + def walk_artist(self, artist_id): + """ + Request a SubSonic artist and iterate over each album. + """ + + response = self.connection.getArtist(artist_id) + + for child in response["artist"]["album"]: + yield child + + def walk_random_songs(self, size, genre=None, from_year=None, + to_year=None): + """ + """ + + response = self.connection.getRandomSongs( + size=size, genre=genre, fromYear=from_year, toYear=to_year) + + for song in response["randomSongs"]["song"]: + song["id"] = int(song["id"]) + + yield song + + def route(self): + mode = self.addon_args.get("mode", ["main_page"])[0] + getattr(self, mode)() + + def add_track(self, track, show_artist=False): + """ + """ + + cover_art_url = self.connection.getCoverArtUrl(track["id"]) + url = self.connection.streamUrl( + sid=track["id"], maxBitRate=self.bitrate, + tformat=self.trans_format) + + if show_artist: + li = xbmcgui.ListItem(track["artist"] + " - " + track["title"]) + else: + li = xbmcgui.ListItem(track["title"]) + + li.setIconImage(cover_art_url) + li.setThumbnailImage(cover_art_url) + li.setProperty("fanart_image", cover_art_url) + li.setProperty("IsPlayable", "true") + li.setInfo(type="Music", infoLabels={ + "Artist": track["artist"], + "Title": track["title"], + "Year": track.get("year"), + "Duration": track.get("duration"), + "Genre": track.get("genre")}) + + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li) + + def add_album(self, album, show_artist=False): + cover_art_url = self.connection.getCoverArtUrl(album["id"]) + url = self.build_url({ + "mode": "track_list", + "album_id": album["id"]}) + + if show_artist: + li = xbmcgui.ListItem(album["artist"] + " - " + album["name"]) + else: + li = xbmcgui.ListItem(album["name"]) + + li.setIconImage(cover_art_url) + li.setThumbnailImage(cover_art_url) + li.setProperty("fanart_image", cover_art_url) + + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li, isFolder=True) + + def main_page(self): + """ + Display main menu. + """ + + menu = [ + {"mode": "playlists_list", "foldername": "Playlists"}, + {"mode": "artist_list", "foldername": "Artists"}, + {"mode": "genre_list", "foldername": "Genres"}, + {"mode": "random_list", "foldername": "Random songs"}] + + for entry in menu: + url = self.build_url(entry) + + li = xbmcgui.ListItem(entry["foldername"]) + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li, isFolder=True) + + xbmcplugin.endOfDirectory(self.addon_handle) + + def playlists_list(self): + """ + Display playlists. + """ + + for playlist in self.walk_playlists(): + cover_art_url = self.connection.getCoverArtUrl( + playlist["coverArt"]) + url = self.build_url({ + "mode": "playlist_list", "playlist_id": playlist["id"]}) + + li = xbmcgui.ListItem(playlist["name"], iconImage=cover_art_url) + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li, isFolder=True) + + xbmcplugin.endOfDirectory(self.addon_handle) + + def playlist_list(self): + """ + Display playlist tracks. + """ + + playlist_id = self.addon_args["playlist_id"][0] + + for track in self.walk_playlist(playlist_id): + self.add_track(track, show_artist=True) + + xbmcplugin.setContent(self.addon_handle, "songs") + xbmcplugin.endOfDirectory(self.addon_handle) + + def genre_list(self): + """ + Display list of genres menu. + """ + + for genre in self.walk_genres(): + url = self.build_url({ + "mode": "albums_by_genre_list", + "foldername": genre["value"].encode("utf-8")}) + + li = xbmcgui.ListItem(genre["value"]) + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li, isFolder=True) + + xbmcplugin.endOfDirectory(self.addon_handle) + + def albums_by_genre_list(self): + """ + Display album list by genre menu. + """ + + genre = self.addon_args["foldername"][0].decode("utf-8") + + for album in self.walk_album_list2_genre(genre): + self.add_album(album, show_artist=True) + + xbmcplugin.setContent(self.addon_handle, "albums") + xbmcplugin.endOfDirectory(self.addon_handle) + + def artist_list(self): + """ + Display artist list + """ + + for artist in self.walk_artists(): + cover_art_url = self.connection.getCoverArtUrl(artist["id"]) + url = self.build_url({ + "mode": "album_list", + "artist_id": artist["id"]}) + + li = xbmcgui.ListItem(artist["name"]) + li.setIconImage(cover_art_url) + li.setThumbnailImage(cover_art_url) + li.setProperty("fanart_image", cover_art_url) + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li, isFolder=True) + + xbmcplugin.setContent(self.addon_handle, "artists") + xbmcplugin.endOfDirectory(self.addon_handle) + + def album_list(self): + """ + Display list of albums for certain artist. + """ + + artist_id = self.addon_args["artist_id"][0] + + for album in self.walk_artist(artist_id): + self.add_album(album) + + xbmcplugin.setContent(self.addon_handle, "albums") + xbmcplugin.endOfDirectory(self.addon_handle) + + def track_list(self): + """ + Display track list. + """ + + album_id = self.addon_args["album_id"][0] + + for track in self.walk_album(album_id): + self.add_track(track) + + xbmcplugin.setContent(self.addon_handle, "songs") + xbmcplugin.endOfDirectory(self.addon_handle) + + def random_list(self): + """ + Display random options. + """ + + menu = [ + {"mode": "random_by_genre_list", "foldername": "By genre"}, + {"mode": "random_by_year_list", "foldername": "By year"}] + + for entry in menu: + url = self.build_url(entry) + + li = xbmcgui.ListItem(entry["foldername"]) + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li, isFolder=True) + + xbmcplugin.endOfDirectory(self.addon_handle) + + def random_by_genre_list(self): + """ + Display random genre list. + """ + + for genre in self.walk_genres(): + url = self.build_url({ + "mode": "random_by_genre_track_list", + "foldername": genre["value"].encode("utf-8")}) + + li = xbmcgui.ListItem(genre["value"]) + xbmcplugin.addDirectoryItem( + handle=self.addon_handle, url=url, listitem=li, isFolder=True) + + xbmcplugin.endOfDirectory(self.addon_handle) + + def random_by_genre_track_list(self): + """ + Display random tracks by genre + """ + + genre = self.addon_args["foldername"][0].decode("utf-8") + + for track in self.walk_random_songs( + size=self.random_count, genre=genre): + self.add_track(track, show_artist=True) + + xbmcplugin.setContent(self.addon_handle, "songs") + xbmcplugin.endOfDirectory(self.addon_handle) + + def random_by_year_list(self): + """ + Display random tracks by year. + """ + + from_year = xbmcgui.Dialog().input( + "From year", type=xbmcgui.INPUT_NUMERIC) + to_year = xbmcgui.Dialog().input( + "To year", type=xbmcgui.INPUT_NUMERIC) + + for track in self.walk_random_songs( + size=self.random_count, from_year=from_year, to_year=to_year): + self.add_track(track, show_artist=True) + + xbmcplugin.setContent(self.addon_handle, "songs") + xbmcplugin.endOfDirectory(self.addon_handle) + + +def main(): + """ + Entry point for this plugin. + """ + + addon_url = sys.argv[0] + addon_handle = int(sys.argv[1]) + addon_args = urlparse.parse_qs(sys.argv[2][1:]) + + # Route request to action + Plugin(addon_url, addon_handle, addon_args).route() + +# Start plugin from Kodi +if __name__ == "__main__": + main() diff --git a/addon.xml b/addon.xml new file mode 100644 index 0000000..51d1f79 --- /dev/null +++ b/addon.xml @@ -0,0 +1,21 @@ + + + + + + + audio + + + Subsonic music addon for Kodi. + Subsonic music addon for Kodi. + + + all + MIT + + + + + + diff --git a/fanart.jpg b/fanart.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1de02c32aaf61a6bbffbf4c2a664ce2453a93771 GIT binary patch literal 119754 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 literal 0 HcmV?d00001 diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8b5a6ea9ec45b28b3c84ab4775e06b07154b0c93 GIT binary patch literal 31311 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 RTiyTw002ovPDHLkV1nybf*=3@ literal 0 HcmV?d00001 diff --git a/lib/libsonic/__init__.py b/lib/libsonic/__init__.py new file mode 100644 index 0000000..503f141 --- /dev/null +++ b/lib/libsonic/__init__.py @@ -0,0 +1,32 @@ +""" +This file is part of py-sonic. + +py-sonic is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +py-sonic is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with py-sonic. If not, see + +For information on method calls, see 'pydoc libsonic.connection' + +---------- +Basic example: +---------- + +import libsonic + +conn = libsonic.Connection('http://localhost' , 'admin' , 'password') +print conn.ping() + +""" + +from connection import * + +__version__ = '0.3.3' diff --git a/lib/libsonic/connection.py b/lib/libsonic/connection.py new file mode 100644 index 0000000..0383c7c --- /dev/null +++ b/lib/libsonic/connection.py @@ -0,0 +1,2441 @@ +""" +This file is part of py-sonic. + +py-sonic is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +py-sonic is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with py-sonic. If not, see +""" + +from base64 import b64encode +from urllib import urlencode +from .errors import * +from pprint import pprint +from cStringIO import StringIO +import json , urllib2, httplib, logging, socket, ssl + +API_VERSION = '1.11.0' + +logger = logging.getLogger(__name__) + +class HTTPSConnectionChain(httplib.HTTPSConnection): + _preferred_ssl_protos = ( + ('TLSv1' , ssl.PROTOCOL_TLSv1) , + ('SSLv3' , ssl.PROTOCOL_SSLv3) , + ('SSLv23' , ssl.PROTOCOL_SSLv23) , + ) + _ssl_working_proto = None + + def connect(self): + sock = socket.create_connection((self.host, self.port), self.timeout) + if self._tunnel_host: + self.sock = sock + self._tunnel() + if self._ssl_working_proto is not None: + # If we have a working proto, let's use that straight away + logger.debug("Using known working proto: '%s'", + self._ssl_working_proto) + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, + ssl_version=self._ssl_working_proto) + return + # Try connecting via the different SSL protos in preference order + for proto_name , proto in self._preferred_ssl_protos: + try: + self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, + ssl_version=proto) + except: + pass + else: + # Cache the working ssl version + HTTPSConnectionChain._ssl_working_proto = proto + break + + +class HTTPSHandlerChain(urllib2.HTTPSHandler): + def https_open(self , req): + return self.do_open(HTTPSConnectionChain, req) + +# install opener +urllib2.install_opener(urllib2.build_opener(HTTPSHandlerChain())) + +class PysHTTPRedirectHandler(urllib2.HTTPRedirectHandler): + """ + This class is used to override the default behavior of the + HTTPRedirectHandler, which does *not* redirect POST data + """ + def redirect_request(self, req, fp, code, msg, headers, newurl): + m = req.get_method() + if (code in (301, 302, 303, 307) and m in ("GET", "HEAD") + or code in (301, 302, 303) and m == "POST"): + newurl = newurl.replace(' ', '%20') + newheaders = dict((k,v) for k,v in req.headers.items() + if k.lower() not in ("content-length", "content-type") + ) + data = None + if req.has_data(): + data = req.get_data() + return urllib2.Request(newurl, + data=data, + headers=newheaders, + origin_req_host=req.get_origin_req_host(), + unverifiable=True) + else: + raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp) + +class Connection(object): + def __init__(self , baseUrl , username , password , port=4040 , + serverPath='/rest' , appName='py-sonic' , apiVersion=API_VERSION): + """ + This will create a connection to your subsonic server + + baseUrl:str The base url for your server. Be sure to use + "https" for SSL connections. If you are using + a port other than the default 4040, be sure to + specify that with the port argument. Do *not* + append it here. + + ex: http://subsonic.example.com + + If you are running subsonic under a different + path, specify that with the "serverPath" arg, + *not* here. For example, if your subsonic + lives at: + + https://mydomain.com:8080/path/to/subsonic/rest + + You would set the following: + + baseUrl = "https://mydomain.com" + port = 8080 + serverPath = "/path/to/subsonic/rest" + username:str The username to use for the connection + password:str The password to use for the connection + port:int The port number to connect on. The default for + unencrypted subsonic connections is 4040 + serverPath:str The base resource path for the subsonic views. + This is useful if you have your subsonic server + behind a proxy and the path that you are proxying + is different from the default of '/rest'. + Ex: + serverPath='/path/to/subs' + + The full url that would be built then would be + (assuming defaults and using "example.com" and + you are using the "ping" view): + + http://example.com:4040/path/to/subs/ping.view + appName:str The name of your application. + apiVersion:str The API version you wish to use for your + application. Subsonic will throw an error if you + try to use/send an api version higher than what + the server supports. See the Subsonic API docs + to find the Subsonic version -> API version table. + This is useful if you are connecting to an older + version of Subsonic. + """ + self._baseUrl = baseUrl + self._username = username + self._rawPass = password + self._port = int(port) + self._apiVersion = apiVersion + self._appName = appName + self._serverPath = serverPath.strip('/') + self._opener = self._getOpener(self._username , self._rawPass) + + # Properties + def setBaseUrl(self , url): + self._baseUrl = url + self._opener = self._getOpener(self._username , self._rawPass) + baseUrl = property(lambda s: s._baseUrl , setBaseUrl) + + def setPort(self , port): + self._port = int(port) + port = property(lambda s: s._port , setPort) + + def setUsername(self , username): + self._username = username + self._opener = self._getOpener(self._username , self._rawPass) + username = property(lambda s: s._username , setUsername) + + def setPassword(self , password): + self._rawPass = password + # Redo the opener with the new creds + self._opener = self._getOpener(self._username , self._rawPass) + password = property(lambda s: s._rawPass , setPassword) + + apiVersion = property(lambda s: s._apiVersion) + + def setAppName(self , appName): + self._appName = appName + appName = property(lambda s: s._appName , setAppName) + + def setServerPath(self , path): + self._serverPath = path.strip('/') + serverPath = property(lambda s: s._serverPath , setServerPath) + + # API methods + def ping(self): + """ + since: 1.0.0 + + Returns a boolean True if the server is alive, False otherwise + """ + methodName = 'ping' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + try: + res = self._doInfoReq(req) + except: + return False + if res['status'] == 'ok': + return True + elif res['status'] == 'failed': + raise getExcByCode(res['error']['code']) + return False + + def getLicense(self): + """ + since: 1.0.0 + + Gets details related to the software license + + Returns a dict like the following: + + {u'license': {u'date': u'2010-05-21T11:14:39', + u'email': u'email@example.com', + u'key': u'12345678901234567890123456789012', + u'valid': True}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getLicense' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getMusicFolders(self): + """ + since: 1.0.0 + + Returns all configured music folders + + Returns a dict like the following: + + {u'musicFolders': {u'musicFolder': [{u'id': 0, u'name': u'folder1'}, + {u'id': 1, u'name': u'folder2'}, + {u'id': 2, u'name': u'folder3'}]}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getMusicFolders' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getNowPlaying(self): + """ + since: 1.0.0 + + Returns what is currently being played by all users + + Returns a dict like the following: + + {u'nowPlaying': {u'entry': {u'album': u"Jazz 'Round Midnight 12", + u'artist': u'Astrud Gilberto', + u'bitRate': 172, + u'contentType': u'audio/mpeg', + u'coverArt': u'98349284', + u'duration': 325, + u'genre': u'Jazz', + u'id': u'2424324', + u'isDir': False, + u'isVideo': False, + u'minutesAgo': 0, + u'parent': u'542352', + u'path': u"Astrud Gilberto/Jazz 'Round Midnight 12/01 - The Girl From Ipanema.mp3", + u'playerId': 1, + u'size': 7004089, + u'suffix': u'mp3', + u'title': u'The Girl From Ipanema', + u'track': 1, + u'username': u'user1', + u'year': 1996}}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getNowPlaying' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getIndexes(self , musicFolderId=None , ifModifiedSince=0): + """ + since: 1.0.0 + + Returns an indexed structure of all artists + + musicFolderId:int If this is specified, it will only return + artists for the given folder ID from + the getMusicFolders call + ifModifiedSince:int If specified, return a result if the artist + collection has changed since the given time + + Returns a dict like the following: + + {u'indexes': {u'index': [{u'artist': [{u'id': u'29834728934', + u'name': u'A Perfect Circle'}, + {u'id': u'238472893', + u'name': u'A Small Good Thing'}, + {u'id': u'9327842983', + u'name': u'A Tribe Called Quest'}, + {u'id': u'29348729874', + u'name': u'A-Teens, The'}, + {u'id': u'298472938', + u'name': u'ABA STRUCTURE'}] , + u'lastModified': 1303318347000L}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getIndexes' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'musicFolderId': musicFolderId , + 'ifModifiedSince': self._ts2milli(ifModifiedSince)}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getMusicDirectory(self , mid): + """ + since: 1.0.0 + + Returns a listing of all files in a music directory. Typically used + to get a list of albums for an artist or list of songs for an album. + + mid:str The string ID value which uniquely identifies the + folder. Obtained via calls to getIndexes or + getMusicDirectory. REQUIRED + + Returns a dict like the following: + + {u'directory': {u'child': [{u'artist': u'A Tribe Called Quest', + u'coverArt': u'223484', + u'id': u'329084', + u'isDir': True, + u'parent': u'234823940', + u'title': u'Beats, Rhymes And Life'}, + {u'artist': u'A Tribe Called Quest', + u'coverArt': u'234823794', + u'id': u'238472893', + u'isDir': True, + u'parent': u'2308472938', + u'title': u'Midnight Marauders'}, + {u'artist': u'A Tribe Called Quest', + u'coverArt': u'39284792374', + u'id': u'983274892', + u'isDir': True, + u'parent': u'9823749', + u'title': u"People's Instinctive Travels And The Paths Of Rhythm"}, + {u'artist': u'A Tribe Called Quest', + u'coverArt': u'289347293', + u'id': u'3894723934', + u'isDir': True, + u'parent': u'9832942', + u'title': u'The Anthology'}, + {u'artist': u'A Tribe Called Quest', + u'coverArt': u'923847923', + u'id': u'29834729', + u'isDir': True, + u'parent': u'2934872893', + u'title': u'The Love Movement'}, + {u'artist': u'A Tribe Called Quest', + u'coverArt': u'9238742893', + u'id': u'238947293', + u'isDir': True, + u'parent': u'9432878492', + u'title': u'The Low End Theory'}], + u'id': u'329847293', + u'name': u'A Tribe Called Quest'}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getMusicDirectory' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName , {'id': mid}) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def search(self , artist=None , album=None , title=None , any=None , + count=20 , offset=0 , newerThan=None): + """ + since: 1.0.0 + + DEPRECATED SINCE API 1.4.0! USE search2() INSTEAD! + + Returns a listing of files matching the given search criteria. + Supports paging with offset + + artist:str Search for artist + album:str Search for album + title:str Search for title of song + any:str Search all fields + count:int Max number of results to return [default: 20] + offset:int Search result offset. For paging [default: 0] + newerThan:int Return matches newer than this timestamp + """ + if artist == album == title == any == None: + raise ArgumentError('Invalid search. You must supply search ' + 'criteria') + methodName = 'search' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'artist': artist , 'album': album , + 'title': title , 'any': any , 'count': count , 'offset': offset , + 'newerThan': self._ts2milli(newerThan)}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def search2(self , query , artistCount=20 , artistOffset=0 , albumCount=20 , + albumOffset=0 , songCount=20 , songOffset=0): + """ + since: 1.4.0 + + Returns albums, artists and songs matching the given search criteria. + Supports paging through the result. + + query:str The search query + artistCount:int Max number of artists to return [default: 20] + artistOffset:int Search offset for artists (for paging) [default: 0] + albumCount:int Max number of albums to return [default: 20] + albumOffset:int Search offset for albums (for paging) [default: 0] + songCount:int Max number of songs to return [default: 20] + songOffset:int Search offset for songs (for paging) [default: 0] + + Returns a dict like the following: + + {u'searchResult2': {u'album': [{u'artist': u'A Tribe Called Quest', + u'coverArt': u'289347', + u'id': u'32487298', + u'isDir': True, + u'parent': u'98374289', + u'title': u'The Love Movement'}], + u'artist': [{u'id': u'2947839', + u'name': u'A Tribe Called Quest'}, + {u'id': u'239847239', + u'name': u'Tribe'}], + u'song': [{u'album': u'Beats, Rhymes And Life', + u'artist': u'A Tribe Called Quest', + u'bitRate': 224, + u'contentType': u'audio/mpeg', + u'coverArt': u'329847', + u'duration': 148, + u'genre': u'default', + u'id': u'3928472893', + u'isDir': False, + u'isVideo': False, + u'parent': u'23984728394', + u'path': u'A Tribe Called Quest/Beats, Rhymes And Life/A Tribe Called Quest - Beats, Rhymes And Life - 03 - Motivators.mp3', + u'size': 4171913, + u'suffix': u'mp3', + u'title': u'Motivators', + u'track': 3}]}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'search2' + viewName = '%s.view' % methodName + + q = {'query': query , 'artistCount': artistCount , + 'artistOffset': artistOffset , 'albumCount': albumCount , + 'albumOffset': albumOffset , 'songCount': songCount , + 'songOffset': songOffset} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def search3(self , query , artistCount=20 , artistOffset=0 , albumCount=20 , + albumOffset=0 , songCount=20 , songOffset=0): + """ + since: 1.8.0 + + Works the same way as search2, but uses ID3 tags for + organization + + query:str The search query + artistCount:int Max number of artists to return [default: 20] + artistOffset:int Search offset for artists (for paging) [default: 0] + albumCount:int Max number of albums to return [default: 20] + albumOffset:int Search offset for albums (for paging) [default: 0] + songCount:int Max number of songs to return [default: 20] + songOffset:int Search offset for songs (for paging) [default: 0] + + Returns a dict like the following (search for "Tune Yards": + {u'searchResult3': {u'album': [{u'artist': u'Tune-Yards', + u'artistId': 1, + u'coverArt': u'al-7', + u'created': u'2012-01-30T12:35:33', + u'duration': 3229, + u'id': 7, + u'name': u'Bird-Brains', + u'songCount': 13}, + {u'artist': u'Tune-Yards', + u'artistId': 1, + u'coverArt': u'al-8', + u'created': u'2011-03-22T15:08:00', + u'duration': 2531, + u'id': 8, + u'name': u'W H O K I L L', + u'songCount': 10}], + u'artist': {u'albumCount': 2, + u'coverArt': u'ar-1', + u'id': 1, + u'name': u'Tune-Yards'}, + u'song': [{u'album': u'Bird-Brains', + u'albumId': 7, + u'artist': u'Tune-Yards', + u'artistId': 1, + u'bitRate': 160, + u'contentType': u'audio/mpeg', + u'coverArt': 105, + u'created': u'2012-01-30T12:35:33', + u'duration': 328, + u'genre': u'Lo-Fi', + u'id': 107, + u'isDir': False, + u'isVideo': False, + u'parent': 105, + u'path': u'Tune Yards/Bird-Brains/10-tune-yards-fiya.mp3', + u'size': 6588498, + u'suffix': u'mp3', + u'title': u'Fiya', + u'track': 10, + u'type': u'music', + u'year': 2009}]}, + + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'search3' + viewName = '%s.view' % methodName + + q = {'query': query , 'artistCount': artistCount , + 'artistOffset': artistOffset , 'albumCount': albumCount , + 'albumOffset': albumOffset , 'songCount': songCount , + 'songOffset': songOffset} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getPlaylists(self , username=None): + """ + since: 1.0.0 + + Returns the ID and name of all saved playlists + The "username" option was added in 1.8.0. + + username:str If specified, return playlists for this user + rather than for the authenticated user. The + authenticated user must have admin role + if this parameter is used + + Returns a dict like the following: + + {u'playlists': {u'playlist': [{u'id': u'62656174732e6d3375', + u'name': u'beats'}, + {u'id': u'766172696574792e6d3375', + u'name': u'variety'}]}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getPlaylists' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'username': username}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getPlaylist(self , pid): + """ + since: 1.0.0 + + Returns a listing of files in a saved playlist + + id:str The ID of the playlist as returned in getPlaylists() + + Returns a dict like the following: + + {u'playlist': {u'entry': {u'album': u'The Essential Bob Dylan', + u'artist': u'Bob Dylan', + u'bitRate': 32, + u'contentType': u'audio/mpeg', + u'coverArt': u'2983478293', + u'duration': 984, + u'genre': u'Classic Rock', + u'id': u'982739428', + u'isDir': False, + u'isVideo': False, + u'parent': u'98327428974', + u'path': u"Bob Dylan/Essential Bob Dylan Disc 1/Bob Dylan - The Essential Bob Dylan - 03 - The Times They Are A-Changin'.mp3", + u'size': 3921899, + u'suffix': u'mp3', + u'title': u"The Times They Are A-Changin'", + u'track': 3}, + u'id': u'44796c616e2e6d3375', + u'name': u'Dylan'}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getPlaylist' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName , {'id': pid}) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def createPlaylist(self , playlistId=None , name=None , songIds=[]): + """ + since: 1.2.0 + + Creates OR updates a playlist. If updating the list, the + playlistId is required. If creating a list, the name is required. + + playlistId:str The ID of the playlist to UPDATE + name:str The name of the playlist to CREATE + songIds:list The list of songIds to populate the list with in + either create or update mode. Note that this + list will replace the existing list if updating + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'createPlaylist' + viewName = '%s.view' % methodName + + if playlistId == name == None: + raise ArgumentError('You must supply either a playlistId or a name') + if playlistId is not None and name is not None: + raise ArgumentError('You can only supply either a playlistId ' + 'OR a name, not both') + + q = self._getQueryDict({'playlistId': playlistId , 'name': name}) + + req = self._getRequestWithList(viewName , 'songId' , songIds , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def deletePlaylist(self , pid): + """ + since: 1.2.0 + + Deletes a saved playlist + + pid:str ID of the playlist to delete, as obtained by getPlaylists + + Returns a dict like the following: + + """ + methodName = 'deletePlaylist' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName , {'id': pid}) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def download(self , sid): + """ + since: 1.0.0 + + Downloads a given music file. + + sid:str The ID of the music file to download. + + Returns the file-like object for reading or raises an exception + on error + """ + methodName = 'download' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName , {'id': sid}) + res = self._doBinReq(req) + if isinstance(res , dict): + self._checkStatus(res) + return res + + def stream(self , sid , maxBitRate=0 , tformat=None , timeOffset=None , + size=None , estimateContentLength=False): + """ + since: 1.0.0 + + Downloads a given music file. + + sid:str The ID of the music file to download. + maxBitRate:int (since: 1.2.0) If specified, the server will + attempt to limit the bitrate to this value, in + kilobits per second. If set to zero (default), no limit + is imposed. Legal values are: 0, 32, 40, 48, 56, 64, + 80, 96, 112, 128, 160, 192, 224, 256 and 320. + tformat:str (since: 1.6.0) Specifies the target format + (e.g. "mp3" or "flv") in case there are multiple + applicable transcodings (since: 1.9.0) You can use + the special value "raw" to disable transcoding + timeOffset:int (since: 1.6.0) Only applicable to video + streaming. Start the stream at the given + offset (in seconds) into the video + size:str (since: 1.6.0) The requested video size in + WxH, for instance 640x480 + estimateContentLength:bool (since: 1.8.0) If set to True, + the HTTP Content-Length header + will be set to an estimated + value for trancoded media + + Returns the file-like object for reading or raises an exception + on error + """ + methodName = 'stream' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': sid , 'maxBitRate': maxBitRate , + 'format': tformat , 'timeOffset': timeOffset , 'size': size , + 'estimateContentLength': estimateContentLength}) + + req = self._getRequest(viewName , q) + res = self._doBinReq(req) + if isinstance(res , dict): + self._checkStatus(res) + return res + + def getCoverArt(self , aid , size=None): + """ + since: 1.0.0 + + Returns a cover art image + + aid:str ID string for the cover art image to download + size:int If specified, scale image to this size + + Returns the file-like object for reading or raises an exception + on error + """ + methodName = 'getCoverArt' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': aid , 'size': size}) + + req = self._getRequest(viewName , q) + res = self._doBinReq(req) + if isinstance(res , dict): + self._checkStatus(res) + return res + + def scrobble(self , sid , submission=True , listenTime=None): + """ + since: 1.5.0 + + "Scrobbles" a given music file on last.fm. Requires that the user + has set this up. + + Since 1.8.0 you may specify multiple id (and optionally time) + parameters to scrobble multiple files. + + Since 1.11.0 this method will also update the play count and + last played timestamp for the song and album. It will also make + the song appear in the "Now playing" page in the web app, and + appear in the list of songs returned by getNowPlaying + + sid:str The ID of the file to scrobble + submission:bool Whether this is a "submission" or a "now playing" + notification + listenTime:int (Since 1.8.0) The time (unix timestamp) at + which the song was listened to. + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'scrobble' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': sid , 'submission': submission , + 'time': self._ts2milli(listenTime)}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def changePassword(self , username , password): + """ + since: 1.1.0 + + Changes the password of an existing Subsonic user. Note that the + user performing this must have admin privileges + + username:str The username whose password is being changed + password:str The new password of the user + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'changePassword' + viewName = '%s.view' % methodName + hexPass = 'enc:%s' % self._hexEnc(password) + + # There seems to be an issue with some subsonic implementations + # not recognizing the "enc:" precursor to the encoded password and + # encodes the whole "enc:" as the password. Weird. + #q = {'username': username , 'password': hexPass.lower()} + q = {'username': username , 'password': password} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getUser(self , username): + """ + since: 1.3.0 + + Get details about a given user, including which auth roles it has. + Can be used to enable/disable certain features in the client, such + as jukebox control + + username:str The username to retrieve. You can only retrieve + your own user unless you have admin privs. + + Returns a dict like the following: + + {u'status': u'ok', + u'user': {u'adminRole': False, + u'commentRole': False, + u'coverArtRole': False, + u'downloadRole': True, + u'jukeboxRole': False, + u'playlistRole': True, + u'podcastRole': False, + u'settingsRole': True, + u'streamRole': True, + u'uploadRole': True, + u'username': u'test'}, + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getUser' + viewName = '%s.view' % methodName + + q = {'username': username} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getUsers(self): + """ + since 1.8.0 + + Gets a list of users + + returns a dict like the following + + {u'status': u'ok', + u'users': {u'user': [{u'adminRole': True, + u'commentRole': True, + u'coverArtRole': True, + u'downloadRole': True, + u'jukeboxRole': True, + u'playlistRole': True, + u'podcastRole': True, + u'scrobblingEnabled': True, + u'settingsRole': True, + u'shareRole': True, + u'streamRole': True, + u'uploadRole': True, + u'username': u'user1'}, + ... + ... + ]} , + u'version': u'1.10.2', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getUsers' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def createUser(self , username , password , email , + ldapAuthenticated=False , adminRole=False , settingsRole=True , + streamRole=True , jukeboxRole=False , downloadRole=False , + uploadRole=False , playlistRole=False , coverArtRole=False , + commentRole=False , podcastRole=False , shareRole=False): + """ + since: 1.1.0 + + Creates a new subsonic user, using the parameters defined. See the + documentation at http://subsonic.org for more info on all the roles. + + username:str The username of the new user + password:str The password for the new user + email:str The email of the new user + + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'createUser' + viewName = '%s.view' % methodName + hexPass = 'enc:%s' % self._hexEnc(password) + + q = {'username': username , 'password': hexPass , 'email': email , + 'ldapAuthenticated': ldapAuthenticated , 'adminRole': adminRole , + 'settingsRole': settingsRole , 'streamRole': streamRole , + 'jukeboxRole': jukeboxRole , 'downloadRole': downloadRole , + 'uploadRole': uploadRole , 'playlistRole': playlistRole , + 'coverArtRole': coverArtRole , 'commentRole': commentRole , + 'podcastRole': podcastRole , 'shareRole': shareRole} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def updateUser(self , username , password=None , email=None , + ldapAuthenticated=False , adminRole=False , settingsRole=True , + streamRole=True , jukeboxRole=False , downloadRole=False , + uploadRole=False , playlistRole=False , coverArtRole=False , + commentRole=False , podcastRole=False , shareRole=False): + """ + since 1.10.1 + + Modifies an existing Subsonic user. + + username:str The username of the user to update. + + All other args are the same as create user and you can update + whatever item you wish to update for the given username. + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'updateUser' + viewName = '%s.view' % methodName + if password is not None: + password = 'enc:%s' % self._hexEnc(password) + q = self._getQueryDict({'username': username , 'password': password , + 'email': email , 'ldapAuthenticated': ldapAuthenticated , + 'adminRole': adminRole , + 'settingsRole': settingsRole , 'streamRole': streamRole , + 'jukeboxRole': jukeboxRole , 'downloadRole': downloadRole , + 'uploadRole': uploadRole , 'playlistRole': playlistRole , + 'coverArtRole': coverArtRole , 'commentRole': commentRole , + 'podcastRole': podcastRole , 'shareRole': shareRole}) + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def deleteUser(self , username): + """ + since: 1.3.0 + + Deletes an existing Subsonic user. Of course, you must have admin + rights for this. + + username:str The username of the user to delete + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'deleteUser' + viewName = '%s.view' % methodName + + q = {'username': username} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getChatMessages(self , since=1): + """ + since: 1.2.0 + + Returns the current visible (non-expired) chat messages. + + since:int Only return messages newer than this timestamp + + NOTE: All times returned are in MILLISECONDS since the Epoch, not + seconds! + + Returns a dict like the following: + {u'chatMessages': {u'chatMessage': {u'message': u'testing 123', + u'time': 1303411919872L, + u'username': u'admin'}}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getChatMessages' + viewName = '%s.view' % methodName + + q = {'since': self._ts2milli(since)} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def addChatMessage(self , message): + """ + since: 1.2.0 + + Adds a message to the chat log + + message:str The message to add + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'addChatMessage' + viewName = '%s.view' % methodName + + q = {'message': message} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getAlbumList(self , ltype , size=10 , offset=0 , fromYear=None , + toYear=None , genre=None , musicFolderId=None): + """ + since: 1.2.0 + + Returns a list of random, newest, highest rated etc. albums. + Similar to the album lists on the home page of the Subsonic + web interface + + ltype:str The list type. Must be one of the following: random, + newest, highest, frequent, recent, + (since 1.8.0 -> )starred, alphabeticalByName, + alphabeticalByArtist + Since 1.10.1 you can use byYear and byGenre to + list albums in a given year range or genre. + size:int The number of albums to return. Max 500 + offset:int The list offset. Use for paging. Max 5000 + fromYear:int If you specify the ltype as "byYear", you *must* + specify fromYear + toYear:int If you specify the ltype as "byYear", you *must* + specify toYear + genre:str The name of the genre e.g. "Rock". You must specify + genre if you set the ltype to "byGenre" + musicFolderId:str Only return albums in the music folder with + the given ID. See getMusicFolders() + + Returns a dict like the following: + + {u'albumList': {u'album': [{u'artist': u'Hank Williams', + u'id': u'3264928374', + u'isDir': True, + u'parent': u'9238479283', + u'title': u'The Original Singles Collection...Plus'}, + {u'artist': u'Freundeskreis', + u'coverArt': u'9823749823', + u'id': u'23492834', + u'isDir': True, + u'parent': u'9827492374', + u'title': u'Quadratur des Kreises'}]}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getAlbumList' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'type': ltype , 'size': size , + 'offset': offset , 'fromYear': fromYear , 'toYear': toYear , + 'genre': genre , 'musicFolderId': musicFolderId}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getAlbumList2(self , ltype , size=10 , offset=0 , fromYear=None , + toYear=None , genre=None): + """ + since 1.8.0 + + Returns a list of random, newest, highest rated etc. albums. + This is similar to getAlbumList, but uses ID3 tags for + organization + + ltype:str The list type. Must be one of the following: random, + newest, highest, frequent, recent, + (since 1.8.0 -> )starred, alphabeticalByName, + alphabeticalByArtist + Since 1.10.1 you can use byYear and byGenre to + list albums in a given year range or genre. + size:int The number of albums to return. Max 500 + offset:int The list offset. Use for paging. Max 5000 + fromYear:int If you specify the ltype as "byYear", you *must* + specify fromYear + toYear:int If you specify the ltype as "byYear", you *must* + specify toYear + genre:str The name of the genre e.g. "Rock". You must specify + genre if you set the ltype to "byGenre" + + Returns a dict like the following: + {u'albumList2': {u'album': [{u'artist': u'Massive Attack', + u'artistId': 0, + u'coverArt': u'al-0', + u'created': u'2009-08-28T10:00:44', + u'duration': 3762, + u'id': 0, + u'name': u'100th Window', + u'songCount': 9}, + {u'artist': u'Massive Attack', + u'artistId': 0, + u'coverArt': u'al-5', + u'created': u'2003-11-03T22:00:00', + u'duration': 2715, + u'id': 5, + u'name': u'Blue Lines', + u'songCount': 9}]}, + u'status': u'ok', + u'version': u'1.8.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getAlbumList2' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'type': ltype , 'size': size , + 'offset': offset , 'fromYear': fromYear , 'toYear': toYear , + 'genre': genre}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getRandomSongs(self , size=10 , genre=None , fromYear=None , + toYear=None , musicFolderId=None): + """ + since 1.2.0 + + Returns random songs matching the given criteria + + size:int The max number of songs to return. Max 500 + genre:str Only return songs from this genre + fromYear:int Only return songs after or in this year + toYear:int Only return songs before or in this year + musicFolderId:str Only return songs in the music folder with the + given ID. See getMusicFolders + + Returns a dict like the following: + + {u'randomSongs': {u'song': [{u'album': u'1998 EP - Airbag (How Am I Driving)', + u'artist': u'Radiohead', + u'bitRate': 320, + u'contentType': u'audio/mpeg', + u'duration': 129, + u'id': u'9284728934', + u'isDir': False, + u'isVideo': False, + u'parent': u'983249823', + u'path': u'Radiohead/1998 EP - Airbag (How Am I Driving)/06 - Melatonin.mp3', + u'size': 5177469, + u'suffix': u'mp3', + u'title': u'Melatonin'}, + {u'album': u'Mezmerize', + u'artist': u'System Of A Down', + u'bitRate': 214, + u'contentType': u'audio/mpeg', + u'coverArt': u'23849372894', + u'duration': 176, + u'id': u'28937492834', + u'isDir': False, + u'isVideo': False, + u'parent': u'92837492837', + u'path': u'System Of A Down/Mesmerize/10 - System Of A Down - Old School Hollywood.mp3', + u'size': 4751360, + u'suffix': u'mp3', + u'title': u'Old School Hollywood', + u'track': 10}]}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getRandomSongs' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'size': size , 'genre': genre , + 'fromYear': fromYear , 'toYear': toYear , + 'musicFolderId': musicFolderId}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getLyrics(self , artist=None , title=None): + """ + since: 1.2.0 + + Searches for and returns lyrics for a given song + + artist:str The artist name + title:str The song title + + Returns a dict like the following for + getLyrics('Bob Dylan' , 'Blowin in the wind'): + + {u'lyrics': {u'artist': u'Bob Dylan', + u'content': u"How many roads must a man walk down", + u'title': u"Blowin' in the Wind"}, + u'status': u'ok', + u'version': u'1.5.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getLyrics' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'artist': artist , 'title': title}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def jukeboxControl(self , action , index=None , sids=[] , gain=None , + offset=None): + """ + since: 1.2.0 + + NOTE: Some options were added as of API version 1.7.0 + + Controls the jukebox, i.e., playback directly on the server's + audio hardware. Note: The user must be authorized to control + the jukebox + + action:str The operation to perform. Must be one of: get, + start, stop, skip, add, clear, remove, shuffle, + setGain, status (added in API 1.7.0), + set (added in API 1.7.0) + index:int Used by skip and remove. Zero-based index of the + song to skip to or remove. + sids:str Used by "add" and "set". ID of song to add to the + jukebox playlist. Use multiple id parameters to + add many songs in the same request. Whether you + are passing one song or many into this, this + parameter MUST be a list + gain:float Used by setGain to control the playback volume. + A float value between 0.0 and 1.0 + offset:int (added in API 1.7.0) Used by "skip". Start playing + this many seconds into the track. + """ + methodName = 'jukeboxControl' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'action': action , 'index': index , + 'gain': gain , 'offset': offset}) + + req = None + if action == 'add': + # We have to deal with the sids + if not (isinstance(sids , list) or isinstance(sids , tuple)): + raise ArgumentError('If you are adding songs, "sids" must ' + 'be a list or tuple!') + req = self._getRequestWithList(viewName , 'id' , sids , q) + else: + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getPodcasts(self , incEpisodes=True , pid=None): + """ + since: 1.6.0 + + Returns all podcast channels the server subscribes to and their + episodes. + + incEpisodes:bool (since: 1.9.0) Whether to include Podcast + episodes in the returned result. + pid:str (since: 1.9.0) If specified, only return + the Podcast channel with this ID. + + Returns a dict like the following: + {u'status': u'ok', + u'version': u'1.6.0', + u'xmlns': u'http://subsonic.org/restapi', + u'podcasts': {u'channel': {u'description': u"Dr Chris Smith...", + u'episode': [{u'album': u'Dr Karl and the Naked Scientist', + u'artist': u'BBC Radio 5 live', + u'bitRate': 64, + u'contentType': u'audio/mpeg', + u'coverArt': u'2f6f7074', + u'description': u'Dr Karl answers all your science related questions.', + u'duration': 2902, + u'genre': u'Podcast', + u'id': 0, + u'isDir': False, + u'isVideo': False, + u'parent': u'2f6f70742f737562736f6e69632f706f6463617374732f4472204b61726c20616e6420746865204e616b656420536369656e74697374', + u'publishDate': u'2011-08-17 22:06:00.0', + u'size': 23313059, + u'status': u'completed', + u'streamId': u'2f6f70742f737562736f6e69632f706f6463617374732f4472204b61726c20616e6420746865204e616b656420536369656e746973742f64726b61726c5f32303131303831382d30343036612e6d7033', + u'suffix': u'mp3', + u'title': u'DrKarl: Peppermints, Chillies & Receptors', + u'year': 2011}, + {u'description': u'which is warmer, a bath with bubbles in it or one without? Just one of the stranger science stories tackled this week by Dr Chris Smith and the Naked Scientists!', + u'id': 1, + u'publishDate': u'2011-08-14 21:05:00.0', + u'status': u'skipped', + u'title': u'DrKarl: how many bubbles in your bath? 15 AUG 11'}, + ... + {u'description': u'Dr Karl joins Rhod to answer all your science questions', + u'id': 9, + u'publishDate': u'2011-07-06 22:12:00.0', + u'status': u'skipped', + u'title': u'DrKarl: 8 Jul 11 The Strange Sound of the MRI Scanner'}], + u'id': 0, + u'status': u'completed', + u'title': u'Dr Karl and the Naked Scientist', + u'url': u'http://downloads.bbc.co.uk/podcasts/fivelive/drkarl/rss.xml'}} + } + + See also: http://subsonic.svn.sourceforge.net/viewvc/subsonic/trunk/subsonic-main/src/main/webapp/xsd/podcasts_example_1.xml?view=markup + """ + methodName = 'getPodcasts' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'includeEpisodes': incEpisodes , + 'id': pid}) + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getShares(self): + """ + since: 1.6.0 + + Returns information about shared media this user is allowed to manage + + Note that entry can be either a single dict or a list of dicts + + Returns a dict like the following: + + {u'status': u'ok', + u'version': u'1.6.0', + u'xmlns': u'http://subsonic.org/restapi', + u'shares': {u'share': [ + {u'created': u'2011-08-18T10:01:35', + u'entry': {u'artist': u'Alice In Chains', + u'coverArt': u'2f66696c65732f6d7033732f412d4d2f416c69636520496e20436861696e732f416c69636520496e20436861696e732f636f7665722e6a7067', + u'id': u'2f66696c65732f6d7033732f412d4d2f416c69636520496e20436861696e732f416c69636520496e20436861696e73', + u'isDir': True, + u'parent': u'2f66696c65732f6d7033732f412d4d2f416c69636520496e20436861696e73', + u'title': u'Alice In Chains'}, + u'expires': u'2012-08-18T10:01:35', + u'id': 0, + u'url': u'http://crustymonkey.subsonic.org/share/BuLbF', + u'username': u'admin', + u'visitCount': 0 + }]} + } + """ + methodName = 'getShares' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def createShare(self , shids=[] , description=None , expires=None): + """ + since: 1.6.0 + + Creates a public URL that can be used by anyone to stream music + or video from the Subsonic server. The URL is short and suitable + for posting on Facebook, Twitter etc. Note: The user must be + authorized to share (see Settings > Users > User is allowed to + share files with anyone). + + shids:list[str] A list of ids of songs, albums or videos + to share. + description:str A description that will be displayed to + people visiting the shared media + (optional). + expires:float A timestamp pertaining to the time at + which this should expire (optional) + + This returns a structure like you would get back from getShares() + containing just your new share. + """ + methodName = 'createShare' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'description': description , + 'expires': self._ts2milli(expires)}) + req = self._getRequestWithList(viewName , 'id' , shids , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def updateShare(self , shid , description=None , expires=None): + """ + since: 1.6.0 + + Updates the description and/or expiration date for an existing share + + shid:str The id of the share to update + description:str The new description for the share (optional). + expires:float The new timestamp for the expiration time of this + share (optional). + """ + methodName = 'updateShare' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': shid , 'description': description , + expires: self._ts2milli(expires)}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def deleteShare(self , shid): + """ + since: 1.6.0 + + Deletes an existing share + + shid:str The id of the share to delete + + Returns a standard response dict + """ + methodName = 'deleteShare' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': shid}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def setRating(self , id , rating): + """ + since: 1.6.0 + + Sets the rating for a music file + + id:str The id of the item (song/artist/album) to rate + rating:int The rating between 1 and 5 (inclusive), or 0 to remove + the rating + + Returns a standard response dict + """ + methodName = 'setRating' + viewName = '%s.view' % methodName + + try: + rating = int(rating) + except: + raise ArgumentError('Rating must be an integer between 0 and 5: ' + '%r' % rating) + if rating < 0 or rating > 5: + raise ArgumentError('Rating must be an integer between 0 and 5: ' + '%r' % rating) + + q = self._getQueryDict({'id': id , 'rating': rating}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getArtists(self): + """ + since 1.8.0 + + Similar to getIndexes(), but this method uses the ID3 tags to + determine the artist + + Returns a dict like the following: + {u'artists': {u'index': [{u'artist': {u'albumCount': 7, + u'coverArt': u'ar-0', + u'id': 0, + u'name': u'Massive Attack'}, + u'name': u'M'}, + {u'artist': {u'albumCount': 2, + u'coverArt': u'ar-1', + u'id': 1, + u'name': u'Tune-Yards'}, + u'name': u'T'}]}, + u'status': u'ok', + u'version': u'1.8.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getArtists' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getArtist(self , id): + """ + since 1.8.0 + + Returns the info (albums) for an artist. This method uses + the ID3 tags for organization + + id:str The artist ID + + Returns a dict like the following: + + {u'artist': {u'album': [{u'artist': u'Tune-Yards', + u'artistId': 1, + u'coverArt': u'al-7', + u'created': u'2012-01-30T12:35:33', + u'duration': 3229, + u'id': 7, + u'name': u'Bird-Brains', + u'songCount': 13}, + {u'artist': u'Tune-Yards', + u'artistId': 1, + u'coverArt': u'al-8', + u'created': u'2011-03-22T15:08:00', + u'duration': 2531, + u'id': 8, + u'name': u'W H O K I L L', + u'songCount': 10}], + u'albumCount': 2, + u'coverArt': u'ar-1', + u'id': 1, + u'name': u'Tune-Yards'}, + u'status': u'ok', + u'version': u'1.8.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getArtist' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': id}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getAlbum(self , id): + """ + since 1.8.0 + + Returns the info and songs for an album. This method uses + the ID3 tags for organization + + id:str The album ID + + Returns a dict like the following: + + {u'album': {u'artist': u'Massive Attack', + u'artistId': 0, + u'coverArt': u'al-0', + u'created': u'2009-08-28T10:00:44', + u'duration': 3762, + u'id': 0, + u'name': u'100th Window', + u'song': [{u'album': u'100th Window', + u'albumId': 0, + u'artist': u'Massive Attack', + u'artistId': 0, + u'bitRate': 192, + u'contentType': u'audio/mpeg', + u'coverArt': 2, + u'created': u'2009-08-28T10:00:57', + u'duration': 341, + u'genre': u'Rock', + u'id': 14, + u'isDir': False, + u'isVideo': False, + u'parent': 2, + u'path': u'Massive Attack/100th Window/01 - Future Proof.mp3', + u'size': 8184445, + u'suffix': u'mp3', + u'title': u'Future Proof', + u'track': 1, + u'type': u'music', + u'year': 2003}], + u'songCount': 9}, + u'status': u'ok', + u'version': u'1.8.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getAlbum' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': id}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getSong(self , id): + """ + since 1.8.0 + + Returns the info for a song. This method uses the ID3 + tags for organization + + id:str The song ID + + Returns a dict like the following: + {u'song': {u'album': u'W H O K I L L', + u'albumId': 8, + u'artist': u'Tune-Yards', + u'artistId': 1, + u'bitRate': 320, + u'contentType': u'audio/mpeg', + u'coverArt': 106, + u'created': u'2011-03-22T15:08:00', + u'discNumber': 1, + u'duration': 192, + u'genre': u'Indie Rock', + u'id': 120, + u'isDir': False, + u'isVideo': False, + u'parent': 106, + u'path': u'Tune Yards/Who Kill/10 Killa.mp3', + u'size': 7692656, + u'suffix': u'mp3', + u'title': u'Killa', + u'track': 10, + u'type': u'music', + u'year': 2011}, + u'status': u'ok', + u'version': u'1.8.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getSong' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': id}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getVideos(self): + """ + since 1.8.0 + + Returns all video files + + Returns a dict like the following: + {u'status': u'ok', + u'version': u'1.8.0', + u'videos': {u'video': {u'bitRate': 384, + u'contentType': u'video/x-matroska', + u'created': u'2012-08-26T13:36:44', + u'duration': 1301, + u'id': 130, + u'isDir': False, + u'isVideo': True, + u'path': u'South Park - 16x07 - Cartman Finds Love.mkv', + u'size': 287309613, + u'suffix': u'mkv', + u'title': u'South Park - 16x07 - Cartman Finds Love', + u'transcodedContentType': u'video/x-flv', + u'transcodedSuffix': u'flv'}}, + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getVideos' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getStarred(self): + """ + since 1.8.0 + + Returns starred songs, albums and artists + + Returns a dict like the following: + {u'starred': {u'album': {u'album': u'Bird-Brains', + u'artist': u'Tune-Yards', + u'coverArt': 105, + u'created': u'2012-01-30T13:16:58', + u'id': 105, + u'isDir': True, + u'parent': 104, + u'starred': u'2012-08-26T13:18:34', + u'title': u'Bird-Brains'}, + u'song': [{u'album': u'Mezzanine', + u'albumId': 4, + u'artist': u'Massive Attack', + u'artistId': 0, + u'bitRate': 256, + u'contentType': u'audio/mpeg', + u'coverArt': 6, + u'created': u'2009-06-15T07:48:28', + u'duration': 298, + u'genre': u'Dub', + u'id': 72, + u'isDir': False, + u'isVideo': False, + u'parent': 6, + u'path': u'Massive Attack/Mezzanine/Massive Attack_02_mezzanine.mp3', + u'size': 9564160, + u'starred': u'2012-08-26T13:19:26', + u'suffix': u'mp3', + u'title': u'Risingson', + u'track': 2, + u'type': u'music'}, + {u'album': u'Mezzanine', + u'albumId': 4, + u'artist': u'Massive Attack', + u'artistId': 0, + u'bitRate': 256, + u'contentType': u'audio/mpeg', + u'coverArt': 6, + u'created': u'2009-06-15T07:48:25', + u'duration': 380, + u'genre': u'Dub', + u'id': 71, + u'isDir': False, + u'isVideo': False, + u'parent': 6, + u'path': u'Massive Attack/Mezzanine/Massive Attack_01_mezzanine.mp3', + u'size': 12179456, + u'starred': u'2012-08-26T13:19:03', + u'suffix': u'mp3', + u'title': u'Angel', + u'track': 1, + u'type': u'music'}]}, + u'status': u'ok', + u'version': u'1.8.0', + u'xmlns': u'http://subsonic.org/restapi'} + """ + methodName = 'getStarred' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getStarred2(self): + """ + since 1.8.0 + + Returns starred songs, albums and artists like getStarred(), + but this uses ID3 tags for organization + + Returns a dict like the following: + + **See the output from getStarred()** + """ + methodName = 'getStarred2' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def updatePlaylist(self , lid , name=None , comment=None , songIdsToAdd=[] , + songIndexesToRemove=[]): + """ + since 1.8.0 + + Updates a playlist. Only the owner of a playlist is allowed to + update it. + + lid:str The playlist id + name:str The human readable name of the playlist + comment:str The playlist comment + songIdsToAdd:list A list of song IDs to add to the playlist + songIndexesToRemove:list Remove the songs at the + 0 BASED INDEXED POSITIONS in the + playlist, NOT the song ids. Note that + this is always a list. + + Returns a normal status response dict + """ + methodName = 'updatePlaylist' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'playlistId': lid , 'name': name , + 'comment': comment}) + if not isinstance(songIdsToAdd , list) or isinstance(songIdsToAdd , + tuple): + songIdsToAdd = [songIdsToAdd] + if not isinstance(songIndexesToRemove , list) or isinstance( + songIndexesToRemove , tuple): + songIndexesToRemove = [songIndexesToRemove] + listMap = {'songIdToAdd': songIdsToAdd , + 'songIndexToRemove': songIndexesToRemove} + req = self._getRequestWithLists(viewName , listMap , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getAvatar(self , username): + """ + since 1.8.0 + + Returns the avatar for a user or None if the avatar does not exist + + username:str The user to retrieve the avatar for + + Returns the file-like object for reading or raises an exception + on error + """ + methodName = 'getAvatar' + viewName = '%s.view' % methodName + + q = {'username': username} + + req = self._getRequest(viewName , q) + try: + res = self._doBinReq(req) + except urllib2.HTTPError: + # Avatar is not set/does not exist, return None + return None + if isinstance(res , dict): + self._checkStatus(res) + return res + + def star(self , sids=[] , albumIds=[] , artistIds=[]): + """ + since 1.8.0 + + Attaches a star to songs, albums or artists + + sids:list A list of song IDs to star + albumIds:list A list of album IDs to star. Use this rather than + "sids" if the client access the media collection + according to ID3 tags rather than file + structure + artistIds:list The ID of an artist to star. Use this rather + than sids if the client access the media + collection according to ID3 tags rather + than file structure + + Returns a normal status response dict + """ + methodName = 'star' + viewName = '%s.view' % methodName + + if not isinstance(sids , list) or isinstance(sids , tuple): + sids = [sids] + if not isinstance(albumIds , list) or isinstance(albumIds , tuple): + albumIds = [albumIds] + if not isinstance(artistIds , list) or isinstance(artistIds , tuple): + artistIds = [artistIds] + listMap = {'id': sids , + 'albumId': albumIds , + 'artistId': artistIds} + req = self._getRequestWithLists(viewName , listMap) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def unstar(self , sids=[] , albumIds=[] , artistIds=[]): + """ + since 1.8.0 + + Removes a star to songs, albums or artists. Basically, the + same as star in reverse + + sids:list A list of song IDs to star + albumIds:list A list of album IDs to star. Use this rather than + "sids" if the client access the media collection + according to ID3 tags rather than file + structure + artistIds:list The ID of an artist to star. Use this rather + than sids if the client access the media + collection according to ID3 tags rather + than file structure + + Returns a normal status response dict + """ + methodName = 'unstar' + viewName = '%s.view' % methodName + + if not isinstance(sids , list) or isinstance(sids , tuple): + sids = [sids] + if not isinstance(albumIds , list) or isinstance(albumIds , tuple): + albumIds = [albumIds] + if not isinstance(artistIds , list) or isinstance(artistIds , tuple): + artistIds = [artistIds] + listMap = {'id': sids , + 'albumId': albumIds , + 'artistId': artistIds} + req = self._getRequestWithLists(viewName , listMap) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getGenres(self): + """ + since 1.9.0 + + Returns all genres + """ + methodName = 'getGenres' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getSongsByGenre(self , genre , count=10 , offset=0): + """ + since 1.9.0 + + Returns songs in a given genre + + genre:str The genre, as returned by getGenres() + count:int The maximum number of songs to return. Max is 500 + default: 10 + offset:int The offset if you are paging. default: 0 + """ + methodName = 'getGenres' + viewName = '%s.view' % methodName + + q = {'genre': genre , + 'count': count , + 'offset': offset , + } + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def hls (self , mid , bitrate=None): + """ + since 1.8.0 + + Creates an HTTP live streaming playlist for streaming video or + audio HLS is a streaming protocol implemented by Apple and + works by breaking the overall stream into a sequence of small + HTTP-based file downloads. It's supported by iOS and newer + versions of Android. This method also supports adaptive + bitrate streaming, see the bitRate parameter. + + mid:str The ID of the media to stream + bitrate:str If specified, the server will attempt to limit the + bitrate to this value, in kilobits per second. If + this parameter is specified more than once, the + server will create a variant playlist, suitable + for adaptive bitrate streaming. The playlist will + support streaming at all the specified bitrates. + The server will automatically choose video dimensions + that are suitable for the given bitrates. + (since: 1.9.0) you may explicitly request a certain + width (480) and height (360) like so: + bitRate=1000@480x360 + + Returns the raw m3u8 file as a string + """ + methodName = 'hls' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': mid , 'bitrate': bitrate}) + req = self._getRequest(viewName , q) + try: + res = self._doBinReq(req) + except urllib2.HTTPError: + # Avatar is not set/does not exist, return None + return None + if isinstance(res , dict): + self._checkStatus(res) + return res.read() + + def refreshPodcasts(self): + """ + since: 1.9.0 + + Tells the server to check for new Podcast episodes. Note: The user + must be authorized for Podcast administration + """ + methodName = 'refreshPodcasts' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def createPodcastChannel(self , url): + """ + since: 1.9.0 + + Adds a new Podcast channel. Note: The user must be authorized + for Podcast administration + + url:str The URL of the Podcast to add + """ + methodName = 'createPodcastChannel' + viewName = '%s.view' % methodName + + q = {'url': url} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def deletePodcastChannel(self , pid): + """ + since: 1.9.0 + + Deletes a Podcast channel. Note: The user must be authorized + for Podcast administration + + pid:str The ID of the Podcast channel to delete + """ + methodName = 'deletePodcastChannel' + viewName = '%s.view' % methodName + + q = {'id': pid} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def deletePodcastEpisode(self , pid): + """ + since: 1.9.0 + + Deletes a Podcast episode. Note: The user must be authorized + for Podcast administration + + pid:str The ID of the Podcast episode to delete + """ + methodName = 'deletePodcastEpisode' + viewName = '%s.view' % methodName + + q = {'id': pid} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def downloadPodcastEpisode(self , pid): + """ + since: 1.9.0 + + Tells the server to start downloading a given Podcast episode. + Note: The user must be authorized for Podcast administration + + pid:str The ID of the Podcast episode to download + """ + methodName = 'downloadPodcastEpisode' + viewName = '%s.view' % methodName + + q = {'id': pid} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getInternetRadioStations(self): + """ + since: 1.9.0 + + Returns all internet radio stations + """ + methodName = 'getInternetRadioStations' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getBookmarks(self): + """ + since: 1.9.0 + + Returns all bookmarks for this user. A bookmark is a position + within a media file + """ + methodName = 'getBookmarks' + viewName = '%s.view' % methodName + + req = self._getRequest(viewName) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def createBookmark(self , mid , position , comment=None): + """ + since: 1.9.0 + + Creates or updates a bookmark (position within a media file). + Bookmarks are personal and not visible to other users + + mid:str The ID of the media file to bookmark. If a bookmark + already exists for this file, it will be overwritten + position:int The position (in milliseconds) within the media file + comment:str A user-defined comment + """ + methodName = 'createBookmark' + viewName = '%s.view' % methodName + + q = self._getQueryDict({'id': mid , 'position': position , + 'comment': comment}) + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def deleteBookmark(self , mid): + """ + since: 1.9.0 + + Deletes the bookmark for a given file + + mid:str The ID of the media file to delete the bookmark from. + Other users' bookmarks are not affected + """ + methodName = 'deleteBookmark' + viewName = '%s.view' % methodName + + q = {'id': mid} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getArtistInfo(self , aid , count=20 , includeNotPresent=False): + """ + since: 1.11.0 + + Returns artist info with biography, image URLS and similar artists + using data from last.fm + + aid:str The ID of the artist, album or song + count:int The max number of similar artists to return + includeNotPresent:bool Whether to return artists that are not + present in the media library + """ + methodName = 'getArtistInfo' + viewName = '%s.view' % methodName + + q = {'id': aid , 'count': count , + 'includeNotPresent': includeNotPresent} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getArtistInfo2(self , aid , count=20 , includeNotPresent=False): + """ + since: 1.11.0 + + Similar to getArtistInfo(), but organizes music according to ID3 tags + + aid:str The ID of the artist, album or song + count:int The max number of similar artists to return + includeNotPresent:bool Whether to return artists that are not + present in the media library + """ + methodName = 'getArtistInfo2' + viewName = '%s.view' % methodName + + q = {'id': aid , 'count': count , + 'includeNotPresent': includeNotPresent} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getSimilarSongs(self , iid , count=50): + """ + since 1.11.0 + + Returns a random collection of songs from the given artist and + similar artists, using data from last.fm. Typically used for + artist radio features. + + iid:str The artist, album, or song ID + count:int Max number of songs to return + """ + methodName = 'getSimilarSongs' + viewName = '%s.view' % methodName + + q = {'id': iid , 'count': count} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def getSimilarSongs2(self , iid , count=50): + """ + since 1.11.0 + + Similar to getSimilarSongs(), but organizes music according to + ID3 tags + + iid:str The artist, album, or song ID + count:int Max number of songs to return + """ + methodName = 'getSimilarSongs2' + viewName = '%s.view' % methodName + + q = {'id': iid , 'count': count} + + req = self._getRequest(viewName , q) + res = self._doInfoReq(req) + self._checkStatus(res) + return res + + def scanMediaFolders(self): + """ + This is not an officially supported method of the API + + Same as selecting 'Settings' > 'Scan media folders now' with + Subsonic web GUI + + Returns True if refresh successful, False otherwise + """ + methodName = 'scanNow' + return self._unsupportedAPIFunction(methodName) + + def cleanupDatabase(self): + """ + This is not an officially supported method of the API + + Same as selecting 'Settings' > 'Clean-up Database' with Subsonic + web GUI + + Returns True if cleanup initiated successfully, False otherwise + + Subsonic stores information about all media files ever encountered. + By cleaning up the database, information about files that are + no longer in your media collection is permanently removed. + """ + methodName = 'expunge' + return self._unsupportedAPIFunction(methodName) + + def _unsupportedAPIFunction(self, methodName): + """ + base function to call unsupported API methods + + Returns True if refresh successful, False otherwise + :rtype : boolean + """ + baseMethod = 'musicFolderSettings' + viewName = '%s.view' % baseMethod + + url = '%s:%d/%s/%s?%s' % (self._baseUrl , self._port , + self._separateServerPath() , viewName, methodName) + req = urllib2.Request(url) + res = self._opener.open(req) + res_msg = res.msg.lower() + return res_msg == 'ok' + + # Private internal methods + def _getOpener(self , username , passwd): + creds = b64encode('%s:%s' % (username , passwd)) + opener = urllib2.build_opener(PysHTTPRedirectHandler , + HTTPSHandlerChain) + opener.addheaders = [('Authorization' , 'Basic %s' % creds)] + return opener + + def _getQueryDict(self , d): + """ + Given a dictionary, it cleans out all the values set to None + """ + for k , v in d.items(): + if v is None: + del d[k] + return d + + def _getRequest(self , viewName , query={}): + qstring = {'f': 'json' , 'v': self._apiVersion , 'c': self._appName} + qstring.update(query) + url = '%s:%d/%s/%s' % (self._baseUrl , self._port , self._serverPath , + viewName) + req = urllib2.Request(url , urlencode(qstring)) + return req + + def _getRequestWithList(self , viewName , listName , alist , query={}): + """ + Like _getRequest, but allows appending a number of items with the + same key (listName). This bypasses the limitation of urlencode() + """ + qstring = {'f': 'json' , 'v': self._apiVersion , 'c': self._appName} + qstring.update(query) + url = '%s:%d/%s/%s' % (self._baseUrl , self._port , self._serverPath , + viewName) + data = StringIO() + data.write(urlencode(qstring)) + for i in alist: + data.write('&%s' % urlencode({listName: i})) + req = urllib2.Request(url , data.getvalue()) + return req + + def _getRequestWithLists(self , viewName , listMap , query={}): + """ + Like _getRequestWithList(), but you must pass a dictionary + that maps the listName to the list. This allows for multiple + list parameters to be used, like in updatePlaylist() + + viewName:str The name of the view + listMap:dict A mapping of listName to a list of entries + query:dict The normal query dict + """ + qstring = {'f': 'json' , 'v': self._apiVersion , 'c': self._appName} + qstring.update(query) + url = '%s:%d/%s/%s' % (self._baseUrl , self._port , self._serverPath , + viewName) + data = StringIO() + data.write(urlencode(qstring)) + for k , l in listMap.iteritems(): + for i in l: + data.write('&%s' % urlencode({k: i})) + req = urllib2.Request(url , data.getvalue()) + return req + + def _doInfoReq(self , req): + # Returns a parsed dictionary version of the result + res = self._opener.open(req) + dres = json.loads(res.read()) + return dres['subsonic-response'] + + def _doBinReq(self , req): + res = self._opener.open(req) + contType = res.info().getheader('Content-Type') + if contType: + if contType.startswith('text/html') or \ + contType.startswith('application/json'): + dres = json.loads(res.read()) + return dres['subsonic-response'] + return res + + def _checkStatus(self , result): + if result['status'] == 'ok': + return True + elif result['status'] == 'failed': + exc = getExcByCode(result['error']['code']) + raise exc(result['error']['message']) + + def _hexEnc(self , raw): + """ + Returns a "hex encoded" string per the Subsonic api docs + + raw:str The string to hex encode + """ + ret = '' + for c in raw: + ret += '%02X' % ord(c) + return ret + + def _ts2milli(self , ts): + """ + For whatever reason, Subsonic uses timestamps in milliseconds since + the unix epoch. I have no idea what need there is of this precision, + but this will just multiply the timestamp times 1000 and return the int + """ + if ts is None: + return None + return int(ts * 1000) + + def _separateServerPath(self): + """ + separate REST portion of URL from base server path. + """ + return urllib2.splithost(self._serverPath)[1].split('/')[0] + diff --git a/lib/libsonic/errors.py b/lib/libsonic/errors.py new file mode 100644 index 0000000..a000c57 --- /dev/null +++ b/lib/libsonic/errors.py @@ -0,0 +1,59 @@ +""" +This file is part of py-sonic. + +py-sonic is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +py-sonic is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with py-sonic. If not, see +""" + +class SonicError(Exception): + pass + +class ParameterError(SonicError): + pass + +class VersionError(SonicError): + pass + +class CredentialError(SonicError): + pass + +class AuthError(SonicError): + pass + +class LicenseError(SonicError): + pass + +class DataNotFoundError(SonicError): + pass + +class ArgumentError(SonicError): + pass + +# This maps the error code numbers from the Subsonic server to their +# appropriate Exceptions +ERR_CODE_MAP = { + 0: SonicError , + 10: ParameterError , + 20: VersionError , + 30: VersionError , + 40: CredentialError , + 50: AuthError , + 60: LicenseError , + 70: DataNotFoundError , +} + +def getExcByCode(code): + code = int(code) + if code in ERR_CODE_MAP: + return ERR_CODE_MAP[code] + return SonicError diff --git a/lib/libsonic_extra/__init__.py b/lib/libsonic_extra/__init__.py new file mode 100644 index 0000000..3b0b8fe --- /dev/null +++ b/lib/libsonic_extra/__init__.py @@ -0,0 +1,231 @@ +import urllib +import urlparse +import libsonic + + +def force_dict(value): + """ + Coerce the input value to a dict. + """ + + if type(value) == dict: + return value + else: + return {} + + +def force_list(value): + """ + Coerce the input value to a list. + + If `value` is `None`, return an empty list. If it is a single value, create + a new list with that element on index 0. + + :param value: Input value to coerce. + :return: Value as list. + :rtype: list + """ + + if value is None: + return [] + elif type(value) == list: + return value + else: + return [value] + + +class Connection(libsonic.Connection): + """ + Extend `libsonic.Connection` with new features and fix a few issues. + + - Add library name property. + - Parse URL for host and port for constructor. + - Make sure API results are of of uniform type. + + :param str name: Name of connection. + :param str url: Full URL (including protocol) of SubSonic server. + :param str username: Username of server. + :param str password: Password of server. + """ + + def __init__(self, url, username, password): + self._intercept_url = False + + # Parse SubSonic URL + parts = urlparse.urlparse(url) + scheme = parts.scheme or "http" + + # Make sure there is hostname + if not parts.hostname: + raise ValueError("Expected hostname for URL: %s" % url) + + # Validate scheme + if scheme not in ("http", "https"): + raise ValueError("Unexpected scheme '%s' for URL: %s" % ( + scheme, url)) + + # Pick a default port + host = "%s://%s" % (scheme, parts.hostname) + port = parts.port or {"http": 80, "https": 443}[scheme] + + # Invoke original constructor + super(Connection, self).__init__(host, username, password, port=port) + + def getArtists(self, *args, **kwargs): + """ + """ + + def _artists_iterator(artists): + for artist in force_list(artists): + artist["id"] = int(artist["id"]) + yield artist + + def _index_iterator(index): + for index in force_list(index): + index["artist"] = list(_artists_iterator(index.get("artist"))) + yield index + + response = super(Connection, self).getArtists(*args, **kwargs) + response["artists"] = response.get("artists", {}) + response["artists"]["index"] = list( + _index_iterator(response["artists"].get("index"))) + + return response + + def getPlaylists(self, *args, **kwargs): + """ + """ + + def _playlists_iterator(playlists): + for playlist in force_list(playlists): + playlist["id"] = int(playlist["id"]) + yield playlist + + response = super(Connection, self).getPlaylists(*args, **kwargs) + response["playlists"]["playlist"] = list( + _playlists_iterator(response["playlists"].get("playlist"))) + + return response + + def getPlaylist(self, *args, **kwargs): + """ + """ + + def _entries_iterator(entries): + for entry in force_list(entries): + entry["id"] = int(entry["id"]) + yield entry + + response = super(Connection, self).getPlaylist(*args, **kwargs) + response["playlist"]["entry"] = list( + _entries_iterator(response["playlist"].get("entry"))) + + return response + + def getArtist(self, *args, **kwargs): + """ + """ + + def _albums_iterator(albums): + for album in force_list(albums): + album["id"] = int(album["id"]) + yield album + + response = super(Connection, self).getArtist(*args, **kwargs) + response["artist"]["album"] = list( + _albums_iterator(response["artist"].get("album"))) + + return response + + def getAlbum(self, *args, **kwargs): + "" + "" + + def _songs_iterator(songs): + for song in force_list(songs): + song["id"] = int(song["id"]) + yield song + + response = super(Connection, self).getAlbum(*args, **kwargs) + response["album"]["song"] = list( + _songs_iterator(response["album"].get("song"))) + + return response + + def getAlbumList2(self, *args, **kwargs): + "" + "" + + def _album_iterator(albums): + for album in force_list(albums): + album["id"] = int(album["id"]) + yield album + + response = super(Connection, self).getAlbumList2(*args, **kwargs) + response["albumList2"]["album"] = list( + _album_iterator(response["albumList2"].get("album"))) + + return response + + def getMusicDirectory(self, *args, **kwargs): + """ + """ + + def _children_iterator(children): + for child in force_list(children): + child["id"] = int(child["id"]) + + if "parent" in child: + child["parent"] = int(child["parent"]) + if "coverArt" in child: + child["coverArt"] = int(child["coverArt"]) + if "artistId" in child: + child["artistId"] = int(child["artistId"]) + if "albumId" in child: + child["albumId"] = int(child["albumId"]) + + yield child + + response = super(Connection, self).getMusicDirectory(*args, **kwargs) + response["directory"]["child"] = list( + _children_iterator(response["directory"].get("child"))) + + return response + + def getCoverArtUrl(self, *args, **kwargs): + """ + Return an URL to the cover art. + """ + + self._intercept_url = True + url = self.getCoverArt(*args, **kwargs) + self._intercept_url = False + + return url + + def streamUrl(self, *args, **kwargs): + """ + Return an URL to the file to stream. + """ + + self._intercept_url = True + url = self.stream(*args, **kwargs) + self._intercept_url = False + + return url + + def _doBinReq(self, *args, **kwargs): + """ + Intercept request URL. + """ + + if self._intercept_url: + parts = list(urlparse.urlparse( + args[0].get_full_url() + "?" + args[0].data)) + parts[4] = dict(urlparse.parse_qsl(parts[4])) + parts[4].update({"u": self.username, "p": self.password}) + parts[4] = urllib.urlencode(parts[4]) + + return urlparse.urlunparse(parts) + else: + return super(Connection, self)._doBinReq(*args, **kwargs) diff --git a/resources/settings.xml b/resources/settings.xml new file mode 100644 index 0000000..d6b3981 --- /dev/null +++ b/resources/settings.xml @@ -0,0 +1,9 @@ + + + + + + + + +