Showing posts with label PlaylistConverter. Show all posts
Showing posts with label PlaylistConverter. Show all posts

Friday, July 31, 2009

Possible Extensions

The code I currently have is working fine for me. However, there may be a few extensions that can be helpful.

Configure locations

For now, some information is hard coded, for example the initial path that the playlists are to be written to. There should be some easy way of configuring this information and storing the information “somewhere”.

Check existence of files

It only makes sense to put files in the playlist if the files exist at the location that is put into the playlist. The Converter could check if the file actually exists and only include the file if it is already there.

This information could be checked at the time the playlist is displayed in the listbox. Not sure if the runtime of all that checking would be acceptable.

Copy the music files if required

The next step would be to have the Converter copy the file from the local path to the server location if the file is missing at the server location. That way, the file would be there whenever it is referenced in the playlist on the server.

Interested or other ideas?

Let me know if one of these or some other feature would be useful to you – just leave a comment or drop me an email. I will then see if I can put that in the code.

Main

Friday, July 24, 2009

Retrieving track information from the iTunes XML library file

In order to convert a playlist as described in the previous post, we need two pieces of information:

  1. the tracks (represented by their IDs) in a playlist
  2. the location of the file of a certain track

How to get this from the iTunes XML library file is described in the next sections.

Get Tracks in a playlist

Similar to the playlist member in the library, the trackIDs member of the playlist is built at the fist time it is accessed:

public List<String> getTrackIDs()
{   // lazy initialization
 if (_trackIDs == null)
 {
     _trackIDs = new List<String>();

     //Query XML for TrackIDs
     IEnumerable<XElement> tracks = (from element in _root.Descendants()
                                       where element.Value.Equals("Track ID")
                                       select element);
     foreach (XElement track in tracks)
     {
         _trackIDs.Add((track.NextNode as XElement).Value );
     }
 };
 return _trackIDs;
}

Again, the LinkToXML query retrieves the items with a certain value (“Track ID”) from the XML. The statement uses just the part representing the playlist (stored in the _root member). It then moves over to the NextNode to get the actual value and stores all of these in a member that the form can then iterate over.

Get filenames for a given TrackID

Once you know the IDs of all the tracks in the playlist, you can then retrieve the filename as follows:
internal string GetTrackLocation(string ptrackID)
{
 // get root of XML-Dictionary for this track
 XElement track = (from element in _root.Descendants().Elements("key")
                   where element.Value.Equals(ptrackID)
                   select element).First().NextNode as XElement;

 XElement location = (from element in track.Descendants()
                   where element.Value.Equals ("Location")
                   select element).First().NextNode as XElement;

 string fileName = HttpUtility.UrlDecode((string)(location  as XElement));

 return fileName;
}
This code is in the library class. I could have used a dedicated track class, but apart from this little piece of information, there wasn’t anything else needed – so that felt a little bit too small to actually build a class for that.

The code gets the location in these steps:

  1. Get the dict for this specific track (lines 4 to 6)
  2. Get the “Location” from within this specific dict (lines 8 to 10)
  3. Get the ‘proper’ formatting by URL-Decoding the value from the XML.
I’m sure that there are easier or more compact ways to achieve this, but this code is working fine and seems quite clear to me. Let me know if you have better suggestions ….

Main

Friday, July 17, 2009

Converting the iTunes Playlists into the M3U format

Once the playlists are displayed in the listbox, the user can select the playlists to be exported and then click the “Convert” button. Here’s the code that is triggered by the button click:

private void btnConvert_Click(object sender, EventArgs e)
{
 // check existence of psPath
 DirectoryInfo di = new DirectoryInfo(txtResultPath.Text+"\\");
 if (!di.Exists) {
     MessageBox.Show ("Path to write to does not exist", "Problem",
         MessageBoxButtons.OK,MessageBoxIcon.Error);
     return;
 }

 foreach (Playlist pl in lbPlaylists.SelectedItems)
 {
     WriteToFile(pl, Path.GetDirectoryName(txtResultPath.Text+"\\"));
 }

 MessageBox.Show("Playlists written","Info",MessageBoxButtons.OK,MessageBoxIcon.Information);
}
After some checking for the existence of the path to write the playlists to (lines 4 to 9), the code loops through all the selected playlists and writes out each playlist to a file by calling the auxiliary function “WriteToFile”.

Here’s this method:

private void WriteToFile(Playlist pl, String psPath)
{
 //create the file - overwrite if necessary
 FileInfo fi = new FileInfo (Path.Combine (psPath, pl.name() + ".m3u"));
 StreamWriter fs = new StreamWriter(fi.Open(FileMode.Create));
 // write stuff
 foreach (String trackID in pl.getTrackIDs())
 {
     String trackLocation = _library.GetTrackLocation(trackID);

     // now remove the "local" part of the location
     int pos = trackLocation.IndexOf("/iTunes Music/");
     string newFileName = trackLocation.Substring(pos+13);
     newFileName = "//Marvin/musik" + newFileName;
     newFileName = newFileName.Replace("/", "\\");

     fs.WriteLine(newFileName);
 }
 fs.Close();
}
This could be a member of the Playlists, but I’ve decided to leave it in the Converter class in order not to introduce additional dependencies in the Playlist class. This way, all the Playlist and Library classes do is read the contents of the iTunes library. This way the Converter class does not have to deal with any XML. The code iterates through all the tracks in the playlist (through their IDs, line 7). The tracks are retrieved in the order that they are in the playlist file (which is implicitly the order of the tracks in the playlist). In the loop, the file location of the track is retrieved (line 9). This location is then stripped of the “local part” (the part up to and including “/iTunes Music/”, lines 12&13), then the path for my server is added (line 14) and some proper formatting is applied (line 15) so the new file path is usable by the TVersity media server. Each constructed filename is written out to the file. The next post describes the additional functionality in the library class in order to retrieve the required track information from the XML.

Main | Next

Friday, July 10, 2009

Display iTunes Playlists in the listbox

In the previous post I described how the playlists in an iTunes library are read from the XML file and then added to the listbox.

In order for the listbox to properly display these objects, the ToString() member for each of these playlist objects is called. Here’s this method:

public override string ToString()
{
  return name() + " (ID: " + id() + ", " + getCount() + " titles)";
}
Basically, it just constructs a string that gives out some information about the playlist. Each of these pieces of information is a simple method that retrieves the required information from the part of the playlist from the XML similar to these examples:
public string id()
{
  return (string)((from element in _root.Descendants()
                   where element.Value.Equals("Playlist ID")
                   select element).First().NextNode as XElement);
}

public int getCount()
{
  return (from element in _root.Descendants()
          where element.Value.Equals("Track ID")
          select element).Count();
}
Each of these queries is quite simple once you have a look at the structure of the XML, but the combination is pretty powerful to display all the playlists in my iTunes library: image

The next post describes how the conversion of the playlists works. Main | Next

Friday, July 3, 2009

Reading the Playlists from the iTunes Library XML

Once you have selected an iTunes Library XML (or if the default location is working), the code reads through the XML file to figure out which playlists are in the file.

The playlist is stored in an instance variable (line 1) that is instantiated with the filename as a parameter (line 3):

private Library _library;
...
_library = new Library(txtLibrary.Text);

In the constructor of the Library class the XML file is read:

public Library (string pfileName) {
 // no other way to set the root
 _root = XElement.Load(pfileName);
 _playlists = null;
}

Line 3 is the first line of code that uses LinqToXML ... not much too it. For a production environment, some kind of error handling would probably have to be added. The instance variable _playlists is not initialized, it will be set up at the time it is accessed.

The fist access happens when the playlists are to be displayed in the list box. This code is from the Playlist Converter Form right after instantiating the _library instance variable:

foreach (Playlist pl in _library.getPlaylists()) {
 lbPlaylists.Items.Add(pl);
}

Getting the playlists requires some XML reading:

public List<Playlist> getPlaylists ()
{   // lazy initialization
 if (_playlists == null)
 {
     _playlists = new List<Playlist>();

     //Query XML for Playlists
     XElement rootList = (from element in _root.Descendants()
                          where element.Value.Equals("Playlists")
                          select element).First().NextNode as XElement;
     IEnumerable<XElement> listIDs = (from listFields in rootList.Descendants()
                                        where listFields.Value.Equals("Playlist Persistent ID")
                                        select listFields);
     foreach (XElement list in listIDs)
     {
         _playlists.Add(new Playlist(list.Parent as XElement));
     }
 };
 return _playlists;
}

The method returns a list of playlists. If the instance variable is already initialized, it is just returned in a getter fashion. Otherwise we build the list in a few simple steps.

First, we find the root element for the playlists in the XML file with the query in lines 8 to 10. We look for a node that has a value of “Playlists”. There is only one in the file, so “.First” returns this single node. The array of playlists is the next node after that, so “.NextNode” returns this array.

Once we have that array, we can then query in it for the individual playlists. Lines 11 to 13 looks for all the “Playlist Persistent ID” nodes in this array. Then we can iterate through these elements and build Playlist objects with the dict for an individual playlist (line 16).

After each of these new playlist objects has been instantiated, they can just be added as an item to the listbox. In order for the listbox to properly display these items in a list, the ToString() member of the playlist will be called. This will be described in the next post …

Main | Next

Friday, June 26, 2009

Base functionality of the Form

Here is a screenshot of the form in designer view:

image

This form is just relatively simple C'# code (the LinQ2XML will be described in future posts). I’ll just point out a few of the important parts:

Choosing the iTunes library

This field holds the fully qualified filename for the iTunes library. There is a sensible default (line 11), but you can choose a different location with the “Choose …” button (code in lines 27ff.)

Populating the listbox of playlists (lbPlaylists)

This listbox holds the playlists in the iTunes library. It is populated at the startup of the form and whenever a different library path is chosen. We’ll have a closer look at the code that achieves this behavior in another post (lines 18ff.).

Select all / Select None of the entries in lbPlaylists

These are just shortcuts to make selecting all or unselecting of the playlists easier. (Or is it select none?!) It’s just a simple loop over all the items in the listbox (lines 64ff and 72ff).

Choosing the path for the converted playlists (noxon Library)

Another simple data field that holds the directory the converted playlists are to be written to. The behavior of the selection dialog is a little bit different than the one for the iTunes library (lines 46ff).

Convert the playlists

The code that is triggered by clicking the “Convert” button will be described in another post.

Here’s the main part of the form code:

public partial class frmConverter : Form
  {
      private Library _library;

      public frmConverter()
      {
          InitializeComponent();
          txtLibrary.Text =
                 Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)
                 + "\\iTunes\\iTunes Music Library.xml";
          txtResultPath.Text = "\\\\Marvin\\musik\\Playlists";
          // for testing
          //txtResultPath.Text = "C:\\temp\\Playlists\\";
          _library = new Library(txtLibrary.Text);
          populate_lbPlaylists();          
      }

      private void populate_lbPlaylists()
      {
          lbPlaylists.Items.Clear();
          foreach (Playlist pl in _library.getPlaylists())
          {
              lbPlaylists.Items.Add(pl);
          }
      }

      private void btnChooseLibrary_Click(object sender, EventArgs e)
      {
          OpenFileDialog dlg = new OpenFileDialog();
          dlg.Filter = "iTunes Library (*.xml)|*.xml";
          dlg.InitialDirectory = Path.GetDirectoryName(txtLibrary.Text);

          if (dlg.ShowDialog() == DialogResult.OK)
          {
              txtLibrary.Text = dlg.FileName;
              _library = new Library(txtLibrary.Text);
              populate_lbPlaylists();
          }
      }

      private void btnClose_Click(object sender, EventArgs e)
      {
          this.Close();
      }

      private void btnChooseResult_Click(object sender, EventArgs e)
      {
          FolderBrowserDialog dlg = new FolderBrowserDialog();
          dlg.SelectedPath = Path.GetDirectoryName(txtResultPath.Text+"\\");
          dlg.Description = "Choose the path to write the playlists to:";
          dlg.ShowNewFolderButton = false;

          if (dlg.ShowDialog() == DialogResult.OK)
          {
              txtResultPath.Text = dlg.SelectedPath;
          }
      }

      private void btnConvert_Click(object sender, EventArgs e)
      {
   // more on this later
      }

      private void btnAll_Click(object sender, EventArgs e)
      {
             for (int i = 0; i < lbPlaylists.Items.Count; i++)
              {
                  lbPlaylists.SetSelected (i, true );
              }
      }

      private void btnNone_Click(object sender, EventArgs e)
      {
          for (int i = 0; i < lbPlaylists.Items.Count; i++)
          {
              lbPlaylists.SetSelected(i, false);
          }
      }
  }

Main | Next

Thursday, June 11, 2009

A look at the iTunes XML and playlist formats

The playlist converter basically grabs the playlists in iTunes and converts them into a different format. Before looking at the code, here is a short description of the relevant formats.

iTunes XML

iTunes stores the “meta-information” about the music, playlists etc. that you can use and set up in iTunes in an XML file called “iTunes Music Library.xml” which is usually stored in the iTunes music folder (usually My Documents\My Music\iTunes).

There is a couple of web documents that describe the format. I have used an article by Niel Bornstein called “Hacking iTunes”. Some examples here are taken from this article.

There are three main sections in the file:

  1. some meta info (such as general location infos, version etc.)
  2. tracks info (a large dictionary of track information)
  3. playlists info (a large dictionary of playlist information)
Tracks

For each track, there is number of key – value pairs. ‘Key’ can be Track ID, Name, Artist and Location, whereas value can be a number or string which denotes the key’s value, e.g.

 <key>Track ID</key><integer>839</integer>
<key>Name</key><string>Sweet Georgia Brown</string>
<key>Artist</key><string>Count Basie & His Orchestra</string>
<key>Composer</key><string>Bernie/Pinkard/Casey</string>
<key>Location</key><string>file://localhost/Users/niel/Music/iTunes/iTunes%20Music/Count%20Basie%20&%20His%20Orchestra/Prime%20Time/03%20Sweet%20Georgia%20Brown.m4p</string>
Playlists
Each playlist also has some meta information (such as name etc., lines 2 to 4) and an array with the TrackIDs of the tracks that make up the playlist (lines 7 to 14):
<dict>
<key>Name</key><string>Funky</string>
<key>Playlist ID</key><integer>6652</integer>
<key>Playlist Persistent ID</key><string>88CED99A2F698F3C</string>
<key>All Items</key><true/>
<key>Playlist Items</key>
<array>
<dict>
<key>Track ID</key><integer>837</integer>
</dict>
<dict>
<key>Track ID</key><integer>754</integer>
</dict>
</array>
</dict>
This playlist (called “Funky”) contains two tracks (with the IDs 837 and 754). Information about these tracks can be obtained from the tracks section using the IDs. As this is a “plain” XML file, it can be queried using LinQ To XML.

M3U playlist

The M3U playlist format is very simple, it is just a list of the files to be played, each title in its own line:
\\Marvin\musik\Download\Run\01 01 Mornin'.mp3
\\Marvin\musik\Download\Run\13 08 Man Of La Mancha (I, Don Quixote).mp3
\\Marvin\musik\Download\Bike\01 01 Child's Anthem.mp3
This playlist contains three titles, each denoted by the location of their MP3 files. Additional information (Artist, Title, Album etc.) can only be obtained from the meta-information stored in the MP3 files. The file is a plain text file – as I only have to write to this file, I’m just using a simple TextStream to write to. If the format was a bit more involved (say another, maybe differently formatted XML file), I could have used LinQToXML again, but it would be overkill for writing M3U files.

Main | Next

Saturday, June 6, 2009

A first look at the PlaylistConverter

The general idea behind the PlaylistConverter is described in a previous post.

Here’s a screenshot of the PlaylistConverter GUI:

image

On the top is the location of your iTunes library. The program uses a sensible default so usually there is no need to point the program to a specific location using the Choose-button to the right.

The middle part lists all the playlists in your iTunes library. It also displays some “internals” (such as the ID of the playlist) and the number of titles in the playlist. From the available lists, you can choose one or more lists to be exported. The ‘Select All’ and ‘Unselect All’ buttons make life a bit easier.

Under the list of playlists is the location that the playlists will be exported to (the so-called ‘noxon Library’ which is actually a folder on my Windows Home Server). I’ve hard-coded a default for my environment, but again you can choose a different location with the Choose-Button.

If you click ‘Convert’ the selected playlists will be converted and saved in the noxon library directory. Each playlist will be saved in an individual file. The file locations of the titles will be changed according to the paths on the Home Server.

So with this program, my wife can set up a playlists on her own computer in iTunes and convert it into a format and locations on the Windows Home Server. There, TVersity picks up the new content and makes it available to the noxon.

End result: You set up a playlist on your own PC and you can play this list on the noxon. Pretty cool!

Here's a list of the posts that describe the PlaylistConverter:

If the current functionality is not sufficient, here are some ideas for extensions. Also, when you're interested in the full source code or an installer, drop me an email.

Friday, June 5, 2009

Introducing the iTunes Playlist Converter (with C# and LinQ)

Here’s the scenario that needed a solution:

  • My wife and I both have our individual PCs with our music collections (not too much overlap between us ;-)). Originally it was mostly ripped CDs but since iTunes und Amazon started selling MP3s without any DRM, we’ve mostly switched to “digital downloads”.
  • We also have a Noxon “internet radio” in our living room so we can listen to Internet streams through wireless LAN.
  • Occasionally we also want to play music from our individual collections (e.g. background music for parties, especially “seasonal” music at Christmas) on our living room stereo.

We could have copied the MP3s to a thumb drive and hooked that up to the Noxon, but that sounded a bit manual. (C’mon - you buy your music digitally from Apple or Amazon, it gets delivered to your PC from America or wherever, but in order to play it on your stereo you have to “sneakernet” it to the living room!) So I was looking for a better solution …

After looking around a bit, I tried TVersity. It is a “streaming server” using the UPnP protocol. TVersity publishes a list of available media to the Noxon, and can stream titles or playlists that are then played on the Noxon using my local wireless LAN.

TVersity even works on my Windows Home Server. So just copy all our music on a regular basis to the Home Server where TVersity can find it, and your almost done. This works great for individual titles and full albums, but playlists don’t.

First, we both use iTunes to manage our music, and iTunes has its own way of storing the playlists (using one larfge file an XML format, and it’s a format that is not supported by the Noxon which uses an individual M3U file for each playlist). Second, all the references in the playlist are to files local to each PC and that is different from where the files are stored on the home server.

A good scenario to try out some C# programming! Also, as iTunes uses an XML file to store all its information, I could play around with LinQ to query the contents.

The book I used to get up to speed on LinQ was this:

I found this to be quite well written, but I needed the work of “doing it myself” before I was able to figure out which parts I had not quite understood. Pretty normal for anything new!

Next: A first look at the PlaylistConverter