Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / XML

WMP Playlist Fix

4.80/5 (2 votes)
6 Dec 2013CPOL6 min read 21K   549  
The goal is to clean-up double entries in Windows Media Player playlists because the player doesn’t do that itself. For that, a WPL class has been written, and a program that uses that class.

Screenshot of sample/controlling software

Introduction

I wanted to automatically clean-up Windows Media Player playlists. Therefore I needed a method to read and write the WPL files that hold playlists. WPLs are extended XML files.

What’s the problem:

  • Windows Media Player does not check for double entries in playlist. So you can end up with playlist that contain a lot of doubles that can only be removed manually.
  • Windows Media Player does not check for non-existent file when a playlist is loaded, only when a non-existent file is encountered, the program shows an error and halts playback, which is a problem.
  • When synchronizing a playlist to a portable device, Windows Media Player always uses an alphabetic write order, even when the list was shuffle using WMP. This is a problem if the playlist is larger than the portable device and you just want a shuffled selection on the device. (The solution for that with WMP is to temporarily save the shuffled list and then sync that list to the device).

So I wanted something that removed the doubles in a playlist and removed non-existent files. Those last ones occur, of course, when after the creation of the playlist the media file is removed or moved. And to top it off, make a shuffled playlist.

Background

Microsoft uses an extended XML format to store Playlists, called WPL. The playlist is stored between smil (Synchronized Multimedia Integration Language) tags. They differ from true XML files by the header.

XML
<?wpl version="1.0"?>
<smil>
 <head>
  <metadata name="ItemCount" content="129" />
  <author />
  <title>Chill-out</title>
 </head>
 <body>
  <seq>
   <media src="..\Santana\Moonflower Disc 2\05-Transcendance.mp3" />
  </seq>
 </body>
</smil>

Reading these files is no problem. Creating them is more of a problem. I used XmlWriter to create the WPL files and to be able to do that, the normal XML checking must be switched off. This can be done by specifying that the XML that is to be written is a fragment. To write the <?wpl version="1.0"?> header, the XMLWriter.WriteProcessingInstruction() method must be used.

C#
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " ";
settings.NewLineChars = "\xD\xA";
settings.ConformanceLevel = ConformanceLevel.Fragment;
settings.DoNotEscapeUriAttributes = true;
settings.CheckCharacters = false;
settings.Encoding = Encoding.UTF8;

XmlWriter writer = XmlWriter.Create(filename, settings);
writer.WriteProcessingInstruction("wpl", "version=\"1.0\"");
writer.WriteStartElement("smil");
header.Write(writer);
body.Write(writer);
writer.WriteEndElement(); // </smil>
writer.Close();

Using the Code

The project consists of three projects:

  • The WMPControlLib: This contains the WPL file control class WMPPlayList and the user control interface WMPPlayListCtrl.
  • The WMPPlayListFix application: This is just a wrapper user interface around the WMPPlayListCtrl user control.
  • The setup project: This is a setup program for the WMPPlayListFix program. It creates a J-Software\Tools folder in which the WMPPlayListFix application is copied, and creates a J-Software startmenu group with a shortcut to the application.

Using the WMPPlayListCtrl User Control

WMPPlayListCtrl user control

To use this user control, add a reference to the WMPControlLib assembly. The control is within the WPLControlLib namespace. Create an application with space (484x360 pixels) space for the control and add the following code:

C#
this.Controls.Add(new WMPPlayListCtrl(baseRegistryPath));

baseRegistryPath is something like this "Software\MyCompany\MyApp".
The registry path is used to store the user data from the control.

Alternatively, drag and drop the control from WMPControlLib Components list in the VS toolbox onto your application. Then set the registry path in the properties.

The user control contains an HTML About box with explanation on how to use the user control.

Using the WMPPlayList Class

The WMPPlayList is in the WMPControlLib DLL assembly within the WPLControlLib namespace. To use the class, add a reference to the DLL and create an instance of the WMPPlayList class.

WMPPlayList is the class that reads/writes and modifies playlists. Here is an example of its use to open a playlist, remove any entries with non-existent files, remove double entries based on the filename, remove the TID and CIS attributes from the media entries and store the playlist using the same filename.

C#
using WMPControlLib;
try
{
  WMPPlayList playlist = new WMPPlayList(filename);
  playlist.RemoveNonFiles();
  playlist.RemoveDoubles(WMPRemoveDoubleType.BasedOnFilename);
  playlist.RemoveTIDAndCIS();
  playlist.Store(filename, null);
}
catch (Exception exp)
{
  MessageBox.Show(exp.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}

The WMPRemoveDoubleType is an enum with three values. Remember that no physical files are removed, only the entries in the playlist!

  • WMPRemoveDoubleType.BasedOnTrackTitle: This will open each MP3 and extract the tracktitle from the metadata. If the mediafile is not an MP3, then the filename is used. This method will remove double tracks from different artists, for example Big Yellow Taxi has been covered by various artists, if you only want one in the playlist, use this remove method on the playlist. This process is slow, because all the media files have to be opened.
  • WMPRemoveDoubleType.BasedOnLocation: The whole file path is used to check for doubles. Use this to remove any media entries that have been accidentally inserted multiple times in the playlist.
  • WMPRemoveDoubleType.BasedOnFilename: Here the filename excluding path and extension is used for comparing items. This will remove doubles that have exactly the same filename but are in different locations. However, if the same music track appears on various "Best Of" albums, then this doesn't work because the track usually has different tracknumbers on different albums, and the tracknumber is usually include in the filename. Use the WMPRemoveDoubleType.BasedOnTrackTitle method instead.

The WMPlayList class uses the WMPPlayListHeader and WMpPlayListBody classes to store the header and body of the playlist. See the source code for details.

Feature Highlighting Code Samples

Iterating through the playlist items

C#
WMPPlayList pl = new WMPPlayList(filename);
WMPPlayListEntry item = pl.GetFirst();
while (item != null)
{
  String mediaFilename = WMPPlayList.BuildMediaPath(item.src);
  // do something with the mediaFilename.
  item = pl.GetNext();
}

Changing the title of a playlist (as displayed by WMP) is easy

C#
WMPPlayList pl = new WMPPlayList(filename);
pl.Store(filename, "Funky tunes of yesterday");

or:

C#
WMPPlayList pl = new WMPPlayList(filename);
pl.Title = "Funky tunes of yesterday";
pl.Store(filename, null);

The title of a playlist does not have to be the same as the filename.

Creating a new playlist programmatically

C#
String filename = WMPPlayList.GetPlayListSavePath() + @"\NewName.WPL";
WMPPlayList pl = new WMPPlayList();
pl.Add(@"pathtosomething.mp3");
pl.Store(filename, "Test of creating WPL");

WMPPlayListFix Application

This sample program for the use of the WPLControlLib control uses most features of the WPLControlLib. It, however, does not create new playlists.

This application has access to an HTML help text using the About item in the form Control box menu. To add (and intercept) the About menu item, the following code is added to the main form:

C#
using System.Runtime.InteropServices;

// P/Invoke constants
private const int WM_SYSCOMMAND = 0x112;
private const int MF_STRING = 0x0;
private const int MF_SEPARATOR = 0x800;

// P/Invoke declarations
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool AppendMenu(IntPtr hMenu, int uFlags, int uIDNewItem, string lpNewItem);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool InsertMenu
(IntPtr hMenu, int uPosition, int uFlags, int uIDNewItem, string lpNewItem);

// ID for the About item on the system menu
private int SYSMENU_ABOUT_ID = 0x1;

protected override void OnHandleCreated(EventArgs e)
{
  base.OnHandleCreated(e);

  // Get a handle to a copy of this form's system (window) menu
  IntPtr hSysMenu = GetSystemMenu(this.Handle, false);

  // Add a separator
  AppendMenu(hSysMenu, MF_SEPARATOR, 0, string.Empty);

  // Add the About menu item
  AppendMenu(hSysMenu, MF_STRING, SYSMENU_ABOUT_ID, "&About…");
}
protected override void WndProc(ref Message m)
{
  base.WndProc(ref m);
  // Test if the About item was selected from the system menu
  if ((m.Msg == WM_SYSCOMMAND) && ((int)m.WParam == SYSMENU_ABOUT_ID))
  {
    AboutForm frm = new AboutForm();
    frm.ShowDialog(this);
  }
}

Points of Interest

  • Language: The default location for playlists with WMP is the "playlists" folder in the "My Music" folder. However, I don't know if Microsoft localizes this "playlists" folder name in other language versions than the English I'm using. If this is the case, then the WMPPlayList class needs some rework. (See the next item.)
  • The paths to media files in playlists are relative to the location of the playlists folder or are absolute network URLs. This last one hasn't been tested with the program, but it should work.
  • WMP doesn't automatically update an open playlist if it is modified "behind" its back, to update a selected playlist in WMP, one has to select another playlist and then reselect the modified playlist.
  • The program is made to work with MP3 music files, I don't use WMP for any other purpose, so using it with video or pictures in a playlist has not been tested.
  • The program includes the WPLControlLib.RecentFileList class, which is a class to track user file or folder selection.
  • The program uses the WPLControlLib.ShellID3TagControl class, which comes from CodeProject but I can't find anymore who published that.
  • The WMPPlayListCtrl user control uses a BackgroundWorker process to prevent user interface blockage when converting, however when the WMPRemoveDoubleType.BasedOnTrackTitle method is used to remove doubles, then the userinterface blocks anyway because that process is slow. Note: This has been fixed by using a ManualResetEvent that is checked regularly by the WMPPlayList class and will cause any WMPPlayList process to instantly abort. Corruption of original playlist files is prevented by writing the WPL first to a temporary file and then copying that over the original (which is a singular action). See the WMPPlayList.RequestAbort, WMPPlayList.CancelAbortRequestcode methods and the WMPPlayList.AbortRequested property.
  • Weird: When WMP first saves a Playlist, it contains a few more metadata entries then after subsequent saves.
  • It seems that after I run the cleanup on my playlists that WMP started faster!
  • The project is a Visual Studio 2010 project.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)