#!/usr/bin/perl5.6.0 # ######################################################################### # # Image Display System 0.31 08/04/2000 # John Moose moosejc@muohio.edu # ######################################################################### # # Please see the files "LICENSE," "README," and "INSTALL" for more # information. # # This software is distributed under the GNU Public License. # ######################################################################### # Change these if you want: $previewMaxDimension = 100; # Size (in pixels) of the longest side of a thumbnail $imagesPerRow = 3; # Number of thumbnails displayed in each row in an album $thumbBorderWidth = 2; # Width (in pixels) of the border around the thumbnail images $displayThumbZoomIcon = 'n'; # (y/n) Display a zoom icon in the bottom right corner of thumbnails ######################################################################## # initialization use CGI qw(:all); use File::Basename; use Image::Magick; use Image::Info qw(image_info); $query = new CGI; $previewOverlay = Image::Magick->new; $preview = Image::Magick->new; # Global variables: ($base,$path,$type) = fileparse($0, '\.[^.]+\z'); $idscgi= $base . $type; # get name of this script # Initialize time variables $time = time(); ($sec,$min,$hour,$mday,$mon,$year) = localtime($time); $mon = $mon + 1; # Remember, perl starts with 0. $year += 1900; #Y2K compliance! if ($min < 10) {$min = "0".$min;} if ($sec < 10) {$sec = "0".$sec;} if ($mon < 10) {$mon = "0".$mon;} if ($mday < 10) {$mday = "0".$mday;} if ($year < 10) {$year = "0".$year;} $currentDate = "$mon\/$mday\/$year"; $currentTime = "$hour:$min:$sec"; # Stuff for flock $lock_sh = 1; $lock_ex = 2; $lock_nb = 4; $lock_un = 8; # program main processData(); if ($mode eq 'home') { generateHome(); } elsif ($mode eq 'album') { generateAlbum(); } elsif ($mode eq 'image') { generateImage(); } else { bail ("Sorry, invalid mode: $!"); } openTemplate(); processVarTags(); renderPage(); ######################################################################## # subfunctions sub bail { # produces and displays an HTML error message # my $error = "@_"; print header, start_html("Error"); print h1("An error has occurred"), p($error),end_html; die "$error \n"; } sub processData { # Interprets any form variables passed to the script. Checks to make sure the input makes sense. # if ($query->param('mode')) { $mode = $query->param('mode'); chomp $mode; unless ($mode =~ /album|image/) {$mode = 'home';} } else { $mode = 'home'; } if ($mode eq 'album') { $albumtodisplay = $query->param('album') || bail ("Sorry, no album name was provided: $!"); unless (-e "albums/$albumtodisplay") { # does this album exist? bail ("Sorry, that album doesn't exist: $!"); } if ($albumtodisplay =~ /\.\./) { # hax0r protection... bail ("Sorry, invalid directory name: $!"); } } if ($mode eq 'image') { $albumtodisplay = $query->param('album') || bail ("Sorry, no album name was provided: $!"); $imagetodisplay = $query->param('image') || bail ("Sorry, no image name was provided: $!"); } if ($query->param('maxDimension')) { $maxDimension = $query->param('maxDimension'); chomp $maxDimension; } else { $maxDimension = ''; } $query->delete_all(); } sub encodeSpecialChars { my($textToEncode) = shift(@_); $textToEncode =~ s/([^a-z0-9\/\.])/sprintf("%%%02x",ord($1))/gei; return $textToEncode; } sub decodeSpecialChars { my($textToDecode) = shift(@_); $textToDecode =~ s/\%([\da-fA-F][\da-fA-F])/chr(hex($1))/gei; return $textToDecode; } sub accessCounter { my($accessFile) = $mode.$albumtodisplay.'_counter.txt'; open (COUNTER,"+<$accessFile") || bail ("can't open counter file: ($!)"); # open the counter file for read/write flock COUNTER, $lock_ex; seek COUNTER, 0, 0; my($numOfAccesses) = (); seek COUNTER, 0, 0; truncate COUNTER, 0; print COUNTER ($numOfAccesses + 1); flock COUNTER, $lock_un; return $numOfAccesses; close (COUNTER) || bail ("can't close counter file: ($!)"); } sub fileSize { my($item) = shift(@_); my($filesize) = ((-s $item) / 1024); #get the file's size in KB. if ($filesize > 1024) { # is it larger than a MB? $filesize = ($filesize / 1024); $filesize =~ s/(\d+\.\d)\d+/$1/; $filesize = $filesize."MB"; } else { $filesize =~ s/(\d+)\.\d+/$1/; $filesize = $filesize."KB"; } return $filesize; } sub getImageDimensions { my $imageName = shift(@_); my($preview) = Image::Magick->new; my($x) = $preview->Read($imageName); # read in the picture warn "$x" if "$x"; my($xSize, $ySize) = $preview->Get('width', 'height'); # Get the pictures dimensions warn "$xSize" if "$xSize"; #print ($xSize . " " . $ySize . "\n"); return ($xSize, $ySize); } sub filenameToPreviewName { my $imageName = shift(@_); ($base,$path,$type) = fileparse($imageName, '\.[^.]+\z'); $path =~ s/^\.\///; my($newPreviewName) = $path . $base . '_pre.jpg'; # what the thumbnail would be named return $newPreviewName; } sub generateAlbumEntry { my $album = shift(@_); $album =~ s/\balbums\///; # trims off 'albums/' from the filepath provided by glob. $album =~ s/\/\Z//;# trims off the trailing slash from the filepath provided by glob. $prettyalbum = $album; $prettyalbum =~ s/\#\d+_//g; # trims off numbers used for list ordering. ex: "#02_" $prettyalbum =~ s/_/ /g; # replaces underscores with spaces $result = '
'.$prettyalbum.'
'."\n"; } sub generatePrevNext { my $albumName = shift(@_); my $imgName = shift(@_); my $title = shift(@_); my($imgSize) = &fileSize("albums/$albumName/$imgName"); my($base,$path,$type) = fileparse($imgName); my($previewName) = &filenameToPreviewName("$imgName"); &generateThumbnail("$previewName"); my($prettyImageTitle) = $base; $prettyImageTitle =~ s/\#\d+_//g; $prettyImageTitle =~ s/\.(\S+)\Z//; $prettyImageTitle =~ s/_/ /g; my($xSize, $ySize) = &getImageDimensions("albums/$albumName/$previewName"); my($previewHTML) = "" . "$title
\n
\n$prettyImageTitle
" . "
$imgSize\n"; return $previewHTML; } sub generateThumbnail { my $itemToPreview = shift(@_); return if (-d $itemToPreview); #this is a directory return if ($itemToPreview =~ /\.avi\b|\.mov\b|\.mpg\b|\.mpeg\b/i); #this is a movie return if ($itemToPreview =~ /\.wav\b|\.mp3\b/i); #This is a audio file return if ($itemToPreview =~ /\.zip\b/i); #This is a archived file return if ($itemToPreview =~ /_pre\.jpg\Z/i); #this is a preview file my($newPreviewName) = &filenameToPreviewName($itemToPreview); # skip thumbnail generation if a thumbnail exists and is newer than image if (-e $newPreviewName) { return if ((-M $itemToPreview) > (-M $newPreviewName)); } my($preview) = Image::Magick->new; my($x) = $preview->Read($itemToPreview); # read in the picture warn "$x" if "$x"; my($xSize, $ySize) = $preview->Get('width', 'height'); # Get the pictures dimensions # calculate dimensions of thumbnail my($scaleFactor); my($previewX); my($previewY); if ($xSize > $ySize) { $scaleFactor = $previewMaxDimension / $xSize; $previewX = $previewMaxDimension; $previewY = int($ySize * $scaleFactor); } else { $scaleFactor = $previewMaxDimension / $ySize; $previewY = $previewMaxDimension; $previewX = int($xSize * $scaleFactor); } $preview->Set(quality=>90); $x = $preview->Scale(width=>$previewX, height=>$previewY); #scale the image to the correct thumbnail size warn "$x" if "$x"; if ($displayThumbZoomIcon eq 'y') { $x = $preview->Composite(image=>$previewOverlay,compose=>'over',geometry=>("+".($previewX - 14)."+".($previewY - 14))); #overlay the zoom icon warn "$x" if "$x"; } $x = $preview->Border(color=>'black',width=>$thumbBorderWidth, height=>$thumbBorderWidth); warn "$x" if "$x"; $x = $preview->Write($newPreviewName); #write out the thumbnail image file in JPEG format warn "$x" if "$x"; } sub generateHome { #produces the top-level page, incorporating a list of albums and site news (if available) # my(@albums) = glob "albums/*/"; #returns the names of all directories in the 'albums' directory my($totalalbums) = ($#albums) + 1; my($albumcounter) = 0; # will be used to count the number of albums displayed $home = $home.'
'; my($album); my($albumcounter); foreach $album (sort @albums) { $albumcounter ++; $home = $home.&generateAlbumEntry($album); if ($albumcounter eq int(($totalalbums + 1) / 2)) { #create a 2 column list $home = $home.''; } } $home = $home.'
'; openNewsDesc(""); # read in site news my($numOfAccesses) = accessCounter(); $footer = "This page generated at $currentTime on $currentDate by ids 0.31.
It has been viewed ".$numOfAccesses.($numOfAccesses == 1 ?' time':' times')."."; } sub generateAlbum { #produces an album, displaying thumbnail pictures with names and filesizes. Displays a description of the album (if present). # my ($previousalbumtemp); if ($albumtodisplay =~ /\//) { ($previousalbumtemp, $albumtitle) = $albumtodisplay =~ /^(.+)\/([^\/]+)$/; $previousalbum = "< back to album"; } else { ($albumtitle) = $albumtodisplay =~ /([^\/]+)$/; $previousalbum = "< Return to the main page"; } $albumtitle =~ s/\#\d+_//g; # trims off numbers used for list ordering. ex: "#02_" $albumtitle =~ s/_/ /g; # replaces underscores with spaces my($imagecounter) = 0; opendir ALBUMDIR, "albums/$albumtodisplay" || bail ("can't open \"albums/$albumtodisplay\" album directory: ($!)"); my(@filesInAlbum) = grep !/^\.+/, readdir ALBUMDIR; closedir ALBUMDIR; # read in the 'exclude' list my(@exclude); open (EXCL, "albums/$albumtodisplay/.exclude"); while () { chomp; push(@exclude,$_); } close EXCL; my($fileInAlbum); my(@itemsToDisplay); my($imagesInAlbum); my($albumsInAlbum); THELIST: foreach $fileInAlbum (sort @filesInAlbum) { $fileInAlbum = "albums/$albumtodisplay/" . $fileInAlbum; next if ($fileInAlbum =~ /_pre\.jpg\Z/i); # this is a preview image- ignore it foreach (@exclude) { next if (/^$/); $excl = "albums/$albumtodisplay/" . $_; next THELIST, if ($excl eq $fileInAlbum); } if (-d "$fileInAlbum") { # Is this a subdirectory? push @itemsToDisplay, $fileInAlbum; # if so, remember its name $albumsInAlbum ++; } if ($fileInAlbum =~ /\.jpg\Z|\.jpeg\Z|\.gif\Z|\.png\Z|\.mov\Z|\.mpg\Z|\.mpeg\Z|\.avi\Z|\.zip\Z|\.wav\Z|\.mp3\Z/i) { # is this a known format? push @itemsToDisplay, $fileInAlbum; # if so, remember its name $imagesInAlbum ++; } } $albumitems = ''; $albumitems = $albumitems."\n"; my($x) = $previewOverlay->Read('site-images/previewicon.png'); # read in the "zoom" icon to overlay on the thumbnail warn "$x" if "$x"; # generate thumbnails my($itemToPreview); foreach $itemToPreview (sort @itemsToDisplay) { &generateThumbnail("$itemToPreview"); } # generate album HTML my($itemToDisplay); foreach $itemToDisplay (sort @itemsToDisplay) { my($imageName) = $itemToDisplay; $imageName =~ s/([^\/]+)\/?$//; #trim off the directory path returned by glob $imageName = $1; my($previewName) = $itemToDisplay; $previewName =~ s/\.([^\.]+)\Z//; # trim off and save the picture's extension (ex: ".gif", ".jpg") my($fileExtensionTemp) = $1; $previewName = $previewName.'_pre.jpg'; my($filesize) = &fileSize($itemToDisplay); my($previewImageSize); if ($fileExtensionTemp =~ /mov|mpg|mpeg|avi/i) { #is this a known movie type? $previewName = 'site-images/genericmovie.jpg'; } elsif ($fileExtensionTemp =~ /mp3|wav|zip/i) { #is this a known audio type? $previewName = 'site-images/image1.gif'; } if ($fileExtensionTemp =~ /mov|mpg|mpeg|avi|mp3|wav|zip/i) { #is this a known type? my($xSize, $ySize) = &getImageDimensions("$previewName"); my($prettyImageTitle) = $imageName; $prettyImageTitle =~ s/\#\d+_//g; # create a link directly to the movie file $albumitems = $albumitems."\n"; } elsif (-d $itemToDisplay) { # this is a directory $previewName = 'site-images/image2.gif'; my($xSize, $ySize) = &getImageDimensions("$previewName"); # create a link to the directory $dirToDisplay = $itemToDisplay; $imageName =~ s/\#\d+_//g; # trims off numbers used for list ordering. ex: "#02_" $imageName =~ s/_/ /g; $dirToDisplay =~ s/albums\///; $albumitems = $albumitems."\n"; } else { #this must be an image file my($xSize, $ySize) = &getImageDimensions("$previewName"); my($prettyImageTitle) = $imageName; $prettyImageTitle =~ s/\#\d+_//g; $albumitems = $albumitems."\n"; } $imagecounter ++; if ($imagecounter == $imagesPerRow) { #is it time to go to the next row? $albumitems = $albumitems."\n"; $imagecounter = 0; } } if (($imagecounter ne $imagesPerRow)) { for ($i = 0; $i < ($imagesPerRow - $imagecounter); $i++) { $albumitems = $albumitems."\n"; #put in cells to finish the row (necessary for Netscape) } } $albumitems = $albumitems."\n"; $albumitems = $albumitems.'
\"$prettyImageTitle\"
$prettyImageTitle

$filesize
\"$imageName\"
$imageName
\"$prettyImageTitle\"
$prettyImageTitle

$filesize
 
'; if (($imagesInAlbum eq '') and ($albumsInAlbum eq '')) {$imagesInAlbum = "0";} $totalitems = $imagesInAlbum + $albumsInAlbum; $totalitems = $totalitems.($totalitems == 1 ? ' item' : ' items'); # correct grammar! openNewsDesc("albums/$albumtodisplay"); # read in album news openItemDesc("albums/$albumtodisplay"); # read in album desc $footer = "The album \"$albumtitle\" generated at $currentTime on $currentDate by ids 0.31."; } sub generateImage { #produces the a page to display an image. Provides image size, dimensions, type, and date uploaded. Can display a description (if present). # $albumtitle = $albumtodisplay; my($xSize, $ySize) = &getImageDimensions("albums/$albumtodisplay/$imagetodisplay"); my($origxSize) = $xSize; my($origySize) = $ySize; $previousalbum = "< back to album"; #@filesInAlbum = glob "albums/$albumtodisplay/*"; # returns the names of _all_ files in this images's directory opendir ALBUMDIR, "albums/$albumtodisplay" || bail ("can't open \"albums/$albumtodisplay\" album directory: ($!)"); my(@filesInAlbum) = grep !/^\.+/, readdir ALBUMDIR; closedir ALBUMDIR; # read in the 'exclude' list my(@exclude); open (EXCL, "albums/$albumtodisplay/.exclude"); while () { chomp; push(@exclude,$_); } close EXCL; THELIST2: foreach $fileInAlbum (sort @filesInAlbum) { next if ($fileInAlbum =~ /_pre\.jpg\Z/i); # this is a preview image- ignore it foreach (@exclude) { next if (/^$/); $excl = "albums/$albumtodisplay/" . $_; next THELIST2, if ($excl eq $fileInAlbum); } if ($fileInAlbum =~ /\.jpg\Z|\.jpeg\Z|\.gif\Z|\.png\Z|\.mov\Z|\.mpg\Z|\.mpeg\Z|\.avi\Z|\.zip\Z|\.wav\Z|\.mp3\Z/i) { # is this a known format? push @imagesInAlbum, $fileInAlbum; # if so, remember its name unless ($fileInAlbum =~ /\.mov\Z|\.mpg\Z|\.mpeg\Z|\.avi\Z|\.zip\Z|\.wav\Z|\.mp3\Z/i) { push @imagesForPrevNext, $fileInAlbum; # for prev/next thumbs } } } undef $where; for ($[ .. $#imagesForPrevNext) { $where = $_, last if ($imagesForPrevNext[$_] eq $imagetodisplay); } if ($where > 0) { $prevthumb = &generatePrevNext($albumtodisplay,$imagesForPrevNext[$where-1],"< previous"); } &generateThumbnail("albums/$albumtodisplay/$imagesForPrevNext[$where-1]"); if ($where < $#imagesForPrevNext) { $nextthumb = &generatePrevNext($albumtodisplay,$imagesForPrevNext[$where+1],"next >"); } &generateThumbnail("albums/$albumtodisplay/$imagesForPrevNext[$where+1]"); if (( -r "albums/$albumtodisplay/.ids_size") && ($maxDimension eq '')) { open (ALBUMSIZE, "; close (ALBUMSIZE); $maxDimension =~ s/\n//; # remove trailing newline if ($maxDimension =~ m@^\d+x\d+@) { ($maxDimensionx) = ($maxDimension =~ m@(\d+)x\d+@); ($maxDimensiony) = ($maxDimension =~ m@\d+x(\d+)@); warn "album: $maxDimensionx, $maxDimensiony" } else { warn "ignoring bad size constraint $maxDimension"; } } if ($maxDimension =~ /\d+/) { $maxDimension =~ s/\n//; #trailing newline if ($maxDimension =~ m@^\d+x\d+@) { ($maxDimensionx) = ($maxDimension =~ m@(\d+)x\d+@); ($maxDimensiony) = ($maxDimension =~ m@\d+x(\d+)@); warn "album: $maxDimensionx, $maxDimensiony" } else { warn "ignoring bad size constraint $maxDimension"; } my($iscaleFactor); my($iX); my($iY); my($xfactor); my($yfactor); if (($xSize > $maxDimensionx) || ($ySize > $maxDimensiony)) { warn "starting with $xSize, $ySize"; $xfactor = $xSize / $maxDimensionx; $yfactor = $ySize / $maxDimensiony; if ($xfactor > $yfactor) { $xSize = $maxDimensionx; $ySize = int($ySize / $xfactor); warn "scaling by $xfactor to $xSize, $ySize"; } else { $ySize= $maxDimensiony; $xSize = int($xSize / $yfactor); warn "scaling by $yfactor to $xSize, $ySize"; } } } $image = ""; my($imageNameTrimmed) = $imagetodisplay; $imageNameTrimmed =~ s/\.(\S+)\Z//; my($fileExtension) = $1; my($filesize) = &fileSize("albums/$albumtodisplay/$imagetodisplay"); my($image) = Image::Magick->new; my($x) = $image->Read("albums/$albumtodisplay/$imagetodisplay"); # read in the picture warn "$x" if "$x"; $description2 = $image->Get('comment'); # Get comments embedded in the image file. Some digital cameras do this. $description2 =~ s/ignored tags.+//i; #removes garbage tags from Canon Powershot cameras $description2 =~ s/\$9000:.+//i; #removes garbage tags from Sony cameras openItemDesc("albums/$albumtodisplay/$imageNameTrimmed"); if ($description2 ne '') { $description = (($description ne '') ? $description . "
" : '').$description2."\n
(embedded)"; } if ($description eq '') { $description = "none"; } my($daysSinceMod) = (-M "albums/$albumtodisplay/$imagetodisplay"); if ($daysSinceMod < 1) { $daysSinceMod = ((int($daysSinceMod * 24)) + 1)." hour".(((int($daysSinceMod * 24)) + 1) <= 1 ? '' : 's')." ago"; } else { $daysSinceMod = int($daysSinceMod + .5)." day".((int($daysSinceMod + .5) < 2) ? '' : 's').' ago'; } if ($fileExtension =~ /jpg|jpeg/i) { $fileExtension = "JPEG"; } elsif ($fileExtension =~ /gif/i) { $fileExtension = "GIF"; } elsif ($fileExtension =~ /png/i) { $fileExtension = "PNG"; } my($camerainfo) = image_info("albums/$albumtodisplay/$imagetodisplay"); my($cam_iso) = $camerainfo->{'ISOSpeedRatings'}; if (defined($camerainfo->{'ExposureTime'})) { @exp_n = $camerainfo->{'ExposureTime'}; $top = $exp_n[0][1] / $exp_n[0][0], unless ($exp_n[0][1] == 0); } elsif (defined($camerainfo->{'ShutterSpeedValue'})) { @frac = $camerainfo->{'ShutterSpeedValue'}; if ($frac[0][1] == 0) { $time = $frac[0]; } else { $time = $frac[0][0] / $frac[0][1]; } $top = int(0.5 + exp($time * log(2))); } $cam_exp = "1/" . $top . "s"; if (defined($camerainfo->{'FNumber'})) { @f_n = $camerainfo->{'FNumber'}; } elsif (defined($camerainfo->{'ApertureValue'})) { @f_n = $camerainfo->{'ApertureValue'}; } if ($f_n[0][1] == 0) { $cam_f = "f" . $f_n[0]; } else { $cam_f = "f" . $f_n[0][0] / $f_n[0][1], } @fl_n = $camerainfo->{'FocalLength'}; if (defined($camerainfo->{'FocalLength'})) { if ($fl_n[0][1] == 0) { $cam_flen = $fl_n[0]*5 . "mm"; } else { $cam_flen = 5*$fl_n[0][0]/$fl_n[0][1] . "mm", unless ($fl_n[0][1] == 0); } } else { $cam_flen = "unknown"; } my($cam_flash) = $camerainfo->{'Flash'}; my($cam_date) = $camerainfo->{'DateTimeOriginal'}; my($cam_make) = $camerainfo->{'Make'}; my($cam_model) = $camerainfo->{'Model'}; $caminfo = "Make: "."\u\L$cam_make"."
Model: $cam_model
Flash used: $cam_flash
Taken on: $cam_date
ISO: $cam_iso
Focal length: $cam_flen
Shutter: $cam_exp
Aperture: $cam_f
", unless ($cam_make =~ /^$/); $pictureinfo = "Image type: $fileExtension
File size: $filesize
Image size: $xSize x $ySize ($origxSize x $origySize)
Uploaded: $daysSinceMod"; $imagetitle = $imageNameTrimmed; $imagetitle =~ s/\#\d+_//g; $imagetitle =~ s/_/ /g; $footer = "Maximum image dimension:

\"$imagetitle\" presented at $currentTime on $currentDate by ids 0.31."; } sub openTemplate { #opens an html template file # open (TEMPLATE,'site-templates/'.$mode.'.html') || bail ("cannot open $mode template for reading: ($!)"); $pageContent = (join "",