#!/usr/bin/perl # # thumbelizer # # Take a directory of photos and create thumbnails # # Required support programs: ImageMagick # Image::Size # Image::Magick # Image::Info # # Credits: Inspired by Garrett LeSage's photo pages. # Html layout made to look like his output (blatent copy). # Ideas from code by Gerard Daubar and imgconvert # Design ideas from dugsong and kia # # Thumbs are generated in name_#.ext format. # denotes image number. # # (C) 2000 Jauder Ho # # 20000128 jauderho [initial whack at the code] # 20000130 jauderho [more work on it. most functions have been cleaned up] # [image reduction is now based on reduction factors instead] # [this should allow better handling of different sized pics] # 20000131 jauderho [image dimensions are now properly reportedly. people do] # [turn the camera occasionally :). first public release] # 20000203 jauderho [moved the layout around a bit which makes things cleaner] # 20001015 lars [added new options for different file system layout, added Image::Info # [for date/time of the picture] # use File::Copy cp; # Self-explanatory use File::Basename; use Image::Size; use Image::Info qw(image_info); use Image::Magick; my $setname; my $set_dir; my (%user,%default,%config); # Adjust to fit to your tastes. You *will* probably want to change these. $default{reduce_factor} = "4 2"; # reduction factor $default{thumb_size} = "120x120"; # size for thumbnails $config{html_filename} = "index.html"; # name for html file created # Edit these for sure if you don't use --interactive $default{sourcedir} = "."; # dir where your pictures are $default{targetdir} = "."; # dir to put finished product in $default{columns} = 4; # number of columns of images $default{page_heading} = "My Photos"; $default{page_desc} = "Some pictures I took with a camera."; $default{sliderdir} = "slides/"; # General config vars that are not prompted in --interactive mode. Adjust to # whatever you like. $config{page_bgcolor} = "#ffffff"; # color for the bg of the page $config{color_link} = "#666666"; # color for link in body tag $config{color_alink} = "#ffffff"; # color for alink in body tag $config{color_vlink} = "#999999"; # color for vlink in body tag $config{color_text} = "#000000"; # text for color $config{font} = "helvetica,arial,sans-serif"; # font for page $config{convert_xoptions} = "-quality 75"; # conver extra options $config{min_columns} = 1; # avoid an error, can ignore $config{max_columns} = 10; # avoid an error, can ignore # These are some personal prefs. You may experiment with them if you want $config{keep_filename} = 0; # Do not rename originals on copy $config{fancy_caption} = 0; # Idea originally from kia's site $config{page_description} = 0; # $config{run_slider} = 1; # Compatibility mode with slider $config{extra_setdir} = 0; # put generated output in extra dir named after set # default is 1 $config{overwrite_set} = 1; # overwrite set. default is 0 # # Do not go any further unless you know what you are doing. You have been # warned appropriately. # $version = "0.8"; # set version number $extra_credit = "(lars)"; $extra_credit_html = "(lars)"; $| = 1; # autoflush output (for progress meter) # Define subroutines sub thumbs_convert_images; sub thumbs_convert_image_sizes; sub thumbs_create_html_file; sub thumbs_create_column; sub thumbs_dir_prepare; sub thumbs_get_prefs; sub thumbs_option_check; sub thumbs_originals_copy; sub thumbs_usage; sub thumbs_version; sub process_prefs; sub slider_create_html; sub slider_create_slides; # Start your engines... thumbs_option_check; # Check ARGV thumbs_version; # Print version # Figure out if we are in interactive mode or use the default prefs instead. ($mode_interactive) ? thumbs_get_prefs : (%user = %default); # Do some post processing on the prefs process_prefs; # Make and prep target dir thumbs_dir_prepare; thumbs_convert_images; # Convert images and place them in target dir thumbs_create_html_file; # Create the HTML thumbs_originals_copy; # Copy originals to set dir slider_create_slides if $config{run_slider}; # create slider html files # The End # Sets user prefs sub thumbs_get_prefs { print "\n"; # Image source directory print "Image source directory [$default{sourcedir}]: "; $user{sourcedir} = ; # Image target directory print "Image target directory [$default{targetdir}]: "; $user{targetdir} = ; # Pref thumb size print "Thumb image size (WxH) [$default{thumb_size}]: "; $user{thumb_size} = ; # Set number of columns print "Number of columns for index [$default{columns}]: "; $user{columns} = ; # Page heading. We are also going to use this to print "Caption [$default{page_heading}]: "; $user{page_heading} = ; # Page description. Only ask if fancy_caption is not turned on and page_description # is turned on if ($config{fancy_caption} != 1 && $config{page_description} == 1) { print "Index description [$default{page_desc}]: "; $user{page_desc} = ; } # Reduction factor i.e. WxH reduced appropriately # Don't be silly and choose something that is not a factor print "Reduction factors [$default{reduce_factor}]: "; $user{reduce_factor} = ; # Fill in the blanks. for (keys %default) { chomp($user{$_}); $user{$_} = $default{$_} if ($user{$_} =~ /^$/); } # Start doing sanity checks and some preprocessing # Make sure that the input directory exists die "ERROR: Source directory does not exist.\n" unless (-d $user{sourcedir}); # Create the target directory if it does not already exist mkdir($user{targetdir},0755) unless (-d $user{targetdir}); # Check for reasonable # of columns die "ERROR: Bad number of columns.\n" if (($user{columns} < $config{min_columns}) || ($user{columns} > $config{max_columns})); print "\n"; } sub process_prefs { # Create @rfactors @rfactors = split(" ", $user{reduce_factor}) unless ($user{reduce_factor} =~ /none/); # Figure out the set name to put stuff in $setname = $user{page_heading}; $setname = lc($setname); $setname =~ s/\W//g; # Drop all non letters } # Creates the dir for the new set # Returns: the dir created on success sub thumbs_dir_prepare { if ($config{extra_setdir}) { $set_dir = "$user{targetdir}/$setname"; } else { $set_dir = $user{targetdir}; } # lars: this is different from thumbelizer 0.8 # if the set directory already exists, but the overwrite_set # config option isn't set, then exit with an error instead of # generating a new set directory name die "ERROR: Unable to create directory $set_dir\n" unless mkdir($set_dir, 0755) || $config{overwrite_set}; mkdir("$set_dir/thumbs", 0755); mkdir("$set_dir/images", 0755); mkdir("$set_dir/originals", 0755); # Will we run slider? if ($config{run_slider} == 1) { mkdir("$set_dir/$default{sliderdir}",0755); mkdir("$set_dir/$default{sliderdir}/0",0755); } # Make the right subdirs for (0 .. $#rfactors) { my $subdir; $subdir = $_ + 1; mkdir("$set_dir/images/$subdir",0755); # create slider subdirs if we run slider mkdir("$set_dir/$default{sliderdir}/$subdir",0755) if ($config{run_slider} == 1); } return($set_dir); } # Convert the images and place them in the proper location. Populate the # $images hash with info. sub thumbs_convert_images { my ($count, $num, $file); my ($x, $y, $sizeinfo); $count = 0; print "Converting images "; for $file (<$user{sourcedir}/*>) { # Make sure input files are binary files and populate the # images hash. if (-B $file && -f $file) { $num = $count + 1; $images[$count]{image_num} = $num; # Figure out the size of the image ($x, $y, $sizeinfo) = imgsize($file); if ($x) { $images[$count]{width} = $x; $images[$count]{height} = $y; } else { ### There's a bug in here somewhere but I ### I haven't quite figured out how to fix it ### just yet. Not to worry, it shouldn't be ### easily triggered. print "\nWARN: $file is in unknown format "; undef $images[$count]; next; } # get image info my $info = image_info($file); die $info->{error} if $info->{error}; # use File::Basename to parse file name # This is based on the premise that the original file # is of the form "base.ext" my ($name, $path, $suffix) = fileparse($file, "." . $info->{file_ext}); $images[$count]{filename} = "$name$suffix"; # Fixup $images[$count]{file_suffix} = $info->{file_ext}; $images[$count]{file_basename} = $name; # store date/time of the picture $images[$count]{DateTimeOriginal} = $info->{DateTimeOriginal}; # read the immage's comment, if any if (-f "$path$name\.txt") { local $COMMENT = "$path$name\.txt"; open COMMENT or die "cannot open $COMMENT: $!"; my $comment = ; close COMMENT; chomp($comment); $images[$count]{comment} = $comment; } $images[$count]{basename} = $images[$count]{filename}; # Lars: filenames must be unique anyway, there is no need # for this indirection # = sprintf("%s_%03d.%s","$setname","$num","$info->{file_ext}"); thumbs_convert_image_sizes($images[$count]); ++$count; } } print " Done\n"; } # Convert the image to all the different sizes sub thumbs_convert_image_sizes { my ($image) = @_; my ($rfactor, $count); # for the progress bar. '.' means file has been skipped, # ':' means files have been converted my $progress = '.'; # Start the counter $count = 1; # Convert to each size. I wish convert was not so fscking slow. for $rfactor (@rfactors) { # only convert images that don't exist or are older than the original if ((not -e "$set_dir/images/$count/$image->{basename}") or (stat("$set_dir/images/$count/$image->{basename}"))[9] < (stat("$user{sourcedir}/$image->{filename}"))[9]) { my ($x, $y); # Figure out using the reduction factor what the final rez is $x = int($image->{width}/$rfactor); $y = int($image->{height}/$rfactor); # Invoke evil system call # todo: replace with PerlMagick call system("convert $config{convert_xoptions} -geometry ${x}x${y}" . " $user{sourcedir}/$image->{filename} " . "$set_dir/images/$count/$image->{basename}"); $count++; $progress = ':'; } } if ((not -e "$set_dir/thumbs/$image->{basename}") or (stat("$set_dir/thumbs/$image->{basename}"))[9] < (stat("$user{sourcedir}/$image->{filename}"))[9]) { # Create a thumbnail. Nasty looking system call isn't it? # system("convert $config{convert_xoptions} -geometry $user{thumb_size}" . # " $user{sourcedir}/$image->{filename} " . # "$set_dir/thumbs/$image->{basename}"); # use PerlMagick to create an image montage my $magick = Image::Magick->new; my $rc = $magick->Read("$user{sourcedir}/$image->{filename}"); warn $rc if $rc; my $thumb = $magick->Montage(geometry=>"$user{thumb_size}", background=>'white'); warn $thumb if !ref($thumb); $rc = $thumb->Write("$set_dir/thumbs/$image->{basename}"); warn $rc if $rc; undef $magick; $progress = ':'; } # Progress meter =). It's a smiley for dria. print $progress; } # Create thumbnail index sub thumbs_create_html_file { my ($date, $count, $num_images, $total_rows, $extra); my ($image_base, $num_left, $td_width, $td_width_extra); my ($i, $j); # grab date, add to heading $date = `date +"%B %e, %Y"`; chomp($date); print "Creating html file "; open(HTML,"> $set_dir/$config{html_filename}") or die("ERROR: Unable to create $set_dir/$config{html_filename}\n"); # Write header information print HTML "\n", "\n\n", "\n", "\t$user{page_heading}\n", "\t\n", "\n\n", "\n\n", "
\n\n"; # Do we want a fancy heading? If so, uppercase $user{page_heading} if ($config{fancy_caption} == 1) { my @caption; my ($string) = uc($user{page_heading}); (@caption) = split(//,$string), print HTML "
", join("    ",@caption), "\n", "


\n"; } else { print HTML "

$user{page_heading}

\n"; if ($config{page_description}) { print HTML "$user{page_desc}

\n"; } } # will we run slider? if ($config{run_slider} == 1) { print HTML "slides "; my $count = 1; for my $rfactor (@rfactors) { print HTML "", "- 1/$rfactor size "; $count++; } print HTML "", "- original size

\n"; } print HTML "Clicking thumbnail will view top resolution ", "listed.

\n"; # Set some needed vars $count = 0; $num_images = @images; # Calculate column width $td_width = int(100 / $user{columns}); # Figure out the number of rows needed $total_rows = int($num_images / $user{columns}); $extra = $num_images % $user{columns}; $total_rows++ unless ($extra == 0); # Print some information for script. XML would probably be useful here. # Feel free to suggest formats. print HTML "\n\n", "\n", "\n", "\n", "\n\n"; # Generate tables for images for ($i = 0; $total_rows > $i; $i++) { print HTML "\t\n", "\t\t\n"; $num_left = $num_images - $count; if ($num_left == $extra) { # Incomplete row for ($j = 0; $extra > $j; $j++) { print "."; $td_width_extra = int(100 / $extra); thumbs_create_column($images[$count],$td_width_extra); ++$count; } } else { # Full row for ($j = 0; $user{columns} > $j; $j++) { print "."; thumbs_create_column($images[$count],$td_width); ++$count; } } print HTML "\t\t\n", "\t
\n\n"; } # Write credit info print HTML "

Generated by ", "", "thumbelizer $version $extra_credit_html"; # Write footer information print HTML "
\n\n", "\n\n", "\n"; close(HTML); print " Done\n"; } # Print out individual columns for html file creation sub thumbs_create_column { my ($image, $td_width) = @_; my ($thumb_width, $thumb_height, $rfactor, $filesize_kb, $count); my ($x, $y); # Get thumb size for html width and height tags to be compliant ($thumb_width, $thumb_height) = ($user{thumb_size} =~ /(\d{1,4})x(\d{1,4})/); $count = 1; # Start the link counter # Print thumb, link to smallest image size print HTML "\t\t

\n", "\t\tImage $image->{image_num}
\n", "\t\t{image_num} - 1; # Account for the case where user does not want reduced size pics if ($user{reduce_factor} =~ /none/) { if ($config{run_slider}) { print HTML "$default{sliderdir}0/$image_num\.html"; } else { print HTML "originals/$image->{basename}"; } } else { if ($config{run_slider}) { print HTML "$default{sliderdir}$count/$image_num\.html"; } else { print HTML "images/$count/$image->{basename}"; } } my $description = "image $image->{image_num}"; # set the description to the image comment if it exists if ($image->{comment}) { $description = $image->{comment}; } # append date/time taken if it exists my $date = $image->{DateTimeOriginal}; if ($date) { $date =~ s/(\d+):(\d+):(\d+) (\d+):(\d+):(\d+)/$1\/$2\/$3 $4:$5:$6/; $description .= " [$date]"; $image->{date} = $date; } $image->{description} = $description; print HTML "\">", "{basename}\" ", "width=\"$thumb_width\" height=\"$thumb_height\" ", "alt=\"$description\" border=1>", "
\n", "\t\t\n"; # Go through and print link to each size for $rfactor (@rfactors) { my $file; $file = "$count/$image->{basename}"; # Figure out the filesize and convert to kb from bytes $filesize_kb = int((-s "$set_dir/images/$file") / 1024); # Figure out the dimensions of the image ($x, $y) = imgsize("$set_dir/images/$file"); # Print the HTML fragment if ($config{run_slider}) { print HTML "\t\t", "${x}x${y} ($filesize_kb kb)
\n"; } else { print HTML "\t\t", "${x}x${y} ($filesize_kb kb)
\n"; } $count++; } # Create a link for the original image too $filesize_kb = int((-s "$user{sourcedir}/$image->{filename}") / 1024); if ($config{run_slider}) { print HTML "\t\t", "original ($filesize_kb kb)
\n"; } else { # Decide if we want to rename the originals if ($config{keep_filename} == 0) { print HTML "\t\t{basename}\">", "original ($filesize_kb kb)
\n"; } else { print HTML "\t\t{filename}\">", "original ($filesize_kb kb)
\n"; } } print HTML "\t\t
\n", "\t\t\n"; } # Copies the original images to $set_dir/originals. Rename if necessary. sub thumbs_originals_copy { my $count = 0; print "Copying originals to $set_dir/originals "; for my $file (<$user{sourcedir}/*>) { # Move binary files, adjust test to fit if (-B $file && -f $file) { # Decide if we want to rename the originals if ($config{keep_filename} == 0) { my $image; # Select the right file to reference $image = $images[$count]; cp("$file","$set_dir/originals/" . "$image->{basename}"); } else { cp("$file","$set_dir/originals"); } $count++; print "."; } } print " Done\n"; } sub slider_create_html { my ($slide_dir,$image_dir) = @_; print "Creating html files for $slide_dir"; for (0 .. $#images) { my ($file,$pfile,$nfile); my $image; $image = $images[$_]; $file = "$slide_dir/$_\.html"; # Set the name of the file # Make sure that we generate the right links if ($_ == 0) { $pfile = "$#images" . ".html"; if ($#images == 0) { $nfile = "0" . ".html"; } else { $nfile = ($_ + 1) . ".html"; } } elsif ($_ == $#images) { $pfile = ($_ - 1) . ".html"; $nfile = "0" . ".html"; } else { $pfile = ($_ - 1) . ".html"; $nfile = ($_ + 1) . ".html"; } open(HTML,"> $file") or die "ERROR: Cannot open file $file for writing.\n"; # Write header information print HTML "\n", "\n\n", "\n"; # Print the title print HTML "\t", "$user{page_heading} ", "[", ($_ + 1), "/", ($#images + 1), "]", "\n"; # Write the rest of the header print HTML "\t\n", "\n\n", "\n\n", "

\n\n"; # Do we want a fancy heading? If so, uppercase # $user{page_heading} if ($config{fancy_caption} == 1) { my @caption; my ($string) = uc($user{page_heading}); (@caption) = split(//,$string), print HTML "
", join("    ",@caption), "\n


\n"; } else { print HTML "

$user{page_heading}

\n"; if ($config{page_description} == 1) { print HTML "$user{page_desc}

\n"; } } # print link to index and nav arrows print HTML "<<<", "     ", "index", "    ", ">>>

\n"; my ($x, $y, $sizeinfo) = imgsize("$set_dir/$image_dir/$image->{basename}"); # Print the picture print HTML "", "{basename}\" ", "align=\"middle\" border=\"1\" ", "width=\"$x\" ", "height=\"$y\" ", "alt=\"$image->{description}\">", "\n"; if ($image->{comment}) { print HTML "

$image->{comment}

\n"; if ($image->{date}) { print HTML "picture taken: $image->{date}

\n"; } } elsif ($image->{date}) { print HTML "

picture taken: $image->{date}

\n"; } else { print HTML "

"; } # Write credit info print HTML "\n

Generated by ", "", "thumbelizer $version $extra_credit_html"; # Write footer information print HTML "

\n\n", "\n\n", "\n"; close(HTML); # Progress meter ... print "."; } # Remove an old index.html if it already exists and create the entry # point for the slide show. unlink("$slide_dir/index.html") if (-f "$slide_dir/index.html"); symlink("0.html","$slide_dir/index.html"); print " Done\n"; } sub slider_create_slides { slider_create_html("$default{sliderdir}0", "originals"); my $count = 1; for my $rfactor (@rfactors) { slider_create_html("$default{sliderdir}$count", "images/$count"); $count++; } } # Print program usage sub thumbs_usage { print "Usage: thumbelizer [OPTION]\n\n", "\t-i, --interactive\tsome config settings prompted\n", "\t-d, --default\t\tdefault mode, uses defaults within file\n", "\t-h, --help\t\tdisplay help (this menu) and exit\n", "\t-v, --version\t\toutput version and exit\n\n"; } # Print program name and version number sub thumbs_version { print "thumbelizer $version $extra_credit\n"; } sub thumbs_option_check { # Make sure we are passed decent arguments unless ($#ARGV == 0) { thumbs_usage; exit; } $_ = $ARGV[0]; if (/^-{1,2}i/) { $mode_interactive = 1; } elsif (/^-{1,2}d/) { $mode_interactive = 0; } elsif (/^-{1,2}v/) { thumbs_version; exit; } else { thumbs_usage; exit; } }