So you purchased an iPhone, whoopie! Now you’ve added your music and flip it on its side to watch an awesome stream of covers for all your awesome music. Instead of all that, you see a bunch of gray music notes. What gives?
Well, the problem is that iTunes has not associated an image with the MP3s in your music collection. There is a feature of iTunes that can grab album artwork. Which perhaps works well for popular music, but given my eclectic music collection I have had very little success with the feature. I happen to use a different music player as my default anyway and it has a very nice set of plug-ins that get the appropriate artwork (most of the time). For the few remaining albums, I got the artwork myself.
Getting the artwork is one thing, the trouble is how to associate the images with the appropriate audio files. Well, assuming you have some sort of order to your music collection, the problem is solvable. Below is a script that can help eliminate this problem. The original idea came from a bash script here, but I re-wrote it with python when I realized that it didn’t handle directories with spaces well.
import os, subprocess, sys
from shutil import copyfile
imageExt = [”.jpg”, “.png”, “.gif”]
coverJpg = “cover.jpg”
coverPng = “cover.png”
def find(arg, dirname, names):
# args: extention to find
# dirname: name of current directory
# list of files in the current directory
print “args are “ + arg
print “dirname is “ + dirname
print “names are:”
print names
foundMp3 = “false”
for item in names:
pathname = os.path.join(dirname, item)
if os.path.isfile(pathname) and pathname.lower().find(arg)>0:
foundMp3 = “true”
if foundMp3 == “false”:
print “No mp3s found”
return
# (1) Get an image to embed
fullCoverJpg = findImage(dirname, names)
#if fullCoverJpg "":
if fullCoverJpg is None or len(fullCoverJpg) 0:
print “No acceptable image found for “ + dirname
return;
# 2) Loop through contents and determine
# if it is a file or a directory. Directories
# can be skipped. Whereas for files, we’ll
# want to make a callback to embed the images.
for item in names:
pathname = os.path.join(dirname, item)
#mode = os.stat(pathname)[ST_MODE]
#if S_ISREG(mode):
# if arg in item:
# #print pathname
# embedImage(pathname, fullCoverJpg)
if os.path.isfile(pathname) and pathname.lower().find(arg)>0:
embedImage(pathname, fullCoverJpg)
# Given a directory, attempt to find a suitable cover image
def findImage(directory, filenames):
fullCoverJpg = “”
# 1) Look in directory for if there is an
# album cover, leave if not
if coverJpg in filenames:
print “Found JPEG cover image, no conversion necessary”
fullCoverJpg = os.path.join(directory, coverJpg)
elif coverPng in filenames:
# No JPEG image found, but a legally named PNG exists
fullCoverJpg = convertImage(directory, coverPng, coverJpg)
else:
desiredImage = guessImageFile(filenames);
# found an acceptable image (based on extention)
# if it is already a JPG, just make a copy. Otherwise do a
# do a conversion
if desiredImage is not None and len(desiredImage) > 0:
print desiredImage.lower().rfind(imageExt[0])
fullCoverJpg = convertImage(directory, desiredImage, coverJpg)
print fullCoverJpg
# by virtue of making it out of the loop, we have admitted that
# no acceptable image was present.
return fullCoverJpg;
def guessImageFile(filenames):
# There is no well-defined image for this directory.
# Attempt to guestimate a reasonable replacement. Look
# through what is available & sort those
potentialImages = []
for aFile in filenames:
for extention in imageExt:
if aFile.lower().rfind(extention) == len(aFile)-len(extention):
potentialImages.append(aFile)
print “potential images:”
print potentialImages
desiredImage = “”
for current in potentialImages:
if len(desiredImage) > 0:
break;
goodKeywords = [ “front”, “large”, “big”, “cover” ]
for keyword in goodKeywords:
print keyword
print current
if current.lower().find(keyword) > 0:
desiredImage = current
print “found image: “ + desiredImage
break;
# The first heuristic did not work, just pick the first image
if desiredImage is None or desiredImage == “”:
if len(potentialImages) > 0:
print “defaulting to first image found”
desiredImage = potentialImages[0];
return desiredImage
def convertImage(directory, foundImage, desiredFilename):
# With imagemagick version 6.4.8.3, the command
# to convert the image is similar to:
# convert cover.png -geometry 300×300 cover.jpg
print “converting image”
print “found image name: “ + foundImage
print “target image name: “ + desiredFilename
fullCoverJpg = os.path.join(directory, desiredFilename)
fullCoverPng = os.path.join(directory, foundImage)
retcode = subprocess.call([“convert”, fullCoverPng, “-geometry”, “300×300”, fullCoverJpg])
return fullCoverJpg
def embedImage(audiofile, image):
zeroBpm(audiofile);
# (1) Remove all existing images
removeOtherImage(audiofile);
removeOtherImage(audiofile);
removeFrontImage(audiofile);
removeFrontImage(audiofile);
# (2) remove cruft
removeCruft(audiofile);
# (3) check if valid ID3 tag
checkValidId3Tag(audiofile);
# (4) add front image
addFrontImage(audiofile, image);
def addFrontImage(theFile, imageFile):
print “the file: “ + theFile
print “image: “ + imageFile
imagePathArg = “—add-image=” + imageFile + “:FRONT_COVER”
return subprocess.call([“eyeD3”, “—no-color”, imagePathArg, theFile])
def checkValidId3Tag(theFile):
return subprocess.call([“eyeD3”, “—no-color”, “—to-v2.4”, theFile])
def zeroBpm(theFile):
return subprocess.call([“eyeD3”, “—bpm=90”, theFile])
def removeFrontImage(theFile):
return subprocess.call([“eyeD3”, “—no-color”, “—add-image=:FRONT_COVER”, theFile])
def removeOtherImage(theFile):
return subprocess.call([“eyeD3”, “—no-color”, “—add-image=:OTHER”, theFile])
def removeCruft(theFile):
return subprocess.call([“id3v2”, “-APIC”, “”, theFile])
if len(sys.argv) != 2:
print “Review usage information”
else:
os.path.walk(sys.argv[1], find, ".mp3")
Download the Script:
Other interesting links: