#!/usr/bin/perl -w # Copyright © 2000-2013 Jamie Zawinski # # Permission to use, copy, modify, distribute, and sell this software and its # documentation for any purpose is hereby granted without fee, provided that # the above copyright notice appear in all copies and that both that # copyright notice and this permission notice appear in supporting # documentation. No representations are made about the suitability of this # software for any purpose. It is provided "as is" without express or # implied warranty. # # Created: 13-Sep-2000. # # Generates an HTML gallery of images, with thumbnail pages, plus an HTML # page for each image, with previous/next/etc links. # # For an example of the kinds of pages this script generates, see the # DNA Lounge photo galleries: # # http://www.dnalounge.com/gallery/ # # Usage: gallery.pl *.jpg # # For each xyz.jpg file, it will create xyz-thumb.jpg and xyz.html, plus # a top-level index.html file displaying the thumbnails. There are a # number of additional options: # # --thumb-height N When generating thumbnail images, how tall they # should be. Note: thumbnails are only generated if # the thumb JPG file does not already exist, so if you # change your mind about the thumb height, delete all # the *-thumb.jpg files first to make them be # regenerated. # # --width N How wide the thumbnail index page should be (by using # a max-width div.) Default unlimited. # # --exif-keywords If this is specified, then the EXIF keywords in the # image files will be used as implicit --heading options. # # --title STRING What to use for the index.html title. # # --verbose Be loud; to be louder, "-vvvvv". # # --debug Don't write any files but show what would happen. # # --re-thumbnail In this mode, no HTML is generated; instead, it # re-builds any thumbnail files that are older than # their corresponding images. In this mode (and only # in this mode) the thumbs will be built with the same # dimensions as before. # # --guess Instead of generating anything, this just looks at # the "index.html" file in the current directory and # prints out a guess as to which gallery.pl args were # used to create it (including --width, --heading flags # and image order). # # --byline "Name URL" Inserts a "Photos by ..." line. # # --youtube "Title URL" Inserts a Youtube video. # # --thumb JPG Marks this image with REL="thumb" and creates a square # "thumb.jpg" from it representing the whole gallery. # # # Additional options are the names of the image files, which can be GIF or # JPEG files. Files ending with "-thumb.jpg" and ".html" are ignored, as # are emacs backup files, so it's safe to do "gallery.pl *" without # worrying about the extra stuff the wildcard will match. # # Additionally, the option "--heading HTML-STRING" can appear mixed in # with the images: it emits a subheading at that point on the index page. # So, the arguments # # 1.jpg 2.jpg 3.jpg --heading 'More Images' 4.jpg 5.jpg 6.jpg # # would put a line break and the "More Images" heading between images # 4 and 5. It will also place a corresponding named anchor there. # # Files are never overwritten unless their contents would have changed, # so you can re-run this without your write dates getting lost. require 5; use diagnostics; use strict; use Config; use POSIX qw(mktime strftime); my $progname = $0; $progname =~ s@.*/@@g; my $version = q{ $Revision: 1.112 $ }; $version =~ s/^[^0-9]+([0-9.]+).*$/$1/; my @signames = split(' ', $Config{sig_name}); my $verbose = 0; my $debug_p = 0; my $page_width = undef; my $thumb_height = 360; # was 120 my $do_last_link_p = 1; my $re_thumb_p = 0; my $url_base = undef; my $js_hack = undef; # Ignore any EXIF keywords beginning with "§" my $excluded_exif_keywords = "^\302\244 "; my $title = "Gallery"; my $thumb_page_header = ' %%TITLE%% %%LINKS%%

%%TITLE%%

'; my $image_page_header = $thumb_page_header; my $thumb_page_footer = " \n\n"; my $image_page_footer = " \n\n"; sub capitalize($) { my ($s) = @_; $s =~ s/_/ /g; # capitalize words, from the perl faq... $s =~ s/((^\w)|(\s\w))/\U$1/g; $s =~ s/([\w\']+)/\u\L$1/g; # lowercase the rest # "O'Foo" $s =~ s/\b([OD]\')(\w)/$1\U$2/g; # conjuctions and other small words get lowercased $s =~ s/\b(a|and|in|is|it|of|the|for|on|to|y|et|le|la|el|von|de|der)\b/\L$1/ig; # initial and final words always get capitalized, regardless $s =~ s@(^|[-/]\s*)(\w)@$1\u$2@gs; $s =~ s/(\s)(\S+)$/$1\u\L$2/; # force caps for some things (CD numbers, roman numerals) $s =~ s/\b(((cd|ep|lp|sf|dj)\d*)|([ivxcdm]{3,}))\b/\U$1/ig; # kludge: downcase some entities $s =~ s/(&(amp|lt|gt|apos|quot);)/\L$1/ig; # In-word entities are downcased. $s =~ s/([a-z]&[a-z\d]+;)/\L$1/ig; $s =~ s/(&[a-z\d]+;[a-z])/\L$1/ig; # Guh, 'S $s =~ s/('s\b)/\L$1/ig; $s =~ s/([:,+]) the\b/$1 The/ig; $s =~ s/([:,]) la\b/$1 La/ig; $s =~ s/( vs\.? )/\L$1/ig; $s =~ s/-ts/-Ts/ig; $s =~ s/\b(GWAR|KMFDM|VNV|RX|XP|VTG|XPQ|DNA)\b/\U$1/ig; $s =~ s/brokeNCYDE/brokeNCYDE/ig; $s =~ s/\b(McCool)/McCool/ig; $s =~ s/\b(B\.C\.)/\U$1/ig; $s =~ s/BloodWIRE/BloodWIRE/ig; $s =~ s/-ettes\b/-Ettes/ig; $s =~ s/-volts\b/-Volts/ig; return $s; } # returns an anchor string from some HTML text # sub make_anchor($$) { my ($anchor, $count) = @_; return '' unless $anchor; $anchor =~ s@^(\s*]*>\s*)+@@sgi; # lose leading white tags $anchor =~ s@]*>.*$@@sgi; # only use first line $anchor =~ s@&[^;\s]+;@@gi; # lose entities $anchor =~ s@]*>@ @gi; # tags that become whitespace $anchor =~ s/<[^<>]*>//g; # lose all other tags $anchor =~ s/\'s/s/gi; # posessives $anchor =~ s/\.//gi; # lose dots $anchor =~ s/[^a-z\d]/ /gi; # non alnum -> space $anchor =~ s/^ +//; # trim leading/trailing space $anchor =~ s/ +$//; $anchor =~ s/\s+/_/g; # convert space to underscore $anchor =~ tr/A-Z/a-z/; # downcase $anchor =~ s/^((_?[^_]+){5}).*$/$1/; # no more than 5 words if ($anchor eq '' && $count > 0) { # kludge for when we had some headings, but then go back to "no heading" # at the end of the gallery... $anchor = 'bottom'; } return $anchor; } my $noindex_p = 0; # kludge for the --noindex option # If there's an index.html file, load the default and stylesheet # for all pages (thumbnail indexes and single image pages) from that. # sub load_template() { my $file = "index.html"; # if ($noindex_p) { # $file = `ls *.html | head -1`; #### KLUDGE # chomp ($file); # } my $galthumb = undef; if (open (my $in, '<', $file)) { print STDERR "$progname: reading template $file\n" if ($verbose > 1); local $/ = undef; # read entire file my $body = <$in>; close $in; $body =~ s@().*?()@$1%%TITLE%%$2@s; $body =~ s@([ \t]*]*>[ \t]*\n)+@%%LINKS%%@s; $body =~ s@^([ \t]*)@%%LINKS%%\n$1@m unless ($body =~ m@%%LINKS%%@); if ($body =~ m@^( .* \s* ) .* ( .* )$@six) { ($thumb_page_header, $thumb_page_footer) = ($1, $2); ($image_page_header, $image_page_footer) = ($1, $2); } else { $body =~ s@(]*>).*$@$1\n@si; $thumb_page_header = $body; $image_page_header = $body; } } } # Returns true if the two files differ (by running "cmp") # sub cmp_files($$) { my ($file1, $file2) = @_; my @cmd = ("cmp", "-s", "$file1", "$file2"); print STDERR "$progname: executing \"" . join(" ", @cmd) . "\"\n" if ($verbose > 3); system (@cmd); my $exit_value = $? >> 8; my $signal_num = $? & 127; my $dumped_core = $? & 128; error ("$cmd[0]: core dumped!") if ($dumped_core); error ("$cmd[0]: signal $signal_num!") if ($signal_num); return $exit_value; } sub diff_files($$) { my ($file1, $file2) = @_; my @cmd = ("diff", # "-U2", "-U1", "--unidirectional-new-file", "$file1", "$file2"); print STDERR "$progname: executing \"" . join(" ", @cmd) . "\"\n" if ($verbose > 3); system (@cmd); my $exit_value = $? >> 8; my $signal_num = $? & 127; my $dumped_core = $? & 128; error ("$cmd[0]: core dumped!") if ($dumped_core); error ("$cmd[0]: signal $signal_num!") if ($signal_num); return $exit_value; } # If the two files differ: # mv file2 file1 # else # rm file2 # sub rename_or_delete($$;$) { my ($file, $file_tmp, $suffix_msg) = @_; my $changed_p = cmp_files ($file, $file_tmp); if ($changed_p && $debug_p) { print STDOUT "\n" . ('#' x 79) . "\n"; diff_files ("$file", "$file_tmp"); $changed_p = 0; } if ($changed_p) { if (!rename ("$file_tmp", "$file")) { unlink "$file_tmp"; error ("mv $file_tmp $file: $!"); } print STDERR "$progname: wrote $file" . ($suffix_msg ? " $suffix_msg" : "") . "\n"; } else { unlink "$file_tmp" || error ("rm $file_tmp: $!\n"); print STDERR "$file unchanged" . ($suffix_msg ? " $suffix_msg" : "") . "\n" if ($verbose); print STDERR "$progname: rm $file_tmp\n" if ($verbose > 2); } } # Write the given body to the file, but don't alter the file's # date if the new content is the same as the existing content. # sub write_file_if_changed($$;$) { my ($outfile, $body, $suffix_msg) = @_; my $file_tmp = "$outfile.tmp"; open (my $out, '>', $file_tmp) || error ("$file_tmp: $!"); (print $out $body) || error ("$file_tmp: $!"); close $out || error ("$file_tmp: $!"); rename_or_delete ($outfile, $file_tmp, $suffix_msg); } # Look at an index.html file and try to guess what gallery.pl args were # used to create it... # sub guess() { open (my $in, '<', "index.html") || error ("index.html does not exist"); print STDERR "$progname: reading index.html\n" if ($verbose > 1); local $/ = undef; # read entire file my $body = <$in>; close $in; my ($title) = ($body =~ m@([^<>]*)@si); $title =~ s@, \d\d+ [A-Z][a-z][a-z] \d{4}$@@s; # lose date $title =~ s/'/'/gs; $title =~ s/"/"/gs; my $titleb = $title; $titleb =~ s@:([^ ])@\001$1@gs; $titleb =~ s@: [^:]+?$@@s; # top-level title minus last subtitle my $titlec = $titleb; $titlec =~ s@: [^:]+?$@@s; # minus last two $titleb =~ s@\001@:@gs; $titlec =~ s@\001@:@gs; $body =~ s@^\s*$@--xref $1@gmi; $body =~ s@@@gsi; if ($body =~ s@^(.*?\s*)(
\n\s*
\n)\s*@@si || $body =~ s@^.*?\s*\s*<(NOBR|TABLE\b[^<>]*)>\s*@@si) { my $head = $1; $body =~ s/\s+/ /gs; my ($byline) = ($head =~ m@ \b Photos \s+ by \s+ ( ]* > .*? \s* | [^<>]+ ) @six); if ($byline) { $byline =~ s/\s+/ /gs; $body = "Photos by $byline\n$body"; } $body =~ #### KLUDGE s@MY LIFE WITH THE
@@gsi; $body =~ s@(<(P|A|BR|OBJECT))\b@\n$1@gsi; $body =~ s@(--xref)@\n$1@gsi; $body =~ s@(Photos by)\s+@$1 @gsi; # unwrap photos href $body =~ s@()@{ # unwrap inside object $_ = $1; s/\n/ /gs; $_; }@gsexi; $body =~ s@\s*(]*> 1); local $/ = undef; # read entire file my $body2 = <$in>; close $in; my ($it) = ($body2 =~ m@([^<>]*)@si); error ("$img.html: no title") unless $it; $it =~ s/, \d\d+ [A-Z][a-z][a-z] \d{4}$//s; # lose date $it =~ s@[,:] \d+$@@s; # lose digits my $itb = $it; $itb =~ s@:([^ ])@\001$1@gs; $itb =~ s@: ([^:]+?)$@@s; # title minus last subtitle my $itb2 = $1 || ''; $itb =~ s@\001@:@gs; $itb2 =~ s@\001@:@gs; $body2 =~ s/^.*//s; my ($img2) = ($body2 =~ m@\"]+)"@si); error ("$img.html: no image") unless $img2; $galthumb = $img2 if ($line =~ m@REL="thumb"@si); $it = $title if ($itb2 =~ m/^(img_|dscn)\d+$/si); # Bah. if ($title eq $it || $titleb eq $it) { # no funny business if ($last_subheading) { push @cmd, ("--heading0", ''); $last_subheading = ''; } } elsif ($title eq $itb || $titleb eq $itb || $titlec eq $itb) { push @cmd, ("--heading0", $itb2) unless (uc($itb2) eq uc($last_subheading)); $last_subheading = $itb2; } else { print STDERR "$progname: WARNING: " . "$img.html and index.html titles don't match:" . " \"$titleb\" vs \"$itb\"\n"; push @cmd, ("--heading0", $itb) unless (uc($itb) eq uc($last_subheading)); $last_subheading = $itb; } push @cmd, $img2; } elsif ($line =~ m@^]*\">\s*()(.*?)@si) { $last_subheading = $2; push @cmd, ("--heading", $2); } elsif ($line =~ m@^]*>\s*)*Photos by\s+(.*)@si) { my $p = $1; my ($url) = ($p =~ m@HREF=\"([^<>\"]+)\"@si); $p =~ s/<[^<>]+>/ /gsi; $p =~ s/^\s+|\s+$//gs; $p =~ s/\s+/ /gs; $p .= " $url" if $url; push @cmd, ("--byline", $p); $max_width = $width if ($width > $max_width); $width = 0; } elsif ($line =~ m@^\"]+)\"@si); my ($title) = ($line =~ m@TITLE=\"([^<>\"]+)\"@si); $url =~ s/[?&].*$//si; $url =~ s@/(v|embed)/@/watch?v=@si; $url = "http:$url" if ($url =~ m@^//@s); push @cmd, ("--youtube", "$title $url"); } elsif ($line =~ m@^ $max_width); $width = 0; } elsif ($line =~ m@^--xref (.*)@si) { my $n = $1; $n =~ s/^\s+|\s+$//gs; push @cmd, ("--xref", $n); } else { print STDERR "MISS: $line\n" if ($verbose); } } push @cmd, ("--thumb", $galthumb) if defined($galthumb); print STDOUT $progname; if ($max_width > 0 && $max_width < 1000) { print STDOUT " --width $max_width"; } elsif ($body =~ m@
2); my @result = (); foreach (split (m/\s*;\s*/, $v)) { push @result, $_ unless m/$excluded_exif_keywords/so; } return @result; } sub scan_exif(@) { my (@files) = @_; my @result = (); my %kwds; my %kwd_count; # Gather keywords of each image, and count number of occurences of each. # print STDERR "$progname: scanning EXIF data...\n" if ($verbose > 1); foreach my $file (@files) { error ("can't use $file with --exif-keywords") if ($file =~ m/^-/s); my @kwds = image_exif_keywords ($file); $kwds{$file} = \@kwds; foreach my $k (@kwds) { $kwd_count{$k} = ($kwd_count{$k} || 0) + 1; } } if ($verbose > 1) { my @keys = keys %kwds; print STDERR "$progname: " . ($#keys+1) . " keywords in " . ($#files+1) . " files\n"; } my $last_heading = ''; my $section_count = 0; foreach my $file (@files) { my @okwds = @{$kwds{$file}}; my @kwds = (); foreach my $k (@okwds) { push @kwds, $k if ($kwd_count{$k} <= $#files); } my $heading = uc(join (' + ', @kwds)); if ($heading ne $last_heading) { push @result, "--heading $heading"; $last_heading = $heading; $section_count++; } push @result, $file; } print STDERR "$progname: chose $section_count headings\n" if ($verbose > 1); return @result; } my %image_size_cache = (); sub image_size($) { my ($file) = @_; my $cache = $image_size_cache{$file}; return @{$cache} if $cache; error ("$file does not exist") unless -f $file; return (0, 0) unless -f $file; my $cmd = ("identify -format '%[width]x%[height]' '${file}[0]'"); print STDERR "$progname: executing: $cmd\n" if ($verbose > 3); my $result = `$cmd`; print STDERR "$progname: ==> $result\n" if ($verbose > 3); my ($w, $h) = ($result =~ m/^(\d+)x(\d+)$/); error ("no size: $file") unless ($w && $h); my @c = ($w, $h); $image_size_cache{$file} = \@c; return ($w, $h); } # Generates a bunch of HTML pages for a gallery of the given image files. # These are the indexN pages that contain inline thumbnails. # sub generate_pages($@) { my ($galthumb, @images) = @_; my %thumbs = (); my %sizes = (); load_template (); # For each image: ensure there is a thumbnail, and find the sizes of both. # my $top_byline = undef; my $byline_count = 0; my $last_byline = ''; foreach my $img (@images) { $byline_count++ if ($img =~ m/^--?byline /); $last_byline = $1 if ($img =~ m/^--?byline (.*)/); next if ($img =~ m/^--?heading0? /); next if ($img =~ m/^--?byline /); next if ($img =~ m/^--?youtube /); next if ($img =~ m/^--?xref /); my ($w, $h) = image_size ($img); if (! $h) { print STDERR "$progname: unable to get dimensions: $img\n"; next; } my @L0 = ($w, $h); $sizes{$img} = \@L0; my $t; ($t, $w, $h) = thumb ($img, $w, $h, $last_byline); $thumbs{$img} = $t; my @L1 = ($w, $h); $sizes{$t} = \@L1; } return if ($re_thumb_p); my $toplevel_title = ''; my $subtitle_subpages_p = 1; my $prev_galthumb = undef; # Extract the title from the existing index.html file, if any. # Also the existing thumb image. { my $file = "index.html"; if (open (my $in, '<', $file)) { local $/ = undef; # read entire file my $body = <$in>; if ($body =~ m@]*>(.*?)]*>@si) { $toplevel_title = $1; } if ($body =~ m@\"]+)\" REL=\"thumb\">@si) { $prev_galthumb = $1; } close $in; } } # Default to the galthumb in the index.html file if there is one # and it wasn't specified on the command line; else use the first image. if (!defined($galthumb)) { $galthumb = $prev_galthumb; } if (!defined($galthumb)) { foreach (@images) { next if m/^-/s; $galthumb = $_; last; } } if (defined($galthumb)) { $galthumb =~ s/(-thumb)?\.[^.]+$//si; $galthumb .= ".html"; } my $ogalthumb = $galthumb; # Determine whether any subheading is already contained within the overall # title. If it is, then strip the overall title from the title of sub-pages. # This is to handle these two cases: # # "DNA Lounge: Cabaret Verdalet" (thumbnail page title) # "DNA Lounge: Cabaret Verdalet: Jill Tracy" (image sub-page title) # "DNA Lounge: Cabaret Verdalet: The Lollies" (image sub-page title) # and # "DNA Lounge: Android Lust + Equilibrium" (thumbnail page title) # "DNA Lounge: Android Lust" (image sub-page title) # "DNA Lounge: Equilibrium" (image sub-page title) # # the goal here is to avoid redundant sub-page titles like: # # "DNA Lounge: Android Lust + Equilibrium: Android Lust" # foreach my $img (@images) { next unless ($img =~ m/^--heading0? (.*)/); my $heading = $1; next if ($heading =~ m/^\s*$/s); my $heading_in_title_p = ($toplevel_title =~ m/\Q$heading\E/i); $subtitle_subpages_p = 0 if ($heading_in_title_p); } my $output = ''; my $heading_count = 0; my $last_h = -1; foreach my $img (@images) { my $xref_p = ($img =~ m/^--?xref /); my $byline_p = ($img =~ m/^--?byline /); my $youtube_p = ($img =~ m/^--?youtube /); my $heading_p = ($img =~ m/^--?heading(0)? /); my $invisible_heading_p = $heading_p && defined($1); my $thumb = $thumbs{$img}; my ($w, $h); ($w, $h) = @{$sizes{$thumb}} unless ($xref_p || $heading_p || $byline_p || $youtube_p); # new line if: # # - this is a heading # - this thumbnail has a different height than the one to the left # my $thumb_height_change_p = (!$heading_p && !$byline_p && !$xref_p && !$youtube_p && $last_h > 0 && $last_h != $h); $thumb_height_change_p = 0; #### $last_h = ($h || -1); if (($heading_p && !$invisible_heading_p) || $thumb_height_change_p) { $output .= "\n\n

\n\n"; } if ($invisible_heading_p) { next; } elsif ($heading_p) { my ($heading) = ($img =~ m/^[^\s]+\s+(.*)$/s); #error ("no heading? $img") unless $heading; my $anchor = make_anchor ($heading, $heading_count); print STDERR "$progname: anchor: $anchor\n" if ($verbose > 2); $heading = #### KLUDGE 'MY LIFE WITH THE
' . $heading if ($heading eq 'THRILL KILL KULT'); if ($heading eq '') { $heading = '

'; } else { $heading = "$heading"; } $output .= "\n"; if ($anchor eq '') { $output .= $heading; } else { my $h = $heading; $h =~ s/& /& /gs; $output .= "

$h

"; $heading_count++; } $output .= "\n"; next; } elsif ($byline_p) { my ($byline) = ($img =~ m/^[^\s]+\s+(.*)$/s); my $top_p = (!$output && $byline_count == 1); $byline =~ s/\bmailto://s; my $url = $1 if ($byline =~ s@\s*\b(https?:/[^\s]+)\s*@@gsi); $url = "mailto:$1" if ($byline =~ s%\s+([-_.a-z\d]+@[-_.a-z\d]+)%%gsi || $byline =~ s%([-_.a-z\d]+@[-_.a-z\d]+)\s+%%gsi); $byline =~ s/^\s+|\s+$//gsi; print STDERR "$progname: " . ($top_p ? "top " : "") . "byline: $byline\n" if ($verbose > 2); error ("no byline? $img") unless $byline; $byline = "$byline" if $url; $byline = "Photos by $byline

\n"; if ($top_p) { error ("botched byline") if $top_byline; $top_byline = $byline; } else { $output .= "\n\n

$byline"; } next; } elsif ($xref_p) { my ($n) = ($img =~ m/^[^\s]+\s+(.*)$/s); $output .= "\n"; next; } elsif ($youtube_p) { my ($ytitle) = ($img =~ m/^[^\s]+\s+(.*)$/s); my $url = $1 if ($ytitle =~ s@\s*\b(https?:/[^\s]+)\s*@@si); error ("no youtube url: $img") unless $url; my ($id) = ($url =~ m@v=([^?&<>]+)@si); error ("$url: no id") unless $id; $ytitle =~ s/^\s+|\s+$//gsi; # Only need to hit Youtube if the command line didn't title the video. if (! $ytitle) { my ($id2, $wh, $ss, $otitle); for (my $i = 0; $i < 10; $i++) { # Retry in case it fails ($id2, $wh, $ss, $otitle) = split(/\t/, `youtubedown --size '$url'`); last if $wh; sleep 1; } error ("youtubedown woes: $url") unless $wh; my ($w, $h) = ($wh =~ m/^(\d+)\s*x\s*(\d+)$/si); $otitle =~ s/^\s+|\s+$//s; $ytitle = $otitle; print STDERR "$progname: youtube: $ytitle $url\n" if ($verbose > 2); error ("no youtube title? $img") unless $ytitle; } $ytitle =~ s/&/&/gs; $ytitle =~ s//>/gs; $url = "//www.youtube.com/embed/$id"; $url .= '?version=3'; # new hotness $url .= '&theme=dark'; # darker controls $url .= '&modestbranding=1'; # lose Youtube logo in controls $url .= '&fs=1'; # enable full screen button $url .= '&rel=0'; # turn off "related" mouseovers $url .= '&showsearch=0'; # turn off search field $url .= '&showinfo=0'; # turn off title overlay $url .= '&iv_load_policy=3'; # turn off annotations $url =~ s/\&/&/gsi; # URL-entity-quotify my $url2 = "http://img.youtube.com/vi/$id/0.jpg"; my $em = ("

" . "
" . "" . "
" . "
"); $output .= "\n$em\n"; $ogalthumb = $url2 if (! defined($ogalthumb)); next; } $output .= "\n "; my $img_html = $img; $img_html =~ s/\.[^.]+$/.html/; my $rel = ''; if (defined($galthumb) && $img_html eq $galthumb) { $rel =' REL="thumb"'; $galthumb = undef; } $output .= ("" . "" . ""); } $output =~ s/^\s*

*//s; # No blank lines between headings and bylines. $output =~ s@()(?:\s*

)+\s*(Photos by)@$1
\n$2@gsi; # Extra blank line above adjacent heading and byline. $output =~ s@(

)\s*(\s*
\s*Photos by)@$1
$2@gsi; # No extra blank line at the top. $output =~ s@^(\s*)

(
)?(
\s*

\s* )+ ) @\n\n

\n

\n\n$1\n
@gsxi; $output = ("
\n" . "$output\n" . "
") if ($page_width && $page_width =~ m/^\d+$/s); my $h = $thumb_page_header; my $t = $toplevel_title; $t =~ s/& /& /gs; $h =~ s@%%TITLE%%@$t@gs; $h =~ s@%%LINKS%%@@gs; $output = ("$h\n" . "
\n" . "
\n" . "\n" . "$output\n" . "
\n" . $thumb_page_footer); $output =~ s/(\n\n)\n+/$1/gs; my $file = "index.html"; $output = splice_existing_header ($output, $top_byline, $file); # Give the image pages the same title as the top-level page. # #### I think this clause might be redundant now? # if ($toplevel_title eq '') { $output =~ m@]*>(.*?)]*>@ || error ("$file: no "); $toplevel_title = $1; $toplevel_title =~ s@\s*\bPage\s*\d+@@gsi; print STDERR "$progname: WARNING: no useful title in index.html: " . "please use --title\n" if ($toplevel_title eq ''); } if ($noindex_p) { print STDERR "$progname: $file skipped\n" if ($verbose); } else { write_file_if_changed ($file, $output); } # Generate the image pages. # my $last_anchor = undef; my $last_anchor_title = undef; my $last_anchor_invis = 0; my @all_images = (); foreach my $img (@images) { my $xref_p = ($img =~ m/^--?xref /); my $byline_p = ($img =~ m/^--?byline /); my $youtube_p = ($img =~ m/^--?youtube /); my $heading_p = ($img =~ m/^--?heading(0)? /); my $invisible_heading_p = $heading_p && defined($1); my $thumb = $thumbs{$img}; my ($w, $h); ($w, $h) = @{$sizes{$thumb}} unless ($heading_p || $xref_p || $byline_p || $youtube_p); if ($img =~ m/^--heading (.*)/) { $last_anchor_title = $1; $last_anchor_invis = 0; $last_anchor = make_anchor ($last_anchor_title, $heading_count); $heading_count++ unless ($last_anchor eq ''); next; } elsif ($img =~ m/^--heading0 (.*)/) { $last_anchor_title = $1; $last_anchor_invis = 1; $last_anchor = undef; next; } elsif ($byline_p || $youtube_p || $xref_p) { next; } # Kludge for numeric titles (don't put them in the page title) $last_anchor_title = undef if ($last_anchor_title && $last_anchor_title =~ m/^\d+$/s); my $ii = ($last_anchor ? "./\#$last_anchor" : "./"); my @crud = ( $img, $ii, $last_anchor_title, $last_anchor_invis ); my @crud_copy = ( @crud ); push @all_images, \@crud_copy; } my ($first, $last); if ($#all_images >= 0) { $first = (@{$all_images[0]})[0]; $last = (@{$all_images[$#all_images]})[0]; } for (my $i = 0; $i <= $#all_images; $i++) { my $crud0 = ($i == 0 ? undef : $all_images[$i-1]); my $crud1 = $all_images[$i]; my $crud2 = $all_images[$i+1]; my $prev = (defined($crud0) ? @{$crud0}[0] : undef); my $next = (defined($crud2) ? @{$crud2}[0] : undef); my $img = @{$crud1}[0]; my $index = @{$crud1}[1]; my $ptitle = @{$crud1}[2]; my $invis = @{$crud1}[3]; # Strip off the last bit of the index file's title after the 2nd colon. # E.g., "DNA Lounge: Hubba Hubba: Caveman" => "DNA Lounge: Hubba Hubba". # # $toplevel_title =~ s@(: .+?): .+?$@$1@s; if (!$ptitle) { $ptitle = $toplevel_title; } else { my $tt = $toplevel_title; my $pt = $ptitle; # Sometimes we want "DNA: Event: Act" but sometimes we want "DNA: Act". $tt =~ s@: .+?$@@ unless ($subtitle_subpages_p); $pt =~ s@<(P|BR)\b[^<>]*>@ / @gi; $pt =~ s@<[^<>]*>@ @gi; $pt = capitalize($pt); $ptitle = "$tt: $pt"; } $ptitle =~ s/'/'/gs; $ptitle =~ s/"/"/gs; # WTF. "DNA Lounge: Hubba Hubba Revue: Hubba Hubba Revue: The Fuxedos" $ptitle =~ s@:([^ ])@\001$1@gs; $ptitle =~ s@(: [^:]+)([:,])\b(.*?)\1@[$1][$2][$3]@gsi; $ptitle =~ s@\001@:@gs; my $file = $img; $file =~ s/\.[^.]+$/.html/; generate_page ($img, $ptitle, $index, $prev, $next, $first, $last); } generate_galthumb ($ogalthumb, $prev_galthumb); } my $cwd_cache = undef; # Generates an HTML page for wrapping the single given image. # sub generate_page($$$$$$$) { my ($img, $title, $index_page, $prev_img, $next_img, $first_img, $last_img) = @_; my $file = $img; $file =~ s/\.[^.]+$/.html/; my $output = $image_page_header; $output =~ s@<H1[^<>]*>[^<>]*</H1[^<>]*>\s*@@gi; # delete <H1> $output =~ s/[ \t]+$//s; my $id = $img; $id =~ s@\.[^.\s/]+$@@; # lose ".jpg" # If the current directory or filename seems to have a date in it, use that. # Unless the title already has a date in it. # $cwd_cache = `pwd` unless $cwd_cache; if ($title =~ m@\b\d\d?[- ][a-z][a-z][a-z][- ]\d\d\d\d\b@si) { } elsif ($cwd_cache =~ m@/(\d{4})[-_./](\d\d)[-_./](\d\d)b?\b@si) { my $tt = mktime (0,0,0, $3, $2-1, $1-1900, 0, 0, -1); $id = strftime ("%d %b %Y", localtime ($tt)); } elsif ($img =~ m@\b(\d{4})[-_./](\d\d)[-_./](\d\d)b?\b@si) { my $tt = mktime (0,0,0, $3, $2-1, $1-1900, 0, 0, -1); $id = strftime ("%d %b %Y", localtime ($tt)); } $title =~ s/'/\'/gs; $title =~ s/"/\"/gs; $title .= ", $id"; $title =~ s/& /& /gs; $output =~ s/%%TITLE%%/$title/g; # Lose any existing versions of the tags we generate. $output =~ s/^\s*<META NAME="(viewport|description|medium)"[^<>]*>\n//gmi; $output =~ s/^\s*<LINK REL=\"(image_src|shortcut icon|top|up|prev|next|first|last)\"[^<>]*>\n//gmi; my ($img_width, $img_height) = image_size ($img); error ("unable to get dimensions: $img") unless $img_width; my $links = ''; my $cwd = `pwd`; my $top = ($cwd =~ m@/dna/.*/.*/@ ? "../../../" : #### Kludge. $cwd =~ m@/dna/ads$@ ? "../" : $cwd =~ m@/(dna|jwz)/.*/@ ? "../../" : "../"); $links .= " <LINK REL=\"top\" HREF=\"$top\">\n"; $links .= " <LINK REL=\"up\" HREF=\"$index_page\">\n"; my $first_file = $first_img; my $last_file = $last_img; $first_file =~ s/\.[^.]+$/.html/; $last_file =~ s/\.[^.]+$/.html/; my $prev = $prev_img || ''; my $next = $next_img || ''; $prev =~ s/\.[^.]+$/.html/s; $next =~ s/\.[^.]+$/.html/s; $links .= " <LINK REL=\"first\" HREF=\"$first_file\">\n" unless ($first_file eq $file); $links .= " <LINK REL=\"prev\" HREF=\"$prev\">\n" if ($prev); $links .= " <LINK REL=\"next\" HREF=\"$next\">\n" if ($next); $links .= " <LINK REL=\"last\" HREF=\"$last_file\">\n" if ($do_last_link_p && $last_file ne $file); my $u = $next || $index_page; # $first_file; $prev = ($prev ? "<A HREF=\"$prev\" CLASS=\"navL\"><<</A>" : "<SPAN CLASS=\"navL\"><<</SPAN>"); $next = ($next ? "<A HREF=\"$next\" CLASS=\"navR\">>></A>" : "<SPAN CLASS=\"navR\">>></SPAN>"); my $index = $title; $index =~ s/^.*?: //s; # Lose "DNA Lounge: " $index =~ s/, .*?$//s; # Lose ", DD MMM YYYY" $index =~ s/^Flyer Archive: 1985-1999: //s; # #### Kludge $output .= (" <DIV ALIGN=CENTER>\n" . " <DIV CLASS=\"top\">\n" . " <DIV CLASS=\"gwbox\">\n" . " $prev\n" . " <A HREF=\"$index_page\">$index</A>\n" . " $next\n" . " </DIV>\n" . " </DIV>\n" . " <A HREF=\"$u\">" . "<IMG SRC=\"$img\" CLASS=\"photo\"" . " STYLE=\"max-width:${img_width}px;" . " max-height:${img_height}px\">" . "</A>\n" . # " $prev\n" . # " $next\n" . " </DIV>\n"); $title =~ s/\"/"/gs; # for "description" meta tag. $title =~ s/& /& /gs; # Hints for Facebook and iPhone. # $img_width += 4; my $img_url = ""; if (! $url_base) { if (open (my $in, '<', $file)) { local $/ = undef; # read entire file my $body = <$in>; ($url_base) = ($body =~ m@<LINK REL="image_src" HREF="([^\"<>]+)"@si); $url_base =~ s@[^/]+$@@si if ($url_base); print STDERR "$progname: guessed URL base $url_base\n" if ($verbose > 1); if (! $js_hack) { ($js_hack) = ($body =~ m@(<SCRIPT[^<>]*?SRC=.*?</SCRIPT>)@si); print STDERR "$progname: guessed JS $js_hack\n" if ($verbose > 1); } } } if ($url_base) { $img_url = $url_base; $img_url .= "/" unless ($img_url =~ m@/$@s); } $img_url .= $img; $links = (" <META NAME=\"viewport\" CONTENT=\"width=device-width\">\n" . " <META NAME=\"description\" CONTENT=\"$title\">\n" . " <META NAME=\"medium\" CONTENT=\"image\">\n" . " <LINK REL=\"shortcut icon\" HREF=\"/favicon.ico\" " . "TYPE=\"image/x-icon\">\n" . " <LINK REL=\"image_src\" HREF=\"$img_url\">\n" . $links); # Kludge to order things the way DNA::Menuify does. $links =~ s@^(.*?) ([ \t]*<LINK \s+ REL="image_src"[^<>]*>\n) (.*?) ([ \t]*<LINK \s+ REL="up"[^<>]*>\n) (.*)$ @$1$3$4$2$5@six; # Total kludge to preserve the "gallery.js" thing used by DNA Lounge. if ($js_hack) { my $j = " $js_hack\n"; $output =~ s@\Q$j@@gsi; $links .= $j; } # Another DNA kludge. # $output =~ s@(<HTML)@<!-- %%NOWRAP%% -->\n$1@si; $output =~ s/%%LINKS%%\n*/$links/g; $output .= $image_page_footer; write_file_if_changed ($file, $output, "for $img (${img_width}x${img_height})"); return ($file, $img_width, $img_height); } # Create a thumbnail jpeg for the given image, unless it already exists. # sub thumb($$$$) { my ($img, $img_width, $img_height, $last_byline) = @_; my $thumb_file = $img; $thumb_file =~ s/(\.[^.]+)$/-thumb.jpg/; die if ($thumb_file eq $img); my $this_height = $thumb_height; my $this_width = int (($thumb_height * $img_width / $img_height) + 0.5); my $generate_p = 0; if ($debug_p) { my ($w2, $h2) = image_size ($thumb_file); if ($w2) { ($this_width, $this_height) = ($w2, $h2); } } elsif (! -s $thumb_file) { $generate_p = 1; } else { print STDERR "$progname: $thumb_file already exists\n" if ($verbose > 1); ($this_width, $this_height) = image_size ($thumb_file); error ("unable to get dimensions: $thumb_file") unless $this_width; if ($re_thumb_p) { my $id = (stat($img))[9]; my $td = (stat($thumb_file))[9]; if ($id <= $td) { print STDERR "$progname: $thumb_file ($this_width x $this_height)" . " is up to date\n" if ($verbose > 1); } else { print STDERR "$progname: $thumb_file was $this_width x $this_height\n" if ($verbose > 1); my $ir = $img_width / $img_height; my $tr = $this_width / $this_height; my $d = $ir - $tr; if ($d > 0.01 || $d < -0.01) { print STDERR "$progname: $thumb_file: ratios differ!" . " $img_width x $img_height vs $this_width x $this_height\n"; } else { $generate_p = 1; } } } } if ($generate_p) { my $crop = ($last_byline =~ m/ShutterSlut/si ? 110 : #### KLUDGE $last_byline =~ m/Attic Floc/si ? 130 : # Attic Blow Up # $last_byline =~ m/Attic Floc/si ? 170 : # Attic Bootie $last_byline =~ m/Alex Stover/si ? 145 : 0); my @cmd = ("convert", $img . '[0]', "-quality", "95", # "-fuzz", "1%", "-trim", "+repage", "-crop", "-0-${crop}", # Lose watermark # "-resize", "1000x$thumb_height>", "-resize", "${thumb_height}x${thumb_height}>", "-strip", $thumb_file); print STDERR "$progname: " . join(' ', @cmd) . "\n" if ($verbose > 1); if (system (@cmd) != 0) { my $status = $? >> 8; my $signal = $? & 127; my $core = $? & 128; if ($core) { print STDERR "$progname: $cmd[0] dumped core\n"; } elsif ($signal) { $signal = "SIG" . $signames[$signal]; print STDERR "$progname: $cmd[0] died with signal $signal\n"; } else { print STDERR "$progname: $cmd[0] exited with status $status\n"; } exit ($status == 0 ? -1 : $status); } ($this_width, $this_height) = image_size ($thumb_file); print STDERR "$progname: wrote $thumb_file for $img " . "(${img_width}x${img_height} => ${this_width}x${this_height})\n"; } return ($thumb_file, $this_width, $this_height); } sub generate_galthumb($$) { my ($f, $of) = @_; $of = '' unless $of; $f =~ s/\.html$/.jpg/s; $of =~ s/\.html$/.jpg/s; my ($w, $h) = ($thumb_height, $thumb_height); my $fuzz = 10; #### my $crop2 = 110; #### 35 for some; 110 for ShutterSlut #### 130 for Blow Up my $out = "thumb.jpg"; my @cmd = ("convert", $f, "-fuzz", "${fuzz}%", "-trim", # Lose borders and dark areas "-crop", "-0-${crop2}", # Lose watermark "+repage", "-gravity", "north", "-resize", "^${w}x${h}", "-extent", "${w}x${h}", "-strip", $out); if ($f && $f ne $of) { print STDERR "$progname: replacing $out ($f vs $of)\n" if ($verbose > 1); unlink $out unless $debug_p; } elsif (-f $out) { print STDERR "$progname: $out already exists\n" if ($verbose > 1); return; } if ($debug_p) { print STDERR "$progname: not executing \"" . join(" ", @cmd) . "\"\n"; } else { print STDERR "$progname: executing \"" . join(" ", @cmd) . "\"\n" if ($verbose > 1); system (@cmd); print STDERR "$progname: wrote $out for $f\n"; } } # If the given file exists, extract the HTML header from it, and return # new HTML with that header. This is so we can re-run this script on a # directory after the HTML at the top of the file has been edited without # overwriting that (but changing the thumbnail HTML.) Kludge! # sub splice_existing_header($$$) { my ($html, $top_byline, $file) = @_; open (my $in, '<', $file) || return $html; local $/ = undef; # read entire file my $old = <$in>; close $in; if ($old =~ m@^(.*?\s*)<BR CLEAR=BOTH>\n\s*<DIV ALIGN=CENTER>\n@si || $old =~ m@^(.*?\s*)<DIV ALIGN=CENTER>\s*<(NOBR|TABLE)\b@si) { my $oh = $1; $top_byline = '' unless defined($top_byline); # if ($top_byline) { ($oh =~ s@^( .*? <P> \s* ) # 1 (?: <BR> \s* )? ( \b Photos \s+ by \s+ # 2 (?: <A\b [^<>]* > .*? </A> \s* | [^<>]+ ) (?: <P> \s* )? ) ( .* )$ # 3 @$1$top_byline$3@six) || ($oh =~ s@^( .*? ) ( ( </DIV> \s* )* ) $ @$1<P>$top_byline$2@six) || error ("unable to splice top byline"); error ("botched top byline") if ($oh =~ m/Photos by.*Photos by/si); $oh =~ s@([^\s])(</DIV>\s*)$@$1\n$2@si; # } ($html =~ s@^.*?\s*(<BR CLEAR=BOTH>\n\s*<DIV ALIGN=CENTER>\n)@$oh$1@si) || ($html =~ s@^.*?\s*(<DIV ALIGN=CENTER>\s*<(NOBR|TABLE))\b@$oh$1@si) || error ("$file: couldn't splice pre-existing header"); print STDERR "$progname: $file: kept pre-existing header\n" if ($verbose > 1); # Another DNA-specific kludge.... my ($nav) = ($oh =~ m@<DIV CLASS="navR">\s*(.*?)(<P>|</DIV>)@si); if ($nav) { $nav =~ s/^\s+|\s+$//gsi; $nav = ("<DIV ALIGN=CENTER>\n" . " <BR CLEAR=BOTH>\n" . " <DIV CLASS=\"navC2\">\n" . " $nav\n" . " </DIV>\n" . "</DIV>\n"); $html =~ s@(<!-- %%BOTTOM_END%% -->)@\n$nav$1@si; $html =~ s@<P>\s*(</DIV>)@$1@gsi; print STDERR "$progname: $file: kept pre-existing footer\n" if ($verbose > 1); } } else { print STDERR "$progname: $file: no pre-existing header\n" if ($verbose > 1); } return $html; } # returns the full path of the named program, or undef. # sub which($) { my ($prog) = @_; foreach (split (/:/, $ENV{PATH})) { if (-x "$_/$prog") { return $prog; } } return undef; } sub error($) { my ($err) = @_; print STDERR "$progname: $err\n"; exit 1; } sub usage() { print STDERR "usage: $progname [--verbose] [--width pixels]\n" . " [--thumb-height pixels] [--exif-keywords]\n" . " [--title string] [--heading string]\n" . " [--byline name [URL]] \n" . " [--re-thumbnail] [--guess]\n" . " [--base URL] [--thumb IMG]\n" . " image-files ...\n"; exit 1; } sub main() { my @images; my $tc = 0; my $guess_p = 0; my $exif_p = 0; my $galthumb = undef; while ($_ = $ARGV[0]) { shift @ARGV; if (m/^--?verbose$/) { $verbose++; } elsif (m/^-v+$/) { $verbose += length($_)-1; } elsif (m/^--?debug$/) { $debug_p++; } elsif (m/^--?width$/) { $page_width = shift @ARGV; } elsif (m/^--?thumb-height$/) { $thumb_height = shift @ARGV; } elsif (m/^--?re-?thumb(nail)?$/) { $re_thumb_p = 1; } elsif (m/^--?no-last$/) { $do_last_link_p = 0; } elsif (m/^--?title$/) { $title = shift @ARGV; error ("multiple titles: did you mean --heading?") if ($tc++ > 0); } elsif (m/^--?heading0?$/) { push @images, "$_ " . shift @ARGV; } elsif (m/^--?byline$/) { push @images, "$_ " . shift @ARGV; } elsif (m/^--?xref$/) { push @images, "$_ " . shift @ARGV; } elsif (m/^--?youtube$/) { push @images, "$_ " . shift @ARGV; } elsif (m/^--?guess$/) { $guess_p = 1; } elsif (m/^--?exif(-keywords)?$/) { $exif_p = 1; } elsif (m/^--?no-?index$/) { $noindex_p = 1; } elsif (m/^--?base$/) { $url_base = shift @ARGV; } elsif (m/^--?thumb$/) { $galthumb = shift @ARGV; } elsif (m/^-./) { print STDERR "$progname: unknown: $_\n"; usage; } else { push @images, $_; } } return guess() if ($guess_p); if ($#images < 0) { print STDERR "$progname: no images\n"; exit 0; #usage; } my @pruned = (); foreach (@images) { next if (m/-thumb\.jpg$/); next if (m/^thumb\.jpg$/); next if (m/\.html$/); next if (m/[~%\#]$/); next if (m/\bCVS$/); s@^(http://[a-z\d.]*youtube.com)@--youtube $1@si; push @pruned, $_; } error ("no images specified?") if ($#pruned < 0); @pruned = scan_exif (@pruned) if ($exif_p); generate_pages ($galthumb, @pruned); } main(); exit 0;