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()