#!/usr/bin/python
"""
thumbnail: generate thumbnails and web pages of images
"""
import sys, os, re, time, calendar, optparse
#------------------------------------------------------------------------------
# REVISION HISTORY
# 1.00 2001-08-08 Original version
# 1.01 2002-02-21 Always generate thumbnail images as ".jpg".
# Added "-[no]debug" qualifiers.
# Added program description to help routine.
# 1.02 2002-02-19 A $do_thumbs should have been a $do_table.
# 1.03 2002-07-28 Make the thumbnail images 200 pixels wide instead of 100.
# Reduced the number of thumbnails per row to 4.
# 1.04 2003-01-17 Added -title and -subtitle qualifiers and related code.
# Added -css qualifier and related code.
# 1.05 2003-02-16 Changed border around thumbnails to 3.
# Define and use a default style sheet for -css qualifier.
# Made the subtitle insert the date by default.
# Made the program exit after giving help.
# 1.06 2003-05-11 Two minor modifications to the parsing of "identify" output.
# Force numeric conversion in date processing,
# 1.07 2004-06-13 Stopped superscript of "rd" on 13th (and 12th and 11th).
# 1.08 2004-08-21 Added "-nolinks" to inhibit links at end of page.
# 2.00 2004-10-23 Added ability to insert EXIF data into thumbnails.
# Added ability to generate LaTeX contact sheet.
# Added printing of stanza for master index.html file.
# 2.01 2005-04-23 Made "index.html" have ALIGN="left" in links.
# 2.02 2005-05-17 Changed "convert" invocation to improve speed and quality.
# 2.03 2005-12-26 Added break before links at end of index.html
# 2.04 2006-04-02 Removed explicit setting of BORDER in IMG tags (now in CSS).
# 2.05 2006-12-29 Made "-nocontact" the default.
# 2.06 2007-02-25 Added the weekday to the date (by invoking external script).
# 2.07 2007-08-11 Increased the spacing in the centred link stanza.
# 3.00 2008-09-26 Complete re-write in Python, removing unwanted features and
# adding support for thumbnails of AVI files via mplayer.
# 3.01 2008-11-02 Minor improvement to layout of index.html stanza.
# 3.02 2009-04-18 Fixed problem with "-links".
# 3.03 2009-11-25 Added "-nosound" to mplayer invocation, used for AVI files.
# 3.04 2010-01-25 Added ability to specify captions in a separate file, along
# with associated -c qualifier and help.
# 3.05 2010-05-01 index.html's '
' -> '
'
# 3.06 2010-09-20 Fix to extraction of extraction of exposure time with flash.
# 3.07 2010-11-21 Inserted quotes into invocations of external programs, to
# handle images whose names include spaces etc.
# 3.08 2011-01-29 Inserted the style rather than linking to a stylesheet.
# 3.09 2011-02-09 Added support for PNG, PGM and PPM images.
# 3.10 2011-03-12 Support exposures of longer than a second.
# 3.11 2011-08-15 Corrected HTML intro in thumbs.html.
#------------------------------------------------------------------------------
# See the help message for copyright information on this program.
#------------------------------------------------------------------------------
def convert_dirdate ():
"""
convert_dirdate: extract the date from the directory name; if this is
not possible, use today's date
"""
wd = os.getcwd ()
mat = re.search ('(\d{4})-?(\d{2})-?(\d{2})', wd)
if mat is None:
now = time.gmtime ()
year = now[0]
month = now[1]
day = now[2]
wday = now[6]
else:
year, month, day = mat.groups ()
year = int (year)
month = int (re.sub ('^0', '', month))
day = int (re.sub('^0', '', day))
wday = calendar.weekday (year, month, day)
date = '%4.4d-%2.2d-%2.2d' % (year, month, day)
sup = 'th'
if day % 10 == 1 and day != 11: sup = 'st'
if day % 10 == 2 and day != 12: sup = 'nd'
if day % 10 == 3 and day != 13: sup = 'rd'
when = '%s %d%s %s %d' % (calendar.day_name[wday], day, sup, \
calendar.month_name[month], year)
result = [year, month, day, date, when]
return result
def get_exif_data (im):
"""
get_exif_data: use jhead to pull EXIF data from an image, then extract
from it size and exposure data
"""
f = os.popen ('jhead "' + im + '"', 'r')
exif = f.read ()
f.close ()
size = (os.stat (im)[6] + 1023) / 1024
valid = 0
mat = re.search ('Resolution\s*:\s*(\d+)\s*x\s*(\d+)', exif)
if mat is None:
npixels, nlines = '?', '?'
else:
npixels, nlines = mat.groups()
valid = 1
mat = re.search ('Exposure time\s*:[^(]+\(([^)]+)\)', exif)
if mat is None:
mat = re.search ('Exposure time\s*: ([0-9\.]+)', exif)
if mat is None:
exposure = '?'
valid = 1
else:
exposure = mat.groups()[0]
valid = 2
else:
exposure = mat.groups()[0]
if exposure == 'semi-auto': # exposures with flash report ("semi-auto")
mat = re.search ('Exposure time\s*:\s*([0-9\.]+)', exif)
exposure = mat.groups()[0]
valid = 2
mat = re.search ('Aperture\s*:\s*(\S+)', exif)
if mat is None:
aperture = '?'
valid = 1
else:
aperture = mat.groups()[0]
mat = re.search ('ISO equiv\.\s*:\s*(\d+)', exif)
if mat is None:
iso = '?'
valid = 1
else:
iso = mat.groups()[0]
return [valid, size, npixels, nlines, exposure, aperture, iso]
def make_thumbnail (im, thumb, size):
"""
make_thumbnail: make a thumbnail from an image
"""
cmd = 'convert -size %dx%d -resize %dx%d +profile "*" "%s" "%s"' % \
(size, size, size, size, im, thumb)
os.system (cmd)
def output_index_html (fn, images, thumbs, info, title, subtitle, size, \
links, css):
"""
output_index_html: generate "index.html"
"""
# If there's alredy a file with this name, rename it like an Emacs backup.
# This is because it's likely to be used as a `diary' for the day and we
# don't want to overwrite lots of typing that someone may have done.
if os.path.exists (fn): os.rename (fn, fn + '~')
# Start the file off.
f = open (fn, "w")
print >>f, ''
print >>f, '\n\n' + title + ''
print >>f, ''
print >>f, '\n'
print >>f, '' + title + '
'
print >>f, '' + subtitle + '
\n'
# Print out the images.
print >>f, ''
for im, th in zip (images, thumbs):
print >>f, '
' % (im, th)
# Finish the file off.
if links:
print >>f, '\n
\n'
print >>f, '
'
print >>f, '[index | thumbnails | '
print >>f, ' ' \
+ 'thumbnail'
print >>f, ' software home page]'
print >>f, '
\n\n'
f.close ()
def output_thumbs_html (fn, images, thumbs, info, title, subtitle, size, \
links, css='../../home/pix/pix.css'):
"""
output_thumbs_html: generate "thumbs.html"
"""
# Start the file off.
f = open (fn, "w")
print >>f, ''
print >>f, '\n\n' + title + ''
print >>f, ''
print >>f, '\n'
print >>f, '' + title + '
'
print >>f, '' + subtitle + '
'
# Print out the table of images.
print >>f, '\n'
for im, th, data in zip (images, thumbs, info):
print >>f, '
' % size
print >>f, ''
print >>f, ' ' % (im, th)
if caption.has_key (im):
print >>f, '%s ' % caption[im]
print >>f, '%s ' % im
(valid, s, M, N, exp, ap, iso) = data
print >>f, ('%s × %s, ' + \
'%s kb ') % (M, N, s)
if valid > 1:
print >>f, '%ss @ %s, ISO %s' % (exp, ap, iso)
else:
print >>f, ' ' # to keep the bottom of thumbnails aligned
print >>f, ' |
\n'
# Finish the file off.
if links:
print >>f, '\n
'
print >>f, '[index | thumbnails | '
print >>f, ' ' \
+ 'thumbnail '
print >>f, ' software home page]'
print >>f, '
\n\n'
f.close ()
def load_captions (fn):
'''
Load captions from a file
'''
f = open (fn, "r")
caption = {}
for line in f:
mat = re.search ('^\s*(\S+)\s*(.+)\s*$', line)
if not mat is None:
im, cap = mat.groups()
caption[im] = cap
f.close ()
return caption
#------------------------------------------------------------------------------
# Main program.
#------------------------------------------------------------------------------
# Ensure we have at least one cmmand-line argument.
if len (sys.argv) <= 1:
print 'Usage:', sys.argv[0], '[--help] [qualifiers] ...'
sys.exit (1)
# Initializations.
thumbs = []
info = []
dirdate = ''
caption_file = 'captions.txt'
# Process the command line.
parser = optparse.OptionParser(description="""This program generates
"thumbnails", small versions of images, and stores them in files whose
names are related to the original files. It also generates two HTML files:
the first is "index.html", which contains the thumbnail images with a
minimum of additional HTML mark-up; it is intended that the user then edits
this file to produce the full web page, perhaps describing the event at
which the photographs were taken and the individual photographs.
The second file is "thumbs.html" and contains the thumbnail images in an
HTML table; under each image is its name, dimensions and the size of the
disk file. If the image contains EXIF headers, the exposure time, aperture
and "film speed" are also output. The number of columns in the table is
controlled by a command-line qualifier, and a link to a CSS style sheet that
governs how the web pages appear can be specified via a command-line
qualifier.
The generation of thumbnail images, the index file, and the table file are
controlled by command-line arguments. Furthermore, if index.html exists
when this script is run, it is renamed to a "backup" file by appending "~"
to its name (the same convention Emacs uses), giving the user a chance to
resurrect any commentary overwritten accidentally.
The thumbnail file's name is obtained by prefixing the original file's name
with "tn-"; the filetype or extension of the thumbnail is always ".jpg,"
irrespective of the name of the input file. The thumbnail's maximum
dimension is controlled by a command-line qualifier, and the generation of
thumbnails can be inhibited from the command line.
The title and sub-title of the web pages generated can both be set by
command-line qualifiers. If the string "" appears in the title or
subtitle, it is replaced by a date extracted from the current directory's
name ("yyyy-mm-dd", the dashes being optional), or today's date if the
directory's name does not have this format. By default, the sub-title
comprises just the date.
If there is a file called (by default) "captions.txt" in the current
directory, it is assumed to be a file containing captions for the images.
Each line of the file should contain the name of the image, one or more
spaces, and the corresponding caption. Captions are written in bold text
underneath the corresponding image in "thumbs.html". Note that not every
image need have a caption. To read the captions from a different file,
use the "-c" (or "--captions") qualifier.
Note that this script uses the "convert" program (from the ImageMagick
package) to generate the thumbnails; "jhead" to extract EXIF (exposure) data
from JPEG images; and "mplayer" to extract the first frame from an AVI
movie. All of these are assumed to be in the user's path. The AVI
extraction stage also uses C-shell-compatible re-direction syntax.
This software is copyright 2001-2008 Adrian F. Clark .
All rights reserved. This program may be modified freely but it or derivative
works may not be charged for.
""")
parser.add_option('-t', '--title', dest='title', default='Photographs',
help='''title (H1) used in the web pages generated
(default: %default)''')
parser.add_option('-s', '--subtitle', dest='subtitle', default=dirdate,
help='''sub-title (H2) used in the web pages generated
(default: %default)''')
parser.add_option('-c', '--captions', dest='caption_file', default=caption_file,
help='''file containing photograph captions
(default: %default)''')
parser.add_option('--css', dest='css', default='../../pix.css',
help='''refer to for the CSS style sheet of the
pages generated (default: %default)''')
parser.add_option('--size', type='int', dest='size', default=200,
help='size of thumbnail images (default: %default)')
parser.add_option('--index', action='store_true', dest='do_index',
default=True, help='''generate a simple HTML index in
index.html, with each thumbnail linking to the
corresponding full image but no other structure
in the file (default)''')
parser.add_option('--links', action='store_true', dest='do_links',
default=True,
help="don't generate links at foot of HTML index")
parser.add_option('--noindex', action='store_false', dest='do_index',
help="don't generate a simple HTML index")
parser.add_option('--nolinks', action='store_false', dest='do_links',
help="don't generate links at foot of HTML index")
parser.add_option('--table', action='store_true', dest='do_table',
default=True, help='''generate an HTML table of thumbnails
in thumbs.html (default)''')
parser.add_option('--notable', action='store_false', dest='do_table',
help="don't generate a table of thumbnails")
parser.add_option('--thumbs', action='store_true', dest='do_thumbs',
default=True, help='generate the thumbnail images (default)')
parser.add_option('--nothumbs', action='store_false', dest='do_thumbs',
help="don't generate the thumbnail images")
(options, files) = parser.parse_args()
# If either the title or the subtitle contains '', replace that
# with the date extracted from the name of the directory.
year, month, day, date, when = convert_dirdate ()
options.title = re.sub (dirdate, when, options.title)
options.subtitle = re.sub (dirdate, when, options.subtitle)
# For each file supplied, which may be an AVI movie or a JPEG image, work
# out the corresponding thumbnail name, generate the thumbnail image, and
# gather the size and (if possible) exposure information.
for f in files:
if re.search ('avi$', f, re.I):
t = 'tna-' + f
t = re.sub ('avi$', 'jpg', t)
cmd = 'mplayer -vo png -nosound -frames 1 -ss 0 %s >& /dev/null' % f
im = '00000001.png' # is there a way to determine this?
jim = '00000001.jpg'
os.system (cmd)
os.system ('convert %s %s' % (im, jim))
info.append (get_exif_data (jim))
if options.do_thumbs: make_thumbnail (jim, t, options.size)
os.remove (im)
os.remove (jim)
thumbs.append (t)
elif re.search ('jpe?g$', f, re.I):
t = 'tn-' + f
t = re.sub ('jpeg$', 'jpg', t)
if options.do_thumbs: make_thumbnail (f, t, options.size)
info.append (get_exif_data (f))
thumbs.append (t)
elif re.search ('png$', f, re.I):
t = 'tn-' + f
t = re.sub ('png$', 'jpg', t)
jim = 'TEMPFILE.jpg'
os.system ('convert %s %s' % (f, jim))
info.append (get_exif_data (jim))
if options.do_thumbs: make_thumbnail (jim, t, options.size)
info.append (get_exif_data (jim))
os.remove (jim)
thumbs.append (t)
elif re.search ('pgm$', f, re.I):
t = 'tn-' + f
t = re.sub ('pgm$', 'jpg', t)
jim = 'TEMPFILE.jpg'
os.system ('convert %s %s' % (f, jim))
if options.do_thumbs: make_thumbnail (jim, t, options.size)
info.append (get_exif_data (jim))
os.remove (jim)
thumbs.append (t)
elif re.search ('ppm$', f, re.I):
t = 'tn-' + f
t = re.sub ('ppm$', 'jpg', t)
jim = 'TEMPFILE.jpg'
os.system ('convert %s %s' % (f, jim))
if options.do_thumbs: make_thumbnail (jim, t, options.size)
info.append (get_exif_data (jim))
os.remove (jim)
thumbs.append (t)
else:
print >>sys.stderr, 'File "' + f + '" is not supported.'
# Load the caption from file, if it exists.
if os.path.exists(caption_file):
caption = load_captions (caption_file)
else:
caption = {}
# Generate the various files containing the thumbnails:
# index.html gets just the thumbnails one after another
# thumbs.html gets the thumbnails as tables that align nicely, plus
# all the exposure data
if options.do_index:
output_index_html ('index.html', files, thumbs, info, options.title,
options.subtitle, options.size, options.do_links,
options.css)
if options.do_table:
output_thumbs_html ('thumbs.html', files, thumbs, info, options.title,
options.subtitle, options.size, options.do_links,
options.css)
# Finally, print out a stanza suitable for insertion into my master index.
#print '%s [diary |' % (date, year, date)
#print ' thumbnails] ' % \
# (year, date)
#print ' %s
' % options.title
print 'do ("%s", "%s")' % (date, options.title)
#------------------------------------------------------------------------------
# End of thumbnail.
#------------------------------------------------------------------------------