Quick Image Sorting
From CodeCodex
This Python script, using PyGTK, takes the names of a bunch of image files or directories containing image files, and presents the images to the user one at a time, using the right- and left-arrow keys to move quickly through the images, and F11 and F12 to rotate the image view in 90-degree increments. It is also possible to bind custom actions (in the form of Shell commands) to keystrokes. For instance, the command
SortPictures --act="b:mv %s bad/" --act="g:mv %s good/" new/
presents all the image files in turn in the new subdirectory, such that pressing the "b" key moves the currently-displayed image into the bad subdirectory, while pressing the "g" key moves the current image into the good subdirectory.
This script allows the user to quickly sort through several hundred images in just a few minutes.
#!/usr/bin/python
#+
# This script displays picture files in turn from specified directories,
# and allows the user to hit keystrokes to apply commands to them.
#
# Invoke this script as follows:
#
# SortPictures [options] item [item ...]
#
# where each "item" is either the name of an image file or of a
# directory containg image files to be shown. Valid options are:
#
# --act k:cmd
# defines a key binding, where k is a single ASCII character
# which, when typed by the user, invokes cmd. This option can be
# specified multiple times with different k values, to define multiple
# key bindings. When cmd is invoked, occurrences of %s are substituted
# with the full name of the image file.
# --random
# equivalent to --sort=random
# --sort=how
# displays the images in order according to how:
# none (default) -- no special sorting
# mod -- sort by last-mod date
# random -- display in random order
#
# Standard keystrokes are:
# right or down arrow -- go to next picture
# left or up arrow -- go to previous picture
# F11 -- rotate picture anticlockwise
# F12 -- rotate picture clockwise
#
# Created 2006 March 30 by Lawrence D'Oliveiro <ldo@geek-central.gen.nz>.
# Add --sort 2007 February 10.
# Scale down large images to fit window and add rotation functions 2007 May 6.
#-
import sys
import os
import random
import getopt
import gobject
import gtk
#+
# Useful stuff
#-
def ForEachFile(ArgList, Action, ActionArg) :
"""invokes Action(FileName, ActionArg) for each non-directory item
found in ArgList. If an item is not a directory, passes it directly
to action; otherwise, passes each file directly contained within it,
unless the name ends with "...", in which case all file descendants
of the directory are passed."""
def ForEach(Item, Recurse) :
if os.path.isdir(Item) :
for Child in os.listdir(Item) :
Child = os.path.join(Item, Child)
if os.path.isdir(Child) :
if Recurse :
ForEach(Child, True)
#end if
else :
Action(Child, ActionArg)
#end if
#end for
else :
Action(Item, ActionArg)
#end if
#end ForEach
for Arg in ArgList :
if Arg.endswith("...") :
Recurse = True
Arg = Arg[: -3]
else :
Recurse = False
#end if
ForEach(Arg, Recurse)
#end for
#end ForEachFile
#+
# GUI callbacks
#-
# globals:
# TheImage -- GDK pixbuf object containing image being displayed
# ImageDisplay -- GTK image object for showing an image
# ImageLabel -- GTK label object for showing image name
#
# Act -- mapping of actions to perform by keystroke
# Files -- list of image files to show
# FileIndex -- index into Files of image being shown
def DestroyWindow(TheWindow) :
# called when main window's close box is clicked.
gtk.main_quit()
#end DestroyWindow
def LoadImage() :
# loads the image from the currently selected file.
global TheImage
ImageName = Files[FileIndex]
try :
TheImage = gtk.gdk.pixbuf_new_from_file(ImageName)
except gobject.GError :
TheImage = None
#end try
ImageLabel.set_text("%u/%u: %s" % (FileIndex + 1, len(Files), ImageName))
#end LoadImage
def RotateImage(Clockwise) :
# rotates the displayed image by 90 degrees.
global TheImage
if Clockwise :
Direction = gtk.gdk.PIXBUF_ROTATE_CLOCKWISE
else :
Direction = gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE
#end if
if TheImage != None :
TheImage = TheImage.rotate_simple(Direction)
#end if
#end RotateImage
def ShowImage() :
# displays the currently-loaded image in the main window.
if TheImage != None :
ImageWidth = TheImage.get_property("width")
ImageHeight = TheImage.get_property("height")
if ImageWidth > MaxImageDisplay.x or ImageHeight > MaxImageDisplay.y :
ScaleFactor = min \
(
float(MaxImageDisplay.x) / ImageWidth,
float(MaxImageDisplay.y) / ImageHeight
)
UseImage = TheImage.scale_simple \
(
dest_width = int(round(ScaleFactor * ImageWidth)),
dest_height = int(round(ScaleFactor * ImageHeight)),
interp_type = gtk.gdk.INTERP_BILINEAR
)
else :
UseImage = TheImage
#end if
ImageDisplay.set_from_pixbuf(UseImage)
else :
ImageDisplay.set_from_stock \
(
gtk.STOCK_MISSING_IMAGE,
gtk.ICON_SIZE_LARGE_TOOLBAR
)
#end if
#end ShowImage
def KeyPressEvent(TheWindow, TheEvent) :
# called in response to a keystroke when the main window has the focus.
global Files, FileIndex
# print "Keypress type %d val %d" % (TheEvent.type, TheEvent.keyval) # debug
Key = TheEvent.keyval
if Key == gtk.keysyms.Down or Key == gtk.keysyms.Right :
if FileIndex + 1 < len(Files) :
FileIndex += 1
LoadImage()
ShowImage()
#end if
elif Key == gtk.keysyms.Up or Key == gtk.keysyms.Left :
if FileIndex > 0 :
FileIndex -= 1
LoadImage()
ShowImage()
#end if
elif Key == gtk.keysyms.F11 :
RotateImage(False)
ShowImage()
elif Key == gtk.keysyms.F12 :
RotateImage(True)
ShowImage()
elif Key in Act :
Cmd = Act[Key] % Files[FileIndex]
print Cmd
os.system(Cmd)
#end if
return True
#end KeyPressEvent
#+
# Mainline
#-
def AddFile(Item, Files) :
# ForEachFile action to collect names of all image files.
Files.append(Item)
#end AddFile
ModDate = {} # cache of mod dates to avoid repeated lookups
def ModDateKey(File) :
"""sort key callback which orders files by their last-mod date."""
return ModDate.setdefault(File, os.path.getmtime(File))
#end ModDateOrder
def Order(Files) :
# sorts Files according to the function defined by Key.
Files.sort(key=Key)
#end Order
(Opts, Args) = getopt.getopt \
(
sys.argv[1:],
"",
["act=", "random", "sort="]
)
Files = []
ForEachFile(Args, AddFile, Files)
Act = {}
Sort = None
for Keyword, Value in Opts :
if Keyword == "--act" :
if len(Value) > 2 and Value[1] == ":" :
Act[ord(Value[0])] = Value[2:]
# print "act %d => \"%s\"" % (ord(Value[0]), Value[2:]) # debug
else :
raise getopt.error("Invalid --act syntax: \"%s\"" % Value)
#end if
elif Keyword == "--random" :
Sort = random.shuffle
elif Keyword == "--sort" :
if Value == "random" :
Sort = random.shuffle
elif Value == "mod" :
Key = ModDateKey
Sort = Order
elif Value == "none" :
Sort = None
else :
raise getopt.error("Invalid sort option %s" % Value)
#end if
#end if
#end for
if len(Files) == 0 :
raise getopt.error("Nothing to do")
#end if
if Sort != None :
Sort(Files)
#end if
TheImage = None # to begin with
FileIndex = 0
# DefaultScreen = gtk.gdk.display_get_default().get_default_screen()
class MaxImageDisplay :
"""maximum bounds of image display"""
x = gtk.gdk.screen_width() - 32
y = gtk.gdk.screen_height() - 96
#end MaxImageDisplay
MainWindow = gtk.Window()
MainWindow.connect("destroy", DestroyWindow)
MainWindow.connect("key_press_event", KeyPressEvent)
MainWindow.set_border_width(10)
MainVBox = gtk.VBox(False, 8)
ImageDisplay = gtk.Image()
MainVBox.pack_start(ImageDisplay, False, False, 0)
ImageLabel = gtk.Label()
MainVBox.pack_start(ImageLabel, False, False, 0)
MainWindow.add(MainVBox)
MainWindow.show_all()
MainWindow.show()
LoadImage()
ShowImage()
gtk.main()

