#!/usr/bin/python
# vim:set ai et sts=2 sw=2:
# vim600: set fdm=marker cms=#%s :
#
# Photon is a static HTML gallery generator.
#   * directory structure is kept intact
#   * scaled images are put in separate directories (800x600, ...)
#   * etc...
#
#  Created: Luc Saillard <luc(@)sailard.org>  Wed, 13 Feb 2002 20:51:12 +0100
#  Last modified: Luc Saillard <luc(@)saillard.org> Tue, 26 Apr 2004 12:01:23 +0100
#  Changelog:
#    v0.1: first release in Python.
#    v0.2.3: ???
#    v0.2.4: speed up improvement
#    v0.2.5: New <link> that indicates previous/next page
#            Key Shorcuts can now be use
#            Slideshow functions
#    v0.2.6: Javascript fix
#            Cmdline option fix
#            New exclude option
#    v0.2.7: Gimp plugin (very slow for the moment)
#            Bug fixes
#            New options: --resize-plugin
#    v0.2.8: Anchor is made for each row in the galery view
#    v0.2.9: Compat with gimp-2.0
#            
#
#
#  Copyright (C) 2002-2004 Luc Saillard <luc(@)saillard.org>
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2 of the License, or
#   (at your option) any later version.
#
#   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.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Includes modules 
import getopt, os, sys, shutil, urllib, re, locale, codecs
from fnmatch import *
from stat import *
from tempfile import *
from array import array
from PIL import Image, ImageFile
from commands import getstatusoutput
import EXIF
# ------------------------------------------------------------------------ 
""" Global options """ # 
options = {}
options['verbose_level'] = 0
options['output_directory'] = './photos'
options['comment_filename'] = '.comments'
options['thumbsize'] = (160,120)
options['sizelist'] = [(0,0), (1024,768), (800,600), (640,480)]
options['forcecreate_html'] = 0
options['display_columns'] = 4
options['display_lines'] = 5
options['generate_root_index_html'] = 1
#options['img_bgcolor'] = "#eee5de"
#options['img_bgcolor'] = "#fff5ee"
options['img_bgcolor'] = "#fffaf5"
options['body_bgcolor'] = "#ccccff"
options['exif'] = 1
options['exif_bordercolor'] = "#008000"
options['exif_bgcolor'] = "#f0fff0"
options['exif_fgcolor'] = "black"
options['javascript'] = 1
options['exclude'] = ["*.mov","*.avi"]
options['resize_plugin'] = "internal"
options['resize_quality_low'] = 0.5
options['resize_quality_high'] = 0.85
options['gimp_program'] = "gimp"
options['charset'] = 'iso-8859-15'

photon_version = '0.2.9'

gimp_list = []  # This is a global list that contains jobs to be run by Gimp

# ------------------------------------------------------------------------ 
#
# Main functions
#
def main():# 
  """ main procedure """

  short_opts="c:d:EfhIl:o:s:t:Vv"
  long_opts=[ "comment=", "display-columns=", "no-exif", "force", "help",
              "no-index", "display-lines=", "output-directory=", "sizelist=",
              "thumbsize=", "version",  "verbose", "javascript",
              "exif-bordercolor=", "exif-bgcolor=", "exif-fgcolor=",
              "body-bgcolor=", "img-bgcolor=", "exclude=", "resize-plugin=",
              "resize-quality-low=", "resize-quality-high=", "gimp_program="
            ]
  try:
    opts, args = getopt.getopt(sys.argv[1:], short_opts, long_opts)
  except getopt.GetoptError, msg:
    # print help information and exit:
    if msg:
      print msg
    usage()
    sys.exit(2)
  for o, a in opts:
    if o in ("-c", "--comment"):
      options['comment_filename'] = a
    elif o in ("-d", "--display-columns"):
      if a.isdigit():
        options['display_columns'] = int(a)
      else:
        print "Bad argument:",a,"must be a number"
    elif o in ("-E", "--no-exif"):
      options['exif'] = 0
    elif o in ("-f", "--force"):
      options['forcecreate_html'] = 1
    elif o in ("-h", "--help"):
      usage()
      sys.exit()
    elif o in ("-I", "--no-index"):
      options['generate_root_index_html'] = 0
    elif o in ("-J", "--no-javascript"):
      options['javascript'] = 0
    elif o in ("-l", "--display-lines"):
      if a.isdigit():
        options['display_lines'] = int(a)
      else:
        print "Bad argument:", a, "must be a number"
    elif o in ("-o", "--output-directory"):
      options['output_directory'] = a
    elif o in ("-s", "--sizelist"):
      l=[]
      for s in a.split(','):
        if s == '0':
          l.append((0,0))
        else:
          m = re.search("^(\d+)x(\d+)$",s)
          if m:
            l.append((int(m.group(1)), int(m.group(2))))
          else:
            print "Bad size:", s, "must in the form 320x240"
      # End: for s in a.split(','):
      options['sizelist'] = l
    elif o in ("-t", "--thumbsize"):
      m = re.search("^(\d+)x(\d+)$",s)
      if m:
        options['thumbsize'] = ((int(m.group(1)),int(m.group(2))))
      else:
        print "Bad size:", s, "must in the form 320x240"
    elif o in ("-V", "--version"):
      print "Photon", photon_version
      sys.exit()
    elif o in ("-v", "--verbose"):
      options['verbose_level'] += 1
    elif o == "--exif-bordercolor":
      if match_color_type(a):
        options['exif_bordercolor'] = a
      else:
        print "Bad color:", a, "must in the form #0055ff or white"
    elif o == "--exif-bgcolor":
      if match_color_type(a):
        options['exif_bgcolor'] = a
      else:
        print "Bad color:", a, "must in the form #0055ff or white"
    elif o == "--exif-fgcolor":
      if match_color_type(a):
        options['exif_fgcolor'] = a
      else:
        print "Bad color:", a, "must in the form #0055ff or white"
    elif o == "--body-bgcolor":
      if match_color_type(a):
        options['body_bgcolor'] = a
      else:
        print "Bad color:", a, "must in the form #0055ff or white"
    elif o == "--img-bgcolor":
      if match_color_type(a):
        options['img_bgcolor'] = a
      else:
        print "Bad color:", a, "must in the form #0055ff or white"
    elif o == "--exclude":
      options['exclude'].append(a)
    elif o == "--resize-plugin":
      if a in ("internal","gimp"):
        options['resize_plugin'] = a
      elif a in ("magick"):
        print "This method plugin ",a," is not available"
      else:
        print "This method plugin ",a," is unknow. Using default plugin: ",options['resize_plugin']
    elif o == "--resize-quality-low":
      if a.isdigit():
        options['resize_quality_low'] = int(a)/100.0
      else:
        print "Bad argument:", a, "must be a number"
    elif o == "--resize-quality-high":
      if a.isdigit():
        options['resize_quality_high'] = int(a)/100.0
      else:
        print "Bad argument:", a, "must be a number"
    elif o == "--gimp-program":
      options['gimp_program'] = a

  print_options()

  if options['thumbsize'] not in options['sizelist']:
    options['sizelist'].append(options['thumbsize'])
  # Fix a bug when decompressing -> compressing a jpeg file
  ImageFile.MAXBLOCK = 1000000 # default is 64k
  locale.setlocale(locale.LC_ALL,'')
  options['charset'] = locale.nl_langinfo(locale.CODESET)
  

  if args:
    for path in args:
      process_directory(path,"")
      # If we are using Gimp, process the file list
    if options['resize_plugin'] == "gimp" and len(gimp_list)>0:
      options['gimp_program'] = find_gimp_program()
      if options['gimp_program'] is None:
        print "Gimp not found\nAborted\n"
      else:
        options['tempdir'] = mkdtemp("","photon")
        gimp_resize_files(gimp_list)
        os.rmdir(options['tempdir'])
  else:
    usage()
    sys.exit()

# ------------------------------------------------------------------------ 
def usage(): # 
  """ Print information to use this program """
  print """
Usage: photon.py [OPTION]... [PATH]...

Options:
  -c NAME --comment               Name of the comment file (default .comments)
  -d NUM  --display-columns=NUM   Number of columns in index (default 3)
  -E      --no-exif               Don't include EXIF information in HTML file
  -f      --force                 Overwrite image files (default no)
  -h      --help                  Print this help
  -I      --no-index              Do not generate the high level index.html
  -J      --no-javascript         Do not use javascript (no shortcuts, ... )
  -l NUM  --display-lines=NUM     Number of lines in index (default 5)
  -o NAME --output-directory=NAME Name of the output directory
  -s LIST --sizelist=LIST         Image sizes (default 0,1024x768,800x600,640x480)
  -t SIZE --thumbsize=SIZE        Size of thumbnails (default 160x120)
  -V      --version               Print Photon version
  -v      --verbose               Be verbose
          --exif-bordercolor=COLOR  Exif window border color (default #008000)
          --exif-bgcolor=COLOR    Exif window background color (default #f0fff0)
          --exif-fgcolor=COLOR    Exif window text color (default 'black')
          --body-bgcolor=COLOR    Body background color (default #ccccff)
          --img-bgcolor=COLOR     Image background color (default 'white')
          --exclude=PATTERN       Exclude files matching PATTERN
          --resize-plugin=PLUG    Program use to create thumbnails
                                     internal: fastest method (default)
                                     gimp: use Gimp>1.x (better quality)
                                     magick: use ImageMagick (not implemented)
          --resize-quality-low=Q  quality for small image. 0 (bad) and 100 (good)
          --resize-quality-high=Q quality for big image 0 (bad) and 100 (good)
          --gimp-program=PROG     use PROG for gimp
          

Shortcuts while viewing an image:
  n/SPACE     Go to the next image (with the same resolution)
  p/BACKSPACE Go to the previous image (with the same resolution)
  s           Start/Stop the slideshow
  +/-         Increase/Decrease by one second the slideshow's period
  h           Show shortcup and help [NOT YET IMPLEMENTED]
  z           Change to the higher resolution [NOT YET IMPLEMENTED]

""" 
# ------------------------------------------------------------------------ 
def print_options():# 
  """ Print Options array that contains all configurable options """

  if options['verbose_level'] >= 1:
    print "Program options:"
    for key in options.keys():
      print " ", key, options[key]
# ------------------------------------------------------------------------ 
#
# Functions that work on a directory or on a file
#
def process_directory(realpath, relativepath): # 
  """ Generate thumbnails, html pages for this directory """

  directories_list = []
  images_list = []

  destdir = os.path.join(options['output_directory'], relativepath)
  safe_mkdir(destdir)

  sourcedir = os.path.join(realpath, relativepath)

  # For each images build alls sub-image,
  # for each directory, recurse into them
  for entry in os.listdir(sourcedir):
    # Don't accept directory or file beginning with a dot
    if entry[0] == '.':
      continue
    pathname = os.path.join(sourcedir, entry)
    for pattern in options['exclude']:
      if fnmatch(pathname,pattern):
        print 'Excluding %s' % pathname
        break
    else:
      mode = os.stat(pathname)[ST_MODE]
      if S_ISDIR(mode):
        process_directory(realpath, os.path.join(relativepath, entry))
        directories_list.append(entry)
      elif S_ISREG(mode):
        picinfo = process_file(realpath, relativepath, entry)
        if picinfo:
          images_list.append(picinfo)
      else:
        print 'Skipping %s' % pathname

  process_comment_file(realpath, relativepath, images_list)

  # Now, we have the complete list of directory, and files ... sort them
  images_list.sort(lambda x, y: cmp(x['filename'], y['filename']))
  directories_list.sort()
  # ... then generate html pages
  make_directory_html(relativepath,directories_list, images_list)
  if len(images_list) > 0:
    make_image_html(relativepath,directories_list, images_list)

# ------------------------------------------------------------------------ 
def process_file(sourcedir, relativepath, filename): # 
  """ Create for this file all thumbnails and return the size of the image """

  srcfile = os.path.join(sourcedir, relativepath, filename)

  # Keep only Jpeg file (python cores dump with some PCD (kodak) file
  if ((filename.lower().rfind(".jpg") == -1) and
      (filename.lower().rfind(".jpeg") == -1)) :
    return None

  pic = {}
  try:
    im = Image.open(srcfile)
    print 'Processing image', srcfile, im.format, "%dx%d" % im.size, im.mode
  except IOError, err:
    print "cannot create thumbnail for", srcfile ,"(", err.strerror, ")"
  else:
    # Foreach size of the image, resize them only when it's different from Original
    pic['filename'] = filename
    pic['original_size'] = im.size
    if options['exif']:
      file = open(srcfile, 'rb')
      pic['exif'] = EXIF.process_file(file)
    if im.size[1] > im.size[0]: # Keep aspect ration
      pic['ratio'] = 34
    else:
      pic['ratio'] = 43
    for (w,h) in options['sizelist']:
      if w == 0 and h == 0: # Special case when it is a original file
        subdir = 'original'
        destdir = os.path.join(options['output_directory'], relativepath, subdir)
        destfile = os.path.join(destdir, filename)
	if not compare_lastmodifiedtime(srcfile, destfile):
          safe_mkdir(destdir)
          shutil.copyfile(srcfile, destfile)
      else:
	if pic['ratio']==34:
          newsize = (h,w)
	else:
          newsize = (w,h)
        subdir='%dx%d' % (w,h)
        destdir = os.path.join(options['output_directory'], relativepath, subdir)
        destfile = os.path.join(destdir, filename)
	if not compare_lastmodifiedtime(srcfile, destfile):
          if w > im.size[0]: # Don't generate thumbnail when original file is smaller
            print "Skipping", srcfile, "for resolution %dx%d" % newsize
            continue
          safe_mkdir(destdir)
          # We have 2 choices use Gimp or use Python Library
          if options['resize_plugin'] == "gimp":
            if w*h<64000:
              gimp_file = (srcfile,destfile,newsize,options['resize_quality_low'])
            else:
              gimp_file = (srcfile,destfile,newsize,options['resize_quality_high'])
            gimp_list.append(gimp_file)
          else: # Use the python internal module resize method
            try:
              im.resize(newsize, Image.BICUBIC).save(destfile, 'JPEG', optimize=1, progressive=1)
            except IOError, err:
              print "Error while writing thumbnail, will try without optimization..."
              print "Perhaps you can try to increase ImageFile.MAXBLOCK in the source file"
              try: # Try to save the Jpeg file without progessive option
                im.resize(newsize,Image.BICUBIC).save(destfile, 'JPEG')
              except IOError, err:
                print "cannot create ", destfile, "(", err, ")"
    return pic

# ------------------------------------------------------------------------ 
def process_comment_file(realpath, relativepath, images_list): # 
  """ Process the .comments in this directory that contains a comment for an image """

  try:
    f = open(os.path.join(realpath, relativepath, options['comment_filename']), 'r')

    r = re.compile('^"([^"]+)"\s+"([^"]+)"')
    s = f.readline()
    while s:
      m = r.search(s)
      if m:
        # Ok we found a filename, and a comment in this line
        # TODO: use map function ?
        for i in images_list:
          if i['filename'] == m.group(1):
            i['comments'] = m.group(2)
            break
      # silently discard bad line ?
      s = f.readline()
    f.close()
  except IOError:
    pass 

# ------------------------------------------------------------------------ 
def make_directory_html(relativepath, directories_list, images_list): # 
  """ Make all html page for index this directory """

  if relativepath == "" and not options['generate_root_index_html']:
    return

  images_processed = 0 # Number of the images currently processed in the list
  page_index = 0       # Number of index.html page currently processed
  total_images = len(images_list) + len(directories_list)
  items_per_page = options['display_columns'] * options['display_lines'];
  output_directory = os.path.join(options['output_directory'], relativepath)

  while images_processed < total_images:

    if images_processed == 0:
      index_html_filename = 'index.html'
    else:
      index_html_filename = 'index%d.html' % (images_processed / items_per_page)

    f = open(os.path.join(output_directory, index_html_filename), 'w')
    output_index_html_header(f, relativepath, None, None)
    if images_processed == 0:
      output_index_html_directories(f, directories_list)
      total_images -= len(directories_list) # Small hack, because i want to do a do {} while(x)
    output_index_html_images(f, images_list[images_processed : images_processed + items_per_page])
    output_index_html_footer(f, page_index, images_processed, total_images)

    f.close()

    images_processed += items_per_page
    page_index += 1

# ------------------------------------------------------------------------ 
def make_image_html(relativepath, directories_list, images_list): # 
  """ Make all html page for all images """

  make_blank_gif(os.path.join(options['output_directory'], relativepath, 'blank.gif'))
  if options['exif']:
    make_exif_js(os.path.join(options['output_directory'], relativepath, 'exif.js'))
  if options['javascript']:
    make_shortcut_js(os.path.join(options['output_directory'], relativepath, 'shortcuts.js'))

  output_directory = os.path.join(options['output_directory'], relativepath)

  total_images = len(images_list)
  k = 0

  while k < total_images:

    image = images_list[k]

    for resolution in options['sizelist']:

      image_html_filename = image_filename_to_image_htmlfilename(image['filename'], resolution)

      # Calculate the previous and next image/link
      if k == 0:
        previous_image = None
        previous_link  = None
      else:
        previous_image = images_list[k - 1]
        previous_link  = image_filename_to_image_htmlfilename(previous_image['filename'], resolution)

      if k + 1 >= len(images_list):
        next_image = None
        next_link  = None
      else:
        next_image = images_list[k + 1]
        next_link  = image_filename_to_image_htmlfilename(next_image['filename'], resolution)

      # Make the html page for this image and this resolution
      f = open(os.path.join(output_directory, urllib.unquote(image_html_filename)), 'w')
      output_image_html_header(f, relativepath, image['filename'], previous_link, next_link)
      output_image_html_body(f, relativepath, images_list, k, resolution)
      if options['exif']:
        output_image_html_exif_window(f, image)
      if options['javascript']:
        output_image_html_help_layer(f, image)
      output_image_html_footer(f)
      f.close()
    # End: for resolution in options['sizelist']:

    k += 1

  # End: for image in images_list

# ------------------------------------------------------------------------ 
#
# Functions that output HTML Code
#
def output_index_html_header(f, relativepath, previous_link, next_link): # 
  """ Write in the file, header of an index.html page (body included) """

  if relativepath == "":
    html_title = "Albums";
  else:
    html_title = relativepath.split(os.sep)[-1];

  navbar = navbar_for_index_html(relativepath)


  f.write("""
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
 <title>Photon: %s</title>
 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
""" % html_title)

  if previous_link:
    f.write(" <link rel=\"prev\" href=\"%s\">" % previous_link)
  if next_link:
    f.write(" <link rel=\"next\" href=\"%s\">" % next_link)

  f.write("""
</head>
<body bgcolor="%s">
<div align=center>
<table bgcolor="black">
<tr>
 <td>
  <table width="100%%" cellpadding=4>
  <tr bgcolor="%s">
   <td colspan=%s>%s</td>
  </tr>
""" % (options['body_bgcolor'], options['img_bgcolor'],
      options['display_columns'], navbar))

# ------------------------------------------------------------------------ 
def output_index_html_directories(f, directories_list): # 
  """ Output a HTML table that contains the directories list """

  column = 0

  f.write('  <tr bgcolor="%s">\n' % options['img_bgcolor'])

  # Create a case for each directory
  for d in directories_list:
    if column >= options['display_columns']:
      f.write('  </tr>\n  <tr bgcolor="%s">\n' % options['img_bgcolor']);
      column = 0
    f.write('   <td align=center><a href="%s/index.html"><b>%s</b></a><br><img width=%d height=1 src="blank.gif" alt=""></td>' % (urllib.quote(d),d,options['thumbsize'][0]))
    column += 1
  
  # Fill the empty case ...
  if column > 0:
    while column < options['display_columns']:
      f.write('   <td><img width=%d height=1 src="blank.gif" alt=""></td>' % options['thumbsize'][0])
      column += 1
    # Close the row
    f.write('  </tr>\n')

# ------------------------------------------------------------------------ 
def output_index_html_images(f, images_list):  # 
  """ Output a HTML table that contains the images list """
  
  column = 0
  row = 0

  f.write('  <tr bgcolor="%s"><a name="row%d">\n' % (options['img_bgcolor'],row))

  # Create a case for each image
  for pic in images_list:
    if column >= options['display_columns']:
      row += 1
      f.write('  </a></tr>\n  <tr bgcolor="%s"><a name="row%d"></a>\n' % (options['img_bgcolor'],row));
      column = 0

    if pic['ratio'] == 34:
      thumbsize_width  = options['thumbsize'][1]
      thumbsize_height = options['thumbsize'][0]
    else:
      (thumbsize_width, thumbsize_height) = options['thumbsize']
 
    imgurl = urllib.quote(os.path.join("%dx%d" % options['thumbsize'], pic['filename']))
    imghtmlurl = image_filename_to_image_htmlfilename(pic['filename'], options['sizelist'][0])

    f.write('   <td valign=bottom align=center><table border=0><tr><td bgcolor=black><a href="%s"><img hspace=3 vspace=3 border=0 src="%s" alt="%s" width=%d height=%d></a></td></tr></table><br><a href="%s"> %s </a></td>\n' % (imghtmlurl,imgurl, pic['filename'], thumbsize_width, thumbsize_height,imghtmlurl, pic['filename']))
    column += 1

  # Fill the empty case ...
  if column > 0:
    while column < options['display_columns']:
      f.write('   <td><img width=%d height=1 src="blank.gif" alt=""></td>' % options['thumbsize'][0])
      column += 1
    # Close the row
    f.write('  </a></tr>\n')

# ------------------------------------------------------------------------ 
def output_index_html_footer(f, page_index, images_processed, total_images): # 
  """ Output the footer for a index.html page """

  items_per_page = options['display_columns'] * options['display_lines'];
  f.write('  <tr bgcolor="%s"><td colspan=%s><table border=0 width="100%%"><tr><td>' 
    % (options['img_bgcolor'],options['display_columns']))
  # Output the Start and the Prev link
  if page_index == 0:
    f.write('Start | Prev %s | ' % items_per_page)
  elif page_index == 1:
    f.write('<a href="index.html">Start</a> | <a href="index.html">Prev %s</a> | ' % items_per_page)
  else:
    f.write('<a href="index.html">Start</a> | <a href="index%d.html">Prev %s</a> | ' % (page_index - 1, items_per_page))

  # Output the Next link
  max_pages = total_images / items_per_page
  if (total_images % items_per_page) == 0:
    max_pages -= 1
  if page_index < max_pages:
    f.write('<a href="index%d.html">Next %s</a> | ' % (page_index + 1, items_per_page))
  else:
    f.write('Next %s | ' % items_per_page)

  # Output the End link
  if page_index < max_pages:
    f.write('<a href="index%d.html">End</a></td>' % max_pages)
  else:
    f.write('End</td>')

  # Output the Number of Images
  if total_images == 0:
    f.write('<td align=right>&nbsp;</td>')
  else:
    max_images_displayed = images_processed + items_per_page
    if max_images_displayed > total_images:
      max_images_displayed = total_images
    f.write('<td align=right>Images %d to %d of %d</td>' 
       % (images_processed + 1, max_images_displayed, total_images))

  f.write("""</td>
    </tr>
    </table>
   </td>
  </tr>
  </table>
 </td>
</tr>
</table>
<font size=-1>Generated by photon %s</font>
</div>
</body>
</html>
""" % photon_version)
          
# ------------------------------------------------------------------------ 
def output_image_html_header(f, relativepath, imagefilename, previous_link, next_link): # 
  """ Write in the file, header of an image.html page (body included) """

  f.write("""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
 <title>Photon: %s</title>
 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> 
 <style type="text/css">
   #exifwindow {position:absolute; height:1; width:1; top:0; left:0;}
   #helpwindow {position:absolute; height:1; width:1; top:0; left:0;}
 </style>
""" % imagefilename)

  if options['exif']:
    f.write (" <script language=\"JavaScript1.2\" src=\"exif.js\"></script>\n")
  if options['javascript']:
    f.write (" <script language=\"JavaScript1.2\" src=\"shortcuts.js\"></script>\n")
  if previous_link:
    f.write(" <link id=\"previous_image\" rel=\"prev\" href=\"%s\">\n" % previous_link)
  if next_link:
    f.write(" <link id=\"next_image\" rel=\"next\" href=\"%s\">\n" % next_link)

  f.write("</head>")

  if options['exif']:
    f.write("<body bgcolor=\"%s\" onload=\"exif_init()\">\n" % options['body_bgcolor'])
  else:
    f.write("<body bgcolor=\"%s\">\n" % options['body_bgcolor'])


# ------------------------------------------------------------------------ 
def output_image_html_body(f, relativepath, images_list, index, resolution): # 
  """ Output in the file f, the content of the body for this image """

  imageinfo = images_list[index]
  if index == 0:
    previous_image = None
  else:
    previous_image = images_list[index - 1]

  if index + 1 >= len(images_list):
    next_image = None
  else:
    next_image = images_list[index + 1]

  resolution_string = "%dx%d" % resolution

  if resolution_string == "0x0":
    resolution_string = "original"
  image_urlname = urllib.quote(os.path.join(resolution_string, imageinfo['filename']));

  vars = { 'img_bgcolor'     : options['img_bgcolor'],
           'display_columns' : options['display_columns'],
           'navbar'          : navbar_for_image_html(relativepath, images_list, index),
           'imgsrc'          : urllib.quote(os.path.join(resolution_string, imageinfo['filename'])),
           'imgalt'          : imageinfo['filename'],
           'version'         : photon_version,
           'body2'           : image_html_body_sub(imageinfo, resolution, previous_image, next_image)}
  
  body = """<div align=center>
<table bgcolor="black">
<tr>
 <td>
  <table width="100%%" cellpadding=4>
  <tr bgcolor="%(img_bgcolor)s">
   <td colspan=%(display_columns)s>%(navbar)s</td>
  </tr>
  <tr bgcolor="%(img_bgcolor)s">
   <td colspan=%(display_columns)s><center><table border=0><tr><td bgcolor=black><img hspace=3 vspace=3 border=0 src="%(imgsrc)s" alt="%(imgalt)s"></td></tr></table></center></td>
  </tr>
""" % vars

  if imageinfo.has_key('comments'):
    body += """
  <tr bgcolor="%s">
   <td colspan=%s><center>%s</center></td>
  </tr>""" % (options['img_bgcolor'],options['display_columns'],imageinfo['comments'])

  body += """
  <tr bgcolor="%(img_bgcolor)s">
   <td colspan=%(display_columns)s>%(body2)s</td>
  </tr>
  </table>
 </td>
</tr>
</table>
<font size=-1>Generated by Photon %(version)s</font>
</div>
""" % vars

  f.write(body)

# ------------------------------------------------------------------------ 
def image_html_body_sub(imageinfo, resolution, previous_image, next_image): # 
  """ Return the HTML code to display 2 thumbnails to include in the image page """

  left_img = '<table border=0><tr><td valign=top width=%d>' % options['thumbsize'][0]
  if previous_image:
    thumblink = image_filename_to_image_htmlfilename(previous_image['filename'], resolution)
    thumbimg = os.path.join("%dx%d" % options['thumbsize'], urllib.quote(previous_image['filename']))
    left_img += '<a href="%s">&lt;-Prev</a><br><table border=0><tr><td bgcolor=black><a href="%s"><img align=middle hspace=2 vspace=2 border=0 src="%s" alt="%s"></a></td></tr></table>' % (thumblink, thumblink, thumbimg, previous_image['filename'])
  else:
    left_img += '<img width=%d height=1 src="blank.gif" alt="">' % options['thumbsize'][0]
  left_img += '</td></tr></table>'

  right_img = '<table border=0><tr><td valign=top align=right width=%d>' % options['thumbsize'][0]
  if next_image:
    thumblink = image_filename_to_image_htmlfilename(next_image['filename'], resolution)
    thumbimg = os.path.join("%dx%d" % options['thumbsize'], urllib.quote(next_image['filename']))
    right_img += '<a href="%s">Next -&gt;</a><br><table border=0><tr><td bgcolor=black><a href="%s"><img align=middle hspace=2 vspace=2 border=0 src="%s" alt="%s"></a></td></tr></table>' % (thumblink, thumblink, thumbimg, next_image['filename'])
  else:
    right_img += '<img width=%d height=1 src="blank.gif" alt="">' % options['thumbsize'][0]
  right_img += '</td></tr></table>'

  if options['exif']:
    sizelist = '<form action=""><input type="button" onclick="exif_hide_show()" value="Image Information"></form><br>'
  else:
    sizelist = ''
  if len(options['sizelist']):
    sizelist += "Other sizes:<br>"
    for r in options['sizelist']:
      if r != resolution:
        if r[0] > imageinfo['original_size'][0]: # Don't add this resolution when original file is smaller
          continue
        if r == (0,0):
          r_str = "Original"
        else:
	  if imageinfo['ratio'] == 34:
            r_str = "%dx%d" % (r[1], r[0])
          else:
            r_str = "%dx%d" % r
        sizelist += '<a href="%s">%s</a><br>' % (image_filename_to_image_htmlfilename(imageinfo['filename'], r), r_str)
      # End: if r != resolution:
    # End: for r in options['sizelist']:
  # End: if len(options['sizelist']):

  return """
    <table border=0 width="100%%">
    <tr>
     <td valign=top>%s</td>
     <td valign=top align=center>%s</td>
     <td valign=top align=right>%s</td>
    </tr>
    </table>
   """ % (left_img,sizelist,right_img)


# ------------------------------------------------------------------------ 
def output_image_html_exif_window(f, imageinfo): # 
  """ Output the HTML code for the exif window page """

  exif_info = ""

  if imageinfo.has_key('exif') and imageinfo['exif'].has_key('Image Make'):
    exif_info += "Taken with a %s %s<br>" % (imageinfo['exif']['Image Make'].printable, imageinfo['exif']['Image Model'].printable)

  for prop in imageinfo['exif'].keys():
    if prop in ('EXIF ExifImageLength', 'EXIF ExifImageWidth', 'EXIF DateTimeDigitized', 'EXIF ExposureTime'):
      exif_info += '<b>%s</b>: %s<br>' % (prop, imageinfo['exif'][prop].printable)

  
  f.write("""
<div id="exifwindow" style="visibility:hidden">
  <table width="1%%" height="1%%" bgcolor="%s"><tr><td><table width="300" height="250" bgcolor="%s">
   <tr><td align="left" valign="middle">%s</td></tr>
   <tr><td align="right" valign="bottom"><a href="" onclick="exif_hide(); return false;">Close this box</a></td></tr>
  </table></td></tr></table>
</div>
""" % (options['exif_bordercolor'], options['exif_bgcolor'], exif_info))
    
# ------------------------------------------------------------------------ 
def output_image_html_help_layer(f, imageinfo): # 
  """ Output the HTML code for the help window page """

  f.write("""
<div id="helpwindow" style="visibility:hidden">
  <table width="1%%" height="1%%" bgcolor="%s"><tr><td><table width="500" height="250" bgcolor="%s">
   <tr><td align="center" valign="top">Shortcuts:</td><tr>
   <tr><td align="left"><b>n/SPACE</b>: Go to the next image</td><tr>
   <tr><td align="left"><b>p/BACKSPACE</b>: Go to the previous image</td><tr>
   <tr><td align="left"><b>s</b>: Start/Stop the slideshow</td><tr>
   <tr><td align="left"><b>+/-</b>: Increase/Decrease by one second the slideshow's period</td><tr>
   <tr><td align="left"><b>h</b>: Show help</td><tr>
   <tr><td align="right" valign="bottom"><a href="" onclick="hide_help_layer(); return false;">Close this box</a></td></tr>
  </table></td></tr></table>
</div>
""" % (options['exif_bordercolor'], options['exif_bgcolor']))
    
# ------------------------------------------------------------------------ 
def output_image_html_footer(f): # 
  """ Output the footer for a image.html page """
  f.write("</body>\n</html>\n")
# ------------------------------------------------------------------------ 
#
# Useful misc function
#
def safe_mkdir(pathname): # 
  """ Create a directory only when it doesn't exist """

  try :
    mode = os.stat(pathname)[ST_MODE]
    if not S_ISDIR(mode):
      os.mkdir(pathname);
  except OSError:
    os.mkdir(pathname);

# ------------------------------------------------------------------------ 
def navbar_for_index_html(relativepath): # 
  """ Transform the directory location in a navigation bar printable in HTML """

  if relativepath == "":
    return "<b>Home</b>"

  ndirs = 1 + relativepath.count(os.sep)
  site_home = "../" * ndirs
  url = '<a href="%sindex.html">Albums</a>' % site_home

  for d in relativepath.split(os.sep):
    if d == "":
      continue
    ndirs -= 1
    if ndirs:
      url += ' -&gt; <a href="' + "../" * ndirs + 'index.html">' + d + '</a>'
    else:
      url += ' -&gt; <b>' + d + '</b>'
  return url

# ------------------------------------------------------------------------ 
def navbar_for_image_html(relativepath, images_list, index): # 
  """ Print the navigation bar for an image.html
  relativepath: Path where to found the image
  images_list: list of the images for this directory
  index: index to the current image in the images_list
  """

  # Calculate the page number where this page is located
  items_per_page = options['display_columns'] * options['display_lines'];
  current_page = index / items_per_page

  if relativepath == "":
    if current_page:
      return '<a href="index%d.html">Albums</a>' % current_page
    else:
      return '<a href="index.html">Albums</a>'

  ndirs = 1 + relativepath.count(os.sep)
  site_home = "../" * ndirs
  url = '<a href="'+site_home+'index.html">Albums</a>'

  for d in relativepath.split(os.sep):
    if d == "":
      continue
    ndirs -= 1
    if ndirs:
      url += ' -&gt; <a href="' + "../" * ndirs + 'index.html">' + d + '</a>'
    else:
      if current_page:
        url += ' -&gt; <a href="index%d.html">%s</a>' % (current_page, d)
        url += ' -&gt; <b>' + images_list[index]['filename'] + '</b>'
      else:
        url += ' -&gt; <a href="index.html">' + d + '</a> -&gt; <b>' + images_list[index]['filename'] + '</b>'
  return url

# ------------------------------------------------------------------------ 
def image_filename_to_image_htmlfilename(imagename, resolution): # 
  """ Return the name of the HTML page for an image and a resolution """
  # Strip .jpg from filename
  i = imagename.find(".")
  image_filename_without_ext = imagename[0:i]

  # if this image is the original size, do not append the resolution to the filename
  resolution_string = "%dx%d" % resolution
  if resolution_string == "0x0":
    image_html_filename="%s.html" % image_filename_without_ext;
  else:
    image_html_filename="%s_%s.html" % (image_filename_without_ext, resolution_string);
  return urllib.quote(image_html_filename)

# ------------------------------------------------------------------------ 
def compare_lastmodifiedtime(pathname1, pathname2): # 
  """ Compare the last modified time from 2 files (or directory) """

  if options['forcecreate_html']:
    return None
  try :
    mode1 = os.stat(pathname1)[ST_MTIME]
    mode2 = os.stat(pathname2)[ST_MTIME]
    return (mode1 < mode2)
  except OSError:
    return None

# ------------------------------------------------------------------------ 
def make_blank_gif(pathname): # 
  """ Make a small gif 1x1 with a transparent color """
  blank_gif="\107\111\106\070\071\141\001\000\001\000\200\000\000\000\000\000"\
            "\377\377\377\041\371\004\001\000\000\000\000\054\000\000\000\000"\
            "\001\000\001\000\100\002\001\104\000\073"

  try :
    f = open(pathname,'wb')
  except OSError:
    return 0
  else:
    f.write(blank_gif)
    f.close()
    
# ------------------------------------------------------------------------ 
def make_shortcut_js(pathname): # 
  """ Make a separate javascript file that contains shortcut functions """

  try :
    f = open(pathname, 'w')
  except OSError:
    return 0
  else:
    f.write("""
// Some variables to autodetect browser type
var ns=(document.layers);
var ie=(document.all);
var w3=(document.getElementById && !ie);

var timeout=5000;

// Return an object
function get_object(id)
{
 if(!ns && !ie && !w3) 
   return null;
 if (ie)
   e=eval("document.all." + id);
 else if (ns)
   e=eval('document.links[id]');
 else if (w3)
   e=eval('document.getElementById(id)');
 return e;
}

// Change the current page to the id found in the page
function jumpto(id)
{
  e = get_object(id);
  if ((e != null) && (e.href != null)) {
    if (mytimeout) {
      location.href = e.href + "?slideshow=" + timeout;
    } else {
      location.href = e.href;
    }
  }
}

// Change the current page
function next_page()
{
  jumpto('next_image');
}

function previous_page()
{
  jumpto('previous_image');
}

function show_help_layer()
{
  if (ie)
    help_layer=eval('document.all.helpwindow.style');
  else if (ns)
    help_layer=eval('document.layers["helpwindow"]');
  else if (w3)
    help_layer=eval('document.getElementById("helpwindow").style');

  if (ie)
   {
     documentWidth  =document.body.offsetWidth/2+document.body.scrollLeft-20;
     documentHeight =document.body.offsetHeight/2+document.body.scrollTop-20;
     help_layer.visibility="visible";
   }    
  else if (ns)
   {
     documentWidth=window.innerWidth/2+window.pageXOffset-20;
     documentHeight=window.innerHeight/2+window.pageYOffset-20;
     help_layer.visibility ="show";
   }
  else if (w3)
   {
     documentWidth=self.innerWidth/2+window.pageXOffset-20;
     documentHeight=self.innerHeight/2+window.pageYOffset-20;
     help_layer.visibility="visible";
   }
  help_layer.left=documentWidth-250;
  help_layer.top =documentHeight-125;
  help_isshow=1;
}

function hide_help_layer()
{
  if (ie||w3)
    help_layer.visibility="hidden";
  else
    help_layer.visibility="hide";
  help_isshow=0;
}

// Activate/Deactivate the slideshow
var mytimeout = 0;
function toggle_slideshow()
{
  if (!mytimeout)
   {
     mytimeout = setTimeout("next_page()",timeout);
     window.status='Slideshow set to ' + (timeout/1000) + ' seconds';
   }
  else
   {
     clearTimeout(mytimeout);
     mytimeout=0;
     window.status='Stopping Slideshow';
   }
}

// Manage timeout for the slideshow
function modify_timeout(t)
{
  timeout+=t;
  if (timeout<1000)
    timeout=1000;
  if (mytimeout)
  { // If the counter is active, reactivate it !
    toggle_slideshow();
    toggle_slideshow();
  }
  else
  {
     window.status='Slideshow timeout set to ' + (timeout/1000) + ' seconds';
  }
}

// Event Handler that receive Key Event
function getkey(e)
{
  if (e == null)
   { // IE
     kcode = window.event.keyCode;
   } 
  else
   { // Mozilla
     kcode = e.which;
   }
  key = String.fromCharCode(kcode).toLowerCase();
//  alert("pressed " + kcode + " <=> " + key);
  switch(key)
   {
     case "n":
     case " ":
       next_page();
       return false;
     case "p":
       previous_page();
       return false;
     case "s":
       toggle_slideshow();
       return false;
     case "+":
       modify_timeout(1000);
       return false;
     case "-":
       modify_timeout(-1000);
       return false;
     case "h":
       show_help_layer();
       return false;
   }
  switch(kcode)
   {
     case 8:
       previous_page();
       return false;
   }
  return true;
}

if(w3 || ie)
{
  document.onkeypress = getkey;
} 
else
{
  document.captureEvents(Event.KEYUP);
  document.onkeyup = getkey; 
  document.captureEvents(Event.KEYPRESS);
  document.onkeypress = getkey;
}

// Test if the slideshow is active
var argstr = location.search.substring(1, location.search.length)
var args = argstr.split('&');
for (var i = 0; i < args.length; i++)
{
  var arg = unescape(args[i]).split('=');
  if (arg[0] == "slideshow") 
   { // ... and set timeout according to the last value
     timeout=parseInt(arg[1]);
     toggle_slideshow();
   }
}

// Some code for preloading the next image
/*
e = get_object('next_image');
if ((e != null) && (e.href != null)) {
  preload_image = new Image();
  preload_image.onload = loadnextimage();
  preload_image.src = e.href;
}
*/
""")
    f.close()
# ------------------------------------------------------------------------ 
def make_exif_js(pathname): # 
  """ Make a separate javascript file that contains dynamic html functions """

  try :
    f = open(pathname, 'w')
  except OSError:
    return 0
  else:
    f.write("""
var ns=(document.layers);
var ie=(document.all);
var w3=(document.getElementById && !ie);
var exif_layer;
var exif_isshow;

function exif_init()
{
   if(!ns && !ie && !w3) 
     return;
   if (ie)
     exif_layer=eval('document.all.exifwindow.style');
   else if (ns)
     exif_layer=eval('document.layers["exifwindow"]');
   else if (w3)
     exif_layer=eval('document.getElementById("exifwindow").style');
   
   exif_hide();
}

function exif_show()
{
  if (ie)
   {
     documentWidth  =document.body.offsetWidth/2+document.body.scrollLeft-20;
     documentHeight =document.body.offsetHeight/2+document.body.scrollTop-20;
     exif_layer.visibility="visible";
   }    
  else if (ns)
   {
     documentWidth=window.innerWidth/2+window.pageXOffset-20;
     documentHeight=window.innerHeight/2+window.pageYOffset-20;
     exif_layer.visibility ="show";
   }
  else if (w3)
   {
     documentWidth=self.innerWidth/2+window.pageXOffset-20;
     documentHeight=self.innerHeight/2+window.pageYOffset-20;
     exif_layer.visibility="visible";
   }
  exif_layer.left=documentWidth-150;
  exif_layer.top =documentHeight-125;
  exif_isshow=1;
  setTimeout("exif_hide()",10000);
}

function exif_hide()
{
  if (ie||w3)
    exif_layer.visibility="hidden";
  else
    exif_layer.visibility="hide";
  exif_isshow=0;
}

function exif_hide_show()
{
  if (exif_isshow)
    exif_hide()
  else
    exif_show()
}
""")
    f.close()
    
# ------------------------------------------------------------------------ 
def match_color_type(color): # 
  """ Return the string when the string match a color in HTML format """
  if color[0] == "#":
    if re.match("^#[0-9a-f]{6}$",color):
      return color
  elif re.match("^[a-z]+$",a):
    return color
  return None

# ------------------------------------------------------------------------ 
def gimp_resize_files(fileslist,flush=0): # 
  """ Use Gimp to resize an images list"""

  # Test Gimp version, script-fu changes with gimp-2.0
  gimp_version = gimp_get_version(options['gimp_program'])
  if gimp_version is None:
    gimp_copy_image_function = "gimp-channel-ops-duplicate"  
  elif gimp_version >= 0x020000:
    gimp_copy_image_function = "gimp-image-duplicate"  
  else:
    gimp_copy_image_function = "gimp-channel-ops-duplicate"  

  # Create our batch file for gimp
  batchfile = os.path.join(options['tempdir'],'photon.scm')
  prefix_in = os.path.join(options['tempdir'],'in-')
  prefix_out = os.path.join(options['tempdir'],'out-')
  try :
    #f = open(batchfile, "wb")
    f = codecs.open(batchfile,"wb",'utf-8');
  except OSError:
    return 0
  else:
    f.write("""; photon.scm
; Copyright: Luc Saillard <luc@saillard.org> 2002
; Licence: Artistic License
; Gimp Script-Fu
; Plugin to resize all files in a batch using the best quality from the gimp

; create a thumbnail from a filename
(define (create-thumbnail filename_in filename_out tn_width tn_height jpeg_quality)
  (let* 
      ((image    (car (gimp-file-load 0 filename_in filename_in)))
       (drawable (car (gimp-image-active-drawable image))))
       
    (gimp-image-undo-disable image)
    (gimp-image-scale image tn_width tn_height)
    (file-jpeg-save 1 image drawable filename_out filename_out jpeg_quality 0.0 1 1 "" 0 1 0 0)
    (gimp-image-delete image)
    ))

; create a thumbnail with a already loaded image
(define (create-thumbnail-multiple image filename_out tn_width tn_height jpeg_quality)
  (let* 
      ((image_copy    (car (%s image)))
       (drawable_copy (car (gimp-image-active-drawable image_copy))))
       
    (gimp-image-undo-disable image_copy)
    (gimp-image-scale image_copy tn_width tn_height)
    (file-jpeg-save 1 image_copy drawable_copy filename_out filename_out jpeg_quality 0.0 1 1 "" 0 1 0 0)
    (gimp-image-delete image_copy)
    ))
""" % gimp_copy_image_function)

    #
    # Ok, for each files in fileslist, we generate a scrit-fu that:
    #    read the file (set! image (car (gimp-file-load 1 \"in.jpeg\" \"in.jpeg\")))
    #    create all thumbnails for this images
    #                  (create-thumbnail-multiple image \"out.jpeg\" 640 480 jpeg_quality)
    #    free memory for the last image
    # We have one function that do in one shot
    #                  (create-thumbnail "in.jpeg" "out" 640 480 jpeg_quality)
    # The big problem is Gimp 2.x: Gimp want utf-8 string, but some file can be with
    # another locale. If we convert filename in UTF-8, gimp can load the file (file not found)
    # So to fix this problem, we link in the tempdir, all the input file.
    #
    oldfile=""
    i=0
    while i < len(fileslist):
      file=fileslist[i]

      filein = "%s-%d" % (prefix_in,i)
      fileout = "%s-%d" % (prefix_out,i)
      os.symlink(file[0],filein)
      os.symlink(file[1],fileout)

      if (file[0] == oldfile):
        f.write("(gimp-message \"Saving %s  %d/%d\")\n" % (unicode(file[1],options['charset']),i,len(fileslist)))
        f.write("(create-thumbnail-multiple image \"%s\" %d %d %f)\n" % (fileout,file[2][0],file[2][1],file[3]))
        if (i+1<len(fileslist) and file[0] != fileslist[i+1][0]):  
          f.write("(gimp-image-delete image)\n")
      else:
        if (i+1<len(fileslist) and file[0] == fileslist[i+1][0]):
          # The next image is in the same sequence than the one present
          f.write("(gimp-message \"Loading %s\")" % unicode(file[0],options['charset']))
          f.write("(set! image (car (gimp-file-load 1 \"%s\" \"%s\")))\n" % (filein,filein))
          f.write("(gimp-message \"Saving %s  %d/%d\")\n" % (unicode(file[1],options['charset']),i,len(fileslist)))
          f.write("(create-thumbnail-multiple image \"%s\" %d %d %f)\n" % (fileout,file[2][0],file[2][1],file[3]))
        else:
          f.write("(create-thumbnail \"%s\" \"%s\" %d %d %f)\n" % (fileout,fileout,file[2][0],file[2][1],file[3]))
      oldfile=file[0]
      i+=1

    f.write("(gimp-quit 0)\n\n")
    f.close()
    ret = os.system("env LANG=C LC_ALL=C %s --no-data --no-interface --verbose --batch '(begin (load \"%s\") (gimp-quit 0))'" %
        (options['gimp_program'],batchfile))
    if os.WEXITSTATUS(ret):
      print "Can't execute gimp."
    # Clean up tempfile
    os.unlink(batchfile)
    i=0
    while i < len(fileslist):
      os.unlink("%s-%d" % (prefix_in,i))
      os.unlink("%s-%d" % (prefix_out,i))
      i+=1
  


# ------------------------------------------------------------------------ 


# ------------------------------------------------------------------------ 
def gimp_get_version(prog): # 
  """ Launch gimp to query the version"""
  (size , version_string) = getstatusoutput("%s --version" % prog)
  if size:
    print "Gimp not found ? (ERROR:%s)\n" % version_string
    return None
  m = re.search("(\d+)\.(\d+)\.(\d+)",version_string)
  if m:
    return (  (int(m.group(1))<<16)
            + (int(m.group(2))<<8)
            + (int(m.group(3))))
  else:
    print "Bad version ? gimp is not installed ?\n"
    print "ERROR: %s\n" % version_string
    return None
# ------------------------------------------------------------------------ 

# ------------------------------------------------------------------------ 
def find_gimp_program(): # 
  """ Launch gimp to query the version"""
  for prog in [ options['gimp_program'] , 'gimp', 'gimp-2.0', 'gimp-1.2' ]:
    if gimp_get_version(prog) is not None:
      return prog
  return None
# ------------------------------------------------------------------------ 

# ------------------------------------------------------------------------ 
def is8bit(string): #
  return re.compile("[\x80-\xff]").search(string)
# ------------------------------------------------------------------------ 
  
  




#import profile

if __name__ == "__main__":
#    profile.run('main()')
    main()


