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.
="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.
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();
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
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:
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.
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
WMPPlayList pl = new WMPPlayList(filename);
WMPPlayListEntry item = pl.GetFirst();
while (item != null)
{
String mediaFilename = WMPPlayList.BuildMediaPath(item.src);
item = pl.GetNext();
}
Changing the title of a playlist (as displayed by WMP) is easy
WMPPlayList pl = new WMPPlayList(filename);
pl.Store(filename, "Funky tunes of yesterday");
or:
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
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:
using System.Runtime.InteropServices;
private const int WM_SYSCOMMAND = 0x112;
private const int MF_STRING = 0x0;
private const int MF_SEPARATOR = 0x800;
[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);
private int SYSMENU_ABOUT_ID = 0x1;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
IntPtr hSysMenu = GetSystemMenu(this.Handle, false);
AppendMenu(hSysMenu, MF_SEPARATOR, 0, string.Empty);
AppendMenu(hSysMenu, MF_STRING, SYSMENU_ABOUT_ID, "&About…");
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
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.