(The code on this page was last checked and verified in June 2014.)
Did you ever want to extract a Sonos playlist so you could share it or keep it for your records outside of Sonos? Well you can. In this post we’ll talk about some simple ways to do it. First, let’s start with a little history. We’ve written about Sonos a couple of other times in the blog. First we showed how to explore your Sonos UPnP topology and then we wrote a simple program using JavaScript to query Sonos (via SOAP requests) to display basic information about what was playing (on GitHub). A reader of the latter blog entry asked the question about generating playlists, and then contributed a Java program which does it (on GitHub). Still later (a year), we developed a WPF application (see WPF Application to Save and Import Sonos Playlists) to extract lists. This post summarizes all the approaches:
- First Approach - Manual
The approach requires minimal programming – you work with DeviceSpy (or something equivalent), issue a command, get the returned XML, and transform it. (If you need info on DeviceSpy see the Exploring Sonos via UPnP.) This approach is discussed in detail below. - Second Approach - Javascript in the Browser
The second approach extracts playlists using JavaScript in a browser page. It requires you to configure your browser so that it can communicate to the Sonos devices (disable same origin policy). This approach is discussed below, but only in comparison to the first approach. See A Simple Sonos JavaScript and Java Application for details on how to use this program. (Code is on GitHub.) - Third Approach - Java Program
The third approach is a simple Java program that takes the IP address of a Sonos device and then spits out what's in its queue. You have to specifically load a playlist in a queue to download it. (This may be useful for just capturing what's in a queue in general.) This approach requires some knowledge of how to run a basic Java program. See A Simple Sonos JavaScript and Java Application for details on how to do this. (Code is on GitHub.) - Fourth Approach - WPF Program
This is a C# program running on Windows 7/8 which can extract one or many playlists. It can also import into a playlist. This approach is discussed here: WPF Application to Save and Import Sonos Playlists.
First Approach – Manual
Step 1: Open DeviceSpy and go to a zone player that is a master. If all your zones are in one group and you look at a Sonos controller (e.g. the handheld or the iPhone application) the master is the first item in the zone list*. To start with just put all the zone players in the same zone to make it easy.***update: Not true. The master is the zone you started the zone group with. You can
**update: Maybe easier is to just create a group of one zone and work with that then you are assured it is the master.
Step 2: In DeviceSpy, go to a zone master expand the Media Server Device, and expand the Content Directory Service.
Step 3: Open (double click or right Invoke Action) the Browse Action.
Step 4: Fill in the parameters in the Browse Action as shown in each query below. The queries end with Query 6 which is what we really want, displaying what’s in a playlist. In each query, you fill in the fields as shown and hit Invoke and the results will be in the Result field as XML. For now you can post the XML in Notepad to get a sense of what it contains. To get the XML from the Result field to the clipboard place your cursor in the field and CTRL + A to select all.
Query 1: Get a Count of What’s in the Queue
- ObjectID = Q
- BrowseFlag = BrowseDirectChildren
- Filter = [leave blank]
- StartingIndex = 0
- RequestedCount = 1
- SortCriteria = [leave blank]
- Result = [look for the childCount attribute in the returned XML]
Query 2: Get What’s in the Queue
- ObjectID = Q:0
- BrowseFlag = BrowseDirectChildren
- Filter = [leave blank]
- StartingIndex = 0
- RequestedCount = 100 (or something high enough to get what you want; if the queue is very big consider doing this in several operations)
- SortCriteria = [leave blank]
- Result = [lots of XML returned about what’s in the queue]
Query 3: See What’s Available to Query In Terms of Searching for Music
- ObjectID = A:
- BrowseFlag = BrowseDirectChildren
- Filter = [leave blank, sure wish I knew the format for using it though…]
- StartingIndex = 0
- RequestedCount = 10
- SortCriteria = [leave blank]
- Result = [XML which shows A:ALBUMARTIST, A:ALBUM, …, A:PLAYLISTS as valid ObjectIDs]
Query 4: See What’s Available to Query System-wide
- ObjectID = 0 [zero]
- BrowseFlag = BrowseDirectChildren
- Filter = [leave blank]
- StartingIndex = 0
- RequestedCount = 10
- SortCriteria = [leave blank]
- Result = [XML which shows A:, S:, Q:, SQ:, R:, AI:, and EN: ]
A: Atribute
S: Music Shares
Q: Queues [we’ve seen an example above]
SQ: Saved Queues
R: Internet Radio
AI: Audio Inputs
EN: Entire Network
Query 5: List Saved Queues (Playlist)
- ObjectID = SQ:
- BrowseFlag = BrowseDirectChildren
- Filter = [leave blank]
- StartingIndex = 0
- RequestedCount = 100
- SortCriteria = [leave blank]
- Result = [XML which shows SQ:1 to however many playlists you have. ]
Query 6: List Songs in a Particular Saved Queue (Playlist)
- ObjectID = SQ:1 [choose a # you know exists using the previous query]
- BrowseFlag = BrowseDirectChildren
- Filter = [leave blank]
- StartingIndex = 0
- RequestedCount = 100
- SortCriteria = [leave blank]
- Result = [XML which shows contents of saved queue or playlist]
The following example below shows some XML from the Results field. The XML shows one track coming from Rhapsody and one from a local server called mediaserver.
<?xml version="1.0" encoding="utf-8"?> <DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"> <item id="SQ:5/radea%3aTra.16238560.mp3:AFreely,Devendra%20Banhart,Smokey%20Rolls%20Down%20Thunder%20Canyon,298" parentID="SQ:5" restricted="true"> <res protocolInfo="real.com-rhapsody-direct:*:audio/mp3:*" duration="0:04:58">radea:Tra.16238560.mp3</res> <upnp:albumArtURI>/getaa?r=1&u=radea%3aTra.16238560.mp3</upnp:albumArtURI> <dc:title>Freely</dc:title> <upnp:class>object.item.audioItem.musicTrack</upnp:class> <dc:creator>Devendra Banhart</dc:creator> <upnp:album>Smokey Rolls Down Thunder Canyon</upnp:album> </item> <item id="S://mediaserver/music/Master/Supertramp/Even%20in%20the%20Quietest%20Moments/Supertramp%20-%2004%20-%20Downstream.flac" parentID="SQ:5" restricted="true"> <res protocolInfo="x-file-cifs:*:audio/flac:*">x-file-cifs://mediamarx/music/Master/Supertramp/Even%20in%20the%20Quietest%20Moments/Supertramp%20-%2004%20-%20Downstream.flac</res> <upnp:albumArtURI>/getaa?u=x-file-cifs%3a%2f%2fmediamarx%2fmusic%2fMaster%2fSupertramp%2fEven%2520in%2520the%2520Quietest%2520Moments%2fSupertramp%2520-%252004%2520-%2520Downstream.flac&v=167</upnp:albumArtURI> <dc:title>Downstream</dc:title> <upnp:class>object.item.audioItem.musicTrack</upnp:class> <dc:creator>Supertramp</dc:creator> <upnp:album>Even in the Quietest Moments...</upnp:album> <upnp:originalTrackNumber>4</upnp:originalTrackNumber> </item> </DIDL-Lite>
Step 6: Take the XML from the last request (a particular saved queue) and put in a file called playlist.xml.
Step 7: Create a XSLT file in the same location as the XML file. Call it transform.xslt. You can create a text document and change the extension to .xslt.
Step 8: In the XSLT file put the following:
<?xml version="1.0" encoding="utf-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:didl="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" > <xsl:output method="html" indent="yes"/> <xsl:template match="didl:DIDL-Lite"> <xsl:for-each select="didl:item"> <xsl:value-of select="dc:creator"/>, <b> <xsl:value-of select="upnp:album"/>, </b> "<xsl:value-of select="dc:title"/>" <br></br> </xsl:for-each> </xsl:template> </xsl:stylesheet>
Step 9: Open the playlist.xml file and put the following line after the version (?xml) line so that it’s the second line in the file:
<?xml-stylesheet type="text/xsl" href="transform.xslt"?>
Step 10: Close and save both files and double-click on the playlist.xml file. It should open in a browser and show the transformed list. For example for the XML shown above the transform produces:
The XML the query returned is DIDL which stands for Digital Item Declaration Language and is an XML dialect for MPEG-21. In the XLST file you have to be sure and use the namespace declarations carefully. The XML file has xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" so we have to use that in XSLT as well. In the XLST we define it as xmlns:didl="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/" using a prefix of “didl”.
In this example we use the browser (verified in Internet Explorer 9 and Chrome) to do the transform.
Second Approach - Javascript in the Browser
The HTML page (using JavaScript and jQuery) that was given in a previous post is approach shown (or really repeated here). Note that the page (HTML and JavaScript) depends on the ability of the browser to “Allow data access across domains” (a setting). See the post for details on doing that at least for IE and Chrome.
1. Populate a drop down list with defined playlists. Uses Query 5 above.
2. Create two buttons next to playlist drop down list. Uses Query 6 above. One button shows the playlist items in the page and the other to copy the playlist to the clipboard. (Since we are stuck with Internet Explorer, we went ahead and used the clipboardData object.
3. Automatically change the zone drop down to the master zone - was a manual process in the previous version.
An example of the page output is shown below. Get the code from GitHub.