Source

sortmusic / sort.py

Full commit
# -*- coding: utf-8 -*-

import sys, os, re, shutil
from string import capwords

class InvalidNameFormat(Exception):
    pass

_album_pattern = re.compile(r'^(.+)\s\((\d{4})\)$')

def extract_album_info(dirname):
    fullname = dirname.strip()

    if not ' - ' in fullname:
        raise InvalidNameFormat('There is no separator in name "%s"' % fullname)
    
    try:
        artist, album = fullname.split(' - ')
    except ValueError:
        raise InvalidNameFormat('There is more than one separator in "%s"' % fullname)

    m = _album_pattern.match(album)
    if not m:
        raise InvalidNameFormat('Can not extract year from album name "%s"' % fullname)

    artist = capwords(artist.strip())
    album = capwords(m.group(1).strip())
    year = int(m.group(2).strip())

    return artist, album, year

def get_albums(dir_path):
    for dirpath, dirnames, filenames in os.walk(dir_path):
        dirpath = dirpath.decode(sys.getfilesystemencoding())
        for dirname in dirnames:
            dirname = dirname.decode(sys.getfilesystemencoding())
            try:
                info = extract_album_info(dirname)
            except InvalidNameFormat, e:
                print e
                continue
            yield os.path.join(dirpath, dirname), info

_from_str = u'абвгдеёжзийклмнопрстуфхцчшщэюя'
_to_str   = u'abvgdeegziiklmnoprstufhccssaii'
def get_first_letter(artist):
    artist = artist.lower()
    for i in xrange(0, 999):
        letter = artist[i]

        if letter in _from_str:
            letter = _to_str[_from_str.find(letter)]

        if letter in _to_str or letter.isdigit():
            return letter

    raise InvalidNameFormat('Can not categorize artist name "%s"' % artist)

def main(from_path, to_path):
    for dirname, (artist, album, year) in get_albums(from_path):
        dir = os.path.join(to_path, get_first_letter(artist), artist)
        try:
            os.makedirs(dir)
        except OSError: # dir may exists
            pass
    
        fullname = '%s - %s (%s)' % (artist, album, year)
        dirpath = os.path.join(dir, fullname)
        shutil.move(dirname, dirpath)
    
        print 'Processing ' + dirname

if __name__ == '__main__':
    if len(sys.argv) == 3:
        main(sys.argv[1], sys.argv[2])
    else:
        print 'Usage: %s source-dir target-dir' % os.path.basename(__file__)