Updated libsonic.

This commit is contained in:
Bas Stottelaar 2015-09-12 11:53:54 +02:00
parent 37496cbb24
commit a32d8be767
2 changed files with 141 additions and 141 deletions

View File

@ -29,4 +29,4 @@ print conn.ping()
from connection import * from connection import *
__version__ = '0.3.3' __version__ = '0.3.4'

View File

@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
class HTTPSConnectionChain(httplib.HTTPSConnection): class HTTPSConnectionChain(httplib.HTTPSConnection):
_preferred_ssl_protos = ( _preferred_ssl_protos = (
('TLSv1' , ssl.PROTOCOL_TLSv1) , ('TLSv1' , ssl.PROTOCOL_TLSv1) ,
('SSLv3' , ssl.PROTOCOL_SSLv3) , ('SSLv3' , ssl.PROTOCOL_SSLv3) ,
('SSLv23' , ssl.PROTOCOL_SSLv23) , ('SSLv23' , ssl.PROTOCOL_SSLv23) ,
) )
@ -68,7 +68,7 @@ urllib2.install_opener(urllib2.build_opener(HTTPSHandlerChain()))
class PysHTTPRedirectHandler(urllib2.HTTPRedirectHandler): class PysHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
""" """
This class is used to override the default behavior of the This class is used to override the default behavior of the
HTTPRedirectHandler, which does *not* redirect POST data HTTPRedirectHandler, which does *not* redirect POST data
""" """
def redirect_request(self, req, fp, code, msg, headers, newurl): def redirect_request(self, req, fp, code, msg, headers, newurl):
@ -91,20 +91,20 @@ class PysHTTPRedirectHandler(urllib2.HTTPRedirectHandler):
raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp) raise urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
class Connection(object): class Connection(object):
def __init__(self , baseUrl , username , password , port=4040 , def __init__(self , baseUrl , username , password , port=4040 ,
serverPath='/rest' , appName='py-sonic' , apiVersion=API_VERSION): serverPath='/rest' , appName='py-sonic' , apiVersion=API_VERSION):
""" """
This will create a connection to your subsonic server This will create a connection to your subsonic server
baseUrl:str The base url for your server. Be sure to use baseUrl:str The base url for your server. Be sure to use
"https" for SSL connections. If you are using "https" for SSL connections. If you are using
a port other than the default 4040, be sure to a port other than the default 4040, be sure to
specify that with the port argument. Do *not* specify that with the port argument. Do *not*
append it here. append it here.
ex: http://subsonic.example.com ex: http://subsonic.example.com
If you are running subsonic under a different If you are running subsonic under a different
path, specify that with the "serverPath" arg, path, specify that with the "serverPath" arg,
*not* here. For example, if your subsonic *not* here. For example, if your subsonic
lives at: lives at:
@ -124,16 +124,16 @@ class Connection(object):
This is useful if you have your subsonic server This is useful if you have your subsonic server
behind a proxy and the path that you are proxying behind a proxy and the path that you are proxying
is different from the default of '/rest'. is different from the default of '/rest'.
Ex: Ex:
serverPath='/path/to/subs' serverPath='/path/to/subs'
The full url that would be built then would be The full url that would be built then would be
(assuming defaults and using "example.com" and (assuming defaults and using "example.com" and
you are using the "ping" view): you are using the "ping" view):
http://example.com:4040/path/to/subs/ping.view http://example.com:4040/path/to/subs/ping.view
appName:str The name of your application. appName:str The name of your application.
apiVersion:str The API version you wish to use for your apiVersion:str The API version you wish to use for your
application. Subsonic will throw an error if you application. Subsonic will throw an error if you
try to use/send an api version higher than what try to use/send an api version higher than what
the server supports. See the Subsonic API docs the server supports. See the Subsonic API docs
@ -185,7 +185,7 @@ class Connection(object):
def ping(self): def ping(self):
""" """
since: 1.0.0 since: 1.0.0
Returns a boolean True if the server is alive, False otherwise Returns a boolean True if the server is alive, False otherwise
""" """
methodName = 'ping' methodName = 'ping'
@ -295,8 +295,8 @@ class Connection(object):
Returns an indexed structure of all artists Returns an indexed structure of all artists
musicFolderId:int If this is specified, it will only return musicFolderId:int If this is specified, it will only return
artists for the given folder ID from artists for the given folder ID from
the getMusicFolders call the getMusicFolders call
ifModifiedSince:int If specified, return a result if the artist ifModifiedSince:int If specified, return a result if the artist
collection has changed since the given time collection has changed since the given time
@ -321,7 +321,7 @@ class Connection(object):
methodName = 'getIndexes' methodName = 'getIndexes'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'musicFolderId': musicFolderId , q = self._getQueryDict({'musicFolderId': musicFolderId ,
'ifModifiedSince': self._ts2milli(ifModifiedSince)}) 'ifModifiedSince': self._ts2milli(ifModifiedSince)})
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
@ -336,8 +336,8 @@ class Connection(object):
Returns a listing of all files in a music directory. Typically used 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. 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 mid:str The string ID value which uniquely identifies the
folder. Obtained via calls to getIndexes or folder. Obtained via calls to getIndexes or
getMusicDirectory. REQUIRED getMusicDirectory. REQUIRED
Returns a dict like the following: Returns a dict like the following:
@ -392,7 +392,7 @@ class Connection(object):
self._checkStatus(res) self._checkStatus(res)
return res return res
def search(self , artist=None , album=None , title=None , any=None , def search(self , artist=None , album=None , title=None , any=None ,
count=20 , offset=0 , newerThan=None): count=20 , offset=0 , newerThan=None):
""" """
since: 1.0.0 since: 1.0.0
@ -416,7 +416,7 @@ class Connection(object):
methodName = 'search' methodName = 'search'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'artist': artist , 'album': album , q = self._getQueryDict({'artist': artist , 'album': album ,
'title': title , 'any': any , 'count': count , 'offset': offset , 'title': title , 'any': any , 'count': count , 'offset': offset ,
'newerThan': self._ts2milli(newerThan)}) 'newerThan': self._ts2milli(newerThan)})
@ -476,7 +476,7 @@ class Connection(object):
methodName = 'search2' methodName = 'search2'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'query': query , 'artistCount': artistCount , q = {'query': query , 'artistCount': artistCount ,
'artistOffset': artistOffset , 'albumCount': albumCount , 'artistOffset': artistOffset , 'albumCount': albumCount ,
'albumOffset': albumOffset , 'songCount': songCount , 'albumOffset': albumOffset , 'songCount': songCount ,
'songOffset': songOffset} 'songOffset': songOffset}
@ -552,7 +552,7 @@ class Connection(object):
methodName = 'search3' methodName = 'search3'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'query': query , 'artistCount': artistCount , q = {'query': query , 'artistCount': artistCount ,
'artistOffset': artistOffset , 'albumCount': albumCount , 'artistOffset': artistOffset , 'albumCount': albumCount ,
'albumOffset': albumOffset , 'songCount': songCount , 'albumOffset': albumOffset , 'songCount': songCount ,
'songOffset': songOffset} 'songOffset': songOffset}
@ -638,9 +638,9 @@ class Connection(object):
""" """
since: 1.2.0 since: 1.2.0
Creates OR updates a playlist. If updating the list, the Creates OR updates a playlist. If updating the list, the
playlistId is required. If creating a list, the name is required. playlistId is required. If creating a list, the name is required.
playlistId:str The ID of the playlist to UPDATE playlistId:str The ID of the playlist to UPDATE
name:str The name of the playlist to CREATE name:str The name of the playlist to CREATE
songIds:list The list of songIds to populate the list with in songIds:list The list of songIds to populate the list with in
@ -696,7 +696,7 @@ class Connection(object):
sid:str The ID of the music file to download. sid:str The ID of the music file to download.
Returns the file-like object for reading or raises an exception Returns the file-like object for reading or raises an exception
on error on error
""" """
methodName = 'download' methodName = 'download'
@ -716,16 +716,16 @@ class Connection(object):
Downloads a given music file. Downloads a given music file.
sid:str The ID of the music file to download. sid:str The ID of the music file to download.
maxBitRate:int (since: 1.2.0) If specified, the server will maxBitRate:int (since: 1.2.0) If specified, the server will
attempt to limit the bitrate to this value, in attempt to limit the bitrate to this value, in
kilobits per second. If set to zero (default), no limit kilobits per second. If set to zero (default), no limit
is imposed. Legal values are: 0, 32, 40, 48, 56, 64, is imposed. Legal values are: 0, 32, 40, 48, 56, 64,
80, 96, 112, 128, 160, 192, 224, 256 and 320. 80, 96, 112, 128, 160, 192, 224, 256 and 320.
tformat:str (since: 1.6.0) Specifies the target format tformat:str (since: 1.6.0) Specifies the target format
(e.g. "mp3" or "flv") in case there are multiple (e.g. "mp3" or "flv") in case there are multiple
applicable transcodings (since: 1.9.0) You can use applicable transcodings (since: 1.9.0) You can use
the special value "raw" to disable transcoding the special value "raw" to disable transcoding
timeOffset:int (since: 1.6.0) Only applicable to video timeOffset:int (since: 1.6.0) Only applicable to video
streaming. Start the stream at the given streaming. Start the stream at the given
offset (in seconds) into the video offset (in seconds) into the video
size:str (since: 1.6.0) The requested video size in size:str (since: 1.6.0) The requested video size in
@ -735,12 +735,12 @@ class Connection(object):
will be set to an estimated will be set to an estimated
value for trancoded media value for trancoded media
Returns the file-like object for reading or raises an exception Returns the file-like object for reading or raises an exception
on error on error
""" """
methodName = 'stream' methodName = 'stream'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'id': sid , 'maxBitRate': maxBitRate , q = self._getQueryDict({'id': sid , 'maxBitRate': maxBitRate ,
'format': tformat , 'timeOffset': timeOffset , 'size': size , 'format': tformat , 'timeOffset': timeOffset , 'size': size ,
'estimateContentLength': estimateContentLength}) 'estimateContentLength': estimateContentLength})
@ -760,12 +760,12 @@ class Connection(object):
aid:str ID string for the cover art image to download aid:str ID string for the cover art image to download
size:int If specified, scale image to this size size:int If specified, scale image to this size
Returns the file-like object for reading or raises an exception Returns the file-like object for reading or raises an exception
on error on error
""" """
methodName = 'getCoverArt' methodName = 'getCoverArt'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'id': aid , 'size': size}) q = self._getQueryDict({'id': aid , 'size': size})
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
@ -792,7 +792,7 @@ class Connection(object):
sid:str The ID of the file to scrobble sid:str The ID of the file to scrobble
submission:bool Whether this is a "submission" or a "now playing" submission:bool Whether this is a "submission" or a "now playing"
notification notification
listenTime:int (Since 1.8.0) The time (unix timestamp) at listenTime:int (Since 1.8.0) The time (unix timestamp) at
which the song was listened to. which the song was listened to.
Returns a dict like the following: Returns a dict like the following:
@ -833,7 +833,7 @@ class Connection(object):
hexPass = 'enc:%s' % self._hexEnc(password) hexPass = 'enc:%s' % self._hexEnc(password)
# There seems to be an issue with some subsonic implementations # There seems to be an issue with some subsonic implementations
# not recognizing the "enc:" precursor to the encoded password and # not recognizing the "enc:" precursor to the encoded password and
# encodes the whole "enc:<hex>" as the password. Weird. # encodes the whole "enc:<hex>" as the password. Weird.
#q = {'username': username , 'password': hexPass.lower()} #q = {'username': username , 'password': hexPass.lower()}
q = {'username': username , 'password': password} q = {'username': username , 'password': password}
@ -851,12 +851,12 @@ class Connection(object):
Can be used to enable/disable certain features in the client, such Can be used to enable/disable certain features in the client, such
as jukebox control as jukebox control
username:str The username to retrieve. You can only retrieve username:str The username to retrieve. You can only retrieve
your own user unless you have admin privs. your own user unless you have admin privs.
Returns a dict like the following: Returns a dict like the following:
{u'status': u'ok', {u'status': u'ok',
u'user': {u'adminRole': False, u'user': {u'adminRole': False,
u'commentRole': False, u'commentRole': False,
u'coverArtRole': False, u'coverArtRole': False,
@ -917,10 +917,10 @@ class Connection(object):
self._checkStatus(res) self._checkStatus(res)
return res return res
def createUser(self , username , password , email , def createUser(self , username , password , email ,
ldapAuthenticated=False , adminRole=False , settingsRole=True , ldapAuthenticated=False , adminRole=False , settingsRole=True ,
streamRole=True , jukeboxRole=False , downloadRole=False , streamRole=True , jukeboxRole=False , downloadRole=False ,
uploadRole=False , playlistRole=False , coverArtRole=False , uploadRole=False , playlistRole=False , coverArtRole=False ,
commentRole=False , podcastRole=False , shareRole=False): commentRole=False , podcastRole=False , shareRole=False):
""" """
since: 1.1.0 since: 1.1.0
@ -957,9 +957,9 @@ class Connection(object):
return res return res
def updateUser(self , username , password=None , email=None , def updateUser(self , username , password=None , email=None ,
ldapAuthenticated=False , adminRole=False , settingsRole=True , ldapAuthenticated=False , adminRole=False , settingsRole=True ,
streamRole=True , jukeboxRole=False , downloadRole=False , streamRole=True , jukeboxRole=False , downloadRole=False ,
uploadRole=False , playlistRole=False , coverArtRole=False , uploadRole=False , playlistRole=False , coverArtRole=False ,
commentRole=False , podcastRole=False , shareRole=False): commentRole=False , podcastRole=False , shareRole=False):
""" """
since 1.10.1 since 1.10.1
@ -967,7 +967,7 @@ class Connection(object):
Modifies an existing Subsonic user. Modifies an existing Subsonic user.
username:str The username of the user to update. username:str The username of the user to update.
All other args are the same as create user and you can update All other args are the same as create user and you can update
whatever item you wish to update for the given username. whatever item you wish to update for the given username.
@ -982,7 +982,7 @@ class Connection(object):
if password is not None: if password is not None:
password = 'enc:%s' % self._hexEnc(password) password = 'enc:%s' % self._hexEnc(password)
q = self._getQueryDict({'username': username , 'password': password , q = self._getQueryDict({'username': username , 'password': password ,
'email': email , 'ldapAuthenticated': ldapAuthenticated , 'email': email , 'ldapAuthenticated': ldapAuthenticated ,
'adminRole': adminRole , 'adminRole': adminRole ,
'settingsRole': settingsRole , 'streamRole': streamRole , 'settingsRole': settingsRole , 'streamRole': streamRole ,
'jukeboxRole': jukeboxRole , 'downloadRole': downloadRole , 'jukeboxRole': jukeboxRole , 'downloadRole': downloadRole ,
@ -1018,7 +1018,7 @@ class Connection(object):
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
return res return res
def getChatMessages(self , since=1): def getChatMessages(self , since=1):
""" """
since: 1.2.0 since: 1.2.0
@ -1072,20 +1072,20 @@ class Connection(object):
self._checkStatus(res) self._checkStatus(res)
return res return res
def getAlbumList(self , ltype , size=10 , offset=0 , fromYear=None , def getAlbumList(self , ltype , size=10 , offset=0 , fromYear=None ,
toYear=None , genre=None , musicFolderId=None): toYear=None , genre=None , musicFolderId=None):
""" """
since: 1.2.0 since: 1.2.0
Returns a list of random, newest, highest rated etc. albums. Returns a list of random, newest, highest rated etc. albums.
Similar to the album lists on the home page of the Subsonic Similar to the album lists on the home page of the Subsonic
web interface web interface
ltype:str The list type. Must be one of the following: random, ltype:str The list type. Must be one of the following: random,
newest, highest, frequent, recent, newest, highest, frequent, recent,
(since 1.8.0 -> )starred, alphabeticalByName, (since 1.8.0 -> )starred, alphabeticalByName,
alphabeticalByArtist alphabeticalByArtist
Since 1.10.1 you can use byYear and byGenre to Since 1.10.1 you can use byYear and byGenre to
list albums in a given year range or genre. list albums in a given year range or genre.
size:int The number of albums to return. Max 500 size:int The number of albums to return. Max 500
offset:int The list offset. Use for paging. Max 5000 offset:int The list offset. Use for paging. Max 5000
@ -1095,7 +1095,7 @@ class Connection(object):
specify toYear specify toYear
genre:str The name of the genre e.g. "Rock". You must specify genre:str The name of the genre e.g. "Rock". You must specify
genre if you set the ltype to "byGenre" genre if you set the ltype to "byGenre"
musicFolderId:str Only return albums in the music folder with musicFolderId:str Only return albums in the music folder with
the given ID. See getMusicFolders() the given ID. See getMusicFolders()
Returns a dict like the following: Returns a dict like the following:
@ -1118,7 +1118,7 @@ class Connection(object):
methodName = 'getAlbumList' methodName = 'getAlbumList'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'type': ltype , 'size': size , q = self._getQueryDict({'type': ltype , 'size': size ,
'offset': offset , 'fromYear': fromYear , 'toYear': toYear , 'offset': offset , 'fromYear': fromYear , 'toYear': toYear ,
'genre': genre , 'musicFolderId': musicFolderId}) 'genre': genre , 'musicFolderId': musicFolderId})
@ -1127,20 +1127,20 @@ class Connection(object):
self._checkStatus(res) self._checkStatus(res)
return res return res
def getAlbumList2(self , ltype , size=10 , offset=0 , fromYear=None , def getAlbumList2(self , ltype , size=10 , offset=0 , fromYear=None ,
toYear=None , genre=None): toYear=None , genre=None):
""" """
since 1.8.0 since 1.8.0
Returns a list of random, newest, highest rated etc. albums. Returns a list of random, newest, highest rated etc. albums.
This is similar to getAlbumList, but uses ID3 tags for This is similar to getAlbumList, but uses ID3 tags for
organization organization
ltype:str The list type. Must be one of the following: random, ltype:str The list type. Must be one of the following: random,
newest, highest, frequent, recent, newest, highest, frequent, recent,
(since 1.8.0 -> )starred, alphabeticalByName, (since 1.8.0 -> )starred, alphabeticalByName,
alphabeticalByArtist alphabeticalByArtist
Since 1.10.1 you can use byYear and byGenre to Since 1.10.1 you can use byYear and byGenre to
list albums in a given year range or genre. list albums in a given year range or genre.
size:int The number of albums to return. Max 500 size:int The number of albums to return. Max 500
offset:int The list offset. Use for paging. Max 5000 offset:int The list offset. Use for paging. Max 5000
@ -1175,7 +1175,7 @@ class Connection(object):
methodName = 'getAlbumList2' methodName = 'getAlbumList2'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'type': ltype , 'size': size , q = self._getQueryDict({'type': ltype , 'size': size ,
'offset': offset , 'fromYear': fromYear , 'toYear': toYear , 'offset': offset , 'fromYear': fromYear , 'toYear': toYear ,
'genre': genre}) 'genre': genre})
@ -1184,7 +1184,7 @@ class Connection(object):
self._checkStatus(res) self._checkStatus(res)
return res return res
def getRandomSongs(self , size=10 , genre=None , fromYear=None , def getRandomSongs(self , size=10 , genre=None , fromYear=None ,
toYear=None , musicFolderId=None): toYear=None , musicFolderId=None):
""" """
since 1.2.0 since 1.2.0
@ -1235,8 +1235,8 @@ class Connection(object):
methodName = 'getRandomSongs' methodName = 'getRandomSongs'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'size': size , 'genre': genre , q = self._getQueryDict({'size': size , 'genre': genre ,
'fromYear': fromYear , 'toYear': toYear , 'fromYear': fromYear , 'toYear': toYear ,
'musicFolderId': musicFolderId}) 'musicFolderId': musicFolderId})
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
@ -1253,7 +1253,7 @@ class Connection(object):
artist:str The artist name artist:str The artist name
title:str The song title title:str The song title
Returns a dict like the following for Returns a dict like the following for
getLyrics('Bob Dylan' , 'Blowin in the wind'): getLyrics('Bob Dylan' , 'Blowin in the wind'):
{u'lyrics': {u'artist': u'Bob Dylan', {u'lyrics': {u'artist': u'Bob Dylan',
@ -1273,29 +1273,29 @@ class Connection(object):
self._checkStatus(res) self._checkStatus(res)
return res return res
def jukeboxControl(self , action , index=None , sids=[] , gain=None , def jukeboxControl(self , action , index=None , sids=[] , gain=None ,
offset=None): offset=None):
""" """
since: 1.2.0 since: 1.2.0
NOTE: Some options were added as of API version 1.7.0 NOTE: Some options were added as of API version 1.7.0
Controls the jukebox, i.e., playback directly on the server's Controls the jukebox, i.e., playback directly on the server's
audio hardware. Note: The user must be authorized to control audio hardware. Note: The user must be authorized to control
the jukebox the jukebox
action:str The operation to perform. Must be one of: get, action:str The operation to perform. Must be one of: get,
start, stop, skip, add, clear, remove, shuffle, start, stop, skip, add, clear, remove, shuffle,
setGain, status (added in API 1.7.0), setGain, status (added in API 1.7.0),
set (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 index:int Used by skip and remove. Zero-based index of the
song to skip to or remove. song to skip to or remove.
sids:str Used by "add" and "set". ID of song to add to the sids:str Used by "add" and "set". ID of song to add to the
jukebox playlist. Use multiple id parameters to jukebox playlist. Use multiple id parameters to
add many songs in the same request. Whether you add many songs in the same request. Whether you
are passing one song or many into this, this are passing one song or many into this, this
parameter MUST be a list parameter MUST be a list
gain:float Used by setGain to control the playback volume. gain:float Used by setGain to control the playback volume.
A float value between 0.0 and 1.0 A float value between 0.0 and 1.0
offset:int (added in API 1.7.0) Used by "skip". Start playing offset:int (added in API 1.7.0) Used by "skip". Start playing
this many seconds into the track. this many seconds into the track.
@ -1303,7 +1303,7 @@ class Connection(object):
methodName = 'jukeboxControl' methodName = 'jukeboxControl'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'action': action , 'index': index , q = self._getQueryDict({'action': action , 'index': index ,
'gain': gain , 'offset': offset}) 'gain': gain , 'offset': offset})
req = None req = None
@ -1323,12 +1323,12 @@ class Connection(object):
""" """
since: 1.6.0 since: 1.6.0
Returns all podcast channels the server subscribes to and their Returns all podcast channels the server subscribes to and their
episodes. episodes.
incEpisodes:bool (since: 1.9.0) Whether to include Podcast incEpisodes:bool (since: 1.9.0) Whether to include Podcast
episodes in the returned result. episodes in the returned result.
pid:str (since: 1.9.0) If specified, only return pid:str (since: 1.9.0) If specified, only return
the Podcast channel with this ID. the Podcast channel with this ID.
Returns a dict like the following: Returns a dict like the following:
@ -1377,7 +1377,7 @@ class Connection(object):
methodName = 'getPodcasts' methodName = 'getPodcasts'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'includeEpisodes': incEpisodes , q = self._getQueryDict({'includeEpisodes': incEpisodes ,
'id': pid}) 'id': pid})
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
res = self._doInfoReq(req) res = self._doInfoReq(req)
@ -1425,16 +1425,16 @@ class Connection(object):
""" """
since: 1.6.0 since: 1.6.0
Creates a public URL that can be used by anyone to stream music 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 or video from the Subsonic server. The URL is short and suitable
for posting on Facebook, Twitter etc. Note: The user must be for posting on Facebook, Twitter etc. Note: The user must be
authorized to share (see Settings > Users > User is allowed to authorized to share (see Settings > Users > User is allowed to
share files with anyone). share files with anyone).
shids:list[str] A list of ids of songs, albums or videos shids:list[str] A list of ids of songs, albums or videos
to share. to share.
description:str A description that will be displayed to description:str A description that will be displayed to
people visiting the shared media people visiting the shared media
(optional). (optional).
expires:float A timestamp pertaining to the time at expires:float A timestamp pertaining to the time at
which this should expire (optional) which this should expire (optional)
@ -1445,7 +1445,7 @@ class Connection(object):
methodName = 'createShare' methodName = 'createShare'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'description': description , q = self._getQueryDict({'description': description ,
'expires': self._ts2milli(expires)}) 'expires': self._ts2milli(expires)})
req = self._getRequestWithList(viewName , 'id' , shids , q) req = self._getRequestWithList(viewName , 'id' , shids , q)
res = self._doInfoReq(req) res = self._doInfoReq(req)
@ -1519,7 +1519,7 @@ class Connection(object):
'%r' % rating) '%r' % rating)
q = self._getQueryDict({'id': id , 'rating': rating}) q = self._getQueryDict({'id': id , 'rating': rating})
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
@ -1545,11 +1545,11 @@ class Connection(object):
u'name': u'T'}]}, u'name': u'T'}]},
u'status': u'ok', u'status': u'ok',
u'version': u'1.8.0', u'version': u'1.8.0',
u'xmlns': u'http://subsonic.org/restapi'} u'xmlns': u'http://subsonic.org/restapi'}
""" """
methodName = 'getArtists' methodName = 'getArtists'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
req = self._getRequest(viewName) req = self._getRequest(viewName)
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
@ -1559,13 +1559,13 @@ class Connection(object):
""" """
since 1.8.0 since 1.8.0
Returns the info (albums) for an artist. This method uses Returns the info (albums) for an artist. This method uses
the ID3 tags for organization the ID3 tags for organization
id:str The artist ID id:str The artist ID
Returns a dict like the following: Returns a dict like the following:
{u'artist': {u'album': [{u'artist': u'Tune-Yards', {u'artist': {u'album': [{u'artist': u'Tune-Yards',
u'artistId': 1, u'artistId': 1,
u'coverArt': u'al-7', u'coverArt': u'al-7',
@ -1610,7 +1610,7 @@ class Connection(object):
id:str The album ID id:str The album ID
Returns a dict like the following: Returns a dict like the following:
{u'album': {u'artist': u'Massive Attack', {u'album': {u'artist': u'Massive Attack',
u'artistId': 0, u'artistId': 0,
u'coverArt': u'al-0', u'coverArt': u'al-0',
@ -1658,7 +1658,7 @@ class Connection(object):
""" """
since 1.8.0 since 1.8.0
Returns the info for a song. This method uses the ID3 Returns the info for a song. This method uses the ID3
tags for organization tags for organization
id:str The song ID id:str The song ID
@ -1810,7 +1810,7 @@ class Connection(object):
but this uses ID3 tags for organization but this uses ID3 tags for organization
Returns a dict like the following: Returns a dict like the following:
**See the output from getStarred()** **See the output from getStarred()**
""" """
methodName = 'getStarred2' methodName = 'getStarred2'
@ -1833,11 +1833,11 @@ class Connection(object):
name:str The human readable name of the playlist name:str The human readable name of the playlist
comment:str The playlist comment comment:str The playlist comment
songIdsToAdd:list A list of song IDs to add to the playlist songIdsToAdd:list A list of song IDs to add to the playlist
songIndexesToRemove:list Remove the songs at the songIndexesToRemove:list Remove the songs at the
0 BASED INDEXED POSITIONS in the 0 BASED INDEXED POSITIONS in the
playlist, NOT the song ids. Note that playlist, NOT the song ids. Note that
this is always a list. this is always a list.
Returns a normal status response dict Returns a normal status response dict
""" """
methodName = 'updatePlaylist' methodName = 'updatePlaylist'
@ -1866,12 +1866,12 @@ class Connection(object):
username:str The user to retrieve the avatar for username:str The user to retrieve the avatar for
Returns the file-like object for reading or raises an exception Returns the file-like object for reading or raises an exception
on error on error
""" """
methodName = 'getAvatar' methodName = 'getAvatar'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'username': username} q = {'username': username}
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
@ -1968,7 +1968,7 @@ class Connection(object):
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
return res return res
def getSongsByGenre(self , genre , count=10 , offset=0): def getSongsByGenre(self , genre , count=10 , offset=0):
""" """
since 1.9.0 since 1.9.0
@ -1983,8 +1983,8 @@ class Connection(object):
methodName = 'getGenres' methodName = 'getGenres'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'genre': genre , q = {'genre': genre ,
'count': count , 'count': count ,
'offset': offset , 'offset': offset ,
} }
@ -1998,23 +1998,23 @@ class Connection(object):
since 1.8.0 since 1.8.0
Creates an HTTP live streaming playlist for streaming video or Creates an HTTP live streaming playlist for streaming video or
audio HLS is a streaming protocol implemented by Apple and audio HLS is a streaming protocol implemented by Apple and
works by breaking the overall stream into a sequence of small works by breaking the overall stream into a sequence of small
HTTP-based file downloads. It's supported by iOS and newer HTTP-based file downloads. It's supported by iOS and newer
versions of Android. This method also supports adaptive versions of Android. This method also supports adaptive
bitrate streaming, see the bitRate parameter. bitrate streaming, see the bitRate parameter.
mid:str The ID of the media to stream mid:str The ID of the media to stream
bitrate:str If specified, the server will attempt to limit the bitrate:str If specified, the server will attempt to limit the
bitrate to this value, in kilobits per second. If bitrate to this value, in kilobits per second. If
this parameter is specified more than once, the this parameter is specified more than once, the
server will create a variant playlist, suitable server will create a variant playlist, suitable
for adaptive bitrate streaming. The playlist will for adaptive bitrate streaming. The playlist will
support streaming at all the specified bitrates. support streaming at all the specified bitrates.
The server will automatically choose video dimensions The server will automatically choose video dimensions
that are suitable for the given bitrates. that are suitable for the given bitrates.
(since: 1.9.0) you may explicitly request a certain (since: 1.9.0) you may explicitly request a certain
width (480) and height (360) like so: width (480) and height (360) like so:
bitRate=1000@480x360 bitRate=1000@480x360
Returns the raw m3u8 file as a string Returns the raw m3u8 file as a string
@ -2066,7 +2066,7 @@ class Connection(object):
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
return res return res
def deletePodcastChannel(self , pid): def deletePodcastChannel(self , pid):
""" """
since: 1.9.0 since: 1.9.0
@ -2109,7 +2109,7 @@ class Connection(object):
""" """
since: 1.9.0 since: 1.9.0
Tells the server to start downloading a given Podcast episode. Tells the server to start downloading a given Podcast episode.
Note: The user must be authorized for Podcast administration Note: The user must be authorized for Podcast administration
pid:str The ID of the Podcast episode to download pid:str The ID of the Podcast episode to download
@ -2168,7 +2168,7 @@ class Connection(object):
methodName = 'createBookmark' methodName = 'createBookmark'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = self._getQueryDict({'id': mid , 'position': position , q = self._getQueryDict({'id': mid , 'position': position ,
'comment': comment}) 'comment': comment})
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
@ -2204,15 +2204,15 @@ class Connection(object):
aid:str The ID of the artist, album or song aid:str The ID of the artist, album or song
count:int The max number of similar artists to return count:int The max number of similar artists to return
includeNotPresent:bool Whether to return artists that are not includeNotPresent:bool Whether to return artists that are not
present in the media library present in the media library
""" """
methodName = 'getArtistInfo' methodName = 'getArtistInfo'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'id': aid , 'count': count , q = {'id': aid , 'count': count ,
'includeNotPresent': includeNotPresent} 'includeNotPresent': includeNotPresent}
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
@ -2226,15 +2226,15 @@ class Connection(object):
aid:str The ID of the artist, album or song aid:str The ID of the artist, album or song
count:int The max number of similar artists to return count:int The max number of similar artists to return
includeNotPresent:bool Whether to return artists that are not includeNotPresent:bool Whether to return artists that are not
present in the media library present in the media library
""" """
methodName = 'getArtistInfo2' methodName = 'getArtistInfo2'
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'id': aid , 'count': count , q = {'id': aid , 'count': count ,
'includeNotPresent': includeNotPresent} 'includeNotPresent': includeNotPresent}
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
@ -2244,8 +2244,8 @@ class Connection(object):
""" """
since 1.11.0 since 1.11.0
Returns a random collection of songs from the given artist and Returns a random collection of songs from the given artist and
similar artists, using data from last.fm. Typically used for similar artists, using data from last.fm. Typically used for
artist radio features. artist radio features.
iid:str The artist, album, or song ID iid:str The artist, album, or song ID
@ -2255,7 +2255,7 @@ class Connection(object):
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'id': iid , 'count': count} q = {'id': iid , 'count': count}
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
@ -2265,7 +2265,7 @@ class Connection(object):
""" """
since 1.11.0 since 1.11.0
Similar to getSimilarSongs(), but organizes music according to Similar to getSimilarSongs(), but organizes music according to
ID3 tags ID3 tags
iid:str The artist, album, or song ID iid:str The artist, album, or song ID
@ -2275,7 +2275,7 @@ class Connection(object):
viewName = '%s.view' % methodName viewName = '%s.view' % methodName
q = {'id': iid , 'count': count} q = {'id': iid , 'count': count}
req = self._getRequest(viewName , q) req = self._getRequest(viewName , q)
res = self._doInfoReq(req) res = self._doInfoReq(req)
self._checkStatus(res) self._checkStatus(res)
@ -2285,7 +2285,7 @@ class Connection(object):
""" """
This is not an officially supported method of the API This is not an officially supported method of the API
Same as selecting 'Settings' > 'Scan media folders now' with Same as selecting 'Settings' > 'Scan media folders now' with
Subsonic web GUI Subsonic web GUI
Returns True if refresh successful, False otherwise Returns True if refresh successful, False otherwise
@ -2297,13 +2297,13 @@ class Connection(object):
""" """
This is not an officially supported method of the API This is not an officially supported method of the API
Same as selecting 'Settings' > 'Clean-up Database' with Subsonic Same as selecting 'Settings' > 'Clean-up Database' with Subsonic
web GUI web GUI
Returns True if cleanup initiated successfully, False otherwise Returns True if cleanup initiated successfully, False otherwise
Subsonic stores information about all media files ever encountered. Subsonic stores information about all media files ever encountered.
By cleaning up the database, information about files that are By cleaning up the database, information about files that are
no longer in your media collection is permanently removed. no longer in your media collection is permanently removed.
""" """
methodName = 'expunge' methodName = 'expunge'
@ -2319,7 +2319,7 @@ class Connection(object):
baseMethod = 'musicFolderSettings' baseMethod = 'musicFolderSettings'
viewName = '%s.view' % baseMethod viewName = '%s.view' % baseMethod
url = '%s:%d/%s/%s?%s' % (self._baseUrl , self._port , url = '%s:%d/%s/%s?%s' % (self._baseUrl , self._port ,
self._separateServerPath() , viewName, methodName) self._separateServerPath() , viewName, methodName)
req = urllib2.Request(url) req = urllib2.Request(url)
res = self._opener.open(req) res = self._opener.open(req)
@ -2329,7 +2329,7 @@ class Connection(object):
# Private internal methods # Private internal methods
def _getOpener(self , username , passwd): def _getOpener(self , username , passwd):
creds = b64encode('%s:%s' % (username , passwd)) creds = b64encode('%s:%s' % (username , passwd))
opener = urllib2.build_opener(PysHTTPRedirectHandler , opener = urllib2.build_opener(PysHTTPRedirectHandler ,
HTTPSHandlerChain) HTTPSHandlerChain)
opener.addheaders = [('Authorization' , 'Basic %s' % creds)] opener.addheaders = [('Authorization' , 'Basic %s' % creds)]
return opener return opener
@ -2369,8 +2369,8 @@ class Connection(object):
def _getRequestWithLists(self , viewName , listMap , query={}): def _getRequestWithLists(self , viewName , listMap , query={}):
""" """
Like _getRequestWithList(), but you must pass a dictionary Like _getRequestWithList(), but you must pass a dictionary
that maps the listName to the list. This allows for multiple that maps the listName to the list. This allows for multiple
list parameters to be used, like in updatePlaylist() list parameters to be used, like in updatePlaylist()
viewName:str The name of the view viewName:str The name of the view