Jump to content

[PYTHON] Make Playlists for all your music!

- - - - -

  • Please log in to reply
No replies to this topic

#1
ZekeDragon

ZekeDragon

    Writes binary right handed and hex left handed

  • Moderators
  • 2,103 posts
Well I was doing what I normally do, namely programming and listening to music, and I was kind of sick of always having a heavyweight music player in the background playing my tunes! I knew that VLC is far less of a resource hog than Rhythmbox is, so I thought about it and I decided that I'd come up with a really good way to organize all my songs into playlists, using the .xspf format (since that's what VLC is good at reading). Well, I could have done this by opening up all the songs, one ripped CD at a time, and made playlists for each one... but that would have been time consuming and really rather boring.

So then I realized... "I'm a programmer, d*mnit! I can make this computer do what I want!"

That's when I set out to write a Python script to generate all my playlists for me, and I wanted it to write the playlists twice, once in the file where the ripped music itself was, and once more in a dedicated "Playlists" file in my Music directory.

After a few hours, here's my finished script:
import sys
import os
import re

# Sh*tty workaround for Python 2.5 relative imports
sys.path.append(os.path.join(os.getcwd(), ".."))
from oggvorbis import OggVorbis

class Album:
    def __init__(self, filename):
        self.file = filename
        self.songs = []

def get_files(dir):
    foundFiles = []

    if dir[-1:] == "/":
        dir = dir[0:-1]

    for x in os.listdir(dir):
        if os.path.isdir(dir + "/" + x):
            foundFiles.extend(get_files(dir + "/" + x))
        else:
            if x[-3:] == "ogg":
                foundFiles.append(dir + "/" + x)

    return foundFiles

def compare_songs(first, second):
    return int(first[:2]) - int(second[:2])

def get_and_split(dir):
    files = get_files(dir)
    retlist, templist = [], []
    
    temp = files[0].split('/')[-2]
    alb = Album(files[0].rpartition('/')[0])
    for file in files:
        temp2 = file.split('/')[-2]
        
        if temp != temp2:
            if alb.songs != []:
                alb.songs.sort(cmp=compare_songs)
                retlist.append(alb)
            
            alb = Album(file.rpartition('/')[0])
            temp = temp2
        else:
            st = file.rpartition('/')[2]
            if re.search(r'^\d{1,2}(?=(\.\s|\s-\s))', st) != None:
                alb.songs.append(st)
            temp = temp2
    
    return retlist

def make_playlist(alb):
    """This should get an Album object. To hell if I checked!"""
    
    # Every file has this precise way to begin:
    location = unicode(alb.file)
    filestring = u"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + \
                 u"<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n" + \
                 u"\t<title>Playlist</title>\n\t<location>file://" + \
                  location + "/" + alb.file.rpartition("/")[2] + \
                 u".xspf</location>\n\t<trackList>\n"
    
    addstring = u""
    
    for song in alb.songs:
        try: thefile = OggVorbis(os.path.join(alb.file, song))
        except IOError: continue
        
        addstring += u"\t\t<track>\n\t\t\t<location>" + location + u"/" + \
                     unicode(song, errors='replace') + u"</location>\n"
        
        try: addstring += u"\t\t\t<title>" + thefile["title"][0] + u"</title>\n"
        except KeyError: pass
        
        try: addstring += u"\t\t\t<creator>" + thefile["artist"][0] + u"</creator>\n"
        except KeyError: pass
        
        try: addstring += u"\t\t\t<album>" + thefile["album"][0] + u"</album>\n"
        except KeyError: pass
        
        addstring += u"\t\t</track>\n"
    
    filestring += addstring + u"\t</tracklist>\n</playlist>"
    return filestring.replace("&", "&")

if len(sys.argv) != 2:
    print "Not correct arguments. Please format your arguments"
    print "like so: Program location"
    exit(0)

# As per usual, my personal scripts don't have any error checking
# whatsoever. If you use it, you better know what it does!

loc = sys.argv[1]
if loc[-1:] == "/":
    loc = loc[0:-1]
    
if not os.path.isdir(loc + "/Playlists"):
    os.mkdir(loc + "/Playlists")

for x in get_and_split(loc):
    y = make_playlist(x).encode('utf-8')
    file = open(x.file + "/" + x.file.rpartition("/")[2] + ".xspf", "w")
    file.write(y)
    file = open(loc + "/Playlists/" + x.file.rpartition("/")[2] + ".xspf", "w")
    file.write(y)
    file.close()
It's probably a bit excessively long and not very pythonic. I wasn't going after making a production program, I had decided. Instead, I wanted something fast I could use. The only hairy part of this program was dealing with Unicode, since I wasn't expecting to have to at first, so you'll see some hastily-put-together Unicode support. Either way, this will produce .xspf playlists for VLC Media Player! I don't see any reason why it wouldn't work on Mac, however this was only tested and really used on a Linux machine. I can say with pretty clear certainty that this won't work on Windows, however I've heard that Python will natively switch slashes to their proper orientation when using them to open files. Still, the .xspf files will be written with hardcoded "/"s, so I don't think they'll work even if you did make the playlists. It shouldn't be terribly hard to modify this for Windows use, though!

Yes, I realize there's no error checking, it's not the most efficient Python program, and all-in-all it's actually rather bad (such as completely ignoring certain Unicode Decoding errors), but this isn't meant to be a production program, it was a personal Quick-and-Dirty solution to me needing some playlists! If you choose to use it, you can use it like so:
python GenPlaylists.py /home/USERNAME/Music
And it should automatically go through each file, find any music that is in .ogg format and begins with either "XX -" or "XX. " (where X is a digit), and make playlists from them! Again, this was just a personal script, but I thought that, on the off chance someone else happens to have a similar set-up (with all their songs beginning with numbers and all in .ogg format) and wants the same thing, they can use it! This program relies on parts of Mutagen, which I have very kindly ripped apart, taken, and placed in a convenient attachment below (along with GenPlaylists, the above program). The easiest way to use it is to simply extract the file pretty much anywhere (MUST be named "PlaylistGen"), open that folder in the command line, and run the above command. That should work.

Since this is based on Mutagen, which is under the GNU General Public License Version 2, my program must also be. Otherwise I'd give it a more permissive license. I might update this in the future, but I doubt it. I think I should emphasize the part of the GPL that states this:

Quote

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
GL, guys. XD

EDIT: Smoothed out some Unicode support. VLC seems to have a problem opening up .xspf files that aren't formatted just for VLC, it keeps putting the first song on the bottom of the playlist and cutting off the last song entirely. Strange error, but I haven't encountered that error with any other program that supports opening XSPF playlists, so I can only conclude that VLC is doing it. >_<

Attached Files


Edited by ZekeDragon, 14 October 2009 - 06:34 PM.
Added Sorting algorithm. XD 2: Generic XSPF support, eliminated VLC specific code, smoothing out Unicode support

Wow I changed my sig!




1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users