#!/usr/bin/python
#Audio Tools, a module and set of tools for manipulating audio data
#Copyright (C) 2007-2009 Brian Langenberger
#This program is free software; you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation; either version 2 of the License, or
#(at your option) any later version.
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
import audiotools
import sys
import os
import os.path
import cStringIO
from bisect import bisect
import gettext
gettext.install("audiotools",unicode=True)
try:
import gtk
import gtk.gdk
import gtk.glade
import gobject
except ImportError:
audiotools.Messenger("coverview",None).error(_(u"PyGTK2 is required"))
sys.exit(1)
#returns the dimensions of image_surface scaled to match the
#display surface size, but without changing its aspect ratio
def image_size(display_surface, image_surface):
display_ratio = float(display_surface[0]) / \
float(display_surface[1])
image_ratio = float(image_surface.get_width()) / \
float(image_surface.get_height())
if (image_ratio > display_ratio): #image wider than display, when scaled
new_width = display_surface[0]
new_height = image_surface.get_height() / \
(float(image_surface.get_width()) / \
float(display_surface[0]))
else: #image taller than display, when scaled
new_width = image_surface.get_width() / \
(float(image_surface.get_height()) / \
float(display_surface[1]))
new_height = display_surface[1]
(new_width,new_height) = map(int,(new_width,new_height))
return (new_width,new_height)
def size_string(size):
if (size < (2 ** 10)):
return u"%dB" % (size)
if (size < (2 ** 20)):
return u"%dKB" % (size / (2 ** 10))
return u"%dMB" % (size / (2 ** 20))
#given a string of raw data and a requested width/height
#returns a new PixBuf at that size
def get_pixbuf(imagedata,width,height):
imagedata = cStringIO.StringIO(imagedata)
l = gtk.gdk.PixbufLoader()
s = imagedata.read(0x1000)
while (len(s) > 0):
l.write(s)
s = imagedata.read(0x1000)
l.close()
pb = l.get_pixbuf()
(width,height) = image_size((width,height),pb)
pb = pb.scale_simple(
width,
height,
[gtk.gdk.INTERP_NEAREST,
gtk.gdk.INTERP_BILINEAR,
gtk.gdk.INTERP_HYPER][bisect([30,300],
min(width,height))])
return pb
def get_raw_pixbuf(imagedata):
l = gtk.gdk.PixbufLoader()
l.write(imagedata)
l.close()
return l.get_pixbuf()
class ImageList:
TARGET_TYPE_PIXMAP = 81
#picture_list is a list of (filepath,Image) tuples
def __init__(self, picture_list, xml):
self.xml = xml
self.image_width = 500
self.image_height = 500
self.thumbnail_width = 120
self.perform_thumbnail_resize = False
image_list = xml.get_widget("image_list")
image = xml.get_widget("picture_image")
self.image_liststore = gtk.ListStore(gtk.gdk.Pixbuf,
gtk.gdk.Pixbuf,
str,str,str,str,
gobject.TYPE_PYOBJECT)
image_list.set_model(self.image_liststore)
image_list.append_column(gtk.TreeViewColumn(
"Image",gtk.CellRendererPixbuf(),pixbuf=0))
self.set_images(picture_list)
#picture_list is a list of (filepath,Image) tuples
def set_images(self, picture_list):
first_image = None
self.xml.get_widget("picture_image").clear()
window = self.xml.get_widget("main_window").window
window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
window.show()
for (path,image) in picture_list:
treeiter = self.image_liststore.append()
pb = get_pixbuf(image.data,
self.thumbnail_width,
self.thumbnail_width)
large_pb = get_raw_pixbuf(image.data)
self.image_liststore.set(treeiter,0,pb)
self.image_liststore.set(treeiter,1,large_pb)
self.image_liststore.set(treeiter,2,image.type_string())
self.image_liststore.set(treeiter,3,path)
self.image_liststore.set(treeiter,4,
u"%(width)d \u00D7 %(height)d %(bits)d-bit %(size)s %(mime_type)s"%\
{"width":image.width,
"height":image.height,
"mime_type":image.suffix().upper(),
"bits":image.color_depth,
"size":size_string(len(image.data))})
self.image_liststore.set(treeiter,5,image.mime_type.encode('ascii'))
self.image_liststore.set(treeiter,6,image.data)
if (first_image is None):
first_image = treeiter
if (len(picture_list) > 0):
self.xml.get_widget("image_list").get_selection().select_iter(
first_image)
self.image_selected()
mime_types = set([image.mime_type for (path,image) in picture_list])
self.xml.get_widget("image_list").drag_source_set(
gtk.gdk.BUTTON1_MASK,
[(mime_type,
0,
self.TARGET_TYPE_PIXMAP) for mime_type in mime_types],
gtk.gdk.ACTION_COPY)
window.set_cursor(None)
#this performs a full resize on all thumbnails
#generating their thumbnail from the full-sized image
def resize_thumbnails(self):
self.image_liststore.foreach(self.resize_thumbnail,None)
self.xml.get_widget("image_list").columns_autosize()
#this performs a full resize on a single thumbnail
def resize_thumbnail(self, model, path, treeiter, user_data):
big_pb = self.image_liststore.get_value(treeiter,1)
(thumb_width,thumb_height) = image_size(
(self.thumbnail_width,self.thumbnail_width),big_pb)
thumb_width = max(thumb_width,20)
thumb_height = max(thumb_height,20)
self.image_liststore.set(treeiter,
0,
big_pb.scale_simple(
thumb_width,
thumb_height,
[gtk.gdk.INTERP_NEAREST,
gtk.gdk.INTERP_BILINEAR,
gtk.gdk.INTERP_HYPER][bisect([30,300],
min(thumb_width,thumb_height))]))
def image_selected(self, *args):
(liststore,treeiter) = \
self.xml.get_widget("image_list").get_selection().get_selected()
if (treeiter is None):
return
selected = liststore.get_value(treeiter,1)
(width,height) = image_size((self.image_width,
self.image_height),
selected)
width = max(width,20)
height = max(height,20)
image = xml.get_widget("picture_image")
image.set_from_pixbuf(selected.scale_simple(
width,
height,
[gtk.gdk.INTERP_NEAREST,
gtk.gdk.INTERP_BILINEAR,
gtk.gdk.INTERP_HYPER][bisect([30,600],
min(width,height))]))
xml.get_widget("image_eventbox").drag_source_set(
gtk.gdk.BUTTON1_MASK,
[(liststore.get_value(treeiter,5),
0,
self.TARGET_TYPE_PIXMAP)],
gtk.gdk.ACTION_COPY)
self.xml.get_widget("file_path").set_text(
liststore.get_value(treeiter,3))
self.xml.get_widget("picture_type").set_text(
liststore.get_value(treeiter,2))
self.xml.get_widget("picture_dimensions").set_text(
liststore.get_value(treeiter,4))
#this opens a file open dialog
def open_file(self, *args):
self.xml.get_widget('file_chooser').run()
#when the file open dialog selects a new track
#this closes the dialog and updates our data
def track_selected(self, dialog, signal):
chooser = self.xml.get_widget('file_chooser')
chooser.hide()
if (signal == -5):
self.image_liststore.clear()
xml.get_widget("picture_image").clear()
xml.get_widget('file_path').set_text('')
xml.get_widget('picture_type').set_text('')
self.xml.get_widget("picture_dimensions").set_text('')
try:
metadata = audiotools.open(
chooser.get_filename()).get_metadata()
if (metadata is not None):
self.set_images([(chooser.get_filename(),img)
for img in metadata.images()])
except audiotools.UnsupportedFile:
pass
#this is called by a resize event on the cover image widget
def image_resized(self, image, rectangle):
if ((self.image_width,self.image_height) !=
(rectangle.width,rectangle.height)):
self.image_width = rectangle.width
self.image_height = rectangle.height
self.image_selected()
#this is called by a resize event on the thumbnail list widget
def thumbnails_resized(self, widget, rectangle):
if (self.thumbnail_width != rectangle.width):
self.thumbnail_width = rectangle.width
#self.image_liststore.foreach(self.resize_thumbnail_quick,None)
self.perform_thumbnail_resize = True
#self.xml.get_widget("image_list").columns_autosize()
def resize_thumbnail_quick(self, model, path, treeiter, user_data):
small_pb = self.image_liststore.get_value(treeiter,0)
(thumb_width,thumb_height) = image_size(
(self.thumbnail_width,self.thumbnail_width),small_pb)
thumb_width = max(thumb_width,20)
thumb_height = max(thumb_height,20)
self.image_liststore.set(
treeiter,
0,
small_pb.scale_simple(
thumb_width,
thumb_height,
gtk.gdk.INTERP_NEAREST))
#this is executed at regular intervals
#it performs a full thumbnail resize if the flag is set
def thumbnail_resize_daemon(self):
if (self.perform_thumbnail_resize):
gobject.idle_add(self.resize_thumbnails)
self.perform_thumbnail_resize = False
return True
def display_about(self,caller):
self.xml.get_widget("about").run()
def dialog_done(self,caller,response):
caller.hide()
def image_drag_data(self, widget, context, selection, targetType,
eventTime):
if (targetType == self.TARGET_TYPE_PIXMAP):
(liststore,treeiter) = \
self.xml.get_widget("image_list").get_selection().get_selected()
if (treeiter is None):
return
selection.set(selection.target,8,liststore.get_value(treeiter,6))
def thumbnail_drag_data(self, widget, context, selection, targetType,
eventTime):
if (targetType == self.TARGET_TYPE_PIXMAP):
(liststore,treeiter) = \
self.xml.get_widget("image_list").get_selection().get_selected()
if (treeiter is None):
return
selection.set(selection.target,8,liststore.get_value(treeiter,6))
if (__name__ == '__main__'):
parser = audiotools.OptionParser(
usage=_(u"%prog [options] <track 1> [track 2] ..."),
version="Python Audio Tools %s" % (audiotools.VERSION))
(options,args) = parser.parse_args()
msg = audiotools.Messenger("coverview",options)
cover_list = []
gladepath = os.path.join(".","coverview.glade")
if (os.path.isfile(gladepath)):
xml = gtk.glade.XML(gladepath,domain="audiotools")
else:
gladepath = os.path.join(sys.prefix,"share/audiotools",
"coverview.glade")
if (os.path.isfile(gladepath)):
xml = gtk.glade.XML(gladepath,domain="audiotools")
else:
msg.error(_(u"coverview.glade not found"))
sys.exit(1)
for audiofile in audiotools.open_files(args):
try:
metadata = audiofile.get_metadata()
if ((metadata is not None) and
(len(metadata.images()) > 0)):
for image in metadata.images():
cover_list.append((audiofile.filename,image))
except audiotools.UnsupportedFile:
continue
imagelist = ImageList(cover_list, xml)
xml.signal_connect("gtk_main_quit",gtk.main_quit)
xml.signal_connect("image_selected",imagelist.image_selected)
xml.signal_connect("open_file",imagelist.open_file)
xml.signal_connect("track_selected",imagelist.track_selected)
xml.signal_connect("image_resize",imagelist.image_resized)
xml.signal_connect("thumbnails_resized",imagelist.thumbnails_resized)
xml.signal_connect("display_about",imagelist.display_about)
xml.signal_connect("dialog_done",imagelist.dialog_done)
xml.signal_connect("image_drag_data_get",imagelist.image_drag_data)
xml.signal_connect("thumbnail_drag_data_get",
imagelist.thumbnail_drag_data)
gobject.timeout_add(1500,imagelist.thumbnail_resize_daemon)
gtk.main()