Sorting your photographs can be very time consuming. I made this script to make it a little less so.

The script will show your photos with an added histogram of the exposure. Pure whites (#FFFFFF) will be marked red and pure blacks (#000000) will be marked blue.

Each image you can reject, approve or skip sorting by pressing the left, right or down arrows respectively.

Photographs that were rejected will be moved to a folder named `n' in the current directory, photographs that were appoved will be moved to a folder named `y'.

This Python3 script depends on an external library, Pillow, and makes use of tkinter, which both have to be installed before you can use this script. Tkinter is part of the standard python installation, but is excluded from the python package in some linux distributions so you may need to install it separately.

On Ubuntu tkinter can be installed by using the following command:

sudo apt-get install python3-tk

Pillow can be installed by using:

sudo apt-get install python3-pil python3-pil.imagetk

Here's the script:

#!/usr/bin/env python3
#
# Copyright © 2015 Hans Alves <halves@localhost>
# This work is free. You can redistribute it and/or modify it under the
# terms of the Do What The Fuck You Want To Public License, Version 2,
# as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
#

import argparse
import glob
import os
import os.path
import random
import shutil
import subprocess
import tkinter

# PIL or Pillow, preferrably Pillow as PIL is no longer maintained
# see https://pypi.python.org/pypi/Pillow/3.0.0
import PIL
import PIL.ExifTags
import PIL.Image
import PIL.ImageDraw
import PIL.ImageTk

description='''photosort.py
Easily sort photos. photosort will show you your photographs with their
histogram and pure whites and blacks marked as red and blue
respectively. For each photograph press <Left> to reject the photo,
press <Right> to approve the photo or press <Down> to skip the photo.
Photos that are approved are moved to a folder named `y', photos that
are rejected are moved to a folder name `n'.
Press <Space> to show the photo without histogram or marked colours.
If no list of photos is provided photosort will sort all files with the
extensions `.JPG', `.jpg', `.JPEG' and `.jpeg'.
'''

def mkdir(name):
    if not os.path.exists(name):
        os.mkdir(name)

class Application(tkinter.Frame):
    def __init__(self, master, photos):
        tkinter.Frame.__init__(self, master)
        self.master = master
        self.photos = photos
        self.path = None
        self.tkimg = None
        self.createwidgets()
        self.pack()
        self.master.update()
        self.load()
        self.status = 1

        self.master.bind('<Escape>', self.exit)
        self.master.bind('<Left>', self.reject)
        self.master.bind('<Right>', self.approve)
        self.master.bind('<Down>', self.next)
        self.master.bind('<space>', self.swap)


    def createwidgets(self):
        self.panel = tkinter.Label(self.master, image=self.tkimg)

        #The Pack geometry manager packs widgets in rows or columns.
        self.panel.pack(side="top", fill="both", expand="yes")

    def load(self):
        self.status = -1
        if not self.photos:
            self.master.destroy()
            return
        self.path = self.photos.pop()
        print('Loading', self.path)
        self.master.title('photosort | ' + self.path)
        img = PIL.Image.open(self.path)
        exif=dict((PIL.ExifTags.TAGS[k], v)
                  for k, v in img._getexif().items()
                  if k in PIL.ExifTags.TAGS)
        orientation = exif.get('Orientation', 1)
        if orientation == 3:
            img = img.rotate(180)
        elif orientation == 6:
            img = img.rotate(-90, expand=True)
        elif orientation == 8:
            img = img.rotate(90, expand=True)
        w, h = img.size
        print('orig size', w, h)
        maxw = self.panel.winfo_width()
        maxh = self.panel.winfo_height()
        print('max size', maxw, maxh)
        if w > maxw:
            h *= maxw / w
            w *= maxw / w
        if h > maxh:
            w *= maxh / h
            h *= maxh / h

        print('scale to', w, h)
        self.img = img.resize((int(w), int(h)))
        # Creates a Tkinter-compatible photo image, which can be used
        # everywhere Tkinter expects an image object.
        self.tkimg = PIL.ImageTk.PhotoImage(self.img)

        self.panel.configure(image=self.tkimg)
        self.calc()

    def calc(self):
        datas = self.img.getdata()

        new = []
        for pix in datas:
            if pix == (255, 255, 255):
                # make pure whites red
                new += (255, 0, 0)
            elif pix == (0, 0, 0):
                # and pure black blue
                new += (0, 0, 255)
            else:
                # leave other colors
                new += pix

        self.imgb = PIL.Image.frombuffer("RGB", self.img.size,
                                         bytes(new), "raw", "RGB", 0, 1)
        g = self.img.convert(mode='L') # convert to greyscale
        hist = g.histogram() # get the histogram from the greyscale image

        # draw the histogram on the image
        w, h = self.imgb.size
        draw = PIL.ImageDraw.Draw(self.imgb)
        draw.line((10, h - 10, 265, h - 10), fill=(0, 0, 0))
        draw.line((10, h - 9, 265, h - 9), fill=(255, 255, 255))
        draw.line((9, h - 110, 9, h - 10), fill=(0, 0, 0))
        draw.line((8, h - 110, 8, h - 10), fill=(255, 255, 255))
        m = max(hist)
        print('histogram max value', m)
        for i in range(len(hist)):
            draw.line((10 + i, h - 10 - int(hist[i] / m * 100),
                       10 + i, h - 10),
                      fill=(0, 0, 0))
            draw.point((10 + i, h - 11 - int(hist[i] / m * 100)),
                       fill=(255, 255, 255))
        del draw
        self.tkimgb = PIL.ImageTk.PhotoImage(self.imgb)
        self.status = 2
        self.panel.configure(image=self.tkimgb)

    def exit(self, *args):
        self.master.destroy()

    def approve(self, *args):
        shutil.move(self.path, 'y')
        print('move', self.path, 'to y')
        self.load()

    def reject(self, *args):
        shutil.move(self.path, 'n')
        print('move', self.path, 'to n')
        self.load()

    def next(self, *args):
        self.load()

    def swap(self, *args):
        if self.status == 1:
            self.panel.configure(image=self.tkimgb)
            self.status = 2
        elif self.status == 2:
            self.panel.configure(image=self.tkimg)
            self.status = 1


def main():
    parser = argparse.ArgumentParser(description=description,
                formatter_class=argparse.RawDescriptionHelpFormatter,)
    mutex = parser.add_mutually_exclusive_group(required=False)
    mutex.add_argument('-r', '--random', dest='random',
                        action='store_true', default=False,
                        help='Show the photos in random order.')
    mutex.add_argument('-o', '--ordered', dest='ordered',
                        action='store_true', default=False,
                        help='Show the photos ordered by filename.')
    parser.add_argument('photos', nargs='*', help='The photos to sort')
    args = parser.parse_args()

    mkdir('y')
    mkdir('n')

    if args.photos:
        photos = args.photos
    else:
        photos = (glob.glob('*.JPG') + glob.glob('*.jpg') +
                  glob.glob('*.JPEG') + glob.glob('*.jpeg'))

    if args.random:
        random.shuffle(photos)
    if args.ordered:
        photos.sort(reverse=True)

    root = tkinter.Tk()
    root.title('photosort')
    root.geometry("1000x1000")
    root.configure(background='grey')
    app = Application(root, photos)
    app.master.mainloop()

if __name__ == '__main__':
    main()