#!/usr/bin/perl # Filename: album # Author: David Ljung Madison # See License: http://MarginalHacks.com/License/ my $VERSION= '3.06'; # Rewrite branched off from: 2.53 # Description: Makes a photo album. use strict; use IO::File; umask 022; # 0755 package album; # For plugins ################################################## ################################################## # SETTINGS ################################################## ################################################## # Operating System? (Get from $^O variable) # OSX=Darwin, Win98=MSWin, WinXP=MSWin (damn), Win2k=MSWin32, Cygwin=cygwin my $OSX = ($^O =~ /darwin/i) ? 1 : 0; my $WINDOWS = (!$OSX && ($^O =~ /Win/i)) ? 1 : 0; my $WIN2K = ($^O =~ /MSWin32/i) ? 1 : 0; my $CYGWIN = ($^O =~ /cygwin/i) ? 1 : 0; # tcap isn't needed under cygwin, and I don't think it's # needed (or works) under Win2k my $TCAP = ($WINDOWS && !$CYGWIN && !$WIN2K) ? 1 : 0; my ($BASENAME,$PROGNAME) = split_path($WINDOWS ? '\\' : '/', $0); # Avoid "Broken pipe" messages $SIG{PIPE} = 'IGNORE'; ################################################## # CONF FILES ################################################## my $PROGFILE = ($PROGNAME =~ /^([^\.-]{3,})[\.-]/) ? $1 : $PROGNAME; my @CONFS = ( "/etc/$PROGFILE/$PROGFILE.conf", "/etc/$PROGFILE.conf", ); push(@CONFS, "$BASENAME/$PROGFILE.conf") if $BASENAME ne '.'; push(@CONFS, "$ENV{HOME}/.${PROGFILE}rc") if $ENV{HOME}; push(@CONFS, "$ENV{HOME}/.$PROGFILE.conf") if $ENV{HOME}; # I like to keep all my dot files in one directory besides my $HOME map { push(@CONFS, "$_/${PROGFILE}.conf") } split(':',$ENV{CONF}) if $ENV{CONF}; # Windows: "C:\Documents and Settings\TheUser" push(@CONFS, "$ENV{USERPROFILE}/$PROGFILE.conf") if $ENV{USERPROFILE}; ################################################## # OPTIONS ################################################## # add_option(lvl, option, type, hash) # lvl = usage printing level: -h=1 -more=2 -More=3 (not shown)=4+ # option = option name # hash{args} = args for usage (i.e., -option ) # hash{default} = default value # hash{usage} = "This is the usage line" # hash{usage} = ["Can also be","an array of lines"] # hash{one_time} = Set for options that shouldn't be saved in album.conf # type is one of: my $OPTION_SEP = 1; # Separator my $OPTION_BOOL = 2; my $OPTION_NUM = 3; my $OPTION_STR = 4; my $OPTION_ARR = 5; # Array of strings add_option(1,'h',\&usage, usage=>"Show usage"); add_option(1,'more',\&usage, usage=>"To show more options."); add_option(2,'More',\&usage, usage=>"To show even more options."); add_option(2,'q',$OPTION_BOOL, one_time=>1, usage=>"Be quiet"); add_option(2,'d',$OPTION_BOOL, one_time=>1, usage=>"Set debug mode"); add_option(99,'pod',\&gen_pod, usage=>"Generate pod text"); add_option(1,'conf', \&read_conf, one_time=>1, args=>'', usage=>"Read a .conf file"); add_option(3,'save_conf',$OPTION_BOOL, default=>1, usage=>"Save $PROGFILE.conf files in photo album"); add_option(3,'configure',$OPTION_BOOL, usage=>"Setup initial $PROGNAME site configuration"); add_option(1,'version',\&version, usage=>"Display program version info"); # Album Options: add_option(1,'Album Options:',$OPTION_SEP); add_option(3,'image_pages',$OPTION_BOOL, default=>1, usage=>"Create a page for each image"); add_option(2,'dir_thumbs',$OPTION_BOOL, default=>1, usage=>"Directories have thumbnail (if supported by theme)"); add_option(1,'medium', $OPTION_STR, args=>'', usage=>"Generate medium size images"); add_option(2,'just_medium',$OPTION_BOOL, usage=>"Don't link to full-size images"); add_option(1,'embed',$OPTION_BOOL, default=>1, usage=>"Use image pages for non-picture image pages"); add_option(3,'columns',$OPTION_NUM, default=>4, usage=>"Number of image columns"); add_option(1,'clean',$OPTION_BOOL, one_time=>1, usage=>"Remove unused thumbnails"); add_option(3,'captions',$OPTION_STR, default=>'captions.txt', usage=>"Specify captions filename"); add_option(2,'album_captions',$OPTION_BOOL, default=>1, usage=>"Also show captions on album page"); add_option(1,'caption_edit',$OPTION_BOOL, usage=>"Add comment tags so that caption_edit.cgi will work"); add_option(1,'exif', $OPTION_ARR, args=>'', usage=>["Append exif info to captions. Use %key% in fmt string", "Example: -exif \"
Camera: %Camera model%\"", "If any %keys% are not found by jhead, nothing is appended."]); add_option(3,'exif_album', $OPTION_ARR, args=>'', usage=>"-exif for just album pages"); add_option(3,'exif_image', $OPTION_ARR, args=>'', usage=>"-exif for just image pages"); add_option(2,'file_sizes',$OPTION_BOOL, usage=>"Show image file sizes"); ## For backwards compat with themes? add_option(999,'image_sizes',$OPTION_BOOL, usage=>"DEPRECATED OPTION"); add_option(2,'fix_urls',$OPTION_BOOL, default=>1, usage=>"Encode unsafe chars as %xx in URLs"); add_option(2,'known_images',$OPTION_BOOL, default=>1, usage=>"Only include known image types"); add_option(2,'top',$OPTION_STR, default=>'../', usage=>"URL for 'Back' link on top page"); add_option(2,'all',$OPTION_BOOL, usage=>"Do not hide files/directories starting with '.'"); add_option(1,'add', $OPTION_ARR, args=>'', one_time=>1, usage=>"Add a new directory to the album it's been placed in"); add_option(2,'depth',$OPTION_NUM, default=>-1, one_time=>1, usage=>"Depth to descend directories (default infinite)"); add_option(2,'hashes',$OPTION_BOOL, default=>1, one_time=>1, usage=>"Show hash marks while generating thumbnails"); add_option(2,'name_length',$OPTION_NUM, default=>40, usage=>"Limit length of image/dir names"); add_option(2,'date_sort',$OPTION_BOOL, usage=>"Sort images/dirs by date instead of captions/name"); add_option(2,'name_sort',$OPTION_BOOL, usage=>"Sort by name, not caption order"); add_option(2,'reverse_sort',$OPTION_BOOL, usage=>"Sort in reverse"); add_option(3,'body',$OPTION_STR, usage=>"Specify tags for non-theme output"); add_option(3,'charset', $OPTION_STR, args=>'', default=>'iso-8859-1', usage=>"Charset for non-theme output"); add_option(2,'image_loop',$OPTION_BOOL, default=>1, usage=>"Do first and last image pages loop around?"); add_option(1,'index', $OPTION_STR, args=>'', usage=>["Select the default 'index.html' to use.", "For file://, try '-index index.html' to add 'index.html' to index links."]); add_option(2,'default_index', $OPTION_STR, args=>'', default=>'index.html', usage=>"The file the webserver accesses when no file is specified."); add_option(3,'html', $OPTION_STR, args=>'', default=>'.html', usage=>"Default postfix for HTML files"); # Thumbnail Options: add_option(1,'Thumbnail Options:',$OPTION_SEP); add_option(1,'geometry', \&parse_geometry, args=>'x', default=>'133x133', usage=>"Size of thumbnail"); add_option(99,'x',$OPTION_NUM, default=>133, usage=>"x Size of thumbnail"); add_option(99,'y',$OPTION_NUM, default=>133, usage=>"y Size of thumbnail"); add_option(1,'type',$OPTION_STR, default=>'jpg', usage=>"Thumbnail type (gif, jpg, tiff,...)"); add_option(1,'medium_type',$OPTION_STR, usage=>"Medium type (default is same type as full image)"); add_option(1,'crop',$OPTION_BOOL, default=>0, usage=>["Crop the image to fit thumbnail size", "otherwise aspect will be maintained"]); add_option(3,'CROP',$OPTION_STR, usage=>"Force cropping to be top, bottom, left or right"); add_option(1,'dir',$OPTION_STR, default=>'tn', usage=>"Thumbnail directory"); add_option(2,'force',$OPTION_BOOL, one_time=>1, usage=>["Force overwrite of existing thumbnails", "otherwise they are only written when changed"]); add_option(2,'sample',$OPTION_BOOL, usage=>"convert -sample for thumbnails (faster, low quality)"); add_option(2,'sharpen', $OPTION_STR, args=>'x', usage=>"Sharpen after scaling"); add_option(1,'animated_gifs',$OPTION_BOOL, usage=>"Take first frame of animated gifs (only some systems)"); add_option(2,'scale_opts',$OPTION_ARR, usage=>"Options for convert (use '--' for mult)"); add_option(3,'medium_scale_opts',$OPTION_ARR, usage=>"List of medium convert options"); add_option(3,'thumb_scale_opts',$OPTION_ARR, usage=>"List of thumbnail convert options"); # Plugin Options: add_option(9001,'Plugin Options:',$OPTION_SEP); add_option(9001,'plugin', $OPTION_ARR, usage=>"Activate a plugin."); add_option(9002,'plugin_path', $OPTION_ARR, one_time=>1, default=>["/etc/$PROGNAME/plugins"], usage=>"Add a path to search for plugins."); add_option(9003,'plugin_post', $OPTION_STR, default=>'.alb', usage=>"Default postfix for plugins."); # Theme Options: add_option(1,'Theme Options:',$OPTION_SEP); add_option(1,'theme', $OPTION_STR, args=>'', usage=>"Specify a theme directory"); add_option(2,'theme_url', $OPTION_STR, args=>'', usage=>"In case you want to refer to the theme by absolute URL"); add_option(2,'theme_path', $OPTION_ARR, args=>'', default=>[], usage=>"Directories that contain themes"); # Paths: add_option($WINDOWS?1:10,'Paths:',$OPTION_SEP); add_option(10,'convert',$OPTION_STR, default=>'convert', usage=>"Path to convert (ImageMagick)"); add_option(10,'identify',$OPTION_STR, default=>'identify', usage=>"Path to identify (ImageMagick)"); add_option(10,'jhead',$OPTION_STR, default=>'jhead', usage=>"Path to jhead (extracts exif info)"); add_option(10,'ffmpeg',$OPTION_STR, default=>'ffmpeg', usage=>"Path to ffmpeg (extracting movie frames)"); add_option(10,'conf_file',$OPTION_STR, default=>'album.conf', usage=>"Conf filename for album configurations"); add_option(10,'dev_null',$OPTION_STR, default=>default_dev_null(), usage=>"Throwaway temp file"); # Windows crap: # "Windows. It may be slow, but at least it's hard to use" add_option($WINDOWS?1:10,'windows',$OPTION_STR, default=>$WINDOWS, usage=>"Are we (unfortunately) running windows?"); add_option($WINDOWS?1:10,'cygwin',$OPTION_STR, default=>$CYGWIN, usage=>"Are we using the Cygwin environment?"); # Win98: Needs TCAP: ftp://ftp.simtel.net/pub/simtelnet/msdos/sysutl/tcap31.zip add_option($TCAP?1:10,'use_tcap',$OPTION_BOOL, default=>$TCAP, usage=>"Use tcap? (win98)"); add_option($TCAP?1:10,'tcap',$OPTION_STR, default=>'tcap', usage=>"Path to tcap (win98)"); add_option($TCAP?1:10,'tcap_out',$OPTION_STR, default=>'atrash.tmp', usage=>"tcap output file (win98)"); add_option($TCAP?1:10,'cmdproxy',$OPTION_STR, default=>'cmdproxy', usage=>"Path to cmdproxy (tcap helper for long lines)"); # Default directory page add_option(10,'header',$OPTION_STR, default=>'header.txt', usage=>"Path to header file"); add_option(10,'footer',$OPTION_STR, default=>'footer.txt', usage=>"Path to footer file"); add_option(10,'no_album',$OPTION_STR, default=>'.no_album', usage=>"Ignore dir/files if file with this postfix exists"); add_option(10,'hide_album',$OPTION_STR, default=>'.hide_album', usage=>"Ignore and don't display these files"); add_option(10,'not_img',$OPTION_STR, default=>'.not_img', usage=>"Don't treat these files as images"); # Generally not used as options stuff.. # add_option(99,'path',$OPTION_STR, usage=>"Path of album so far"); # Hacky kludge stuff for internal/my purposes add_option(99,'transform_url',$OPTION_STR, usage=>"Transform image URL"); add_option(99,'enter_eperl',$OPTION_STR, default=>'<:', usage=>"Enter code region in theme"); add_option(99,'leave_eperl',$OPTION_STR, default=>':>', usage=>"Leave code region in theme"); add_option(99,'num_hashes',$OPTION_NUM, default=>20, one_time=>1, usage=>"How many hashes to print"); add_option(99,'hash_width',$OPTION_NUM, default=>78, usage=>"Width of screen (for hashes)"); # As of "ImageMagick 4.2.9 99/09/01" # May not be the same as your version of convert, but damn it's alot! my $IMAGE_TYPES = "AVS|BMP|BMP24|CMYK|DCM|DCX|DIB|EPDF|EPI|EPS|EPS2|EPSF|EPSI|EPT|FAX|". "FITS|G3|GIF|GIF87|GRADATION|GRANITE|GRAY|HDF|HISTOGRAM|ICB|ICC|ICO|". "IPTC|JPG|JPEG|JPEG24|LABEL|LOGO|MAP|MATTE|MIFF|MNG|MONO|MPG|MPEG|MTV|NULL|P7|". "PBM|PCD|PCDS|PCL|PCT|PCX|PDF|PIC|PICT|PICT24|PIX|PLASMA|PGM|PM|PNG|". "PNM|PPM|PREVIEW|PS|PS2|PS3|PSD|PTIF|PWP|RAS|RGB|RGBA|RLA|RLE|SCT|SFW|". "SGI|SHTML|STEGANO|SUN|TEXT|TGA|TIF|TIFF|TIFF24|TILE|TIM|TTF|TXT|UIL|". "UYVY|VDA|VICAR|VID|VIFF|VST|X|XBM|XC|XPM|XV|XWD|YUV"; ################################################## # Data structures # (hashes unless otherwise specified) ################################################## # $opt # ---- # Options from command line and config files # (and also themes and a few from index.html as well) # # $opt->{$option} Array or string or number depending on @OPTIONS above. # # Also: # $opt->{image.th} The image.th file # $opt->{album.th} The album.th file # $opt->{topdir} The path to the top album. # # $data # ----- # Each directory in the album has it's own data structure: # # $data->{unknown} Does the directory contain an unknown HTML? # $data->{start} We are starting our call to album here # @{$data->{dir_pieces}} All the pieces of the path to this part of the album # $data->{depth} Depth of the album to this part # @{$data->{pics}} Array of all the pics in this album # @{$data->{dirs}} Array of all the child directories # $data->{obj}{$obj} Object (image/directory) info (see $obj structure) # $data->{paths} Path info: # {dir} Current working album directory # {album_file} Full path to the album index.html # {album_path} The path of parent directories # {theme} Full path to the theme directory # {img_theme} Full path to the theme directory from image pages # {page_post_url} The ".html" to add onto image pages # {parent_albums} Array of parent albums (album_path split up) # # $obj # ---- # Objects are images, movies, subdirectories.. # Each object has an $obj structure at $data->{obj}{$pic} # # $obj->{type} Which list? pics or dirs # $obj->{is_movie} Boolean: is this a movie? # $obj->{has_thumb} Boolean: does it have a thumbnail? # $obj->{name} Name (cleaned and optionally from captions) # $obj->{cap} Image caption # $obj->{capfile} Optional caption file # $obj->{alt} Alt tag # $obj->{num_pics} [directories only, -dir_thumbs] Num of pics in directory # $obj->{num_dirs} [directories only, -dir_thumbs] Num of dirs in directory # # There are three sizes of images for each pic: full, medium, thumb. # For each of these sizes, we have: # # $obj->{}{x} Width # $obj->{}{y} Height # $obj->{}{file} Filename (without path) # $obj->{}{path} Filename (full path) # $obj->{}{filesize} Filesize in bytes # $obj->{full}{tag} Tag - either 'image' or 'embed' (only for full) # # Image objs also have URL info: # # $obj->{URL} URL paths: {URL}{from_page}{to} # {album_page}{image} Image_URL from album_page # {album_page}{thumb} Thumbnail from album_page # {image_page}{image} Image_URL from image_page # {image_page}{image_page} This image page from another image page # {image_page}{image_src} The URL for the image page # {image_page}{thumb} Thumbnail from image_page # # And directory objs have: # # $obj->{URL}{album_page}{dir} URL to the directory from it's parent album page # # Internal $data/$opt fields # -------------------------- # There are also some _fields in $data that are internal (not for theme use) # # $opt->{_theme}{..} Contains entire text of the image.th/album.th # $opt->{_theme_line}{..} The starting line # $opt->{_captions}{$dir} Captions cache for a path # (in opt to survive multiple do_album calls) # @{$opt->{_albums}} List of albums to run on # $opt->{_album}{$alb} Hash of any album settings (such as 'add') # $data->{paths}{_date_sort_cache} Date sort values # $data->{eperl} The ePerl support text ################################################## # Bootstrap/Simple utilities ################################################## sub attempt_require { eval "require $_[0]"; $@ ? 0 : 1; } my %OPTIONS; my @OPTIONS; my %DEFAULTS; sub add_option { my ($lvl,$option,$type,%hash) = @_; $hash{lvl}=$lvl; $hash{type}=$type; push(@OPTIONS,$option); $hash{default}=0 unless $hash{default} || $type!=$OPTION_BOOL; $OPTIONS{$option} = \%hash; $DEFAULTS{$option} = $hash{default}; } ######################### # Windows blows ######################### # 1) Can't handle "\Qfile\E"; sub file_quote { my ($opt,$file) = @_; $opt->{windows} ? "\"$file\"" : "\Q$file\E"; } # 2) Can't create .files # 3) .exe extension if we don't have it # (fixed in get_defaults) # 4) Stupid $0 is probably '/' not '\' # (Fixed in PROGNAME split_path above) # 5) Can't handle 'open(FOO,"cmd |")' or 2>&1 # (Though 2>&1 works in Win2000, ActivePerl and Cygwin) my $TMPFILE; sub open_pipe { my ($opt,$cmd,$args) = @_; print STDERR "run: $cmd $args\n" if $opt->{d}; my $fh = new IO::File; $cmd = file_quote($opt,$cmd); # Happy Unix return (open($fh, "$cmd $args 2>&1 |")) && $fh unless $opt->{cygwin} || $opt->{use_tcap}; # Win98 (use TCAP) if ($opt->{use_tcap}) { usage("Couldn't find 'tcap'") unless $opt->{tcap}; # Put tcap args in the tcap env var, so to reduce line length (128 limit) $ENV{tcap}="-overwrite *$opt->{tcap_out}"; $TMPFILE = $opt->{tcap_out}; # Interrupt handlers can remove it.. my $tcap = file_quote($opt,$opt->{tcap}); $tcap .= " ".file_quote($opt,$opt->{cmdproxy}) if $opt->{cmdproxy}; system("$tcap -c $cmd $args"); (open($fh, "$opt->{tcap_out}")) || fatal($opt,"Can't open $opt->{tcap} output [$opt->{tcap_out}]"); return $fh; } # Windows2000,XP: -| pipe method, doesn't seem to work on Win98 # (Only works under Cygwin??) # Otherwise error: '-' is not recognized as an internal or external # command, operable program or batch file my $pid = (open($fh,"-|")); return undef unless defined $pid; # Failed return $fh if $pid; # Parent # Child (open(STDERR,">&STDOUT")) || fatal($opt,"open_pipe(): Can't dup stdout\n"); exec("$cmd $args"); } # 5 1/2) Clean up the tmp file (for Win98) sub all_done { print STDERR "@_\n" if @_; unlink($TMPFILE) if $TMPFILE; exit; } $SIG{INT} = \&all_done; $SIG{TERM} = \&all_done; $SIG{HUP} = \&all_done; $SIG{QUIT} = \&all_done; $SIG{EXIT} = \&all_done; $SIG{__DIE__} = \&all_done; # 6) Can't handle /dev/null sub default_dev_null() { return '/dev/null' if !$WINDOWS || $CYGWIN; ($ENV{TMP} || $ENV{TEMP} || '/tmp')."/$PROGFILE.null"; } ######################### # URLs for these scripts - don't change ######################### my $HOME = "http://MarginalHacks.com/"; my $ALBUM_URL = "${HOME}Hacks/album/"; my $GEN_STRING = "album $HOME"; my $OLD_GEN_RE = "Generated by $PROGNAME and thumb"; sub get_defaults { my $opt = \%DEFAULTS; # Windows defaults are slightly different (no .files) if ($opt->{windows}) { # These aren't that important because of search_path_win() $opt->{convert} .= ".exe" unless $opt->{convert} =~ /\.exe$/; $opt->{identify} .= ".exe" unless $opt->{identify} =~ /\.exe$/; $opt->{no_album} =~ s/^\.//g; $opt->{hide_album} =~ s/^\.//g; } $opt; } ################################################## # COMMAND-LINE OPTIONS AND CONFIGURATIONS ################################################## sub fatal { my ($opt,@msg) = @_; print STDERR "\n[$PROGNAME] ",join("\n", @msg),"\n\n" if @msg; exit -1; } sub usage { my (@msg) = @_; # Called with error my ($opt,$option,$val) = @_; # Called from option version(); # If it was called from -h, -more or -More my $show=1; if (ref $opt eq 'HASH') { $show = 2 if $option eq "more"; $show = 3 if $option eq "More"; undef @msg; } # Otherwise we have a usage error if (@msg) { map { print STDERR "\nERROR: $_\n"; } @msg; print STDERR "\nTry '$PROGNAME -h' for usage info.\n\n"; exit -1; } # Print usage. print STDERR < \tMakes a photo album \tAll boolean options can be turned off with '-no_