Iterate The Albums
Our first programming task will be to iterate over the albums in preparation for downloading the album artwork. The reason we want to iterate only the albums and not all the files is because we want our application to be efficient. We don't want to call the web service any more often than is absolutely necessary. Since the album artwork applies to all the songs in an album, we can do the lookup once and apply it to each file, rather than retrieve the album artwork one time for each track on the album.
Code: Note: I've dimmed code from the previous articles that has little to do with this step
So we've built a list of directories that points to each album and created a loop to iterate over each one using the glob library. You can learn more about glob at http://docs.python.org/library/glob.html. For the purposes of this article, I am only using 2 albums, as seen above. Now we can move on to the next step and actually retrieve the album artwork for each album.
Our first programming task will be to iterate over the albums in preparation for downloading the album artwork. The reason we want to iterate only the albums and not all the files is because we want our application to be efficient. We don't want to call the web service any more often than is absolutely necessary. Since the album artwork applies to all the songs in an album, we can do the lookup once and apply it to each file, rather than retrieve the album artwork one time for each track on the album.
Code: Note: I've dimmed code from the previous articles that has little to do with this step
#!/usr/bin/python
import sys, os, fnmatch, shutil, argparse, re, urllib, urllib2, xml.dom.minidom, glob
from mutagen.easyid3 import EasyID3
from mutagen.mp3 import MP3
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):
audio = MP3(file, ID3=ID3)
# Add ID3 tag if none exist
try:
audio.add_tags()
except:
pass
audio.save()
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)
# Build a list of all the album directories
dirList = glob.glob(os.path.join(output, '*', '*'))
dirList = filter(lambda f: os.path.isdir(f), dirList)
for albumDir in dirList:
print "Album:", albumDir
main()
Result:
$ ./mp3-tagger.py -d /UntamedMusic/ -o /Music/ Album: /Music/Coheed And Cambria/No World For Tomorrow Album: /Music/Thrice/The Alchemy Index, Volumes III & IV
So we've built a list of directories that points to each album and created a loop to iterate over each one using the glob library. You can learn more about glob at http://docs.python.org/library/glob.html. For the purposes of this article, I am only using 2 albums, as seen above. Now we can move on to the next step and actually retrieve the album artwork for each album.