Monday, April 27, 2009

Album Art in Fremantle

Most of the media players need album art. So, ever application does the handling of the art by themselves (just like getting the metadata). For the metadata, we have tracker to get the metadata - one less headache. For the album art, we have tracker, hildon-thumbnailer and a standard to help alleviate another headache.

Standard? In Fremantle we have finally agreed on a standard on how to store album art and media art in general. This means that applications will be able to share the files, so album art is stored only once, retrieved from the internet if needed and a thumbnail suitable for list views is stored in common way.

We also worked together with Banshee team to make this a standard on linux desktop as well (Kudos to Philip van Hoof for it). You can read the spec in http://live.gnome.org/MediaArtStorageSpec

So, what does this mean in practice?
1. Tracker and hildon-thumbnailer do a lot of work for you in advance
- Tracker gets the embedded album art automatically
- Hildon-thumbnailer makes the thumbnails in advance in freedesktop.org standard
- Heuristic search is used in tracker and hildon-thumbnailer as specified in the media-art spec.
2. You can extend hildon-thumbnailer with content source plugins that download missing covers from the internet.
3. You can handle the album art all by yourself and just save some time if the condition 1. has hit its mark.

The option 2. is of course the preferred way of handling the album art, but heck: this ain't a perfect world, and I'm doing things a bit dirty, so I'll go for route 3. If the condition 2. would apply and an internet download plugin is available, I would change ukmp to depend on that package and thus, the following code would (mostly) not be needed at all.

However, quite a bit of it is useful. First of all: How is the album art filename (to the full version) calculated? I have nice copy paste functions here. Feel free to use them as is under any license.

You will need unicodedata and md5 as non-usual dependencies, so:

import md5
import unicodedata



Then the functions. First we create the album art filename as specified in the standard. The following function handles that conveniently for you. Now, it's up to you on how to use that. You can either just depend on whatever tracker and hildon thumbnailer have created for you (with or without the plugins), or, as ukmp does, which is, that if first checks whether the album art exists, if not, it downloads it from the internet.

coverlocation=homedir+"/.cache/media-art/"

def getCoverArtFileName( album ):
"""Returns the cover art's filename that is formed from the album name."""
albumString=dropInsideContent(album,"[","]" )
albumString=dropInsideContent(albumString,"{","}" )
albumString=dropInsideContent(albumString,"(",")" )
albumString=albumString.strip('()_{}[]!@#$^&*+=|\\/"\'?<>~`')
albumString=albumString.lstrip(' ')
albumString=albumString.rstrip(' ')
albumString=dropInsideContent(albumString,"{","}" )
albumString=albumString.lower()
albumString=string.replace(albumString,"\t"," ")
albumString=string.replace(albumString," "," ")

try:
albumString=unicodedata.normalize('NFKD',albumString).encode()
albumString=albumString.encode()
print albumString
except:
try:
albumString=albumString.encode('latin-1', 'ignore')
albumString=unicodedata.normalize('NFKD',albumString).encode("ascii")
albumString=str(albumString)
print albumString
except:
albumString="unknown"
if len(albumString)==0: albumString=" "

albumMD5=md5.new(albumString).hexdigest()
emptyMD5=md5.new(" ").hexdigest()
albumArt=coverlocation+"album-"+emptyMD5+"-"+albumMD5+".jpeg"
return albumArt


def dropInsideContent(s, startMarker, endMarker):
startPos=s.find(startMarker)
endPos=s.find(endMarker)
if startPos>0 and endPos>0 and endPos>startPos:
return s[0:startPos]+s[endPos+1:len(s)]
return s




Ok, great, now we have the full version. But, as ukmp needs mostly the thumbnail version, we need the filename to the thumbnail itself.


thumbnailLocation=homedir+"/.thumbnails/normal/"
def getCoverArtThumbFileName( album ):
artFile=getCoverArtFileName(album)
thumbFile=thumbnailLocation+md5.new(artFile).hexdigest()+".jpeg"
return thumbFile


If it happens that the thumbnail does not exist (e.g. wasn't created, has been removed or whatnot), you have a few options:
1. you can create the thumbnail yourself (I'll give an example soon for that)
2. you can request hildon-thumbnailer to create it for you

For the first option, you can just call hildon-thumbailer on the dbus:
https://stage.maemo.org/svn/maemo/projects/haf/trunk/hildon-thumbnail/daemon/thumbnailer.xml

I am not using the method myself at the moment, so here is a quick example. The method is not blocking, so proper use would need to also receive the finished signal from h-t with the thumbnailHandle property. Of course, you can also be polling to see when it has been generated. Usually in non congested situation, this is going to be some tenths of a second. If there is congestion, the content is handled lifo fashion in h-t.


import dbus, time
filename="file:///user/home/.images/01.jpg"
bus = dbus.SessionBus()
handle=time.time()
thumbnailproxy = bus.get_object('org.freedesktop.thumbnailer','/org/freedesktop/thumbnailer/Generic')
thumbnailHandle=thumbnailproxy.Queue([filename],["image/jpeg"],dbus.UInt32(handle))


I'm scaling inline in ukmp. I'm using PIL to scale down the image. It's slower than using pygame (or h-t), but looks better, as it has good anti-aliasing. Anyway, it's once in a lifetime happening, so it's ok to take a while. Here we are also using the above created functions (wehey). I'm using freedesktop org standard size: normal, which is 128x128. Be aware that the media player in Fremantle uses 124x124, so I might switch to that resolution as well. The coverlocation will then also switch from '~/.thumbnails/normal' to '~/.thumbnails/cropped'.

import PIL
thumbFile=getCoverArtThumbFileName(album)
fullCoverFileName=getCoverArtFileName(album)
if (os.path.exists(fullCoverFileName)):
thumbFile=getCoverArtThumbFileName(album)
fullCoverFileName=getCoverArtFileName(album)
image = Image.open(fullCoverFileName)
image = image.resize( THUMBNAIL_SIZE, Image.ANTIALIAS )
thumbFile=thumbFile
image.save( thumbFile, "JPEG" )

1 comment:

  1. thanks a lot, i tested the dbus code (on my n900) and it works :) but I noticed that it is creating cropped thumbnails (in the cropped folder). I have no idea how to set the thumbnail "flavor" (it looks like hildon-thumbnail is not following the specs 100%)...

    ReplyDelete