I didn’t update CENSORED for quite a while, but during the last week, I finally took some time to push the project further. The result is version 0.2.0, available from SourceForge since yesterday night. The new version is all about artwork support: JPEG or PNG files that are placed next to music files will now be shown on playback. This sounds simple, but in fact, this little feature took me about half of the time I spent for the whole 0.1 series. Alas, this has only partly to do with the complex data structures Apple tends to use – a good share of the problems I had was generated by myself …
Tuesday: Basic Investigation
First, I have to say that CENSORED wouldn’t exist without the excellent work of the iPodLinux guys, because these people managed to create a quite comprehensive documentation about the iPod’s internal data formats. I used this reference for rebuild_db (and even contributed to it), I used it in CENSORED, and artwork support wouldn’t have worked without it, too. So the first thing to do when fiddling around with iPod control files is read this documentation.
The second thing to do is generate real-world example data. This means that I put the music files on my iPod out of the way (by »unfreezing« the iPod database, thus degrading the tracks to mere data files not recognized by the iPod or iTunes) to create a little »playground« for iTunes. I then copied one track with artwork to the iPod and compared the actual layout of the ArtworkDB file (the main controlling file for cover artwork) with the information from the iTunesDB documentation.
After some hours of work, I had code that produced a ArtworkDB the iPod accepted.
Wednesday: First images
One thing was strange, though. Normally, iTunes renders all the images into two files,
F1027_1.ithmb for the larger cover images and
F1031_1.ithmb for the smaller thumbnails. Since the file names were stored a zillion times all over the place in ArtworkDB, I assumed that these were arbitrary. So I used my own naming scheme to avoid clobbering iTunes-generated data. That didn’t work well – the iPod always showed empty images, even though the files existed and contained valid data.
It took me some hours to find out that the file names iTunes uses are not arbitrary, but obligatory – an iPod nano’s large artwork images must be stored in file 1027, and the small images are required to sit in 1031, even though every conceivable parameter of the images is stored explicitly in the ArtworkDB. I even managed to crash the iPod by interchanging the two files :)
Having resolved this problem, the next step was to generate actual image content. Up to that point, I just put dummy data files onto the iPod – if the device showed pixel noise, everything was OK, if it didn’t show anything or just white pixels, I assumed that something went wrong.
The images on iPods are all stored uncompressed in the display’s native pixel format, which is 16-bit little-endian RGB565 (i.e. looks like GBRG3553). I know this format from the graphics programming I do on the nano, so I wasn’t surprised. I quickly wrote the conversion code in Python (which happily turned out to execute much faster than I first thought) and tested it. Unfortunately, the image wasn’t correct. I got the typical artifacts that result from bad shifting of packed pixel formats – the shapes looked somewhat right, but there was something wrong about the colors. I debugged this for about one hour, and didn’t find a bug until I found out that there was indeed none – at least not in the place where I searched it: The shifting stuff was correct, I just got an index wrong. Argh.
Thursday: Partial Rewrite
The next step was to integrate the ArtworkDB code into the main CENSORED codebase. However, I noticed that the data model of the interface was far from useful for that purpose, so I spent that day with rearranging the code.
Friday: CENSORED Integration
With all the code in the right place, I finally integrated all the logic into CENSORED. (I’m still not completely happy with the interface, but at least it works.) The moment of truth came soon: I let CENSORED process all the tracks on my iPod. Of course, it didn’t work :) The iPod didn’t show artwork at all. After double-checking that the ArtworkDB was indeed correct (it was), I tested disabling multiple-instantiated images. These are images that are stored only once physically, but assigned to more than one track. This is the common case e.g. for album artwork. Strange enough, by reducing the instantiation count of every image to one (i.e. assigning album artwork to only one track of the album), everything worked smoothly.
Saturday: Making It Work
I concluded that the iPod didn’t like it if multiple images loaded their image data from the sample place. I absolutely didn’t understand why this was the case, but since it didn’t work otherwise, that had to be it. Since even iTunes put album artwork into the image files multiple times when I tested it, I was pretty sure that this was the problem, so I restructured my code again.
I was quite surprised that this didn’t work either – the iPod kept ignoring the ArtworkDB. Again, if I turned off multiple instantiation, everything was fine again. I was about to go mad, until I suddenly found out that there was that tiny little field in one of the list headers I stopped paying attention to after they were first implemented. This field is called »image count«, and I assigned it something like
len(imagelist). The problem is that
imagelist isn’t a plain list if images. Instead it is a dictionary that maps each image’s file name to a list of songs that should display this image. A list of a list, so to speak. And consequently, after replacing the simple length calculation by
sum(map(len, imagelist.values())), it finally worked. I could even return to real multiple instantiation (one physical image data record for all tracks that refer to one image).
The rest of the day was spent with building a image caching system similar to the existing track metadata cache, packaging the program for Windows via py2exe, updating the documentation, uploading everything and finally watching the third-place match of the World Cup …