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 ×tamp_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," | <A HREF=\"$info{$pathname}{medium}\">medium</A>"); 1347 } 1348 1349 # Detail list link 1350 if ($do_detail == 1) { 1351 push(@index," | <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*$/ .../; 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: <A HREF=\"$filename\">$info{$pathname}{geometry}</A>"); 1612 push(@details," ($info{$pathname}{size})<BR>"); 1613 unless (defined($skipmedium{$pathname})) { 1614 push(@details,"Medium: <A HREF=\"$info{$pathname}{medium}\">"); 1615 push(@details,$info{$pathname}{'med_x'} . 'x' . $info{$pathname}{'med_y'} . "</A>"); 1616 push(@details," ($info{$pathname}{med_size})<BR>"); 1617 } 1618 unless (defined($skipthumb{$pathname})) { 1619 push(@details,"Thumbnail: <A HREF=\"$info{$pathname}{thumb}\">"); 1620 push(@details,$info{$pathname}{'thumb_x'} . 'x' . $info{$pathname}{'thumb_y'} . "</A>"); 1621 push(@details," ($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: $info{$pathname}{scenes} scenes<BR>"); 1627 } 1628 1629 # Video file stuff 1630 # 1631 if (defined ($info{$pathname}{'video_format'})) { 1632 push (@details, "Video Format: $info{$pathname}{'video_format'}<BR>"); 1633 } 1634 if (defined ($info{$pathname}{'video_bitrate'})) { 1635 push (@details, "Video Bitrate: $info{$pathname}{'video_bitrate'}<BR>"); 1636 } 1637 if (defined ($info{$pathname}{'video_fps'})) { 1638 push (@details, "Video Rate: $info{$pathname}{'video_fps'} f/s<BR>"); 1639 } 1640 if (defined ($info{$pathname}{'audio_codec'})) { 1641 push (@details, "Audio Codec: $info{$pathname}{'audio_codec'}<BR>"); 1642 } 1643 if (defined ($info{$pathname}{'audio_bitrate'})) { 1644 push (@details, "Audio Bitrate: $info{$pathname}{'audio_bitrate'} kbit/s<BR>"); 1645 } 1646 if (defined ($info{$pathname}{'length'})) { 1647 push (@details, "Length (time): $info{$pathname}{'length'} sec<BR>"); 1648 } 1649 1650 # 1651 # EXIF data 1652 # 1653 if (defined($info{$pathname}{'flash'})) { 1654 push(@details,"Flash: $info{$pathname}{flash}<BR>"); 1655 } 1656 if (defined($info{$pathname}{'exposure_time'})) { 1657 push(@details,"Exposure time: $info{$pathname}{exposure_time}<BR>"); 1658 } 1659 if (defined($info{$pathname}{'focus_dist'})) { 1660 push(@details,"Focus distance: $info{$pathname}{focus_dist}<BR>"); 1661 } 1662 if (defined($info{$pathname}{'focal_length'})) { 1663 push(@details,"Focal length: $info{$pathname}{focal_length}<BR>"); 1664 } 1665 if (defined($info{$pathname}{'aperture'})) { 1666 push(@details,"Aperture: $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 " | " 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 " | " if ($numlink != 0); 2443 print "<A HREF=\"$detailfile\">$detaillinktext</A>"; 2444 $numlink++; 2445 } 2446 2447 if (($this eq 'detail') and defined($indexlinktext)) { 2448 print " | " 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><edwinh at computer dot org></I>"; 2501# print "<BR>\n"; 2502# print "<A HREF=\"http://www.reynoldsnet.org/\">John Reynolds</A> <I><johnjen at reynoldsnet dot org></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}\">< previous</A> | "; 2781 } else { 2782 print "< previous | "; 2783 } 2784 print "<A HREF=\"../$indexfile\" TARGET=\"_top\">index</A>"; 2785 if (defined($forward{$pathname})) { 2786 print " | <A HREF=\"$forward{$pathname}\">next ></A>"; 2787 } else { 2788 print " | next >"; 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/\&/\&/g; 3445 $caption =~ s/\</\</g; 3446 $caption =~ s/\>/\>/g; 3447 $caption =~ s/\"/\"/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\ \)/; 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/\ /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/\ /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