#! /usr/bin/perl -w # gallery-ed: Make an html gallery of photos, then allow them to be # copied or moved elsewhere, or deleted. # Copyright (C) 2004, 2011 Nick Urbanik # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # This is an old, crufty program updated in 2009, but with many hard-coded # values. # Based directly on gphoto's gallery, but with features essential to me. # This program does the following: # In the current directory are photos (maybe thousands of them). # It sorts the filenames by date, and then # builds a list of files with the same date as the earliest. The date # is found from stating the file, or perhaps by using the exif.pm module. # It creates a directory with a name equal to the date, e.g., # 2000_01_20. # It moves all the photos with that date into the directory. # It copies (while editing the tags) the html files from the gphoto # gallery directory using the theme of your choice. It does that as # follows: # It copies all files from the theme that are not index_top.html, # index_bottom.html, picture.html or thumbnail.html directly to the # target directory. (Could use a common directory?) # It then copies index_top.html to index.html in the target dir while # parsing the tags. # It then builds an html table to index.html. Border width is # configurable. It prints the opening table tag, # Five thumbnails to a row, start a row. # It uses ImageMagick to create a thumbnail from each photo; # 1. Sort the filenames by file date (set originally with camtouch.pl) # 2. create one directory for each day # 3. move the jpeg files into that dir # 4. copy the gphoto gallery files (except index_top.html, # index_bottom.html, picture.html and thumbnail.html) into that dir # 5. copy and edit the index_top.html into index.html # 6. start a table # 7. for each row of five, start a row # 8. for each photo, generate a thumbnail with ImageMagick, build a small # table using thumbnail.html linked to the big photo, # 9. finish the table # 10. append and edit index_bottom.html to the end of index.html # 11. Maybe (much) later add a button to each thumbnail that can select # it to allow moving or copying it to another location on the server, # using mod_perl to drive it. This will allow my wife to sort the # photos out. Ideally all labels should be able to be edited though # the web interface. # Each photo has four buttons that select one of four areas to move # the photo to. # make so that can run anytime without damaging a previosly existing # directory. TOP PRIORITY. This means: Do not make new thumbnails if # they exist already. Build the index.html and picturexxx.html from # scratch, but from files in the directory, not just in the current # (parent) directory. # Change to allow the above item: No longer maintain a data structure # of all files, but deal with each directory by itself. # for each JPEG file, # make a directory name from the date stamp # make the directory if it does not exist; # move the file to the directory, if a file of that name does # not exist there already # for each directory in the current directory, # if it matches nn-Mon-yyyy, # change to that directory # copy theme files if necessary # start editing index.html with index_top.html and # for each non-thumb JPEG file in that directory # start a new row when necessary # make a thumbnail if not already exist with right size # # copy across picture.html, overwriting any previous #
, copy thumbnail.html into index.html,
# append index_bottom.html to index.html # OTHER TODO ITEMS: # Should the photo mtime be changed to match the EXIF time? # Probably not automatically, as sometimes the camera clock is wrong, # and the mtimes have been manually adjusted, but not the EXIF times. # Make pages that link to each month, then to individula days (I # imagine a calendar layout: a table with around 365 cells). Years # link to these pages. Perhaps 6, 4 or 3 month periods from one page? # Show the day of the week. Perhaps implement this with a directory # structure like this: $PHOTOS/yyyy/mm/yyyy_mm_dd/photo.jpg If # implement the directory structure, can implement the index pages a # bit later, as the automatic indexing would be okay. There is a # limit to how long we can go with one directory per day all under # $PHOTOS. # provide links that move from one month to the previous or the next. # Provide more command line options to vary parameters. # Make this cross platform so it definitely works under Windows. # Note that there's a problem if have an image file index.jpg! use strict; use Getopt::Long; use Image::Magick; use File::Copy; use File::Basename; use CGI qw/ -no_debug :standard :html3 start_table end_table /; use Carp qw/ cluck confess /; use Image::Size; # qw/ imgsize html_imgsize /; use Time::Local; use Image::ExifTool; use Cwd qw/ cwd abs_path fast_abs_path /; $Image::Size::NO_CACHE = 1; use vars qw/ $gallery_name $gallery_index $date $thumbnail /; use vars qw/ $thumbnail_filename $debug /; use vars qw/ $thumbnail_number $picture $picture_filename $picture_number /; use vars qw/ $picture_next $picture_previous /; use vars qw/ $picture_s $picture_s_next $picture_s_previous /; use vars qw/ $picture_l $picture_l_next $picture_l_previous /; use vars qw/ @themes $chosentheme /; use vars qw/ $border $imageborder $pics_per_row $theme_dir $indexfile /; use vars qw/ $max_thumb_dim $tol $startdir $abspath /; use vars qw/ $alldirs $checkthumbsizes $full_size $max_dirs /; use vars qw/ @photo_locations $photo_locations_file /; use vars qw/ $add_delete_buttons $add_buttons $lastdate $earliestdate /; use vars qw/ $picture_title $thumbnail_width /; # LOTS of hard-coded values! These need changing. use constant DATE_DEBUG => 3; use constant DEBUG_LINKS => 2; use constant TITLE_FILE => '.titles'; #use constant GALLERYDIR => '/usr/share/gphoto/gallery/'; use constant GALLERYDIR => '/home/nicku/.gallery/templates/'; use constant CGI_PROGRAM => '/~nicku/cgi-bin/photo-select.cgi'; use constant TITLE_CGI_PROGRAM => '/~nicku/cgi-bin/edit-title.cgi'; use constant PHOTOS_BASE => '/home/nicku/photos'; use constant PHOTOS_BASE_OLD => '/home/nicku/.photos'; $debug = 0; $alldirs = 0; $checkthumbsizes = 0; @themes = qw/ CSStheme Default RedNGray /; #$cgi_program = "/home/p/public_html/photo-select.cgi"; $photo_locations_file = "/home/nicku/.photo_locations"; $startdir = cwd; $chosentheme = 0; $theme_dir = GALLERYDIR . $themes[ $chosentheme ] . "/"; $border = 1; $imageborder = 1; $pics_per_row = 4; $indexfile = "index.html"; $gallery_index = $indexfile; $max_thumb_dim = 200; # pixels for biggest dim of thumbnails # Determines table width containing thumb: $thumbnail_width = "width='$max_thumb_dim'"; $tol = 0.1; # Tolerance for thumbnail size. $add_buttons = ( ( ( basename $0 ) eq "gallery-ed" ) or ( basename $0 ) eq "gallery-ed.cgi" ) ? 1 : 0; print "add_buttons = $add_buttons\n" if $debug; # Format of $photo_locations_file is: # name on menu item => directory-name # Lines starting with a hash # are comments and are skipped # blank lines are allowed. sub read_photo_locations(\@) { my $photo_locations = shift; open LOCATIONS, "<", $photo_locations_file or die "Cannot open \"$photo_locations_file\" to read photo locations: $!"; while ( ) { chomp; next if /^#/; next if /^\s*$/; my ( $key, $directory ) = split /\s*=>\s*/; die "Bad photo locations file $photo_locations_file\n" unless $key and $directory; # Throw away the second item: the directory. Keep the key. push @$photo_locations, $key; } } # Copy the sourcefile to the destfile while editing the tags. # Nick 28 Jan 2003: Added PICTURE_S and PICTURE_L # For 1/4 scale, full scale picture.html files. # Also created picture-[sl].html files. # PICTURE_TITLE for the textfield editing the title on big # single picture html files. sub gallery_parse_tags { my ( $sourcefile, $destfile ) = @_; open SOURCE, "< $sourcefile" or die "Cannot open $sourcefile for reading: $!"; open DEST, ">> $destfile" or die "Cannot open $destfile for appending: $!"; while ( ) { s!#GALLERY_NAME#!$gallery_name!; s!#GALLERY_INDEX#!$gallery_index!; s!#DATE#!$date!; s!#THUMBNAIL#!$thumbnail!; s!#THUMBNAIL_FILENAME#!$thumbnail_filename!; s!#THUMBNAIL_NUMBER#!$thumbnail_number!; s!#THUMB_WIDTH#!$thumbnail_width!; s!#PICTURE#!$picture!; s!#PICTURE_S#!$picture_s!; s!#PICTURE_L#!$picture_l!; s!#PICTURE_FILENAME#!$picture_filename!; s!#PICTURE_NUMBER#!$picture_number!; s!#PICTURE_NEXT#!$picture_next!; s!#PICTURE_PREVIOUS#!$picture_previous!; s!#PICTURE_S_NEXT#!$picture_s_next!; s!#PICTURE_S_PREVIOUS#!$picture_s_previous!; s!#PICTURE_L_NEXT#!$picture_l_next!; s!#PICTURE_L_PREVIOUS#!$picture_l_previous!; s!#PICTURE_TITLE#!$picture_title!; print DEST $_; } close SOURCE or die "Cannot close $sourcefile: $!"; close DEST or die "Cannot close $destfile: $!"; } # copy the gphoto gallery files (except index_top.html, # index_bottom.html, picture.html and thumbnail.html) into that dir # One parameter: the target directory. # Dies if fails. sub copy_gallery_files { my $targetdir = shift; my $sourcedir = $theme_dir; my $file; opendir( GAL, $sourcedir ) or die "Cannot open $sourcedir: $!"; while ( defined ( $file = readdir GAL ) ) { next if $file =~ /^\.\.?$/; next if $file eq "index_top.html" or $file eq "index_bottom.html" or $file eq "picture.html" or $file eq "thumbnail.html"; copy( "$sourcedir$file", $targetdir ) or die "Could not copy $sourcedir$file to $targetdir: $!"; } closedir GAL or die "Cannot close $sourcedir: $!"; } sub convert_exif_text_to_ctime($$) { my ( $datetimeoriginal, $name ) = @_; # Date/time-stamps are stored in EXIF files in the form # "2004:12:31 23:59:59". This regular expression will match the # date and time. if ( ! $datetimeoriginal || $datetimeoriginal !~ /(\d{4}):(\d{2}):(\d{2})\s(\d{2}):(\d{2}):(\d{2})/ ) { warn "$name: Unable to read original date/time, skipping.\n"; return; } my ( $year, $month, $day, $hour, $min, $sec ) = ( $1, $2, $3, $4, $5, $6 ); warn "bad EXIF time for '$name': '$datetimeoriginal'\n" and return if $month < 1 or $year < 1900; my $time = timelocal( $sec, $min, $hour, $day, $month - 1, $year - 1900 ); return $time; } sub get_exif_date_text { my ( $filename ) = @_; my $exifTool = new Image::ExifTool; my $exif_info = $exifTool->ImageInfo( $filename ); return $exif_info->{DateTimeOriginal}; } # $date is in that odd EXIF time format sub set_exif_date { my ( $filename, $date ) = @_; return unless $date; my $exifTool = new Image::ExifTool; $exifTool->SetNewValue( 'DateTimeOriginal', $date ); $exifTool->WriteInfo( $filename ) or warn "Failed to update '$filename' with ", "date '$date': $!" and return; return 1; } # Get the time a photo was taken from the EXIF DateTimeOriginal, or # from the mtime otherwise. sub get_photo_date { my ( $filename ) = @_; my $datetimeoriginal = get_exif_date_text $filename; if ( $datetimeoriginal ) { my $mtime = convert_exif_text_to_ctime $datetimeoriginal, $filename; return $mtime if $mtime; } return ( stat( $filename ) )[ 9 ]; } sub make_thumbnail_filename { my $bigname = shift; my ( $name, $dir, $suffix ) = fileparse( $bigname, "\.jpg" ); # my $thumbname = "$dir$name-thumb.jpg"; my $thumbname = "$name-thumb.jpg"; return $thumbname; } # ( $x, $y ) = size_of_image_file( "file.jpg" ); # Note: imgsize dies if the image has problems. sub size_of_image_file($) { my $image_filename = shift; my ( $x, $y ); eval { ( $x, $y ) = imgsize( $image_filename ); }; if ( $@ ) { warn $@; return 0; } return ( $x, $y ); } # sub size_of_image_file_newer_Magick($) # { # my $image_filename = shift; # my $image = Image::Magick->new; # return $image->Ping( $image_filename ); # } # sub size_of_image_file_old_Magick($) # { # my $image_filename = shift; # my $image = Image::Magick->new; # return split ',', $image->Ping( $image_filename ); # } sub thumbsize_ok { return 1 unless $checkthumbsizes; my $thumbfile = shift; my $exists = -f $thumbfile; print "\$exists = $exists\n" if $debug > 5; my ( $tx, $ty ); ( ($tx, $ty ) = size_of_image_file( $thumbfile ) ) || ( warn "Cannot get size of $thumbfile!\n" && return 0 ); print "\$thumbfile = $thumbfile, \$tx = $tx, \$ty = $ty\n" if $debug > 5; my $retval = ( abs( $tx - $max_thumb_dim ) < $tol * $max_thumb_dim or abs( $ty - $max_thumb_dim ) < $tol * $max_thumb_dim ); print "\$retval = $retval\n" if $debug > 5; print "$thumbfile exists but has the wrong size ($tx, $ty)\n" unless $retval; return $retval; } # Note that file mtime is often the EXIF time modified by the timezone # difference from the current time zone. So if the picture was taken # in Hong Kong, during a time when daylight saving was in effect, and # we are currently in Sydney running this program, and the camera time # was correct, then often the mtime will be 3 hours ahead of the time stored # in the EXIF DateTimeOriginal attribute. # When annotating the time a photograph was taken, we are usually # interested in the time that was local to the photograph. So it is # best to depend on the EXIF time rather than the file modification # time. If the EXIF time is wrong, then it is better to correct it. # It is important to have the time correct in the camera. sub touch { my ( $file, $new_ctime ) = @_; my $original_ctime = ( stat( $file ) )[ 9 ]; return if $original_ctime eq $new_ctime; utime $new_ctime, $new_ctime, $file or die "unable to change timestamp on '$file' from '$original_ctime' ", "to '$new_ctime': $!"; return 1; } # returns 1 if successful else 0. # Success is a thumbnail that is okay (even if it was there before). # Should check that it is not converting a JPEG file that is # already a thumbnail. # Image::Magick documentation seems wrong about return values of # Read and Write. sub make_thumbnail { my ( $bigphoto, $thumbnail ) = @_; return 1 if ( -f $thumbnail and thumbsize_ok( $thumbnail ) ); my $image = Image::Magick->new; my $x = $image->Read( "$bigphoto" ); if ( $x ) { # Corrupt JPEG file: print STDERR "\$bigphoto = $bigphoto\n" if $debug > 2; print STDERR "$bigphoto does not exist!\n" unless -f $bigphoto; warn $x; return 0; } $image->Scale( geometry => $max_thumb_dim . "x" . $max_thumb_dim ); my $y = $image->Write( $thumbnail ); if ( $y ) { # ?????????????? Disk full ???? Bug in Image::Magick? # Should we die if this happens? print STDERR "CANNOT WRITE $thumbnail: IS DISK FULL?\n"; warn $y; return 0; } my $exiftime = get_exif_date_text $bigphoto; if ( $exiftime ) { my $thumb_date = get_exif_date_text $thumbnail; set_exif_date $thumbnail, $exiftime unless $thumb_date and $thumb_date eq $exiftime; my $mtime = convert_exif_text_to_ctime $exiftime, $bigphoto; touch $thumbnail, $mtime; } return 1; # print "\$x = $x and \$y = $y\n"; } sub print_to_index($@) { my( $file, @text ) = @_; open INDEX, ">> $file" or die "Cannot write to $file: $!"; print INDEX @text; close INDEX or die "Cannot close $file: $!"; } use vars qw/ @short_month_names @long_month_names /; @short_month_names = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ); @long_month_names = ( "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ); # Build a string of form "02_Jan_2000" # Input parameter is a 32-bit Unix date. sub unix_time_to_dir_date_name_orig { my $mtime = shift; my ( $mday, $monnum, $year ) = ( localtime $mtime )[ 3, 4, 5 ]; $mday = sprintf "%02d", $mday; my $mon = ( @short_month_names )[ $monnum ]; return $mday . "_" . $mon . "_" . ( $year + 1900 ); } # Build a string of form "2002_10_14" for 14 Oct, 2002. # Idea is can sort by name. Better for backups too. # Input parameter is a 32-bit Unix date. sub unix_time_to_dir_date_name { my $mtime = shift; confess unless defined $mtime; my ( $mday, $monnum, $year ) = ( localtime $mtime )[ 3, 4, 5 ]; $mday = sprintf "%02d", $mday; $monnum = sprintf "%02d", $monnum + 1; return ( $year + 1900 ) . "_" . $monnum . "_" . $mday; } # Build a string of form "12.45 pm" # Input parameter is a 32-bit Unix date. sub mtime_to_string { my $mtime = shift or cluck "no defined mtime" and return; my ( $sec, $min, $hour ) = ( localtime $mtime )[ 0, 1, 2 ]; my $am_pm = "am"; ++$min if $sec >= 30; ++$hour if $min == 60; $am_pm = "pm" if $hour > 11; $hour -= 12 if $hour > 12; return sprintf "%d:%02d $am_pm", $hour, $min; } # Verify we have a string of form "02_Jan_2000" # Only work until 2099. sub valid_dir_date_name_orig { my $date = shift; return $date =~ /^[0-3]\d_(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)_(19|20)\d\d$/; } # Verify we have a string of form "2002_10_14" sub valid_dir_date_name($) { return dir_date_name_to_unix_time( shift ); } my %months = ( "Jan" => 0, "Feb" => 1, "Mar" => 2, "Apr" => 3, "May" => 4, "Jun" => 5, "Jul" => 6, "Aug" => 7, "Sep" => 8, "Oct" => 9, "Nov" => 10, "Dec" => 11, ); # Returns an array of three integers: # ( $mday, $month, $year ) = parse_dir_date_name( $dirname ); # mday = 1..31, $month = 0..11, $year = four digit year sub parse_dir_date_name_orig { my $dir_date_name = shift; return unless defined $dir_date_name; my ( $mday, $month, $year ); $dir_date_name =~ /^([0-3]\d)_(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)_((19|20)\d\d)$/; $mday = $1; $month = $2; $year = $3; my $mon = $months{$month}; return ( $mday, $mon, $year ); # print "\$mday = $mday, \$month = $month, \$year = $year\n"; } # Returns an array of three integers: # ( $mday, $month, $year ) = parse_dir_date_name( $dirname ); # mday = 1..31, $month = 0..11, $year = four digit year sub parse_dir_date_name($) { my $dir_date_name = shift; return unless defined $dir_date_name; confess "parse_dir_date_name(): undefined directory name: $!" unless $dir_date_name; if ( $dir_date_name =~ /^((?:19|20)\d\d)_([01]\d)_([0-3]\d)$/ ) { my ( $year, $month, $mday ) = ( $1, $2, $3 ); --$month; print "parse_dir_date_name(): \$dir_date_name =$dir_date_name, \$mday =$mday, ", " \$month =$month, \$year =$year\n" if $debug >= DATE_DEBUG; return ( $mday, $month, $year ); } else { print "parse_dir_date_name(): bad dir date name $dir_date_name\n" if $debug >= DATE_DEBUG; return; } } # Create a 32-bit unix_time value from a string of the form "02_Jan_2000". # The time is set to midnight on that day. sub dir_date_name_to_unix_time_orig { my ( $mday, $month, $year ) = parse_dir_date_name_orig( shift ); return unless defined $year; $year -= 1900; # print "\n\$dir_date_name = $dir_date_name, \$mday = $mday, \$mon = # $mon,", " \$year = $year\n"; my $time = timelocal( 0, 0, 0, $mday, $month, $year ); return $time; } # Create a 32-bit unix_time value from a string of the form "02_Jan_2000". # The time is set to midnight on that day. sub dir_date_name_to_unix_time_2 { my $date = shift; return unless defined $date; if ( $date =~ /^((?:19|20)\d\d)_([01]\d)_([0-3]\d)$/ ) { my ( $year, $month, $mday ) = ( $1, $2, $3 ); $year -= 1900; --$month; my $unix_time; eval { $unix_time = timelocal 0, 0, 0, $mday, $month, $year; }; return $unix_time unless $@; return; } else { return; } } # Create a 32-bit unix_time value from a string of the form "2002_10_14" # The time is set to midnight on that day. sub dir_date_name_to_unix_time($) { my ( $mday, $month, $year ); my $date = shift; print "dir_date_name_to_unix_time(): 1 \$date=$date\n" if $debug >= DATE_DEBUG; if ( ( $mday, $month, $year ) = parse_dir_date_name( $date ) ) { $year -= 1900; my $unix_time; eval { $unix_time = timelocal 0, 0, 0, $mday, $month, $year; }; print "dir_date_name_to_unix_time(): exception: $@" if $@ and $debug >= DATE_DEBUG; return $unix_time unless $@; return; } else { print "dir_date_name_to_unix_time(): 2 parse_dir_date_name failed\n" if $debug >= DATE_DEBUG; return; } } sub old_dir_name_to_new_dir_name($) { my $unix_time = dir_date_name_to_unix_time_orig( shift ); confess unless defined $unix_time; return unix_time_to_dir_date_name( $unix_time ); } # sub dir_date_name_to_unix_time # { # my $dir_date_name = shift; # my ( $mday, $month, $year ); # $dir_date_name =~ /^([0-3]\d)_(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)_((19|20)\d\d)$/; # $mday = $1; # $month = $2; # $year = $3; # # print "\$mday = $mday, \$month = $month, \$year = $year\n"; # $year -= 1900; # my $mon = $months{$month}; # # print "\n\$dir_date_name = $dir_date_name, \$mday = $mday, \$mon = # # $mon,", " \$year = $year\n"; # my $time = timelocal( 0, 0, 0, $mday, $mon, $year ); # return $time; # } our @weekdaynames = qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday ); # Want to see the day of the week on the page for each day, # so we calculate this once for each directory. sub dir_date_name_to_day_name { my $time = dir_date_name_to_unix_time( shift ); my $weekday = ( localtime $time )[ 6 ]; return $weekdaynames[ $weekday ]; } # Input is a datename of the form yyyy_mm_dd, e.g., 2002_10_14 # Output is a string of the form "dd Month yyyy", e.g., "14 October 2002" sub dir_date_name_to_dd_Month_yyyy { my $datestring = dir_date_name_to_nice_title( shift ); print "dir_date_name_to_dd_Mon_yyyy(): 1 \$datestring=$datestring\n"; $datestring =~ s/^[^,]+,\s+(.*)/$1/; print "dir_date_name_to_dd_Mon_yyyy(): 2 \$datestring=$datestring\n"; return $datestring; } # Input is a datename of the form yyyy_mm_dd, e.g., 2002_10_14 # Output is a string of the form "dd Mon yyyy", e.g., "14 Oct 2002" sub dir_date_name_to_dd_Mon_yyyy { my $time = dir_date_name_to_unix_time( shift ); my ( $mday, $month, $year ) = ( localtime $time )[ 3, 4, 5 ]; my $monthname = $short_month_names[ $month ]; $year += 1900; return "$mday $monthname $year"; } sub dir_date_name_to_nice_title($) { my $time = dir_date_name_to_unix_time( shift ); my ( $mday, $month, $year, $weekday ) = ( localtime $time )[ 3, 4, 5, 6 ]; my $dayname = $weekdaynames[ $weekday ]; my $monthname = $long_month_names[ $month ]; $year += 1900; return "$dayname, $mday $monthname $year"; } # Input is a 32-bit unix_time number # output is a string of the form "dd Mon yyyy", e.g., "14 Oct 2002" sub unix_time_to_dd_Mon_yyyy($) { my $unix_time = shift; my ( $mday, $monnum, $year ) = ( localtime $unix_time )[ 3, 4, 5 ]; $mday = sprintf "%02d", $mday; my $mon = ( @short_month_names )[ $monnum ]; return $mday . " " . $mon . " " . ( $year + 1900 ); } # return a string that is the correct format for the date directory # from the link name of a symbolic link to a photo. # The date directory in the link can have either the old or the new format. # Return false if unsuccessful, or if file is not a link. sub get_link_target_new_directory_date_name($) { my ( $filename ) = @_; return unless -l $filename; my $link_name = readlink $filename; # m!^@{[PHOTOS_BASE_OLD]}/(public|fish)/([^/]+)/(.*)! ) { if ( $link_name =~ m!.*/(public|fish)/([^/]+)/([^/]+)! ) { my $dirname = $2; $dirname = old_dir_name_to_new_dir_name( $dirname ) if valid_dir_date_name_orig( $dirname ); return $dirname; } else { warn "$filename is a link to $link_name ", "but cannot find parse the link\n"; } return; } # Pass it a directory name (this will include the directory of the # form yyyy_mm_dd as the last path element). # If there is a file of the name TITLE_FILE (defined above) in this # directory, then read into a new hash and return a reference to it. # The format of this file is: # base jpeg file name => title of picture # Lines starting with a hash # are comments and are skipped blank # lines are allowed. # Note that if duplicate lines are present, the last line is used. sub read_titles($) { my ( $directory ) = @_; my %title_list = (); my $title_file = $directory . '/' . TITLE_FILE; open TITLE, '<', $title_file or return; while ( ) { chomp; next if /^#/; next if /^\s*$/; my ( $key, $title ) = split /\s*=>\s*/; $title_list{$key} = $title; } close TITLE; return \%title_list; } use constant ROTATION_FILE_COMPONENTS => qw( -090 -180 -270 ); use constant ROTATION_LABELS => qw( +90 180 -90 ); sub get_unrotated_name($) { my $rotated_name = shift; # print "rotated_name='$rotated_name' "; return $rotated_name unless grep { index( $rotated_name, $_ ) != -1 } ROTATION_FILE_COMPONENTS; foreach my $rot ( ROTATION_FILE_COMPONENTS ) { $rotated_name =~ s!$rot!!; } # print "unrotated_name='$rotated_name'\n"; return $rotated_name; } sub usage { my $prog = basename $0; my $buttons = $add_buttons ? "yes" : "no"; die <<DEATH; Usage: $prog [options] OPTIONS: --alldirs, -a\t\tdescend into all directories of form year_mm_dd --checkthumbsizes, --nocheckthumbsizes, -t\tcheck thumbnail sizes --startdir=dir, -s dir\tStart from directory dir (default = cwd) --fullsize, -f, --nofullsize\tshow picture at fullsize (default = no) --buttons, -b\tAdd buttons to rotate. copy photos (default = $buttons) --delete, -d, --nodelete\tadd/do not add delete buttons (default = no) --maxdirs=n, -m n\tMax number of directories to process (from newest, \t\tor --lastdate, if specified). Overrides --earliestdate --verbose, -v\t\tAdd to increase verbosity --lastdate=yyyy_mm_dd, -l\tLast date to generate thumbnails for --earliestdate=yyyy_mm_dd, -e\tEarliest date to generate thumbnails for --help, -h\t\tShow this help. DEATH } my $usage = 0; Getopt::Long::Configure( "bundling" ); GetOptions( "alldirs!" => \$alldirs, "a+" => \$alldirs, "checkthumbsizes!" => \$checkthumbsizes, "t+" => \$checkthumbsizes, "startdir=s" => \$startdir, "s=s" => \$startdir, "maxdirs=i" => \$max_dirs, "m=i" => \$max_dirs, "fullsize!" => \$full_size, "f+" => \$full_size, "delete!" => \$add_delete_buttons, "d+" => \$add_delete_buttons, "buttons!" => \$add_buttons, "b+" => \$add_buttons, "verbose+" => \$debug, "v+" => \$debug, "help+" => \$usage, "h+" => \$usage, "lastdate=s" => \$lastdate, "l=s" => \$lastdate, "earliestdate=s" => \$earliestdate, "e=s" => \$earliestdate, ) or usage(); usage() if $usage; my $stdir = abs_path( $startdir ); $startdir = $stdir if $stdir; $add_buttons = 1 if $add_delete_buttons; die "--earliestdate=$earliestdate overiden by --maxdirs=$max_dirs\n" if $earliestdate and $max_dirs; die "--earliestdate=$earliestdate is newer than --lastdate=$lastdate\n" if $earliestdate and $lastdate and $earliestdate gt $lastdate; die "--lastdate=$lastdate is invalid\n" unless not $lastdate or valid_dir_date_name $lastdate; die "--earliestdate=$earliestdate is invalid\n" unless not $earliestdate or valid_dir_date_name $earliestdate; print STDERR "\$startdir = $startdir\n" if $debug; # Read the names of the JPEG files in the current directory, # remembering the mtimes. use vars qw/ %time /; use vars qw/ @files /; # Used to remember which directories contain new files: use vars qw/ %newfiledirs /; undef %newfiledirs; read_photo_locations( @photo_locations ) if $add_buttons; # for each JPEG file, # make a directory name from the date stamp # make the directory if it does not exist; # move the file to the directory, if a file of that name does # not exist there already # READ ALL JPEG FILES IN STARTDIR, MOVE INTO YYYY_MM_DD DIRECTORIES # CREATE EACH YYYY_MM_DD DIRECTORY IF NOT EXIST chdir $startdir or die "Cannot change to photo directory $startdir: $!"; our $cwd = cwd(); print STDERR "Creating directories and moving JPEG files from ", "current directory $cwd:\n"; our $file; opendir TOP, $cwd or die "Cannot open current directory $cwd: $!"; FILE: while ( defined ( $file = readdir TOP ) ) { next unless $file =~ /\.jpg$/; next if $file =~ /-thumb\.jpg$/; foreach my $rot ( ROTATION_FILE_COMPONENTS ) { my $base = basename $file, '.jpg'; print "$base=>$base$rot.jpg\n" if $debug > 4; next FILE if -f "$base$rot.jpg"; } print STDERR "$file\n"; # Nick: 14 Oct 2002: # Migrating from old directory name format to new format: # 14_Oct_2002 => 2002_10_14 # Get dirname in each format, and rename old to new if new does # not exist # 2 Dec 2004: now cope with dangling links my $cname_type_filestamp = get_photo_date $file; my ( $olddirname, $dirname ); if ( $cname_type_filestamp ) { $dirname = unix_time_to_dir_date_name( $cname_type_filestamp ); $olddirname = unix_time_to_dir_date_name_orig( $cname_type_filestamp ); } else { $dirname = get_link_target_new_directory_date_name $file; $olddirname = dir_date_name_to_dd_Mon_yyyy $dirname; $olddirname =~ s/\s/_/g; warn "$file: \$dirname=$dirname, \$olddirname=$olddirname.\n"; } warn "PROBLEM with $file\n" and next FILE unless $dirname and $olddirname; if ( -d $olddirname and not -d $dirname ) { # Need to convert the old directory names to the new format: move( $olddirname, $dirname ) or die "Cannot move $olddirname to $dirname: $!"; } if ( -d $olddirname and -d $dirname ) { warn "Oh Dear, we have both $olddirname and $dirname directories\n"; } unless ( -d $dirname) { # Note: the mode must be writable for group, otherwise the # user p cannot write to the directory to do rotations, and so on. mkdir $dirname, 0775 or die "Cannot make directory $dirname: $!"; } ++$newfiledirs{$dirname}; # Now move the photos into the YYYY_MM_DD directory: unless ( -f "$dirname/$file" ) { move( $file, $dirname ) or die "Cannot move $file to $dirname: $!"; } # Set the times for $dirname to midnight of the date of the name: my $time = dir_date_name_to_unix_time( $dirname ); print "\$dirname = $dirname\n" if $debug >= DATE_DEBUG; if ( defined $time ) { print "\$time = $time\n" if $debug >= DATE_DEBUG; } else { print "\$time undefined\n"; } my $abs = abs_path( $dirname ); warn "\$time undefined: $!" unless defined $time; warn "\$dirname undefined $!" unless defined $dirname; utime $time, $time, $dirname or warn "1: Cannot update utime of $abs in $cwd\n"; } # closedir TOP or die "Cannot close current directory (top): $!"; # for each directory in the current directory, if it matches nn-Mon-yyyy, # change to that directory # copy theme files if necessary # start editing index.html with index_top.html and <table><tr> # for each non-thumb JPEG file in that directory # Build a hash of mtimes indexed by filename # sort hash into an array # for each file in the sorted array # start a new row when necessary # make a thumbnail if not already exist with right size # <td>, copy thumbnail.html into index.html, </td> # copy across picture.html, overwriting any previous # </table> # append index_bottom.html to index.html # cd .. # BUILD SORTED ARRAY OF YYYY_MM_DD DIRECTORIES UNDER STARTDIR: # Want to build an array of directory names, sorted by their date value. # Use this to make next, previous tags for pages of thumbnails. use vars qw/ @directories @sorted_directories $dir_previous $dir_next /; use vars qw/ $name_previous $name_next $num_dirs /; rewinddir TOP or die "Cannot rewind current (top) directory: $!"; while ( defined ( my $dir = readdir TOP ) ) { next unless -d $dir; # next if $dir =~ /^\.\.?$/; if ( valid_dir_date_name_orig( $dir ) and not valid_dir_date_name( $dir ) ) { my $olddir = $dir; my $newdir = old_dir_name_to_new_dir_name( $olddir ); if ( -d $olddir and -d $newdir ) { foreach my $source ( <$olddir/*> ) { my $basename = basename $source; my $target = "$newdir/$basename"; if ( -r $target ) { unlink $source or die "Unable to unlink $source: $!"; } else { move $source, $newdir or die "Unable to move $source, $newdir: $!"; } } rmdir $olddir or die "Unable to rmdir $olddir: $!"; } elsif ( -d $olddir and not -d $newdir ) { move( $olddir, $newdir ) or die "Cannot move $olddir to $newdir: $!"; $dir = $newdir; } } next unless valid_dir_date_name( $dir ); push @directories, $dir; } closedir TOP or die "Cannot close current (top) directory: $!"; # To sort the array of directory names in order of date: sub compare_dirs_orig { my $atime = dir_date_name_to_unix_time( $a ); my $btime = dir_date_name_to_unix_time( $b ); return $atime <=> $btime; } sub year_links(\@) { my $years = shift; my @html; foreach my $year ( @$years ) { push @html, "<a href=\"$year.html\">$year</a>"; } return join '   ', @html; } # Note: must have my patch applied. use HTML::Calendar::Simple; use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Quotekeys = 0; sub generate_calendar_pages(\@) { my $sorted_dirs = shift; my $style=<<END; <!-- body { color: black; background-color: #ffffff; padding:0px; } h1 { margin:0px 0px 5px 0px; padding:0px; font-family: Verdana, Arial, Helvetica, sans-serif; color: black; } td { font-size:12px; font-family: Verdana, Arial, Helvetica, sans-serif; } a { color:Blue; background: yellow; text-decoration:none; } a:link { color: Blue;background: yellow;} a:visited {color: Purple;background: yellow;} a:hover {color:Red;background: yellow;} --> END my %calendar_data = (); foreach ( @$sorted_dirs ) { my ( $year, $month, $day ) = m!^(\d{4})_(\d\d)_(\d\d)$!; ( $year, $month, $day ) = ( 0 + $year, 0 + $month, 0 + $day ); $calendar_data{$year}{$month}{$day} = "$_/index.html"; } print Data::Dumper->Dump( [ \%calendar_data ], [ '*calendar_data' ] ); my @years = sort { $a <=> $b } keys %calendar_data; foreach my $year ( @years ) { $calendar_data{$year}{year} = $year; my $html = HTML::Calendar::Simple->calendar_year( $calendar_data{$year} ); print "HTML before munge:\n$html"; my $title = "Photos taken in $year"; $html =~ s!<th>$year</th>!<th><h1>$title</h1></th>!; print "HTML after munge:\n$html"; open YEAR, '>', "$year.html" or die "unable to open $year.html: $!"; #print YEAR start_html( $title ), h1( $title ), "\n"; print YEAR start_html( -style => { -code => $style }, -title => $title ), "\n"; print YEAR year_links @years; print YEAR $html; print YEAR year_links @years; print YEAR "\n", end_html; close YEAR; } return unless @years; unlink "index.html"; symlink "$years[ $#years ].html", "index.html" or die "unable to link $years[ $#years ].html, index.html: $!"; return 1; } # GENERATE HTML PAGES, ONE FOR EACH YEAR: # The beauty of the new directory naming scheme is that directories # sort lexically: sub compare_dirs { return $a cmp $b; } @sorted_directories = sort compare_dirs @directories; generate_calendar_pages @sorted_directories; #exit; # FIND WHICH DIRECTORY TO START FROM (WE START FROM MOST RECENT): our $last_index; if ( $lastdate ) { for ( my $i = $#sorted_directories; $i >= 0; --$i ) { if ( $sorted_directories[ $i ] le $lastdate ) { $last_index = $i; last; } } warn "No files older than $lastdate found\n" unless $last_index; } $last_index = $#sorted_directories unless $last_index; our $earliest_index; if ( $earliestdate and not $max_dirs ) { for ( my $i = 0; $i <= $last_index; ++$i ) { if ( $sorted_directories[ $i ] ge $lastdate ) { $earliest_index = $i; last; } } warn "No files newer than $earliestdate found\n" unless $earliest_index; } # FIND OLDEST DIRECTORY TO FINISH AT: $earliest_index = 0 unless $earliest_index; our $update_without_jpeg = $alldirs || $earliestdate || $lastdate || $max_dirs; # print <<EOF; # \$update_without_jpeg=$update_without_jpeg # \$alldirs=$alldirs # \$earliestdate=$earliestdate # \$lastdate=$lastdate # \$max_dirs=$max_dirs # EOF # SEE IF THERE IS ANYTHING TO DO: unless ( $update_without_jpeg or %newfiledirs ) { warn "No files found to update.\n"; exit; } # LOOP ONCE FOR EACH DAY DIRECTORY YYYY_MM_DD: $num_dirs = 0; our $new_indexfile =".index.html"; for ( my $i = $last_index; $i >= $earliest_index; --$i ) { my $dir = $sorted_directories[ $i ]; unless ( $alldirs or $earliestdate or $lastdate or $max_dirs ) { # Need to update previous and next page too, since the links # in index.html need to be updated if a new directory is created. next unless exists $newfiledirs{$dir} or defined $newfiledirs{$sorted_directories[ $i - 1 ]} or defined $newfiledirs{$sorted_directories[ $i + 1 ]}; } # BUILD THE MAIN HTML PAGE FOR ONE DAY: print STDERR "\n\nChanging to work on directory $dir:\n"; chdir $dir or die "Cannot cd to $dir: $!"; copy_gallery_files( "." ); ( my $year = $dir ) =~ s/_\d\d_\d\d$//; my $parent_html = "$year.html"; # print "\$dir='$dir', \$parent_html='$parent_html'\n"; $gallery_name = dir_date_name_to_nice_title( $dir ); $gallery_name = "Photos taken on $gallery_name"; $date = unix_time_to_dd_Mon_yyyy( time ); #( $date = unix_time_to_dir_date_name( time ) ) =~ s/_/ /g; unlink $new_indexfile or die "Cannot unlink $new_indexfile: $!" if -e $new_indexfile; gallery_parse_tags( $theme_dir . "index_top.html", $new_indexfile ); print_to_index( $new_indexfile, start_form( -action => CGI_PROGRAM ), hidden( -name => 'directory', -value => cwd(), -override => 1 ), start_table( border => $border ), ); if ( $i == 0 ) { #$dir_previous = "../$parent_html"; #$name_previous = "Up"; $dir_previous = ''; $name_previous = ''; } else { my $dir_previous_basename = basename( $sorted_directories[ $i - 1 ] ); print "\$dir_previous_basename=$dir_previous_basename.\n" if $debug >= DEBUG_LINKS; $dir_previous = "../$dir_previous_basename/index.html"; print "\$dir_previous=$dir_previous.\n" if $debug >= DEBUG_LINKS; $name_previous = dir_date_name_to_dd_Mon_yyyy( $dir_previous_basename ); print "\$name_previous=$name_previous.\n" if $debug >= DEBUG_LINKS; } if ( $i == $#sorted_directories ) { #$dir_next = "../$parent_html"; #$name_next = "Up"; $dir_next = ''; $name_next = ''; } else { my $dir_next_basename = basename( $sorted_directories[ $i + 1 ] ); print "\$dir_next_basename=$dir_next_basename.\n" if $debug >= DEBUG_LINKS; $dir_next = "../$dir_next_basename/index.html"; print "\$dir_next=$dir_next.\n" if $debug >= DEBUG_LINKS; $name_next = dir_date_name_to_dd_Mon_yyyy( $dir_next_basename ); print "\$name_next=$name_next.\n" if $debug >= DEBUG_LINKS; } my $navigation = <<TOP_NAVIGATION; <tr> <td><p> <a href="$dir_previous">$name_previous</a> | <a href="../$parent_html">Up</a> | <a href="$dir_next">$name_next</a></p></td> <td> </td> <td> </td> <td> </td> </tr> TOP_NAVIGATION print_to_index( $new_indexfile, $navigation ); # LOOP THROUGH EACH PHOTO, BUILDING HASH OF TIMES INDEXED BY FILENAME: # This hash contains only the basic JPEG files, not the thumbnails # or the rotated versions of the originals. # Proposal (Not done yet): # Note, Nick, 30 Jan 2004: I think we would be better off building a # better data structure to avoid the silly recalculation of the same # data. I reckon better to build an array of hashes. Each hash # entry is of the form: # [ oname => original-jpeg-file-name, # rname => rotated-name, # degrees => degrees, # mtime => mtime ] # The array will be sorted by time. # End of discussion of proposal. # for each non-thumb JPEG file in that directory # Build a hash of mtimes indexed by filename undef %time; opendir SUB, "." or die "Cannot open current directory $dir: $!"; SELECTED_FILE: while ( defined ( my $file = readdir SUB ) ) { next unless $file =~ /\.jpg$/; next if $file =~ /-thumb\.jpg$/; next if grep { index( $file, $_ ) != -1 } ROTATION_FILE_COMPONENTS; my $mtime = get_photo_date $file; warn "Cannot get mtime for $file" and next SELECTED_FILE unless $mtime; $time{$file} = $mtime; my $required_dir = unix_time_to_dir_date_name( $time{$file} ); if ( $required_dir ne $dir ) { warn "File $file has datestamp $required_dir, ", "but we are in $dir.\n"; } } closedir SUB or die "Cannot close current directory $dir: $!"; # sort hash into an array undef @files; # Sort the file names by their times, oldest first: @files = sort { $time{$a} <=> $time{$b} } keys %time; # for each file in the sorted array # start a new row when necessary # make a thumbnail if not already exist with right size # <td>, copy thumbnail.html into index.html, </td> # copy across picture.html, overwriting any previous # $displayed_image_number is used purely to determine when to # start a new row. Some images are corrupt, and for them, a # thumbnail is not generated. The idea is that each image should # be labelled with its real index, so that corrupt images can be # detected. However, only start a new row after $pics_per_row # thumbnail images have been actually put into a row. my $title_list = read_titles cwd; my $displayed_image_number = 0; print_to_index( $new_indexfile, "\n<tr>\n" ); JPEGFILE: for ( my $i = 0; $i < @files; ++$i ) { my $file = $files[ $i ]; die "Non JPEG file $file!\n" unless $file =~ /\.jpg$/; die "Thumbfile $file in wrong place!\n" if $file =~ /-thumb\.jpg$/; my $unrotated_file_name = $file; ROT: foreach my $rot ( ROTATION_FILE_COMPONENTS ) { my $base = basename $file, '.jpg'; $file = $files[ $i ] = "$base$rot.jpg" and last ROT if -r "$base$rot.jpg"; } my ( $lx, $ly ) = size_of_image_file( $file ); unless ( defined $lx and defined $ly and $lx > 50 and $ly > 50 ) { warn "Cannot get size of $file!\n"; next JPEGFILE; } print "file=$file, \$lx = $lx, \$ly = $ly\n" if $debug > 3; $picture_l = sprintf( "<img alt=\"$file\" src=\"$file\" height=%d width=%d>", $ly, $lx ); # Want the picture to be shown at half size unless it's small: my ( $mx, $my, $sx, $sy ) = ( $lx, $ly, $lx, $ly ); unless ( $full_size or $lx < 1153 ) { $mx = $lx / 2; $my = $ly / 2; # ...and quarter size: $sx = $lx / 4; $sy = $ly / 4; print "Shrinking: \$mx=$mx, \$my=$my, \$sx=$sx, \$sy=$sy.\n" if $debug > 3; } #$picture = "<img alt=\"$file\" src=\"$file\" $size>"; $picture = sprintf( "<img alt=\"$file\" src=\"$file\" height=%d width=%d>", $my, $mx ); $picture_s = sprintf( "<img alt=\"$file\" src=\"$file\" height=%d width=%d>", $sy, $sx ); print STDERR "\$picture = $picture\n" if $debug > 3; print STDERR "\$picture_l = $picture_l\n" if $debug > 3; print STDERR "\$picture_s = $picture_s\n" if $debug > 3; # Start a new row every $pics_per_row thumbnails: print_to_index( $new_indexfile, "\n</tr><tr>\n" ) if $displayed_image_number % $pics_per_row == 0 and ( $displayed_image_number != 0 ); ++$displayed_image_number; # THUMBNAILS # The value of $thumbnail_filename is the title under each thumbnail. # $actual_thumbnail_filename is the real file name of the thumbnail. my $actual_thumbnail_filename = make_thumbnail_filename( $file ); my @values = ( 'ok', 'cp', ROTATION_LABELS ); if ( $add_delete_buttons ) { splice @values, 2, 0, 'del'; # insert after second position } my $title = $unrotated_file_name; # If the file name is long, it may be words separated by hyphens # or underlines, so remove them: $title =~ s/[-_]/ /g if length $title > 15; # print "exists\n" if $title_list; # print "ref\n" if ref $title_list; #print "Check ref for $title\n"; if ( $title_list and exists $title_list->{$unrotated_file_name} ) { #print "title=$title\n"; $title = $title_list->{$unrotated_file_name} if $title_list->{$unrotated_file_name}; #print "title=$title\n"; } $thumbnail_filename = $title; my $current_dir = cwd; $picture_title = start_form( -action => TITLE_CGI_PROGRAM ) . textfield( -name => 'picture_title', -default => $title, -override => 1, -size => 80, ) . hidden( -name => 'titlefile', -default => "$current_dir/" . TITLE_FILE, -override => 1, ) . hidden( -name => 'key', -default => "$unrotated_file_name", -override => 1, ) . end_form; if ( $add_buttons ) { $thumbnail_filename .= radio_group( -name => $unrotated_file_name, -values => [ @values ], -default => 'ok', -linebreak => 0, -rows => 2, -cols => 3, ); } $thumbnail_filename .= "</td><td>"; make_thumbnail( $file, $actual_thumbnail_filename ) or next JPEGFILE; my ( $x, $y ) = size_of_image_file( $actual_thumbnail_filename ) or warn "Cannot get size of $file!\n"; my $picture_html_filename = basename( $unrotated_file_name, ".jpg" ) . ".html"; my $picture_s_html_filename = basename( $unrotated_file_name, ".jpg" ) . "-s.html"; my $picture_l_html_filename = basename( $unrotated_file_name, ".jpg" ) . "-l.html"; $thumbnail = sprintf( "<a href=\"$picture_html_filename\">" . "<img alt=\"$file\" " . "src=\"$actual_thumbnail_filename\" " . "border=\"$imageborder\" " . "height=%d width=%d></a>", $y, $x ); # This is just a sanity check: my $derived_unrot_name = get_unrotated_name $file; cluck "'$derived_unrot_name' from '$file' not match ", "'$unrotated_file_name'" unless $derived_unrot_name eq $unrotated_file_name; # end of sanity check. my $timestamp = mtime_to_string( $time{ $unrotated_file_name } ); $thumbnail_number = sprintf "%03d  $timestamp", $i + 1; $thumbnail_number .= "  <a href=\"$picture_s_html_filename\">Sml</a>" . " <a href=\"$picture_l_html_filename\">Big</a>"; print_to_index( $new_indexfile, "<td>\n" ); gallery_parse_tags( $theme_dir . "thumbnail.html", $new_indexfile ); print_to_index( $new_indexfile, "\n</td>\n" ); # END OF THUMBNAIL # NOW BUILD PICTURE HTML FILES: # First part of the heading for the individual picture html files. # Appears above each large photo. $picture_number = "$file taken at $timestamp"; if ( $i == 0 ) { $picture_previous = $indexfile; $picture_s_previous = $indexfile; $picture_l_previous = $indexfile; } else { my $prev = get_unrotated_name $files[ $i - 1 ]; $picture_previous = basename( $prev, ".jpg" ) . ".html"; $picture_s_previous = basename( $prev, ".jpg" ) . "-s.html"; $picture_l_previous = basename( $prev, ".jpg" ) . "-l.html"; } if ( $i == $#files ) { $picture_next = $indexfile; $picture_s_next = $indexfile; $picture_l_next = $indexfile; } else { my $next = get_unrotated_name $files[ $i + 1 ]; $picture_next = basename( $next, ".jpg" ) . ".html"; $picture_s_next = basename( $next, ".jpg" ) . "-s.html"; $picture_l_next = basename( $next, ".jpg" ) . "-l.html"; } # unlink $theme_dir . "picture.html"; unlink $picture_html_filename; unlink $picture_s_html_filename; unlink $picture_l_html_filename; gallery_parse_tags( ( $theme_dir . "picture.html" ), $picture_html_filename ); gallery_parse_tags( ( $theme_dir . "picture-s.html" ), $picture_s_html_filename ); gallery_parse_tags( ( $theme_dir . "picture-l.html" ), $picture_l_html_filename ); print STDERR "$file "; # A crude progress meter. # END OF PICTURE HTML FILE CONSTRUCTION } print_to_index( $new_indexfile, $navigation ); # </table> # append index_bottom.html to index.html # cd .. print_to_index( $new_indexfile, "\n</table>\n" ); if ( $add_buttons ) { print_to_index( $new_indexfile, popup_menu( -name => "where_to", -values => [ @photo_locations ] ), submit( -value => "Selected Photos", -name => "action", ), submit( -value => "Copy ALL Photos", -name => "action", ), ); } if ( $add_delete_buttons ) { print_to_index( $new_indexfile, submit( -value => "Delete ALL Photos", -name => "action", ), ) } print_to_index( $new_indexfile, endform() ); gallery_parse_tags( $theme_dir . "index_bottom.html", $new_indexfile ); unlink $indexfile; rename $new_indexfile, $indexfile or die "Unable to rename $new_indexfile, $indexfile: $!"; print STDERR "\nChanging directory to '$startdir'\n" if $debug; chdir( $startdir ) or die "Cannot change directory back to $startdir: $!"; my $time = dir_date_name_to_unix_time( $dir ); my $abs = abs_path( $dir ); my $cwd = cwd; utime $time, $time, $dir or warn "Cannot update utime of $abs in $cwd\n"; last if $max_dirs and ++$num_dirs == $max_dirs; } print STDERR "\n\nAll finished.\n"; __END__