#!/usr/bin/perl -w
#
# Edwin Huffstutler <edwinh@computer.org>
# John Reynolds <johnjen@reynoldsnet.org>
#
# perl script for: web page index/thumbnails of photos.
# orginally started life as a menu selector for fvwm2 backgrounds...
#
# USAGE:
#
#  imageindex [options] <directory>
#
#  <directory> is assumed to be "." if not given
#
#  Options: (can be abbreviated if unique)
#
#    -title <string>  title for this page (saved for susbsequent runs)
#    -destdir <dir>   export image/html tree to dir (will be created if needed)
#    -[no]recurse     enable/disable recursion into subdirectories
#    -[no]medium      enable/disable medium size images/links
#    -[no]slide       enable/disable slideshow files
#    -[no]detail      enable/disable detail file
#    -[no]dirs        enable/disable directory entries
#    -[no]montage     enable/disable directory montages
#    -forceregen      force regeneration of thumbnails
#    -columns <num>   number of columns in html table (saved for susbsequent runs)
#    -exclude <file>  Exclude <file> from processing. Can be used multiple times
#    -includeall      Nullifies excluded file list (saved from previous run)
#    -skipmont <file> Exclude <file> from being included in a directory montage.
#    -reverse         Order timestamps with newest first
#    -x <num>         override thumbnail x size
#    -y <num>         override thumbnail y size
#    -help            show this text
#    -version         show the current version
#    -d 'var=val'     force override of global variable
#
#  See also the configuration section at the top of the program itself,
#  or in ~/.imageindexrc
#
# (non-html-generating, utility options)
#
#    -lowercase               Lowercase all image files in a directory
#    -caption <file> <string> Store comment in image
#    -rotate <file> [cw|ccw]  Rotate an image clockwise or counterclockwise
#    -showexcluded            Show which files were excluded in a prior run
#
######################################################################

#
# Configuration options
#

# sizes / dirs
$thumbnail_dir = 'thumbnail';
$default_thumbnail_x = 200;
$default_thumbnail_y = 200;

# If both dimensions of the original are within this much of the thumb
# dimensions we will skip the thumbnail and just use the original
$thumbnail_threshold = 1.0;

# tesla

$med_x = 800;
$med_y = 600;
$med_dir = 'medium';

# If both dimensions of the original are within this much of the "medium"
# dimensions we will skip creating the medium-size format and just use the
# original
$med_threshold = 1.6;

# Enable/disable features, set default for various flags
$do_recurse = 0;                      # Recurse into subdirs?
$do_medium = 1;                       # Generate medium-format?
$do_slide = 1;                        # Generate slides/frame view?
$do_detail = 1;                       # Generate details page?
$do_dirs = 1;                         # Create directory entries?
$do_captions = 1;                     # Use caption info stored in images?
$do_montage = 1;                      # Create directory montages?
$do_emoticons = 1;                    # Replace ASCII smiley's with images?
$do_reverse = 0;                      # Sort timestamps in reverse order?
$do_video_files = 1;                  # process video files with mplayer?
$do_video_thumbnail_icons = 1;        # Annotate movie icon onto thumbnails of video files

# video file options

# Brute force way of filtering out any "movie" files (mpg, mp4, avi,
# mov, etc.) which might lurk in a directory
#
$video_regexp = '(avi|mov|mpg|mpeg|mjpeg|m1v|m2v|wmv|fli|nuv|vob|ogm|vcd|svcd|mp4|qt)';

# Control which corner video icons are overlayed onto thumbnails of
# of video files. Values can be: SouthWest, NorthWest, NorthEast, SouthEast
$video_icon_gravity = 'SouthWest';

# Control which video "icon" is overlayed on top of thumbnails of video files
# 1 = yellow dot with "play" arrow
# 2 = purplish icon of video camera
$video_icon = 1;

# end video file options

$max_video_icons = 0;      # do not adjust, this is just a global variable

# What the various image links point to - can be 'index', 'fullsize',
# 'medium', 'thumbnail', 'slide', or 'details'
$index_linkto = 'slide';
$details_linkto = 'index';
$slide_linkto = 'fullsize';

# Default number of columns to use
$default_columns = 3;

# Orientation of slide frame - 'horizontal' or 'vertical'
$frame_orient = 'vertical';

# Location of items in slide pages; 'top', 'bottom', or 'none'
$slide_caption = 'top';
$slide_date = 'bottom';

# Details index uses thumbs reduced by this amount
$detailshrink = 2;

# Quality for generated images
$thumb_quality = 50;
$med_quality = 80;

# Minimum and maximum number of tiles in directory montage images
$montage_min = 4;
$montage_max = 36;

# Space between montage images
$montage_whitespace = 2;

# What to do with leftover montage tiles; can be
# 'blank' or 'repeat'
$montage_fill = 'blank';

# Stylesheet specs
# Set element font, etc. properties here
$stylesheet = '
body { color: black; background: white; }

/* Fonts in the title */
h1.title { font-family: "Comic Sans MS",Helvetica,sans-serif; font-size: 200%; font-weight: bold; text-align: center; }
h2.daterange { font-family: Arial,Helvetica,sans-serif; font-size: 125%; text-align: center; }
h3 { font-family: Arial,Helvetica,sans-serif; font-size: 90%; text-align: center; }

/* Photo captions & Directory titles */
div.caption { font-family: Arial,Helvetica,sans-serif; font-size: 100%; font-weight: bold; margin: 1em; }

/* Overall fonts on the index and details page */
div.index { font-family: Arial,Helvetica,sans-serif; font-size: 80%; }
div.detail { font-family: Arial,Helvetica,sans-serif; font-size: 80%; }
div.credits { font-family: Arial,Helvetica,sans-serif; font-size: 80%; text-align: right; margin: 10px }

/* Table attributes */
table.index { background: #ffffff; border: none; border-spacing: 8px; }
td.index { border: none; padding: 3px }
table.frame { background: #ffffff; border: none }
td.frame { border: none; padding: 0px }

/* Image attributes */
img.index { border: none; }
img.slide { border: none; }
img.frame { border: none; }

/* Link attributes */
a:link { color: blue; }
a:visited { color: green; }
a:hover { color: red; }
a:active { color: red; }

';


# Text
$emptycell          = "<I>empty</I>";
$updirtext          = "up one directory";
$framelinktext      = "slideshow view (frames)";
$detaillinktext     = "details index";
$indexlinktext      = "main index";
$default_titletext  = "Image directory";

# These five variables control the TITLE attribute on anchor constructs in the
# index and frame views. When TITLE attributes are given they are usually
# rendered as "tooltip" bubbles that show text when a cursor hovers and stops
# over the active link. We use them here to give a visual cue about the image.
# These variables work much like printf(1) strings.
#
#   %f => replaced with the filename of the image
#   %d => replaced with the date/time of the image (or mtime of the file)
#   %s => replaced with the size of the file (in Kb)
#   %r => replaced with the resolution (XxY) of the original image
#   %c => replaced with the image's caption (if stored with one)
#   %% => replaced with a literal '%' character
#
# The following are used when directories are processed and a montage of
# that directory is used as the thumbnail of the dir.
#
#   %n => replaced with number of images in a directory
#   %b => replaced with the "begin" date from a directory of images
#   %e => replaced with the "end" date from a directory of images
#   %t => replaced with the "title" from a directory of images
#
# Other characters (including spaces) are literal. "undef" these in
# your ~/.imageindexrc file if you don't want them to show up. The "date/time"
# related constructs are interpolated using the date/time format variables
# defined below.
#
$framethumbtitle  = "%f - %d";
$indexthumbtitle  = "%f (%s)";
$slidethumbtitle  = "%f (%s)";
$detailthumbtitle = "%c";
$montagetitle     = "%n images %b through %e";

# Date/Time format strings. These strings are formatted much like the above
# variables and the definitions of the escape sequences come from the POSIX
# strftime(3) definitions. NOT ALL of strftime(3) are supported for obvious
# reasons.
#
#    %S    is replaced by the second as a decimal number (00-60).
#    %M    is replaced by the minute as a decimal number (00-59).
#    %I    is replaced by the hour (12-hour clock) as a decimal number (01-12).
#    %H    is replaced by the hour (24-hour clock) as a decimal number (00-23).
#    %p    is replaced by national representation of either "ante meridiem" or
#          "post meridiem" as appropriate (currently only U.S. "am" or "pm")
#    %R    is equivalent to "%H:%M" (in *timeformat variables only).
#    %r    is equivalent to "%I:%M:%S %p" (in *timeformat variables only).
#
#    %Y    is replaced by the year with century as a decimal number.
#    %y    is replaced by the year without century as a decimal number (00-99).
#    %m    is replaced by the month as a decimal number (01-12).
#    %d    is replaced by the day of the month as a decimal number (01-31).
#    %F    is equivalent to "%Y-%m-%d" (in *dateformat variables only).
#    %D    is equivalent to "%m/%d/%y" (in *dateformat variables only).
#    %%    is replaced by a literal "%".

$framedateformat = "%m/%d/%Y";
$frametimeformat = "%r";

$indexdateformat = "%m/%d/%Y";
$indextimeformat = "%r";

$slidedateformat = "%m/%d/%Y";
$slidetimeformat = "%r";

$detaildateformat = "%m/%d/%Y";
$detailtimeformat = "%I:%M %p";

# Pathnames
$indexfile = 'index.html';
$detailfile = 'details.html';
$framefile = 'frame.html';
$slidefile =  'slides.html';
$slide_dir = 'slides';
$stylefile = 'style.css';
$montagefile = 'montage.jpg';
$emoticonprefix = 'ii_';
$emoticonsmile = $emoticonprefix . 'smile.png';
$emoticonwink  = $emoticonprefix . 'wink.png';
$emoticonfrown = $emoticonprefix . 'frown.png';

# File exclusion customization (regex)
# (Anything non-image and non-dir will be skipped automatically, this just
#  makes it silent)
@exclude = qw(
	      ^CVS$
	      ^.nautilus-metafile.xml$
	      ^.thumbnails$
	      ^.nfs.*$
	      ^.xvpics$
	      ^.thumbcache$
	      ^ALBUM.OFA$
	      ^desktop.ini$
	      ^.*.txt$
	      );


# Metatags
$columnsmetatag   = 'Columns';
$titlemetatag     = 'Title';
$begindatemetatag = 'DateBegin';
$enddatemetatag   = 'DateEnd';
$excludemetatag   = 'ExcludedFiles';
$skipmetatag      = 'SkipMontageFiles';
$numimagesmetatag = 'NumImages';
$reversemetatag   = 'Reverse';
$thumbxmetatag    = 'ThumbnailX';
$thumbymetatag    = 'ThumbnailY';

# Any of the above can be overridden in an rc file in the user's home dir
$rcfile = "$ENV{'HOME'}/.imageindexrc";

######################################################################
#
# $Id: imageindex,v 1.175 2007/04/04 19:55:51 edwinh Exp $
#
#  imageindex is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2, or (at your option)
#  any later version.
#
#  imageindex is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with imageindex; see the file COPYING.
#
######################################################################

use Image::Magick;                                # comes with ImageMagick

# from CPAN - optional
eval('use Image::Info qw(image_info)');

# Shipped with perl
use POSIX;
use Getopt::Long;
use FileHandle;
use File::Basename;
use File::Copy;
use English;
use Carp;
require 'flush.pl';

# to shut up -w
use vars qw($opt_recurse);
use vars qw($opt_slide);
use vars qw($opt_dirs);
use vars qw($opt_detail);
use vars qw($opt_lowercase);
use vars qw($opt_help);
use vars qw($opt_debug);
use vars qw($opt_showexcluded);
use vars qw($opt_version);
use vars qw($opt_updirindexoverride);

&GetOptions(
	    'title=s',
	    'columns=i',
	    'x=i',
	    'y=i',
	    'forceregen',
	    'medium!',
	    'slide!',
	    'detail!',
	    'dirs!',
	    'montage!',
	    'recurse!',
	    'destdir=s',
	    'lowercase',
	    'caption=s',
	    'rotate=s',
	    'exclude=s@',
	    'skipmont=s@',
	    'showexcluded',
	    'includeall',
	    'version',
	    'help',
	    'debug',
	    'reverse!',
	    'd=s%',
	    'updirindexoverride'
	    ) or die ("Invalid flag\n");

# Find out which platform we're on so we don't give incorrect options to needed
# commands
#
$uname = `uname -s`;
chomp ($uname);

# Override config variables
foreach my $var (keys %opt_d) {
    $value = $opt_d{$var};
    print "(override) $var = $value\n";
    eval("\$$var=\"$value\"");
}

&init_png_array();

# Read RC file
if (-e $rcfile) {
    print "Using settings in $rcfile...\n" if ! defined ($opt_version);
    require $rcfile;
}

# Rotate or caption image (then exit)
if (defined ($opt_rotate)) {
    &rotate_image($opt_rotate,\@ARGV);
    exit (0);
} elsif (defined ($opt_caption)) {
    &caption_image($opt_caption,\@ARGV);
    exit (0);
} elsif (defined ($opt_showexcluded)) {
    &showexcluded($ARGV[0]);
    exit (0);
} elsif (defined ($opt_version)) {
    printf ("imageindex version: %s\n", &versionstring);
    exit (0);
}

# The directory to search is the first argument
if (defined($ARGV[0])) {
    $srcdir = $ARGV[0];
    $srcdir =~ s:/$::;
} else {
    $srcdir = ".";
}

# Give usage message
if (defined($opt_help)) {
    &usage();
    exit(0);
}

# Show backtrace if debug given
if (defined($opt_debug)) {
    $SIG{__WARN__} = \&Carp::cluck;
}

# Where to generate files
$destdir = $srcdir;
if (defined($opt_destdir)) {
    $destdir = $opt_destdir;
    $destdir =~ s:/$::;
    print "Exporting to $destdir\n";
    unless (-d $destdir) {
	printf ("Creating destination directory '$destdir'.\n");
	mkdir ($destdir, 0755);
    }
}

unless (-w $destdir) {
    printf ("No write permission for $destdir\n");
    exit (1);
}

if (defined($opt_medium)) {
    $do_medium = $opt_medium
}

if (defined($opt_slide)) {
    $do_slide = $opt_slide;
}

if (defined($opt_detail)) {
    $do_detail = $opt_detail;
}

if (defined($opt_dirs)) {
    $do_dirs = $opt_dirs;
}

if (defined($opt_montage)) {
    $do_montage = $opt_montage;
}

if (defined($opt_recurse)) {
    $do_recurse = $opt_recurse;
}

# no montages if we aren't doing dirs anyway
if ($do_dirs == 0) {
    $do_montage = 0;
}

&initialize_current_vars();
&read_stored_meta_data();
&override_by_commandline();

if (!defined(&image_info)) {
    print "Image::Info not found, not extracting EXIF data\n";
}

opendir(DIR, "$srcdir") || die "Can't open dir $srcdir: ($!)\n";
@files = readdir DIR;
closedir(DIR);
@files = grep (!/^\.?\.$/, @files);

# Skip the files/dirs we use or generate. Any other patterns go in the
# config section (@exclude) or in exclude file
my @generated_files = ($thumbnail_dir, $med_dir, $slide_dir,
		       $indexfile, $detailfile, $stylefile,
		       );

foreach my $pattern (@generated_files, @exclude) {
    @files = grep (!/^$pattern$/, @files);
}

@files = &exclude_files(@files);

# Change all the names of image files to lowercase.
if (defined ($opt_lowercase)) {
    &lower_case_files(@files);
    exit (0);
}

# Keep track of which column to be in
my $col_counter = 1;

# Count how many files we create
my $object_counter = 0;
my $dir_counter = 0;
my $image_counter = 0;
my $thumbnail_counter = 0;
my $med_counter = 0;
my $slide_counter = 0;
my $modified_thumb = 0;

# Keep track of max thumb sizes to use for slide frame width
my $max_thumb_x = 0;
my $max_thumb_y = 0;

# Keep track of max thumb sizes to use for montage creation
my $max_mont_thumb_x = 0;
my $max_mont_thumb_y = 0;

my $mplayer_prog = &find_in_path ('mplayer');

# Extract info
print "Extracting image info";
flush (STDOUT);

foreach my $file (@files) {

    # If directory, grab the timestamp
    if (-d "$srcdir/$file") {

	my $ts;

	# Grab timestamp from meta tag
	if (-e "$srcdir/$file/$indexfile") {

	    my $begin = &extract_meta_tag($begindatemetatag,"$srcdir/$file/$indexfile");
	    if (defined($begin)) {
		if (!defined($firstdate) or ($begin < $firstdate)) {
		    $firstdate = $begin;
		}
		$ts = $begin;
	    }

	    my $end = &extract_meta_tag($enddatemetatag,"$srcdir/$file/$indexfile");
	    if (defined($end)) {
		if (!defined($lastdate) or ($end > $lastdate)) {
		    $lastdate = $end;
		}
		$ts = $end if (!defined($ts));
	    }

	}

	# Fallback on dir mtime
	if (!defined($ts)) {
	    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
		$atime,$mtime,$ctime,$blksize,$blocks) = stat("$srcdir/$file");
	    $ts = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
	}

	push(@{$dir_timestamp{$ts}}, $file);

    } else {

	# Collect info from the image or video
	&extract_file_info($file);

    }
}
print "\n";


# Do dirs first
if ($do_dirs) {
    foreach my $ts (sort bynumber keys %dir_timestamp) {
	foreach my $dir (sort @{$dir_timestamp{$ts}}) {
	    &dir_entry($dir);
	} # foreach dir that has this timestamp
    }  # foreach timestamp
}


# Bail if nothing here
if ($object_counter == 0) {
    print "Nothing to do!\n";
    unlink("$destdir/$indexfile") if (-e "$destdir/$indexfile");
    unlink("$destdir/$detailfile") if (-e "$destdir/$detailfile");
    unlink("$destdir/$stylefile") if (-e "$destdir/$stylefile");
    exit(0);
}

# Make thumb dirs if needed
foreach my $checkdir ($thumbnail_dir, $med_dir, $slide_dir) {
    unless (-d "$destdir/$checkdir") {
	mkdir("$destdir/$checkdir",0777);
    }
}

# Nuke old thumbnails if original image gone
&nuke_out_of_date();

# Iterate over the files based on timestamp
# This is just to get back/forward links
undef $prev;
foreach (sort bynumber keys %timestamp) {

    foreach my $pathname (sort @{$timestamp{$_}}) {

	if (defined($prev)) {
	    my ($name,$path,$suffix);

	    ($name,$path,$suffix) = fileparse($prev,'\.\S+');
	    $back{$pathname} = "$name.html";

	    ($name,$path,$suffix) = fileparse($pathname,'\.\S+');
	    $forward{$prev} = "$name.html";
	}
	$prev = $pathname;

    } # foreach image that has this timestamp

} # foreach timestamp

# Iterate over the files based on timestamp
# This will do the real work
foreach (sort bynumber keys %timestamp) {

    foreach my $pathname (sort @{$timestamp{$_}}) {

	my $filename = $info{$pathname}{'file'};
	my $thumbnail = $info{$pathname}{'thumb'};
	my $medium = $info{$pathname}{'medium'};
	my $slide = $info{$pathname}{'slide'};

	my $tmp_jpg_dir = "tmp_jpg_$$";

	if (!defined($firstdate) or ($info{$pathname}{'date'} < $firstdate)) {
	    $firstdate = $info{$pathname}{'date'};
	}

	if (!defined($lastdate) or ($info{$pathname}{'date'} > $lastdate)) {
	    $lastdate = $info{$pathname}{'date'};
	}

	#
	# First, deal with medium format of the image since we can save time shrinking
	# the medium down to the thumbnail rather than fullsize->thumbnail
	#
	# If the file is a video, we must first extract the first frame of the video
	# stream into JPEG form and we may or may not maniplate it from there.
	#

	# Skip if we want no medium images at all
	#
	if ($do_medium == 0) {
	    $skipmedium{$pathname} = 1;
	    unlink("$destdir/$medium") if (-e "$destdir/$medium");

	} elsif (($info{$pathname}{'x'} <= ($med_x * $med_threshold)) and
		 ($info{$pathname}{'y'} <= ($med_y * $med_threshold))) {

	    my $image = new Image::Magick;
	    my $retval;

	    # Regardless of whether the "size" of a frame of a video is
	    # close to our medium threshold size, we need to create an
	    # image and have it there. If it is an image then the HTML will
	    # just link to the actual image rather than creating a 'medium'
	    # version. But for video files we need to make one regardless.
	    #
	    if ($info{$pathname}{'is_video'}) {
		if ((! -e "$destdir/$medium") or
		    ( -M $pathname < -M "$destdir/$medium") or
		    defined($opt_forceregen)) {

		    my $icon = new Image::Magick;

		    print "Creating $destdir/$medium from first frame of $pathname\n";

		    my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
		    $retval = $image->Read(filename=>$tmpfile);
		    warn "$retval" if "$retval";
		    $retval = $image->Set(interlace=>Line);
		    warn "$retval" if "$retval";
		    # always want 100% here instead of $med_quality
		    $retval = $image->Set(quality=>100);
		    warn "$retval" if "$retval";

		    # If the user wants to, overlay a small icon on top of the
		    # thumbnail of the video file to give a visual cue that this
		    # file is a video and not a still photo
		    #
		    if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
			my $iconfile;
			if ($video_icon_gravity ne 'SouthWest' &&
			    $video_icon_gravity ne 'NorthWest' &&
			    $video_icon_gravity ne 'SouthEast' &&
			    $video_icon_gravity ne 'NorthEast') {
			    printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
			    $video_icon_gravity = 'SouthWest';
			}
			&write_video_icons ($tmp_jpg_dir);
			if ($video_icon < 1 ||
			    $video_icon > $max_video_icons) {
			    printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
			    $video_icon = 1;
			}
			$iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
			$retval = $icon->Read(filename=>$iconfile);
			warn "$retval" if "$retval";

			$image->Composite(image=>$icon,gravity=>$video_icon_gravity);
		    }

		    $retval = $image->Write(filename=>"$destdir/$medium");
		    warn "$retval" if "$retval";

		    &delete_tmp_jpg_dir($tmp_jpg_dir);

		} else {
		    # Up to date, existing medium
		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
		    # the file here and the code below will set the thumb_x/y properties.
		    #
		    $retval = $image->Read("$destdir/$medium");
		    warn "$retval" if "$retval";

		}

		$info{$pathname}{'med_size'} = &convert_to_kb($image->Get('filesize'));
		$info{$pathname}{'med_x'} = $image->Get('width');
		$info{$pathname}{'med_y'} = $image->Get('height');

		$med_counter++;


	    } else {
		# Skip if we are below the threshold size
		$skipmedium{$pathname} = 1;
		unlink("$destdir/$medium") if (-e "$destdir/$medium");
	    }

	} else {
	    my $image = new Image::Magick;
	    my $retval;

	    # The size of the file was not within the "threshold" so we need to create a
	    # medium version

	    if ($info{$pathname}{'is_video'}) {

		# Create a medium version out of the first frame of the video file and then
		# resize it according to $med_x and $med_y
		#
		if ((! -e "$destdir/$medium") or
		    ( -M $pathname < -M "$destdir/$medium") or
		    defined($opt_forceregen)) {

		    my $icon = new Image::Magick;
		    my $newgeom = $med_x . "x" . $med_y;

		    print "Creating $destdir/$medium from first frame of $pathname\n";

		    my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
		    $retval = $image->Read(filename=>$tmpfile);
		    warn "$retval" if "$retval";
		    $retval = $image->Resize(geometry=>$newgeom);
		    warn "$retval" if "$retval";
		    $retval = $image->Set(interlace=>Line);
		    warn "$retval" if "$retval";
		    # always want 100% here instead of $med_quality
		    $retval = $image->Set(quality=>100);
		    warn "$retval" if "$retval";

		    # If the user wants to, overlay a small icon on top of the
		    # thumbnail of the video file to give a visual cue that this
		    # file is a video and not a still photo
		    #
		    if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
			my $iconfile;
			if ($video_icon_gravity ne 'SouthWest' &&
			    $video_icon_gravity ne 'NorthWest' &&
			    $video_icon_gravity ne 'SouthEast' &&
			    $video_icon_gravity ne 'NorthEast') {
			    printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
			    $video_icon_gravity = 'SouthWest';
			}
			&write_video_icons ($tmp_jpg_dir);
			if ($video_icon < 1 ||
			    $video_icon > $max_video_icons) {
			    printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
			    $video_icon = 1;
			}
			$iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
			$retval = $icon->Read(filename=>$iconfile);
			warn "$retval" if "$retval";

			$image->Composite(image=>$icon,gravity=>$video_icon_gravity);
		    }

		    $retval = $image->Write(filename=>"$destdir/$medium");
		    warn "$retval" if "$retval";

		    &delete_tmp_jpg_dir($tmp_jpg_dir);

		} else {
		    # Up to date, existing medium
		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
		    # the file here and the code below will set the thumb_x/y properties.
		    #
		    $retval = $image->Read("$destdir/$medium");
		    warn "$retval" if "$retval";

		}

		$info{$pathname}{'med_size'} = &convert_to_kb($image->Get('filesize'));
		$info{$pathname}{'med_x'} = $image->Get('width');
		$info{$pathname}{'med_y'} = $image->Get('height');

		$med_counter++;

	    } else {
		my $image = new Image::Magick;
		my $retval;

		# Create medium sized pic if it is not there,
		# or is out of date with respect to original image
		if ((! -e "$destdir/$medium") or
		    ( -M $pathname < -M "$destdir/$medium") or
		    defined($opt_forceregen)) {

		    my $newgeom = $med_x . "x" . $med_y;

		    print "Creating $destdir/$medium\n";

		    $retval = $image->Read(filename=>$pathname);
		    warn "$retval" if "$retval";
		    $retval = $image->Resize(geometry=>$newgeom);
		    warn "$retval" if "$retval";
		    $retval = $image->Set(interlace=>Line);
		    warn "$retval" if "$retval";
		    $retval = $image->Set(quality=>$med_quality);
		    warn "$retval" if "$retval";
		    if ($info{$pathname}{'is_multi_image_file'}) {
			$retval = $image->[0]->Write(filename=>"$destdir/$medium");
			warn "$retval" if "$retval";
		    } else {
			$retval = $image->Write(filename=>"$destdir/$medium");
			warn "$retval" if "$retval";
		    }

		    $image_cache{$pathname} = $image;

		} else {

		    # Up to date, existing medium, grab dimensions
		    # Get the right hsize/vsize tags for the medium slides. Simply do a "Read" of
		    # the file here and the code below will set the med_x/y properties.
		    #
		    $retval = $image->Read("$destdir/$medium");
		    warn "$retval" if "$retval";
		}

		$info{$pathname}{'med_size'} = &convert_to_kb($image->Get('filesize'));
		$info{$pathname}{'med_x'} = $image->Get('width');
		$info{$pathname}{'med_y'} = $image->Get('height');

		$med_counter++;
	    }

	}

	#
	# Next, deal with the thumbnail for this image. If we have just created a medium
	# version of the image, then an open image "handle" will exist for it. We simply
	# shrink that down to thumbnail size (if appropriate) rather than reading in the
	# original file again just to shrink it (saves processing time).
	#

	# Skip thumb if we are below the threshold size
	if (($info{$pathname}{'x'} <= ($current_thumbnail_x * $thumbnail_threshold)) and
	    ($info{$pathname}{'y'} <= ($current_thumbnail_y * $thumbnail_threshold))) {

	    my $image = new Image::Magick;
	    my $retval;

	    if ($info{$pathname}{'is_video'}) {
		# If the file is a video but the X/Y is smaller than that of our
		# thumbnail size we still need to make a JPG out of the first
		# frame and store it in the thumbnail directory. This code is
		# mostly like the code below for creating video thumbnails but
		# we're just not resizing the first frame down to thumbnail size.
		#
		my $icon = new Image::Magick;

		# We need to make a thumbnail from this video file. Get the first frame

		print "Creating $destdir/$thumbnail from first frame of $pathname\n";

		my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
		$retval = $image->Read(filename=>$tmpfile);
		warn "$retval" if "$retval";
		$retval = $image->Set(interlace=>Line);
		warn "$retval" if "$retval";
		$retval = $image->Set(quality=>$thumb_quality);
		warn "$retval" if "$retval";

		# If the user wants to, overlay a small icon on top of the
		# thumbnail of the video file to give a visual cue that this
		# file is a video and not a still photo
		#
		if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
		    my $iconfile;
		    if ($video_icon_gravity ne 'SouthWest' &&
			$video_icon_gravity ne 'NorthWest' &&
			$video_icon_gravity ne 'SouthEast' &&
			$video_icon_gravity ne 'NorthEast') {
			printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
			$video_icon_gravity = 'SouthWest';
		    }
		    &write_video_icons ($tmp_jpg_dir);
		    if ($video_icon < 1 ||
			$video_icon > $max_video_icons) {
			printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
			$video_icon = 1;
		    }
		    $iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
		    $retval = $icon->Read(filename=>$iconfile);
		    warn "$retval" if "$retval";

		    $image->Composite(image=>$icon,gravity=>$video_icon_gravity);
		}


		$retval = $image->Write(filename=>"$destdir/$thumbnail");
		warn "$retval" if "$retval";

		push(@montagefiles,"$destdir/$thumbnail");

		$modified_thumb++;

		$info{$pathname}{'thumb_size'} = &convert_to_kb($image->Get('filesize'));
		$info{$pathname}{'thumb_x'} = $image->Get('width');
		$info{$pathname}{'thumb_y'} = $image->Get('height');

		$thumbnail_counter++;

		&delete_tmp_jpg_dir($tmp_jpg_dir);

	    } else {
		# is NOT a video file
		$info{$pathname}{'thumb_x'} = $info{$pathname}{'x'};
		$info{$pathname}{'thumb_y'} = $info{$pathname}{'y'};

		$skipthumb{$pathname} = 1;
		if (-e "$destdir/$thumbnail") {
		    unlink("$destdir/$thumbnail");
		    $modified_thumb++;
		}
		push(@montagefiles,"$destdir/$filename");
	    }

	} else {
	    my $image = new Image::Magick;
	    my $retval;

	    if ($info{$pathname}{'is_video'}) {

		my $icon = new Image::Magick;

		# We need to make a thumbnail from this video file. Get the first frame and
		# resize it down to the appropriate size
		if ((! -e "$destdir/$thumbnail") or
		    ( -M $pathname < -M "$destdir/$thumbnail") or
		    defined($opt_forceregen)) {

		    my $newgeom = $current_thumbnail_x . "x" . $current_thumbnail_y;

		    print "Creating $destdir/$thumbnail from first frame of $pathname\n";

		    my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
		    $retval = $image->Read(filename=>$tmpfile);
		    warn "$retval" if "$retval";
		    $retval = $image->Resize(geometry=>$newgeom);
		    warn "$retval" if "$retval";
		    $retval = $image->Set(interlace=>Line);
		    warn "$retval" if "$retval";
		    # Always want 100% here instead of $thumb_quality
		    $retval = $image->Set(quality=>100);
		    warn "$retval" if "$retval";

		    # If the user wants to, overlay a small icon on top of the thumbnail
		    # of the video file to give a visual cue that this file is a video and
		    # not a still photo
		    #
		    if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
			my $iconfile;
			if ($video_icon_gravity ne 'SouthWest' &&
			    $video_icon_gravity ne 'NorthWest' &&
			    $video_icon_gravity ne 'SouthEast' &&
			    $video_icon_gravity ne 'NorthEast') {
			    printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
			    $video_icon_gravity = 'SouthWest';
			}
			&write_video_icons ($tmp_jpg_dir);
			if ($video_icon < 1 ||
			    $video_icon > $max_video_icons) {
			    printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
			    $video_icon = 1;
			}
			$iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
			$retval = $icon->Read(filename=>$iconfile);
			warn "$retval" if "$retval";

			$image->Composite(image=>$icon,gravity=>$video_icon_gravity);
		    }


		    $retval = $image->Write(filename=>"$destdir/$thumbnail");
		    warn "$retval" if "$retval";


		    push(@montagefiles,"$destdir/$thumbnail");

		    $modified_thumb++;

		    &delete_tmp_jpg_dir($tmp_jpg_dir);

		} else {
		    # Up to date, existing thumb
		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
		    # the file here and the code below will set the thumb_x/y properties.
		    #
		    $retval = $image->Read("$destdir/$thumbnail");
		    warn "$retval" if "$retval";

		    push(@montagefiles,"$destdir/$thumbnail");
		}

		$info{$pathname}{'thumb_size'} = &convert_to_kb($image->Get('filesize'));
		$info{$pathname}{'thumb_x'} = $image->Get('width');
		$info{$pathname}{'thumb_y'} = $image->Get('height');

		$thumbnail_counter++;


	    } else {
		# Is NOT a video file
		my $image = new Image::Magick;
		my $retval;

		# Create thumbnail if it is not there,
		# or is out of date with respect to original image
		if ((! -e "$destdir/$thumbnail") or
		    ( -M $pathname < -M "$destdir/$thumbnail") or
		    defined($opt_forceregen)) {

		    my $newgeom = $current_thumbnail_x . "x" . $current_thumbnail_y;

		    print "Creating $destdir/$thumbnail\n";

		    if (defined ($image_cache{$pathname})) {
			$image = $image_cache{$pathname};

			$retval = $image->Resize(geometry=>$newgeom);
			warn "$retval" if "$retval";
			$retval = $image->Set(quality=>$thumb_quality);
			warn "$retval" if "$retval";
			if ($info{$pathname}{'is_multi_image_file'}) {
			    $retval = $image->[0]->Write(filename=>"$destdir/$thumbnail");
			    warn "$retval" if "$retval";
			} else {
			    $retval = $image->Write(filename=>"$destdir/$thumbnail");
			    warn "$retval" if "$retval";
			}
		    }
		    else {
			$retval = $image->Read(filename=>$pathname);
			warn "$retval" if "$retval";
			$retval = $image->Resize(geometry=>$newgeom);
			warn "$retval" if "$retval";
			$retval = $image->Set(interlace=>Line);
			warn "$retval" if "$retval";
			$retval = $image->Set(quality=>$thumb_quality);
			warn "$retval" if "$retval";
			if ($info{$pathname}{'is_multi_image_file'}) {
			    $retval = $image->[0]->Write(filename=>"$destdir/$thumbnail");
			    warn "$retval" if "$retval";
			} else {
			    $retval = $image->Write(filename=>"$destdir/$thumbnail");
			    warn "$retval" if "$retval";
			}
		    }
		    push(@montagefiles,"$destdir/$thumbnail");

		    $modified_thumb++;


		} else {

		    # Up to date, existing thumb
		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
		    # the file here and the code below will set the thumb_x/y properties.
		    #
		    $retval = $image->Read("$destdir/$thumbnail");
		    warn "$retval" if "$retval";

		    push(@montagefiles,"$destdir/$thumbnail");

		}

		$info{$pathname}{'thumb_size'} = &convert_to_kb($image->Get('filesize'));
		$info{$pathname}{'thumb_x'} = $image->Get('width');
		$info{$pathname}{'thumb_y'} = $image->Get('height');

		$thumbnail_counter++;
	    }
	}

	# Set the max thumb sizes, to be used for slide frame width
	if ($info{$pathname}{'thumb_x'} > $max_thumb_x) {
	    $max_thumb_x = $info{$pathname}{'thumb_x'};
	}
	if ($info{$pathname}{'thumb_y'} > $max_thumb_y) {
	    $max_thumb_y = $info{$pathname}{'thumb_y'};
	}


	# Set the max montage thumb sizes, to be used when creating montage images
	#
	$bn = basename ($thumbnail);
	unless (defined ($skipmont{$bn})) {
	    if ($info{$pathname}{'thumb_x'} > $max_mont_thumb_x) {
		$max_mont_thumb_x = $info{$pathname}{'thumb_x'};
	    }
	    if ($info{$pathname}{'thumb_y'} > $max_mont_thumb_y) {
		$max_mont_thumb_y = $info{$pathname}{'thumb_y'};
	    }
	}

	#
	# Finally, create html for this image
	#

	&image_entry($pathname);

    } # foreach image that has this timestamp

} # foreach timestamp


# Finish up the columns if needed
if (($col_counter != 1) and
    ($col_counter <= $current_columns) and
    ($object_counter > $current_columns)) {
    foreach ($col_counter..$current_columns) {
	push(@index, "    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">$emptycell</TD>\n");
	push(@details, "    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">$emptycell</TD>\n");
    }
    push(@index, "  </TR>\n");
    push(@details, "  </TR>\n");
}

# Nuke generated dirs if no contents
system("rm -rf $destdir/$thumbnail_dir") if ($thumbnail_counter == 0);
system("rm -rf $destdir/$slide_dir") if ($slide_counter == 0);
system("rm -rf $destdir/$med_dir") if ($med_counter == 0);

# Create montage if we had more than just dir entries here
if (($dir_counter != $object_counter)) {
    &create_montage(@montagefiles);
}

# Create stylesheet
&write_css();

# Write index web page
open(INDEX,">$destdir/$indexfile") or die ("Can't open $destdir/$indexfile: $!\n");
&page_header('index', $index_linkto);
foreach (@index) {
    print INDEX;
}
&page_footer('index');
close(INDEX);

# Write photo details file
if ($do_detail == 1) {
    open(INDEX,">$destdir/$detailfile") or die ("Can't open $destdir/$indexfile: $!\n");
    &page_header('detail', $details_linkto);
    foreach (@details) {
	print INDEX;
    }
    &page_footer('detail');
    close(INDEX);
} else {
    unlink("$destdir/$detailfile") if (-e "$destdir/$detailfile");
}

# Write slide/frame files
if (($do_slide == 1) and ($slide_counter > 1)) {
    &write_frameset();
} else {
    system("rm -rf $destdir/$slide_dir") if (-d "$destdir/$slide_dir");
}

# Optionally export images somewhere else
if ($opt_destdir) {
    printf ("Copying image files from '$srcdir' to '$destdir'.\n");
    foreach my $image (keys %info) {
	# BSD's default 'cp' cannot preserve links like GNU fileutils cp can
	#
	if ($uname =~ /BSD/) {
	    system("cp -pv $image $destdir");
	}
	else {
	    system("cp -dpuv $image $destdir");
	}
    }
}

if (defined ($do_emoticons) && $do_emoticons) {
    foreach $icon ('wink', 'smile', 'frown') {
	if ($emoticon{$icon}) {
	    &write_emoticon_png ($icon);
	} else {
	    unlink ($destdir . '/' . $thumbnail_dir . "/$emoticonprefix${icon}.png");
	}
    }
}

######################################################################
#
# Write the various HTML parts for this image
#
######################################################################
sub image_entry {

    my $pathname = shift(@_);
    my $filename = $info{$pathname}{'file'};
    my $link;

    &index_html($pathname);

    if ($do_detail == 1) {
	&details_html($pathname);
    }

    if (($do_slide == 1) and ($image_counter > 1)) {
	&slide_html($pathname);
    } else {
	my $file = $info{$pathname}{slide};
	unlink($file) if (-e $file);
    }

    # Increment for next time
    $col_counter++;
    $col_counter = 1 if ($col_counter > $current_columns);

}


###############################################################################
#
# Generate HTML for index page entry
#
###############################################################################

sub index_html {

    my $pathname = shift(@_);
    my $filename = $info{$pathname}{'file'};
    my $link;
    my $anchortext;

    # At beginning of row?
    if ($col_counter == 1) {
	push(@index, "  <TR>\n");
    }

    # Image
    push(@index, "    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");
    push(@index, "      <DIV CLASS=\"index\">");
    push(@index, &format_date($info{$pathname}{'date'}, 'index'));
    push(@index,"</DIV>\n");

    if (($index_linkto eq 'details') and ($do_detail == 1)) {
	$link = "$detailfile#$filename";
    } elsif (($index_linkto eq 'medium') and !defined($skipmedium{$pathname})) {
	$link = $info{$pathname}{'medium'};
    } elsif (($index_linkto eq 'thumbnail') and !defined($skipthumb{$pathname})) {
	$link = $info{$pathname}{'thumb'};
    } elsif (($index_linkto eq 'slide') and ($do_slide == 1) and ($image_counter > 1)) {
	$link = $info{$pathname}{'slide'};
    } else {
	$link = $filename;
    }

    $anchortext = "      <A HREF=\"$link\" ";

    if (defined ($indexthumbtitle) && $indexthumbtitle ne '') {
	my ($str);
	$str = &interpolate_title_string ($indexthumbtitle, $pathname, 'index');
	if ($str ne '') {
	    $anchortext .= sprintf ("TITLE=\"%s\" ", $str);
	}
    }

    $anchortext .= "NAME=\"$filename\">";

    push(@index, $anchortext);

    if (defined($skipthumb{$pathname})) {
	push(@index,"<IMG SRC=\"$filename\"");
    } else {
	push(@index,"<IMG SRC=\"$info{$pathname}{thumb}\"");
    }
    push(@index," WIDTH=\"$info{$pathname}{thumb_x}\" HEIGHT=\"$info{$pathname}{thumb_y}\"");
    push(@index," ALT=\" $filename \"");
    push(@index," CLASS=\"index\"");
    push(@index,"></A>\n");

    push(@index, "      <DIV CLASS=\"index\">");

    # Full size link
    push(@index,"<A HREF=\"$filename\">full size</A>");

    # Medium size link if within the threshold
    unless (defined($skipmedium{$pathname})) {
	push(@index,"&nbsp;|&nbsp;<A HREF=\"$info{$pathname}{medium}\">medium</A>");
    }

    # Detail list link
    if ($do_detail == 1) {
	push(@index,"&nbsp;|&nbsp;<A HREF=\"$detailfile#$filename\">details</A>");
    }

    push(@index,"</DIV>\n");

    # Caption if any (jpeg comment field)
    if (($do_captions == 1) and defined($info{$pathname}{'comment'})) {
	my ($tmp);
	push(@index, "      <DIV CLASS=\"caption\">");
	# Hack: if a comment has an ellipsis at the very end, make the HTML use a
	# non-breakable space before it so that the ellipsis doesn't "wrap" inside
	# the table field. It just looks better for those cases where the comment
	# is just long enough to wrap when rendered in the space given
	#
	$tmp = $info{$pathname}{'comment'};
	$tmp = &htmlize_caption ($tmp);
	if ($tmp =~ /(\s+)\.\.\.\s*$/) {
	    $tmp =~ s/(\s+)\.\.\.\s*$/&nbsp;.../;
	}
	push(@index, $tmp);
	push(@index,"</DIV>\n");
    }

    push(@index, "    </TD>\n\n");

    # At end of row?
    if ($col_counter == $current_columns) {
	push(@index, "  </TR>\n");
    }

}


###############################################################################
#
# Generate HTML for slide/frame pages
#
###############################################################################
sub slide_html {

    my $pathname = shift(@_);
    my $filename = $info{$pathname}{'file'};
    my $link;
    my $anchortext;

    #
    # First the index frame info
    #
    if ($frame_orient eq 'horizontal') {
	push(@frame,"    <TD CLASS=\"frame\" ALIGN=\"center\" VALIGN=\"middle\">\n");
    } else {
	push(@frame,"  <TR>\n    <TD CLASS=\"frame\" ALIGN=\"center\" VALIGN=\"middle\">\n");
    }

    $anchortext = "      <A HREF=\"../$info{$pathname}{slide}\" ";
    if (defined ($framethumbtitle) && $framethumbtitle ne '') {
	my ($str);
	$str = &interpolate_title_string ($framethumbtitle, $pathname, 'frame');
	if ($str ne '') {
	    $anchortext .= sprintf ("TITLE=\"%s\" ", $str);
	}
    }

    $anchortext .= "TARGET=\"view\">";
    push(@frame, $anchortext);

    if (defined($skipthumb{$pathname})) {
	push(@frame,"<IMG SRC=\"../$filename\"");
    } else {
	push(@frame,"<IMG SRC=\"../$info{$pathname}{thumb}\"");
    }
    push(@frame," WIDTH=\"$info{$pathname}{thumb_x}\" HEIGHT=\"$info{$pathname}{thumb_y}\"");
    push(@frame," ALT=\" $filename \"");
    push(@frame," CLASS=\"frame\"");
    push(@frame,"></A>\n");
    if ($frame_orient eq 'horizontal') {
	push(@frame,"    </TD>");
    } else {
	push(@frame,"    </TD>\n  </TR>");
    }
    push(@frame,"\n");

    #
    # Then the individual slides
    #
    my $slide = new FileHandle "> $destdir/$info{$pathname}{slide}";
    if (!defined($slide)) {
	die("$destdir/$info{$pathname}{slide}: $!");
    }

    select($slide);
    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n";
    print "\"http://www.w3.org/TR/html401/strict.dtd\">\n";
    print "<HTML>\n";
    print "<HEAD>\n";
    $verstring = &versionstring();
    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">\n");
    print "<TITLE>$current_titletext - $filename</TITLE>\n";
    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">\n";
    print "</HEAD>\n<BODY>\n";

    &next_prev_links($pathname);

    # Caption if any
    if (($do_captions == 1) and ($slide_caption eq 'top') and defined($info{$pathname}{'comment'})) {
	print "<DIV CLASS=\"caption\">";
	my $tmp = &htmlize_caption ($info{$pathname}{'comment'}, 'slide');
	print $tmp;
	print "</DIV>\n";
    }

    # Date, filename
    if ($slide_date eq 'top') {
	print "<DIV CLASS=\"index\">";
	print &format_date($info{$pathname}{'date'}, 'slide');
	print " $filename";
	print "</DIV>\n";
    }

    if ($slide_linkto eq 'index') {
	$link = "../$indexfile#$filename";
    } elsif (($slide_linkto eq 'details') and ($do_detail == 1)) {
	$link = "../$detailfile#$filename";
    } elsif (($slide_linkto eq 'medium') and !defined($skipmedium{$pathname})) {
	$link = "../$info{$pathname}{medium}";
    } elsif (($slide_linkto eq 'thumbnail') and !defined($skipthumb{$pathname})) {
	$link = "../$info{$pathname}{thumb}";
    } else {
	$link = "../$filename";
    }

    print "\n<P>\n";

    $anchortext = "<A HREF=\"$link\"";
    if (defined ($slidethumbtitle) && $slidethumbtitle ne '') {
	my ($str);
	$str = &interpolate_title_string ($slidethumbtitle, $pathname, 'slide');
	if ($str ne '') {
	    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
	}
    }

    $anchortext .= ">";
    print $anchortext;

    if ($info{$pathname}{'is_video'}) {
	print "<IMG SRC=\"../$info{$pathname}{medium}\"";
	print " WIDTH=\"$info{$pathname}{x}\" HEIGHT=\"$info{$pathname}{y}\"";
    } else {
	if (defined($skipmedium{$pathname})) {
	    print "<IMG SRC=\"../$filename\"";
	    print " WIDTH=\"$info{$pathname}{x}\" HEIGHT=\"$info{$pathname}{y}\"";
	} else {
	    print "<IMG SRC=\"../$info{$pathname}{medium}\"";
	    print " WIDTH=\"$info{$pathname}{med_x}\" HEIGHT=\"$info{$pathname}{med_y}\"";
	}
    }

    print " ALT=\" $filename \"";
    print " CLASS=\"slide\">";
    print "</A>\n";

    print "</P>\n";

    # Caption if any
    if (($do_captions == 1) and  ($slide_caption eq 'bottom') and defined($info{$pathname}{'comment'})) {
	print "<DIV CLASS=\"caption\">";
	my $tmp = &htmlize_caption ($info{$pathname}{'comment'}, 'slide');
	print $tmp;
	print "</DIV>\n";
    }

    # Date, filename
    if ($slide_date eq 'bottom') {
	print "<DIV CLASS=\"index\">";
	print &format_date($info{$pathname}{'date'}, 'slide');
	print " $filename";
	print "</DIV>\n";
    }

    &next_prev_links($pathname);
    print "</BODY>\n</HTML>\n";

    select(STDOUT);
    $slide->close();
    $slide_counter++;

    unless(defined($first_slide)) {
	$first_slide = $info{$pathname}{'slide'};
    }
}


###############################################################################
#
# Generate HTML for details page
#
###############################################################################
sub details_html {

    my $pathname = shift(@_);
    my $filename = $info{$pathname}{'file'};
    my ($link, $anchortext);

    # At beginning of row?
    if ($col_counter == 1) {
	push(@details, "  <TR>\n");
    }


    if ($details_linkto eq 'index') {
	$link = "$indexfile#$filename";
    } elsif (($details_linkto eq 'medium') and !defined($skipmedium{$pathname})) {
	$link = "$info{$pathname}{medium}";
    } elsif (($details_linkto eq 'thumbnail') and !defined($skipthumb{$pathname})) {
	$link = "$info{$pathname}{thumb}";
    } elsif (($details_linkto eq 'slide') and ($do_slide == 1) and ($image_counter > 1)) {
	$link = $info{$pathname}{'slide'};
    } else {
	$link = $filename;
    }

    push(@details,"    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");
    push(@details,"      <TABLE BORDER=0 WIDTH=\"100%\">\n");
    push(@details,"        <TR>\n");
    push(@details,"          <TD VALIGN=\"middle\" ALIGN=\"center\">\n");
    push(@details,"            <DIV CLASS=\"detail\">\n");
    push(@details,"            <A NAME=\"$filename\">");
    push(@details, &format_date($info{$pathname}{'date'}, 'detail'));
    push(@details,"</A><BR>\n");

    $anchortext = "<A HREF=\"$link\"";
    if (defined ($detailthumbtitle) && $detailthumbtitle ne '') {
	my ($str);
	$str = &interpolate_title_string ($detailthumbtitle, $pathname, 'detail');
	if ($str ne '') {
	    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
	}
    }

    $anchortext .= ">";
    push(@details, $anchortext);

    if (defined($skipthumb{$pathname})) {
	push(@details,"<IMG SRC=\"$filename\"");
    } else {
	push(@details,"<IMG SRC=\"$info{$pathname}{thumb}\"");
    }
    my $x = $info{$pathname}{'thumb_x'} / $detailshrink ;
    my $y = $info{$pathname}{'thumb_y'} / $detailshrink ;
    push(@details, sprintf(" WIDTH=\"%d\" HEIGHT=\"%d\"", $x, $y));
    push(@details," ALT=\" $filename \"");
    push(@details," CLASS=\"index\"");
    push(@details,"></A><BR>");
    push(@details,"$filename<BR>");
    push(@details,"</DIV>\n");
    push(@details,"          </TD>\n\n");
    push(@details,"          <TD VALIGN=\"middle\" ALIGN=\"left\">\n");
    push(@details,"            <DIV CLASS=\"detail\">");
    push(@details,"Original:&nbsp;<A HREF=\"$filename\">$info{$pathname}{geometry}</A>");
    push(@details,"&nbsp;($info{$pathname}{size})<BR>");
    unless (defined($skipmedium{$pathname})) {
	push(@details,"Medium:&nbsp;<A HREF=\"$info{$pathname}{medium}\">");
	push(@details,$info{$pathname}{'med_x'} . 'x' . $info{$pathname}{'med_y'} . "</A>");
	push(@details,"&nbsp;($info{$pathname}{med_size})<BR>");
    }
    unless (defined($skipthumb{$pathname})) {
	push(@details,"Thumbnail:&nbsp;<A HREF=\"$info{$pathname}{thumb}\">");
	push(@details,$info{$pathname}{'thumb_x'} . 'x' . $info{$pathname}{'thumb_y'} . "</A>");
	push(@details,"&nbsp;($info{$pathname}{thumb_size})<BR>");
    }

    if (defined ($info{$pathname}{'is_multi_image_file'}) &&
	$info{$pathname}{'is_multi_image_file'} == 1) {
	push(@details,"Multi-image File:&nbsp;$info{$pathname}{scenes} scenes<BR>");
    }

    # Video file stuff
    #
    if (defined ($info{$pathname}{'video_format'})) {
	push (@details, "Video Format:&nbsp;$info{$pathname}{'video_format'}<BR>");
    }
    if (defined ($info{$pathname}{'video_bitrate'})) {
	push (@details, "Video Bitrate:&nbsp;$info{$pathname}{'video_bitrate'}<BR>");
    }
    if (defined ($info{$pathname}{'video_fps'})) {
	push (@details, "Video Rate:&nbsp;$info{$pathname}{'video_fps'} f/s<BR>");
    }
    if (defined ($info{$pathname}{'audio_codec'})) {
	push (@details, "Audio Codec:&nbsp;$info{$pathname}{'audio_codec'}<BR>");
    }
    if (defined ($info{$pathname}{'audio_bitrate'})) {
	push (@details, "Audio Bitrate:&nbsp;$info{$pathname}{'audio_bitrate'} kbit/s<BR>");
    }
    if (defined ($info{$pathname}{'length'})) {
	push (@details, "Length (time):&nbsp;$info{$pathname}{'length'} sec<BR>");
    }

    #
    # EXIF data
    #
    if (defined($info{$pathname}{'flash'})) {
	push(@details,"Flash:&nbsp;$info{$pathname}{flash}<BR>");
    }
    if (defined($info{$pathname}{'exposure_time'})) {
	push(@details,"Exposure time:&nbsp;$info{$pathname}{exposure_time}<BR>");
    }
    if (defined($info{$pathname}{'focus_dist'})) {
	push(@details,"Focus distance:&nbsp;$info{$pathname}{focus_dist}<BR>");
    }
    if (defined($info{$pathname}{'focal_length'})) {
	push(@details,"Focal length:&nbsp;$info{$pathname}{focal_length}<BR>");
    }
    if (defined($info{$pathname}{'aperture'})) {
	push(@details,"Aperture:&nbsp;$info{$pathname}{aperture}<BR>");
    }

    push(@details,"\n");
    push(@details,"            </DIV>\n");
    push(@details,"          </TD>\n");
    push(@details,"        </TR>\n");
    push(@details,"      </TABLE>\n");
    push(@details,"    </TD>\n");

    # At end of row?
    if ($col_counter == $current_columns) {
	push(@details, "  </TR>\n");
    }


}

sub delete_tmp_jpg_dir {
    my ($tmp_jpg_dir) = @_;
    while ($name = <$tmp_jpg_dir/*>) {
	unlink ($name);
    }
    rmdir $tmp_jpg_dir;
}

sub extract_first_frame_jpg {

    my ($filename, $tmp_jpg_dir, $mplayer_prog) = @_;
    my ($retval, $cmd);

    # Need to give 2 frames here. On some MPG files mplayer produces no output when
    # you request 1 frame.
    $cmd = "$mplayer_prog $filename -noautosub -nosound -vo jpeg:outdir=${tmp_jpg_dir}:quality=100 -frames 2 > /dev/null 2>&1";
    print "About to execute: '$cmd'\n" if $opt_debug;
    $retval = system ($cmd);
    if ($retval) {
	printf ("warning: mplayer returned %d\n", $? >> 8);
    } else {
	return "${tmp_jpg_dir}/00000001.jpg";
    }

}
######################################################################
#
# Extract info from a given file
#
######################################################################
sub extract_file_info {

    my $filename = shift (@_);
    my $pathname = "$srcdir/$filename";
    my $retval;

    if ($filename =~ /\.${video_regexp}$/i) {
	$retval = &extract_movie_info ($filename);
	# mplayer told us that this wasn't a movie file so at least yell
	# at the user so that the video regexp might be adjusted
	if ($retval == -1) {
	    print "\nwarning: $pathname identified by extension as video file but mplayer doesn't recognize it\n";
	    flush (STDOUT);
	}
    } else  {
	&extract_image_info ($filename);
    }

}

######################################################################
#
# Extract info from movie file
#
######################################################################
sub extract_movie_info {

    my $filename = shift (@_);
    my $pathname = "$srcdir/$filename";
    my ($retval, $cmd, $qt_found, $tmp);
    my $mplayer_prog = &find_in_path ('mplayer');
    my ($format, $bitrate, $x, $y, $fps, $aspect, $acodec, $abitrate);
    my ($arate, $anch, $length, $is_video);

    print ".";
    flush (STDOUT);

    if ($mplayer_prog eq '' || ($do_video_files == 0)) {
	if (($do_video_files != 0) && $mplayer_prog eq '') {
	    print "\nwarning: Trying to process video files but cannot find mplayer in \$path!\n";
	    flush (STDOUT);
	}
	print "\nSkipping $pathname";
	flush (STDOUT);
	return 0;
    } else {
	$object_counter++;
	$image_counter++;
    }


    $cmd = "$mplayer_prog -noautosub -vo null -ao null -frames 0 -identify $pathname |";

    if (! open (PIPE, $cmd)) {
	printf (STDERR "Could not open pipe from mplayer! - $!\n");
	return 0;
    }

    $is_video = 0;

    $qt_found = 0;

    while (<PIPE>) {
	if (/VIDEO:\s+(\S+)/) {
	    $format = $1;
	    $format =~ s/\[//g;
	    $format =~ s/\]//g;
	}
	if (/QuickTime.*detected/) {
	    $qt_found = 1;
	}
	if (/ID_VIDEO_FORMAT=(\S+)/) {
	    $tmp = $1;
	    if ($format eq 'jpeg' && $qt_found) {
		$tmp = 'QuickTime';
	    }
	    if ($tmp =~ /0x/) {
		$tmp = $format;
	    }
	    $format = $tmp;
	    $is_video = 1;
	}
	if (/ID_VIDEO_BITRATE=(\S+)/) {
	    $tmp = $1;
	    if ($tmp == 0) {
		$tmp = 'VBR';
	    }
	    $bitrate = $tmp;
	}
	if (/ID_VIDEO_WIDTH=(\S+)/) {
	    $x = $1;
	}
	if (/ID_VIDEO_HEIGHT=(\S+)/) {
	    $y = $1;
	}
	if (/ID_VIDEO_FPS=(\S+)/) {
	    $fps = $1;
	}
	if (/ID_VIDEO_ASPECT=(\S+)/) {
	    $aspect = $1;
	}
	if (/ID_AUDIO_CODEC=(\S+)/) {
	    $acodec = $1;
	}
	if (/ID_AUDIO_BITRATE=(\S+)/) {
	    $tmp = $1;
	    if ($tmp == 0) {
		$tmp = 'VBR';
	    }
	    $abitrate = $tmp;
	}
	if (/ID_AUDIO_RATE=(\S+)/) {
	    $arate = $1;
	}
	if (/ID_AUDIO_NCH=(\S+)/) {
	    $anch = $1;
	}
	if (/ID_LENGTH=(\S+)/) {
	    $length = $1;
	}

    }

    close (PIPE);

    if ($is_video) {

	$info{$pathname}{'is_video'} = 1;

	$info{$pathname}{'file'} = $filename;
	$info{$pathname}{'video_format'} = $format;
	$info{$pathname}{'video_bitrate'} = $bitrate;
	$info{$pathname}{'x'} = $x;
	$info{$pathname}{'y'} = $y;
	$info{$pathname}{'video_fps'} = $fps;
	$info{$pathname}{'video_aspect'} = $aspect;
	$info{$pathname}{'audio_codec'} = $acodec;
	$info{$pathname}{'audio_bitrate'} = $abitrate / 1000.0;
	$info{$pathname}{'audio_rate'} = $arate;
	$info{$pathname}{'audio_nch'} = $anch;
	$info{$pathname}{'length'} = $length;

	my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
	    $atime,$mtime,$ctime,$blksize,$blocks) = stat($pathname);

	$info{$pathname}{'date'} = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
	$info{$pathname}{'size'} = &convert_to_kb($size);

	$info{$pathname}{'geometry'} = $info{$pathname}{'x'} . "x" . $info{$pathname}{'y'};

	$info{$pathname}{'format'} = $info{$pathname}{'video_format'};

	my ($name,$path,$suffix) = fileparse($filename,'\.\S+');

	if (-e "${name}.txt") {
	    my $text;
	    if (! open (IN, "${name}.txt")) {
		warn "Cannot open ${name}.txt for reading - $!\n";
	    } else {
		$text = <IN>;
		$info{$pathname}{'comment'} = $text;
		close (IN);
	    }
	}

	$info{$pathname}{'thumb'} = "$thumbnail_dir/$name.jpg";
	$thumb_backref{"$thumbnail_dir/$name.jpg"} = $pathname;

	$info{$pathname}{'medium'} = "$med_dir/$name.jpg";
	$med_backref{"$med_dir/$name.jpg"} = $pathname;

	$info{$pathname}{'slide'} = "$slide_dir/$name.html";
	$slide_backref{"$slide_dir/$name.html"} = $pathname;

	push(@{$timestamp{"$info{$pathname}{date}"}}, $pathname);

	return (0);
    } else {
	return (1);
    }

}

######################################################################
#
# Extract info from image
#
######################################################################
sub extract_image_info {

    my $filename = shift (@_);
    my $pathname = "$srcdir/$filename";
    my $image = new Image::Magick;
    my $retval;
    my $i;

    print ".";
    flush (STDOUT);

    $retval = $image->Read($pathname);


    if ($retval ne "") {
	print "\nSkipping $pathname";
	flush (STDOUT);
	return;
    } else {
	$object_counter++;
	$image_counter++;
    }

    # iterate over the number of scenes in this image (if the file format
    # supports it). "normal" files will only have $image->[0] defined. If there
    # is a better way to do this, I'm all ears--even the C code embedded within
    # ImageMagick itself iterates like this--there doesn't seem to be an
    # 'attribute' that can be easily fetched.
    #
    for ($i = 0; defined $image->[$i]; $i++) {
	# empty
    }

    if ($i > 1) {
	$info{$pathname}{'is_multi_image_file'} = 1;
	$info{$pathname}{'scenes'} = $i;
    } else {
	$info{$pathname}{'is_multi_image_file'} = 0;
	$info{$pathname}{'scenes'} = 1;
    }

    $info{$pathname}{'file'} = $filename;

    # Use mtime as a fallback date in case we don't have exif data
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
	$atime,$mtime,$ctime,$blksize,$blocks) = stat($pathname);

    $info{$pathname}{'date'} = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
    $info{$pathname}{'x'} = $image->Get('width');
    $info{$pathname}{'y'} = $image->Get('height');
    $info{$pathname}{'geometry'} = $info{$pathname}{'x'} . "x" . $info{$pathname}{'y'};

    $info{$pathname}{'size'} = &convert_to_kb($image->Get('filesize'));

    $info{$pathname}{'format'} = $image->Get('format');

    $info{$pathname}{'comment'} = $image->Get('comment');

    my ($name,$path,$suffix) = fileparse($filename,'\.\S+');


    if ($info{$pathname}{'format'} =~ /JFIF/i) {

	$info{$pathname}{'thumb'} = "$thumbnail_dir/$filename";
	$thumb_backref{"$thumbnail_dir/$filename"} = $pathname;

	$info{$pathname}{'medium'} = "$med_dir/$filename";
	$med_backref{"$med_dir/$filename"} = $pathname;

	if (defined(&image_info)) {

	    my $exif = image_info("$pathname");

	    if (my $error = $exif->{error}) {
		warn "Can't parse image info: $error\n";
	    }

	    if (defined($opt_debug)) {
		print "EXIF data for $pathname:\n";
		foreach (keys %$exif) {
		    print "   $_ = $exif->{$_}\n";
		}
		print "\n";
	    }

	    if (defined($exif->{DateTimeOriginal})) {
		# some models use / instead of : in this field... (need to check spec)
		$exif->{DateTimeOriginal} =~ s/\//:/g;
		$exif->{DateTimeOriginal} =~ /\s*([\d:]+)\s+([\d:]+)/;
		my $dt = $1;
		my $tm = $2;
		$tm =~ s/://;
		$tm =~ s/:/\./;
		$dt =~ s/://g;
		$info{$pathname}{'date'} = $dt . $tm;
	    }

	    if (defined($exif->{Flash})) {
		$info{$pathname}{'flash'} = $exif->{'Flash'};
		$info{$pathname}{'flash'} =~ s/0/no/;
		$info{$pathname}{'flash'} =~ s/1/yes/;
	    }

	    if (defined($exif->{FocalLength})) {
		$info{$pathname}{'focal_length'} = sprintf("%4.1fmm", eval("$exif->{FocalLength}"));
	    }

	    if (defined($exif->{SubjectDistance})) {
		$info{$pathname}{'focus_dist'} = sprintf("%4.1fm", eval("$exif->{SubjectDistance}"));
	    }

	    if (defined($exif->{ExposureTime})) {
		$info{$pathname}{'exposure_time'} = $exif->{ExposureTime} . 's';
	    }

	    if (defined($exif->{FNumber})) {
		$info{$pathname}{'aperture'} =  "f/" . eval ("$exif->{FNumber}");
	    }

	}

    } else {

	$info{$pathname}{'thumb'} = "$thumbnail_dir/$name.jpg";
	$thumb_backref{"$thumbnail_dir/$name.jpg"} = $pathname;

	$info{$pathname}{'medium'} = "$med_dir/$name.jpg";
	$med_backref{"$med_dir/$name.jpg"} = $pathname;

    }

    $info{$pathname}{'slide'} = "$slide_dir/$name.html";
    $slide_backref{"$slide_dir/$name.html"} = $pathname;

    push(@{$timestamp{"$info{$pathname}{date}"}}, $pathname);

}


######################################################################
#
# Write HTML for directory entries
#
######################################################################
sub dir_entry {

    my $dir = shift(@_);
    my $destdirname = "$destdir/$dir";
    my $srcdirname = "$srcdir/$dir";
    my $anchortext;

    print "Processing directory $srcdirname\n";

    # Recurse first
    if ($do_recurse == 1) {
	my $flags = "";
	$flags .= "-medium " if ($do_medium == 1);
	$flags .= "-nomedium " if ($do_medium == 0);
	$flags .= "-slide " if ($do_slide == 1);
	$flags .= "-noslide " if ($do_slide == 0);
	$flags .= "-dirs " if ($do_dirs == 1);
	$flags .= "-nodirs " if ($do_dirs == 0);
	$flags .= "-montage " if ($do_montage == 1);
	$flags .= "-nomontage " if ($do_montage == 0);
	$flags .= "-detail " if ($do_detail == 1);
	$flags .= "-nodetail " if ($do_detail == 0);
	$flags .= "-reverse " if ($do_reverse == 1);
	$flags .= "-noreverse " if ($do_reverse == 0);
	$flags .= "-forceregen " if (defined($opt_forceregen));
	$flags .= "-includeall " if (defined($opt_includeall));
	$flags .= "-columns $current_columns " if (defined($opt_columns));
	$flags .= "-x $opt_x " if (defined($opt_x));
	$flags .= "-y $opt_y " if (defined($opt_y));
	$flags .= "-destdir $destdirname " if ($destdir ne $srcdir);
	foreach my $var (keys %opt_d) {
	    $flags .= " -d $var=$opt_d{$var}";
	}
	# If we're doing recursion and $updirtext is set either by default or
	# within a .imageindexrc file, then we're going to employ a terrible
	# hack here. We know that once this recursive call to ourselves returns
	# we're going to create an HTML page for the directory we're in now.
	# However, the recursive call will not "see" that file created and thus
	# the <a href> link isn't made back to "../$indexfile". We will tell
	# the recursive call to ourselves that it needs to think there's
	# a ../$indexfile file there regardless of whether it's there or not
	#
	if (defined $updirtext) {
	    $flags .= "-updirindexoverride ";
	}
	system("cd \"$srcdirname\" ;$0 $flags -recurse");

    }

    my $dirtitle = "";
    my $first;
    my $last;
    my $montage;
    my $montage_x;
    my $montage_y;

    # Only add entry if this dir has an index file
    if (-r "$destdirname/$indexfile") {

	# Go fetch the title and dates from the HTML
	my $tmp1 = &extract_meta_tag ($titlemetatag,"$destdirname/$indexfile");
	my $tmp2 = &extract_meta_tag ($begindatemetatag,"$destdirname/$indexfile");
	my $tmp3 = &extract_meta_tag ($enddatemetatag,"$destdirname/$indexfile");
	if (defined($tmp1)) {
	    $dirtitle = $tmp1;
	}
	if (defined($tmp2)) {
	    $first = $tmp2;
	}
	if (defined($tmp3)) {
	    $last = $tmp3;
	}

	# If we found generated files in this dir, flag that we found something
	# valid to index
	$object_counter++;
	$dir_counter++;

	# Set montage file if we found it
	if (($do_montage == 1) and ( -r "$destdirname/$thumbnail_dir/$montagefile")) {

	    print "Found montage in $destdirname\n" if defined($opt_debug);
	    $montage = "$destdirname/$thumbnail_dir/$montagefile";

	    my $image = new Image::Magick;
	    my $retval;

	    $retval = $image->Read(filename=>$montage);
	    warn "$retval" if "$retval";

	    $montage_x = $image->Get('width');
	    $montage_y = $image->Get('height');

	}


	# At beginning of row?
	if ($col_counter == 1) {
	    push(@index, "<TR>\n");
	    push(@details, "<TR>\n");
	}

	# Entry for this directory in main & details file
	push(@index, "<TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");
	push(@details, "<TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");

	push(@details, "<TABLE BORDER=\"0\" WIDTH=\"100%\">\n");

	if (defined($montage)) {
	    push(@details, "<TR><TD VALIGN=\"middle\" ALIGN=\"center\">\n");
	} else {
	    push(@details, "<TR><TD COLSPAN=\"2\" VALIGN=\"middle\" ALIGN=\"center\">\n");
	}

	if (defined($first)) {

	    my ($tmp_first, $tmp_last);

	    push(@index, "<DIV CLASS=\"index\">");
	    push(@details, "<DIV CLASS=\"detail\">");

	    $tmp_first = &format_date ($first, 'index', 'dayonly');
	    $tmp_last = &format_date ($last, 'index', 'dayonly');

	    if ($first ne $last) {
		push(@index, "$tmp_first - $tmp_last");
	    } else {
		push(@index, "$tmp_first");
	    }

	    $tmp_first = &format_date ($first, 'detail', 'dayonly');
	    $tmp_last = &format_date ($last, 'detail', 'dayonly');

	    if ($first ne $last) {
		push(@details, "$tmp_first - $tmp_last");
	    } else {
		push(@details, "$tmp_first");
	    }

	    push(@index, "</DIV>\n");
	    push(@details, "</DIV>\n");
	}


	if (defined($montage)) {

	    $anchortext = "<A HREF=\"$dir/$indexfile\"";
	    if (defined ($montagetitle) && $montagetitle ne '') {
		my ($str);
		$str = &interpolate_title_string_dir ($montagetitle, $dir, 'index');
		if ($str ne '') {
		    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
		}
	    }
	    $anchortext .= ">";
	    push(@index, $anchortext);

	    push(@index, "<IMG CLASS=\"index\" SRC=\"$dir/$thumbnail_dir/$montagefile\"");
	    push(@index, " WIDTH=\"$montage_x\" HEIGHT=\"$montage_y\"");
	    push(@index, " ALT=\"\"");
	    push(@index, ">");
	    push(@index, "</A>\n");

	    push(@index,"<DIV CLASS=\"index\">");
	    push(@index, "<A HREF=\"$dir/$indexfile\">$dir</A>");
	    push(@index,"</DIV>\n");

	    $anchortext = "<A HREF=\"$dir/$detailfile\"";
	    if (defined ($montagetitle) && $montagetitle ne '') {
		my ($str);
		$str = &interpolate_title_string_dir ($montagetitle, $dir, 'index');
		if ($str ne '') {
		    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
		}
	    }
	    $anchortext .= ">";
	    push(@details, $anchortext);

	    push(@details, "<IMG CLASS=\"index\" SRC=\"$dir/$thumbnail_dir/$montagefile\"");
	    my $x = $montage_x / $detailshrink ;
	    my $y = $montage_y / $detailshrink ;
	    push(@details, sprintf(" WIDTH=\"%d\" HEIGHT=\"%d\"", $x, $y));
	    push(@details, " ALT=\"\"");
	    push(@details, ">");
	    push(@details, "</A>");

	    push(@details, "</TD><TD VALIGN=\"middle\" ALIGN=\"left\">\n");

	} else {

	    push(@index,"<DIV CLASS=\"index\">");
	    push(@index, "<A HREF=\"$dir/$indexfile\">$dir</A>");
	    push(@index,"</DIV>\n");

	}

	push(@index, "<DIV CLASS=\"caption\">");
	push(@details, "<DIV CLASS=\"detail\">");

	if ($dirtitle ne "") {
	    push(@index, "$dirtitle");
	    push(@details, "$dirtitle");
	}

	push(@details, "<BR><A HREF=\"$dir/$detailfile\">$dir</A>");

	push(@index, "</DIV>\n");
	push(@details, "</DIV>\n");

	push(@details,"</TD></TR></TABLE>\n");

	push(@index, "</TD>\n");
	push(@details, "</TD>\n");

	# At end of row?
	if ($col_counter == $current_columns) {
	    push(@index, "</TR>\n");
	    push(@details, "</TR>\n");
	}


	# Increment for next item
	$col_counter++;
	$col_counter = 1 if ($col_counter > $current_columns);

    } # if dir had index file

}

######################################################################
#
# Top of HTML index/detail files
#
######################################################################
sub page_header {

    my $this = shift(@_);
    my $linkto = shift(@_);
    my $numlink = 0;
    my $verstring;

    select(INDEX);
    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n";
    print "\"http://www.w3.org/TR/html401/strict.dtd\">\n";
    print "<HTML>\n";
    print "<HEAD>\n";
    $verstring = &versionstring();
    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">\n");
    if (defined ($write_meta_tag{$titlemetatag})) {
	print "<META NAME=\"$titlemetatag\" CONTENT=\"$current_titletext\">\n";
    }
    if (defined ($write_meta_tag{$columnsmetatag})) {
	print "<META NAME=\"$columnsmetatag\" CONTENT=\"$current_columns\">\n";
    }
    if (defined ($write_meta_tag{$thumbxmetatag})) {
	print "<META NAME=\"$thumbxmetatag\" CONTENT=\"$current_thumbnail_x\">\n";
    }
    if (defined ($write_meta_tag{$thumbymetatag})) {
	print "<META NAME=\"$thumbymetatag\" CONTENT=\"$current_thumbnail_y\">\n";
    }
    if (defined ($write_meta_tag{$reversemetatag})) {
	print "<META NAME=\"$reversemetatag\" CONTENT=\"$current_reverse\">\n";
    }
    if (defined($firstdate)) {
	print "<META NAME=\"$begindatemetatag\" CONTENT=\"$firstdate\">\n";
    }
    if (defined($lastdate)) {
	print "<META NAME=\"$enddatemetatag\" CONTENT=\"$lastdate\">\n";
    }
    if (!defined ($opt_includeall) && defined (@opt_exclude) && scalar (@opt_exclude)) {
	my $tmp = join (',', @opt_exclude);
	my $etmp;

	# We need to "encode" this string in the HTML so that raw filenames
	# (that people should not try to access) are not exposed to the
	# outside world.
	#
	$etmp = &encodestring ($tmp);
	printf ("<META NAME=\"$excludemetatag\" CONTENT=\"%s\">\n", $etmp);
    }
    printf ("<META NAME=\"$numimagesmetatag\" CONTENT=\"%d\">\n", $image_counter);

    if (defined (@opt_skipmont) && scalar (@opt_skipmont)) {
	my $tmp = join (',', @opt_skipmont);
	printf ("<META NAME=\"$skipmetatag\" CONTENT=\"%s\">\n", $tmp);
    }
    print "<TITLE>$current_titletext</TITLE>\n";
    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"$stylefile\">\n";
    print "</HEAD>\n";
    print "<BODY>\n";

    # Break out of frames
    print "<SCRIPT TYPE=\"textvascript\">\n";
    print "if (parent.frames.length > 0) {\n";
    print "   parent.location.href = self.document.location\n";
    print "}\n";
    print "</SCRIPT>\n";

    print "<H1 CLASS=\"title\">$current_titletext</H1>\n";

    print "<H3>";

    # On all these links, check to see if the variable is also defined. If
    # not (done in a .imageindexrc file perhaps) then skip the link
    # If the script-use-only flag updirindexoverride was given it means we've
    # come in from a recursive call and $updirtext was set--therefore there will
    # be $destdir/../$indexfile eventually ... link to it.
    #
    if ((defined ($opt_updirindexoverride) and ($do_dirs == 1)) or
	((-e "$destdir/../$indexfile") and ($do_dirs == 1) and defined($updirtext))) {
	print "<A HREF=\"../$indexfile\">$updirtext</A>";
	$numlink++;
    }

    if (($do_slide == 1) and ($slide_counter > 1) and
	defined($framelinktext)) {
	print "&nbsp;&nbsp;|&nbsp;&nbsp;" if ($numlink != 0);
	print "<A HREF=\"$slide_dir/$framefile\">$framelinktext</A>";
	$numlink++;
    }

    if (($do_detail == 1) and ($this eq 'index') and defined($detaillinktext)) {
	print "&nbsp;&nbsp;|&nbsp;&nbsp;" if ($numlink != 0);
	print "<A HREF=\"$detailfile\">$detaillinktext</A>";
	$numlink++;
    }

    if (($this eq 'detail') and	defined($indexlinktext)) {
	print "&nbsp;&nbsp;|&nbsp;&nbsp;" if ($numlink != 0);
	print "<A HREF=\"$indexfile\">$indexlinktext</A>";
	$numlink++;
    }

    print "\n<BR>\n" if ($numlink != 0);

    print "</H3>\n";

    if (defined($firstdate) and defined($lastdate)) {

	my $tmp1 = &format_date($firstdate, $this, 'dayonly');
	my $tmp2 = &format_date($lastdate, $this, 'dayonly');

	if ($tmp1 ne $tmp2) {
	    if ($current_reverse == 0) {
		print "<H2 CLASS=\"daterange\">$tmp1 - $tmp2</H2>\n";
	    } else {
		print "<H2 CLASS=\"daterange\">$tmp2 - $tmp1</H2>\n";
	    }
	} else {
	    print "<H2 CLASS=\"daterange\">$tmp1</H2>\n";
	}
    }

    print "<TABLE WIDTH=\"100%\" CLASS=\"index\">\n";

    select(STDOUT);

}


######################################################################
#
# Bottom of HTML file
#
######################################################################
sub page_footer {

    my $time = localtime(time);

    my $progurl = 'http://www.edwinh.org/imageindex/';

    select(INDEX);

    print "</TABLE>\n";

    print "<DIV CLASS=\"credits\">";
    print "<I>page created on $time</I><BR>\n";
    print "by <A HREF=\"$progurl\">imageindex</A> ";
    print &versionstring();
#    print "<BR>\n";
#    print "<A HREF=\"http://www.edwinh.org/\">Edwin Huffstutler</A> <I>&lt;edwinh at computer dot org&gt;</I>";
#    print "<BR>\n";
#    print "<A HREF=\"http://www.reynoldsnet.org/\">John Reynolds</A> <I>&lt;johnjen at reynoldsnet dot org&gt;</I>";
    print "</DIV>\n";

    print "</BODY>\n</HTML>\n";

    select(STDOUT);
}


######################################################################
#
# A "quickie" routine to show which files were excluded in a prior run
#
######################################################################

sub showexcluded {
    my ($file) = @_;
    my ($rfile, $tmp, $utmp, @files, $str);

    if (! defined ($file)) {
	if (-r $indexfile) {
	    $rfile = $indexfile;
	}
    }
    else {
	$rfile = $file;
    }
    $tmp = &extract_meta_tag ($excludemetatag, $rfile);
    if (defined($tmp)) {
	# We need to "decode" this string as it has been encoded for storage
	# in the HTML so that raw filenames (that people should not try to
	# access) are not exposed to the outside world.
	#
	$utmp = &decodestring ($tmp);
	(@files) = split (/,/, $utmp);
	$str = join (',', @files);
	printf ("File '$rfile' shows the following record of excluded files:\n");
	printf ("%s\n", $str);
    }
    else {
	printf ("File '$rfile' shows no record of excluded files.\n");
    }
    return;
}

######################################################################
#
# Ignore certain files via META data stored in the index.html file
#
# Exports global variable %skipmont used later during montage
# generation.
#
######################################################################
sub exclude_files {

    my @files = @_;
    my (@filelist, $f, %exclude, $token, @tokens);

    undef %exclude;

    # -skipmont flags override any META data found. Else, look for the META tag
    # then process. Check to see if any of the -skipmont options were given as
    # strings of filenames concatenated with ',' characters. If so, support it.
    #
    if (defined (@opt_skipmont)) {
	foreach (@opt_skipmont) {
	    (@tokens) = split (/,/, $_);
	    foreach $token (@tokens) {
		$skipmont{$token}++;
	    }
	}
    }
    elsif (-r "$destdir/$indexfile") {
	my $tmp = &extract_meta_tag ($skipmetatag, "$destdir/$indexfile");
	if (defined($tmp)) {
	    (@opt_skipmont) = split (/,/, $tmp);
	    my $str = join (',', @opt_skipmont);
	    printf ("Using saved skip-montage files: %s\n", $str);
	    foreach (@opt_skipmont) {
		$skipmont{$_}++;
	    }
	}
    }

    # -exclude flags override any META data found. Else, look for the META tag
    # then process. Check to see if any of the -exclude options were given as
    # strings of filenames concatenated with ',' characters. If so, support it.
    #
    if (defined (@opt_exclude)) {
	# -includeall takes priority over -exclude on the commandline if they are
	# used together (wierd, but ...)
	#
	unless (defined ($opt_includeall)) {
	    foreach (@opt_exclude) {
		(@tokens) = split (/,/, $_);
		foreach $token (@tokens) {
		    $exclude{$token}++;
		}
	    }
	}
    }
    elsif (-r "$destdir/$indexfile") {
	my $tmp = &extract_meta_tag ($excludemetatag, "$destdir/$indexfile");
	my $utmp;
	if (defined($tmp) && !defined ($opt_includeall)) {
	    # We need to "decode" this string as it has been encoded for storage
	    # in the HTML so that raw filenames (that people should not try to
	    # access) are not exposed to the outside world.
	    #
	    $utmp = &decodestring ($tmp);
	    (@opt_exclude) = split (/,/, $utmp);
	    my $str = join (',', @opt_exclude);
	    printf ("Using saved excluded files: %s\n", $str);
	    foreach (@opt_exclude) {
		$exclude{$_}++;
	    }
	}
    }

    foreach $f (@files) {
	if (! $exclude{$f}) {
	    push (@filelist, $f);
	} else {
	    print "Excluding '$f'\n";
	    if (-d $f) {
		chmod (0700, $f);
	    }
	    else {
		chmod (0600, $f);
	    }
	}
    }
    return (@filelist);
}


######################################################################
#
# Nuke generated files if original image gone
#
######################################################################
sub nuke_out_of_date {
    foreach my $checkdir ($thumbnail_dir, $med_dir, $slide_dir) {
	opendir(THUMBS,"$destdir/$checkdir") || die "Can't open dir $checkdir: ($!)\n";
	foreach (readdir(THUMBS)) {
	    next if (m/^\.?\.$/);
	    next if (m/$framefile/);
	    next if (m/$slidefile/);
	    next if (m/$montagefile/);
	    next if (m/$emoticonsmile/);
	    next if (m/$emoticonwink/);
	    next if (m/$emoticonfrown/);
	    if (!defined($thumb_backref{"$checkdir/$_"}) and
		!defined($slide_backref{"$checkdir/$_"}) and
		!defined($med_backref{"$checkdir/$_"})) {
		print "Removing stale $destdir/$checkdir/$_\n";
		unlink("$destdir/$checkdir/$_") || warn "Can't unlink $destdir/$checkdir/$_: ($!)\n";
		$modified_thumb++;

	    }
	}
	closedir(THUMBS);
    }

}

######################################################################
#
# Convert bytes to kb string
#
######################################################################
sub convert_to_kb {

    my $bytes = shift(@_);
    if ($bytes > (1024 * 1024)) {
	$bytes = sprintf("%.1fM", $bytes / (1024.0 * 1024.0));
    } else {
	$bytes = sprintf("%dk", $bytes / 1024);
    }
    return($bytes);
}

######################################################################
#
# Sortq by integer date stamp
#
######################################################################
sub bynumber {
    if ($current_reverse == 0) {
	$a <=> $b;
    } else {
	$b <=> $a;
    }
}


######################################################################
#
# Write frameset file for slideshows
#
######################################################################
sub write_frameset {

    # This is impossible to get rid of
    my $framefudge = 35;
    my $verstring;

    open(FRAME,">$destdir/$slide_dir/$framefile") or die ("Can't open $destdir/$slide_dir/$framefile: $!\n");

    select(FRAME);

    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\"\n";
    print "\"http://www.w3.org/TR/html401/frameset.dtd\">\n";
    print "<HTML>\n";
    print "<HEAD>\n";
    $verstring = &versionstring();
    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">\n");
    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">";
    print "<TITLE>$current_titletext</TITLE>\n";
    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">\n";
    print "</HEAD>\n";
    if ($frame_orient eq 'horizontal') {
	printf("<FRAMESET ROWS=\"%d, *\">\n", $max_thumb_y + $framefudge);
    } else {
	printf("<FRAMESET COLS=\"%d, *\">\n", $max_thumb_x + $framefudge);
    }
    print "<FRAME NAME=\"thumb\" SRC=\"$slidefile\">\n";
    print "<FRAME NAME=\"view\" SRC=\"../$first_slide\">\n";
    print "<NOFRAMES>No frames in this browser...go back</NOFRAMES>\n";
    print "</FRAMESET>\n";
    print "</HTML>\n";

    select(STDOUT);
    close (FRAME);


    open(FRAME,">$destdir/$slide_dir/$slidefile") or die ("Can't open $destdir/$slide_dir/$slidefile: $!\n");
    select(FRAME);


    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n";
    print "\"http://www.w3.org/TR/html401/strict.dtd\">\n";
    print "<HTML>\n";
    print "<HEAD>\n";
    $verstring = &versionstring();
    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=ISO-8859-1\">\n");
    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">\n";
    print "<TITLE>$current_titletext</TITLE>\n";
    print "</HEAD>\n<BODY>\n";
    print "<TABLE CLASS=\"frame\"\n";
    print "  <TR>\n" if ($frame_orient eq 'horizontal');
    foreach (@frame) {
	print;
    }
    print "  </TR>\n" if ($frame_orient eq 'horizontal');
    print "</TABLE>\n";
    print "</BODY>\n</HTML>\n";

    select(STDOUT);
    close(FRAME);

}


######################################################################
#
# Do next/index/prev links on slide pages
#
######################################################################
sub next_prev_links {

    my $pathname = shift(@_);

    print "<DIV CLASS=\"index\">";

    if (defined($back{$pathname})) {
	print "<A HREF=\"$back{$pathname}\">&lt;&nbsp;previous</A>&nbsp;|&nbsp;";
    } else {
	print "&lt;&nbsp;previous&nbsp;|&nbsp;";
    }
    print "<A HREF=\"../$indexfile\">index</A>";
    if (defined($forward{$pathname})) {
	print "&nbsp;|&nbsp;<A HREF=\"$forward{$pathname}\">next&nbsp;&gt;</A>";
    } else {
	print "&nbsp;|&nbsp;next&nbsp;&gt;";
    }

    print "</DIV>\n";

}


######################################################################
#
# Lower-case all the filenames. I hate the uppercase filenames that come
# from my camera's default software (and Windud software). Plus I didn't
# want this "utility" in another script, so just place it here.
#
######################################################################
sub lower_case_files {
    my (@files) = @_;
    my ($newfile, $lowername);

    foreach $name (@files) {
	($lowername = $name) =~ tr/A-Z/a-z/;
	if ($name =~ /[A-Z]/) {
	    print "Moving '$name' to '$lowername'\n";
	    move("$name","$lowername");
	}
    }
}


######################################################################
#
# extract the NAME tag from an HTML file
#
######################################################################
sub extract_meta_tag {
    my ($tag, $filename) = @_;
    my ($name, $content, $retval);

    if (! (open (FILE, $filename))) {
	print STDERR "Cannot open '$filename' for reading - $!\n";
	return (0);
    }
    # <META NAME="Columns" CONTENT="3">
    #
    while (<FILE>) {
	if (/<META\s+NAME=\"(.*?)\"\s+CONTENT=\"(.*)\">/) {
	    $name = $1;
	    $content = $2;
	    if ($name eq $tag) {
		$retval = $content;
		last;
	    }
	}
    }
    close (FILE);
    return ($retval);
}


###############################################################################
#
# Rotate given image 90 degrees
#
###############################################################################
sub rotate_image {

    my $file = shift(@_);
    my $argv = shift(@_);

    if ($file =~ m/^(cw|ccw)$/) {
	# If file is cw or ccw,
	# assume the args were given backwards
	my $tmp = $file;
	$file = $$argv[0];
	$$argv[0] = $tmp;
    }

    -r "$file" || die("$file: ", $!);
    -w "$file" || die("$file: ", $!);


    # grab the mtime of the file so we can reset it after we update it
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
	$ctime,$blksize,$blocks) = stat($file);
    my $posix_mtime = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));

    my ($name,$path,$suffix) = fileparse($file,'\.\S+');
    my $thumb;
    my $medium;

    my $image = new Image::Magick;

    my $retval = $image->Read("$file");
    warn "$retval" if "$retval";

    if (!defined($$argv[0]) or
	($$argv[0] !~ m/^cc?w$/i)) {
	print "Need 'cw' or 'ccw' argument to rotate image clockwise/counterclockwise\n";
	exit(1);
    }

    if ($$argv[0] =~ /^ccw$/i) {
	$deg = -90;
    } else {
	$deg = 90;
    }

    print "Rotating $file $deg degrees\n";
    $retval = $image->Rotate($deg);
    warn "$retval" if "$retval";

    $retval = $image->Write(filename=>"$file");
    warn "$retval" if "$retval";

    system ("touch -t $posix_mtime $file");

    # Nuke the generated images if they exist
    # (touching the timestamp above breaks automatic regeneration logic)
    if ($image->Get('format') =~ /JFIF/i) {
	$thumb = $path . "$thumbnail_dir/$name" . $suffix;
	$medium = $path . "$med_dir/$name" . $suffix;
    } else {
	$thumb = $path . "$thumbnail_dir/$name.jpg";
	$medium = $path . "$med_dir/$name.jpg";
    }
    unlink($thumb) if (-e "$thumb");
    unlink($medium) if (-e "$medium");



}

###############################################################################
#
# Set or display caption for a particular image
#
###############################################################################
sub caption_image {

    my $file = shift(@_);
    my $argv = shift(@_);
    my ($esc_comment, $tmpfile);

    -r "$file" || die("$file: ", $!);

    # grab the mtime of the file so we can reset it after we update it
    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
	$ctime,$blksize,$blocks) = stat($file);
    my $posix_mtime = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));

    my $image = new Image::Magick;

    my $retval = $image->Read("$file");
    warn "$retval" if "$retval";

    my $format = $image->Get('format');
    warn "$retval" if "$retval";

    # Set caption if another arg is present, or just display it
    if (defined($$argv[0])) {

	-w "$file" || die("$file: ", $!);

	# Try to find wrjpgcom so we can use it for adding captions to JPG images
	my $wrjpgcom_prog = &find_in_path ('wrjpgcom');
	my $quote_file = quotemeta ($file);

	# If a jpeg file and we found a wrjpgcom program in our path, use
	# it! It simply puts the comment in the JPEG header without reading
	# (uncompressing) and writing (re-compressing) the file out so
	# there is no chance for data loss.
	#
	if (($format =~ /JFIF/i) and defined($wrjpgcom_prog)) {

	    $tmpfile = "$file.$$";
	    my $tmpfile_quote = "$quote_file.$$";
	    $esc_comment = quotemeta ($$argv[0]);
	    # FIXME
	    # check to see how '?' and other punctuation is escaped and fix
	    # it seems things are not correct.
	    system ("$wrjpgcom_prog -replace -comment $esc_comment $quote_file > $tmpfile_quote");
	    if (($? >> 8) != 0) {
		printf(STDERR "Error in creating JPEG comment with 'wrjpgcom'. Leaving existing file intact.\n");
	    } else {
		move($tmpfile, $file);
	    }

	} else {
	    # Fall back to PerlMagick's routines.
	    #
	    $retval = $image->Comment("$$argv[0]");
	    warn "$retval" if "$retval";

	    $retval = $image->Write(filename=>"$file", quality=>"95",
				    sampling_factor=>"1x1");
	    warn "$retval" if "$retval";
	}

	system ("touch -t $posix_mtime $quote_file");

    } else {

	my $text = $image->Get('comment');

	if (defined($text)) {
	    print "$file: \"$text\"\n";
	} else {
	    print "$file: (no caption)\n";
	}

    }

}


###############################################################################
#
# Print usage info from top of file
#
###############################################################################
sub usage {

    open(FILE,"$0") or die "Can't open $0: $OS_ERROR";
    while(<FILE>) {
	last if (m/^\#\s+USAGE:/);
    }
    while(<FILE>) {
	last if (m/^\#\#\#\#\#\#\#/);
	s/^\# ?//;
	print;
    }
    close(FILE);

}

######################################################################
#
# Format timestamp for HTML pages. This routine assumes that the date
# given to it is in YYYYMMDDHHMM.SS format that we've created from the
# EXIF/mtime date using strftime().
#
######################################################################
sub format_date {
    my ($date, $context, $dayonly) = @_;
    my ($timeformat, $dateformat);

    if ($context eq 'frame') {
	$timeformat = $frametimeformat;
	$dateformat = $framedateformat;
    }
    elsif ($context eq 'index') {
	$timeformat = $indextimeformat;
	$dateformat = $indexdateformat;
    }
    elsif ($context eq 'slide') {
	$timeformat = $slidetimeformat;
	$dateformat = $slidedateformat;
    }
    else {
	$timeformat = $detailtimeformat;
	$dateformat = $detaildateformat;
    }

    # Replace "macro" patterns in the format string first
    #
    $timeformat =~ s/\%R/\%H:\%M/g;
    $timeformat =~ s/\%r/\%I:\%M:\%S \%p/g;
    $dateformat =~ s/\%F/\%Y-\%m-\%d/g;
    $dateformat =~ s/\%D/\%m\/\%d\/\%y/g;

    $date =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)?(\d\d)?\.?(\d\d)?/;
    my $year = $1;
    my $month = $2;
    my $day = $3;
    my $hour = $4;
    my $min = $5;
    my $sec = $6;
    my ($ampm, $two_digit_year, $twelve_hour);

    if ($year =~ /^\d\d(\d\d)$/) {
	$two_digit_year = $1;
    }
    else {
	$two_digit_year = '??'; # shouldn't ever been seen
    }

    # If we're told to, only format a date with no time
    #
    if (defined ($dayonly)) {
	$dateformat =~ s/\%Y/$year/g;
	$dateformat =~ s/\%y/$two_digit_year/g;
	$dateformat =~ s/\%m/$month/g;
	$dateformat =~ s/\%d/$day/g;
	$dateformat =~ s/\%\%/\%/g;
	return ($dateformat);
    }
    else {
	if (defined($hour)) {
	    $twelve_hour = $hour;
	    $ampm = 'AM';
	    if ($hour >= 12) {
		$ampm = 'PM';
	    }
	    if ($hour > 12) {
		$twelve_hour -= 12;
	    }
	}
	else {
	    $hour = '??';
	    $twelve_hour = '??';
	    $ampm = '??'; #again, should never be seen
	}
	if (! defined ($min)) {
	    $min = '??';
	}
	if (! defined ($sec)) {
	    $sec = '??';
	}

	$dateformat =~ s/\%Y/$year/g;
	$dateformat =~ s/\%y/$two_digit_year/g;
	$dateformat =~ s/\%m/$month/g;
	$dateformat =~ s/\%d/$day/g;
	$dateformat =~ s/\%\%/\%/g;

	$timeformat =~ s/\%S/$sec/g;
	$timeformat =~ s/\%M/$min/g;
	$timeformat =~ s/\%I/$twelve_hour/g;
	$timeformat =~ s/\%H/$hour/g;
	$timeformat =~ s/\%p/$ampm/g;
	$timeformat =~ s/\%\%/\%/g;

	return("$dateformat $timeformat");
    }

}

######################################################################
#
# Return version string from CVS tag
#
######################################################################
sub versionstring {

    my $ver = ' $Name: v1_1 $ ';
    $ver =~ s/Name//g;
    $ver =~ s/[:\$]//g;
    $ver =~ s/\s+//g;
    $ver =~ s/^v//g;
    $ver =~ s/_/\./g;
    if ($ver eq '') {
	$ver = "cvs devel - " . '$Revision: 1.175 $ ';
	# Nuke the $ signs -- what if somebody is keeping pages under RCS
	# or CVS control?
	$ver =~ s/\$//g;
	$ver =~ s/\s*$//;
    }
    return($ver);

}

###############################################################################
#
#  Create CSS file that is shared among the HTML pages
#
###############################################################################
sub write_css {


    open(CSS,">$destdir/$stylefile") or die ("Can't open $destdir/$stylefile: $!\n");
    select(CSS);

    print $stylesheet;

    select(STDOUT);
    close(CSS);


}

###############################################################################
#
# "Interpolate" %? escapes found in our printf-like strings defined for the
# TITLE attributes. See the beginning of this file for their definition
#
###############################################################################

sub interpolate_title_string {
    my ($formatstring, $pathname, $context) = @_;
    my ($filename, $date, $size, $resolution, $caption);
    my ($tmp);

    $filename = $info{$pathname}{'file'};
    $date = &format_date ($info{$pathname}{'date'}, $context);
    $size = $info{$pathname}{'size'};
    $resolution = $info{$pathname}{'geometry'};
    $caption = $info{$pathname}{'comment'};
    if (! defined ($caption)) {
	$caption = '';
    }
    $tmp = $formatstring;

    $tmp =~ s/\%f/$filename/g if $filename;
    $tmp =~ s/\%d/$date/g if $date;
    $tmp =~ s/\%s/$size/g if $size;
    $tmp =~ s/\%r/$resolution/g if $resolution;
    $tmp =~ s/\%c/$caption/g;
    $tmp =~ s/\%\%/%/g;

    # In case the format string has " marks in it, change all those to '.
    # The " marks are needed to mark the argument to the TITLE attribute.
    #
    $tmp =~ s/\"/\'/g;
    return ($tmp);

}

###############################################################################
#
# "Interpolate" %? escapes found in our printf-like strings defined for the
# TITLE attributes. However, the %? escapes for this function are based on what
# you could conceivably need when processing a directory.
#
# See the beginning of this file for their definition
#
###############################################################################

sub interpolate_title_string_dir {
    my ($formatstring, $dir, $context) = @_;
    my ($tmp, $num, $date, $metadate, $metatitle);

    $tmp = $formatstring;
    $num = &extract_meta_tag($numimagesmetatag, "$srcdir/$dir/$indexfile");

    # If we plucked out the number of images from the metadata of a directory's
    # index.html file, replace it. Else, give a warning if we didn't find it but
    # somebody still used %n
    #
    if (defined ($num)) {
	$tmp =~ s/\%n/$num/g if $num;
    }
    else {
	if ($tmp =~ /\%n/) {
	    if (!defined ($remember_warning{$dir})) {
		printf (STDERR "Warning: %%n escape used in format string and %s META tag not found in %s. Re-run imageindex in '$dir'.\n", $numimagesmetatag, "$srcdir/$dir/$indexfile");
		$remember_warning{$dir}++;
	    }
	}
    }

    $metadate = &extract_meta_tag($begindatemetatag, "$srcdir/$dir/$indexfile");
    $date = &format_date ($metadate, $context, 'dayonly');
    $tmp =~ s/\%b/$date/g if $date;

    $metadate = &extract_meta_tag($enddatemetatag, "$srcdir/$dir/$indexfile");
    $date = &format_date ($metadate, $context, 'dayonly');
    $tmp =~ s/\%e/$date/g if $date;

    $metatitle = &extract_meta_tag($titlemetatag, "$srcdir/$dir/$indexfile");
    $tmp =~ s/\%t/$metatitle/g if $metatitle;

    # In case the format string has " marks in it, change all those to '.
    # The " marks are needed to mark the argument to the TITLE attribute.
    #
    $tmp =~ s/\"/\'/g;
    return ($tmp);

}

###############################################################################
#
#  Look for external programs we depend on in the $PATH. It just finds the first
#  occurence of $prog in $PATH.
#
###############################################################################
sub find_in_path {
    my ($prog) = @_;
    my ($retval);

    undef $retval;
    foreach $dir (split (/:/, $ENV{'PATH'})) {
	if (-r "$dir/$prog" && -x "$dir/$prog") {
	    $retval = "$dir/$prog";
	}
    }
    return ($retval);
}


###############################################################################
#
# Encode/decode routines for exclude filenames when stuffed in a meta tag
#
###############################################################################
sub encodestring {
    my ($tmp) = @_;
    my $etmp;
    $etmp = pack ("u*", $tmp);
    # Hack the string to get rid of \n chars so we can store it on 1 line
    $etmp =~ s/\n/..1xn!_ltr../g;

    # Get rid of ampersands
    $etmp =~ s/\&/..xn!_ltr1../g;

    # Get rid of double-quotes
    $etmp =~ s/\"/..sb!_lho1../g;
    return ($etmp);
}

sub decodestring {
    my ($tmp) = @_;
    my $utmp;

    # Unhack the string to bring back & characters
    $tmp =~ s/\.\.sb\!_lho1\.\./\"/g;
    $tmp =~ s/\.\.xn\!_ltr1\.\./\&/g;

    # Unhack the string to bring back & characters
    $tmp =~ s/\.\.xn\!_ltr1\.\./\&/g;

    # Unhack the string to bring back \n characters
    $tmp =~ s/\.\.1xn\!_ltr\.\./\n/g;
    $utmp = unpack ("u*", $tmp);
    return ($utmp);
}

#############################################################################
#
#  This routine samples linearly (as possible) across the available files in
#  a directory. The first pass at sampling is a simple modulo function based
#  upon the ratio of files to the number of tiles we can use in the montage.
#  If that first pass sample did not produce enough files, then we go back
#  iteratively through the list and as evenly-as-possible select unused
#  files from those left in the pool.
#
#############################################################################
sub sample_files_for_montage {
    my (@files) = @_;
    my ($numdiv, $numchosen, $chunksize, $numfiles, $numleft);
    my ($i, $index, $f, @ret);

    $numfiles = scalar (@files);
    $numdiv = sprintf ("%d", $numfiles / $montage_max);
    $numdiv++;

    for ($i = 0; $i < $numfiles; $i++) {
	if (($i % $numdiv) == 0) {
	    $chosen{$files[$i]}++;
	}
    }

    $numchosen = scalar (keys %chosen);

    $numleft = $montage_max - $numchosen;

    if ($numleft) {
	$chunksize = sprintf ("%d", $numfiles / $numleft);
	$index = 0;
	for ($i = 0; $i < $numleft; $i++) {
	    &mark_next_file_for_montage ($index + 1, $numfiles, @files);
	    $index = $index + $chunksize;
	}
    }

    foreach $f (@files) {
	if ($chosen{$f}) {
	    push (@ret, $f);
	}
    }

    return (@ret);
}

#############################################################################
#
# cycle through the given list of files. If the list[$index] is already marked
# (via the global hash %chosen) then move onto the next one, etc.
#
#############################################################################
sub mark_next_file_for_montage {
    my ($index, $numfiles, @files) = @_;
    my ($i);

    for ($i = $index; $i < $numfiles; $i++) {
	if (! $chosen{$files[$i]}) {
	    $chosen{$files[$i]}++;
	    last;
	}
    }
}

###############################################################################
#
# Exclude certain filenames from the list of thumbnails to be used in the
# montage image.
#
###############################################################################
sub exclude_montage_files {
    my (@files) = @_;
    my (@tmp, $file);

    foreach (@files) {
	$file = basename ($_);
	unless (defined ($skipmont{$file})) {
	    push (@tmp, $_);
	}
    }
    return (@tmp);
}

###############################################################################
#
# "html-ize" a caption found in an image. Just in case there are certain
# characters used which we want to "escape."
#
###############################################################################
sub htmlize_caption {
    my ($caption, $slide) = @_;

    $caption =~ s/\&/\&amp;/g;
    $caption =~ s/\</\&lt;/g;
    $caption =~ s/\>/\&gt;/g;
    $caption =~ s/\"/\&quot;/g;

    # Help smiley's render in a "mo-better" way when they are at the end of a
    # caption and enclosed in parens
    #
    if ($caption =~ /(:\-?[\(\)])\s*\)\s*$/) {
	my $tmp = $1;
	$caption =~ s/:\-?[\(\)]\s*\)\s*$/$tmp\&nbsp;\)/;
    }

    $caption = &emoticonify ($caption, $slide);

    return ($caption);

}

###############################################################################
#
# Translate ASCII smiley's embedded into image captions into emoticons
#
###############################################################################
sub emoticonify {
    my ($caption, $slide) = @_;
    my ($thumbdir, $attr);

    return ($caption) if (! defined ($do_emoticons) || $do_emoticons == 0);

    # This is a hack, please ignore and move on ... nothing to see here.
    #
    $caption =~ s/\&nbsp;/NoNBrEaKaBleSpacE/g;

    $thumbdir = $thumbnail_dir;
    if ($slide) {
	$thumbdir = '../' . $thumbdir;
    }
    $attr = 'STYLE="vertical-align: middle;" WIDTH="19" HEIGHT="19"';

    if ($caption =~ s/:\-?\)/\<IMG SRC=\"$thumbdir\/$emoticonsmile\" $attr ALT=\" \[smiley icon\] \"\>/g) {
	$emoticon{'smile'}++;
    }
    if ($caption =~ s/;\-?\)/\<IMG SRC=\"$thumbdir\/$emoticonwink\" $attr ALT=\" \[smiley icon\] \"\>/g) {
	$emoticon{'wink'}++;
    }
    if ($caption =~ s/:\-?\(/\<IMG SRC=\"$thumbdir\/$emoticonfrown\" $attr ALT=\" \[frown icon\] \"\>/g) {
	$emoticon{'frown'}++;
    }

    # Undo the hack
    #
    $caption =~ s/NoNBrEaKaBleSpacE/\&nbsp;/g;
    return ($caption);

}

###############################################################################
#
# Write out PNG files representing the emoticons
#
###############################################################################
sub write_emoticon_png {
    my ($type) = @_;
    my ($img);

    if (! open (IMG, '>' . $destdir . '/' . $thumbnail_dir . "/$emoticonprefix" . $icon . ".png")) {
	printf (STDERR "Could not open emoticon file for '$icon' for writing - $!\n");
	return;
    }
    # UUDecode the small PNG files that represent the emoticons and dump them to
    # the appropriate files in $thumbnail_dir
    #
    $img = unpack ("u*", $png{$type});
    print IMG $img;
    close (IMG);
}

###############################################################################
#
# Write out PNG files representing the emoticons
#
###############################################################################
sub write_video_icons {
    my ($tmp_jpg_dir) = @_;
    my ($img);

    foreach $key (keys %png) {
	next if $key !~ /video/;
	if (! open (IMG, '>' . $tmp_jpg_dir . '/' . $key . ".png")) {
	    printf (STDERR "Could not open video icon file for '$key' for writing - $!\n");
	    return;
	}
	# UUDecode the small PNG files that represent the emoticons and dump them to
	# the appropriate files in $thumbnail_dir
	#
	$img = unpack ("u*", $png{$key});
	print IMG $img;
	$max_video_icons++;
	close (IMG);
    }
}

###############################################################################
#
# Create a montage of images in the current directory. This image will be
# pointed to by the parent directory's index.html file to show a sort of
# "thumbnail preview" of the contents of this directory.
#
###############################################################################

sub create_montage {

    my @files = @_;
    my (@modfiles);

    @files = &exclude_montage_files (@files);

    foreach (@files) {
	push (@modfiles, quotemeta ($_));
    }

    # If we have defined that a lesser number of "tiles" can be used in the
    # montage vs. the # of files in this directory, then we'll "sample" the
    # files as evenly as possible to avoid clustering of shots that might be
    # similar to each other.
    #
    if (scalar (@modfiles) > $montage_max) {
	@modfiles = &sample_files_for_montage (@modfiles);
    }

    if ($do_montage == 1) {

	if (($modified_thumb != 0) or (! -e "$destdir/$thumbnail_dir/$montagefile")) {

	    my $number = $#modfiles + 1;
	    my $tile_x = 1;;
	    my $tile_y = 1;

	    # FIXME these both blindly expand x before expanding y
	    # Should this depend on some aspect ratio?
	    while(($tile_x * $tile_y) < $montage_min) {
		$tile_x++;
		$tile_y++ if (($tile_x * $tile_y) < $montage_min);
	    }
	    while(($tile_x * $tile_y) < $number) {
		$tile_x++;
		$tile_y++ if (($tile_x * $tile_y) < $number);
	    }

	    my $index = 0;
	    while (($#modfiles + 1) < ($tile_x * $tile_y)) {
		if ($montage_fill eq 'blank') {
		    push(@modfiles, "NULL:");
		} else {
		    push(@modfiles, $modfiles[$index]);
		    $index = ($index+1) % $number;

		}
	    }

	    my $tile = sprintf("%dx%d", $tile_x, $tile_y);
	    my $geom = sprintf("%dx%d", $max_mont_thumb_x, $max_mont_thumb_y);
	    my $newgeom = sprintf("%dx%d", $current_thumbnail_x, $current_thumbnail_y);

	    print "Picked $tile array of $geom for montage\n" if ($opt_debug);

	    print "Creating $destdir/$thumbnail_dir/$montagefile\n";

	    system("montage -quality $thumb_quality -bordercolor white -transparent white -border $montage_whitespace -geometry $geom -tile $tile @modfiles \"$destdir/$thumbnail_dir/$montagefile\"");
	    if (($? >> 8) != 0) {
		printf(STDERR "Error in creating montage file\n");
		return(-1);
	    }

	    # Resize to std. thumbnail
	    my $image = new Image::Magick;
	    my $retval;

	    $retval = $image->Read(filename=>"$destdir/$thumbnail_dir/$montagefile");
	    warn "$retval" if "$retval";
	    $retval = $image->Resize(geometry=>$newgeom);
	    warn "$retval" if "$retval";
	    $retval = $image->Set(interlace=>Line);
	    warn "$retval" if "$retval";
	    $retval = $image->Write(filename=>"$destdir/$thumbnail_dir/$montagefile");
	    warn "$retval" if "$retval";

	}

    } else {

	unlink("$destdir/$thumbnail_dir/$montagefile")
	    if (-e "$destdir/$thumbnail_dir/$montagefile");

    }

}

sub read_stored_meta_data {
    my ($tmp);

    if (-r "$destdir/$indexfile") {
	$tmp = &extract_meta_tag ($columnsmetatag, "$destdir/$indexfile");
	# If we found data, check it against program defaults
	if (defined ($tmp)) {
	    $current_columns = $tmp;
	    print "Using saved number of columns: $current_columns\n" if ! defined ($opt_columns);
	}

	$tmp = &extract_meta_tag ($titlemetatag, "$destdir/$indexfile");
	# If we found data, check it against program defaults
	if (defined ($tmp)) {
	    $current_titletext = $tmp;
	    print "Using saved title: $current_titletext\n" if ! defined ($opt_title);
	}

	$tmp = &extract_meta_tag ($thumbxmetatag, "$destdir/$indexfile");
	# If we found data, check it against program defaults
	if (defined ($tmp)) {
	    $current_thumbnail_x = $tmp;
	    print "Using saved thumbnail X size: $current_thumbnail_x\n" if ! defined ($opt_x);
	}

	$tmp = &extract_meta_tag ($thumbymetatag, "$destdir/$indexfile");
	# If we found data, check it against program defaults
	if (defined ($tmp)) {
	    $current_thumbnail_y = $tmp;
	    print "Using saved thumbnail Y size: $current_thumbnail_y\n" if ! defined ($opt_y);
	}

	$tmp = &extract_meta_tag ($reversemetatag, "$destdir/$indexfile");
	# If we found data, check it against program defaults
	if (defined ($tmp)) {
	    $current_reverse = $tmp;
	    print "Using saved reverse: $current_reverse\n" if ! defined ($opt_reverse);
	}

	&decide_which_md_to_store();
    }
}

sub override_by_commandline {
    if (defined($opt_columns)) {
	$current_columns = $opt_columns;
    }
    if (defined($opt_title)) {
	$current_titletext = $opt_title;
    }
    if (defined($opt_reverse)) {
	$current_reverse = $opt_reverse;
    }
    if (defined($opt_x)) {
	$current_thumbnail_x = $opt_x;
	if ($current_thumbnail_x != $default_thumbnail_x) {
	    $opt_forceregen = 1;
	}
    }
    if (defined($opt_y)) {
	$current_thumbnail_y = $opt_y;
	if ($current_thumbnail_y != $default_thumbnail_y) {
	    $opt_forceregen = 1;
	}
    }
    &decide_which_md_to_store();
}

sub decide_which_md_to_store {
    if ($current_columns != $default_columns) {
	$write_meta_tag{$columnsmetatag}++;
    }
    else {
	undef $write_meta_tag{$columnsmetatag};
    }

    if ($current_thumbnail_x != $default_thumbnail_x) {
	$write_meta_tag{$thumbxmetatag}++;
    }
    else {
	undef $write_meta_tag{$thumbxmetatag};
    }

    if ($current_thumbnail_y != $default_thumbnail_y) {
	$write_meta_tag{$thumbymetatag}++;
    }
    else {
	undef $write_meta_tag{$thumbymetatag};
    }

    if ($current_titletext ne $default_titletext) {
	$write_meta_tag{$titlemetatag}++;
    }
    else {
	undef $write_meta_tag{$titlemetatag};
    }

    if ($current_reverse ne $do_reverse) {
	$write_meta_tag{$reversemetatag}++;
    }
    else {
	undef $write_meta_tag{$reversemetatag};
    }
}

sub initialize_current_vars {
    $current_columns = $default_columns;
    $current_titletext = $default_titletext;
    $current_thumbnail_x = $default_thumbnail_x;
    $current_thumbnail_y = $default_thumbnail_y;
    $current_reverse = $do_reverse;
}

##############################################################################
#
# Just initialize the 'png' array with UUENCODED PNG files for emoticons. This
# was placed down here so as not to clutter up the top of the file where the
# other globals are initialized.
#
##############################################################################
sub init_png_array {

$png{'wink'} = <<'EOF';
MB5!.1PT*&@H````-24A$4@```!,````3!`,```"`?BO_````$E!,5$7____,
MS``S,P#___\```#__P!/FRMM`````7123E,`0.;89@````%B2T=$!?AOZ<<`
M``!F241!5'C:;8_1#8`P"$3O@Q'L!CJ!#M`FQP`FL/\J%FJ-)O+U<H&7`P!(
M5N2PN"_)TJESH'I.G6'&XFNB`<UV=5,_*]0.->:R.N?=+?A#36P6)H9!(F)Z
CI1BYC1+=-K2?9J^^SQ<7J48K([9O4:P`````245.1*Y"8((`
`
EOF

$png{'smile'} = <<'EOF';
MB5!.1PT*&@H````-24A$4@```!,````3!`,```"`?BO_````$E!,5$7__P#,
MS`!F9@#_,P````#___]]YKD%````!G123E/______P"SOZ2_`````6)+1T0%
M^&_IQP```&U)1$%4>-I-C\$-P"`,`UV)`?I@@#ZZ03M`*L&?!]E_E=H$(5`>
MEP39#MR]E%+=&T@GD*NPD\A"PWBU@4,VADQ$*J,:OHF'<14(BX]14R#0%J1+
M>!.I(&,I=(!Q3+IT2\\+N2D#A\JP)]ORKBM^`[0;1*VK3]P`````245.1*Y"
"8((`
`
EOF

$png{'frown'} = <<'EOF';
MB5!.1PT*&@H````-24A$4@```!,````3!`,```"`?BO_````'E!,5$7____,
MS`"9F0!F9@`S,P#,S#/___\S,S,```#__P`[/ZS;`````7123E,`0.;89@``
M``%B2T=$"?'9I>P```"$241!5'C:78^Q#<,P#`19:@1I`WN!`%G`@`<(8"T0
M>`.GE-R8*EV%OVU(24YA@L7A^>"31$3,G*@6!\!7=D$@\(8%!=K)Q&/#]U-4
M=AC>\;&.0I0A:YQVG$FM)\<E`X9X`F/'5A4UK304G0Z&&3->$;-N<-TJEM;0
@UQOZ@DOVMWO_7_P`Y9]*.M\PG><`````245.1*Y"8((`
`
EOF

$png{'video_icon1'} = <<'EOF';
MB5!.1PT*&@H````-24A$4@```!D````="`8```!?>,=U````!F)+1T0`_P#_
M`/^@O:>3````"7!(67,```!(````2`!&R6L^```&$4E$051(Q[V6WXM=5Q7'
M/VN?<^Z],YUDIDF3W#-ITJ9B6S'X$D-2"A6*6,6*A3XI2'WJ@P\JH@511'T3
M6Z$//O<_L(J"H'UH06B)4%O0MEIIDR;IF>F0=I*Y]_S8>Z^U?+CYJ0WVJ0L.
M'-B'\]G[N]9>WP4?0\C-%IJF"4%R>6!Z*-[LF\V-MPOW$4Y1@&M=U_J1($W3
M%"+*='J[7KQP*A2RLRK$PV[YB.`'7*0$B;@)C.>#'3B5;>UM\Y'6];K_7TC3
M-!(D2A6V9!3>.UF$X>LAV,D0..R:UR"7IHH@!!',3;.-SB7;]TSO=SWI++5U
M/?6;0IJF*8)DGY2;HU$X^SVQV7=%9%\(!1`0"4A1D',F6,1T`(]8;HG#I!VJ
MX[_4\N!3$+JZKO/UD'#=NU72KDR*K1^/*O^)%*-]A0@A5(1J%RX37$%01")N
M$=,(>4;I&\M5?.7[(;WS+3R'IFGD?R!-TT@(.8RJ^0]$_`E7QH(A18$4RTA1
M(H5@&.**NN$`GB&4B!A%>N.6T>R/3X3AGU_!;\Q_:)IF%9`BQ).C:O9X**U2
M5Y``Q0KJCND<TQ[7'DT]EGK0%E*'QP%WHPA"9<V>:O:G'X5XYIX;("+2!;&J
ME$N/"Q?WNW:X)0("8@@]ECLDS_$\PZT#[2%U6.S)W4#NY^34HJE%VC>/%A>?
M^^'&V==6KD(<ST7H/S>6\U_U?`DL$4+`0TG.$4T=%CLTS7";(]8M`*G%8H?8
M'-(<B3NX1K"N"MV;CTA\]]@52!D8O.#]SZ/;JV:1HEHF%`'5B%NBL!ZS@:Z;
ML;5U@;5E86ELF$;$$V8#9CUB/9X'4$6XL+N<O7@<>`$@%'IQ=QE/'\6V(8`1
M4#7$$J06U1:1R(6M;7[WAU=X_?6SI*''+:':X3:`#5B."QES#WF.Q#.W7JLN
MB:LBLUU8)LB8`(L=>L1=<<U@F1P3Y\YO\]:9#QC:1.$9<2-X1BX_F.)F&(*;
M7KTKI3C1DT?%801!$L&&Q8YT6%00"6S@7'.)O[YZEB(D'CRQE^6Q(GG`?0#-
MBT)(<5'2Y?QJXDLW-7S04`[@<RP)KOU"AM1CJ<-SQ&//,"1>_OL63S_S-PK[
M)`\>7V,RRGA.>!S(<8Z;XJ($'>RJ7.:63/.`*N0.T@X^[$!L\=PM@-+A/D=0
M<G;^??H2+[VRP<YLAN>.U,[(PPYN+05ST!VRSO6:7*%TBHD).V"ZD"=WN.=%
M8C52A8CF#I',WK62SWYJA2^>V,WN,I/:B.>>@A[U#O6$FR`N[56(AKJ-(S]3
MV$L4WN(ZH+''B8A%+/=HR*A&QB/G@6-K?//A*<?NGE!(AWL"&5`;\)S`#*DF
MJ9CL>^VJ7-/U0Q%9_JVF8F9#0LB$TA#/6$ZX&:Z9(,:MNRKN/KS,X6E%56;`
M,$NX)=PSN(,K4N[]P/=\^5\;[[XC`.7FYOD0Y(.79;3VG+7;7W*-8[,!3P-F
M'=B`6V2ERGSAQ"IW[@O<MLM(<5%5KG&12^_!#9$2&:^_;)-/O.5A<CGQ)NS=
M?W0[RCU/(K)IOFB`P3K$+O>HU+,RCIRXM^+0;8[X@%G$M87<@J7%*2BP<$N.
MY<%GIT=.SNKI?@<(=;UN3;,A:I,WLE:G0RA!,FH=GGI(`];/\7[.2!)B<UQG
M8#N0YPL`!I0@`5DZ\&+8__"S'V):CA6K[Z>E^WYJH6X(%:X#:(M=Z:ZTF'>H
MS?&T#:E=_)OKO$.J"TR._&S]WD>WFHWWY`9(7==>KQ]V'=WU0C]YX!=6'MK&
MP..<'&>8]:BVN,WQ',$$/%QV;P,?`(VL?.8IF3[V/,`5J?[;?JGK@V;5H5_G
MI?L?\]&=KW+9%8M@BSODF87K^>($KB`E,KG]8EB][^<<^O;3TWL>T:;9N,%^
M;SIWO??VG^\NNK]\Q[O37]-+S:T^>PW/LVNPXA8H]T26CIP*Z]_X5;%\Q^_S
MZ+!.U^^X^;1R?31-(W5=^^;Y?Y1B_8/>;SX4WO_-'IV_5;@R1ML9XX/;['_H
M%,N??KX^<O_FYL9&,'?JNK:/"KF2*VN:S0)R*?D"KMU"*5<(8V&T%\*2X![K
=>OJAT^/'%O\!>',9BLN`3D\`````245.1*Y"8((`
`
EOF

$png{'video_icon2'} = <<'EOF';
MB5!.1PT*&@H````-24A$4@```"0````D"`,```#6WFBJ```!LU!,5$4````<
M&!(F#0\L$@PP(AA((QY6+BAD.C9S0C8L&DLN'5TL(4,[)%@F*FP])VX[,VE(
M,VE(.71G1D)T5DUX6UAQ9EE:27:$@701,I`Y)H4W,X0_,I4<.*8</;0G.JDQ
M.:P[.;5'*H5+-(M%,951.YI*+J%&.*M2-J9)-[E7/+T>1:LL0:L\4:PH1[,U
M2+<]6;Y&08M318U#2Y)=49%B68QS:I1%1JM41ZM;6J9'1;)72+5#4[A75+Q=
M:+)J:*A@:K)./<M9/L17/=DL4<1$2L=:1L984L)<1M=<4M=A2<YG5LEA2]EG
M5-M'8\5;<LA.;-5===5E:\-E><ET>\=A8-1L>=1W=]!>1NIC3>IF5N=K5/%B
M9>A^;.]]?N!L:?MN?/5]B+1H@<YYA<1IA-)VA]9VEMI?I-Y^J-]F@>EXF.%X
MK^Y_ONQ\M_%_P>^&D(F%HI2&LJ*'P:^!E,>$F-F4DMB&MLR(H]R"E>N"JN:#
MMN>%J_.&MO*TM.2&RL.&Q]2(U]J,XMZ6XMR&Q>R(U.R6WN.%R/:'V/><V/.@
MUOR(Y?:.\O:5_/RHY_*D_?G,V_`,`@3___]7$.SF````D7123E/_________
M____________________________________________________________
M____________________________________________________________
M____________________________________________________________
M__\`,OEAFP````%B2T=$D'@*C@P````)<$A9<P```&(```!B`(^5@48````)
M=G!!9P```"0````D`'@'&X\```.E241!5#C+;93]0]I&&,?3JM6YRD3F"A,0
M8AF$F=IUFQ:*N@1S`1+95FI!W!Q4,EHP>Z$;PV80&LC!G6SP)^\"UFK7[]T/
ME\LGS_-<GGL>:O2.#A,)&1Q<WZ.N/HBR*,OYG'P$B-X/$4+*BWE+V40";('_
M0X?R43[.NYUN,D/.]40^]]88=>DIG]KSNCV;GG669=U.IX_/9:/U:Y`L9Z/.
ML'/CZ5]U,DJ[,=;C9K+9"Y?4A1W9%V;CB@9[:##H=HSZMW'6[4MD)RZI"9-@
MV4>[+V%O,.CW!_TN[IT]VU]?#8D36Q8DR9(0WDB7.SV(^H0;=/L]:);V-\(^
M,0]*$T@^W/1&]A7#T"'A.JB+=60:Y4P:A'EQ+SJ&4H\%[T:ZV#!,J'<[`X@@
MTO66H2G%@[B7$:-_6%!2C+#IDYIA:%#'&`]>(]Q%J&,:RDEZW2?D`(%D272S
MWREG!H00X=\<<TM$CC_UEEE6BK%O1%'\FQK]&/5Z]ZOEEJ%"%6''G5:SV?S=
M\6L+FF9%V0Z)N<VGU.@).?[/U;$AC)MSO[2;3;UYYV-=[QCE<C'"\+DM:I3R
M>NZ5E"K46_B\.CTS-1P.;PYGII9T9+RJO-A>%<5C*I?XW+-?,LVV8UAHW[AQ
M<WBAZ=D>JIXVTF$N;T%>;Z*BJ>WEH6-Y[I(A%&[!BO(\XN,!)?[@]MX[M4(>
M7B$LZ#6$6OED+R`!2CADW0+)+)X;7M?T.83FV>.(B^.IT?>`!B].$5X:%IJW
M9L9ACS751K!5.5@00D?4Z!'P?Z:8$!=F_SEWW+(.9V$SLP6$H/D*^),,H$9Q
ML+9RH))\M+':_O`RKF6$$=2>/0PP)/"1$%U;>0)5%:MDMT-22P91FW@SM>)*
MB.>.K=S=]3-UDWQH8>0-20_&*B)+M?)P-<B`?\DMD`3Z;LF`"!%C*E)-DAYB
MU^*?[ZZYN)QL714YN44_*)#<C2EB":F3:61H6RB5/Y[<3"'H_[*F01*8BDTK
M.,N9:I8_672EI..+0DA&Z4_O*V?DYXT'A%6R-&K^11?'_/2F6F0A22_8=TH5
M0S,TTX2&J6J-K^TV.R<PX++NY*1`VQ?H[5JCKFF52KE1VZ%M-COCXQ)7*E@2
MA.!'MVV+][_*9#([.P]<MVWV`",P\K5>($O1+^A%OVW^`TOS\RY[@).V4N]T
M%0#D:##@\MML?I?=%0PP63[YIJU<Z4\`2#$NRC'!32Z4E*38R_=W.@'$>%)A
=$L^#&,B\W?\/+ROZRK9):IT`````245.1*Y"8((`
`
EOF

}
