Playing Play List files (PLS) in Windows Media Player
Arne Olav Hallingstad13th 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
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" )
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 ) ...
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:
- py2exe - converts Python scripts into executable Windows programs, able to run without requiring a Python installation.
- pyInstaller - PyInstaller is a program that converts (packages) Python programs into stand-alone executables, under Windows, Linux, and Mac OS X.
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
- PLS File Format
- ASX File Format
- Windows Media Player
- Digitally Imported Radio
- TkInter (Python's de-facto standard GUI (Graphical User Interface)
- GUI Frameworks For Python
- wxPython
- wxWidgets
- py2exe ("Converts Python scripts into executable Windows programs, able to run without requiring a Python installation")
- pyInstaller ("PyInstaller is a program that converts (packages) Python programs into stand-alone executables, under Windows, Linux, and Mac OS X.")
- Ultimate Packer for eXecutables (UPX, "UPX is a free, portable, extendable, high-performance executable packer for several executable formats.")