M3u files in a tree

Wednesday 5 November 2003

On Sunday I wrote about Reading mp3 metadata, in an effort to create m3u playlist files for mp3 files stored in directories by artist and album. I got an unexpected solution from an unexpected source.

I had cast the problem as how to read data out of mp3 files so that I could populate m3u files. You see, there's no spec I could find for m3u files, so the best I could do was to create files like those created by Winamp. They look kind of like this:

#EXTM3U
#EXTINF:173,Aimee Mann - One
\music\mp3\Aimee Mann\Magnolia\01-One.mp3
#EXTINF:207,Aimee Mann - Momentum
\music\mp3\Aimee Mann\Magnolia\02-Momentum.mp3

The number after #EXTINF is the duration of the song, in seconds. The rest of the line is the title to display in the player. So I figured I needed to determine the length of the song, and find a good title. But the title data is dirty and stored in discouragingly diverse ways. The length isn't stored at all, and seems to require reading all of the song data to decode fiddly bit rates. Yuck.

Then I got an email on the topic from Martin Fowler of all people. He wasn't delivering some deep insight about refactoring or object orientation. He told me about how he's organized his mp3 files, and in particular, he told me he makes m3u files that are just a long list of file names.

Of course! Just make a list of file names, and Winamp will read the other data itself when the song plays. Simple. A few minutes with Python, and I had it all done:

# Create a tree of .m3u files, one for each directory in the tree,
# containing all the .mp3 files in the subtree rooted there.

import path

# A stack of .m3u files.
m3us = []

def doDir(d):
    '''Called recursively for each directory in the tree.'''

    # Make a new .m3u file for this directory.
    m3u = d / (d.name + '.m3u')
    m3us.append(open(m3u, 'w'))

    # Add all the .mp3's in this dir to all the .m3u's.
    for f in d.files('*.mp3'):
        for m3ufile in m3us:
            m3ufile.write(f + '\n')

    # Recurse into all the subdirectories.
    for dchild in d.dirs():
        doDir(dchild)

    # We're done with this directory's .m3u file.
    m3us.pop()

doDir(path.path('.').abspath())

Thanks, Martin!

Update: In January 2004 I finally got a complete solution together.

Comments

[gravatar]
William Ryken 2:00 PM on 6 Nov 2003

there is a Notes database in the Sandbox that contains code for reading and setting ID3 tags in MP3 files. and it then can create m3u files on the fly for you. I thought it was interesting...

MP3 PlayList v1.2

[gravatar]
Sam 9:42 PM on 12 Dec 2003

Hi
Found this, and thought I would give it a whirl.
It seems to puke on certain characters in dir names (on Win XP).

This is the error message I got
-----------------------------------
Traceback (most recent call last):
File "E:\m3u.py", line 30, in ?
doDir(path.path('.').abspath())
File "E:\m3u.py", line 25, in doDir
doDir(dchild)
File "E:\m3u.py", line 25, in doDir
doDir(dchild)
File "E:\m3u.py", line 25, in doDir
doDir(dchild)
File "E:\m3u.py", line 25, in doDir
doDir(dchild)
File "E:\m3u.py", line 21, in doDir
m3ufile.write(f + '\n')
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe8' in position 60: ordinal not in range(128)
-------------------------

Feedback appreciated.

[gravatar]
Kris 1:36 PM on 24 Dec 2003

I could not get this to work 'as-is'. I don't know if it's because I am using Windows or a different version of Python (2.3.3). These lines are the problems:

import path
.
.
doDir(path.path('.').abspath())

There is no such module as path, so I modified mine to

import os.path as ospath
.
.
doDir(ospath.abspath('.'))

But now I think your function is expecting 'd' to be a type other than str.

I think this because the '/' operator fails and the .name and .files functions also fail (if I remove the '/').

I could not find what module or whatever contained these attributes (name and files)?

[gravatar]
Ned Batchelder 6:13 PM on 28 Dec 2003

The path module is available at
jorendorff.com

[gravatar]
Ron 1:23 AM on 28 Oct 2005

Pity you didn't explain this more in detail... Only the ones who already "know" would understand!

Add a comment:

name
email
Ignore this:
not displayed and no spam.
Leave this empty:
www
not searched.
 
Name and either email or www are required.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.