Aubin, Dischi, and anyone else interested in album covers,
Below is some correspondence between a friend (Brad Spencer) and I about support for per-mp3 covers and related things. He sent me a few patches, the latest of which is attached. You guys are more into this part of the code so please review / change / apply. I think there are also some bugfixes.
Please read below, it is our emails in chronological order. This patch should basicly fix some COVER_DIR handling and add support for per-mp3 covers.
-Rob
--------
Here's a patch to the current CVS. It changes coversearch so if the normal spot for coversearch covers is readonly (or has another error), it tries to use the FREEVO_CACHEDIR instead. Changes in autoitem allow it to look there too, and tada! CD- or DVD-based MP3s and OGGs can have covers found via coversearch. And so can my readonly samba-mounts And they stay across loads as long as the cache is not blown away.
BTW, say you have an album stored like so:
/dos/smegma/d/mp3/Music/Chet/*.mp3 # all songs on the Chet album
Then the cover ends up in
$FREEVO_CACHEDIR/dos/smegma/d/mp3/Music/Chet/cover.jpg
i.e. the same place as it would without this patch, except with the $FREEVO_CACHEDIR thrown on the front. This approach has worked well for me with photos.jacknife.org's thumbnails..
-----------
This one's way smarter and probably has less bugs. It also allows per-item album covers to be saved (they were already supported in audioitem, but I made them work from the cache and coversearch as well).
Also, the covers are now in $FREEVO_CACHEDIR/covers/...
Please make it smarter or throw it away as you see fit :)
------------
On Sun, Oct 12, 2003 at 07:46:55PM -0300, Rob Shortt wrote:
>> >> Have you checked out the COVER_DIR config item? Doesn't that eliminate >> the need for the cover dir in the cache (which could get hosed btw).
It could, if it were re-organized. It looks like not everything knows about COVER_DIR, and those that do only look in $COVER_DIR/$ALBUM_NAME.jpg, which is kinda silly if you ask me. But, I could easily change it to use COVER_DIR instead.
What I've actually done is used freevo to build the covers and then I copied them back out of the cache. Putting them directly in COVER_DIR might be a better move..
-----------
Ok, I put everything in COVERS_DIR and fixed the damn image display. It was way wack, trying to create Image objects (but the _wrong_ Image objects, not gui.Image) and pass them to MenuItems. But that can't work; MenuItem needs filenames, and it made sense to download them just _once_ per menu display anyway (!) so I put them in $FREEVO_CACHEDIR/coversearch.
But then it looks like mmpython is hosed and doesn't know when a file is updated and keeps displaying the old version of the image. So I had to make the files have unique parts in them.
On the way, I noticed that the code in gui/Image.py is totally fucked and can't possibly work, if I understand python at all (it calls the base class contructor _after_ messing with its data, so the messing with is forgotten).
Oh, and games/__init__.py constantly pops up "please update DIR_GAMES in local_conf.py" when I do a recursive random playlist. I don't have any of the game stuff setup, so I commented out the popup box for now
And when does all that data in mmpython (and worse, in thumbnails) get cleaned up?
Python is very cool, except the "runtime member binding" means it takes me about 100 times longer to make a change 'cause the compiler can't catch my typos and mistakes
I didn't test every single pathway through this code, but it seems to work...
--------------
On Mon, Oct 13, 2003 at 08:10:49AM -0300, Rob Shortt wrote:
>> LOL, it's not supposed to work yet. Nothing else uses it... hehehe.
Hahahahahahaha! Yeah, at first I played with it to see if it I could make it work the way that coversearch.py was trying to use it but this was just plain easier.
>> That's wierd, do you have an old style DIR_GAMES in your local_conf.py >> anyways?
No... None at all. That's the weird part.
>> I'm going to forward your stuff to the mailing list, unless you want to,
>> so Aubin and Dischi can take a look at it. Dischi's also a developer of
>> mmpython so maybe he can fix any problems with it or give some insight.
Forward away. My point was for you to kinda say "you're insane" or "maybe that's okay". I only intended to change like 5 lines of code when I started, but then I was like "Oh! That's broken too!"
Anyway, it works great for me The code can easily be rearranged to put COVER_DIR ahead of the media directories themselves in the "search order" if necessary.
-------------
Index: audioitem.py
===================================================================
RCS file: /cvsroot/freevo/freevo/src/audio/audioitem.py,v
retrieving revision 1.38
diff -u -p -r1.38 audioitem.py
--- audioitem.py 21 Sep 2003 13:15:56 -0000 1.38
+++ audioitem.py 13 Oct 2003 02:59:01 -0000
@@ -107,7 +107,7 @@ class AudioItem(Item):
# use the standard imghdr function to check if
# it's a real png, and not a lying one :)
- # Check for cover in COVER_DIR
+ # Check for simplistic $ALBUM.jpg cover in COVER_DIR
if config.COVER_DIR:
base = os.path.join(config.COVER_DIR, os.path.basename(file))
self.image = util.getimage(base, self.image)
@@ -118,11 +118,22 @@ class AudioItem(Item):
elif os.path.isfile(cover_logo+'jpg') and imghdr.what(cover_logo+'jpg'):
self.image = cover_logo+'jpg'
- # Allow per mp3 covers. As per Chris' request ;)
+ # Check for cover in the cache directory as if it were in this directory
+ coverdir = config.COVER_DIR + '/'
+ if os.path.isfile(coverdir + cover_logo + 'png'):
+ self.image = coverdir + cover_logo + 'png'
+ elif os.path.isfile(coverdir + cover_logo + 'jpg'):
+ self.image = coverdir + cover_logo + 'jpg'
+
+ # Allow per-item covers, considering the cache, too
if os.path.isfile(os.path.splitext(file)[0] + '.png'):
self.image = os.path.splitext(file)[0] + '.png'
elif os.path.isfile(os.path.splitext(file)[0] + '.jpg'):
self.image = os.path.splitext(file)[0] + '.jpg'
+ elif os.path.isfile(os.path.splitext(coverdir + file)[0] + '.png'):
+ self.image = os.path.splitext(coverdir + file)[0] + '.png'
+ elif os.path.isfile(os.path.splitext(coverdir + file)[0] + '.jpg'):
+ self.image = os.path.splitext(coverdir + file)[0] + '.jpg'
# Let's try to find if there is any image in the current directory
# that could be used as a cover
Index: plugins/coversearch.py
===================================================================
RCS file: /cvsroot/freevo/freevo/src/audio/plugins/coversearch.py,v
retrieving revision 1.19
diff -u -p -r1.19 coversearch.py
--- plugins/coversearch.py 10 Sep 2003 19:30:08 -0000 1.19
+++ plugins/coversearch.py 13 Oct 2003 02:59:01 -0000
@@ -92,8 +92,6 @@ import re
import urllib2
import time
import config
-import Image
-import cStringIO
from xml.dom import minidom # ParseError used by amazon module
from gui.PopupBox import PopupBox
@@ -218,41 +216,79 @@ class PluginInterface(plugin.ItemPlugin)
return
items = []
+ per_items = []
+
+ # Where will we store the temporary images? We clean up the directory
+ # each time we use it so that (a) we don't have to hook to cleanup on
+ # exit, and (b) to workaround mmpython not knowing when these files
+ # have changed. The mmpython thing looks like a bug.
+ image_dir = config.FREEVO_CACHEDIR + '/coversearch/'
+ if os.access(image_dir, os.W_OK) == 0:
+ os.makedirs(image_dir, 0755)
+ for file in os.listdir(image_dir):
+ os.unlink(os.path.join(image_dir, file))
# Check if they're valid before presenting the list to the user
# Grrr I wish Amazon wouldn't return an empty gif (807b)
for i in range(len(cover)):
+ found = False
+ label = ''
+
+ # Where will we store the temporary image? Make the name "sort of unique"
+ # so mmpython doesn't think it's another file.
+ image_file = image='%s/img%u.%s.jpg' % (image_dir, i, cover[i].Asin)
+
m = urllib2.urlopen(cover[i].ImageUrlLarge)
if not (m.info()['Content-Length'] == '807'):
- image = Image.open(cStringIO.StringIO(m.read()))
- items += [ menu.MenuItem('%s' % cover[i].ProductName,
- self.cover_create, cover[i].ImageUrlLarge,
- image=image) ]
- m.close()
- else:
+ found = True
+
+ if not found:
m.close()
# see if a small one is available
- n = urllib2.urlopen(cover[i].ImageUrlMedium)
- if not (n.info()['Content-Length'] == '807'):
- image = Image.open(cStringIO.StringIO(n.read()))
- items += [ menu.MenuItem('%s [small]' % cover[i].ProductName,
- self.cover_create, cover[i].ImageUrlMedium) ]
- n.close()
- else:
- n.close()
- # maybe the url is wrong, try to change '.01.' to '.03.'
- cover[i].ImageUrlLarge = cover[i].ImageUrlLarge.replace('.01.',
'.03.')
- n = urllib2.urlopen(cover[i].ImageUrlLarge)
- if not (n.info()['Content-Length'] == '807'):
- image = Image.open(cStringIO.StringIO(n.read()))
- items += [ menu.MenuItem('%s [small]' % cover[i].ProductName,
- self.cover_create,
cover[i].ImageUrlLarge) ]
- n.close()
+ m = urllib2.urlopen(cover[i].ImageUrlMedium)
+ if not (m.info()['Content-Length'] == '807'):
+ found = True
+ label = ' [small]'
+
+ if not found:
+ m.close()
+ # maybe the url is wrong, try to change '.01.' to '.03.'
+ cover[i].ImageUrlLarge = cover[i].ImageUrlLarge.replace('.01.',
'.03.')
+ m = urllib2.urlopen(cover[i].ImageUrlLarge)
+ if not (m.info()['Content-Length'] == '807'):
+ found = True
+
+ if found:
+ # If we found a usable image URL, download it to a temporary file
+ try:
+ os.unlink(image_file)
+ except:
+ pass
+
+ fp = open(image_file, 'wb')
+ fp.write(m.read())
+ fp.close()
+
+ # Is it real?
+ items += [ menu.MenuItem('%s%s' % (cover[i].ProductName, label),
+ self.cover_create, [image_file, 0],
+ image=image_file) ]
+ if config.COVERSEARCH_PER_ITEM:
+ per_items += [ menu.MenuItem('[per-item] %s%s' %
(cover[i].ProductName, label),
+ self.cover_create,
+ [image_file, 1], image=image_file) ]
+
+ # Close the file regardless
+ m.close()
box.destroy()
+
+ # Add any per-item entries to the menu. There may be none.
+ items += per_items
+
if len(items) == 1:
- self.cover_create(arg=items[0].arg, menuw=menuw)
+ self.cover_create(arg=[items[0].arg, 0], menuw=menuw)
return
if items:
moviemenu = menu.Menu('Cover Results', items)
@@ -272,19 +308,40 @@ class PluginInterface(plugin.ItemPlugin)
"""
import amazon
import directory
-
- box = PopupBox(text='getting data...')
+
+ is_per_item = arg[1]
+ popup_text = 'Downloading and saving cover'
+ if is_per_item:
+ popup_text += ' (per item)'
+ box = PopupBox(text=popup_text)
box.show()
- #filename = os.path.splitext(self.item.filename)[0]
if self.item.type == 'audiocd':
filename = '%s/mmpython/disc/%s.jpg' % (config.FREEVO_CACHEDIR,
self.item.info['id'])
else:
- filename = '%s/cover.jpg' % (os.path.dirname(self.item.filename))
+ # Apply this cover per-item?
+ if not is_per_item:
+ # No, just a directory cover
+ filename = '%s/cover.jpg' % (os.path.dirname(self.item.filename))
+ else:
+ filename = os.path.splitext(self.item.filename)[0] + '.jpg'
+
+ # Try to open the file for writing
+ try:
+ m = open(filename,'wb')
+ except IOError:
+ if not config.COVER_DIR:
+ raise
+ # Try using the COVER_DIR cache instead; maybe the item's directory is
read-only
+ coverdir = config.COVER_DIR + '/' + os.path.dirname(self.item.filename)
+ if os.access(coverdir, os.W_OK) == 0:
+ os.makedirs(coverdir, 0775)
+ filename = config.COVER_DIR + '/' + filename
+ m = open(filename,'wb')
- fp = urllib2.urlopen(str(arg))
- m = open(filename,'wb')
+ # Save the cover image to that filename
+ fp = open(arg[0], 'rb')
m.write(fp.read())
m.close()
fp.close()
@@ -292,7 +349,7 @@ class PluginInterface(plugin.ItemPlugin)
if self.item.type == 'audiocd':
self.item.image = filename
- if not self.item.type == 'audiocd' and self.item.parent.type == 'dir':
+ if not self.item.type == 'audiocd' and self.item.parent.type == 'dir' and not
is_per_item:
# set the new cover to all items
self.item.parent.image = filename
for i in self.item.parent.menu.choices:
