Taming Your Music Collection: Filename To ID3

Distinguish The Files
     The first task on our plan is distinguishing files that follow our predefined pattern but are missing ID3 info. Consider the first requirement stating the files must follow our predefined pattern. How does one prove the files follow our pattern? It could be done manually, but we are trying to do everything automatically. We could count the number of parent directories of each file and assume a structure of /Artist/Album/ if we count 2. It's never a good idea to assume things, in life or in software, though. One sure-fire way to ensure the files will have the proper structure is to check if the output directory matches the input directory. Our output directory should only ever contain files that have been examined and moved into a proper structure. Thus, it is safe to count on the file path to match a specific pattern we can use to extract the ID3 data.
Code:
#!/usr/bin/python

import sys, os, fnmatch, shutil, argparse
from mutagen.easyid3 import EasyID3

def getTrackNumber(track): if track.find("/") > -1: return getTrackNumber(track[0:track.find("/")]) elif track.isdigit(): return track.zfill(2) def move(output, file): id3info = EasyID3(file) _trackNumber = getTrackNumber(id3info["tracknumber"][0]) _artist = id3info["artist"][0] _title = id3info["title"][0] _album = id3info["album"][0] outputDir = output + "/" + _artist + "/" + _album + "/" outputFile = _trackNumber + ". " + _artist + " - " + _title + ".mp3" 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]) if directory == output: print "Directories are the same." else: print "Directories do not match." # for root, subFolders, filenames in os.walk(directory): # for filename in fnmatch.filter(filenames, '*.mp3'): # move(output, os.path.join(root, filename)) main()
Result:
$ ./mp3-tagger.py -d /Music/ -o /Music/
Directories are the same.
$ ./mp3-tagger.py -d /Music/ -o /NotMusic/
Directories do not match.
     I've commented out the code for iterating over the files and moving them, as we just want to test our check for whether the directories are the same or not. As you can see from the results, it works exactly as desired.

     Next, we need to check if the file contains ID3 info. We'll use simple exception handling for this. We'll test for the tags and, if they do not already exist and the output and input directories match, we'll extract the ID3 data from the file path before we save the file.
Code:
#!/usr/bin/python

import sys, os, fnmatch, shutil, argparse
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 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:
            print "No ID3 tags for ", file

    return

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

    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 /Music/ -o /Music/
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/10. Pierre Langer - Arriving Abroad.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/06. Pierre Langer - Jubilee.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/03. Pierre Langer - Last Song Home.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/02. Pierre Langer - October Night in B Minor.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/07. Pierre Langer - Tea For Two.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/09. Pierre Langer - Bliss & Disguise.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/04. Pierre Langer - Downfall.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/01. Pierre Langer - Untameable Fire.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/08. Pierre Langer - Poem of Haste & Rest.mp3
No ID3 tags for  /Music/Pierre Langer/Acoustic Guitar Vol 1/05. Pierre Langer - A Little Chronology.mp3
     Now we can determine whether a file has the ID3 tags or not to prepare for our next step. You'll notice I added an extra bit of code that will check the file for ID3 tags in general before trying to use the EasyID3 package. For some reason, if there are no ID3 tags saved in the file, EasyID3 will error instead of create them for you. Seems silly to have to have the extra step, but if we want to use their modules, it will have to do.



;