Saturday, January 15, 2011

Extracting Sonos Playlists - A Simple Sonos JavaScript Application, Take II

Overview

(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.
Browse Action Dialog


Step 3: Open (double click or right Invoke Action) the Browse Action.

Invoke Action Dialog

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&amp;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&amp;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:

Output of transforming Sonos XML

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.



8 comments:

  1. HI TravelMarx, Thanks for your nice extended version. While testing, i found four little syntax errors: line 177: a missing ';'
    lines 183,187 and 191 lost the logical or character (||). The old version functions are
    still running, but the playlists are not loaded.

    ReplyDelete
  2. Thanks! 177 was my fault. Weird that I found it in my source too and the page (in success) runs fine. I guess it means I didn't check that execution path. Was expecting VS to complain about syntax but it must be nested too deeply or something. The other lines are due to the encoding program I use to encode the code before sticking in the blog page. The program doesn't deal well with ||. This happened before and I thought I checked it but oh well. Thanks again for the corrections. I fixed the spots above you mentioned.

    ReplyDelete
  3. Hi TravelMarx, it's me again. While testing i found additional errors. Line 214 has 3 missing '||' operators. Also in line 384 you lost a '&&' operator. Because the playlist function is still not working yet - i think in the getPlaylist() function is a SQ: missing near the 'value' variable. The playlists are send from the sonus player, but not shown in the select box. But i still looking for this problem, because i'm new to the javascript and jQuery environment.

    oTsch

    ReplyDelete
  4. Thanks again. Boy, i'm screwing up!

    Line 214 needed just "|" need to use |
    Line 215 was screwed up. I'm trying to show a br element there but it comes out funny,
    Line 384 was missing "|", an OR
    Line 480/481 mising a"|" now is there. Just for UI so not that important
    Line 508-512 ditto, missing four visual "|"

    getPlaylist(), the SQ: value comes from the dropdown option value attribute

    Mind you, line numbers will change a bit as fixes are made (two lines may be combined into one).

    I made the changes and copied the code back to my environment and it worked. Hopefully no more problems. But to be safe, include download of code and will in future posts.

    http://cid-3faa5c9b69a80c67.office.live.com/self.aspx/Public/WhatSonosIsPlaying.htm

    ReplyDelete
  5. the plkaylist no charge

    ReplyDelete
  6. Wow that's a wonderfull blog having all details & helpful. DC web application

    ReplyDelete
  7. where can i donwload the program?

    ReplyDelete
  8. https://github.com/travelmarx/travelmarx-blog has the all the code/programs discussed here.

    ReplyDelete

All comments go through a moderation process. Even though it may not look like the comment was accepted, it probably was. Check back in a day if you asked a question. Thanks!