1#!/usr/bin/perl -w
2#
3# Edwin Huffstutler <edwinh@computer.org>
4# John Reynolds <johnjen@reynoldsnet.org>
5#
6# perl script for: web page index/thumbnails of photos.
7# orginally started life as a menu selector for fvwm2 backgrounds...
8#
9# USAGE:
10#
11#  imageindex [options] <directory>
12#
13#  <directory> is assumed to be "." if not given
14#
15#  Options: (can be abbreviated if unique)
16#
17#    -title <string>  title for this page (saved for susbsequent runs)
18#    -destdir <dir>   export image/html tree to dir (will be created if needed)
19#    -[no]recurse     enable/disable recursion into subdirectories
20#    -[no]medium      enable/disable medium size images/links
21#    -[no]slide       enable/disable slideshow files
22#    -[no]detail      enable/disable detail file
23#    -[no]dirs        enable/disable directory entries
24#    -[no]montage     enable/disable directory montages
25#    -forceregen      force regeneration of thumbnails
26#    -columns <num>   number of columns in html table (saved for susbsequent runs)
27#    -exclude <file>  Exclude <file> from processing. Can be used multiple times
28#    -includeall      Nullifies excluded file list (saved from previous run)
29#    -skipmont <file> Exclude <file> from being included in a directory montage.
30#    -charset <str>   Use <str> instead of default 'ISO-8859-1'
31#    -reverse         Order timestamps with newest first
32#    -x <num>         override thumbnail x size
33#    -y <num>         override thumbnail y size
34#    -help            show this text
35#    -version         show the current version
36#    -d 'var=val'     force override of global variable
37#
38#  See also the configuration section at the top of the program itself,
39#  or in ~/.imageindexrc
40#
41# (non-html-generating, utility options)
42#
43#    -lowercase               Lowercase all image files in a directory
44#    -caption <file> <string> Store comment in image
45#    -timestamp <file> <date> Set timestamp in file (and mtime)
46#    -rotate <file> [cw|ccw]  Rotate an image clockwise or counterclockwise
47#    -showexcluded            Show which files were excluded in a prior run
48#
49######################################################################
50
51#
52# Configuration options
53#
54
55# sizes / dirs
56$thumbnail_dir = 'thumbnail';
57$default_thumbnail_x = 200;
58$default_thumbnail_y = 200;
59
60# If both dimensions of the original are within this much of the thumb
61# dimensions we will skip the thumbnail and just use the original
62$thumbnail_threshold = 1.0;
63
64# tesla
65
66$med_x = 800;
67$med_y = 600;
68$med_dir = 'medium';
69
70# If both dimensions of the original are within this much of the "medium"
71# dimensions we will skip creating the medium-size format and just use the
72# original
73$med_threshold = 1.6;
74
75# Enable/disable features, set default for various flags
76$do_recurse = 0;                      # Recurse into subdirs?
77$do_medium = 1;                       # Generate medium-format?
78$do_slide = 1;                        # Generate slides/frame view?
79$do_detail = 1;                       # Generate details page?
80$do_dirs = 1;                         # Create directory entries?
81$do_captions = 1;                     # Use caption info stored in images?
82$do_montage = 1;                      # Create directory montages?
83$do_emoticons = 1;                    # Replace ASCII smiley's with images?
84$do_reverse = 0;                      # Sort timestamps in reverse order?
85$do_video_files = 1;                  # process video files with mplayer?
86$do_video_thumbnail_icons = 1;        # Annotate movie icon onto thumbnails of video files
87
88# video file options
89
90# Brute force way of filtering out any "movie" files (mpg, mp4, avi,
91# mov, etc.) which might lurk in a directory
92#
93$video_regexp = '(3gp|avi|mov|mpg|mpeg|mjpeg|m1v|m2v|wmv|fli|nuv|vob|ogm|vcd|svcd|mp4|qt|flv)';
94
95# Control which corner video icons are overlayed onto thumbnails of
96# of video files. Values can be: SouthWest, NorthWest, NorthEast, SouthEast
97$video_icon_gravity = 'SouthWest';
98
99# Control which video "icon" is overlayed on top of thumbnails of video files
100# 1 = yellow dot with "play" arrow
101# 2 = purplish icon of video camera
102$video_icon = 1;
103
104# end video file options
105
106$max_video_icons = 0;      # do not adjust, this is just a global variable
107
108# What the various image links point to - can be 'index', 'fullsize',
109# 'medium', 'thumbnail', 'slide', or 'details'
110$index_linkto = 'slide';
111$details_linkto = 'index';
112$slide_linkto = 'fullsize';
113
114# Default number of columns to use
115$default_columns = 3;
116
117# Orientation of slide frame - 'horizontal' or 'vertical'
118$frame_orient = 'vertical';
119
120# Location of items in slide pages; 'top', 'bottom', or 'none'
121$slide_caption = 'top';
122$slide_date = 'bottom';
123
124# Details index uses thumbs reduced by this amount
125$detailshrink = 2;
126
127# Quality for generated images
128$thumb_quality = 50;
129$med_quality = 80;
130
131# Minimum and maximum number of tiles in directory montage images
132$montage_min = 4;
133$montage_max = 36;
134
135# Space between montage images
136$montage_whitespace = 2;
137
138# What to do with leftover montage tiles; can be
139# 'blank' or 'repeat'
140$montage_fill = 'blank';
141
142# Default charset is ISO-8859-1
143$charset = 'ISO-8859-1';
144
145# Stylesheet specs
146# Set element font, etc. properties here
147$stylesheet = '
148body { color: black; background: white; }
149
150/* Fonts in the title */
151h1.title { font-family: "Comic Sans MS",Helvetica,sans-serif; font-size: 200%; font-weight: bold; text-align: center; }
152h2.daterange { font-family: Arial,Helvetica,sans-serif; font-size: 125%; text-align: center; }
153h3 { font-family: Arial,Helvetica,sans-serif; font-size: 90%; text-align: center; }
154
155/* Photo captions & Directory titles */
156div.caption { font-family: Arial,Helvetica,sans-serif; font-size: 100%; font-weight: bold; margin: 1em; }
157
158/* Overall fonts on the index and details page */
159div.index { font-family: Arial,Helvetica,sans-serif; font-size: 80%; }
160div.detail { font-family: Arial,Helvetica,sans-serif; font-size: 80%; }
161div.credits { font-family: Arial,Helvetica,sans-serif; font-size: 80%; text-align: right; margin: 10px }
162
163/* Table attributes */
164table.index { background: #ffffff; border: none; border-spacing: 8px; }
165td.index { border: none; padding: 3px }
166table.frame { background: #ffffff; border: none }
167td.frame { border: none; padding: 0px }
168
169/* Image attributes */
170img.index { border: none; }
171img.slide { border: none; }
172img.frame { border: none; }
173
174/* Link attributes */
175a:link { color: blue; }
176a:visited { color: green; }
177a:hover { color: red; }
178a:active { color: red; }
179
180';
181
182
183# Text
184$emptycell          = "<I>empty</I>";
185$updirtext          = "up one directory";
186$framelinktext      = "slideshow view (frames)";
187$detaillinktext     = "details index";
188$indexlinktext      = "main index";
189$default_titletext  = "Image directory";
190
191# These five variables control the TITLE attribute on anchor constructs in the
192# index and frame views. When TITLE attributes are given they are usually
193# rendered as "tooltip" bubbles that show text when a cursor hovers and stops
194# over the active link. We use them here to give a visual cue about the image.
195# These variables work much like printf(1) strings.
196#
197#   %f => replaced with the filename of the image
198#   %d => replaced with the date/time of the image (or mtime of the file)
199#   %s => replaced with the size of the file (in Kb)
200#   %r => replaced with the resolution (XxY) of the original image
201#   %c => replaced with the image's caption (if stored with one)
202#   %% => replaced with a literal '%' character
203#
204# The following are used when directories are processed and a montage of
205# that directory is used as the thumbnail of the dir.
206#
207#   %n => replaced with number of images in a directory
208#   %b => replaced with the "begin" date from a directory of images
209#   %e => replaced with the "end" date from a directory of images
210#   %t => replaced with the "title" from a directory of images
211#
212# Other characters (including spaces) are literal. "undef" these in
213# your ~/.imageindexrc file if you don't want them to show up. The "date/time"
214# related constructs are interpolated using the date/time format variables
215# defined below.
216#
217$framethumbtitle  = "%f - %d";
218$indexthumbtitle  = "%f (%s)";
219$slidethumbtitle  = "%f (%s)";
220$detailthumbtitle = "%c";
221$montagetitle     = "%n images %b through %e";
222
223# Date/Time format strings. These strings are formatted much like the above
224# variables and the definitions of the escape sequences come from the POSIX
225# strftime(3) definitions. NOT ALL of strftime(3) are supported for obvious
226# reasons.
227#
228#    %S    is replaced by the second as a decimal number (00-60).
229#    %M    is replaced by the minute as a decimal number (00-59).
230#    %I    is replaced by the hour (12-hour clock) as a decimal number (01-12).
231#    %H    is replaced by the hour (24-hour clock) as a decimal number (00-23).
232#    %p    is replaced by national representation of either "ante meridiem" or
233#          "post meridiem" as appropriate (currently only U.S. "am" or "pm")
234#    %R    is equivalent to "%H:%M" (in *timeformat variables only).
235#    %r    is equivalent to "%I:%M:%S %p" (in *timeformat variables only).
236#
237#    %Y    is replaced by the year with century as a decimal number.
238#    %y    is replaced by the year without century as a decimal number (00-99).
239#    %m    is replaced by the month as a decimal number (01-12).
240#    %d    is replaced by the day of the month as a decimal number (01-31).
241#    %F    is equivalent to "%Y-%m-%d" (in *dateformat variables only).
242#    %D    is equivalent to "%m/%d/%y" (in *dateformat variables only).
243#    %%    is replaced by a literal "%".
244
245$framedateformat = "%m/%d/%Y";
246$frametimeformat = "%r";
247
248$indexdateformat = "%m/%d/%Y";
249$indextimeformat = "%r";
250
251$slidedateformat = "%m/%d/%Y";
252$slidetimeformat = "%r";
253
254$detaildateformat = "%m/%d/%Y";
255$detailtimeformat = "%I:%M %p";
256
257# Pathnames
258$indexfile = 'index.html';
259$detailfile = 'details.html';
260$framefile = 'frame.html';
261$slidefile =  'slides.html';
262$slide_dir = 'slides';
263$stylefile = 'style.css';
264$montagefile = 'montage.jpg';
265$emoticonprefix = 'ii_';
266$emoticonsmile = $emoticonprefix . 'smile.png';
267$emoticonwink  = $emoticonprefix . 'wink.png';
268$emoticonfrown = $emoticonprefix . 'frown.png';
269
270# File exclusion customization (regex)
271# (Anything non-image and non-dir will be skipped automatically, this just
272#  makes it silent)
273@exclude = qw(
274	      ^CVS$
275	      ^.nautilus-metafile.xml$
276	      ^.thumbnails$
277	      ^.nfs.*$
278	      ^.xvpics$
279	      ^.thumbcache$
280	      ^ALBUM.OFA$
281              ^Thumbs.db$
282	      ^desktop.ini$
283	      ^.*.txt$
284	      );
285
286
287# Metatags
288$columnsmetatag   = 'Columns';
289$titlemetatag     = 'Title';
290$begindatemetatag = 'DateBegin';
291$enddatemetatag   = 'DateEnd';
292$excludemetatag   = 'ExcludedFiles';
293$skipmetatag      = 'SkipMontageFiles';
294$numimagesmetatag = 'NumImages';
295$reversemetatag   = 'Reverse';
296$thumbxmetatag    = 'ThumbnailX';
297$thumbymetatag    = 'ThumbnailY';
298
299# Any of the above can be overridden in an rc file in the user's home dir
300$rcfile = "$ENV{'HOME'}/.imageindexrc";
301
302######################################################################
303#
304# $Id: imageindex,v 1.183 2014/09/13 14:43:34 jjreynold Exp $
305#
306#  imageindex is free software; you can redistribute it and/or modify
307#  it under the terms of the GNU General Public License as published by
308#  the Free Software Foundation; either version 2, or (at your option)
309#  any later version.
310#
311#  imageindex is distributed in the hope that it will be useful,
312#  but WITHOUT ANY WARRANTY; without even the implied warranty of
313#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
314#  GNU General Public License for more details.
315#
316#  You should have received a copy of the GNU General Public License
317#  along with imageindex; see the file COPYING.
318#
319######################################################################
320
321use Image::Magick;                                # comes with ImageMagick
322
323# from CPAN - optional
324eval('use Image::Info qw(image_info)');
325
326# Shipped with perl
327use POSIX;
328use Getopt::Long;
329use FileHandle;
330use File::Basename;
331use File::Copy;
332use English;
333use Carp;
334#require 'flush.pl';
335
336# to shut up -w
337use vars qw($opt_recurse);
338use vars qw($opt_slide);
339use vars qw($opt_dirs);
340use vars qw($opt_detail);
341use vars qw($opt_lowercase);
342use vars qw($opt_help);
343use vars qw($opt_debug);
344use vars qw($opt_showexcluded);
345use vars qw($opt_version);
346use vars qw($opt_updirindexoverride);
347
348&GetOptions(
349	    'title=s',
350	    'columns=i',
351	    'x=i',
352	    'y=i',
353	    'forceregen',
354	    'medium!',
355	    'slide!',
356	    'detail!',
357	    'dirs!',
358	    'montage!',
359	    'recurse!',
360	    'destdir=s',
361	    'lowercase',
362	    'caption=s',
363	    'timestamp=s',
364	    'rotate=s',
365	    'exclude=s@',
366	    'skipmont=s@',
367	    'showexcluded',
368	    'includeall',
369	    'version',
370	    'help',
371	    'debug',
372	    'reverse!',
373            'charset=s',
374	    'd=s%',
375	    'updirindexoverride'
376	    ) or die ("Invalid flag\n");
377
378# Find out which platform we're on so we don't give incorrect options to needed
379# commands
380#
381$uname = `uname -s`;
382chomp ($uname);
383
384# Override config variables
385foreach my $var (keys %opt_d) {
386    $value = $opt_d{$var};
387    print "(override) $var = $value\n";
388    eval("\$$var=\"$value\"");
389}
390
391&init_png_array();
392
393# Read RC file
394if (-e $rcfile) {
395    print "Using settings in $rcfile...\n" if ! defined ($opt_version);
396    require $rcfile;
397}
398
399# Rotate or caption or timestamp image (then exit)
400if (defined ($opt_rotate)) {
401    &rotate_image($opt_rotate,\@ARGV);
402    exit (0);
403} elsif (defined ($opt_caption)) {
404    &caption_image($opt_caption,\@ARGV);
405    exit (0);
406} elsif (defined ($opt_timestamp)) {
407    &timestamp_image($opt_timestamp,\@ARGV);
408    exit (0);
409} elsif (defined ($opt_showexcluded)) {
410    &showexcluded($ARGV[0]);
411    exit (0);
412} elsif (defined ($opt_version)) {
413    printf ("imageindex version: %s\n", &versionstring);
414    exit (0);
415}
416
417# The directory to search is the first argument
418if (defined($ARGV[0])) {
419    $srcdir = $ARGV[0];
420    $srcdir =~ s:/$::;
421} else {
422    $srcdir = ".";
423}
424
425# Give usage message
426if (defined($opt_help)) {
427    &usage();
428    exit(0);
429}
430
431# Show backtrace if debug given
432if (defined($opt_debug)) {
433    $SIG{__WARN__} = \&Carp::cluck;
434}
435
436# Where to generate files
437$destdir = $srcdir;
438if (defined($opt_destdir)) {
439    $destdir = $opt_destdir;
440    $destdir =~ s:/$::;
441    print "Exporting to $destdir\n";
442    unless (-d $destdir) {
443	printf ("Creating destination directory '$destdir'.\n");
444	mkdir ($destdir, 0755);
445    }
446}
447
448unless (-w $destdir) {
449    printf ("No write permission for $destdir\n");
450    exit (1);
451}
452
453if (defined($opt_medium)) {
454    $do_medium = $opt_medium
455}
456
457if (defined($opt_slide)) {
458    $do_slide = $opt_slide;
459}
460
461if (defined($opt_detail)) {
462    $do_detail = $opt_detail;
463}
464
465if (defined($opt_dirs)) {
466    $do_dirs = $opt_dirs;
467}
468
469if (defined($opt_montage)) {
470    $do_montage = $opt_montage;
471}
472
473if (defined($opt_recurse)) {
474    $do_recurse = $opt_recurse;
475}
476
477if (defined($opt_charset)) {
478    $charset = $opt_charset;
479}
480
481# no montages if we aren't doing dirs anyway
482if ($do_dirs == 0) {
483    $do_montage = 0;
484}
485
486&initialize_current_vars();
487&read_stored_meta_data();
488&override_by_commandline();
489
490if (!defined(&image_info)) {
491    print "Image::Info not found, not extracting EXIF data\n";
492}
493
494opendir(DIR, "$srcdir") || die "Can't open dir $srcdir: ($!)\n";
495@files = readdir DIR;
496closedir(DIR);
497@files = grep (!/^\.?\.$/, @files);
498
499# Skip the files/dirs we use or generate. Any other patterns go in the
500# config section (@exclude) or in exclude file
501my @generated_files = ($thumbnail_dir, $med_dir, $slide_dir,
502		       $indexfile, $detailfile, $stylefile,
503		       );
504
505foreach my $pattern (@generated_files, @exclude) {
506    @files = grep (!/^$pattern$/, @files);
507}
508
509@files = &exclude_files(@files);
510
511# Change all the names of image files to lowercase.
512if (defined ($opt_lowercase)) {
513    &lower_case_files(@files);
514    exit (0);
515}
516
517# Keep track of which column to be in
518my $col_counter = 1;
519
520# Count how many files we create
521my $object_counter = 0;
522my $dir_counter = 0;
523my $image_counter = 0;
524my $thumbnail_counter = 0;
525my $med_counter = 0;
526my $slide_counter = 0;
527my $modified_thumb = 0;
528
529# Keep track of max thumb sizes to use for slide frame width
530my $max_thumb_x = 0;
531my $max_thumb_y = 0;
532
533# Keep track of max thumb sizes to use for montage creation
534my $max_mont_thumb_x = 0;
535my $max_mont_thumb_y = 0;
536
537my $mplayer_prog = &find_in_path ('mplayer');
538
539# Extract info
540print "Extracting image info";
541&flush (STDOUT);
542
543foreach my $file (@files) {
544
545    # If directory, grab the timestamp
546    if (-d "$srcdir/$file") {
547
548	my $ts;
549
550	# Grab timestamp from meta tag
551	if (-e "$srcdir/$file/$indexfile") {
552
553	    my $begin = &extract_meta_tag($begindatemetatag,"$srcdir/$file/$indexfile");
554	    if (defined($begin)) {
555		if (!defined($firstdate) or ($begin < $firstdate)) {
556		    $firstdate = $begin;
557		}
558		$ts = $begin;
559	    }
560
561	    my $end = &extract_meta_tag($enddatemetatag,"$srcdir/$file/$indexfile");
562	    if (defined($end)) {
563		if (!defined($lastdate) or ($end > $lastdate)) {
564		    $lastdate = $end;
565		}
566		$ts = $end if (!defined($ts));
567	    }
568
569	}
570
571	# Fallback on dir mtime
572	if (!defined($ts)) {
573	    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
574		$atime,$mtime,$ctime,$blksize,$blocks) = stat("$srcdir/$file");
575	    $ts = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
576	}
577
578	push(@{$dir_timestamp{$ts}}, $file);
579
580    } else {
581
582	# Collect info from the image or video
583	&extract_file_info($file);
584
585    }
586}
587print "\n";
588
589
590# Do dirs first
591if ($do_dirs) {
592    foreach my $ts (sort bynumber keys %dir_timestamp) {
593	foreach my $dir (sort @{$dir_timestamp{$ts}}) {
594	    &dir_entry($dir);
595	} # foreach dir that has this timestamp
596    }  # foreach timestamp
597}
598
599
600# Bail if nothing here
601if ($object_counter == 0) {
602    print "Nothing to do!\n";
603    unlink("$destdir/$indexfile") if (-e "$destdir/$indexfile");
604    unlink("$destdir/$detailfile") if (-e "$destdir/$detailfile");
605    unlink("$destdir/$stylefile") if (-e "$destdir/$stylefile");
606    exit(0);
607}
608
609# Make thumb dirs if needed
610foreach my $checkdir ($thumbnail_dir, $med_dir, $slide_dir) {
611    unless (-d "$destdir/$checkdir") {
612	mkdir("$destdir/$checkdir",0777);
613    }
614}
615
616# Nuke old thumbnails if original image gone
617&nuke_out_of_date();
618
619# Iterate over the files based on timestamp
620# This is just to get back/forward links
621undef $prev;
622foreach (sort bynumber keys %timestamp) {
623
624    foreach my $pathname (sort @{$timestamp{$_}}) {
625
626	if (defined($prev)) {
627	    my ($name,$path,$suffix);
628
629	    ($name,$path,$suffix) = fileparse($prev,'\.\S+');
630	    $back{$pathname} = "$name.html";
631
632	    ($name,$path,$suffix) = fileparse($pathname,'\.\S+');
633	    $forward{$prev} = "$name.html";
634	}
635	$prev = $pathname;
636
637    } # foreach image that has this timestamp
638
639} # foreach timestamp
640
641# Iterate over the files based on timestamp
642# This will do the real work
643foreach (sort bynumber keys %timestamp) {
644
645    foreach my $pathname (sort @{$timestamp{$_}}) {
646
647	my $filename = $info{$pathname}{'file'};
648	my $thumbnail = $info{$pathname}{'thumb'};
649	my $medium = $info{$pathname}{'medium'};
650	my $slide = $info{$pathname}{'slide'};
651
652	my $tmp_jpg_dir = "tmp_jpg_$$";
653
654	if (!defined($firstdate) or ($info{$pathname}{'date'} < $firstdate)) {
655	    $firstdate = $info{$pathname}{'date'};
656	}
657
658	if (!defined($lastdate) or ($info{$pathname}{'date'} > $lastdate)) {
659	    $lastdate = $info{$pathname}{'date'};
660	}
661
662	#
663	# First, deal with medium format of the image since we can save time shrinking
664	# the medium down to the thumbnail rather than fullsize->thumbnail
665	#
666	# If the file is a video, we must first extract the first frame of the video
667	# stream into JPEG form and we may or may not maniplate it from there.
668	#
669
670	# Skip if we want no medium images at all
671	#
672	if ($do_medium == 0) {
673	    $skipmedium{$pathname} = 1;
674	    unlink("$destdir/$medium") if (-e "$destdir/$medium");
675
676	} elsif (($info{$pathname}{'x'} <= ($med_x * $med_threshold)) and
677		 ($info{$pathname}{'y'} <= ($med_y * $med_threshold))) {
678
679	    my $image = new Image::Magick;
680	    my $retval;
681
682	    # Regardless of whether the "size" of a frame of a video is
683	    # close to our medium threshold size, we need to create an
684	    # image and have it there. If it is an image then the HTML will
685	    # just link to the actual image rather than creating a 'medium'
686	    # version. But for video files we need to make one regardless.
687	    #
688	    if ($info{$pathname}{'is_video'}) {
689		if ((! -e "$destdir/$medium") or
690		    ( -M $pathname < -M "$destdir/$medium") or
691		    defined($opt_forceregen)) {
692
693		    my $icon = new Image::Magick;
694
695		    print "Creating $destdir/$medium from first frame of $pathname\n";
696
697		    my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
698		    $retval = $image->Read(filename=>$tmpfile);
699		    warn "$retval" if "$retval";
700		    $retval = $image->Set(interlace=>Line);
701		    warn "$retval" if "$retval";
702		    # always want 100% here instead of $med_quality
703		    $retval = $image->Set(quality=>100);
704		    warn "$retval" if "$retval";
705
706		    # If the user wants to, overlay a small icon on top of the
707		    # thumbnail of the video file to give a visual cue that this
708		    # file is a video and not a still photo
709		    #
710		    if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
711			my $iconfile;
712			if ($video_icon_gravity ne 'SouthWest' &&
713			    $video_icon_gravity ne 'NorthWest' &&
714			    $video_icon_gravity ne 'SouthEast' &&
715			    $video_icon_gravity ne 'NorthEast') {
716			    printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
717			    $video_icon_gravity = 'SouthWest';
718			}
719			&write_video_icons ($tmp_jpg_dir);
720			if ($video_icon < 1 ||
721			    $video_icon > $max_video_icons) {
722			    printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
723			    $video_icon = 1;
724			}
725			$iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
726			$retval = $icon->Read(filename=>$iconfile);
727			warn "$retval" if "$retval";
728
729			$image->Composite(image=>$icon,gravity=>$video_icon_gravity);
730		    }
731
732		    $retval = $image->Write(filename=>"$destdir/$medium");
733		    warn "$retval" if "$retval";
734
735		    &delete_tmp_jpg_dir($tmp_jpg_dir);
736
737		} else {
738		    # Up to date, existing medium
739		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
740		    # the file here and the code below will set the thumb_x/y properties.
741		    #
742		    $retval = $image->Read("$destdir/$medium");
743		    warn "$retval" if "$retval";
744
745		}
746
747		$info{$pathname}{'med_size'} = &convert_to_kb($image->Get('filesize'));
748		$info{$pathname}{'med_x'} = $image->Get('width');
749		$info{$pathname}{'med_y'} = $image->Get('height');
750
751		$med_counter++;
752
753
754	    } else {
755		# Skip if we are below the threshold size
756		$skipmedium{$pathname} = 1;
757		unlink("$destdir/$medium") if (-e "$destdir/$medium");
758	    }
759
760	} else {
761	    my $image = new Image::Magick;
762	    my $retval;
763
764	    # The size of the file was not within the "threshold" so we need to create a
765	    # medium version
766
767	    if ($info{$pathname}{'is_video'}) {
768
769		# Create a medium version out of the first frame of the video file and then
770		# resize it according to $med_x and $med_y
771		#
772		if ((! -e "$destdir/$medium") or
773		    ( -M $pathname < -M "$destdir/$medium") or
774		    defined($opt_forceregen)) {
775
776		    my $icon = new Image::Magick;
777		    my $newgeom = $med_x . "x" . $med_y;
778
779		    print "Creating $destdir/$medium from first frame of $pathname\n";
780
781		    my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
782		    $retval = $image->Read(filename=>$tmpfile);
783		    warn "$retval" if "$retval";
784		    $retval = $image->Resize(geometry=>$newgeom);
785		    warn "$retval" if "$retval";
786		    $retval = $image->Set(interlace=>Line);
787		    warn "$retval" if "$retval";
788		    # always want 100% here instead of $med_quality
789		    $retval = $image->Set(quality=>100);
790		    warn "$retval" if "$retval";
791
792		    # If the user wants to, overlay a small icon on top of the
793		    # thumbnail of the video file to give a visual cue that this
794		    # file is a video and not a still photo
795		    #
796		    if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
797			my $iconfile;
798			if ($video_icon_gravity ne 'SouthWest' &&
799			    $video_icon_gravity ne 'NorthWest' &&
800			    $video_icon_gravity ne 'SouthEast' &&
801			    $video_icon_gravity ne 'NorthEast') {
802			    printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
803			    $video_icon_gravity = 'SouthWest';
804			}
805			&write_video_icons ($tmp_jpg_dir);
806			if ($video_icon < 1 ||
807			    $video_icon > $max_video_icons) {
808			    printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
809			    $video_icon = 1;
810			}
811			$iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
812			$retval = $icon->Read(filename=>$iconfile);
813			warn "$retval" if "$retval";
814
815			$image->Composite(image=>$icon,gravity=>$video_icon_gravity);
816		    }
817
818		    $retval = $image->Write(filename=>"$destdir/$medium");
819		    warn "$retval" if "$retval";
820
821		    &delete_tmp_jpg_dir($tmp_jpg_dir);
822
823		} else {
824		    # Up to date, existing medium
825		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
826		    # the file here and the code below will set the thumb_x/y properties.
827		    #
828		    $retval = $image->Read("$destdir/$medium");
829		    warn "$retval" if "$retval";
830
831		}
832
833		$info{$pathname}{'med_size'} = &convert_to_kb($image->Get('filesize'));
834		$info{$pathname}{'med_x'} = $image->Get('width');
835		$info{$pathname}{'med_y'} = $image->Get('height');
836
837		$med_counter++;
838
839	    } else {
840		my $image = new Image::Magick;
841		my $retval;
842
843		# Create medium sized pic if it is not there,
844		# or is out of date with respect to original image
845		if ((! -e "$destdir/$medium") or
846		    ( -M $pathname < -M "$destdir/$medium") or
847		    defined($opt_forceregen)) {
848
849		    my $newgeom = $med_x . "x" . $med_y;
850
851		    print "Creating $destdir/$medium\n";
852
853		    $retval = $image->Read(filename=>$pathname);
854		    warn "$retval" if "$retval";
855		    $retval = $image->Resize(geometry=>$newgeom);
856		    warn "$retval" if "$retval";
857		    $retval = $image->Set(interlace=>Line);
858		    warn "$retval" if "$retval";
859		    $retval = $image->Set(quality=>$med_quality);
860		    warn "$retval" if "$retval";
861		    if ($info{$pathname}{'is_multi_image_file'}) {
862			$retval = $image->[0]->Write(filename=>"$destdir/$medium");
863			warn "$retval" if "$retval";
864		    } else {
865			$retval = $image->Write(filename=>"$destdir/$medium");
866			warn "$retval" if "$retval";
867		    }
868
869		    $image_cache{$pathname} = $image;
870
871		} else {
872
873		    # Up to date, existing medium, grab dimensions
874		    # Get the right hsize/vsize tags for the medium slides. Simply do a "Read" of
875		    # the file here and the code below will set the med_x/y properties.
876		    #
877		    $retval = $image->Read("$destdir/$medium");
878		    warn "$retval" if "$retval";
879		}
880
881		$info{$pathname}{'med_size'} = &convert_to_kb($image->Get('filesize'));
882		$info{$pathname}{'med_x'} = $image->Get('width');
883		$info{$pathname}{'med_y'} = $image->Get('height');
884
885		$med_counter++;
886	    }
887
888	}
889
890	#
891	# Next, deal with the thumbnail for this image. If we have just created a medium
892	# version of the image, then an open image "handle" will exist for it. We simply
893	# shrink that down to thumbnail size (if appropriate) rather than reading in the
894	# original file again just to shrink it (saves processing time).
895	#
896
897	# Skip thumb if we are below the threshold size
898	if (($info{$pathname}{'x'} <= ($current_thumbnail_x * $thumbnail_threshold)) and
899	    ($info{$pathname}{'y'} <= ($current_thumbnail_y * $thumbnail_threshold))) {
900
901	    my $image = new Image::Magick;
902	    my $retval;
903
904	    if ($info{$pathname}{'is_video'}) {
905		# If the file is a video but the X/Y is smaller than that of our
906		# thumbnail size we still need to make a JPG out of the first
907		# frame and store it in the thumbnail directory. This code is
908		# mostly like the code below for creating video thumbnails but
909		# we're just not resizing the first frame down to thumbnail size.
910		#
911		my $icon = new Image::Magick;
912
913		# We need to make a thumbnail from this video file. Get the first frame
914
915		print "Creating $destdir/$thumbnail from first frame of $pathname\n";
916
917		my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
918		$retval = $image->Read(filename=>$tmpfile);
919		warn "$retval" if "$retval";
920		$retval = $image->Set(interlace=>Line);
921		warn "$retval" if "$retval";
922		$retval = $image->Set(quality=>$thumb_quality);
923		warn "$retval" if "$retval";
924
925		# If the user wants to, overlay a small icon on top of the
926		# thumbnail of the video file to give a visual cue that this
927		# file is a video and not a still photo
928		#
929		if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
930		    my $iconfile;
931		    if ($video_icon_gravity ne 'SouthWest' &&
932			$video_icon_gravity ne 'NorthWest' &&
933			$video_icon_gravity ne 'SouthEast' &&
934			$video_icon_gravity ne 'NorthEast') {
935			printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
936			$video_icon_gravity = 'SouthWest';
937		    }
938		    &write_video_icons ($tmp_jpg_dir);
939		    if ($video_icon < 1 ||
940			$video_icon > $max_video_icons) {
941			printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
942			$video_icon = 1;
943		    }
944		    $iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
945		    $retval = $icon->Read(filename=>$iconfile);
946		    warn "$retval" if "$retval";
947
948		    $image->Composite(image=>$icon,gravity=>$video_icon_gravity);
949		}
950
951
952		$retval = $image->Write(filename=>"$destdir/$thumbnail");
953		warn "$retval" if "$retval";
954
955		push(@montagefiles,"$destdir/$thumbnail");
956
957		$modified_thumb++;
958
959		$info{$pathname}{'thumb_size'} = &convert_to_kb($image->Get('filesize'));
960		$info{$pathname}{'thumb_x'} = $image->Get('width');
961		$info{$pathname}{'thumb_y'} = $image->Get('height');
962
963		$thumbnail_counter++;
964
965		&delete_tmp_jpg_dir($tmp_jpg_dir);
966
967	    } else {
968		# is NOT a video file
969		$info{$pathname}{'thumb_x'} = $info{$pathname}{'x'};
970		$info{$pathname}{'thumb_y'} = $info{$pathname}{'y'};
971
972		$skipthumb{$pathname} = 1;
973		if (-e "$destdir/$thumbnail") {
974		    unlink("$destdir/$thumbnail");
975		    $modified_thumb++;
976		}
977		push(@montagefiles,"$destdir/$filename");
978	    }
979
980	} else {
981	    my $image = new Image::Magick;
982	    my $retval;
983
984	    if ($info{$pathname}{'is_video'}) {
985
986		my $icon = new Image::Magick;
987
988		# We need to make a thumbnail from this video file. Get the first frame and
989		# resize it down to the appropriate size
990		if ((! -e "$destdir/$thumbnail") or
991		    ( -M $pathname < -M "$destdir/$thumbnail") or
992		    defined($opt_forceregen)) {
993
994		    my $newgeom = $current_thumbnail_x . "x" . $current_thumbnail_y;
995
996		    print "Creating $destdir/$thumbnail from first frame of $pathname\n";
997
998		    my $tmpfile = &extract_first_frame_jpg ($pathname, $tmp_jpg_dir, $mplayer_prog);
999		    $retval = $image->Read(filename=>$tmpfile);
1000		    warn "$retval" if "$retval";
1001		    $retval = $image->Resize(geometry=>$newgeom);
1002		    warn "$retval" if "$retval";
1003		    $retval = $image->Set(interlace=>Line);
1004		    warn "$retval" if "$retval";
1005		    # Always want 100% here instead of $thumb_quality
1006		    $retval = $image->Set(quality=>100);
1007		    warn "$retval" if "$retval";
1008
1009		    # If the user wants to, overlay a small icon on top of the thumbnail
1010		    # of the video file to give a visual cue that this file is a video and
1011		    # not a still photo
1012		    #
1013		    if (defined ($do_video_thumbnail_icons) && $do_video_thumbnail_icons) {
1014			my $iconfile;
1015			if ($video_icon_gravity ne 'SouthWest' &&
1016			    $video_icon_gravity ne 'NorthWest' &&
1017			    $video_icon_gravity ne 'SouthEast' &&
1018			    $video_icon_gravity ne 'NorthEast') {
1019			    printf (STDERR "WARNING: \$video_icon_gravity set to unknown value. Assuming 'SouthWest'\n");
1020			    $video_icon_gravity = 'SouthWest';
1021			}
1022			&write_video_icons ($tmp_jpg_dir);
1023			if ($video_icon < 1 ||
1024			    $video_icon > $max_video_icons) {
1025			    printf (STDERR "WARNING: \$video_icon set to unknown value. Assuming '1'\n");
1026			    $video_icon = 1;
1027			}
1028			$iconfile = $tmp_jpg_dir . '/video_icon' . $video_icon . '.png';
1029			$retval = $icon->Read(filename=>$iconfile);
1030			warn "$retval" if "$retval";
1031
1032			$image->Composite(image=>$icon,gravity=>$video_icon_gravity);
1033		    }
1034
1035
1036		    $retval = $image->Write(filename=>"$destdir/$thumbnail");
1037		    warn "$retval" if "$retval";
1038
1039
1040		    push(@montagefiles,"$destdir/$thumbnail");
1041
1042		    $modified_thumb++;
1043
1044		    &delete_tmp_jpg_dir($tmp_jpg_dir);
1045
1046		} else {
1047		    # Up to date, existing thumb
1048		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
1049		    # the file here and the code below will set the thumb_x/y properties.
1050		    #
1051		    $retval = $image->Read("$destdir/$thumbnail");
1052		    warn "$retval" if "$retval";
1053
1054		    push(@montagefiles,"$destdir/$thumbnail");
1055		}
1056
1057		$info{$pathname}{'thumb_size'} = &convert_to_kb($image->Get('filesize'));
1058		$info{$pathname}{'thumb_x'} = $image->Get('width');
1059		$info{$pathname}{'thumb_y'} = $image->Get('height');
1060
1061		$thumbnail_counter++;
1062
1063
1064	    } else {
1065		# Is NOT a video file
1066		my $image = new Image::Magick;
1067		my $retval;
1068
1069		# Create thumbnail if it is not there,
1070		# or is out of date with respect to original image
1071		if ((! -e "$destdir/$thumbnail") or
1072		    ( -M $pathname < -M "$destdir/$thumbnail") or
1073		    defined($opt_forceregen)) {
1074
1075		    my $newgeom = $current_thumbnail_x . "x" . $current_thumbnail_y;
1076
1077		    print "Creating $destdir/$thumbnail\n";
1078
1079		    if (defined ($image_cache{$pathname})) {
1080			$image = $image_cache{$pathname};
1081
1082			$retval = $image->Resize(geometry=>$newgeom);
1083			warn "$retval" if "$retval";
1084			$retval = $image->Set(quality=>$thumb_quality);
1085			warn "$retval" if "$retval";
1086			if ($info{$pathname}{'is_multi_image_file'}) {
1087			    $retval = $image->[0]->Write(filename=>"$destdir/$thumbnail");
1088			    warn "$retval" if "$retval";
1089			} else {
1090			    $retval = $image->Write(filename=>"$destdir/$thumbnail");
1091			    warn "$retval" if "$retval";
1092			}
1093		    }
1094		    else {
1095			$retval = $image->Read(filename=>$pathname);
1096			warn "$retval" if "$retval";
1097			$retval = $image->Resize(geometry=>$newgeom);
1098			warn "$retval" if "$retval";
1099			$retval = $image->Set(interlace=>Line);
1100			warn "$retval" if "$retval";
1101			$retval = $image->Set(quality=>$thumb_quality);
1102			warn "$retval" if "$retval";
1103			if ($info{$pathname}{'is_multi_image_file'}) {
1104			    $retval = $image->[0]->Write(filename=>"$destdir/$thumbnail");
1105			    warn "$retval" if "$retval";
1106			} else {
1107			    $retval = $image->Write(filename=>"$destdir/$thumbnail");
1108			    warn "$retval" if "$retval";
1109			}
1110		    }
1111		    push(@montagefiles,"$destdir/$thumbnail");
1112
1113		    $modified_thumb++;
1114
1115
1116		} else {
1117
1118		    # Up to date, existing thumb
1119		    # Get the right hsize/vsize tags for the inline thumbs. Simply do a "Read" of
1120		    # the file here and the code below will set the thumb_x/y properties.
1121		    #
1122		    $retval = $image->Read("$destdir/$thumbnail");
1123		    warn "$retval" if "$retval";
1124
1125		    push(@montagefiles,"$destdir/$thumbnail");
1126
1127		}
1128
1129		$info{$pathname}{'thumb_size'} = &convert_to_kb($image->Get('filesize'));
1130		$info{$pathname}{'thumb_x'} = $image->Get('width');
1131		$info{$pathname}{'thumb_y'} = $image->Get('height');
1132
1133		$thumbnail_counter++;
1134	    }
1135	}
1136
1137	# Set the max thumb sizes, to be used for slide frame width
1138	if ($info{$pathname}{'thumb_x'} > $max_thumb_x) {
1139	    $max_thumb_x = $info{$pathname}{'thumb_x'};
1140	}
1141	if ($info{$pathname}{'thumb_y'} > $max_thumb_y) {
1142	    $max_thumb_y = $info{$pathname}{'thumb_y'};
1143	}
1144
1145
1146	# Set the max montage thumb sizes, to be used when creating montage images
1147	#
1148	$bn = basename ($thumbnail);
1149	unless (defined ($skipmont{$bn})) {
1150	    if ($info{$pathname}{'thumb_x'} > $max_mont_thumb_x) {
1151		$max_mont_thumb_x = $info{$pathname}{'thumb_x'};
1152	    }
1153	    if ($info{$pathname}{'thumb_y'} > $max_mont_thumb_y) {
1154		$max_mont_thumb_y = $info{$pathname}{'thumb_y'};
1155	    }
1156	}
1157
1158	#
1159	# Finally, create html for this image
1160	#
1161
1162	&image_entry($pathname);
1163
1164    } # foreach image that has this timestamp
1165
1166} # foreach timestamp
1167
1168
1169# Finish up the columns if needed
1170if (($col_counter != 1) and
1171    ($col_counter <= $current_columns) and
1172    ($object_counter > $current_columns)) {
1173    foreach ($col_counter..$current_columns) {
1174	push(@index, "    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">$emptycell</TD>\n");
1175	push(@details, "    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">$emptycell</TD>\n");
1176    }
1177    push(@index, "  </TR>\n");
1178    push(@details, "  </TR>\n");
1179}
1180
1181# Nuke generated dirs if no contents
1182system("rm -rf $destdir/$thumbnail_dir") if ($thumbnail_counter == 0);
1183system("rm -rf $destdir/$slide_dir") if ($slide_counter == 0);
1184system("rm -rf $destdir/$med_dir") if ($med_counter == 0);
1185
1186# Create montage if we had more than just dir entries here
1187if (($dir_counter != $object_counter)) {
1188    &create_montage(@montagefiles);
1189}
1190
1191# Create stylesheet
1192&write_css();
1193
1194# Write index web page
1195open(INDEX,">$destdir/$indexfile") or die ("Can't open $destdir/$indexfile: $!\n");
1196&page_header('index', $index_linkto);
1197foreach (@index) {
1198    print INDEX;
1199}
1200&page_footer('index');
1201close(INDEX);
1202
1203# Write photo details file
1204if ($do_detail == 1) {
1205    open(INDEX,">$destdir/$detailfile") or die ("Can't open $destdir/$indexfile: $!\n");
1206    &page_header('detail', $details_linkto);
1207    foreach (@details) {
1208	print INDEX;
1209    }
1210    &page_footer('detail');
1211    close(INDEX);
1212} else {
1213    unlink("$destdir/$detailfile") if (-e "$destdir/$detailfile");
1214}
1215
1216# Write slide/frame files
1217if (($do_slide == 1) and ($slide_counter > 1)) {
1218    &write_frameset();
1219} else {
1220    system("rm -rf $destdir/$slide_dir") if (-d "$destdir/$slide_dir");
1221}
1222
1223# Optionally export images somewhere else
1224if ($opt_destdir) {
1225    printf ("Copying image files from '$srcdir' to '$destdir'.\n");
1226    foreach my $image (keys %info) {
1227	# BSD's default 'cp' cannot preserve links like GNU fileutils cp can
1228	#
1229	if ($uname =~ /BSD/) {
1230	    system("cp -pv $image $destdir");
1231	}
1232	else {
1233	    system("cp -dpuv $image $destdir");
1234	}
1235    }
1236}
1237
1238if (defined ($do_emoticons) && $do_emoticons) {
1239    foreach $icon ('wink', 'smile', 'frown') {
1240	if ($emoticon{$icon}) {
1241	    &write_emoticon_png ($icon);
1242	} else {
1243	    unlink ($destdir . '/' . $thumbnail_dir . "/$emoticonprefix${icon}.png");
1244	}
1245    }
1246}
1247
1248######################################################################
1249#
1250# Write the various HTML parts for this image
1251#
1252######################################################################
1253sub image_entry {
1254
1255    my $pathname = shift(@_);
1256    my $filename = $info{$pathname}{'file'};
1257    my $link;
1258
1259    &index_html($pathname);
1260
1261    if ($do_detail == 1) {
1262	&details_html($pathname);
1263    }
1264
1265    if (($do_slide == 1) and ($image_counter > 1)) {
1266	&slide_html($pathname);
1267    } else {
1268	my $file = $info{$pathname}{slide};
1269	unlink($file) if (-e $file);
1270    }
1271
1272    # Increment for next time
1273    $col_counter++;
1274    $col_counter = 1 if ($col_counter > $current_columns);
1275
1276}
1277
1278
1279###############################################################################
1280#
1281# Generate HTML for index page entry
1282#
1283###############################################################################
1284
1285sub index_html {
1286
1287    my $pathname = shift(@_);
1288    my $filename = $info{$pathname}{'file'};
1289    my $link;
1290    my $anchortext;
1291
1292    # At beginning of row?
1293    if ($col_counter == 1) {
1294	push(@index, "  <TR>\n");
1295    }
1296
1297    # Image
1298    push(@index, "    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");
1299    push(@index, "      <DIV CLASS=\"index\">");
1300    push(@index, &format_date($info{$pathname}{'date'}, 'index'));
1301    push(@index,"</DIV>\n");
1302
1303    if (($index_linkto eq 'details') and ($do_detail == 1)) {
1304	$link = "$detailfile#$filename";
1305    } elsif (($index_linkto eq 'medium') and !defined($skipmedium{$pathname})) {
1306	$link = $info{$pathname}{'medium'};
1307    } elsif (($index_linkto eq 'thumbnail') and !defined($skipthumb{$pathname})) {
1308	$link = $info{$pathname}{'thumb'};
1309    } elsif (($index_linkto eq 'slide') and ($do_slide == 1) and ($image_counter > 1)) {
1310	$link = $info{$pathname}{'slide'};
1311    } else {
1312	$link = $filename;
1313    }
1314
1315    $anchortext = "      <A HREF=\"$link\" ";
1316
1317    if (defined ($indexthumbtitle) && $indexthumbtitle ne '') {
1318	my ($str);
1319	$str = &interpolate_title_string ($indexthumbtitle, $pathname, 'index');
1320	if ($str ne '') {
1321	    $anchortext .= sprintf ("TITLE=\"%s\" ", $str);
1322	}
1323    }
1324
1325    $anchortext .= "NAME=\"$filename\">";
1326
1327    push(@index, $anchortext);
1328
1329    if (defined($skipthumb{$pathname})) {
1330	push(@index,"<IMG SRC=\"$filename\"");
1331    } else {
1332	push(@index,"<IMG SRC=\"$info{$pathname}{thumb}\"");
1333    }
1334    push(@index," WIDTH=\"$info{$pathname}{thumb_x}\" HEIGHT=\"$info{$pathname}{thumb_y}\"");
1335    push(@index," ALT=\" $filename \"");
1336    push(@index," CLASS=\"index\"");
1337    push(@index,"></A>\n");
1338
1339    push(@index, "      <DIV CLASS=\"index\">");
1340
1341    # Full size link
1342    push(@index,"<A HREF=\"$filename\">full size</A>");
1343
1344    # Medium size link if within the threshold
1345    unless (defined($skipmedium{$pathname})) {
1346	push(@index,"&nbsp;|&nbsp;<A HREF=\"$info{$pathname}{medium}\">medium</A>");
1347    }
1348
1349    # Detail list link
1350    if ($do_detail == 1) {
1351	push(@index,"&nbsp;|&nbsp;<A HREF=\"$detailfile#$filename\">details</A>");
1352    }
1353
1354    push(@index,"</DIV>\n");
1355
1356    # Caption if any (jpeg comment field)
1357    if (($do_captions == 1) and defined($info{$pathname}{'comment'})) {
1358	my ($tmp);
1359	push(@index, "      <DIV CLASS=\"caption\">");
1360	# Hack: if a comment has an ellipsis at the very end, make the HTML use a
1361	# non-breakable space before it so that the ellipsis doesn't "wrap" inside
1362	# the table field. It just looks better for those cases where the comment
1363	# is just long enough to wrap when rendered in the space given
1364	#
1365	$tmp = $info{$pathname}{'comment'};
1366	$tmp = &htmlize_caption ($tmp);
1367	if ($tmp =~ /(\s+)\.\.\.\s*$/) {
1368	    $tmp =~ s/(\s+)\.\.\.\s*$/&nbsp;.../;
1369	}
1370	push(@index, $tmp);
1371	push(@index,"</DIV>\n");
1372    }
1373
1374    push(@index, "    </TD>\n\n");
1375
1376    # At end of row?
1377    if ($col_counter == $current_columns) {
1378	push(@index, "  </TR>\n");
1379    }
1380
1381}
1382
1383
1384###############################################################################
1385#
1386# Generate HTML for slide/frame pages
1387#
1388###############################################################################
1389sub slide_html {
1390
1391    my $pathname = shift(@_);
1392    my $filename = $info{$pathname}{'file'};
1393    my $link;
1394    my $anchortext;
1395
1396    #
1397    # First the index frame info
1398    #
1399    if ($frame_orient eq 'horizontal') {
1400	push(@frame,"    <TD CLASS=\"frame\" ALIGN=\"center\" VALIGN=\"middle\">\n");
1401    } else {
1402	push(@frame,"  <TR>\n    <TD CLASS=\"frame\" ALIGN=\"center\" VALIGN=\"middle\">\n");
1403    }
1404
1405    $anchortext = "      <A HREF=\"../$info{$pathname}{slide}\" ";
1406    if (defined ($framethumbtitle) && $framethumbtitle ne '') {
1407	my ($str);
1408	$str = &interpolate_title_string ($framethumbtitle, $pathname, 'frame');
1409	if ($str ne '') {
1410	    $anchortext .= sprintf ("TITLE=\"%s\" ", $str);
1411	}
1412    }
1413
1414    $anchortext .= "TARGET=\"view\">";
1415    push(@frame, $anchortext);
1416
1417    if (defined($skipthumb{$pathname})) {
1418	push(@frame,"<IMG SRC=\"../$filename\"");
1419    } else {
1420	push(@frame,"<IMG SRC=\"../$info{$pathname}{thumb}\"");
1421    }
1422    push(@frame," WIDTH=\"$info{$pathname}{thumb_x}\" HEIGHT=\"$info{$pathname}{thumb_y}\"");
1423    push(@frame," ALT=\" $filename \"");
1424    push(@frame," CLASS=\"frame\"");
1425    push(@frame,"></A>\n");
1426    if ($frame_orient eq 'horizontal') {
1427	push(@frame,"    </TD>");
1428    } else {
1429	push(@frame,"    </TD>\n  </TR>");
1430    }
1431    push(@frame,"\n");
1432
1433    #
1434    # Then the individual slides
1435    #
1436    my $slide = new FileHandle "> $destdir/$info{$pathname}{slide}";
1437    if (!defined($slide)) {
1438	die("$destdir/$info{$pathname}{slide}: $!");
1439    }
1440
1441    select($slide);
1442    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n";
1443    print "\"http://www.w3.org/TR/html401/strict.dtd\">\n";
1444    print "<HTML>\n";
1445    print "<HEAD>\n";
1446    $verstring = &versionstring();
1447    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
1448    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=%s\">\n", $charset);
1449    print "<TITLE>$current_titletext - $filename</TITLE>\n";
1450    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">\n";
1451    print "</HEAD>\n<BODY>\n";
1452
1453    &next_prev_links($pathname);
1454
1455    # Caption if any
1456    if (($do_captions == 1) and ($slide_caption eq 'top') and defined($info{$pathname}{'comment'})) {
1457	print "<DIV CLASS=\"caption\">";
1458	my $tmp = &htmlize_caption ($info{$pathname}{'comment'}, 'slide');
1459	print $tmp;
1460	print "</DIV>\n";
1461    }
1462
1463    # Date, filename
1464    if ($slide_date eq 'top') {
1465	print "<DIV CLASS=\"index\">";
1466	print &format_date($info{$pathname}{'date'}, 'slide');
1467	print " $filename";
1468	print "</DIV>\n";
1469    }
1470
1471    if ($slide_linkto eq 'index') {
1472	$link = "../$indexfile#$filename";
1473    } elsif (($slide_linkto eq 'details') and ($do_detail == 1)) {
1474	$link = "../$detailfile#$filename";
1475    } elsif (($slide_linkto eq 'medium') and !defined($skipmedium{$pathname})) {
1476	$link = "../$info{$pathname}{medium}";
1477    } elsif (($slide_linkto eq 'thumbnail') and !defined($skipthumb{$pathname})) {
1478	$link = "../$info{$pathname}{thumb}";
1479    } else {
1480	$link = "../$filename";
1481    }
1482
1483    print "\n<P>\n";
1484
1485    $anchortext = "<A HREF=\"$link\"";
1486    if (defined ($slidethumbtitle) && $slidethumbtitle ne '') {
1487	my ($str);
1488	$str = &interpolate_title_string ($slidethumbtitle, $pathname, 'slide');
1489	if ($str ne '') {
1490	    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
1491	}
1492    }
1493
1494    $anchortext .= ">";
1495    print $anchortext;
1496
1497    if ($info{$pathname}{'is_video'}) {
1498	print "<IMG SRC=\"../$info{$pathname}{medium}\"";
1499	print " WIDTH=\"$info{$pathname}{x}\" HEIGHT=\"$info{$pathname}{y}\"";
1500    } else {
1501	if (defined($skipmedium{$pathname})) {
1502	    print "<IMG SRC=\"../$filename\"";
1503	    print " WIDTH=\"$info{$pathname}{x}\" HEIGHT=\"$info{$pathname}{y}\"";
1504	} else {
1505	    print "<IMG SRC=\"../$info{$pathname}{medium}\"";
1506	    print " WIDTH=\"$info{$pathname}{med_x}\" HEIGHT=\"$info{$pathname}{med_y}\"";
1507	}
1508    }
1509
1510    print " ALT=\" $filename \"";
1511    print " CLASS=\"slide\">";
1512    print "</A>\n";
1513
1514    print "</P>\n";
1515
1516    # Caption if any
1517    if (($do_captions == 1) and  ($slide_caption eq 'bottom') and defined($info{$pathname}{'comment'})) {
1518	print "<DIV CLASS=\"caption\">";
1519	my $tmp = &htmlize_caption ($info{$pathname}{'comment'}, 'slide');
1520	print $tmp;
1521	print "</DIV>\n";
1522    }
1523
1524    # Date, filename
1525    if ($slide_date eq 'bottom') {
1526	print "<DIV CLASS=\"index\">";
1527	print &format_date($info{$pathname}{'date'}, 'slide');
1528	print " $filename";
1529	print "</DIV>\n";
1530    }
1531
1532    &next_prev_links($pathname);
1533    print "</BODY>\n</HTML>\n";
1534
1535    select(STDOUT);
1536    $slide->close();
1537    $slide_counter++;
1538
1539    unless(defined($first_slide)) {
1540	$first_slide = $info{$pathname}{'slide'};
1541    }
1542}
1543
1544
1545###############################################################################
1546#
1547# Generate HTML for details page
1548#
1549###############################################################################
1550sub details_html {
1551
1552    my $pathname = shift(@_);
1553    my $filename = $info{$pathname}{'file'};
1554    my ($link, $anchortext);
1555
1556    # At beginning of row?
1557    if ($col_counter == 1) {
1558	push(@details, "  <TR>\n");
1559    }
1560
1561
1562    if ($details_linkto eq 'index') {
1563	$link = "$indexfile#$filename";
1564    } elsif (($details_linkto eq 'medium') and !defined($skipmedium{$pathname})) {
1565	$link = "$info{$pathname}{medium}";
1566    } elsif (($details_linkto eq 'thumbnail') and !defined($skipthumb{$pathname})) {
1567	$link = "$info{$pathname}{thumb}";
1568    } elsif (($details_linkto eq 'slide') and ($do_slide == 1) and ($image_counter > 1)) {
1569	$link = $info{$pathname}{'slide'};
1570    } else {
1571	$link = $filename;
1572    }
1573
1574    push(@details,"    <TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");
1575    push(@details,"      <TABLE BORDER=0 WIDTH=\"100%\">\n");
1576    push(@details,"        <TR>\n");
1577    push(@details,"          <TD VALIGN=\"middle\" ALIGN=\"center\">\n");
1578    push(@details,"            <DIV CLASS=\"detail\">\n");
1579    push(@details,"            <A NAME=\"$filename\">");
1580    push(@details, &format_date($info{$pathname}{'date'}, 'detail'));
1581    push(@details,"</A><BR>\n");
1582
1583    $anchortext = "<A HREF=\"$link\"";
1584    if (defined ($detailthumbtitle) && $detailthumbtitle ne '') {
1585	my ($str);
1586	$str = &interpolate_title_string ($detailthumbtitle, $pathname, 'detail');
1587	if ($str ne '') {
1588	    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
1589	}
1590    }
1591
1592    $anchortext .= ">";
1593    push(@details, $anchortext);
1594
1595    if (defined($skipthumb{$pathname})) {
1596	push(@details,"<IMG SRC=\"$filename\"");
1597    } else {
1598	push(@details,"<IMG SRC=\"$info{$pathname}{thumb}\"");
1599    }
1600    my $x = $info{$pathname}{'thumb_x'} / $detailshrink ;
1601    my $y = $info{$pathname}{'thumb_y'} / $detailshrink ;
1602    push(@details, sprintf(" WIDTH=\"%d\" HEIGHT=\"%d\"", $x, $y));
1603    push(@details," ALT=\" $filename \"");
1604    push(@details," CLASS=\"index\"");
1605    push(@details,"></A><BR>");
1606    push(@details,"$filename<BR>");
1607    push(@details,"</DIV>\n");
1608    push(@details,"          </TD>\n\n");
1609    push(@details,"          <TD VALIGN=\"middle\" ALIGN=\"left\">\n");
1610    push(@details,"            <DIV CLASS=\"detail\">");
1611    push(@details,"Original:&nbsp;<A HREF=\"$filename\">$info{$pathname}{geometry}</A>");
1612    push(@details,"&nbsp;($info{$pathname}{size})<BR>");
1613    unless (defined($skipmedium{$pathname})) {
1614	push(@details,"Medium:&nbsp;<A HREF=\"$info{$pathname}{medium}\">");
1615	push(@details,$info{$pathname}{'med_x'} . 'x' . $info{$pathname}{'med_y'} . "</A>");
1616	push(@details,"&nbsp;($info{$pathname}{med_size})<BR>");
1617    }
1618    unless (defined($skipthumb{$pathname})) {
1619	push(@details,"Thumbnail:&nbsp;<A HREF=\"$info{$pathname}{thumb}\">");
1620	push(@details,$info{$pathname}{'thumb_x'} . 'x' . $info{$pathname}{'thumb_y'} . "</A>");
1621	push(@details,"&nbsp;($info{$pathname}{thumb_size})<BR>");
1622    }
1623
1624    if (defined ($info{$pathname}{'is_multi_image_file'}) &&
1625	$info{$pathname}{'is_multi_image_file'} == 1) {
1626	push(@details,"Multi-image File:&nbsp;$info{$pathname}{scenes} scenes<BR>");
1627    }
1628
1629    # Video file stuff
1630    #
1631    if (defined ($info{$pathname}{'video_format'})) {
1632	push (@details, "Video Format:&nbsp;$info{$pathname}{'video_format'}<BR>");
1633    }
1634    if (defined ($info{$pathname}{'video_bitrate'})) {
1635	push (@details, "Video Bitrate:&nbsp;$info{$pathname}{'video_bitrate'}<BR>");
1636    }
1637    if (defined ($info{$pathname}{'video_fps'})) {
1638	push (@details, "Video Rate:&nbsp;$info{$pathname}{'video_fps'} f/s<BR>");
1639    }
1640    if (defined ($info{$pathname}{'audio_codec'})) {
1641	push (@details, "Audio Codec:&nbsp;$info{$pathname}{'audio_codec'}<BR>");
1642    }
1643    if (defined ($info{$pathname}{'audio_bitrate'})) {
1644	push (@details, "Audio Bitrate:&nbsp;$info{$pathname}{'audio_bitrate'} kbit/s<BR>");
1645    }
1646    if (defined ($info{$pathname}{'length'})) {
1647	push (@details, "Length (time):&nbsp;$info{$pathname}{'length'} sec<BR>");
1648    }
1649
1650    #
1651    # EXIF data
1652    #
1653    if (defined($info{$pathname}{'flash'})) {
1654	push(@details,"Flash:&nbsp;$info{$pathname}{flash}<BR>");
1655    }
1656    if (defined($info{$pathname}{'exposure_time'})) {
1657	push(@details,"Exposure time:&nbsp;$info{$pathname}{exposure_time}<BR>");
1658    }
1659    if (defined($info{$pathname}{'focus_dist'})) {
1660	push(@details,"Focus distance:&nbsp;$info{$pathname}{focus_dist}<BR>");
1661    }
1662    if (defined($info{$pathname}{'focal_length'})) {
1663	push(@details,"Focal length:&nbsp;$info{$pathname}{focal_length}<BR>");
1664    }
1665    if (defined($info{$pathname}{'aperture'})) {
1666	push(@details,"Aperture:&nbsp;$info{$pathname}{aperture}<BR>");
1667    }
1668
1669    push(@details,"\n");
1670    push(@details,"            </DIV>\n");
1671    push(@details,"          </TD>\n");
1672    push(@details,"        </TR>\n");
1673    push(@details,"      </TABLE>\n");
1674    push(@details,"    </TD>\n");
1675
1676    # At end of row?
1677    if ($col_counter == $current_columns) {
1678	push(@details, "  </TR>\n");
1679    }
1680
1681
1682}
1683
1684sub delete_tmp_jpg_dir {
1685    my ($tmp_jpg_dir) = @_;
1686    while ($name = <$tmp_jpg_dir/*>) {
1687	unlink ($name);
1688    }
1689    rmdir $tmp_jpg_dir;
1690}
1691
1692sub extract_first_frame_jpg {
1693
1694    my ($filename, $tmp_jpg_dir, $mplayer_prog) = @_;
1695    my ($retval, $cmd);
1696
1697    # Need to give 2 frames here. On some MPG files mplayer produces no output when
1698    # you request 1 frame.
1699    $qm_filename = quotemeta($filename);
1700    $cmd = "$mplayer_prog $qm_filename -noautosub -nosound -vo jpeg:outdir=${tmp_jpg_dir}:quality=100 -frames 2 > /dev/null 2>&1";
1701    print "About to execute: '$cmd'\n" if $opt_debug;
1702    &flush(STDOUT);
1703    $retval = system ($cmd);
1704    if ($retval) {
1705	printf ("warning: mplayer returned %d\n", $? >> 8);
1706    } else {
1707	return "${tmp_jpg_dir}/00000001.jpg";
1708    }
1709
1710}
1711######################################################################
1712#
1713# Extract info from a given file
1714#
1715######################################################################
1716sub extract_file_info {
1717
1718    my $filename = shift (@_);
1719    my $pathname = "$srcdir/$filename";
1720    my $retval;
1721
1722    if ($filename =~ /\.${video_regexp}$/i) {
1723	$retval = &extract_movie_info ($filename);
1724	# mplayer told us that this wasn't a movie file so at least yell
1725	# at the user so that the video regexp might be adjusted
1726	if ($retval == -1) {
1727	    print "\nwarning: $pathname identified by extension as video file but mplayer doesn't recognize it\n";
1728	    &flush (STDOUT);
1729	}
1730    } else  {
1731	&extract_image_info ($filename);
1732    }
1733
1734}
1735
1736######################################################################
1737#
1738# Extract info from movie file
1739#
1740######################################################################
1741sub extract_movie_info {
1742
1743    my $filename = shift (@_);
1744    my $pathname = "$srcdir/$filename";
1745    my ($retval, $cmd, $qt_found, $tmp);
1746    my $mplayer_prog = &find_in_path ('mplayer');
1747    my ($format, $bitrate, $x, $y, $fps, $aspect, $acodec, $abitrate);
1748    my ($arate, $anch, $length, $is_video);
1749
1750    print ".";
1751    &flush (STDOUT);
1752
1753    if ($mplayer_prog eq '' || ($do_video_files == 0)) {
1754	if (($do_video_files != 0) && $mplayer_prog eq '') {
1755	    print "\nwarning: Trying to process video files but cannot find mplayer in \$path!\n";
1756	    &flush (STDOUT);
1757	}
1758	print "\nSkipping $pathname";
1759	&flush (STDOUT);
1760	return 0;
1761    } else {
1762	$object_counter++;
1763	$image_counter++;
1764    }
1765
1766    $qm_pathname = quotemeta($pathname);
1767
1768    $cmd = "$mplayer_prog -noautosub -vo null -ao null -frames 0 -identify $qm_pathname 2> /dev/null |";
1769
1770    if (! open (PIPE, $cmd)) {
1771	printf (STDERR "Could not open pipe from mplayer! - $!\n");
1772	return 0;
1773    }
1774
1775    $is_video = 0;
1776
1777    $meta_data_create_time_found = 0;
1778
1779    $qt_found = 0;
1780
1781    while (<PIPE>) {
1782	if (/VIDEO:\s+(\S+)/) {
1783	    $format = $1;
1784	    $format =~ s/\[//g;
1785	    $format =~ s/\]//g;
1786	}
1787	if (/QuickTime.*detected/) {
1788	    $qt_found = 1;
1789	}
1790	if (/ID_VIDEO_FORMAT=(\S+)/) {
1791	    $tmp = $1;
1792	    if ($format eq 'jpeg' && $qt_found) {
1793		$tmp = 'QuickTime';
1794	    }
1795	    if ($tmp =~ /0x/) {
1796		$tmp = $format;
1797	    }
1798	    $format = $tmp;
1799	    $is_video = 1;
1800	}
1801	if (/ID_VIDEO_BITRATE=(\S+)/) {
1802	    $tmp = $1;
1803	    if ($tmp == 0) {
1804		$tmp = 'VBR';
1805	    }
1806	    $bitrate = $tmp;
1807	}
1808        # Grab these fields--sometimes we can infer the creation time from them without
1809        # having to rely on the date/time stamp in the filesystem which might be wrong.
1810        #
1811        if (/ID_CLIP_INFO_NAME(\d+)=/) {
1812            ($junk, $val) = split (/=/, $_);
1813            chomp ($val);
1814            $clip_info{$1} = $val;
1815        }
1816        if (/ID_CLIP_INFO_VALUE(\d+)=/) {
1817            ($junk, $val) = split (/=/, $_);
1818            chomp ($val);
1819            $clip_info_value{$1} = $val;
1820        }
1821	if (/ID_VIDEO_WIDTH=(\S+)/) {
1822	    $x = $1;
1823	}
1824	if (/ID_VIDEO_HEIGHT=(\S+)/) {
1825	    $y = $1;
1826	}
1827	if (/ID_VIDEO_FPS=(\S+)/) {
1828	    $fps = $1;
1829	}
1830	if (/ID_VIDEO_ASPECT=(\S+)/) {
1831	    $aspect = $1;
1832	}
1833	if (/ID_AUDIO_CODEC=(\S+)/) {
1834	    $acodec = $1;
1835	}
1836	if (/ID_AUDIO_BITRATE=(\S+)/) {
1837	    $tmp = $1;
1838	    if ($tmp == 0) {
1839		$tmp = 'VBR';
1840	    }
1841	    $abitrate = $tmp;
1842	}
1843	if (/ID_AUDIO_RATE=(\S+)/) {
1844	    $arate = $1;
1845	}
1846	if (/ID_AUDIO_NCH=(\S+)/) {
1847	    $anch = $1;
1848	}
1849	if (/ID_LENGTH=(\S+)/) {
1850	    $length = $1;
1851	}
1852
1853    }
1854
1855    foreach $val (keys %clip_info) {
1856        if ($clip_info{$val} eq "creation_time") {
1857            $meta_data_create_time_found = 1;
1858            $video_creation_time = $clip_info_value{$val};
1859            $video_creation_time =~ s/\-//g;
1860            $video_creation_time =~ s/\s//g;
1861            # just delete one of them
1862            $video_creation_time =~ s/://;
1863            # and make last one a '.'
1864            $video_creation_time =~ s/:/./;
1865        }
1866        if ($clip_info{$val} eq "Digitization Time") {
1867            my (%number_months, $tmp, $month, $day, $time, $year);
1868
1869            $meta_data_create_time_found = 1;
1870            $tmp = $clip_info_value{$val};
1871
1872            %number_months = (
1873                'jan' => '01',
1874                'feb' => '02',
1875                'mar' => '03',
1876                'apr' => '04',
1877                'may' => '05',
1878                'jun' => '06',
1879                'jul' => '07',
1880                'aug' => '08',
1881                'sep' => '09',
1882                'oct' => '10',
1883                'nov' => '11',
1884                'dec' => '12');
1885
1886            if ($tmp =~ /(\S+)\s+(\S+)\s+(\S+)\s+([\d:]+)\s+(\S+)/ ) {
1887                $month = $2;
1888                $day = $3;
1889                $time = $4;
1890                $year = $5;
1891                $month =~ tr/A-Z/a-z/;
1892                $num_month = $number_months{$month};
1893                $tmp = "${year}-${num_month}-${day} $time";
1894            }
1895
1896            $video_creation_time = $tmp;
1897            $video_creation_time =~ s/\-//g;
1898            $video_creation_time =~ s/\s//g;
1899            # just delete one of them
1900            $video_creation_time =~ s/://;
1901            # and make last one a '.'
1902            $video_creation_time =~ s/:/./;
1903        }
1904
1905    }
1906
1907    close (PIPE);
1908
1909    if ($is_video) {
1910
1911	$info{$pathname}{'is_video'} = 1;
1912
1913	$info{$pathname}{'file'} = $filename;
1914	$info{$pathname}{'video_format'} = $format;
1915	$info{$pathname}{'video_bitrate'} = $bitrate;
1916	$info{$pathname}{'x'} = $x;
1917	$info{$pathname}{'y'} = $y;
1918	$info{$pathname}{'video_fps'} = $fps;
1919	$info{$pathname}{'video_aspect'} = $aspect;
1920	$info{$pathname}{'audio_codec'} = $acodec;
1921	$info{$pathname}{'audio_bitrate'} = $abitrate / 1000.0;
1922	$info{$pathname}{'audio_rate'} = $arate;
1923	$info{$pathname}{'audio_nch'} = $anch;
1924	$info{$pathname}{'length'} = $length;
1925
1926	my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
1927	    $atime,$mtime,$ctime,$blksize,$blocks) = stat($pathname);
1928
1929        if ($meta_data_create_time_found) {
1930            $info{$pathname}{'date'} = $video_creation_time;
1931        } else {
1932            $info{$pathname}{'date'} = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
1933        }
1934	$info{$pathname}{'size'} = &convert_to_kb($size);
1935
1936	$info{$pathname}{'geometry'} = $info{$pathname}{'x'} . "x" . $info{$pathname}{'y'};
1937
1938	$info{$pathname}{'format'} = $info{$pathname}{'video_format'};
1939
1940	my ($name,$path,$suffix) = fileparse($filename,'\.\S+');
1941
1942	if (-e "${name}.txt") {
1943	    my $text;
1944	    if (! open (IN, "${name}.txt")) {
1945		warn "Cannot open ${name}.txt for reading - $!\n";
1946	    } else {
1947		$text = <IN>;
1948		$info{$pathname}{'comment'} = $text;
1949		close (IN);
1950	    }
1951	}
1952
1953	$info{$pathname}{'thumb'} = "$thumbnail_dir/$name.jpg";
1954	$thumb_backref{"$thumbnail_dir/$name.jpg"} = $pathname;
1955
1956	$info{$pathname}{'medium'} = "$med_dir/$name.jpg";
1957	$med_backref{"$med_dir/$name.jpg"} = $pathname;
1958
1959	$info{$pathname}{'slide'} = "$slide_dir/$name.html";
1960	$slide_backref{"$slide_dir/$name.html"} = $pathname;
1961
1962	push(@{$timestamp{"$info{$pathname}{date}"}}, $pathname);
1963
1964	return (0);
1965    } else {
1966	return (1);
1967    }
1968
1969}
1970
1971######################################################################
1972#
1973# Extract info from image
1974#
1975######################################################################
1976sub extract_image_info {
1977
1978    my $filename = shift (@_);
1979    my $pathname = "$srcdir/$filename";
1980    my $image = new Image::Magick;
1981    my $retval;
1982    my $i;
1983
1984    print ".";
1985    &flush (STDOUT);
1986
1987    $retval = $image->Read($pathname);
1988
1989
1990    if ($retval ne "") {
1991	print "\nSkipping $pathname";
1992	&flush (STDOUT);
1993	return;
1994    } else {
1995	$object_counter++;
1996	$image_counter++;
1997    }
1998
1999    # iterate over the number of scenes in this image (if the file format
2000    # supports it). "normal" files will only have $image->[0] defined. If there
2001    # is a better way to do this, I'm all ears--even the C code embedded within
2002    # ImageMagick itself iterates like this--there doesn't seem to be an
2003    # 'attribute' that can be easily fetched.
2004    #
2005    for ($i = 0; defined $image->[$i]; $i++) {
2006	# empty
2007    }
2008
2009    if ($i > 1) {
2010	$info{$pathname}{'is_multi_image_file'} = 1;
2011	$info{$pathname}{'scenes'} = $i;
2012    } else {
2013	$info{$pathname}{'is_multi_image_file'} = 0;
2014	$info{$pathname}{'scenes'} = 1;
2015    }
2016
2017    $info{$pathname}{'file'} = $filename;
2018
2019    # Use mtime as a fallback date in case we don't have exif data
2020    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
2021	$atime,$mtime,$ctime,$blksize,$blocks) = stat($pathname);
2022
2023    $info{$pathname}{'date'} = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
2024    $info{$pathname}{'x'} = $image->Get('width');
2025    $info{$pathname}{'y'} = $image->Get('height');
2026    $info{$pathname}{'geometry'} = $info{$pathname}{'x'} . "x" . $info{$pathname}{'y'};
2027
2028    $info{$pathname}{'size'} = &convert_to_kb($image->Get('filesize'));
2029
2030    $info{$pathname}{'format'} = $image->Get('format');
2031
2032    $info{$pathname}{'comment'} = $image->Get('comment');
2033
2034    my ($name,$path,$suffix) = fileparse($filename,'\.\S+');
2035
2036    if ($info{$pathname}{'format'} =~ /JFIF/i) {
2037
2038	$info{$pathname}{'thumb'} = "$thumbnail_dir/$filename";
2039	$thumb_backref{"$thumbnail_dir/$filename"} = $pathname;
2040
2041	$info{$pathname}{'medium'} = "$med_dir/$filename";
2042	$med_backref{"$med_dir/$filename"} = $pathname;
2043
2044	if (defined(&image_info)) {
2045
2046	    my $exif = image_info("$pathname");
2047
2048	    if (my $error = $exif->{error}) {
2049		warn "Can't parse image info: $error\n";
2050	    }
2051
2052	    if (defined($opt_debug)) {
2053		print "EXIF data for $pathname:\n";
2054		foreach (keys %$exif) {
2055		    print "   $_ = $exif->{$_}\n";
2056		}
2057		print "\n";
2058	    }
2059
2060	    if (defined($exif->{DateTimeOriginal})) {
2061		# some models use / instead of : in this field... (need to check spec)
2062		$exif->{DateTimeOriginal} =~ s/\//:/g;
2063		$exif->{DateTimeOriginal} =~ /\s*([\d:]+)\s+([\d:]+)/;
2064		my $dt = $1;
2065		my $tm = $2;
2066		$tm =~ s/://;
2067		$tm =~ s/:/\./;
2068		$dt =~ s/://g;
2069		$info{$pathname}{'date'} = $dt . $tm;
2070	    }
2071
2072	    if (defined($exif->{Flash})) {
2073		$info{$pathname}{'flash'} = $exif->{'Flash'};
2074		$info{$pathname}{'flash'} =~ s/0/no/;
2075		$info{$pathname}{'flash'} =~ s/1/yes/;
2076	    }
2077
2078	    if (defined($exif->{FocalLength})) {
2079		$info{$pathname}{'focal_length'} = sprintf("%4.1fmm", eval("$exif->{FocalLength}"));
2080	    }
2081
2082	    if (defined($exif->{SubjectDistance})) {
2083		$info{$pathname}{'focus_dist'} = sprintf("%4.1fm", eval("$exif->{SubjectDistance}"));
2084	    }
2085
2086	    if (defined($exif->{ExposureTime})) {
2087		$info{$pathname}{'exposure_time'} = $exif->{ExposureTime} . 's';
2088	    }
2089
2090	    if (defined($exif->{FNumber})) {
2091		$info{$pathname}{'aperture'} =  "f/" . eval ("$exif->{FNumber}");
2092	    }
2093
2094	}
2095
2096    } else {
2097
2098	$info{$pathname}{'thumb'} = "$thumbnail_dir/$name.jpg";
2099	$thumb_backref{"$thumbnail_dir/$name.jpg"} = $pathname;
2100
2101	$info{$pathname}{'medium'} = "$med_dir/$name.jpg";
2102	$med_backref{"$med_dir/$name.jpg"} = $pathname;
2103
2104    }
2105
2106    $info{$pathname}{'slide'} = "$slide_dir/$name.html";
2107    $slide_backref{"$slide_dir/$name.html"} = $pathname;
2108
2109    push(@{$timestamp{"$info{$pathname}{date}"}}, $pathname);
2110
2111}
2112
2113
2114######################################################################
2115#
2116# Write HTML for directory entries
2117#
2118######################################################################
2119sub dir_entry {
2120
2121    my $dir = shift(@_);
2122    my $destdirname = "$destdir/$dir";
2123    my $srcdirname = "$srcdir/$dir";
2124    my $anchortext;
2125
2126    print "Processing directory $srcdirname\n";
2127
2128    # Recurse first
2129    if ($do_recurse == 1) {
2130	my $flags = "";
2131	$flags .= "-medium " if ($do_medium == 1);
2132	$flags .= "-nomedium " if ($do_medium == 0);
2133	$flags .= "-slide " if ($do_slide == 1);
2134	$flags .= "-noslide " if ($do_slide == 0);
2135	$flags .= "-dirs " if ($do_dirs == 1);
2136	$flags .= "-nodirs " if ($do_dirs == 0);
2137	$flags .= "-montage " if ($do_montage == 1);
2138	$flags .= "-nomontage " if ($do_montage == 0);
2139	$flags .= "-detail " if ($do_detail == 1);
2140	$flags .= "-nodetail " if ($do_detail == 0);
2141	$flags .= "-reverse " if ($do_reverse == 1);
2142	$flags .= "-noreverse " if ($do_reverse == 0);
2143	$flags .= "-forceregen " if (defined($opt_forceregen));
2144	$flags .= "-includeall " if (defined($opt_includeall));
2145	$flags .= "-columns $current_columns " if (defined($opt_columns));
2146	$flags .= "-x $opt_x " if (defined($opt_x));
2147	$flags .= "-y $opt_y " if (defined($opt_y));
2148	$flags .= "-destdir $destdirname " if ($destdir ne $srcdir);
2149	foreach my $var (keys %opt_d) {
2150	    $flags .= " -d $var=$opt_d{$var}";
2151	}
2152	# If we're doing recursion and $updirtext is set either by default or
2153	# within a .imageindexrc file, then we're going to employ a terrible
2154	# hack here. We know that once this recursive call to ourselves returns
2155	# we're going to create an HTML page for the directory we're in now.
2156	# However, the recursive call will not "see" that file created and thus
2157	# the <a href> link isn't made back to "../$indexfile". We will tell
2158	# the recursive call to ourselves that it needs to think there's
2159	# a ../$indexfile file there regardless of whether it's there or not
2160	#
2161	if (defined $updirtext) {
2162	    $flags .= "-updirindexoverride ";
2163	}
2164	system("cd \"$srcdirname\" ;$0 $flags -recurse");
2165
2166    }
2167
2168    my $dirtitle = "";
2169    my $first;
2170    my $last;
2171    my $montage;
2172    my $montage_x;
2173    my $montage_y;
2174
2175    # Only add entry if this dir has an index file
2176    if (-r "$destdirname/$indexfile") {
2177
2178	# Go fetch the title and dates from the HTML
2179	my $tmp1 = &extract_meta_tag ($titlemetatag,"$destdirname/$indexfile");
2180	my $tmp2 = &extract_meta_tag ($begindatemetatag,"$destdirname/$indexfile");
2181	my $tmp3 = &extract_meta_tag ($enddatemetatag,"$destdirname/$indexfile");
2182	if (defined($tmp1)) {
2183	    $dirtitle = $tmp1;
2184	}
2185	if (defined($tmp2)) {
2186	    $first = $tmp2;
2187	}
2188	if (defined($tmp3)) {
2189	    $last = $tmp3;
2190	}
2191
2192	# If we found generated files in this dir, flag that we found something
2193	# valid to index
2194	$object_counter++;
2195	$dir_counter++;
2196
2197	# Set montage file if we found it
2198	if (($do_montage == 1) and ( -r "$destdirname/$thumbnail_dir/$montagefile")) {
2199
2200	    print "Found montage in $destdirname\n" if defined($opt_debug);
2201	    $montage = "$destdirname/$thumbnail_dir/$montagefile";
2202
2203	    my $image = new Image::Magick;
2204	    my $retval;
2205
2206	    $retval = $image->Read(filename=>$montage);
2207	    warn "$retval" if "$retval";
2208
2209	    $montage_x = $image->Get('width');
2210	    $montage_y = $image->Get('height');
2211
2212	}
2213
2214
2215	# At beginning of row?
2216	if ($col_counter == 1) {
2217	    push(@index, "<TR>\n");
2218	    push(@details, "<TR>\n");
2219	}
2220
2221	# Entry for this directory in main & details file
2222	push(@index, "<TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");
2223	push(@details, "<TD CLASS=\"index\" VALIGN=\"middle\" ALIGN=\"center\">\n");
2224
2225	push(@details, "<TABLE BORDER=\"0\" WIDTH=\"100%\">\n");
2226
2227	if (defined($montage)) {
2228	    push(@details, "<TR><TD VALIGN=\"middle\" ALIGN=\"center\">\n");
2229	} else {
2230	    push(@details, "<TR><TD COLSPAN=\"2\" VALIGN=\"middle\" ALIGN=\"center\">\n");
2231	}
2232
2233	if (defined($first)) {
2234
2235	    my ($tmp_first, $tmp_last);
2236
2237	    push(@index, "<DIV CLASS=\"index\">");
2238	    push(@details, "<DIV CLASS=\"detail\">");
2239
2240	    $tmp_first = &format_date ($first, 'index', 'dayonly');
2241	    $tmp_last = &format_date ($last, 'index', 'dayonly');
2242
2243	    if ($first ne $last) {
2244		push(@index, "$tmp_first - $tmp_last");
2245	    } else {
2246		push(@index, "$tmp_first");
2247	    }
2248
2249	    $tmp_first = &format_date ($first, 'detail', 'dayonly');
2250	    $tmp_last = &format_date ($last, 'detail', 'dayonly');
2251
2252	    if ($first ne $last) {
2253		push(@details, "$tmp_first - $tmp_last");
2254	    } else {
2255		push(@details, "$tmp_first");
2256	    }
2257
2258	    push(@index, "</DIV>\n");
2259	    push(@details, "</DIV>\n");
2260	}
2261
2262
2263	if (defined($montage)) {
2264
2265	    $anchortext = "<A HREF=\"$dir/$indexfile\"";
2266	    if (defined ($montagetitle) && $montagetitle ne '') {
2267		my ($str);
2268		$str = &interpolate_title_string_dir ($montagetitle, $dir, 'index');
2269		if ($str ne '') {
2270		    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
2271		}
2272	    }
2273	    $anchortext .= ">";
2274	    push(@index, $anchortext);
2275
2276	    push(@index, "<IMG CLASS=\"index\" SRC=\"$dir/$thumbnail_dir/$montagefile\"");
2277	    push(@index, " WIDTH=\"$montage_x\" HEIGHT=\"$montage_y\"");
2278	    push(@index, " ALT=\"\"");
2279	    push(@index, ">");
2280	    push(@index, "</A>\n");
2281
2282	    push(@index,"<DIV CLASS=\"index\">");
2283	    push(@index, "<A HREF=\"$dir/$indexfile\">$dir</A>");
2284	    push(@index,"</DIV>\n");
2285
2286	    $anchortext = "<A HREF=\"$dir/$detailfile\"";
2287	    if (defined ($montagetitle) && $montagetitle ne '') {
2288		my ($str);
2289		$str = &interpolate_title_string_dir ($montagetitle, $dir, 'index');
2290		if ($str ne '') {
2291		    $anchortext .= sprintf (" TITLE=\"%s\"", $str);
2292		}
2293	    }
2294	    $anchortext .= ">";
2295	    push(@details, $anchortext);
2296
2297	    push(@details, "<IMG CLASS=\"index\" SRC=\"$dir/$thumbnail_dir/$montagefile\"");
2298	    my $x = $montage_x / $detailshrink ;
2299	    my $y = $montage_y / $detailshrink ;
2300	    push(@details, sprintf(" WIDTH=\"%d\" HEIGHT=\"%d\"", $x, $y));
2301	    push(@details, " ALT=\"\"");
2302	    push(@details, ">");
2303	    push(@details, "</A>");
2304
2305	    push(@details, "</TD><TD VALIGN=\"middle\" ALIGN=\"left\">\n");
2306
2307	} else {
2308
2309	    push(@index,"<DIV CLASS=\"index\">");
2310	    push(@index, "<A HREF=\"$dir/$indexfile\">$dir</A>");
2311	    push(@index,"</DIV>\n");
2312
2313	}
2314
2315	push(@index, "<DIV CLASS=\"caption\">");
2316	push(@details, "<DIV CLASS=\"detail\">");
2317
2318	if ($dirtitle ne "") {
2319	    push(@index, "$dirtitle");
2320	    push(@details, "$dirtitle");
2321	}
2322
2323	push(@details, "<BR><A HREF=\"$dir/$detailfile\">$dir</A>");
2324
2325	push(@index, "</DIV>\n");
2326	push(@details, "</DIV>\n");
2327
2328	push(@details,"</TD></TR></TABLE>\n");
2329
2330	push(@index, "</TD>\n");
2331	push(@details, "</TD>\n");
2332
2333	# At end of row?
2334	if ($col_counter == $current_columns) {
2335	    push(@index, "</TR>\n");
2336	    push(@details, "</TR>\n");
2337	}
2338
2339
2340	# Increment for next item
2341	$col_counter++;
2342	$col_counter = 1 if ($col_counter > $current_columns);
2343
2344    } # if dir had index file
2345
2346}
2347
2348######################################################################
2349#
2350# Top of HTML index/detail files
2351#
2352######################################################################
2353sub page_header {
2354
2355    my $this = shift(@_);
2356    my $linkto = shift(@_);
2357    my $numlink = 0;
2358    my $verstring;
2359
2360    select(INDEX);
2361    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n";
2362    print "\"http://www.w3.org/TR/html401/strict.dtd\">\n";
2363    print "<HTML>\n";
2364    print "<HEAD>\n";
2365    $verstring = &versionstring();
2366    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
2367    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=%s\">\n", $charset);
2368    if (defined ($write_meta_tag{$titlemetatag})) {
2369	print "<META NAME=\"$titlemetatag\" CONTENT=\"$current_titletext\">\n";
2370    }
2371    if (defined ($write_meta_tag{$columnsmetatag})) {
2372	print "<META NAME=\"$columnsmetatag\" CONTENT=\"$current_columns\">\n";
2373    }
2374    if (defined ($write_meta_tag{$thumbxmetatag})) {
2375	print "<META NAME=\"$thumbxmetatag\" CONTENT=\"$current_thumbnail_x\">\n";
2376    }
2377    if (defined ($write_meta_tag{$thumbymetatag})) {
2378	print "<META NAME=\"$thumbymetatag\" CONTENT=\"$current_thumbnail_y\">\n";
2379    }
2380    if (defined ($write_meta_tag{$reversemetatag})) {
2381	print "<META NAME=\"$reversemetatag\" CONTENT=\"$current_reverse\">\n";
2382    }
2383    if (defined($firstdate)) {
2384	print "<META NAME=\"$begindatemetatag\" CONTENT=\"$firstdate\">\n";
2385    }
2386    if (defined($lastdate)) {
2387	print "<META NAME=\"$enddatemetatag\" CONTENT=\"$lastdate\">\n";
2388    }
2389    if (!defined ($opt_includeall) && defined (@opt_exclude) && scalar (@opt_exclude)) {
2390	my $tmp = join (',', @opt_exclude);
2391	my $etmp;
2392
2393	# We need to "encode" this string in the HTML so that raw filenames
2394	# (that people should not try to access) are not exposed to the
2395	# outside world.
2396	#
2397	$etmp = &encodestring ($tmp);
2398	printf ("<META NAME=\"$excludemetatag\" CONTENT=\"%s\">\n", $etmp);
2399    }
2400    printf ("<META NAME=\"$numimagesmetatag\" CONTENT=\"%d\">\n", $image_counter);
2401
2402    if (defined (@opt_skipmont) && scalar (@opt_skipmont)) {
2403	my $tmp = join (',', @opt_skipmont);
2404	printf ("<META NAME=\"$skipmetatag\" CONTENT=\"%s\">\n", $tmp);
2405    }
2406    print "<TITLE>$current_titletext</TITLE>\n";
2407    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"$stylefile\">\n";
2408    print "</HEAD>\n";
2409    print "<BODY>\n";
2410
2411    # Break out of frames
2412    print "<SCRIPT TYPE=\"text/javascript\">\n";
2413    print "if (parent.frames.length > 0) {\n";
2414    print "   parent.location.href = self.document.location\n";
2415    print "}\n";
2416    print "</SCRIPT>\n";
2417
2418    print "<H1 CLASS=\"title\">$current_titletext</H1>\n";
2419
2420    print "<H3>";
2421
2422    # On all these links, check to see if the variable is also defined. If
2423    # not (done in a .imageindexrc file perhaps) then skip the link
2424    # If the script-use-only flag updirindexoverride was given it means we've
2425    # come in from a recursive call and $updirtext was set--therefore there will
2426    # be $destdir/../$indexfile eventually ... link to it.
2427    #
2428    if ((defined ($opt_updirindexoverride) and ($do_dirs == 1)) or
2429	((-e "$destdir/../$indexfile") and ($do_dirs == 1) and defined($updirtext))) {
2430	print "<A HREF=\"../$indexfile\">$updirtext</A>";
2431	$numlink++;
2432    }
2433
2434    if (($do_slide == 1) and ($slide_counter > 1) and
2435	defined($framelinktext)) {
2436	print "&nbsp;&nbsp;|&nbsp;&nbsp;" if ($numlink != 0);
2437	print "<A HREF=\"$slide_dir/$framefile\">$framelinktext</A>";
2438	$numlink++;
2439    }
2440
2441    if (($do_detail == 1) and ($this eq 'index') and defined($detaillinktext)) {
2442	print "&nbsp;&nbsp;|&nbsp;&nbsp;" if ($numlink != 0);
2443	print "<A HREF=\"$detailfile\">$detaillinktext</A>";
2444	$numlink++;
2445    }
2446
2447    if (($this eq 'detail') and	defined($indexlinktext)) {
2448	print "&nbsp;&nbsp;|&nbsp;&nbsp;" if ($numlink != 0);
2449	print "<A HREF=\"$indexfile\">$indexlinktext</A>";
2450	$numlink++;
2451    }
2452
2453    print "\n<BR>\n" if ($numlink != 0);
2454
2455    print "</H3>\n";
2456
2457    if (defined($firstdate) and defined($lastdate)) {
2458
2459	my $tmp1 = &format_date($firstdate, $this, 'dayonly');
2460	my $tmp2 = &format_date($lastdate, $this, 'dayonly');
2461
2462	if ($tmp1 ne $tmp2) {
2463	    if ($current_reverse == 0) {
2464		print "<H2 CLASS=\"daterange\">$tmp1 - $tmp2</H2>\n";
2465	    } else {
2466		print "<H2 CLASS=\"daterange\">$tmp2 - $tmp1</H2>\n";
2467	    }
2468	} else {
2469	    print "<H2 CLASS=\"daterange\">$tmp1</H2>\n";
2470	}
2471    }
2472
2473    print "<TABLE WIDTH=\"100%\" CLASS=\"index\">\n";
2474
2475    select(STDOUT);
2476
2477}
2478
2479
2480######################################################################
2481#
2482# Bottom of HTML file
2483#
2484######################################################################
2485sub page_footer {
2486
2487    my $time = localtime(time);
2488
2489    my $progurl = 'http://www.edwinh.org/imageindex/';
2490
2491    select(INDEX);
2492
2493    print "</TABLE>\n";
2494
2495    print "<DIV CLASS=\"credits\">";
2496    print "<I>page created on $time</I><BR>\n";
2497    print "by <A HREF=\"$progurl\">imageindex</A> ";
2498    print &versionstring();
2499#    print "<BR>\n";
2500#    print "<A HREF=\"http://www.edwinh.org/\">Edwin Huffstutler</A> <I>&lt;edwinh at computer dot org&gt;</I>";
2501#    print "<BR>\n";
2502#    print "<A HREF=\"http://www.reynoldsnet.org/\">John Reynolds</A> <I>&lt;johnjen at reynoldsnet dot org&gt;</I>";
2503    print "</DIV>\n";
2504
2505    print "</BODY>\n</HTML>\n";
2506
2507    select(STDOUT);
2508}
2509
2510
2511######################################################################
2512#
2513# A "quickie" routine to show which files were excluded in a prior run
2514#
2515######################################################################
2516
2517sub showexcluded {
2518    my ($file) = @_;
2519    my ($rfile, $tmp, $utmp, @files, $str);
2520
2521    if (! defined ($file)) {
2522	if (-r $indexfile) {
2523	    $rfile = $indexfile;
2524	}
2525    }
2526    else {
2527	$rfile = $file;
2528    }
2529    $tmp = &extract_meta_tag ($excludemetatag, $rfile);
2530    if (defined($tmp)) {
2531	# We need to "decode" this string as it has been encoded for storage
2532	# in the HTML so that raw filenames (that people should not try to
2533	# access) are not exposed to the outside world.
2534	#
2535	$utmp = &decodestring ($tmp);
2536	(@files) = split (/,/, $utmp);
2537	$str = join (',', @files);
2538	printf ("File '$rfile' shows the following record of excluded files:\n");
2539	printf ("%s\n", $str);
2540    }
2541    else {
2542	printf ("File '$rfile' shows no record of excluded files.\n");
2543    }
2544    return;
2545}
2546
2547######################################################################
2548#
2549# Ignore certain files via META data stored in the index.html file
2550#
2551# Exports global variable %skipmont used later during montage
2552# generation.
2553#
2554######################################################################
2555sub exclude_files {
2556
2557    my @files = @_;
2558    my (@filelist, $f, %exclude, $token, @tokens);
2559
2560    undef %exclude;
2561
2562    # -skipmont flags override any META data found. Else, look for the META tag
2563    # then process. Check to see if any of the -skipmont options were given as
2564    # strings of filenames concatenated with ',' characters. If so, support it.
2565    #
2566    if (defined (@opt_skipmont)) {
2567	foreach (@opt_skipmont) {
2568	    (@tokens) = split (/,/, $_);
2569	    foreach $token (@tokens) {
2570		$skipmont{$token}++;
2571	    }
2572	}
2573    }
2574    elsif (-r "$destdir/$indexfile") {
2575	my $tmp = &extract_meta_tag ($skipmetatag, "$destdir/$indexfile");
2576	if (defined($tmp)) {
2577	    (@opt_skipmont) = split (/,/, $tmp);
2578	    my $str = join (',', @opt_skipmont);
2579	    printf ("Using saved skip-montage files: %s\n", $str);
2580	    foreach (@opt_skipmont) {
2581		$skipmont{$_}++;
2582	    }
2583	}
2584    }
2585
2586    # -exclude flags override any META data found. Else, look for the META tag
2587    # then process. Check to see if any of the -exclude options were given as
2588    # strings of filenames concatenated with ',' characters. If so, support it.
2589    #
2590    if (defined (@opt_exclude)) {
2591	# -includeall takes priority over -exclude on the commandline if they are
2592	# used together (wierd, but ...)
2593	#
2594	unless (defined ($opt_includeall)) {
2595	    foreach (@opt_exclude) {
2596		(@tokens) = split (/,/, $_);
2597		foreach $token (@tokens) {
2598		    $exclude{$token}++;
2599		}
2600	    }
2601	}
2602    }
2603    elsif (-r "$destdir/$indexfile") {
2604	my $tmp = &extract_meta_tag ($excludemetatag, "$destdir/$indexfile");
2605	my $utmp;
2606	if (defined($tmp) && !defined ($opt_includeall)) {
2607	    # We need to "decode" this string as it has been encoded for storage
2608	    # in the HTML so that raw filenames (that people should not try to
2609	    # access) are not exposed to the outside world.
2610	    #
2611	    $utmp = &decodestring ($tmp);
2612	    (@opt_exclude) = split (/,/, $utmp);
2613	    my $str = join (',', @opt_exclude);
2614	    printf ("Using saved excluded files: %s\n", $str);
2615	    foreach (@opt_exclude) {
2616		$exclude{$_}++;
2617	    }
2618	}
2619    }
2620
2621    foreach $f (@files) {
2622	if (! $exclude{$f}) {
2623	    push (@filelist, $f);
2624	} else {
2625	    print "Excluding '$f'\n";
2626	    if (-d $f) {
2627		chmod (0700, $f);
2628	    }
2629	    else {
2630		chmod (0600, $f);
2631	    }
2632	}
2633    }
2634    return (@filelist);
2635}
2636
2637
2638######################################################################
2639#
2640# Nuke generated files if original image gone
2641#
2642######################################################################
2643sub nuke_out_of_date {
2644    foreach my $checkdir ($thumbnail_dir, $med_dir, $slide_dir) {
2645	opendir(THUMBS,"$destdir/$checkdir") || die "Can't open dir $checkdir: ($!)\n";
2646	foreach (readdir(THUMBS)) {
2647	    next if (m/^\.?\.$/);
2648	    next if (m/$framefile/);
2649	    next if (m/$slidefile/);
2650	    next if (m/$montagefile/);
2651	    next if (m/$emoticonsmile/);
2652	    next if (m/$emoticonwink/);
2653	    next if (m/$emoticonfrown/);
2654	    if (!defined($thumb_backref{"$checkdir/$_"}) and
2655		!defined($slide_backref{"$checkdir/$_"}) and
2656		!defined($med_backref{"$checkdir/$_"})) {
2657		print "Removing stale $destdir/$checkdir/$_\n";
2658		unlink("$destdir/$checkdir/$_") || warn "Can't unlink $destdir/$checkdir/$_: ($!)\n";
2659		$modified_thumb++;
2660
2661	    }
2662	}
2663	closedir(THUMBS);
2664    }
2665
2666}
2667
2668######################################################################
2669#
2670# Convert bytes to kb string
2671#
2672######################################################################
2673sub convert_to_kb {
2674
2675    my $bytes = shift(@_);
2676    if ($bytes > (1024 * 1024)) {
2677	$bytes = sprintf("%.1fM", $bytes / (1024.0 * 1024.0));
2678    } else {
2679	$bytes = sprintf("%dk", $bytes / 1024);
2680    }
2681    return($bytes);
2682}
2683
2684######################################################################
2685#
2686# Sortq by integer date stamp
2687#
2688######################################################################
2689sub bynumber {
2690    if ($current_reverse == 0) {
2691	$a <=> $b;
2692    } else {
2693	$b <=> $a;
2694    }
2695}
2696
2697
2698######################################################################
2699#
2700# Write frameset file for slideshows
2701#
2702######################################################################
2703sub write_frameset {
2704
2705    # This is impossible to get rid of
2706    my $framefudge = 35;
2707    my $verstring;
2708
2709    open(FRAME,">$destdir/$slide_dir/$framefile") or die ("Can't open $destdir/$slide_dir/$framefile: $!\n");
2710
2711    select(FRAME);
2712
2713    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Frameset//EN\"\n";
2714    print "\"http://www.w3.org/TR/html401/frameset.dtd\">\n";
2715    print "<HTML>\n";
2716    print "<HEAD>\n";
2717    $verstring = &versionstring();
2718    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
2719    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=%s\">\n", $charset);
2720    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">";
2721    print "<TITLE>$current_titletext</TITLE>\n";
2722    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">\n";
2723    print "</HEAD>\n";
2724    if ($frame_orient eq 'horizontal') {
2725	printf("<FRAMESET ROWS=\"%d, *\">\n", $max_thumb_y + $framefudge);
2726    } else {
2727	printf("<FRAMESET COLS=\"%d, *\">\n", $max_thumb_x + $framefudge);
2728    }
2729    print "<FRAME NAME=\"thumb\" SRC=\"$slidefile\">\n";
2730    print "<FRAME NAME=\"view\" SRC=\"../$first_slide\">\n";
2731    print "<NOFRAMES>No frames in this browser...go back</NOFRAMES>\n";
2732    print "</FRAMESET>\n";
2733    print "</HTML>\n";
2734
2735    select(STDOUT);
2736    close (FRAME);
2737
2738
2739    open(FRAME,">$destdir/$slide_dir/$slidefile") or die ("Can't open $destdir/$slide_dir/$slidefile: $!\n");
2740    select(FRAME);
2741
2742
2743    print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\"\n";
2744    print "\"http://www.w3.org/TR/html401/strict.dtd\">\n";
2745    print "<HTML>\n";
2746    print "<HEAD>\n";
2747    $verstring = &versionstring();
2748    printf ("<META NAME=\"GENERATOR\" CONTENT=\"imageindex %s\">\n", $verstring);
2749    printf ("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=%s\">\n", $charset);
2750    print "<LINK TYPE=\"text/css\" REL=\"stylesheet\" HREF=\"../$stylefile\">\n";
2751    print "<TITLE>$current_titletext</TITLE>\n";
2752    print "</HEAD>\n<BODY>\n";
2753    print "<TABLE CLASS=\"frame\"\n";
2754    print "  <TR>\n" if ($frame_orient eq 'horizontal');
2755    foreach (@frame) {
2756	print;
2757    }
2758    print "  </TR>\n" if ($frame_orient eq 'horizontal');
2759    print "</TABLE>\n";
2760    print "</BODY>\n</HTML>\n";
2761
2762    select(STDOUT);
2763    close(FRAME);
2764
2765}
2766
2767
2768######################################################################
2769#
2770# Do next/index/prev links on slide pages
2771#
2772######################################################################
2773sub next_prev_links {
2774
2775    my $pathname = shift(@_);
2776
2777    print "<DIV CLASS=\"index\">";
2778
2779    if (defined($back{$pathname})) {
2780	print "<A HREF=\"$back{$pathname}\">&lt;&nbsp;previous</A>&nbsp;|&nbsp;";
2781    } else {
2782	print "&lt;&nbsp;previous&nbsp;|&nbsp;";
2783    }
2784    print "<A HREF=\"../$indexfile\" TARGET=\"_top\">index</A>";
2785    if (defined($forward{$pathname})) {
2786	print "&nbsp;|&nbsp;<A HREF=\"$forward{$pathname}\">next&nbsp;&gt;</A>";
2787    } else {
2788	print "&nbsp;|&nbsp;next&nbsp;&gt;";
2789    }
2790
2791    print "</DIV>\n";
2792
2793}
2794
2795
2796######################################################################
2797#
2798# Lower-case all the filenames. I hate the uppercase filenames that come
2799# from my camera's default software (and Windud software). Plus I didn't
2800# want this "utility" in another script, so just place it here.
2801#
2802######################################################################
2803sub lower_case_files {
2804    my (@files) = @_;
2805    my ($newfile, $lowername);
2806
2807    foreach $name (@files) {
2808	($lowername = $name) =~ tr/A-Z/a-z/;
2809	if ($name =~ /[A-Z]/) {
2810	    print "Moving '$name' to '$lowername'\n";
2811	    move("$name","$lowername");
2812	}
2813    }
2814}
2815
2816
2817######################################################################
2818#
2819# extract the NAME tag from an HTML file
2820#
2821######################################################################
2822sub extract_meta_tag {
2823    my ($tag, $filename) = @_;
2824    my ($name, $content, $retval);
2825
2826    if (! (open (FILE, $filename))) {
2827	print STDERR "Cannot open '$filename' for reading - $!\n";
2828	return (0);
2829    }
2830    # <META NAME="Columns" CONTENT="3">
2831    #
2832    while (<FILE>) {
2833	if (/<META\s+NAME=\"(.*?)\"\s+CONTENT=\"(.*)\">/) {
2834	    $name = $1;
2835	    $content = $2;
2836	    if ($name eq $tag) {
2837		$retval = $content;
2838		last;
2839	    }
2840	}
2841    }
2842    close (FILE);
2843    return ($retval);
2844}
2845
2846
2847###############################################################################
2848#
2849# Rotate given image 90 degrees
2850#
2851###############################################################################
2852sub rotate_image {
2853
2854    my $file = shift(@_);
2855    my $argv = shift(@_);
2856
2857    if ($file =~ m/^(cw|ccw)$/) {
2858	# If file is cw or ccw,
2859	# assume the args were given backwards
2860	my $tmp = $file;
2861	$file = $$argv[0];
2862	$$argv[0] = $tmp;
2863    }
2864
2865    -r "$file" || die("$file: ", $!);
2866    -w "$file" || die("$file: ", $!);
2867
2868
2869    # grab the mtime of the file so we can reset it after we update it
2870    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
2871	$ctime,$blksize,$blocks) = stat($file);
2872    my $posix_mtime = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
2873
2874    my ($name,$path,$suffix) = fileparse($file,'\.\S+');
2875    my $thumb;
2876    my $medium;
2877
2878    my $image = new Image::Magick;
2879
2880    my $retval = $image->Read("$file");
2881    warn "$retval" if "$retval";
2882
2883    if (!defined($$argv[0]) or
2884	($$argv[0] !~ m/^cc?w$/i)) {
2885	print "Need 'cw' or 'ccw' argument to rotate image clockwise/counterclockwise\n";
2886	exit(1);
2887    }
2888
2889    if ($$argv[0] =~ /^ccw$/i) {
2890	$deg = -90;
2891    } else {
2892	$deg = 90;
2893    }
2894
2895    print "Rotating $file $deg degrees\n";
2896    $retval = $image->Rotate($deg);
2897    warn "$retval" if "$retval";
2898
2899    $retval = $image->Write(filename=>"$file");
2900    warn "$retval" if "$retval";
2901
2902    system ("touch -t $posix_mtime $file");
2903
2904    # Nuke the generated images if they exist
2905    # (touching the timestamp above breaks automatic regeneration logic)
2906    if ($image->Get('format') =~ /JFIF/i) {
2907	$thumb = $path . "$thumbnail_dir/$name" . $suffix;
2908	$medium = $path . "$med_dir/$name" . $suffix;
2909    } else {
2910	$thumb = $path . "$thumbnail_dir/$name.jpg";
2911	$medium = $path . "$med_dir/$name.jpg";
2912    }
2913    unlink($thumb) if (-e "$thumb");
2914    unlink($medium) if (-e "$medium");
2915
2916
2917
2918}
2919
2920###############################################################################
2921#
2922# Set or display caption for a particular image
2923#
2924###############################################################################
2925sub caption_image {
2926
2927    my $file = shift(@_);
2928    my $argv = shift(@_);
2929    my ($esc_comment, $tmpfile);
2930
2931    -r "$file" || die("$file: ", $!);
2932
2933    # grab the mtime of the file so we can reset it after we update it
2934    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
2935	$ctime,$blksize,$blocks) = stat($file);
2936    my $posix_mtime = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
2937
2938    my $image = new Image::Magick;
2939
2940    my $retval = $image->Read("$file");
2941    warn "$retval" if "$retval";
2942
2943    my $format = $image->Get('format');
2944    warn "$retval" if "$retval";
2945
2946    # Set caption if another arg is present, or just display it
2947    if (defined($$argv[0])) {
2948
2949	-w "$file" || die("$file: ", $!);
2950
2951	# Try to find wrjpgcom so we can use it for adding captions to JPG images
2952	my $wrjpgcom_prog = &find_in_path ('wrjpgcom');
2953	my $quote_file = quotemeta ($file);
2954
2955	# If a jpeg file and we found a wrjpgcom program in our path, use
2956	# it! It simply puts the comment in the JPEG header without reading
2957	# (uncompressing) and writing (re-compressing) the file out so
2958	# there is no chance for data loss.
2959	#
2960	if (($format =~ /JFIF/i) and defined($wrjpgcom_prog)) {
2961
2962	    $tmpfile = "$file.$$";
2963	    my $tmpfile_quote = "$quote_file.$$";
2964	    $esc_comment = quotemeta ($$argv[0]);
2965	    # FIXME
2966	    # check to see how '?' and other punctuation is escaped and fix
2967	    # it seems things are not correct.
2968	    system ("$wrjpgcom_prog -replace -comment $esc_comment $quote_file > $tmpfile_quote");
2969	    if (($? >> 8) != 0) {
2970		printf(STDERR "Error in creating JPEG comment with 'wrjpgcom'. Leaving existing file intact.\n");
2971	    } else {
2972		move($tmpfile, $file);
2973	    }
2974
2975	} else {
2976	    # Fall back to PerlMagick's routines.
2977	    #
2978	    $retval = $image->Comment("$$argv[0]");
2979	    warn "$retval" if "$retval";
2980
2981	    $retval = $image->Write(filename=>"$file", quality=>"95",
2982				    sampling_factor=>"1x1");
2983	    warn "$retval" if "$retval";
2984	}
2985
2986	system ("touch -t $posix_mtime $quote_file");
2987
2988    } else {
2989
2990	my $text = $image->Get('comment');
2991
2992	if (defined($text)) {
2993	    print "$file: \"$text\"\n";
2994	} else {
2995	    print "$file: (no caption)\n";
2996	}
2997
2998    }
2999
3000}
3001
3002###############################################################################
3003#
3004# Set timestamp for a particular image
3005#
3006###############################################################################
3007sub timestamp_image {
3008
3009    my $file = shift(@_);
3010    my $argv = shift(@_);
3011    my ($esc_comment, $tmpfile);
3012
3013    -r "$file" || die("$file: ", $!);
3014
3015    # grab the mtime of the file so we can reset it after we update it
3016    my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
3017	$ctime,$blksize,$blocks) = stat($file);
3018    my $posix_mtime = POSIX::strftime ("%Y%m%d%H%M.%S",localtime($mtime));
3019
3020    my $image = new Image::Magick;
3021
3022    my $retval = $image->Read("$file");
3023    warn "$retval" if "$retval";
3024
3025    my $format = $image->Get('format');
3026    warn "$retval" if "$retval";
3027
3028    my $quote_file = quotemeta ($file);
3029
3030    # Set caption if another arg is present, or just display it
3031    if (defined($$argv[0])) {
3032
3033	-w "$file" || die("$file: ", $!);
3034
3035	system ("touch -t $posix_mtime $quote_file");
3036
3037    }
3038}
3039
3040###############################################################################
3041#
3042# Print usage info from top of file
3043#
3044###############################################################################
3045sub usage {
3046
3047    open(FILE,"$0") or die "Can't open $0: $OS_ERROR";
3048    while(<FILE>) {
3049	last if (m/^\#\s+USAGE:/);
3050    }
3051    while(<FILE>) {
3052	last if (m/^\#\#\#\#\#\#\#/);
3053	s/^\# ?//;
3054	print;
3055    }
3056    close(FILE);
3057
3058}
3059
3060######################################################################
3061#
3062# Format timestamp for HTML pages. This routine assumes that the date
3063# given to it is in YYYYMMDDHHMM.SS format that we've created from the
3064# EXIF/mtime date using strftime().
3065#
3066######################################################################
3067sub format_date {
3068    my ($date, $context, $dayonly) = @_;
3069    my ($timeformat, $dateformat);
3070
3071    if ($context eq 'frame') {
3072	$timeformat = $frametimeformat;
3073	$dateformat = $framedateformat;
3074    }
3075    elsif ($context eq 'index') {
3076	$timeformat = $indextimeformat;
3077	$dateformat = $indexdateformat;
3078    }
3079    elsif ($context eq 'slide') {
3080	$timeformat = $slidetimeformat;
3081	$dateformat = $slidedateformat;
3082    }
3083    else {
3084	$timeformat = $detailtimeformat;
3085	$dateformat = $detaildateformat;
3086    }
3087
3088    # Replace "macro" patterns in the format string first
3089    #
3090    $timeformat =~ s/\%R/\%H:\%M/g;
3091    $timeformat =~ s/\%r/\%I:\%M:\%S \%p/g;
3092    $dateformat =~ s/\%F/\%Y-\%m-\%d/g;
3093    $dateformat =~ s/\%D/\%m\/\%d\/\%y/g;
3094
3095    $date =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)?(\d\d)?\.?(\d\d)?/;
3096    my $year = $1;
3097    my $month = $2;
3098    my $day = $3;
3099    my $hour = $4;
3100    my $min = $5;
3101    my $sec = $6;
3102    my ($ampm, $two_digit_year, $twelve_hour);
3103
3104    if ($year =~ /^\d\d(\d\d)$/) {
3105	$two_digit_year = $1;
3106    }
3107    else {
3108	$two_digit_year = '??'; # shouldn't ever been seen
3109    }
3110
3111    # If we're told to, only format a date with no time
3112    #
3113    if (defined ($dayonly)) {
3114	$dateformat =~ s/\%Y/$year/g;
3115	$dateformat =~ s/\%y/$two_digit_year/g;
3116	$dateformat =~ s/\%m/$month/g;
3117	$dateformat =~ s/\%d/$day/g;
3118	$dateformat =~ s/\%\%/\%/g;
3119	return ($dateformat);
3120    }
3121    else {
3122	if (defined($hour)) {
3123	    $twelve_hour = $hour;
3124	    $ampm = 'AM';
3125	    if ($hour >= 12) {
3126		$ampm = 'PM';
3127	    }
3128	    if ($hour > 12) {
3129		$twelve_hour -= 12;
3130	    }
3131	}
3132	else {
3133	    $hour = '??';
3134	    $twelve_hour = '??';
3135	    $ampm = '??'; #again, should never be seen
3136	}
3137	if (! defined ($min)) {
3138	    $min = '??';
3139	}
3140	if (! defined ($sec)) {
3141	    $sec = '??';
3142	}
3143
3144	$dateformat =~ s/\%Y/$year/g;
3145	$dateformat =~ s/\%y/$two_digit_year/g;
3146	$dateformat =~ s/\%m/$month/g;
3147	$dateformat =~ s/\%d/$day/g;
3148	$dateformat =~ s/\%\%/\%/g;
3149
3150	$timeformat =~ s/\%S/$sec/g;
3151	$timeformat =~ s/\%M/$min/g;
3152	$timeformat =~ s/\%I/$twelve_hour/g;
3153	$timeformat =~ s/\%H/$hour/g;
3154	$timeformat =~ s/\%p/$ampm/g;
3155	$timeformat =~ s/\%\%/\%/g;
3156
3157	return("$dateformat $timeformat");
3158    }
3159
3160}
3161
3162######################################################################
3163#
3164# Return version string from CVS tag
3165#
3166######################################################################
3167sub versionstring {
3168
3169    my $ver = ' $Name: v2_0 $ ';
3170    $ver =~ s/Name//g;
3171    $ver =~ s/[:\$]//g;
3172    $ver =~ s/\s+//g;
3173    $ver =~ s/^v//g;
3174    $ver =~ s/_/\./g;
3175    if ($ver eq '') {
3176	$ver = "cvs devel - " . '$Revision: 1.183 $ ';
3177	# Nuke the $ signs -- what if somebody is keeping pages under RCS
3178	# or CVS control?
3179	$ver =~ s/\$//g;
3180	$ver =~ s/\s*$//;
3181    }
3182    return($ver);
3183
3184}
3185
3186###############################################################################
3187#
3188#  Create CSS file that is shared among the HTML pages
3189#
3190###############################################################################
3191sub write_css {
3192
3193
3194    open(CSS,">$destdir/$stylefile") or die ("Can't open $destdir/$stylefile: $!\n");
3195    select(CSS);
3196
3197    print $stylesheet;
3198
3199    select(STDOUT);
3200    close(CSS);
3201
3202
3203}
3204
3205###############################################################################
3206#
3207# "Interpolate" %? escapes found in our printf-like strings defined for the
3208# TITLE attributes. See the beginning of this file for their definition
3209#
3210###############################################################################
3211
3212sub interpolate_title_string {
3213    my ($formatstring, $pathname, $context) = @_;
3214    my ($filename, $date, $size, $resolution, $caption);
3215    my ($tmp);
3216
3217    $filename = $info{$pathname}{'file'};
3218    $date = &format_date ($info{$pathname}{'date'}, $context);
3219    $size = $info{$pathname}{'size'};
3220    $resolution = $info{$pathname}{'geometry'};
3221    $caption = $info{$pathname}{'comment'};
3222    if (! defined ($caption)) {
3223	$caption = '';
3224    }
3225    $tmp = $formatstring;
3226
3227    $tmp =~ s/\%f/$filename/g if $filename;
3228    $tmp =~ s/\%d/$date/g if $date;
3229    $tmp =~ s/\%s/$size/g if $size;
3230    $tmp =~ s/\%r/$resolution/g if $resolution;
3231    $tmp =~ s/\%c/$caption/g;
3232    $tmp =~ s/\%\%/%/g;
3233
3234    # In case the format string has " marks in it, change all those to '.
3235    # The " marks are needed to mark the argument to the TITLE attribute.
3236    #
3237    $tmp =~ s/\"/\'/g;
3238    return ($tmp);
3239
3240}
3241
3242###############################################################################
3243#
3244# "Interpolate" %? escapes found in our printf-like strings defined for the
3245# TITLE attributes. However, the %? escapes for this function are based on what
3246# you could conceivably need when processing a directory.
3247#
3248# See the beginning of this file for their definition
3249#
3250###############################################################################
3251
3252sub interpolate_title_string_dir {
3253    my ($formatstring, $dir, $context) = @_;
3254    my ($tmp, $num, $date, $metadate, $metatitle);
3255
3256    $tmp = $formatstring;
3257    $num = &extract_meta_tag($numimagesmetatag, "$srcdir/$dir/$indexfile");
3258
3259    # If we plucked out the number of images from the metadata of a directory's
3260    # index.html file, replace it. Else, give a warning if we didn't find it but
3261    # somebody still used %n
3262    #
3263    if (defined ($num)) {
3264	$tmp =~ s/\%n/$num/g if $num;
3265    }
3266    else {
3267	if ($tmp =~ /\%n/) {
3268	    if (!defined ($remember_warning{$dir})) {
3269		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");
3270		$remember_warning{$dir}++;
3271	    }
3272	}
3273    }
3274
3275    $metadate = &extract_meta_tag($begindatemetatag, "$srcdir/$dir/$indexfile");
3276    $date = &format_date ($metadate, $context, 'dayonly');
3277    $tmp =~ s/\%b/$date/g if $date;
3278
3279    $metadate = &extract_meta_tag($enddatemetatag, "$srcdir/$dir/$indexfile");
3280    $date = &format_date ($metadate, $context, 'dayonly');
3281    $tmp =~ s/\%e/$date/g if $date;
3282
3283    $metatitle = &extract_meta_tag($titlemetatag, "$srcdir/$dir/$indexfile");
3284    $tmp =~ s/\%t/$metatitle/g if $metatitle;
3285
3286    # In case the format string has " marks in it, change all those to '.
3287    # The " marks are needed to mark the argument to the TITLE attribute.
3288    #
3289    $tmp =~ s/\"/\'/g;
3290    return ($tmp);
3291
3292}
3293
3294###############################################################################
3295#
3296#  Look for external programs we depend on in the $PATH. It just finds the first
3297#  occurence of $prog in $PATH.
3298#
3299###############################################################################
3300sub find_in_path {
3301    my ($prog) = @_;
3302    my ($retval);
3303
3304    undef $retval;
3305    foreach $dir (split (/:/, $ENV{'PATH'})) {
3306	if (-r "$dir/$prog" && -x "$dir/$prog") {
3307	    $retval = "$dir/$prog";
3308	}
3309    }
3310    return ($retval);
3311}
3312
3313
3314###############################################################################
3315#
3316# Encode/decode routines for exclude filenames when stuffed in a meta tag
3317#
3318###############################################################################
3319sub encodestring {
3320    my ($tmp) = @_;
3321    my $etmp;
3322    $etmp = pack ("u*", $tmp);
3323    # Hack the string to get rid of \n chars so we can store it on 1 line
3324    $etmp =~ s/\n/..1xn!_ltr../g;
3325
3326    # Get rid of ampersands
3327    $etmp =~ s/\&/..xn!_ltr1../g;
3328
3329    # Get rid of double-quotes
3330    $etmp =~ s/\"/..sb!_lho1../g;
3331    return ($etmp);
3332}
3333
3334sub decodestring {
3335    my ($tmp) = @_;
3336    my $utmp;
3337
3338    # Unhack the string to bring back & characters
3339    $tmp =~ s/\.\.sb\!_lho1\.\./\"/g;
3340    $tmp =~ s/\.\.xn\!_ltr1\.\./\&/g;
3341
3342    # Unhack the string to bring back & characters
3343    $tmp =~ s/\.\.xn\!_ltr1\.\./\&/g;
3344
3345    # Unhack the string to bring back \n characters
3346    $tmp =~ s/\.\.1xn\!_ltr\.\./\n/g;
3347    $utmp = unpack ("u*", $tmp);
3348    return ($utmp);
3349}
3350
3351#############################################################################
3352#
3353#  This routine samples linearly (as possible) across the available files in
3354#  a directory. The first pass at sampling is a simple modulo function based
3355#  upon the ratio of files to the number of tiles we can use in the montage.
3356#  If that first pass sample did not produce enough files, then we go back
3357#  iteratively through the list and as evenly-as-possible select unused
3358#  files from those left in the pool.
3359#
3360#############################################################################
3361sub sample_files_for_montage {
3362    my (@files) = @_;
3363    my ($numdiv, $numchosen, $chunksize, $numfiles, $numleft);
3364    my ($i, $index, $f, @ret);
3365
3366    $numfiles = scalar (@files);
3367    $numdiv = sprintf ("%d", $numfiles / $montage_max);
3368    $numdiv++;
3369
3370    for ($i = 0; $i < $numfiles; $i++) {
3371	if (($i % $numdiv) == 0) {
3372	    $chosen{$files[$i]}++;
3373	}
3374    }
3375
3376    $numchosen = scalar (keys %chosen);
3377
3378    $numleft = $montage_max - $numchosen;
3379
3380    if ($numleft) {
3381	$chunksize = sprintf ("%d", $numfiles / $numleft);
3382	$index = 0;
3383	for ($i = 0; $i < $numleft; $i++) {
3384	    &mark_next_file_for_montage ($index + 1, $numfiles, @files);
3385	    $index = $index + $chunksize;
3386	}
3387    }
3388
3389    foreach $f (@files) {
3390	if ($chosen{$f}) {
3391	    push (@ret, $f);
3392	}
3393    }
3394
3395    return (@ret);
3396}
3397
3398#############################################################################
3399#
3400# cycle through the given list of files. If the list[$index] is already marked
3401# (via the global hash %chosen) then move onto the next one, etc.
3402#
3403#############################################################################
3404sub mark_next_file_for_montage {
3405    my ($index, $numfiles, @files) = @_;
3406    my ($i);
3407
3408    for ($i = $index; $i < $numfiles; $i++) {
3409	if (! $chosen{$files[$i]}) {
3410	    $chosen{$files[$i]}++;
3411	    last;
3412	}
3413    }
3414}
3415
3416###############################################################################
3417#
3418# Exclude certain filenames from the list of thumbnails to be used in the
3419# montage image.
3420#
3421###############################################################################
3422sub exclude_montage_files {
3423    my (@files) = @_;
3424    my (@tmp, $file);
3425
3426    foreach (@files) {
3427	$file = basename ($_);
3428	unless (defined ($skipmont{$file})) {
3429	    push (@tmp, $_);
3430	}
3431    }
3432    return (@tmp);
3433}
3434
3435###############################################################################
3436#
3437# "html-ize" a caption found in an image. Just in case there are certain
3438# characters used which we want to "escape."
3439#
3440###############################################################################
3441sub htmlize_caption {
3442    my ($caption, $slide) = @_;
3443
3444    $caption =~ s/\&/\&amp;/g;
3445    $caption =~ s/\</\&lt;/g;
3446    $caption =~ s/\>/\&gt;/g;
3447    $caption =~ s/\"/\&quot;/g;
3448
3449    # Help smiley's render in a "mo-better" way when they are at the end of a
3450    # caption and enclosed in parens
3451    #
3452    if ($caption =~ /(:\-?[\(\)])\s*\)\s*$/) {
3453	my $tmp = $1;
3454	$caption =~ s/:\-?[\(\)]\s*\)\s*$/$tmp\&nbsp;\)/;
3455    }
3456
3457    $caption = &emoticonify ($caption, $slide);
3458
3459    return ($caption);
3460
3461}
3462
3463###############################################################################
3464#
3465# Translate ASCII smiley's embedded into image captions into emoticons
3466#
3467###############################################################################
3468sub emoticonify {
3469    my ($caption, $slide) = @_;
3470    my ($thumbdir, $attr);
3471
3472    return ($caption) if (! defined ($do_emoticons) || $do_emoticons == 0);
3473
3474    # This is a hack, please ignore and move on ... nothing to see here.
3475    #
3476    $caption =~ s/\&nbsp;/NoNBrEaKaBleSpacE/g;
3477
3478    $thumbdir = $thumbnail_dir;
3479    if ($slide) {
3480	$thumbdir = '../' . $thumbdir;
3481    }
3482    $attr = 'STYLE="vertical-align: middle;" WIDTH="19" HEIGHT="19"';
3483
3484    if ($caption =~ s/:\-?\)/\<IMG SRC=\"$thumbdir\/$emoticonsmile\" $attr ALT=\" \[smiley icon\] \"\>/g) {
3485	$emoticon{'smile'}++;
3486    }
3487    if ($caption =~ s/;\-?\)/\<IMG SRC=\"$thumbdir\/$emoticonwink\" $attr ALT=\" \[smiley icon\] \"\>/g) {
3488	$emoticon{'wink'}++;
3489    }
3490    if ($caption =~ s/:\-?\(/\<IMG SRC=\"$thumbdir\/$emoticonfrown\" $attr ALT=\" \[frown icon\] \"\>/g) {
3491	$emoticon{'frown'}++;
3492    }
3493
3494    # Undo the hack
3495    #
3496    $caption =~ s/NoNBrEaKaBleSpacE/\&nbsp;/g;
3497    return ($caption);
3498
3499}
3500
3501###############################################################################
3502#
3503# Write out PNG files representing the emoticons
3504#
3505###############################################################################
3506sub write_emoticon_png {
3507    my ($type) = @_;
3508    my ($img);
3509
3510    if (! open (IMG, '>' . $destdir . '/' . $thumbnail_dir . "/$emoticonprefix" . $icon . ".png")) {
3511	printf (STDERR "Could not open emoticon file for '$icon' for writing - $!\n");
3512	return;
3513    }
3514    # UUDecode the small PNG files that represent the emoticons and dump them to
3515    # the appropriate files in $thumbnail_dir
3516    #
3517    $img = unpack ("u*", $png{$type});
3518    print IMG $img;
3519    close (IMG);
3520}
3521
3522###############################################################################
3523#
3524# Write out PNG files representing the emoticons
3525#
3526###############################################################################
3527sub write_video_icons {
3528    my ($tmp_jpg_dir) = @_;
3529    my ($img);
3530
3531    foreach $key (keys %png) {
3532	next if $key !~ /video/;
3533	if (! open (IMG, '>' . $tmp_jpg_dir . '/' . $key . ".png")) {
3534	    printf (STDERR "Could not open video icon file for '$key' for writing - $!\n");
3535	    return;
3536	}
3537	# UUDecode the small PNG files that represent the emoticons and dump them to
3538	# the appropriate files in $thumbnail_dir
3539	#
3540	$img = unpack ("u*", $png{$key});
3541	print IMG $img;
3542	$max_video_icons++;
3543	close (IMG);
3544    }
3545}
3546
3547###############################################################################
3548#
3549# Create a montage of images in the current directory. This image will be
3550# pointed to by the parent directory's index.html file to show a sort of
3551# "thumbnail preview" of the contents of this directory.
3552#
3553###############################################################################
3554
3555sub create_montage {
3556
3557    my @files = @_;
3558    my (@modfiles);
3559
3560    @files = &exclude_montage_files (@files);
3561
3562    foreach (@files) {
3563	push (@modfiles, quotemeta ($_));
3564    }
3565
3566    # If we have defined that a lesser number of "tiles" can be used in the
3567    # montage vs. the # of files in this directory, then we'll "sample" the
3568    # files as evenly as possible to avoid clustering of shots that might be
3569    # similar to each other.
3570    #
3571    if (scalar (@modfiles) > $montage_max) {
3572	@modfiles = &sample_files_for_montage (@modfiles);
3573    }
3574
3575    if ($do_montage == 1) {
3576
3577	if (($modified_thumb != 0) or (! -e "$destdir/$thumbnail_dir/$montagefile")) {
3578
3579	    my $number = $#modfiles + 1;
3580	    my $tile_x = 1;;
3581	    my $tile_y = 1;
3582
3583	    # FIXME these both blindly expand x before expanding y
3584	    # Should this depend on some aspect ratio?
3585	    while(($tile_x * $tile_y) < $montage_min) {
3586		$tile_x++;
3587		$tile_y++ if (($tile_x * $tile_y) < $montage_min);
3588	    }
3589	    while(($tile_x * $tile_y) < $number) {
3590		$tile_x++;
3591		$tile_y++ if (($tile_x * $tile_y) < $number);
3592	    }
3593
3594	    my $index = 0;
3595	    while (($#modfiles + 1) < ($tile_x * $tile_y)) {
3596		if ($montage_fill eq 'blank') {
3597		    push(@modfiles, "NULL:");
3598		} else {
3599		    push(@modfiles, $modfiles[$index]);
3600		    $index = ($index+1) % $number;
3601
3602		}
3603	    }
3604
3605	    my $tile = sprintf("%dx%d", $tile_x, $tile_y);
3606	    my $geom = sprintf("%dx%d", $max_mont_thumb_x, $max_mont_thumb_y);
3607	    my $newgeom = sprintf("%dx%d", $current_thumbnail_x, $current_thumbnail_y);
3608
3609	    print "Picked $tile array of $geom for montage\n" if ($opt_debug);
3610
3611	    print "Creating $destdir/$thumbnail_dir/$montagefile\n";
3612
3613	    system("montage -quality $thumb_quality -bordercolor white -transparent white -border $montage_whitespace -geometry $geom -tile $tile @modfiles \"$destdir/$thumbnail_dir/$montagefile\"");
3614	    if (($? >> 8) != 0) {
3615		printf(STDERR "Error in creating montage file\n");
3616		return(-1);
3617	    }
3618
3619	    # Resize to std. thumbnail
3620	    my $image = new Image::Magick;
3621	    my $retval;
3622
3623	    $retval = $image->Read(filename=>"$destdir/$thumbnail_dir/$montagefile");
3624	    warn "$retval" if "$retval";
3625	    $retval = $image->Resize(geometry=>$newgeom);
3626	    warn "$retval" if "$retval";
3627	    $retval = $image->Set(interlace=>Line);
3628	    warn "$retval" if "$retval";
3629	    $retval = $image->Write(filename=>"$destdir/$thumbnail_dir/$montagefile");
3630	    warn "$retval" if "$retval";
3631
3632	}
3633
3634    } else {
3635
3636	unlink("$destdir/$thumbnail_dir/$montagefile")
3637	    if (-e "$destdir/$thumbnail_dir/$montagefile");
3638
3639    }
3640
3641}
3642
3643sub read_stored_meta_data {
3644    my ($tmp);
3645
3646    if (-r "$destdir/$indexfile") {
3647	$tmp = &extract_meta_tag ($columnsmetatag, "$destdir/$indexfile");
3648	# If we found data, check it against program defaults
3649	if (defined ($tmp)) {
3650	    $current_columns = $tmp;
3651	    print "Using saved number of columns: $current_columns\n" if ! defined ($opt_columns);
3652	}
3653
3654	$tmp = &extract_meta_tag ($titlemetatag, "$destdir/$indexfile");
3655	# If we found data, check it against program defaults
3656	if (defined ($tmp)) {
3657	    $current_titletext = $tmp;
3658	    print "Using saved title: $current_titletext\n" if ! defined ($opt_title);
3659	}
3660
3661	$tmp = &extract_meta_tag ($thumbxmetatag, "$destdir/$indexfile");
3662	# If we found data, check it against program defaults
3663	if (defined ($tmp)) {
3664	    $current_thumbnail_x = $tmp;
3665	    print "Using saved thumbnail X size: $current_thumbnail_x\n" if ! defined ($opt_x);
3666	}
3667
3668	$tmp = &extract_meta_tag ($thumbymetatag, "$destdir/$indexfile");
3669	# If we found data, check it against program defaults
3670	if (defined ($tmp)) {
3671	    $current_thumbnail_y = $tmp;
3672	    print "Using saved thumbnail Y size: $current_thumbnail_y\n" if ! defined ($opt_y);
3673	}
3674
3675	$tmp = &extract_meta_tag ($reversemetatag, "$destdir/$indexfile");
3676	# If we found data, check it against program defaults
3677	if (defined ($tmp)) {
3678	    $current_reverse = $tmp;
3679	    print "Using saved reverse: $current_reverse\n" if ! defined ($opt_reverse);
3680	}
3681
3682	&decide_which_md_to_store();
3683    }
3684}
3685
3686sub override_by_commandline {
3687    if (defined($opt_columns)) {
3688	$current_columns = $opt_columns;
3689    }
3690    if (defined($opt_title)) {
3691	$current_titletext = $opt_title;
3692    }
3693    if (defined($opt_reverse)) {
3694	$current_reverse = $opt_reverse;
3695    }
3696    if (defined($opt_x)) {
3697	$current_thumbnail_x = $opt_x;
3698	if ($current_thumbnail_x != $default_thumbnail_x) {
3699	    $opt_forceregen = 1;
3700	}
3701    }
3702    if (defined($opt_y)) {
3703	$current_thumbnail_y = $opt_y;
3704	if ($current_thumbnail_y != $default_thumbnail_y) {
3705	    $opt_forceregen = 1;
3706	}
3707    }
3708    &decide_which_md_to_store();
3709}
3710
3711sub decide_which_md_to_store {
3712    if ($current_columns != $default_columns) {
3713	$write_meta_tag{$columnsmetatag}++;
3714    }
3715    else {
3716	undef $write_meta_tag{$columnsmetatag};
3717    }
3718
3719    if ($current_thumbnail_x != $default_thumbnail_x) {
3720	$write_meta_tag{$thumbxmetatag}++;
3721    }
3722    else {
3723	undef $write_meta_tag{$thumbxmetatag};
3724    }
3725
3726    if ($current_thumbnail_y != $default_thumbnail_y) {
3727	$write_meta_tag{$thumbymetatag}++;
3728    }
3729    else {
3730	undef $write_meta_tag{$thumbymetatag};
3731    }
3732
3733    if ($current_titletext ne $default_titletext) {
3734	$write_meta_tag{$titlemetatag}++;
3735    }
3736    else {
3737	undef $write_meta_tag{$titlemetatag};
3738    }
3739
3740    if ($current_reverse ne $do_reverse) {
3741	$write_meta_tag{$reversemetatag}++;
3742    }
3743    else {
3744	undef $write_meta_tag{$reversemetatag};
3745    }
3746}
3747
3748sub flush {
3749    local($old) = select(shift);
3750    $| = 1;
3751    print "";
3752    $| = 0;
3753    select($old);
3754}
3755
3756sub initialize_current_vars {
3757    $current_columns = $default_columns;
3758    $current_titletext = $default_titletext;
3759    $current_thumbnail_x = $default_thumbnail_x;
3760    $current_thumbnail_y = $default_thumbnail_y;
3761    $current_reverse = $do_reverse;
3762}
3763
3764##############################################################################
3765#
3766# Just initialize the 'png' array with UUENCODED PNG files for emoticons. This
3767# was placed down here so as not to clutter up the top of the file where the
3768# other globals are initialized.
3769#
3770##############################################################################
3771sub init_png_array {
3772
3773$png{'wink'} = <<'EOF';
3774MB5!.1PT*&@H````-24A$4@```!,````3!`,```"`?BO_````$E!,5$7____,
3775MS``S,P#___\```#__P!/FRMM`````7123E,`0.;89@````%B2T=$!?AOZ<<`
3776M``!F241!5'C:;8_1#8`P"$3O@Q'L!CJ!#M`FQP`FL/\J%FJ-)O+U<H&7`P!(
3777M5N2PN"_)TJESH'I.G6'&XFNB`<UV=5,_*]0.->:R.N?=+?A#36P6)H9!(F)Z
3778CI1BYC1+=-K2?9J^^SQ<7J48K([9O4:P`````245.1*Y"8((`
3779`
3780EOF
3781
3782$png{'smile'} = <<'EOF';
3783MB5!.1PT*&@H````-24A$4@```!,````3!`,```"`?BO_````$E!,5$7__P#,
3784MS`!F9@#_,P````#___]]YKD%````!G123E/______P"SOZ2_`````6)+1T0%
3785M^&_IQP```&U)1$%4>-I-C\$-P"`,`UV)`?I@@#ZZ03M`*L&?!]E_E=H$(5`>
3786MEP39#MR]E%+=&T@GD*NPD\A"PWBU@4,VADQ$*J,:OHF'<14(BX]14R#0%J1+
3787M>!.I(&,I=(!Q3+IT2\\+N2D#A\JP)]ORKBM^`[0;1*VK3]P`````245.1*Y"
3788"8((`
3789`
3790EOF
3791
3792$png{'frown'} = <<'EOF';
3793MB5!.1PT*&@H````-24A$4@```!,````3!`,```"`?BO_````'E!,5$7____,
3794MS`"9F0!F9@`S,P#,S#/___\S,S,```#__P`[/ZS;`````7123E,`0.;89@``
3795M``%B2T=$"?'9I>P```"$241!5'C:78^Q#<,P#`19:@1I`WN!`%G`@`<(8"T0
3796M>`.GE-R8*EV%OVU(24YA@L7A^>"31$3,G*@6!\!7=D$@\(8%!=K)Q&/#]U-4
3797M=AC>\;&.0I0A:YQVG$FM)\<E`X9X`F/'5A4UK304G0Z&&3->$;-N<-TJEM;0
3798@UQOZ@DOVMWO_7_P`Y9]*.M\PG><`````245.1*Y"8((`
3799`
3800EOF
3801
3802$png{'video_icon1'} = <<'EOF';
3803MB5!.1PT*&@H````-24A$4@```!D````="`8```!?>,=U````!F)+1T0`_P#_
3804M`/^@O:>3````"7!(67,```!(````2`!&R6L^```&$4E$051(Q[V6WXM=5Q7'
3805M/VN?<^Z],YUDIDF3W#-ITJ9B6S'X$D-2"A6*6,6*A3XI2'WJ@P\JH@511'T3
3806M6Z$//O<_L(J"H'UH06B)4%O0MEIIDR;IF>F0=I*Y]_S8>Z^U?+CYJ0WVJ0L.
3807M'-B'\]G[N]9>WP4?0\C-%IJF"4%R>6!Z*-[LF\V-MPOW$4Y1@&M=U_J1($W3
3808M%"+*='J[7KQP*A2RLRK$PV[YB.`'7*0$B;@)C.>#'3B5;>UM\Y'6];K_7TC3
3809M-!(D2A6V9!3>.UF$X>LAV,D0..R:UR"7IHH@!!',3;.-SB7;]TSO=SWI++5U
3810M/?6;0IJF*8)DGY2;HU$X^SVQV7=%9%\(!1`0"4A1D',F6,1T`(]8;HG#I!VJ
3811MX[_4\N!3$+JZKO/UD'#=NU72KDR*K1^/*O^)%*-]A0@A5(1J%RX37$%01")N
3812M$=,(>4;I&\M5?.7[(;WS+3R'IFGD?R!-TT@(.8RJ^0]$_`E7QH(A18$4RTA1
3813M(H5@&.**NN$`GB&4B!A%>N.6T>R/3X3AGU_!;\Q_:)IF%9`BQ).C:O9X**U2
3814M5Y``Q0KJCND<TQ[7'DT]EGK0%E*'QP%WHPA"9<V>:O:G'X5XYIX;("+2!;&J
3815ME$N/"Q?WNW:X)0("8@@]ECLDS_$\PZT#[2%U6.S)W4#NY^34HJE%VC>/%A>?
3816M^^'&V==6KD(<ST7H/S>6\U_U?`DL$4+`0TG.$4T=%CLTS7";(]8M`*G%8H?8
3817M'-(<B3NX1K"N"MV;CTA\]]@52!D8O.#]SZ/;JV:1HEHF%`'5B%NBL!ZS@:Z;
3818ML;5U@;5E86ELF$;$$V8#9CUB/9X'4$6XL+N<O7@<>`$@%'IQ=QE/'\6V(8`1
3819M4#7$$J06U1:1R(6M;7[WAU=X_?6SI*''+:':X3:`#5B."QES#WF.Q#.W7JLN
3820MB:LBLUU8)LB8`(L=>L1=<<U@F1P3Y\YO\]:9#QC:1.$9<2-X1BX_F.)F&(*;
3821M7KTKI3C1DT?%801!$L&&Q8YT6%00"6S@7'.)O[YZEB(D'CRQE^6Q(GG`?0#-
3822MBT)(<5'2Y?QJXDLW-7S04`[@<RP)KOU"AM1CJ<-SQ&//,"1>_OL63S_S-PK[
3823M)`\>7V,RRGA.>!S(<8Z;XJ($'>RJ7.:63/.`*N0.T@X^[$!L\=PM@-+A/D=0
3824M<G;^??H2+[VRP<YLAN>.U,[(PPYN+05ST!VRSO6:7*%TBHD).V"ZD"=WN.=%
3825M8C52A8CF#I',WK62SWYJA2^>V,WN,I/:B.>>@A[U#O6$FR`N[56(AKJ-(S]3
3826MV$L4WN(ZH+''B8A%+/=HR*A&QB/G@6-K?//A*<?NGE!(AWL"&5`;\)S`#*DF
3827MJ9CL>^VJ7-/U0Q%9_JVF8F9#0LB$TA#/6$ZX&:Z9(,:MNRKN/KS,X6E%56;`
3828M,$NX)=PSN(,K4N[]P/=\^5\;[[XC`.7FYOD0Y(.79;3VG+7;7W*-8[,!3P-F
3829M'=B`6V2ERGSAQ"IW[@O<MLM(<5%5KG&12^_!#9$2&:^_;)-/O.5A<CGQ)NS=
3830M?W0[RCU/(K)IOFB`P3K$+O>HU+,RCIRXM^+0;8[X@%G$M87<@J7%*2BP<$N.
3831MY<%GIT=.SNKI?@<(=;UN3;,A:I,WLE:G0RA!,FH=GGI(`];/\7[.2!)B<UQG
3832M8#N0YPL`!I0@`5DZ\&+8__"S'V):CA6K[Z>E^WYJH6X(%:X#:(M=Z:ZTF'>H
3833MS?&T#:E=_)OKO$.J"TR._&S]WD>WFHWWY`9(7==>KQ]V'=WU0C]YX!=6'MK&
3834MP..<'&>8]:BVN,WQ',$$/%QV;P,?`(VL?.8IF3[V/,`5J?[;?JGK@V;5H5_G
3835MI?L?\]&=KW+9%8M@BSODF87K^>($KB`E,KG]8EB][^<<^O;3TWL>T:;9N,%^
3836M;SIWO??VG^\NNK]\Q[O37]-+S:T^>PW/LVNPXA8H]T26CIP*Z]_X5;%\Q^_S
3837MZ+!.U^^X^;1R?31-(W5=^^;Y?Y1B_8/>;SX4WO_-'IV_5;@R1ML9XX/;['_H
3838M%,N??KX^<O_FYL9&,'?JNK:/"KF2*VN:S0)R*?D"KMU"*5<(8V&T%\*2X![K
3839=>OJAT^/'%O\!>',9BLN`3D\`````245.1*Y"8((`
3840`
3841EOF
3842
3843$png{'video_icon2'} = <<'EOF';
3844MB5!.1PT*&@H````-24A$4@```"0````D"`,```#6WFBJ```!LU!,5$4````<
3845M&!(F#0\L$@PP(AA((QY6+BAD.C9S0C8L&DLN'5TL(4,[)%@F*FP])VX[,VE(
3846M,VE(.71G1D)T5DUX6UAQ9EE:27:$@701,I`Y)H4W,X0_,I4<.*8</;0G.JDQ
3847M.:P[.;5'*H5+-(M%,951.YI*+J%&.*M2-J9)-[E7/+T>1:LL0:L\4:PH1[,U
3848M2+<]6;Y&08M318U#2Y)=49%B68QS:I1%1JM41ZM;6J9'1;)72+5#4[A75+Q=
3849M:+)J:*A@:K)./<M9/L17/=DL4<1$2L=:1L984L)<1M=<4M=A2<YG5LEA2]EG
3850M5-M'8\5;<LA.;-5===5E:\-E><ET>\=A8-1L>=1W=]!>1NIC3>IF5N=K5/%B
3851M9>A^;.]]?N!L:?MN?/5]B+1H@<YYA<1IA-)VA]9VEMI?I-Y^J-]F@>EXF.%X
3852MK^Y_ONQ\M_%_P>^&D(F%HI2&LJ*'P:^!E,>$F-F4DMB&MLR(H]R"E>N"JN:#
3853MMN>%J_.&MO*TM.2&RL.&Q]2(U]J,XMZ6XMR&Q>R(U.R6WN.%R/:'V/><V/.@
3854MUOR(Y?:.\O:5_/RHY_*D_?G,V_`,`@3___]7$.SF````D7123E/_________
3855M____________________________________________________________
3856M____________________________________________________________
3857M____________________________________________________________
3858M__\`,OEAFP````%B2T=$D'@*C@P````)<$A9<P```&(```!B`(^5@48````)
3859M=G!!9P```"0````D`'@'&X\```.E241!5#C+;93]0]I&&,?3JM6YRD3F"A,0
3860M8AF$F=IUFQ:*N@1S`1+95FI!W!Q4,EHP>Z$;PV80&LC!G6SP)^\"UFK7[]T/
3861ME\LGS_-<GGL>:O2.#A,)&1Q<WZ.N/HBR*,OYG'P$B-X/$4+*BWE+V40";('_
3862M0X?R43[.NYUN,D/.]40^]]88=>DIG]KSNCV;GG669=U.IX_/9:/U:Y`L9Z/.
3863ML'/CZ5]U,DJ[,=;C9K+9"Y?4A1W9%V;CB@9[:##H=HSZMW'6[4MD)RZI"9-@
3864MV4>[+V%O,.CW!_TN[IT]VU]?#8D36Q8DR9(0WDB7.SV(^H0;=/L]:);V-\(^
3865M,0]*$T@^W/1&]A7#T"'A.JB+=60:Y4P:A'EQ+SJ&4H\%[T:ZV#!,J'<[`X@@
3866MTO66H2G%@[B7$:-_6%!2C+#IDYIA:%#'&`]>(]Q%J&,:RDEZW2?D`(%D272S
3867MWREG!H00X=\<<TM$CC_UEEE6BK%O1%'\FQK]&/5Z]ZOEEJ%"%6''G5:SV?S=
3868M\6L+FF9%V0Z)N<VGU.@).?[/U;$AC)MSO[2;3;UYYV-=[QCE<C'"\+DM:I3R
3869M>NZ5E"K46_B\.CTS-1P.;PYGII9T9+RJO-A>%<5C*I?XW+-?,LVV8UAHW[AQ
3870M<WBAZ=D>JIXVTF$N;T%>;Z*BJ>WEH6-Y[I(A%&[!BO(\XN,!)?[@]MX[M4(>
3871M7B$LZ#6$6OED+R`!2CADW0+)+)X;7M?T.83FV>.(B^.IT?>`!B].$5X:%IJW
3872M9L9ACS751K!5.5@00D?4Z!'P?Z:8$!=F_SEWW+(.9V$SLP6$H/D*^),,H$9Q
3873ML+9RH))\M+':_O`RKF6$$=2>/0PP)/"1$%U;>0)5%:MDMT-22P91FW@SM>)*
3874MB.>.K=S=]3-UDWQH8>0-20_&*B)+M?)P-<B`?\DMD`3Z;LF`"!%C*E)-DAYB
3875MU^*?[ZZYN)QL714YN44_*)#<C2EB":F3:61H6RB5/Y[<3"'H_[*F01*8BDTK
3876M.,N9:I8_672EI..+0DA&Z4_O*V?DYXT'A%6R-&K^11?'_/2F6F0A22_8=TH5
3877M0S,TTX2&J6J-K^TV.R<PX++NY*1`VQ?H[5JCKFF52KE1VZ%M-COCXQ)7*E@2
3878MA.!'MVV+][_*9#([.P]<MVWV`",P\K5>($O1+^A%OVW^`TOS\RY[@).V4N]T
3879M%0#D:##@\MML?I?=%0PP63[YIJU<Z4\`2#$NRC'!32Z4E*38R_=W.@'$>%)A
3880=$L^#&,B\W?\/+ROZRK9):IT`````245.1*Y"8((`
3881`
3882EOF
3883
3884}
3885