Taming Your Music Collection: File Structure

Moving The Files
     Now we can work toward the most important step of all: moving the file to its new home and tucking it into bed. First, we need to accept another input from the user to specify where the output directory is. We'll tackle that the same way we tackled the directory input. This also leads to a new requirement: if the output directory doesn't exist already, make sure to create it before trying to move the files to it.
Code:
#!/usr/bin/python

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

def move(output, file):
    id3info = EasyID3(file)

    _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"

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

    print 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])

    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/Ghosts\ I\ mp3/ -o /Music/
/Music/Nine Inch Nails/Ghosts I-IV/7/36. Nine Inch Nails - 7 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/4/36. Nine Inch Nails - 4 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/9/36. Nine Inch Nails - 9 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/8/36. Nine Inch Nails - 8 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/1/36. Nine Inch Nails - 1 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/3/36. Nine Inch Nails - 3 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/2/36. Nine Inch Nails - 2 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/5/36. Nine Inch Nails - 5 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/6/36. Nine Inch Nails - 6 Ghosts I.mp3

     As you can see from the results, the track number from the id3 info contains a slash that would create an extra directory we don't want or need. We need to write another function for handling track numbers that don't fit our ideal specifications. We need to remove any slashes that exist and zero-pad any numbers less than 10.
Code:
#!/usr/bin/python

import sys, os, fnmatch, 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)

    print 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])

    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/Ghosts\ I\ mp3/ -o /Music/
/Music/Nine Inch Nails/Ghosts I-IV/07. Nine Inch Nails - 7 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/04. Nine Inch Nails - 4 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/09. Nine Inch Nails - 9 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/08. Nine Inch Nails - 8 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/01. Nine Inch Nails - 1 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/03. Nine Inch Nails - 3 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/02. Nine Inch Nails - 2 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/05. Nine Inch Nails - 5 Ghosts I.mp3
/Music/Nine Inch Nails/Ghosts I-IV/06. Nine Inch Nails - 6 Ghosts I.mp3
     This will handle the extra directory we were seeing as well as address the fact we don't want to see track 10 above track 2 in our directories. The only thing left is actually moving the files. Everything looks good from the tests so we're ready to work on some live data. I'd suggest working with some test mp3s that you have backed up first to weed out any typos or errors in your code. We'll use the shutil library to handle our file operations, which you can learn about from http://docs.python.org/library/shutil.html.
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])

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

main()




;