#!/usr/bin/python # Audio Tools, a module and set of tools for manipulating audio data # Copyright (C) 2008-2014 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 from __future__ import print_function import sys import os import os.path from io import BytesIO import audiotools import audiotools.text as _ # 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[0]) / float(image_surface[1]) if image_ratio > display_ratio: # image wider than display, when scaled new_width = display_surface[0] new_height = image_surface[1] / (float(image_surface[0]) / float(display_surface[0])) else: # image taller than display, when scaled new_width = image_surface[0] / (float(image_surface[1]) / float(display_surface[1])) new_height = display_surface[1] (new_width, new_height) = map(int, (new_width, new_height)) return (new_width, new_height) try: import pygtk pygtk.require("2.0") import gtk import gtk.gdk class ImageWidget_Gtk(gtk.DrawingArea): def __init__(self): gtk.DrawingArea.__init__(self) self.full_pixbuf = None self.scaled_thumbnail = None self.connect("expose_event", self.expose_event) self.connect("configure_event", self.configure_event) self.widget_width = 0 self.widget_height = 0 def set_pixbuf(self, pixbuf): self.full_pixbuf = pixbuf self.scaled_thumbnail = None self.queue_draw() def clear(self): self.full_pixbuf = None self.queue_draw() def expose_event(self, widget, area): self.draw_image() def configure_event(self, widget, area): self.widget_width = area.width self.widget_height = area.height self.draw_image() def draw_image(self): if self.full_pixbuf is None: return (image_width, image_height) = image_size((self.widget_width, self.widget_height), (self.full_pixbuf.get_width(), self.full_pixbuf.get_height())) if (((self.scaled_thumbnail is None) or (self.scaled_thumbnail.get_width() != image_width) or (self.scaled_thumbnail.get_height() != image_height))): self.scaled_thumbnail = self.full_pixbuf.scale_simple( image_width, image_height, gtk.gdk.INTERP_HYPER) self.window.draw_pixbuf( gc=self.get_style().fg_gc[gtk.STATE_NORMAL], pixbuf=self.scaled_thumbnail, src_x=0, src_y=0, dest_x=(self.widget_width - image_width) // 2, dest_y=(self.widget_height - image_height) // 2, width=image_width, height=image_height) class Coverview_Gtk: def __init__(self): self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.connect("delete_event", self.delete_event) self.window.connect("destroy", self.destroy) table = gtk.Table(rows=2, columns=2, homogeneous=False) self.list = gtk.ListStore(str, gtk.gdk.Pixbuf, str, str, str, str) self.list_widget = gtk.TreeView(self.list) self.list_widget.append_column( gtk.TreeViewColumn( None, gtk.CellRendererText(), text=0)) self.list_widget.set_headers_visible(False) list_scroll = gtk.ScrolledWindow() list_scroll.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) list_scroll.add(self.list_widget) self.list_widget.connect("cursor-changed", self.image_selected) self.image = ImageWidget_Gtk() accelgroup = gtk.AccelGroup() self.window.add_accel_group(accelgroup) menubar = gtk.MenuBar() file_menu = gtk.Menu() file_item = gtk.MenuItem("File") file_item.set_submenu(file_menu) help_menu = gtk.Menu() help_item = gtk.MenuItem("Help") help_item.set_right_justified(True) help_item.set_submenu(help_menu) open_item = gtk.ImageMenuItem(gtk.STOCK_OPEN, accelgroup) open_item.connect("activate", self.open) file_menu.append(open_item) quit_item = gtk.ImageMenuItem(gtk.STOCK_QUIT, accelgroup) quit_item.connect("activate", self.destroy) file_menu.append(quit_item) about_item = gtk.ImageMenuItem(gtk.STOCK_ABOUT, accelgroup) about_item.connect("activate", self.about) help_menu.append(about_item) menubar.append(file_item) menubar.append(help_item) image_info_bar = gtk.HBox() self.size_bits = gtk.Label() self.size_pixels = gtk.Label() self.bits = gtk.Label() self.type = gtk.Label() self.size_bits.set_alignment(1.0, 0.0) self.size_pixels.set_alignment(1.0, 0.0) self.bits.set_alignment(1.0, 0.0) self.type.set_alignment(1.0, 0.0) image_info_bar.pack_start(self.size_bits) image_info_bar.pack_start(gtk.VSeparator(), expand=False, fill=False, padding=3) image_info_bar.pack_start(self.size_pixels) image_info_bar.pack_start(gtk.VSeparator(), expand=False, fill=False, padding=3) image_info_bar.pack_start(self.bits) image_info_bar.pack_start(gtk.VSeparator(), expand=False, fill=False, padding=3) image_info_bar.pack_start(self.type) self.statusbar = gtk.Statusbar() self.statusbar.push(0, "") table.attach(menubar, 0, 2, 0, 1, yoptions=0) table.attach(list_scroll, 0, 1, 1, 2, xoptions=0, xpadding=5) table.attach(self.image, 1, 2, 1, 2) table.attach(gtk.HSeparator(), 0, 2, 2, 3, yoptions=0) table.attach(image_info_bar, 0, 2, 3, 4, yoptions=0) table.attach(self.statusbar, 0, 2, 5, 6, yoptions=0) self.file_dialog = gtk.FileChooserDialog( title=_.LAB_CHOOSE_FILE, action=gtk.FILE_CHOOSER_ACTION_OPEN, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) self.about_dialog = gtk.AboutDialog() self.about_dialog.set_program_name(u"coverview") self.about_dialog.set_version(audiotools.VERSION) self.about_dialog.set_copyright(u"(c) Brian Langenberger") self.about_dialog.set_comments(_.LAB_COVERVIEW_ABOUT) self.about_dialog.set_website(_.LAB_AUDIOTOOLS_URL) self.window.add(table) self.window.set_default_size(800, 600) self.window.show_all() def delete_event(self, widget, event, data=None): return False def open(self, widget, data=None): response = self.file_dialog.run() if response == gtk.RESPONSE_OK: filename = self.file_dialog.get_filename() try: self.set_metadata( audiotools.open(filename).get_metadata()) self.set_filename(filename) except audiotools.UnsupportedFile: error = gtk.MessageDialog( type=gtk.MESSAGE_ERROR, message_format=_.ERR_UNSUPPORTED_FILE % (filename), buttons=gtk.BUTTONS_OK) error.run() error.destroy() except audiotools.InvalidFile: error = gtk.MessageDialog( type=gtk.MESSAGE_ERROR, message_format=_.ERR_INVALID_FILE % (filename), buttons=gtk.BUTTONS_OK) error.run() error.destroy() except IOError: error = gtk.MessageDialog( type=gtk.MESSAGE_ERROR, message_format=_.ERR_OPEN_IOERROR % (filename), buttons=gtk.BUTTONS_OK) error.run() error.destroy() elif response == gtk.RESPONSE_CANCEL: pass self.file_dialog.hide() def about(self, widget, data=None): self.about_dialog.run() self.about_dialog.hide() def destroy(self, widget, data=None): gtk.main_quit() def image_selected(self, treeview=None): (liststore, selection) = self.list_widget.get_selection().get_selected_rows() row = selection[0][0] treeiter = liststore.get_iter(row) self.image.set_pixbuf(liststore.get_value(treeiter, 1)) self.size_bits.set_text(liststore.get_value(treeiter, 2)) self.size_pixels.set_text(liststore.get_value(treeiter, 3)) self.bits.set_text(liststore.get_value(treeiter, 4)) self.type.set_text(liststore.get_value(treeiter, 5)) def image_unselected(self): self.image.clear() self.size_bits.set_text(u"") self.size_pixels.set_text(u"") self.bits.set_text(u"") self.type.set_text(u"") def set_filename(self, path): self.statusbar.pop(0) self.statusbar.push(0, path) def set_metadata(self, metadata): def get_pixbuf(imagedata): l = gtk.gdk.PixbufLoader() l.write(imagedata) l.close() return l.get_pixbuf() self.list.clear() images = metadata.images() first_image = None for image in images: tree_iter = self.list.append() self.list.set(tree_iter, 0, image.type_string()) self.list.set(tree_iter, 1, get_pixbuf(image.data)) self.list.set(tree_iter, 2, _.LAB_BYTE_SIZE % (len(image.data))) self.list.set(tree_iter, 3, _.LAB_DIMENSIONS % (image.width, image.height)) self.list.set(tree_iter, 4, _.LAB_BITS_PER_PIXEL % (image.color_depth)) self.list.set(tree_iter, 5, image.mime_type.decode('ascii')) if first_image is None: first_image = tree_iter if len(images) > 0: self.list_widget.get_selection().select_iter(first_image) self.image_selected() else: self.image_unselected() def main_gtk(initial_audiofile=None): coverview = Coverview_Gtk() if initial_audiofile is not None: coverview.set_filename(initial_audiofile.filename) coverview.set_metadata(initial_audiofile.get_metadata()) gtk.main() GTK_AVAILABLE = True except ImportError: GTK_AVAILABLE = False try: from Tkinter import * import Image import ImageTk import tkFileDialog import tkMessageBox try: import _imaging as test except ImportError as err: if os.uname()[0].lower() == 'darwin': print("""*** Unable to load Python Imaging Library. You may need to run Python in 32-bit mode in order for it to load correctly. If running a Bourne-like shell, try: % export VERSIONER_PYTHON_PREFER_32_BIT=yes or, if running a C-like shell, try: % setenv VERSIONER_PYTHON_PREFER_32_BIT yes Consult "man python" for further details. """) raise err class ImagePair_Tk: def __init__(self, path, image_obj): """image_obj is an audiotools.Image object""" self.path = path self.audiotools = image_obj self.pil = Image.open(BytesIO(image_obj.data)) class Statusbar_Tkinter(Frame): def __init__(self, master): Frame.__init__(self, master) self.image_info = Frame(self) self.path_info = Frame(self) self.path = Label(self.path_info, bd=1, relief=SUNKEN, anchor=W) self.path.pack(fill=X) self.size_bits = Label(self.image_info, bd=1, relief=SUNKEN, anchor=E) self.size_pixels = Label(self.image_info, bd=1, relief=SUNKEN, anchor=E) self.bits = Label(self.image_info, bd=1, relief=SUNKEN, anchor=E) self.type = Label(self.image_info, bd=1, relief=SUNKEN, anchor=E) self.size_bits.grid(row=0, column=0, sticky=E + W) self.size_pixels.grid(row=0, column=1, sticky=E + W) self.bits.grid(row=0, column=2, sticky=E + W) self.type.grid(row=0, column=3, sticky=E + W) self.image_info.columnconfigure(0, weight=1) self.image_info.columnconfigure(1, weight=1) self.image_info.columnconfigure(2, weight=1) self.image_info.columnconfigure(3, weight=1) self.image_info.pack(fill=X) self.path_info.pack(fill=X) def set_path(self, path): self.path.config(text=path) def set_image_data(self, image): if image is None: return else: at_image = image.audiotools self.path.config(text=image.path) self.size_bits.config(text=_.LAB_BYTE_SIZE % (len(at_image.data))) self.size_pixels.config(text=_.LAB_DIMENSIONS % (at_image.width, at_image.height)) self.bits.config( text=_.LAB_BITS_PER_PIXEL % (at_image.color_depth)) self.type.config(text=at_image.mime_type) def clear_image_data(self): self.path.config(text="") self.size_bits.config(text="") self.size_pixels.config(text="") self.bits.config(text="") self.type.config(text="") class About_Tkinter(Frame): def __init__(self, master): Frame.__init__(self, master, padx=10, pady=10) Label(self, text="coverview", font=("Helvetica", 48)).pack(side=TOP) Label(self, text="Python Audio Tools %s" % (audiotools.VERSION)).pack(side=TOP) Label(self, text="(c) by Brian Langenberger", font=("Helvetica", 10)).pack(side=TOP) Label(self, text=_.LAB_COVERVIEW_ABOUT).pack(side=TOP) Label(self, text=_.LAB_AUDIOTOOLS_URL).pack(side=TOP) close = Button(self, text=_.LAB_CLOSE) close.bind(sequence="", func=self.close) close.pack(side=BOTTOM) self.master = master def close(self, event): self.master.withdraw() class Coverview_Tkinter: def __init__(self, master): self.master = master frame = Frame(master, width=800, height=600) self.statusbar = Statusbar_Tkinter(frame) self.statusbar.pack(side=BOTTOM, fill=X) self.images = Listbox(frame, selectmode=BROWSE) self.images.pack(fill=Y, expand=0, side=LEFT) self.images.bind(sequence="", func=self.event_selected) self.images.bind(sequence="", func=self.event_selected) self.canvas = Canvas(frame) self.canvas.pack(fill=BOTH, expand=1, side=LEFT) self.canvas.bind(sequence="", func=self.event_resized) self.photo_image_id = None self.current_image = None self.image_objects = [] frame.pack(fill=BOTH, expand=1, side=LEFT) frame.master.title("coverview") frame.master.geometry("%dx%d" % (800, 600)) self.image_width = int(self.canvas.cget("width")) self.image_height = int(self.canvas.cget("height")) self.menubar = Menu(master) file_menu = Menu(self.menubar, tearoff=0) file_menu.add_command(label="Open", command=self.open) file_menu.add_command(label="Quit", command=self.quit) self.menubar.add_cascade(label="File", menu=file_menu) help_menu = Menu(self.menubar, tearoff=0) help_menu.add_command(label="About", command=self.about) self.menubar.add_cascade(label="Help", menu=help_menu) # master.bind_all(sequence="", func=self.open) # master.bind_all(sequence="", func=self.quit) master.config(menu=self.menubar) self.about_window = Toplevel() self.about_window.withdraw() self.about_window_frame = About_Tkinter(self.about_window) self.about_window_frame.pack(fill=BOTH, expand=1) def open(self, *args): new_file = tkFileDialog.askopenfilename( parent=self.master, title=_.LAB_CHOOSE_FILE) if len(new_file) > 0: try: self.set_metadata(new_file, audiotools.open(new_file).get_metadata()) except audiotools.UnsupportedFile: self.show_error(_.ERR_UNSUPPORTED_FILE % (new_file)) except audiotools.InvalidFile: self.show_error(_.ERR_INVALID_FILE % (new_file)) except IOError as err: self.show_error(_.ERR_OPEN_IOERROR % (new_file)) def quit(self, *args): self.master.quit() def about(self, *args): self.about_window.state("normal") def show_error(self, error): if self.photo_image_id is not None: self.canvas.delete(self.photo_image_id) self.statusbar.clear_image_data() self.images.selection_clear(0, END) self.images.delete(0, END) self.image_objects = [] self.statusbar.set_path(error) def event_selected(self, event): try: self.current_image = self.image_objects[ int(self.images.curselection()[0])] self.set_image(self.current_image) except IndexError: self.current_image = None self.set_image(None) def event_resized(self, event): self.image_width = event.width self.image_height = event.height self.set_image(self.current_image) def set_image(self, image): if image is None: if self.photo_image_id is not None: self.canvas.delete(self.photo_image_id) self.statusbar.clear_image_data() return (image_width, image_height) = image_size( (self.image_width, self.image_height), (self.current_image.pil.size[0], self.current_image.pil.size[1])) resized_image = self.current_image.pil.resize((image_width, image_height), Image.ANTIALIAS) self.photo_image = ImageTk.PhotoImage(resized_image) if self.photo_image_id is not None: self.canvas.delete(self.photo_image_id) self.photo_image_id = self.canvas.create_image( (self.image_width - image_width) // 2, (self.image_height - image_height) // 2, image=self.photo_image, anchor=NW) self.statusbar.set_image_data(image) def set_metadata(self, path, metadata): self.images.selection_clear(0, END) self.images.delete(0, END) self.image_objects = [] if metadata is not None: image_list = metadata.images() for image in image_list: self.images.insert(END, image.type_string()) self.image_objects.append(ImagePair_Tk(path, image)) if len(image_list) > 0: self.images.index(0) self.images.selection_set(0) self.current_image = self.image_objects[0] self.set_image(self.current_image) else: self.set_image(None) self.statusbar.set_path(path) else: self.set_image(None) self.statusbar.set_path(path) def main_tkinter(initial_audiofile=None): root = Tk() app = Coverview_Tkinter(root) if initial_audiofile is not None: app.set_metadata(initial_audiofile.filename, initial_audiofile.get_metadata()) root.mainloop() TKINTER_AVAILABLE = True except ImportError: TKINTER_AVAILABLE = False if (__name__ == '__main__'): import argparse parser = argparse.ArgumentParser(description=_.DESCRIPTION_COVERVIEW) parser.add_argument("--version", action="version", version="Python Audio Tools %s" % (audiotools.VERSION)) if GTK_AVAILABLE: parser.add_argument('--no-gtk', dest="no_gtk", action='store_true', default=False, help=_.OPT_NO_GTK) if TKINTER_AVAILABLE: parser.add_argument('--no-tkinter', dest="no_tkinter", action='store_true', default=False, help=_.OPT_NO_TKINTER) parser.add_argument("filenames", metavar="FILENAME", nargs="*", help=_.OPT_INPUT_FILENAME) options = parser.parse_args() args = options.filenames messenger = audiotools.Messenger() use_gtk = (hasattr(options, "no_gtk") and not options.no_gtk) use_tkinter = (hasattr(options, "no_tkinter") and not options.no_tkinter) if use_gtk: if len(args) > 0: try: main_gtk(audiotools.open(args[0])) except audiotools.UnsupportedFile: messenger.error(_.ERR_UNSUPPORTED_FILE % (audiotools.Filename(args[0]),)) sys.exit(1) except audiotools.InvalidFile: messenger.error(_.ERR_INVALID_FILE % (audiotools.Filename(args[0]),)) sys.exit(1) except IOError: messenger.error(_.ERR_OPEN_IOERROR % (audiotools.Filename(args[0]),)) sys.exit(1) else: main_gtk(None) elif use_tkinter: if len(args) > 0: try: main_tkinter(audiotools.open(args[0])) except audiotools.UnsupportedFile: messenger.error(_.ERR_UNSUPPORTED_FILE % (audiotools.Filename(args[0]),)) sys.exit(1) except audiotools.InvalidFile: messenger.error(_.ERR_INVALID_FILE % (audiotools.Filename(args[0]),)) sys.exit(1) except IOError: messenger.error(_.ERR_OPEN_IOERROR % (audiotools.Filename(args[0]),)) sys.exit(1) else: main_tkinter(None) else: messenger.error(_.ERR_NO_GUI) sys.exit(1)