Playing Play List files (PLS) in Windows Media Player

Arne Olav Hallingstad
13th December 2010
Home

Introduction

I bought a premium subscription for streaming radio at Digitally Imported Radio so I could listen to the high quality streams without ads. The 256 kbps file (di.fm.favorites.premium.pls) I downloaded is in the PLS file format which cannot be opened in Windows Media Player (WMP).

Since WMP does not support PLS files I opened up the file and found the URIs:

	[playlist]
	NumberOfEntries=9
	File1=http://72.26.216.106:80/trance_hi
	Title1=Trance
	Length1=-1
	File2=http://72.26.216.106:80/chillout_hi
	Title2=Chillout
	Length2=-1
	File3=http://72.26.216.106:80/goapsy_hi
	Title3=Goa-Psy Trance
	Length3=-1
	File4=http://72.26.216.106:80/drumandbass_hi
	Title4=Drum and Bass
	Length4=-1
	File5=http://72.26.216.106:80/psychill_hi
	Title5=PsyChill
	Length5=-1
	File6=http://72.26.216.106:80/dubstep_hi
	Title6=Dubstep
	Length6=-1
	File7=http://72.26.216.106:80/liquiddnb_hi
	Title7=Liquid DnB
	Length7=-1
	File8=http://72.26.216.106:80/techno_hi
	Title8=Techno
	Length8=-1
	File9=http://72.26.216.106:80/chilloutdreams_hi
	Title9=Chillout Dreams
	Length9=-1
	Version=2
alternate text

I tried to strip everything from the file except for the URIs and ended up with di.fm.favorites.premium.asx. Renaming the extension to Advanced Stream Redirector (ASX) so Windows Media Player could recognize the file:

	http://72.26.216.106:80/trance_hi
	http://72.26.216.106:80/chillout_hi
	http://72.26.216.106:80/goapsy_hi
	http://72.26.216.106:80/drumandbass_hi
	http://72.26.216.106:80/psychill_hi
	http://72.26.216.106:80/dubstep_hi
	http://72.26.216.106:80/liquiddnb_hi
	http://72.26.216.106:80/techno_hi
	http://72.26.216.106:80/chilloutdreams_hi

To my surprise WMP opened the file picking up all the lines as individual play list items. Playing the stream worked without problems as well.

Yes I know this isn't XML formatted text, which is what's used in ASX, but this worked and that was all I needed, a way of playing the 256 kbps streams found in the PLS file in WMP.

Making A PLS to ASX Converter

The initial success was great, but if I lost the ASX file I had created, or if I created a new list of favourites for downloading I would have to go through the repetitive task of extracting the URIs again. Using python I therefore started working on a converter.

Well it's fairly trivial to strip the unneeded information using a regular expression so I wouldn't have to manually copy/paste, but it wasn't as much of a challenge as this!

Python Command Line Converter

First solution was a simple command line program in python that takes source and destination as the input:

The program opens the source files and reads it line by line, searches the lines using a regular expression with a capture group, and outputs the resulting capture group if found to the destination file:

	# parse lines
	if lines != None:
		# open the destination for writing
		with open( dst, "w" ) as f:
			for line in lines:
				# search for the URI
				match = re.match( "^[ \\t]*file[0-9]*[ \\t]*=[ \\t]*(.*$)", line, flags=re.IGNORECASE )
				if match != None:
					if match.group( 1 ) != None:
						# URI found, it's saved in the second match group
						# output the URI to the destination file
						print "Found '%s'" % match.group( 1 )
						f.write( match.group( 1 ) )
						f.write( "\n" )

Full source

GUI Converter

Ideally there'd be a standalone executable with a GUI where you can easily browse for the source and destination files instead of having to run the program from the command line. Using a python script also has a dependency on python being installed on the computer which is undesired.

All we'd need for a GUI based version of the converter is a small window with a button you can click. If clicked it opens two file dialogues; one for the source file and one for the destination file. Once both have been closed the converter converts from source file to destination file.

There's several ways of making GUI based applications using python, most notably TkInter (Python's de-facto standard GUI (Graphical User Interface) which comes bundled with python.

Here we'll use wxPython. wxPython is the python bindings for the C++ framework called wxWidgets, a popular and cross-platform GUI framework which in my opinion is very easy to use. I also have previous experience using wxWidgets in C++.

The wxPython window creation looks like this:

	#
	# wxWidgets application frame
	# implements the application window frame
	#
	class AppFrame(wx.Frame):
		def __init__(self, parent, ID, title, pos=wx.DefaultPosition,
			size=(250, 75), style=wx.MINIMIZE_BOX | wx.SYSTEM_MENU |wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN ):
			wx.Frame.__init__(self, parent, ID, title, pos, size, style)

			# bind the windows close event
			wx.EVT_CLOSE(self, self.OnCloseWindow)

			# create a button, it's the only
			# item in the frame so it will
			# automatically stretch
			button = wx.Button(self, 1004, "Load Play Pist File (*.pls)")
			# bind to the button press event
			wx.EVT_BUTTON(self, 1004, self.OnPressMe)

		...

When the application button is pressed two file dialogues pops up, asking for the source and destination files.

	# on button press
	def OnPressMe(self, event):
		# open a file dialog so user can locate the pls file
		srcDlg = wx.FileDialog( self, "Locate pls file...", "", "", "pls files (*.pls)|*.pls|All files|*", wx.FD_OPEN )

		# user located a valid file
		if srcDlg.ShowModal() == wx.ID_OK:
			# get path to the source file
			srcPath = os.path.join( srcDlg.GetDirectory(), srcDlg.GetFilename() )
			print "src: '%s'" % srcPath

			# specify where and what name to use for output
			dstDlg = wx.FileDialog( self, "Save asx file...", "", "", "asx files (*.asx)|*.asx|All files|*", wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT )

			# user specified an output file
			if dstDlg.ShowModal() == wx.ID_OK:
				# get path to the dest file
				dstPath = os.path.join( dstDlg.GetDirectory(), dstDlg.GetFilename() )
				print "dst: '%s'" % dstPath
				# actually write to output file given the input file
				PLSToASXConverter( srcPath, dstPath )

	...

Full source

Creating a Single-file Executable

Now that there's a GUI based converter we'll create a single-file executable without additional dependencies.

I found two options for doing this:

I tried both since they were quick to test, but they initially created an executable with about 16 additional file dependencies in the same folder. pyInstaller was the one I first managed to create a single-file executable with, but there are ways of doing the same for py2exe by reading online.

Reducing Executable Size

Now that we have a single executable, the file size was fairly large so I downloaded Ultimate Packer for eXecutables (UPX).

UPX is a free, portable, extendable, high-performance executable packer for several executable formats.") to reduce the huge executable size.

It was as easy as copying the upx.exe into the python path and re-create the executable with pyInstaller.

The final executable is a LZMA compressed executable, 72% of the original size. The executable code includes byte-compiled versions of all required python code, the python DLL, requireds windows DLL dependencies and wx widget DLL dependencies (these are big).

4.3MB executable for this tiny wxPython GUI is huge, but given it has no additional dependencies and comes at an unpacked size of ~16MB it isn't bad at all.

Conclusion

Full python source for both the command line and wxPython version of the converter can be found here

References


Tweet

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 Unported License.