#!/bin/bash

# 'makealbum.sh'
#
# A script to prepare an HTML "album" of digital pictures.
#
# Arguments: Name or wildcard of the file(s) to convert.
#
# - allows selection of final size (1024x768, 800x600, etc)
# - allows selection of image rotation (left, right, keep, auto)
# - collection of input data is separated from actual image
#   conversion, so we do not need to wait in between :-)
# - all files are transformed on-the-fly
# - the original files are NEVER changed
# - generates HTML code for "index.html"
# - generates thumbnails automagically
# - generates HTML code for thumbnails linked to final images
# - allows selection of thumbnail size (150x110, 220x160)
# - can extract EXIF data and show them in tabular form
#   (requires 'metacam' - FIXME: now 'convert' can do this, too)
# - can add dropshadow on-the-fly (requires separate script)
# - can add code for JavaScript slideshow
# - can auto-rotate images
#
# -----------------------------------------------------------------
# History:
#   2001-10-21, JHa (first "draft", written in tcsh).
#   2001-12-16, JHa (now writes to parent directory)
#   2002-02-04, JHa (major rewrite for bash, now separated the
#		collection of input data from transformation step)
#   2002-02-05, JHa (misc refinements)
#   2002-02-07, JHa (added automatic username/date)
#   2002-02-22, JHa (removed automatic username again, 'convert'
# 		option to remove EXIF header from converted jpg
#		files; various improvements from Marco Nierlich)
#   2002-02-23, MNi (added the EXIF support for html output)
#   2002-02-25, JHa (handling of error conditions, streamlined
#		HTML output of EXIF data, bigger thumbnails in
#		better quality, misc. improvements)
#   2002-02-26, JHa (HTML refinements, as suggested by MNi)
#   2002-08-24, JHa (option to invoke drop-shadow)
#   2002-08-31, JHa (option to select thumbnail size)
#   2002-10-20, JHa (binary selection using "which"; added size)
#   2003-01-08, JHa (option: automatic mode)
#   2003-01-24, JHa (added a few functions to streamline the script)
#   2003-02-20, JHa (now OK for files with space in their name)
#   2003-03-14, JHa (added optional gamma correction)
#   2003-05-12, JHa (added "ignore" key to image selection)
#   2003-07-21, JHa (backup of existing "index.html" files)
#   2003-11-09, JHa (added javascript stuff for slide show;
#               this required adding the "name" tag to images)
#   2003-11-11, JHa (improved slide show; new directory structure)
#   2003-12-06, JHa (slide show now supports titles - Merci Florian!)
#   2003-12-08, JHa (slide show titles now below image)
#   2003-12-11, JHa (changed image "id" against "name")
#   2003-12-15, JHa (typo fixed)
#   2004-07-28, JHa (missing <p></p> around slideshow anchor;
#                   'mailto' is now encoded)
#   2004-08-04, JHa (typo in header fixed)
#   2005-03-08, JHa (thumbnail size now dermined from file itself,
#                   license now explicitly GPL *v2*)
#   2005-03-18, Jha (fix in checkfor() error message)
#   2005-11-30, JHa (allows any target directory)
#   2005-12-18, JHa (bugfix in same function)
#   2006-01-10, JHa (fixed typo in scaling of thumbnails)
#   2006-10-17, JHa (added option for filename suffix)
#   2007-01-02, JHa (added check for perl modules)
#   2008-01-23, JHa (added interactive query for suffix)
#   2008-01-24, JHa (refined some functions)
#   2008-01-25, JHa (removed gamma stuff by generic option)
#   2009-01-08, JHa (resolved resizing of img in portrait format)
#   2009-08-09, JHa (minor bugfix in HTML code)
# -----------------------------------------------------------------
# Copyright (c) 2001...2009 Joerg Hau <joerg.hau(at)dplanet.ch>.
#
# Many useful details and EXIF support option (tabular output)
# contributed by Marco Nierlich <mani(at)access.unizh.ch>
#
# Slideshow based on a script from http://www.ricocheting.com/,
# improved to support titles etc. by Florian Grosse-Coosmann.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# -----------------------------------------------------------------

# The defaults:
# - we are in the subdirectory with the original images (e.g. ~/images/party/orig),
# - thumbnails will be in a "parallel" directory (e.g. ~/images/party/img)
# - HTML code in the parent directory (e.g. ~/images/party/index.html)
#
BDIR="../"				    # target base directory
IDIR="img"		            # directory for thumbnails

# Some personal data (for copyright notice etc.)
#
NAME="Joerg Hau"			        # who?
EMAIL="joerg.hau&#64;dplanet.ch" 	# guess what ;-)

# seconds to wait between images. Increase this if you display
# very large files (e.g. 8-MB PNG files ;-)
#
DELAY="3"

# --- (usually) no user adjustable parts below this line :-)
#

# -----------------------------------------------------------------
# subroutine to check for some required executables
# argument: (list of) programs to test for
# will exit with rc=1 if any program was not found
# !!! Not every version of 'which' behaves the same !!!
# -----------------------------------------------------------------
function checkfor()
{
for i in $*; do
    PROG=`which $i`
    if [ -x "$PROG" ] ; then
        shift
    else
        echo "'$i' command not found, exiting."
        exit 1
    fi
done
}


# ------------------------------------------------------------
# subroutine to check error status of last command
# argument: error message
# will exit with rc=1 if errorlevel was not 0
# ------------------------------------------------------------
function errcheck()
{
if [ $? != 0 ]; then
    echo "Abort: $1, status: $?"
    exit 1
fi
}


# ------------------------------------------------------------
# verify if a directory is existing and accessible
# - if it does not exist, create ist.
# - arguments: $1 = path/to/directory
# ------------------------------------------------------------
function checkdir()
{
if [ ! -d $1 ] ; then
    mkdir -p $1 || errcheck "Error during creation of '$1'"
fi
if [ ! -r $1 ] ; then
    errcheck "'$1' exists but is not readable"
fi
}


#
# === main script starts here ===
#

# if script is called w/o arguments, give usage instructions
#
if (( $# < 1 )) ;  then
    echo "Syntax: ${0##*/} file1 file2 ... "
    exit 1
fi

# check for some mandatory stuff.
# This script relies heavily on ImageMagick!
# For the "auto-rotate" stuff, at least IM 6 is required.
#
checkfor sed awk convert identify echo mkdir date

# file to store some intermediate information
#
tmpfile="/tmp/$$.tmp"

# action for Ctrl-C and other nasty things ...
#
trap 'if [ -x ${tmpfile} ] ; then rm ${tmpfile}; fi; exit 1' 0 1 2 3 15

# obtain year from actual date
#
YEAR=`date +%Y`

#
# --- ask for target directory ---
#
TMP=$BDIR
read -p "Base directory for images (no spaces, default is '$BDIR') ?  " BDIR
if [ -z $BDIR ] ; then       # empty string?
    BDIR=$TMP                # copy back
fi

# see if directory exists; if not, create it without asking
#
checkdir ${BDIR}/${IDIR}

# compose name of HTML file
#
IDX="${BDIR}/index.html"

#
# --- ask for final image size ---
#
read -n 1 -p "Desired size of final image: [6]40x480, [8]00x600, [1]024x768, or [*]original?  " key
echo ""		# linefeed makes it look better
case "$key" in
    8)
	    SCALE="-scale 800x800"     # IM will take the longest size, so this fits portrait AND landscape
        ;;
    6)
      	SCALE="-scale 640x640"
        ;;
    1)
      	SCALE="-scale 1024x1024"
        ;;
    *)
    	SCALE=""
        ;;
esac


#
# --- ask for thumbnail image size ---
#
read -n 1 -p "Desired size of thumbnails: 13[3]x100, [1]50x110 (default), or [2]20x160?  " key
echo ""		# linefeed makes it look better
case "$key" in
    1)
    	THUMBSCALE="-scale 150x150"
        ;;
    2)
      	THUMBSCALE="-scale 220x220"
        ;;
    3)
        THUMBSCALE="-scale 133x133"
        ;;
    *)
    	THUMBSCALE="-scale 150x150"
        ;;
esac


#
# --- ask for EXIF information ---
#
read -n 1 -p "Create table with EXIF information? [y|*]  " key
echo ""			# linefeed
case "$key" in
	y | Y)
		EXIF=`which metacam`
		# we'll use 'metacam' to extract the information
                ;;
	*)
        	EXIF=""
                ;;
esac

# see if the executable exists
# NOTE: metacam can now be replaced by ImageMagick's 'identify -format "%[EXIF:*]"'
#
if [ ! -x $EXIF ] ; then
	echo "${EXIF} not found ... no EXIF extraction possible."
	exit 1
fi
#
# --- end of EXIF input ---
#

#
# --- ask for drop shadow effect ---
#
# requires the Gimp-Perl script published by Erich Lerch
# at http://lerchs.org/gimp_en.html
#
read -n 1 -p "Add DropShadow to images? [y|*]  " key
echo ""			# linefeed
case "$key" in
	y | Y)
		DROPSHADOW=`which dropshadow.pl`
           ;;
	*)
       	DROPSHADOW=""
           ;;
esac

# see if the executable exists
#
  if [ ! -x $DROPSHADOW ] ; then
      echo "${DROPSHADOW} not found ... no DropShadow will be added :-( ."
      exit 1
  fi

if [ $DROPSHADOW ] ; then
  # check for required perl module, exit if missing
  #
  PERLMOD="Gimp"
  for i in $PERLMOD; do
    perl -e "use $i" 2>/dev/null || errcheck "Required perl module '$i' was not found."
  done
fi

#
# --- end of DROPSHADOW input ---
#

#
# --- ask for slideshow ---
#
read -n 1 -p "Create code for slide show (uses JavaScript) [y|*]  " key
echo ""			# linefeed
case "$key" in
	y | Y)
        SLIDESHOW="1"
                ;;
	*)
        SLIDESHOW=""
                ;;
esac
#
# --- end of slideshow input ---
#


#
# --- ask for image suffix ---
# this is useful if you want to mix images from various authors
# e.g. 20061112T111213-jha.jpg instead of 20061112T111213.jpg
#
read -p "Suffix for image filenames (e.g. \"-jha\", default is none)?  " SUFFIX

#
# --- ask other options for 'convert' ---
#
read -p "Enter other processing options (e.g. '-gamma 1.3/1.3/1.3'), or press ENTER to continue: " OPTION

#
# --- ask for orientation/confirmation ---
#
# if the camera does not provide rotation information,
# all images will simply be processed in landscape format.
#
read -n 1 -p "Use [a]utomatic orientation of images, or [c]onfirm (default) each image manually? " key
echo ""			# linefeed
case "$key" in
	a | A)
		AUTOROTATE="-auto-orient"
        CONFIRM=""
                ;;
	c | C | *)
		AUTOROTATE=""
        CONFIRM="TRUE"
                ;;
esac
#
# --- end of orientation/confirmation ---
#


# Now run a loop for each file on the cmd line.
# In this first loop we only _collect_ information about the rotation of the image,
# while the actual rotation is performed afterwards (so that we do not need to sit
# and wait for these processes to finish).
#
nr=0		# counter
flist=""	# list of files
rlist=""	# list of rotation instructions

for i in "$@"; do
    #
    # show image and ask for rotation (if AUTO is set, skip this).
    #
    # Popping up another X window removes focus from the active view,
    # so we simply leave the image for a few seconds and then kill
    # the display process, with all error msg going to /dev/null ;-)
    #
    if [ $CONFIRM ] ; then
      display -size 300x200 $i 2> /dev/null &
      sleep $DELAY

      # modified by MNi so that the process gets killed with any PID:
      kill `ps -a | grep display | sed 's/ *//' | awk '{print $1}'`

      read -p ${i}": [I]gnore, rotate [r]ight, [l]eft, [a]uto, or keep[*] ?  " key
      case "$key" in
	    i | I)
		        ROTATE="ignore"
                echo ""		# linefeed
                ;;
	    r | R)
		        ROTATE="-rotate 90"
                echo ""		# linefeed
                ;;
        l | L)
 		        ROTATE="-rotate -90"
                echo ""		# linefeed
		        ;;
	    a | A)
		        ROTATE=${AUTOROTATE}
                echo ""		# linefeed
                ;;
	    *)
                ROTATE=""
                ;;		# usually ENTER -> no linefeed required :-)
      esac
    else        # if manual confirmation was NOT desired
            ROTATE=${AUTOROTATE}
    fi				# end of AUTO loop

    flist[$nr]=$i		    # store filename in list
    rlist[$nr]=$ROTATE		# store rotation info in list

    # to exclude the images where "ignore" was selected,
    # simply increase counter only if "ignore" is NOT set
    # (if set, the counter will be re-used ;-)
    if [ "$ROTATE" != "ignore" ] ; then
        nr=`expr ${nr} + 1`
    fi
done

read -p "Title for this page?  " TITLE
read -p "Keywords (comma separated)?  " KEYWORDS

#
# --- interaction is finished, the rest is done automagically ...
#

# avoid overwriting of existing index file by creating backup
# (attach a date-time stamp to the filename)
#
if [ -e ${IDX} ] ; then
    BKUP=${IDX}.`date +%Y%m%dT%H%M%S`
    echo -n "Preserving existing file ${IDX} as ${BKUP} ... "
    cp ${IDX} ${BKUP} || errcheck "Error during creation of ${BKUP}."
    echo "done."
fi


# Write header of HTML page
#
cat <<EOF > ${IDX} || errcheck "Error during creation of ${IDX}."
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
EOF

echo "<title>"$TITLE"</title>" >> ${IDX}
echo "<!-- Copyright (C) "$YEAR" by "$NAME". All rights reserved. -->" >> ${IDX}

cat <<EOF >> ${IDX}
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15">
<meta name="generator" content="makealbum.sh by joerg.hau(at)dplanet.ch">
EOF

echo "<meta name=\"keywords\" content=\"$KEYWORDS\">" >>${IDX}
echo "<meta name=\"Author\" content=\"$NAME\">" >>${IDX}

# note that CSS is not always interpreted well by Netscape 4.x
#
cat <<EOF >> ${IDX}
<style type="text/css">
body	{
	font-family: verdana,helvetica,arial;
	font-style:normal;
	color:#000000;
	background-color:#ffffff;
	margin: 1em;
	}
h1 	{
	color: #ffffff;
	background-color: #408080;
	font-weight:bold;
	margin-top:1.1em;
	padding: 0.2em;
	}
h2	{
	color: #888888;
	border-bottom: solid 1px #888888;
	}
table	{
	background-color: #ffffff;
	color: #000000;
	vertical-align: top;
	font-family: verdana,helvetica,arial;
	font-style:normal;
        font-size:small;
	}
.tablehdr {
	background-color: #efefef;
	color: #555555;
	font-weight:bold;
	}
.tablehi {
        text-align:right;
        }
.tablehi2 {
        text-align:right;
        color:#888888;
        }
.low    {
        color:#888888;
        }
.footer	{
	font-size: small;
        text-align: right;
        border-top: solid 1px #111111;
        }
.img	{
	vertical-align:middle;
        }
</style>
EOF


# if requested, add code for slideshow
#
if [ $SLIDESHOW ] ; then
cat <<EOF >> ${IDX}
<script type="text/javascript">
<!-- slideshow from http://www.ricocheting.com/js/, improved by Florian Grosse-Coosmann  -->
<!-- this is Free Software; you can redistribute it and/or modify it under the terms of the GNU General Public License -->
<!-- start
  var x = 0;
  var pictureTitle = null;

  function rotate(num) {
    var opt, star, name, file, h;
    x = num % document.slideForm.slide.length;
    if ( x < 0) {
      x = document.slideForm.slide.length-1
    };
    opt = document.slideForm.slide.options[x].value;
    star = opt.lastIndexOf("*");
    name = opt.substr(0, star);
    file = opt.substr(star + 1);
    document.images.show.src = file;
    /* finally assign 'name' as the title somewhere */
    document.slideForm.slide.selectedIndex = x;
    if (pictureTitle == null) {
      pictureTitle = document.getElementById("picturetitle");
    }
    if (pictureTitle != null) {
      pictureTitle.innerHTML = name;
      /* alternatively use
       * pictureTitle.firstChild.data = name;
       */
    }
  }
  function apRotate() {
    if (document.slideForm.slidebutton.value == "Stop") {
      rotate(++x);
      window.setTimeout("apRotate()", 5000);
    }
  }
//end -->
</script>
EOF
fi
# -- end of slideshow javascript stuff ---

echo "</head>" >> ${IDX}
echo "<body>" >> ${IDX}
echo "<h1>"$TITLE"</h1>" >> ${IDX}

# provide link to slideshow-part
#
if [ $SLIDESHOW ] ; then
echo '<p><a href="#slideshow">Slideshow</a></p>' >> ${IDX}
fi

# if EXIF info is desired, write a table
# else just write a <p>
#
if [ $EXIF ] ; then
    echo '<center><table border="0" cellspacing="0" cellpadding="1" summary="Images and EXIF data">' >> ${IDX}
else
    echo "<p>" >> ${IDX}
fi

if [ $SLIDESHOW ] ; then
echo "<!-- You can replace the 'name' tag by a more descriptive title. -->" >> ${IDX}
fi

# now loop through all the files
#
for (( cnt=0 ; cnt<$nr ; cnt++ )); do
    # remove filename path _and_ extension
    #
    fn=`basename ${flist[$cnt]%.*}`${SUFFIX}
    # echo "["$cnt"]" ${flist[$cnt]} "("${rlist[$cnt]}")" '$fn' "-->" ${BDIR}/${IDIR}/${fn}

    # By default the original files from a digital camera are all landscape:
    # image orientation does not matter as long as geometry is specified
    # BEFORE rotation.
    # 'convert +profile APP1' removes the EXIF header (15 kB!)
    #
    echo -n "Converting ${flist[$cnt]} .."
    convert $SCALE ${rlist[$cnt]} +profile APP1 $OPTION -comment "(C) $NAME" \
    -quality 85 ${flist[$cnt]} ${BDIR}/${IDIR}/${fn}.jpg || errcheck "Error during image creation of ${flist[$cnt]}."

    if [ $DROPSHADOW ] ; then
    	$DROPSHADOW ${BDIR}/${IDIR}/${fn}.jpg && mv output.jpg ${BDIR}/${IDIR}/${fn}.jpg
    fi

    echo -n "."		# show progress ...
    convert $THUMBSCALE ${rlist[$cnt]} +profile APP1 $OPTION -comment "(C) $NAME"\
	-quality 60 ${flist[$cnt]} ${BDIR}/${IDIR}/${fn}_i.jpg || errcheck "Error during thumbnail creation of ${flist[$cnt]}."
    #
    # The geometry of the thumbnail is not always exact, so
    # obtain it directly and write the HTML code to file:
    #
    size1=`identify -format "%w" ${BDIR}/${IDIR}/${fn}_i.jpg`
    size2=`identify -format "%h" ${BDIR}/${IDIR}/${fn}_i.jpg`

    if [ $EXIF ] ; then
    	$EXIF ${flist[$cnt]} > ${tmpfile}	# Now we have output in a table
        echo '<tr><td rowspan="7" class=img>' >> ${IDX}
    fi

    # output image thumbnail and link (always the same scheme)
    # we will use the "alt" tag to access the image later
    # the "id" tag can be replaced later by a caption text
    #
    echo '<a href="'${IDIR}/${fn}.jpg'"><img src="'${IDIR}/${fn}_i.jpg'" width="'${size1}'" height="'${size2}'" alt="'${IDIR}/${fn}.jpg'" name="'${fn}'"></a>' >> ${IDX}

    if [ $EXIF ] ; then

	# parse EXIF information and extract data into a table
    # Note that not all EXIF fields are available on all cameras,
    # e.g. my Fuji FinePix2400 Zoom does not know about "Image Number";
    # "Exposure Time" is replaced by "Shutter Speed Value"
    # if you add/remove something here, update 'td rowspan' above!!
    #
	echo "</td><td class=tablehi>Image:</td><td>"${fn}"</td></tr>" >> ${IDX}
        sed -n '/Model:/p' ${tmpfile} | awk '{print "<tr><td class=tablehi>Camera "$1"</td><td>" $2 " " $3 " " $4 "</td></tr>"}' >> ${IDX}
	sed -n '/Capture Date/p' ${tmpfile} | awk '{print "<tr><td class=tablehi>"$1 " " $2 " " $3"</td><td>" $4 " " $5"</td></tr>"}' >> ${IDX}
	sed -n '/Focal Length/p' ${tmpfile} | sed '2,4d' | awk '{print "<tr><td class=tablehi2>" $1 " " $2"</td><td class=low>" $3"</td></tr>"}' >> ${IDX}
	sed -n '/Aperture:/p' ${tmpfile} | awk '{print "<tr><td class=tablehi2>"$1"</td><td class=low>"$2"</td></tr>"}' >> ${IDX}
	sed -n '/ISO/p' ${tmpfile} | sed -e 's/(//' -e 's/)//' | awk '{print "<tr><td class=tablehi2>"$1 " " $2 " " $3"</td><td class=low>"$5"</td></tr>"}' >> ${IDX}
	sed -n '/Flash:/p' ${tmpfile} | awk '{print "<tr><td class=tablehi2>"$1"</td><td class=low>"$2"</td></tr>"}' >> ${IDX}
 	echo "<tr><td colspan="3">&nbsp;</td></tr>" >> ${IDX}
        echo "" >> ${IDX}
    fi
    echo " done."
done

# im case EXIF info was desired, close the table
# else just close </p>
#
if [ $EXIF ] ; then
    echo '</table></center>' >> ${IDX}
else
    echo "</p>" >> ${IDX}
fi

# if requested, add code for slideshow
#
if [ $SLIDESHOW ] ; then
cat <<'EOF' >> ${IDX}
<h2><a name="slideshow">Slideshow</a></h2>
<form name="slideForm">
  <select name="slide" onChange="rotate(this.selectedIndex);">
    <script type="text/javascript">
    <!--
      for (i = 0; i < document.images.length; i++) {
      	document.write("<option value=\"" + document.images[i].name + "*" + document.images[i].alt + "\">" + document.images[i].name + "</option>");
      }
    //-->
    </script>
  </select>

  <input type=button onclick="rotate(0);" value="|&lt;&lt;" title="Jump to beginning">
  <input type=button onclick="rotate(x-1);" value="&lt;" title="Last Picture">
  <input type=button name="slidebutton" onClick="this.value=((this.value=='Stop')?'Start':'Stop');apRotate();" value="Start" title="Autoplay">
  <input type=button onclick="rotate(x+1);" value="&gt;" title="Next Picture">
  <input type=button onclick="rotate(this.form.slide.length-1);" value="&gt;&gt;|" title="Jump to end">
</form>

<script type="text/javascript">
<!--
  document.write("<p><img src=\"" + document.images[0].alt + "\" alt=\"current image\" name=\"show\"></p>");
  document.write("<p id=\"picturetitle\">" + document.images[0].name + "</p>");
//-->
</script>
EOF
fi

# finalise HTML code by adding a footer
#
echo -n "<p class=\"footer\">&copy; "$YEAR >> ${IDX}
echo -n " by <a href=\"&#109;ail&#116;&#111;:$EMAIL\">$NAME</a>. " >> ${IDX}

cat <<'EOF' >> ${IDX}
All rights reserved.</p>
</body>
</html>
EOF

if [ -x ${tmpfile} ] ; then rm ${tmpfile}; fi
exit 0


