#! /bin/sh # This script creates tumbnails, reduced versions of large images, and an # index.html file pointing to all of it. # Design goals are # (1) exist in a single script that can be mailed around easily, etc. # (2) generate clean, simple, portable HTML # (3) no databases # (4) portable portable portable # There are many programs that violate some or all of these goals. They # are real neat, they make great web pages, they organize your life. They # don't fit in a single ugly Bourne shell script whose only point in life # is to take pictures off your digital camera and make them basically # presentable to your friends. # This script is in the public domain, share and enjoy. # The latest version is always at http://www.molenda.com/makethumbs/ # Written by Jason Molenda, 1998-09-13; rewritten less lamely 2001-10-26. ## Guidelines for hacking: ## ## Start at main() and trace the function calls from there. ## Remember that there is no scoping of variables, so local variables aren't. ## You'll see some function-local variables with common names ("$fn" etc) ## prefixed with an abbreviation of the function name so that they don't ## overwrite a similarly named variable in the caller. ## No cycles in the call graph or you'll lose. ## As much as I like Bourne shell, I probably should have just done this ## in perl or python. On the down side, whichever of those I pick, I'll ## hear constant whining from about half of my friends. No one likes ## Bourne shell, so it's the safest language choice. ## Return variables are prefixed with "RETURN_". Command line options ## are prefixed with "ARGV_". Defaults are prefixed with "DEFAULT_", ## and globals are prefixed with "GLOBAL_". ## GLOBAL_ will hold all the settings that we should act on. These came ## either from a DEFAULT_ or an ARGV_ if the user overrode the DEFAULT_. ## This script used to have no functions at all, but I found that annoying ## to maintain, so now I've embraced function-mania. main () { init_defaults read_dotrc_file find_needed make_dir_transversible "." parse_args $* get_image_list point_stdout_to_index_file progress_update_start_creating_index_html [ $GLOBAL_print_html_header_footer -eq 1 ] && print_html_header print_html_table_start iterate_over_image_list print_html_table_end [ $GLOBAL_print_html_header_footer -eq 1 ] && print_html_footer progress_update_done_creating_index_html [ $GLOBAL_create_slideshow -eq 1 ] && create_slideshow exit 0 } create_slideshow () { [ $GLOBAL_create_slideshow -eq 0 ] && return progress_update_start_creating_slideshow_pages remove_existing_slideshow_html_files create_slideshow_image_list while read fileset do prev=`echo "$fileset" | cut -d\| -f1` cur=`echo "$fileset" | cut -d\| -f2` next=`echo "$fileset" | cut -d\| -f3` create_slideshow_source_file "$prev" "$cur" "$next" if [ $GLOBAL_reduce_big_pics -eq 1 -a $GLOBAL_remove_originals -ne 1 ] then create_slideshow_reduced_file "$prev" "$cur" "$next" fi progress_update_created_slideshow_page done < $GLOBAL_slideshow_image_list progress_update_done_creating_slideshow_pages } create_slideshow_source_file () { cssf_prev_image="$1" cssf_cur_image="$2" cssf_next_image="$3" image_name_to_html_name "$cssf_cur_image" cssf_html_file="$RETURN_html_name" add_cleanup "$cssf_html_file" print_slideshow_file_header "$cssf_cur_image" "$cssf_html_file" print_slideshow_top_navlinks "$cssf_prev_image" "$cssf_cur_image" \ "$cssf_next_image" "source" "$cssf_html_file" print_slideshow_image "$cssf_cur_image" "$cssf_html_file" "source" print_slideshow_bottom_navlinks "$cssf_prev_image" "$cssf_cur_image" \ "$cssf_next_image" "source" "$cssf_html_file" print_slideshow_file_footer "$cssf_html_file" remove_cleanup "$cssf_html_file" } create_slideshow_reduced_file () { cssf_prev_image="$1" cssf_cur_image="$2" cssf_next_image="$3" cssf_cur_source_image="$cssf_cur_image" cssf_cur_reduced_image="$cssf_cur_image" if [ "$cssf_prev_image" != NULL ] then source_name_to_reduced_name "$cssf_prev_image" cssf_prev_image="$RETURN_reduced_name" fi source_name_to_reduced_name "$cssf_cur_image" cssf_cur_reduced_image="$RETURN_reduced_name" if [ -f "$RETURN_reduced_name" ] then cssf_cur_image="$RETURN_reduced_name" fi if [ "$cssf_next_image" != NULL ] then source_name_to_reduced_name "$cssf_next_image" cssf_next_image="$RETURN_reduced_name" fi image_name_to_html_name "$cssf_cur_reduced_image" cssf_html_file="$RETURN_html_name" add_cleanup "$cssf_html_file" print_slideshow_file_header "$cssf_cur_image" "$cssf_html_file" print_slideshow_top_navlinks "$cssf_prev_image" "$cssf_cur_image" \ "$cssf_next_image" "reduced" "$cssf_html_file" print_slideshow_image "$cssf_cur_image" "$cssf_html_file" "reduced" print_slideshow_bottom_navlinks "$cssf_prev_image" "$cssf_cur_image" \ "$cssf_next_image" "reduced" "$cssf_html_file" print_slideshow_file_footer "$cssf_html_file" remove_cleanup "$cssf_html_file" } print_slideshow_file_header () { psfh_image_file="$1" psfh_html_file="$2" get_file_english_name "$psfh_image_file" psfh_english_name="$RETURN_english_name" # The following exec goop so I don't have to manually redirect every # message to stderr in this function. exec 4>&1 # save stdout fd to fd #4 exec > "$psfh_html_file" # redirect stdout to stderr echo '' echo '' echo '' echo " $psfh_english_name" [ -n "$GLOBAL_meta_tag" ] && echo "$GLOBAL_meta_tag" if [ "$GLOBAL_html_charset" != "undef" ] then echo " " fi echo ' ' echo ' ' echo '' echo "$GLOBAL_body_tag" echo '' echo "" exec 1>&4 # Copy stdout fd back from temporary save fd, #4 make_file_readable "$psfh_html_file" } print_slideshow_file_footer () { psff_html_file="$1" echo '' >> "$psff_html_file" echo "" >> "$psff_html_file" echo "" >> "$psff_html_file" } print_slideshow_image () { psi_cur_image="$1" psi_html_file="$2" psi_type="$3" psi_target_pre="" psi_target_post="" if [ $psi_type = source ] then source_name_to_reduced_name "$psi_cur_image" if [ -f "$RETURN_reduced_name" ] then image_name_to_html_name "$RETURN_reduced_name" psi_target_pre="" psi_target_post="" fi else is_reduced_name "$psi_cur_image" if [ "$RETURN_is_reduced_name" -eq 1 ] then reduced_name_to_source_name "$psi_cur_image" if [ -f "$RETURN_source_name" ] then image_name_to_html_name "$RETURN_source_name" psi_target_pre="" psi_target_post="" fi fi fi get_dimensions "$psi_cur_image" echo "" >> "$psi_html_file" echo "

" >> "$psi_html_file" echo "

$psi_target_pre$psi_target_post
" >> "$psi_html_file" echo "

" >> "$psi_html_file" echo "" >> "$psi_html_file" } print_slideshow_bottom_navlinks () { print_slideshow_top_navlinks "$1" "$2" "$3" "$4" "$5" } print_slideshow_top_navlinks () { pstn_prev_image="$1" pstn_cur_image="$2" pstn_next_image="$3" pstn_page_type="$4" pstn_html_page="$5" pstn_prev_html="$pstn_prev_image" pstn_cur_html="$pstn_cur_image" pstn_next_html="$pstn_next_image" pstn_middle_markup="" if [ "$pstn_prev_image" != NULL ] then image_name_to_html_name "$pstn_prev_image" pstn_prev_html="$RETURN_html_name" fi image_name_to_html_name "$pstn_cur_image" pstn_cur_html="$RETURN_html_name" if [ "$pstn_next_image" != NULL ] then image_name_to_html_name "$pstn_next_image" pstn_next_html="$RETURN_html_name" fi ## Emit link to previous image if [ "$pstn_prev_image" = NULL ] then pstn_prev_markup="$GLOBAL_slideshow_previous_pre_link$GLOBAL_slideshow_previous$GLOBAL_slideshow_previous_post_link" else pstn_prev_markup="$GLOBAL_slideshow_previous_pre_link$GLOBAL_slideshow_previous$GLOBAL_slideshow_previous_post_link" fi ## Emit link to next image if [ "$pstn_next_image" = NULL ] then pstn_next_markup="$GLOBAL_slideshow_next_pre_link$GLOBAL_slideshow_next$GLOBAL_slideshow_next_post_link" else pstn_next_markup="$GLOBAL_slideshow_next_pre_link$GLOBAL_slideshow_next$GLOBAL_slideshow_next_post_link" fi ## Emit link to index pstn_middle_markup="$GLOBAL_slideshow_ret_to_index_pre_link$GLOBAL_slideshow_ret_to_index$GLOBAL_slideshow_ret_to_index_post_link" echo '' >> "$pstn_html_page" echo '' >> "$pstn_html_page" echo '

' >> "$pstn_html_page" echo " " >> "$pstn_html_page" echo " " >> "$pstn_html_page" echo " " >> "$pstn_html_page" echo '
$pstn_prev_markup$pstn_middle_markup$pstn_next_markup
' >> "$pstn_html_page" echo '' >> "$pstn_html_page" echo '' >> "$pstn_html_page" } is_reduced_name () { RETURN_is_reduced_name=0 if echo "$*" | grep -- '-r\.[a-z][a-z][a-z]' >/dev/null 2>&1 then RETURN_is_reduced_name=1 fi } create_slideshow_image_list () { number_of_files=`cat $GLOBAL_image_list_tmpfile | wc -l | sed 's, ,,g'` cur_num=1 make_tmpfile ss-source GLOBAL_slideshow_image_list=$RETURN_tmpfile add_cleanup $GLOBAL_slideshow_image_list while [ $cur_num -le $number_of_files ] do prev_num=`expr $cur_num - 1` next_num=`expr $cur_num + 1` if [ $cur_num -eq 1 ] then prev_image=NULL else prev_image=`cat $GLOBAL_image_list_tmpfile | sed -n "${prev_num}p"` fi cur_image=`cat $GLOBAL_image_list_tmpfile | sed -n "${cur_num}p"` if [ $cur_num -eq $number_of_files ] then next_image=NULL else next_image=`cat $GLOBAL_image_list_tmpfile | sed -n "${next_num}p"` fi echo "${prev_image}|${cur_image}|${next_image}" >> $GLOBAL_slideshow_image_list cur_num=`expr $cur_num + 1` done } remove_existing_slideshow_html_files () { make_tmpfile file_list file_list=$RETURN_tmpfile add_cleanup $file_list ls -1 | grep '\.html' > $file_list while read fn do if grep "makethumbs generated HTML file which can be removed without warning" "$fn" >/dev/null 2>&1 then rm -f "$fn" fi done < $file_list } # RETURN_found is 1 if found, 0 if not found. If found, $RETURN_fullname # contains the path + filename. find_in_path () { target="$1" RETURN_found=0 OFS="$IFS" IFS=: for fip_i in $PATH do if [ -z "$fip_i" ] then dir="." else dir="$fip_i" fi if [ -f "$dir/$target" ] then RETURN_fullname="$dir/$target" RETURN_found=1 break fi done IFS="$OFS" } # Parse a user's $HOME/.makethumbsrc. The code here is kind of # funky because I didn't want to just bourne-shell source the file # (". $HOME/.makethumbsrc"); any syntax error in that startup file # would have caused makethumbs to fail in weird ways and users could have # trouble debugging it. It's a huge amount of work to get all the parsing # correct so spaces, quote marks, apostrophies, etc., are all carried over # accurately. The sed expression where $value gets set is not something # I'm too proud of - this is always the sort of thing that is tricky in # Bourne shell. read_dotrc_file () { dotrc_file="$HOME/.makethumbsrc" GLOBAL_found_dotrc_vars=0 [ ! -f "$dotrc_file" ] && return make_tmpfile dotrc tmpf=$RETURN_tmpfile add_cleanup $tmpf cat "$dotrc_file" | grep -v '^[ ]*#' | grep = > $tmpf [ ! -s $tmpf ] && return while read line do varname_is_valid=0 varname=`echo "$line" | sed -e 's,=.*,,' -e 's,[^A-Za-z_0-9],,g' \ -e 's,^ARGV_,,' -e 's,^GLOBAL_,,' \ -e 's,^DEFAULT_,,' -e 's,^DOTRC_,,'` value=`echo "$line" | sed -e 's,^[^=]*=,,' -e 's,^[ ]*,,' \ -e 's,[ ]*$,,' \ -e s,^\[\"\'\]\[\ \ \]\*,, \ -e s,\[\ \ \]\*\[\"\'\]\$,, \ -e 's,",\\\\",g'` # Some of what the above sed does: Remove spaces at beginning or end. # Remove " or ' marks at end/begin, and any spaces after them. # Backslash escape any " marks so the evals below work right. eval [ -n \"\$GLOBAL_$varname\" ] && varname_is_valid=1 if [ $varname_is_valid -eq 0 ] then echo ERROR: "$dotrc_file" has unrecognized variable name, \"$varname\"!>&2 continue fi # Try to do a little verification if the current value is numeric. I could # probably add some extra checks if the current val is 0 or 1, making an # assumption that it's a boolean value. (someone might try to use "yes" # instead of '1', for instance) curval=`eval echo \\$GLOBAL_$varname` if echo "$curval" | grep '^[0-9]*$' >/dev/null 2>&1 then if [ -z "$value" ] then echo WARNING: Variable $varname currently has a numberic value of $curval >&2 echo WARNING: but you\'re setting it to an empty value. >&2 else if echo "$value" | grep '^[0-9]*$' >/dev/null 2>&1 then : else echo WARNING: Variable $varname currently has a numeric value of $curval >&2 echo WARNING: but you\'re setting it to \"$value\". >&2 fi fi fi GLOBAL_found_dotrc_vars=1 eval DOTRC_$varname=\"$value\" eval GLOBAL_$varname=\$DOTRC_$varname done < $tmpf } # Returns a temp file in $RETURN_tmpfile. # Takes an optional description name argument. make_tmpfile () { find_in_path mktemp if [ $RETURN_found -eq 1 ] then RETURN_tmpfile=`mktemp -q "$TMPDIR/makethumbs-$1.XXXXXXX"` if [ $? -eq 0 ] then return 0 fi fi base_tmpfile="$TMPDIR/makethumbs-$1.$$" RETURN_tmpfile="$base_tmpfile" n=0 while [ -f "$RETURN_tmpfile" ] do n=`expr $n + 1` RETURN_tmpfile="${base_tmpfile}-$n" done return 0 } # Ends program execution if a necessary program is missing. find_needed () { missed_something=0 for progname in djpeg cjpeg pnmscale do find_in_path $progname if [ $RETURN_found -eq 0 ] then missed_something=1 echo ERROR: Missing \"$progname\"! >&2 if [ $progname = djpeg -o $progname = cjpeg ] then echo ERROR: You may be able to find a copy of this at ftp://ftp.uu.net/graphics/jpeg >&2 fi if [ $progname = pnmscale ] then echo ERROR: You need to install the \"netpbm\" utilities. >&2 echo ERROR: You can find this at http://netpbm.sourceforge.net/ >&2 fi fi done if [ $missed_something -eq 1 ] then echo "" >&2 echo ERROR: You will find all the necessary utilities pre-installed >&2 echo ERROR: on most Linux systems. >&2 exit 1 fi } update_cleanups () { trap "rm -f $GLOBAL_cleanuplist; exit 1" 0 1 2 15 } add_cleanup () { GLOBAL_cleanuplist="$GLOBAL_cleanuplist $*" update_cleanups } remove_cleanup () { for i in $* do GLOBAL_cleanuplist=`echo $GLOBAL_cleanuplist | sed "s,${i},,"` done update_cleanups } # Only accepts *one* argument, which may contain space chars. make_file_readable () { if [ -f "$*" ] then chmod a+r "$*" chmod a-x "$*" fi } # Only accepts *one* argument, which may contain space chars. make_dir_transversible () { if [ -d "$*" ] then chmod a+x "$*" fi } # DEFAULT_ values are treated as const and are not normally referenced # by functions in this script. Functions should refer to the GLOBAL_ # version -- it will either be set to the DEFAULT_ or to the ARGV_ value # if the user overrides it on the cmd line. init_defaults () { TMPDIR=${TMPDIR-/tmp} DEFAULT_columns=3 DEFAULT_max_thumb_size=150 DEFAULT_max_thumb_width=$DEFAULT_max_thumb_size DEFAULT_max_thumb_height=$DEFAULT_max_thumb_size DEFAULT_use_two_windows=0 DEFAULT_reduce_big_pics=1 DEFAULT_reduce_trigger_height=768 DEFAULT_reduce_trigger_width=1024 DEFAULT_reduce_height=600 DEFAULT_reduce_width=800 DEFAULT_remove_originals=0 DEFAULT_create_slideshow=1 t=`pwd` DEFAULT_pwd_english=`basename "$t"` DEFAULT_rotation_file="rot-state.txt" DEFAULT_show_progress=1 DEFAULT_body_tag="" DEFAULT_meta_tag="" DEFAULT_html_charset="undef" DEFAULT_index_filename="index.html" DEFAULT_index_hrefname="index.html" DEFAULT_slideshow_previous_pre_link="

[" DEFAULT_slideshow_previous="previous" DEFAULT_slideshow_previous_post_link="]

" DEFAULT_slideshow_next_pre_link="

[" DEFAULT_slideshow_next="next" DEFAULT_slideshow_next_post_link="]

" DEFAULT_slideshow_ret_to_index_pre_link="

[" DEFAULT_slideshow_ret_to_index="index" DEFAULT_slideshow_ret_to_index_post_link="]

" GLOBAL_columns=$DEFAULT_columns GLOBAL_max_thumb_width=$DEFAULT_max_thumb_width GLOBAL_max_thumb_height=$DEFAULT_max_thumb_height GLOBAL_use_two_windows=$DEFAULT_use_two_windows GLOBAL_reduce_big_pics=$DEFAULT_reduce_big_pics GLOBAL_reduce_trigger_height=$DEFAULT_reduce_trigger_height GLOBAL_reduce_trigger_width=$DEFAULT_reduce_trigger_width GLOBAL_reduce_height=$DEFAULT_reduce_height GLOBAL_create_slideshow=$DEFAULT_create_slideshow GLOBAL_reduce_width=$DEFAULT_reduce_width GLOBAL_pwd_english="$DEFAULT_pwd_english" GLOBAL_rotation_file=$DEFAULT_rotation_file GLOBAL_show_progress=$DEFAULT_show_progress GLOBAL_remove_originals=$DEFAULT_remove_originals GLOBAL_body_tag=$DEFAULT_body_tag GLOBAL_meta_tag=$DEFAULT_meta_tag GLOBAL_html_charset=$DEFAULT_html_charset GLOBAL_index_filename=$DEFAULT_index_filename GLOBAL_index_hrefname=$DEFAULT_index_hrefname GLOBAL_slideshow_previous_pre_link=$DEFAULT_slideshow_previous_pre_link GLOBAL_slideshow_previous=$DEFAULT_slideshow_previous GLOBAL_slideshow_previous_post_link=$DEFAULT_slideshow_previous_post_link GLOBAL_slideshow_next_pre_link=$DEFAULT_slideshow_next_pre_link GLOBAL_slideshow_next=$DEFAULT_slideshow_next GLOBAL_slideshow_next_post_link=$DEFAULT_slideshow_next_post_link GLOBAL_slideshow_ret_to_index_pre_link=$DEFAULT_slideshow_ret_to_index_pre_link GLOBAL_slideshow_ret_to_index=$DEFAULT_slideshow_ret_to_index GLOBAL_slideshow_ret_to_index_post_link=$DEFAULT_slideshow_ret_to_index_post_link GLOBAL_stdout_has_been_redirected=0 } parse_args () { while [ $# -gt 0 ] do optname="`echo $1 | sed 's,=.*,,'`" optarg="`echo $1 | sed 's,^[^=]*=,,'`" case "$1" in --maxthumbsize=* | --max-thumb-size=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_max_thumb_size=$optarg GLOBAL_max_thumb_height=$ARGV_max_thumb_size GLOBAL_max_thumb_width=$ARGV_max_thumb_size ;; --maxthumbheight=* | --max-thumb-height=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_max_thumb_height=$optarg exit_if_empty GLOBAL_max_thumb_height=$ARGV_max_thumb_height ;; --maxthumbwidth=* | --max-thumb-width=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_max_thumb_width=$optarg GLOBAL_max_thumb_width=$ARGV_max_thumb_width ;; --columns=* | --cols=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_columns=$optarg GLOBAL_columns=$optarg ;; --use-two-windows*|--enable-two-windows|--with-two-windows) ARGV_use_two_windows=1 GLOBAL_use_two_windows=$ARGV_use_two_windows ;; --remove-orig*) ARGV_remove_originals=1 GLOBAL_remove_originals=$ARGV_remove_originals ;; --progress) ARGV_show_progress=1 GLOBAL_show_progress=$ARGV_show_progress ;; --quiet | -q) ARGV_show_progress=0 GLOBAL_show_progress=$ARGV_show_progress ;; --disable-reduce|--without-reduce) ARGV_reduce_big_pics=0 GLOBAL_reduce_big_pics=$ARGV_reduce_big_pics ;; --disable-slideshow|--without-slideshow) ARGV_create_slideshow=0 GLOBAL_create_slideshow=$ARGV_create_slideshow ;; --enable-reduce|--with-reduce) ARGV_reduce_big_pics=1 GLOBAL_reduce_big_pics=$ARGV_reduce_big_pics ;; --reduce-height=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_reduce_height=$optarg GLOBAL_reduce_height=$ARGV_reduce_height ;; --reduce-width=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_reduce_width=$optarg GLOBAL_reduce_width=$ARGV_reduce_width ;; --reduce-trigger-height=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_reduce_trigger_height=$optarg GLOBAL_reduce_trigger_height=$ARGV_reduce_trigger_height ;; --reduce-trigger-width=*) optarg=`echo $optarg | sed 's,[^0-9],,g'` exit_if_empty "$optname" "$optarg" ARGV_reduce_trigger_width=$optarg GLOBAL_reduce_trigger_width=$ARGV_reduce_trigger_width ;; -h | --help | -v | --version | -V) help exit 1 ;; -*) echo "`basename $0`: ERROR: Unrecognized option \"$1\"" >&2 ;; *) ARGV_image_list="$ARGV_image_list $1" ;; esac shift done } exit_if_empty () { desc="$1" shift val="$*" if [ -z "$val" ] then echo ERROR: No argument given with \"$desc\" command line argument! >&2 exit 1 fi } help () { # The following exec goop so I don't have to manually redirect every # message to stderr in this function. exec 4>&1 # save stdout fd to fd #4 exec 1>&2 # redirect stdout to stderr cat <<__EOM__ Usage: `basename $0` [--maxthumbsize=n] [--columns=n] [--show-filesize] [--use-two-windows] [--disable-reduce] [filenames...] --maxthumbsize Maximum size of thumbnails, in pixels, default $DEFAULT_max_thumb_size --columns Number of thumbnails per line, default $DEFAULT_columns --disable-reduce Don't create a reduced img if the picture is large --disable-slideshow Don't create slideshow files --use-two-windows Bring up a new window to see images, default is `[ $DEFAULT_use_two_windows -eq 1 ] && echo enabled || echo disabled` __EOM__ if [ $DEFAULT_show_progress -eq 1 ] then echo '--quiet Avoid unnecessary output while running' else echo '--progress Show progress updates as the script runs' fi cat <<__EOM__ --remove-originals Remove original images if we make reduced versions. Useful when disk space is limited; default is `[ $DEFAULT_remove_originals -eq 1 ] && echo enabled || echo disabled` WARNING!!! This option *will* remove your original images if reduced images are available! Run this script in a directory of JPEG files to create thumbnail images and an ${GLOBAL_index_filename}. If an ${GLOBAL_index_filename} is already present, its HTML is sent to stdout. You may list the JPEG files on the command line if you prefer. By default, `basename $0` works on every JPEG file in the current working directory. You can permanently override options by creating a ~/.makethumbsrc file. You need to use the program's internal variable names here. For instance, if you always want --use-two-windows mode, the internal name is $GLOBAL_use_two_windows so you'd need to set use_two_windows=1 in your .makethumbsrc. Most usefully, you can specify what kind of tag you want used in your web page with a setting like body_tag= Determining the variable names for the .makethumbsrc is not very user friendly, but I suspect this feature will not be widely used. If I'm wrong, I'll see about making it a little more user friendly. This script written by Jason Molenda, makethumbs(AT)molenda.com. This is version '$Revision: 1.76 $'. The latest version of this script is always available at http://www.molenda.com/makethumbs/ __EOM__ exec 1>&4 # Copy stdout fd back from temporary save fd, #4 } # Sets file at $GLOBAL_image_list_tmpfile with images to process. # Tries to normalize filename extensions. get_image_list () { make_tmpfile first-image-list prelim_image_tmpfile="$RETURN_tmpfile" make_tmpfile image-list GLOBAL_image_list_tmpfile="$RETURN_tmpfile" add_cleanup "$prelim_image_tmpfile" "$GLOBAL_image_list_tmpfile" if [ -n "$ARGV_image_list" ] then for i in $ARGV_image_list do echo $ARGV_image_list >> $prelim_image_tmpfile done else ls -1 | egrep -i '\.jpg$|\.jpeg$|\.png$|\.gif$' > $prelim_image_tmpfile fi cat $prelim_image_tmpfile | while read filename do [ -s "$filename" ] || continue if echo "$filename" | egrep -- '-t\.|-r\.' >/dev/null 2>&1 then continue fi normalized_filename=`echo "$filename" | sed -e 's,.JPG$,.jpg,' -e 's,.jpeg$,.jpg,' -e 's,.JPEG$,.jpg,' \ -e 's,.PNG$,.png,' -e 's,.GIF$,.gif,'` if [ ! -f "$normalized_filename" -a -n "$normalized_filename" ] then if mv "$filename" "$normalized_filename" then filename="$normalized_filename" fi fi #FIXME: This will probably fail on a MacOS HFS+ type system where testing # for "foo.JPG" and "foo.jpg" may both return true if one of the names # is present... Maybe check inode #s or something to be sure. if [ "$filename" != "$normalized_filename" -a \ -f "$filename" -a -f "$normalized_filename" ] then echo "WARNING: Both $filename and $normalized_filename exist, and this script does" 1>&2 echo " odd things when this happens!" 1>&2 fi make_file_readable "$filename" echo "$filename" >> $GLOBAL_image_list_tmpfile done if [ ! -s "$GLOBAL_image_list_tmpfile" ] then echo ERROR: No images found! >&2 exit 1 fi } point_stdout_to_index_file () { if [ -n "$ARGV_image_list" ] then GLOBAL_print_html_header_footer=0 else GLOBAL_print_html_header_footer=1 if [ -f "$GLOBAL_index_filename" ] then remove_file_if_unmodified "$GLOBAL_index_filename" fi if [ ! -f "$GLOBAL_index_filename" ] then exec > "$GLOBAL_index_filename" GLOBAL_stdout_has_been_redirected=1 else GLOBAL_stdout_has_been_redirected=0 fi make_file_readable "$GLOBAL_index_filename" fi } iterate_over_image_list () { col=0 first_row=1 progress_update_start_creating_reduced_images while read fn do if [ $col -eq 0 ] then print_html_row_start fi print_html_column_start print_image_entry "$fn" print_html_column_end col=`expr $col + 1` if [ $col -eq $GLOBAL_columns ] then print_html_row_end col=0 fi done < $GLOBAL_image_list_tmpfile progress_update_done_creating_reduced_images } print_image_entry () { pie_fn="$*" create_thumbnail "$pie_fn" thumb_width=$RETURN_thumb_width thumb_height=$RETURN_thumb_height create_reduced "$pie_fn" was_reduced=$RETURN_was_reduced print_image_link $thumb_width $thumb_height $was_reduced "$pie_fn" print_image_size "$pie_fn" } # Only reduce JPG images. Reducing GIF/PNG images is a little tricky - # see the RCS comment on rev 1.60 for more details on why this is tricky. maybe_remove_original () { mro_name="$1" RETURN_original_removed=0 RETURN_reduced_still_around=1 [ "$GLOBAL_remove_originals" -eq 0 ] && return if echo "$mro_name" | grep -v '\.jpg$' > /dev/null 2>&1 then return fi source_name_to_reduced_name "$mro_name" mro_reduced_name="$RETURN_reduced_name" if [ -f "$mro_name" -a -f "$mro_reduced_name" -a -s "$mro_reduced_name" ] then mv "$mro_name" "${mro_name}.bak" mv "$mro_reduced_name" "$mro_name" if [ ! -f "$mro_reduced_name" -a -f "$mro_name" -a -s "$mro_name" ] then rm -f "${mro_name}.bak" RETURN_original_removed=1 RETURN_reduced_still_around=0 else echo ERROR: I got confused while trying to remove original file >&2 echo ERROR: \"$mro_name\"! Aborting... >&2 exit 1 fi fi } get_file_english_name () { gfen_fn="$*" RETURN_english_name=`echo "$gfen_fn" | sed -e 's,-[tr]\.[a-z][a-z][a-z]$,,' \ -e 's,\.[a-z][a-z][a-z]$,,' \ -e 's,-, ,g' -e 's,_, ,g'` } # A little complicated - parameters are # THUMBNAIL-WIDTH THUMBNAIL-HEIGHT REDUCED-IMG-MADE? FILENAME # $thumb_width $thumb_height $was_reduced "$fn" print_image_link () { twidth=$1; shift theight=$1; shift reduced=$1; shift pil_fn="$*" get_file_english_name "$pil_fn" pil_english_name="$RETURN_english_name" source_name_to_thumb_name "$pil_fn" pil_thumb_name="$RETURN_thumb_name" if [ $GLOBAL_create_slideshow -eq 1 ] then image_name_to_html_name "$pil_fn" pil_slideshow_name="$RETURN_html_name" source_name_to_reduced_html_name "$pil_fn" pil_reduced_slideshow_name="$RETURN_reduced_html_name" fi pil_image_name="$pil_fn" source_name_to_reduced_name "$pil_image_name" pil_reduced_name="$RETURN_reduced_name" if [ $reduced -eq 1 ] then if [ $GLOBAL_create_slideshow -eq 1 ] then main_link_href="$pil_reduced_slideshow_name" secondary_link_href="$pil_slideshow_name" else main_link_href="$pil_reduced_name" secondary_link_href="$pil_image_name" fi else if [ $GLOBAL_create_slideshow -eq 1 ] then main_link_href="$pil_slideshow_name" else main_link_href="$pil_image_name" fi fi echo -n ' " echo -n "
$pil_english_name
" if [ $reduced -eq 1 ] then echo -n "
[Large version]" fi } print_image_size () { bytes=`ls -l "$fn" | awk '{print $5}'` kbytes=`expr $bytes / 1000` echo " (${kbytes}k)" } foo_to_pnm () { case "$*" in *.jpg) RETURN_foo_to_pnm="djpeg -ppm";; *.gif) RETURN_foo_to_pnm="giftopnm";; *.tif) RETURN_foo_to_pnm="tifftopnm";; *.png) RETURN_foo_to_pnm="pngtopnm";; *) echo ERROR: Unable to proceed with file "$*"! >&2 exit 1;; esac } source_name_to_thumb_name () { RETURN_thumb_name=`echo "$*" | sed 's,\.[^.]*$,-t.jpg,'` if [ "$*" = "$RETURN_thumb_name" ] then echo ERROR: I couldn\'t create a thumb name for "\"$*\""! >&2 exit 1 fi if echo "$RETURN_thumb_name" | egrep -- '-t-t\.' >/dev/null 2>&1 then echo ERROR: I couldn\'t create a reduced name for "\"$*\""! >&2 exit 1 fi } source_name_to_reduced_name () { RETURN_reduced_name=`echo "$*" | sed 's,\.[^.]*$,-r.jpg,'` if [ "$*" = "$RETURN_reduced_name" ] then echo ERROR: I couldn\'t create a reduced name for "\"$*\""! >&2 exit 1 fi if echo "$RETURN_reduced_name" | egrep -- '-r-r\.' >/dev/null 2>&1 then echo ERROR: I couldn\'t create a reduced name for "\"$*\""! >&2 exit 1 fi } source_name_to_source_html_name () { RETURN_source_html_name=`echo "$*" | sed 's,\.[^.]*$,.html,'` if [ "$*" = "$RETURN_source_html_name" ] then echo ERROR: I couldn\'t create an HTML name for "\"$*\""! >&2 exit 1 fi } image_name_to_html_name () { RETURN_html_name=`echo "$*" | sed 's,\.[^.]*$,.html,'` if [ "$*" = "$RETURN_html_name" ] then echo ERROR: I couldn\'t create an HTML name for "\"$*\""! >&2 exit 1 fi } # We may have a reduced image, or we may not. If the reduced image # is present, we return the source image HTML name (so # sourc_name_to_source_html_name is equivalent to this function), # otherwise we return the reduced image HTML name. source_name_to_reduced_html_name () { source_name_to_reduced_name "$*" sntrhn_reduced_name="$RETURN_reduced_name" if [ ! -f "$sntrhn_reduced_name" ] then source_name_to_source_html_name "$*" RETURN_reduced_html_name="$RETURN_source_html_name" else RETURN_reduced_html_name=`echo "$sntrhn_reduced_name" | sed 's,\.[^.]*$,.html,'` fi if [ "$sntrhn_reduced_name" = "$RETURN_reduced_html_name" ] then echo ERROR: I couldn\'t create an HTML name for "\"$sntrhn_reduced_name\""! >&2 exit 1 fi } reduced_name_to_source_name () { rntsn_nameroot=`echo "$*" | sed 's,-r\.jpg$,,'` rntsn_source=`grep "^${rntsn_nameroot}\.[a-z][a-z][a-z]\$" $GLOBAL_image_list_tmpfile` if [ -z "$rntsn_source" -o ! -f "$rntsn_source" ] then echo ERROR: I couldn\'t find the source name from \"$*\"! >&2 exit 1 fi if [ "$*" = "$rntsn_source" ] then echo ERROR: I couldn\'t find convert source name with \"$*\"! >&2 exit 1 fi RETURN_source_name="$rntsn_source" } create_thumbnail () { source_fn="$*" source_name_to_thumb_name "$source_fn" thumb_fn="$RETURN_thumb_name" if [ -f "$thumb_fn" -a -s "$thumb_fn" ] then make_file_readable "$thumb_fn" get_dimensions "$thumb_fn" RETURN_thumb_width=$RETURN_width RETURN_thumb_height=$RETURN_height return fi foo_to_pnm "$source_fn" conv="$RETURN_foo_to_pnm" add_cleanup "$thumb_fn" check_for_rotation "$source_fn" $conv "$source_fn" | pnmscale -xysize $GLOBAL_max_thumb_width \ $GLOBAL_max_thumb_height 2>/dev/null | $RETURN_rotation_cmd $RETURN_rotation_angle | cjpeg -optimize > "$thumb_fn" make_file_readable "$thumb_fn" remove_cleanup "$thumb_fn" if [ ! -s "$thumb_fn" ] then echo "ERROR: Unable to create thumbnail for \"$source_fn\"! Exiting." >&2 exit 1 fi get_dimensions "$thumb_fn" RETURN_thumb_width=$RETURN_width RETURN_thumb_height=$RETURN_height } create_reduced () { cr_source_fn="$*" source_name_to_reduced_name "$cr_source_fn" cr_reduced_fn="$RETURN_reduced_name" RETURN_was_reduced=0 if [ $GLOBAL_reduce_big_pics -eq 0 ] then return fi if [ -f "$cr_reduced_fn" -a -s "$cr_reduced_fn" ] then make_file_readable "$cr_reduced_fn" maybe_remove_original "$cr_source_fn" [ $RETURN_reduced_still_around -eq 1 ] && RETURN_was_reduced=1 return fi get_dimensions "$cr_source_fn" if [ $RETURN_width -ge $GLOBAL_reduce_trigger_width -o \ $RETURN_height -ge $GLOBAL_reduce_trigger_height ] then foo_to_pnm "$cr_source_fn" start_conv="$RETURN_foo_to_pnm" add_cleanup "$cr_reduced_fn" check_for_rotation "$cr_source_fn" $start_conv "$cr_source_fn" | pnmscale -xysize $GLOBAL_reduce_width \ $GLOBAL_reduce_height 2>/dev/null | $RETURN_rotation_cmd $RETURN_rotation_angle | cjpeg -optimize > "$cr_reduced_fn" make_file_readable "$cr_reduced_fn" remove_cleanup "$cr_reduced_fn" progress_update_created_reduced_image maybe_remove_original "$cr_source_fn" [ $RETURN_reduced_still_around -eq 1 ] && RETURN_was_reduced=1 fi if [ $RETURN_was_reduced -eq 1 -a ! -s "$cr_reduced_fn" ] then echo "ERROR: Unable to create reduced img of \"$source_fn\"! Exiting." >&2 exit 1 fi } get_dimensions () { gd_fn="$*" foo_to_pnm "$gd_fn" conv="$RETURN_foo_to_pnm" dimens=`$conv "$gd_fn" | pnmfile | awk '{print $4 " " $6}'` RETURN_width=`echo $dimens | cut -d ' ' -f1` RETURN_height=`echo $dimens | cut -d ' ' -f2` if [ -z "$RETURN_width" -o -z "$RETURN_height" ] then echo ERROR: Internal error in get_dimensions, for some reason I couldn\'t get >&2 echo ERROR: the dimensions of \"$gd_fn\". Exiting. >&2 exit 1 fi } check_for_rotation () { cfr_fn="$*" RETURN_do_rotation=0 RETURN_rotation_cmd=cat RETURN_rotation_angle="" [ ! -f $GLOBAL_rotation_file ] && return if grep -i "^[a-z-]* ${fn}" $GLOBAL_rotation_file >/dev/null 2>&1 then : else return fi rot_cmd=`grep -i "^[a-z-]* ${fn}" $GLOBAL_rotation_file | sed "s, ${fn},,"` if [ -z "$rot_cmd" ] then echo WARNING: Unrecognized rotation for "$cfr_fn"! Ignoring rotate. >&2 return fi if [ "$rot_cmd" != "clockwise" -a "$rot_cmd" != "counter-clockwise" ] then echo WARNING: Unrecognized rotation for "$cfr_fn"! Ignoring rotate. >&2 return fi RETURN_do_rotation=1 RETURN_rotation_cmd=pnmrotate if [ "$rot_cmd" = "clockwise" ] then RETURN_rotation_angle=-90 else RETURN_rotation_angle=90 fi } print_html_header () { echo '' echo '' echo '' echo " $GLOBAL_pwd_english" [ -n "$GLOBAL_meta_tag" ] && echo "$GLOBAL_meta_tag" if [ "$GLOBAL_html_charset" != "undef" ] then echo " " fi echo ' ' echo '' echo "$GLOBAL_body_tag" echo "" echo "

$GLOBAL_pwd_english

" echo "" } print_html_table_start () { echo '' } print_html_table_end () { echo "" echo '
' } print_html_row_start () { echo "" echo '' } print_html_row_end () { echo '' echo "" # echo " " } print_html_column_start () { echo ' ' } print_html_column_end () { echo ' ' } print_html_footer () { echo "" echo "

This page created on `date '+%Y-%m-%d'`." cat << __EOM__ __EOM__ echo '' echo '' add_checksum_to_file "$GLOBAL_index_filename" } ######################################################### #### progress printing functions ######################################################### progress_update_start_creating_index_html () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return echo "Start creating $GLOBAL_index_filename" >&2 } progress_update_done_creating_index_html () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return echo "Done creating $GLOBAL_index_filename" >&2 } progress_update_start_creating_reduced_images () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return echo -n " Making reduced images " >&2 GLOBAL_did_we_reduce_anything=0 } progress_update_created_reduced_image () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return echo -n "." >&2 GLOBAL_did_we_reduce_anything=1 } progress_update_done_creating_reduced_images () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return if [ $GLOBAL_did_we_reduce_anything -eq 0 ] then echo "(none regenerated)" >&2 else echo " done." >&2 fi } progress_update_start_creating_slideshow_pages () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return echo -n "Making slideshow pages " >&2 } progress_update_created_slideshow_page () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return echo -n "." >&2 } progress_update_done_creating_slideshow_pages () { [ $GLOBAL_stdout_has_been_redirected -eq 0 ] && return [ $GLOBAL_show_progress -eq 0 ] && return echo " done." >&2 } ######################################################### #### checksum checking functions, unmodified file removal ######################################################### remove_file_if_unmodified () { fiu_filename="$*" verify_file_checksum "$fiu_filename" if [ $RETURN_cksum_ok -eq 1 ] then rm -f "$fiu_filename" return fi } # $RETURN_cksum_ok is '1' if a valid checksum is found. If no checksum # is found, or the checksum doesn't match, a '0' is returned (i.e. the # user may have modified this file for all we know). verify_file_checksum () { vfc_filename="$*" RETURN_cksum_ok=0 if grep ',\1,'` vfc_recorded_sum=`grep ',\2,'` find_in_path "$vfc_sum_program" if [ $RETURN_found -eq 0 ] then return fi vfc_sum_output=`grep -v '" >> "$actf_filename" } normalize_checksum_string () { istr="$*" istr=`echo "$istr" | sed -e 's,^[ ]*,,' -e 's,[ ]*$,,' \ -e 's,[ ][ ]*, ,'` RETURN_str_norm="$istr" } ######################################################### #### call to main ######################################################### main $*