#!/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, '>f, ' "http://www.w3.org/TR/html4/strict.dtd">' 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, '>f, ' "http://www.w3.org/TR/html4/strict.dtd">' 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. #------------------------------------------------------------------------------