Source

cdjlabeler / label.py

Full commit
"""
CDJ Burning software. Lets you browse tracks, select them,
decide which to burn, and create a funky label.

Written by Jesper Noehr <jesper@noehr.org>, licensed under BSD.
"""
import sys, os, id3reader, ConfigParser, logging, glob, re, datetime, shutil

if '-d' in sys.argv:
    logging.basicConfig(level=logging.DEBUG)

MP3_FORMAT = re.compile(r'^(?P<track>.+)\s-\s(?P<key>.+)\s-\s(?P<bpm>\d+)\.mp3', re.I)
BP_FORMAT = re.compile(r'^(\d{5,7})_')
PN_FORMAT = re.compile(r'\sPN$')

class MP3File(object):
    def __init__(self, path, config):
        self.path = path
        self.basename = os.path.basename(path)
        self.config = config
        
        self.parse()

    def parse(self):
        m = MP3_FORMAT.match(self.basename)

        if not m:
            return
            
        self.track = m.group('track')
        self.key = m.group('key')
        self.bpm = int(m.group('bpm'))

        # Get the ID3 stuff too
        id3r = id3reader.Reader(self.path)

        self.id3_album = id3r.getValue('album')
        self.id3_artist = id3r.getValue('performer')
        self.id3_title = id3r.getValue('title')
        self.id3_genre = id3r.getValue('genre')

        # Cut off beatport identifier if chosen.
        if self.config.getboolean('CDJLabeler', 'cut_beatport'):
            self.track = BP_FORMAT.sub('', self.track)

        # Remove some tidbits
        self.track = self.track.replace("_", " ")
        self.track = self.track.replace("Original Mix", "(Org Mix)")
        self.track = self.track.replace("   ", " ")
        self.track = self.track.replace("Remix", "Rmx")
        self.track = self.track.replace("Beatport Exclusive", "")
        self.track = self.track.replace(" feat ", " ft. ")

        # Strip the " PN" bit.
        self.track = PN_FORMAT.sub('', self.track)

    def __repr__(self):
        return u'<Track: %s key=%s bpm=%d>' % (self.track, self.key, self.bpm)

def read_config(fn):
    if not os.path.exists(fn):
        logging.warning("Couldn't read %s" % fn)
        return ConfigParser.ConfigParser()

    logging.info("Reading config file %s" % fn)
    
    config = ConfigParser.RawConfigParser()
    config.read(['cdjlabeler.conf', fn])
    
    return config

def load_music(path, config):
    for fn in glob.glob("%s/*.mp3" % path):
        yield MP3File(fn, config)

def folder_format(ts):
    return ts.strftime("%b %d %Y %H%M%S")

def dump_tracklist(tracks):
    out = [ ]

    for track in tracks:
        out.append("%-42s %3d %3s" % (trunc("%s %s" % (track.track, 
            track.id3_artist), 42), track.bpm, trunc(track.key, 3)))
        
    return '\n'.join(out)

def trunc(str, length):
    if len(str) > length:
        return str[0:length]
    return str

if __name__ == "__main__":
    cfn = os.path.expanduser('~/.cdjlabler.conf')
    conf = read_config(cfn)
    
    music_dir = os.path.expanduser(conf.get('CDJLabeler', 'music'))
    
    logging.info("Music is in %s" % music_dir)
    
    music = list(load_music(music_dir, conf))
    music.sort(lambda x, y: cmp(x.bpm, y.bpm))
    
    for track_id, track in enumerate(music):
        print "%3d: %3d - %s - %s" % (track_id, track.bpm, track.track, track.key)

    choice = ""
    choices = set()
    
    while choice != "q":
        choice = raw_input("\nTrack ID to burn ('q' to finish): ")
        
        if choice.isdigit():
            choices.add(int(choice))
            print "Got %s" % choice
            
        if choice.startswith("list"):
            print "So far:"
            
            for index in choices:
                print music[index]
    
    print "Chose %d tracks" % len(choices)
    
    move = raw_input("\nCopy to burn folder? [Y/n]:") != "n"
    tracklist = dump_tracklist([ music[i] for i in choices ])

    if move:
        top_folder = os.path.expanduser(conf.get('CDJLabeler', 'burn_dir'))
        named_folder = folder_format(datetime.datetime.now())
        sub_folder = os.path.join(top_folder, named_folder)
        tl_file = os.path.join(sub_folder, 'tracklist.txt')

        # Make the folder
        os.makedirs(sub_folder)
        
        fp = open(tl_file, 'w')
        fp.write(tracklist)
        fp.close()
        
        logging.debug("Wrote tracklist to %s" % tl_file)
        
        # Copy files
        for track in [ music[i] for i in choices ]:
            new_path = os.path.join(sub_folder, track.basename)
            shutil.copyfile(track.path, new_path)
            logging.info("Copied %s" % track.basename)
    
        os.system("open '%s'" % sub_folder)
        
    print "Tracklist is:"
    
    print tracklist