Taming Your Music Collection: ID3 From Web

Saving The Files
     Our code is already in place for moving the files to the proper location to match our file structure. All we have to do is save the ID3 data, populate the variables for building the file path, and remove a few return and print statements we were using for testing. We'll also add a printed message to notify the user of the progress the application has made.
Code:
#!/usr/bin/python

import sys, os, fnmatch, shutil, argparse, re, urllib, urllib2, xml.dom.minidom
from mutagen.easyid3 import EasyID3
from mutagen.id3 import ID3, TIT2

def getTrackNumber(track):
    if track.find("/") > -1:
        return getTrackNumber(track[0:track.find("/")])
    elif track.isdigit():
        return track.zfill(2)

def getID3FromFilename(file, id3info):
    filename = os.path.basename(file)
    m = re.match(r"(?P<trackNumber>\d{2})\. (?P<artist>((?! -).)+) - (?P<title>[^\.]+)\.mp3", filename)

    id3info["tracknumber"] = m.group('trackNumber')
    id3info["artist"] = m.group('artist')
    id3info["title"] = m.group('title')
    id3info["album"] = file.split('/')[-2]

    id3info.save()
    return id3info

def getID3FromWeb(file, id3info):
    # Get the Artist and Title from id3info to make the query
    _artist = id3info["artist"][0]
    _title = id3info["title"][0]

    # Query the MusicBrainz web service
    try:
        query = { 'query' : 'artist:' + _artist + ' AND recording:' + _title }
        response = urllib2.urlopen('http://musicbrainz.org/ws/2/recording?' + urllib.urlencode(query))
        x = xml.dom.minidom.parse(response)

        recordingList = x.getElementsByTagNameNS('http://musicbrainz.org/ns/mmd-2.0#', 'recording-list')[0]
        recording = recordingList.getElementsByTagName('recording')[0]
        if recording.nodeType == 1 and recording.attributes.get('ext:score').value == '100':
            id3info["tracknumber"] = getTrackNumber(str(int(recording.getElementsByTagName('track-list')[0].attributes.get('offset').value)+1))
            id3info["artist"] = recording.getElementsByTagName('name')[0].firstChild.nodeValue
            id3info["title"] = recording.getElementsByTagName('title')[0].firstChild.nodeValue

            # Check for multiple releases containing the title
            releaseList = recording.getElementsByTagName('release-list')[0]
            releases = releaseList.getElementsByTagName('release')

            releaseOpts = []

            for release in releases:
                releaseTitle = release.getElementsByTagName('title')[0].firstChild.nodeValue

                if releaseTitle not in releaseOpts:
                    releaseOpts.append(releaseTitle)

            if len(releaseOpts) > 1:
                # Ask the user which option they'd prefer
                print file + " has multiple options:"
                index = 0
                print "0) Ignore file and discard edits."
                for opt in releaseOpts:
                    index += 1
                    print str(index) + ") " + opt

                choice = input("Choice: ")

                if choice == 0:
                    print "You chose to ignore the file."
                else:
                    id3info["album"] = releaseOpts[choice - 1]
                    id3info["tracknumber"] = getTrackNumber(str(int(releases[choice - 1].getElementsByTagName('track-list')[0].attributes.get('offset').value)+1))
            else:
                id3info["album"] = releaseOpts[0]

            id3info.save()
    except:
        print sys.exc_info()[0]

    return id3info

def move(output, file, dirMatch):
    # Check if there are ID3 tags to begin with, if not, it will complain
    try:
        tag = ID3(file)
    except:
        tag = ID3()
        tag.add(TIT2(encoding=3, text=["Title"]))
        tag.save(file)

    id3info = EasyID3(file)

    try:
        _trackNumber = getTrackNumber(id3info["tracknumber"][0])
        _artist = id3info["artist"][0]
        _title = id3info["title"][0]
        _album = id3info["album"][0]
    except KeyError:
        if dirMatch:
            id3info = getID3FromFilename(file, id3info)
        else:
            id3info = getID3FromWeb(file, id3info)

        _trackNumber = id3info["tracknumber"][0]
        _artist = id3info["artist"][0]
        _title = id3info["title"][0]
        _album = id3info["album"][0]

    outputDir = output + "/" + _artist + "/" + _album + "/"
    outputFile = _trackNumber + ". " + _artist + " - " + _title + ".mp3"

    print "Saving file ", outputDir + outputFile

    if not os.path.exists(outputDir):
        os.makedirs(outputDir)

    shutil.move(file, outputDir + outputFile)

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('-d', '--directory', nargs=1, required=True, help='')
    parser.add_argument('-o', '--output', nargs=1, required=True, help='')

    args = parser.parse_args()

    directory = os.path.abspath(args.directory[0])
    output = os.path.abspath(args.output[0])

    dirMatch = (directory == output)

    for root, subFolders, filenames in os.walk(directory):
        for filename in fnmatch.filter(filenames, '*.mp3'):
            move(output, os.path.join(root, filename), dirMatch)

main()
Result:
$ ./mp3-tagger.py -d /UntamedMusic/ -o /Music/
Saving file  /Music/Thrice/The Alchemy Index, Volumes III & IV/01. Thrice - Moving Mountains.mp3
Saving file  /Music/Thrice/The Alchemy Index, Volumes III & IV/06. Thrice - Child of Dust.mp3
Saving file  /Music/Thrice/The Alchemy Index, Volumes III & IV/03. Thrice - The Earth Isn't Humming.mp3
Saving file  /Music/Thrice/The Alchemy Index, Volumes III & IV/02. Thrice - Digging My Own Grave.mp3
Saving file  /Music/Thrice/The Alchemy Index, Volumes III & IV/04. Thrice - The Lion and the Wolf.mp3
/UntamedMusic/The Alchemy Index Vol. 4 - Earth/Thrice - Come All You Weary.mp3 has multiple options:
0) Ignore file and discard edits.
1) Come All You Weary
2) The Alchemy Index, Volumes III & IV
Choice: 2
Saving file  /Music/Thrice/The Alchemy Index, Volumes III & IV/05. Thrice - Come All You Weary.mp3




; ;