====== Python Subsonic Library ====== ===== About ===== ''py-sonic'' is a Python library that wraps the [[http://www.subsonic.org/pages/api.jsp|Subsonic REST API]]. This is meant to essentially be a convenience layer when you are writing an app to work against your [[http://www.subsonic.org|Subsonic]] server. You will notice when going through the documentation here that this is a thin layer on top of the [[http://www.subsonic.org/pages/api.jsp|REST API]]. I have plans to make this a bit more "Pythonic" (if I can ever muster up enough time to do it), but though this returns simple language constructs (dict, list, etc.) at this point, it is still very usable. ===== Downloading ===== You can download this the package from [[https://github.com/crustymonkey/py-sonic|Github]], or just ''git clone'' it. You can also use ''pip'' or ''easy_install'': pip install py-sonic **NOTE:** The RPMs are **not** guaranteed to be the latest versions. Check [[https://github.com/crustymonkey/py-sonic|Github]] to find out what the latest version is. That said, I will try and keep these up to date. {{:programming:python:py-sonic-0.2.2-1.src.rpm|py-sonic-0.2.2-1.src.rpm}} \\ {{:programming:python:py-sonic-0.2.2-1.noarch.rpm|py-sonic-0.2.2-1.noarch.rpm}} ===== Donations ===== By no means should anyone feel they //have// to donate. However, if you've used this stuff to save some time, prevent head shaped holes in the wall, raise your children or you just think this is awesome and want to contribute to my [[http://www.surlybrewing.com/|Surly]] fund (that stuff isn't cheap, but it's soooooo good), you can click the [[http://paypal.com|Paypal]] donate button below and toss me a couple of dollars. I've heard from others who have donated that they were visited by flying monkeys that spoke Icelandic, and were subsequently showered with fantastic gifts from said monkeys. I don't know if this is true, or if drug use was involved, but it still sounds pretty awesome.
===== Issues and Feature Requests ===== Please use the bug tracker built in to [[http://github.com|Github]] for all bug reports and feature requests ===== Code Contributions ===== I'm always open to code contributions. Please use the "fork" and "pull request" mechanisms on [[http://github.com|Github]] for this. ===== Installing ===== You can install manually: tar -xvzf py-sonic-*.tar.gz cd py-sonic-* python setup.py install Or you can install with ''pip'' or ''easy_install'': pip install py-sonic ===== libsonic.Connection ===== **NOTE:** All of the information in this section is also available with ''pydoc'': pydoc libsonic.connection This is the only real class (at this point) that you need to use. You will notice that this very much follows the API as documented on [[http://www.subsonic.org/pages/api.jsp|the subsonic website]]. You will notice that the methods for your ''libsonic.Connection'' instance all note their relative ''REST API'' versions. You can find the API version to [[http://www.subsonic.org|Subsonic]] version on [[http://www.subsonic.org/pages/api.jsp|Subsonic's API documentation page]]. FIXME I've found that there is a major wart in the upstream API. It appears that any of the calls which can return multiple results will, instead of always returning a ''list'', will return only a ''dict'' instead if there is one item. For example, if you call ''getPlaylists()'' with only one playlist set up on your server, you would get a structure like the following: { u'playlists': {u'playlist': {u'id': u'62656174732e6d3375', u'name': u'beats' } } u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } However, if there are multiple playlists, you will get a structure like this: { 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' } As of this writing, you will have to check whether you receive a ''list'' or a ''dict'' back. I plan on fixing this in a future release so that you **always** we receive a ''list'' when it is possible to have multiple entries, even if there is only one entry. ==== Enabling HTTPS Connections ==== A quick note about when you enable HTTPS on your Subsonic server. I recently discovered that when you set ''SUBSONIC_HTTPS_PORT'' to a non-zero value (essentially, turning on HTTPS), this will cause Subsonic to 302 redirect all incoming traffic to the now enabled HTTPS port. This should not be a problem, but it seems that the Python ''urllib2.HTTPRedirectHandler'' does not handle this correctly, probably because I'm submitting a POST request here. I'm going to look into this further, but the simple workaround is simply: If you set the ''SUBSONIC_HTTPS_PORT'', be sure that your ''libsonic.Connection'' arguments are pointing to the HTTPS port (for the example, I assume you set ''SUBSONIC_HTTPS_PORT=4430''): import libsonic conn = libsonic.Connection('https://example.com' , 'myuser' , 'mypass' , port=4430) **UPDATE:** I found that the ''urllib2.HTTPRedirectHandler'' issue is actually a "bug" wherein the POST //data// is not redirected. It is somewhat questionable practice in terms of security, so who knows if it will be accepted. Since ''py-sonic'' sends everything via POST requests, redirection was breaking it by dropping all data. Though I submitted a [[http://bugs.python.org/issue14144|bug report with patch]], I'm going to put a fix into ''py-sonic'' that fixes this regardless of whether the upstream Python maintainers fix this. **UPDATE to the UPDATE:** I've fixed this in version 0.1.10. This will now handle redirects from Subsonic correctly. ==== Important Notes About API Version 1.8.0 (and later) ==== It's important to note that Subsonic API version 1.8.0 added some methods that are not compatible with previous methods. Prior to 1.8.0, the access to your library was only usable within a directory hierarchy context. With 1.8.0, a number of access methods were added to search and get artists, albums, songs, etc. based on, instead of file system structure, the ID3 tags of the music files themselves. The problem this brings about is that the IDs returned for one type of lookup are **not** compatible with the other. For example, if you were to use the [[#search2(self, query, artistCount=20, artistOffset=0, albumCount=20, albumOffset=0, songCount=20, songOffset=0)|search2()]] method to search for, say, an artist. If you then used the resulting artist ID from that search to [[#getArtist(self , id)|getArtist(id)]], you would **not** get what you expected! Instead, you would get either an error or a different artist altogether. The crux of this is that if you must make sure you don't mix these methods in what your code. Anything that is introduced in API 1.8.0 (or later) that makes mention of using ID3 tags will **not** be compatible with things like [[#getMusicDirectory(self, mid)|getMusicDirectory()]]. ==== __init__(self, baseUrl, username, password, port=4040, serverPath='/rest', appName='py-sonic' , apiVersion=libsonic.API_VERSION) ==== This is the initialization method for the class. As you can see, the ''baseUrl'', ''username'' and ''password'' are all required. Set the rest of the options as needed. This will return a ''libsonic.Connection'' instance that you will use to perform all interactions with the server. ^ Name ^ Type ^ Description ^ | baseUrl | str | The base url for your server. Be sure to use "https" for SSL connections. ex: http://subsonic.example.com | | 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 differnt 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 version of the API you wish your application to support. Subsonic will throw an error if you Subsonic will throw an error if you the server supports. See the [[http://www.subsonic.org/pages/api.jsp|Subsonic API docs]] to find the Subsonic version -> API version table. This is useful if you are connecting to an olderversion of Subsonic. | ==== addChatMessage(self, message) ==== __Since API version: 1.2.0__ Adds a message to the chat log ^ Name ^ Type ^ Description ^ | 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' } ==== changePassword(self, username, password) ==== __Since API version: 1.2.0__ Changes the password of an existing Subsonic user. Note that the user performing this must have admin privileges. ^ Name ^ Type ^ Description ^ | 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' } ==== createPlaylist(self, playlistId=None, name=None, songIds=[]) ==== __Since API version: 1.2.0__ Creates **OR** updates a playlist. If updating the list, the ''playlistId'' is required. If creating a list, the ''name'' is required. ^ Name ^ Type ^ Description ^ | playlistId | str | The ID of the playlist to **UPDATE** | | name | str | The name of the playlist to **CREATE** | | songIds | list[str] | 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' } ==== createPodcastChannel(self, url) ==== __Since API version: 1.9.0__ Adds a new Podcast channel. Note: The user must be authorized for Podcast administration ^ Name ^ Type ^ Description ^ | url | str | The URL of the Podcast 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' } ==== createShare(self, shids=[], description=None, expires=None) ==== __Since API version: 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 toshare files with anyone). ^ Name ^ Type ^ Description ^ | shids | list[str] | A list of IDs of songs, albums or videos | | 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. ==== createUser(self, username, password, ldapAuthenticated=False, adminRole=False, settingsRole=True, streamRole=True, jukeboxRole=False, downloadRole=False, uploadRole=False, playlistRole=False, coverArtRole=False, commentRole=False, podcastRole=False) ==== __Since API version: 1.1.0__ Creates a new subsonic user, using the parameters defined. See the documentation at [[http://subsonic.org|the Subsonic site]] for more info on all the roles. ^ Name ^ Type ^ Description ^ | username | str | The username of the new user | | password | str | The password for the new user | See the documention on [[http://subsonic.org|the Subsonic site]] for more information on all the boolean role args. Returns a dict like the following: { u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== deleteBookmark(self, mid) ==== __Since API version: 1.9.0__ Deletes the bookmark for a given file ^ Name ^ Type ^ Description ^ | mid | str | The ID of the media file to delete the bookmark from. Other users' bookmarks are not affected | Returns a dict like the following: { u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== deletePlaylist(self, pid) ==== __Since API version: 1.2.0__ Deletes a saved playlist. ^ Name ^ Type ^ Description ^ | pid | str | ID of the playlist to delete, as obtained by ''getPlaylists()'' | Returns a dict like the following: { u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== deletePodcastChannel(self, pid) ==== __Since API version: 1.9.0__ Deletes a Podcast channel. Note: The user must be authorized for Podcast administration ^ Name ^ Type ^ Description ^ | pid | str | ID of the Podcast channel 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' } ==== deletePodcastEpisode(self, pid) ==== __Since API version: 1.9.0__ Deletes a Podcast episode. Note: The user must be authorized for Podcast administration ^ Name ^ Type ^ Description ^ | pid | str | ID of the Podcast episode 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' } ==== deleteShare(self, shid) ==== __Since API version: 1.6.0__ Deletes an existing share. ^ Name ^ Type ^ Description ^ | shid | str | The ID of the share 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' } ==== deleteUser(self, username) ==== __Since API version: 1.3.0__ Deletes an existing Subsonic user. Of course, you must have admin rights for this. ^ Name ^ Type ^ Description ^ | 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' } ==== download(self, sid) ==== __Since API version: 1.0.0__ Downloads a given media file. ^ Name ^ Type ^ Description ^ | sid | str | The ID of the media file to download. | Returns the file-like object for reading or raises an exception on error. ==== downloadPodcastEpisode(self , pid) ==== __Since API version: 1.9.0__ Tells the server to start downloading a given Podcast episode. Note: The user must be authorized for Podcast administration ^ Name ^ Type ^ Description ^ | pid | str | The ID of the Podcast episode to download. | Returns a dict like the following: { u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== getAlbum(self , id) ==== __Since API version: 1.8.0__ Returns the info and songs for an album. This method uses the ID3 tags for organization. ^ Name ^ Type ^ Description ^ | id | str | The ID of the album. | 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'} ==== getAlbumList(self, ltype, size=10, offset=0) ==== __Since API version: 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. ^ Name ^ Type ^ Description ^ | ltype | str | The list type. Must be one of the following: random, newest, highest, frequent, recent | | size | int | The number of albums to return. Max 500 | | offset | int | The list offset. Use for paging. Max 5000 | 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' } ==== getAlbumList2(self, ltype, size=10, offset=0) ==== __Since API version: 1.8.0__ Returns a list of random, newest, highest rated etc. albums. This is similar to getAlbumList, but uses ID3 tags for organization. ^ Name ^ Type ^ Description ^ | ltype | str | The list type. Must be one of the following: random, newest, highest, frequent, recent | | size | int | The number of albums to return. Max 500 | | offset | int | The list offset. Use for paging. Max 5000 | 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'} ==== getArtist(self , id) ==== __Since API version: 1.8.0__ ^ Name ^ Type ^ Description ^ | id | int | Get the artist by id | ==== getArtists(self) ==== __Since API version: 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'} ==== getAvatar(self, username) ==== __Since API version: 1.8.0__ Returns the avatar for a user or None if the avatar does not exist ^ Name ^ Type ^ Description ^ | username | str | The user to retrieve the avatar for | Returns the file-like object for reading or raises an exception on error ==== getBookmarks(self) ==== __Since API version: 1.9.0__ Returns all bookmarks for this user. A bookmark is a position within a media file { "bookmarks": { "bookmark": { "changed": "2013-04-28T13:18:55", "comment": "testing a bookmark", "created": "2013-04-28T13:18:55", "entry": { "album": "The Anthology", "albumId": 3480, "artist": "A Tribe Called Quest", "artistId": 1512, "bitRate": 128, "contentType": "audio/mpeg", "coverArt": 45794, "created": "2005-07-27T14:27:42", "duration": 292, "id": 45827, "isDir": false, "isVideo": false, "parent": 45794, "path": "A Tribe Called Quest/The Anthology/13 Stressed Out.mp3", "size": 4685211, "suffix": "mp3", "title": "Stressed Out", "track": 13, "type": "music" }, "position": 30000, "username": "admin" } }, "status": "ok", "version": "1.9.0", "xmlns": "http://subsonic.org/restapi" } ==== getChatMessages(self, since=1) ==== __Since API version: 1.2.0__ Returns the current visible (non-expired) chat messages. ^ Name ^ Type ^ Description ^ | 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' } ==== getCoverArt(self, aid, size=None) ==== __Since API version: 1.0.0__ Returns a cover art image ^ Name ^ Type ^ Description ^ | 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. ==== getGenres(self) ==== __Since API version: 1.9.0__ Returns all genres ==== getIndexes(self, musicFolderId=None, ifModifiedSince=0) ==== __Since API version: 1.0.0__ Returns an indexed structure of all artists ^ Name ^ Type ^ Description ^ | 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'name': 'A' , u'artist': [{u'id': u'29834728934', u'name': u'A Perfect Circle' } , {u'id': u'238472893', u'name': u'A Small Good Thing' } , ... ... ] } , {u'name': 'B' , u'artist': [{u'id': u'983249823' , ... ... }] }] } , u'lastModified': 1303318347000L } Since this is a bit ugly and hard to discern, I'm going to lay it out in types table here. ^ Name ^ Type ^ Keys (if a dict) ^ Description ^ | b | dict | u'status', u'xmlns', u'version', u'indexes' | This is the root object that contains the usual ("status", "version", "xmlns") and also "indexes" | | b['indexes'] | dict | u'index', u'shortcut', u'lastModified' | This contains a list of shortcuts, which may or may not be there for you (I won't describe this here). It also contains the "index", which is where all our real data is | | b['indexes']['index'] | list | N/A | This is a list of dictionaries containing the root of the index. | | b['indexes']['index'][0] | dict | u'name', u'artist' | The ''name'' value here is a string that will be the index name, basically the first letter of this sections artist names (or a # for numbers) | | b['indexes']['index'][0]['artist'] | list | N/A | This will be a list of the artist dictionaries within the ''name'' section. So, if ''name'' is "A", then this will be a list of all the artists starting with the letter "A" | | b['indexes']['index'][0]['artist'][0] | dict | u'id', u'name' | This is the internal ID value for the artist and the actual artist name | I hope that table clarifies things a bit. ==== getInternetRadioStations(self) ==== __Since API version: 1.9.0__ Returns all internet radio stations Returns a dict like the following: { "internetRadioStations": { "internetRadioStation": { "homePageUrl": "http://www.di.fm/", "id": 0, "name": "DI hard dance", "streamUrl": "http://listen.di.fm/public3/harddance.pls" } }, "status": "ok", "version": "1.9.0", "xmlns": "http://subsonic.org/restapi" } ==== getLicense(self) ==== __Since API version: 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' } ==== getLyrics(self, artist=None, title=None) ==== __Since API version: 1.2.0__ Searches for and returns lyrics for a given song ^ Name ^ Type ^ Description ^ | 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' } ==== getMusicDirectory(self, mid) ==== __Since API version: 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. ^ Name ^ Type ^ Description ^ | mid | str | The string ID value which uniquely identifies the folder. Obtained via calls to ''getIndexes()'' or ''getMusicDirectory()''. | Returns a dict like the following if I were to call this with id for the artist "A Tribe Called Quest": { 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'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' } ==== getMusicFolders(self) ==== __Since API version: 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' } ==== getNowPlaying(self) ==== __Since API version: 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' } ==== getPlaylist(self, pid) ==== __Since API version: 1.0.0__ Returns a listing of files in a saved playlist ^ Name ^ Type ^ Description ^ | 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'album': u'Another album', ... ... }] , 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' } ==== getPlaylists(self) ==== __Since API version: 1.0.0__ Returns the ID and name of all saved playlists 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' } ==== getPodcasts(self , incEpisodes=True , pid=None) ==== __Since API version: 1.6.0__ Returns all podcast channels the server subscribes to and their episodes. ^ Name ^ Type ^ Description ^ | 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'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'2f6f70742f', 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'2f6f70742f7', u'publishDate': u'2011-08-17 22:06:00.0', u'size': 23313059, u'status': u'completed', u'streamId': u'2f6f70742', u'suffix': u'mp3', u'title': u'DrKarl: Peppermints, Chillies & Receptors', u'year': 2011 } , {u'album': u'klsddfjks', ... ... }], 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' } }, u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } See also: [[http://subsonic.svn.sourceforge.net/viewvc/subsonic/trunk/subsonic-main/src/main/webapp/xsd/podcasts_example_1.xml?view=markup]] ==== getRandomSongs(self, size=10, genre=None, fromYear=None, toYear=None, musicFolderId=None) ==== __Since API version: 1.2.0__ Returns random songs matching the given criteria ^ Name ^ Type ^ Description ^ | 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'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== getShares(self) ==== __Since API version: 1.2.0__ Returns information about shared media this user is allowed to manage Returns a dict like the following: { u'shares': {u'share': [{u'created': u'2011-08-18T10:01:35', u'entry': [{u'artist': u'Alice In Chains', u'coverArt': u'2f66696c65732f6d', u'id': u'2f66696c65732f', u'isDir': True, u'parent': u'2f66696c65732f6d7', u'title': u'Alice In Chains' } , {u'artist': u'ldksjfklsjf', ... ... }] , u'expires': u'2012-08-18T10:01:35', u'id': 0, u'url': u'http://someone.subsonic.org/share/BuLbF', u'username': u'admin', u'visitCount': 0 }] } , u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== getSong(self, id) ==== __Since API version: 1.8.0__ Returns the info for a song. This method uses the ID3 tags for organization. ^ Name ^ Type ^ Description ^ | id | str | The song ID | {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'} ==== getSongsByGenre(self, genre , count=10 , offset=0) ==== __Since API version: 1.9.0__ Returns songs in a given genre ^ Name ^ Type ^ Description ^ | 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 | ==== getStarred(self) ==== __Since API version: 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'} ==== getStarred2(self) ==== __Since API version: 1.8.0__ Returns starred songs, albums and artists like getStarred(), but this uses ID3 tags for organization. **See the output from [[#getStarred(self)]]** ==== getUser(self, username) ==== __Since API version: 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. ^ Name ^ Type ^ Description ^ | 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'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'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== getVideos(self) ==== __Since API version: 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'} ==== hls(self , mid , bitrate=None) ==== __Since API version: 1.9.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. ^ Name ^ Type ^ Description ^ | 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 ==== jukeboxControl(self, action, index=None, sids=[], gain=None , offset=None) ==== __Since API version: 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 ^ Name ^ Type ^ Description ^ | 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 version: 1.7.0//) Used by the "skip" action. Start playing this many seconds into the track. | ==== ping(self) ==== __Since API version: 1.0.0__ Returns a boolean True if the server is alive, False otherwise ==== refreshPodcasts(self) ==== __Since API version: 1.9.0__ Tells the server to check for new Podcast episodes. Note: The user must be authorized for Podcast administration Returns a dict like the following: { u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== scrobble(self, sid, submission=True) ==== __Since API version: 1.5.0__ "Scrobbles" a given music file on last.fm. Requires that the user has set this up. ^ Name ^ Type ^ Description ^ | sid | str | The ID of the file to scrobble | | submission | bool | Whether this is a "submission" or a "now playing" notification | | listenTime | int | The unix timestamp for the listen to scrobble. | Returns a dict like the following: { u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== search(self, artist=None, album=None, title=None, any=None, count=20, offset=0, newerThan=None) ==== __Since API version: 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 ^ Name ^ Type ^ Description ^ | 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 | ==== search2(self, query, artistCount=20, artistOffset=0, albumCount=20, albumOffset=0, songCount=20, songOffset=0) ==== __Since API version: 1.4.0__ Returns albums, artists and songs matching the given search criteria. Supports paging through the result. ^ Name ^ Type ^ Description ^ | 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' } ==== search3(self, query, artistCount=20, artistOffset=0, albumCount=20, albumOffset=0, songCount=20, songOffset=0) ==== __Since API version: 1.8.0__ Works the same way as [[#search2(self, query, artistCount=20, artistOffset=0, albumCount=20, albumOffset=0, songCount=20, songOffset=0)|search2()]], but uses ID3 tags for organization ^ Name ^ Type ^ Description ^ | 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'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'} ==== setAppName(self, appName) ==== Sets the ''appName'', as is set in ''__init__'' to the new ''appName'' ==== setBaseUrl(self, url) ==== Changes the base url to ''url'' ==== setPassword(self, password) ==== Sets the connection password to ''password'' ==== setPort(self, port) ==== Sets the connection port to ''port'' ==== setRating(self, id, rating) ==== __Since API version: 1.6.0__ Sets the rating for a media file ^ Name ^ Type ^ Description ^ | 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 dict like the following: { u'status': u'ok', u'version': u'1.5.0', u'xmlns': u'http://subsonic.org/restapi' } ==== setServerPath(self, path) ==== Sets the path portion of the url to ''path''. See ''__init__'' ==== setUsername(self, username) ==== Changes the connection username to ''username'' ==== stream(self, sid, maxBitRate=0 , tformat=None , timeOffset=None , size=None , estimateContentLength=False) ==== __Since API version: 1.0.0__ Streams a given music file. ^ Name ^ Type ^ Description ^ | sid | str | The ID of the music file to download. | | maxBitRate | int | (since API version: 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 ==== updateShare(self, shid, description=None, expires=None) ==== __Since API version: 1.6.0__ Updates the description and/or expiration date for an existing share ^ Name ^ Type ^ Description ^ | 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). | ==== Constants ==== These are the constants defined for the class: === API_VERSION === The version of the Subsonic API that this library currently supports. === ERR_CODE_MAP === A mapping of numbers to ''libsonic.error.*''. This is for internal use to map the error number from the server to the correct error type in this library. ===== Usage ===== The basic usage