Embedding Images in MP3 Files

197 days ago

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:

grubs

,

---

Avoid the Arthur Murray Franchise Dance Studios

202 days ago

If you are interested in learning to dance, may I suggest that you avoid the Arthur Murray Franchise Dance Studios. Franchised businesses like this will teach someone how to dance, but what is taught is exorbitantly expensive (almost $150 for 40 minutes!) and limited. This could be me just not liking competitive ballroom dancing; granted, that is a legitimate criticism.

For a half to a third of the above rate, I can receive an excellent hour-long instruction in a single dance like Argentine Tango or Salsa. It has been my experience that teachers outside of the franchised dance system are much more receptive to teaching dance moves that I wanted to learn. A novel concept it would seem, but not something I found at a some franchised studios. Instead there was a rigid leveling system that was not deviated from. The general logic being that an individual cannot learn moves from level B until completing level A. Translation: until a customer has completed ten lessons of Tango he (or she) cannot learn new Tango figures.

Speaking of lessons, this is how it work at the studio I attended. When you first go, an instructor “assess” your dancing ability. This is complete rubbish, regardless of ability the consumer can rest assured that he (or she) will be told that you need to about 100 to 120 lessons to advance to the next rung in dance system – that will total to a sum greater than $10,000 USD!

However, the worst part their legal contract. Read the contract! This is important as I was verbally told that my lessons were fully refundable. In practice, there was a clause in the contract that stated the refund was only valid for a twelve month time period. Naturally, it was after this had expired that I was no longer interested in continuing instruction with this business. Not surprisingly, the studio has a history of shady behavior.

Now, I do enjoy dancing. I have been dancing the Argentine Tango weekly for over two years. I truly enjoy it. And I am not saying that you cannot learn how to dance with a Franchise dance company. You will learn, but it will take a long time and it will be extremely expensive. The dancing is also generally geared more towards competitive dancing, and if that is what you are looking for then perhaps this is the place for you (and the studio will be happy to take your money). Trust me, there are better ways to learn dancing.

My suggestions:

  1. Ask around at work or school or gym (or etc…) if someone can recommend a place to take dance instruction.
  2. Find a dance spot (Salsa Club, Tango Milonga, etc…) and watch people dance. If someone’s dancing impresses you, ask them if they have suggestions for where to take lessons.
grubs

,

---

irqbalance away those conflicts

548 days ago

As a follow-up to , another possible solution for IRQ conflicts on Linux is irqbalance. Removing unnecessary components from a kernel generally is an optimal solution for removing IRQ conflicts; however, sometimes figuring out how to do so may be too time-consuming or obtuse. All is not lost.

A nice feature of Linux is that one can re-route IRQs manually. While it is great that this can be done, think before typing. In fact, this solution course of action is suggestible only to a small portion of the average Linux users. For the rest, irqbalance may be a feasible solution to difficult IRQ problems.

grubs

,

---

IRQ Conflicts and a USB Mouse

559 days ago

I recently wrestled with some USB configuration issues when updating my kernel from 2.6.25 to 2.6.28.4. There were several kinks that needed to be ironed out, these centered around IRQ conflicts. The first of these was that the USB mouse and keyboard would not work at the login screen. To debug, I had to enable ssh at boot-time and remotely login to debug. Once this issue was resolved, then my USB mouse would mysteriously stop working after 10-15 minutes.

For the specific problem that ails you, the two best ways to resolve this issue will likely be dmesg and /proc/interupts. Granted, tracing through the output of dmesg may take some time and is far from glamous. For my particular situation, here is how I resolved the issue.

Below is the result of /proc/interupts for the 2.6.25 Kernel (which worked):

           CPU0       CPU1
  0:         24          0    XT-PIC-XT        timer
  1:          2          0    XT-PIC-XT        i8042
  2:          0          0    XT-PIC-XT        cascade
  5:       1179        620    XT-PIC-XT        ehci_hcd:usb1, eth0
  7:       6526      75918    XT-PIC-XT
  8:          1          0    XT-PIC-XT        rtc
  9:          0          0    XT-PIC-XT        acpi
 10:       5070        999    XT-PIC-XT        sata_nv, HDA Intel
 11:      63483       4594    XT-PIC-XT        sata_nv, nvidia
 12:          4          0    XT-PIC-XT        i8042
 15:       6176        310    XT-PIC-XT        ohci_hcd:usb2
NMI:          0          0   Non-maskable interrupts
LOC:     304278     304257   Local timer interrupts
RES:       7399      10427   Rescheduling interrupts
CAL:         18       1215   function call interrupts
TLB:       1036       1655   TLB shootdowns
TRM:          0          0   Thermal event interrupts
THR:          0          0   Threshold APIC interrupts
SPU:          0          0   Spurious interrupts
ERR:      82444

The first attempt at using 2.6.28 resulted in an dmesg output of:

Probing IDE interface ide0...
ide0 at 0x1f0-0x1f7,0x3f6 on irq 14
Probing IDE interface ide1...
ide1 at 0x170-0x177,0x376 on irq 15
ide-gd driver 1.18
ide-cd driver 5.00

Some new default (?) kernel option added in IDE support to the kernel for my arch which was causing an IRQ conflict. Given that there is only SATA in box, I just had to find and disable this kernel option. By turning this off and USB/HID on, the keyboard started working. The mouse only somewhat.

The mouse issue was odd. Initially, the mouse worked fine for a few minutes. Then after some period of time (usually 10-15 minutes, but one time up to an hour), the wirelss mouse stopped working. This seemed odd, given that the wireless keyboard still worked. The next step was to look at the Linux USB HowTo.

Section "InputDevice"
    Identifier  "USB Mice"
    Driver      "mouse"
    Option      "Protocol"       "IMPS/2"
    Option      "Device"         "/dev/input/mice"
    Option      "ZAxisMapping"   "4 5"
    Option      "Buttons"        "5"
EndSection

In the /etc/X11/xorg.conf, it seems that the xorg settings had somehow changed for the Protocol option. Editing this to IMPS/2 and restarting the X-server resolved this issue.

grubs

,

---