#!/usr/bin/python

# thumbnail_htm.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       August 29, 2004         bar
#       August 30, 2004         bar
#       August 31, 2004         bar     ALT the file name with _ changed to space
#       September 5, 2004       bar     normalize mirror_lake_fog_pct_01
#       October 10, 2004        bar     add a picture to normalizers list
#       October 31, 2004        bar     new pictures
#       December 13, 2004       bar     make the spaced (ALT) name visible for the tag so that maybe Google's robot will find the pictures
#       February 20, 2005       bar     new images
#                                       ignores
#       February 21, 2005       bar     get some spellings right
#       March 13, 2005          bar     new pictures
#       March 16, 2005          bar     ooops, unrotate the carbon river shots
#       April 18, 2005          bar     roosevelt_02 rotated
#       April 24, 2005          bar     more pictures
#       April 29, 2005          bar     don't put .jpg in the dictionary file names!
#       May 1, 2005             bar     more pics
#                                       ignore names starting with underscore
#       May 15, 2005            bar     new ones
#       May 28, 2005            bar     new ones
#       May 30, 2005            bar     new ones
#       June 4, 2005            bar     new ones
#       June 5, 2005            bar     new ones
#       June 26, 2005           bar     new ones
#       July 4, 2005            bar     new ones
#       July 9, 2005            bar     new ones
#       July 11, 2005           bar     case-insensitive file name sort
#       July 17, 2005           bar     new ones
#       July 18, 2005           bar     ooops, forgot to rotate one
#       July 23, 2005           bar     Snow Lake stuff
#       July 24, 2005           bar     more
#       July 31, 2005           bar     Mt Freemont with Summer and Romain
#       August 1, 2005          bar     oooops, Freemont is spelled Fremont
#       August 2, 2005          bar     options, etc
#       August 6, 2005          bar     Rattlesnake Mountain trail August
#       August 7, 2005          bar     Blue Angels
#       August 15, 2005         bar     Rock Lake
#       August 18, 2005         bar     Rock Lake spelling
#       August 21, 2005         bar     more
#       August 27, 2005         bar     Mowich Lake to Spray Park
#       August 28, 2005         bar     convert the big pictures, too
#       September 5, 2005       bar     another image
#       September 17, 2005      bar     put the image processing directives in a cfg file
#                                       buncha new cmd line params - make this program more generic, to replace igal.pl
#       September 18, 2005      bar     able to take ambiguous file names as input, too
#                                       convert program options
#       September 19, 2005      bar     insure the 'big' dir exists
#       September 19, 2005      bar     --thumb_label option
#       September 21, 2005      bar     put quotes around file names for convert program
#       September 22, 2005      bar     better end-of-file-name number stripping
#       September 26, 2005      bar     put in local links
#       October 1, 2005         bar     force to_dir to exist
#       October 16, 2005        bar     do JPG and GIF files
#                                       all output files are .jpg
#                                       output WIDTH and HEIGHT for the thumbnails
#                                       don't default TRUE the _file_name and file_name_##### and _original logic (cmd line options for them now)
#                                       do .bmp files, too (like GIF, untested)
#                                       --clean option
#                                       whack possibly-previously created, ignored pictures
#       December 16, 2005       bar     --label
#                               bar     --thumb_labels
#       December 25, 2005       bar     improve an error printout
#       January 27, 2006        bar     date sort
#                                       update the pictures if the source pictures are newer
#                                       set the file date/time to that of the source (needs to use the EXIF data)
#       February 9, 2006        bar     prepare to use gimp to do the image manipulation
#                                       fix the logic for the never-used --convert and --thumb_convert options
#       February 12, 2006       bar     get gimp going under windows
#                                       don't crash tring to get file times when --clean
#       February 13, 2006       bar     find programs on path
#                                       ..._quality cmd line setting (for gimp)
#                                       gimp: make the width 200 - don't make the smallest dimension 200 (--geometry, that is)
#                                       hz and vrt alignment options - and change default to top/left from center
#       February 14, 2006       bar     jpeg_comment
#       February 16, 2006       bar     thumb_width
#       April 18, 2007          bar     tweak the strip-trailing-numeric logic
#       June 17, 2007           bar     image_exif_lat_lon
#       June 26, 2007           bar     comment and get rid of the set_exif_lat_lon - use tz_jpg.py
#       June 27, 2007           bar     fix exif time function from a bad goof from long ago
#       July 2, 2007            bar     use tz_jpg
#       July 5, 2007            bar     move some stuff in to tz_google_earth
#                                       reorg the code
#                                       put out icons for KML
#       July 6, 2007            bar     comment
#       July 21, 2007           bar     use pictures_to_kml.py, not (it doesn't have URL logic in yet - and other things we do)
#       August 21, 2007         bar     add where and when descriptions for KM? photos
#       September 23, 2007      bar     --rename_regx_to
#       November 18, 2007       bar     turn on doxygen
#       November 27, 2007       bar     insert boilerplate copyright
#       February 21, 2008       bar     shadows
#       February 24, 2008       bar     find most recent gimp 2.x
#       March 2, 2008           bar     use gimp-console program under win32 so that the dos console box doesn't stay up waiting for a keypress
#       March 8, 2008           bar     put the shadow files in the target dir, not the current dir
#       May 17, 2008            bar     email adr
#       June 3, 2008            bar     make labels work
#       August 23, 2008         bar     start linux port
#       August 29, 2008         bar     basestring instead of StringType because of unicode strings and others
#       September 9, 2008       bar     remove a couple of debugging prints
#       October 21, 2008        bar     rss
#                                       use geometry even if not convert_options are given
#                                       kmz, not kml
#       October 22, 2008        bar     remove the RSS image border
#                                       put rss link in <head>
#                                       no shadowing for rss images (because the browsers show 'em badly)
#       October 28, 2008        bar     comments
#                                       output track files
#       October 29, 2008        bar     all_icons.htm
#                                       if the picture has a track, send out the kmz to google maps
#       March 21, 2010          bar     get_create_time() and its use
#                                       make icon htm 3 * columns rather than 5
#                                       no, just put a style width % in the td's
#       May 14, 2010            bar     do kmz links for files with geotags rather than a direct location google maps url
#                                       _recent.htm file output
#       May 15, 2010            bar     nearby pictures
#                                       let's make that 32 pics, since they include the key picture
#       May 16, 2010            bar     put the arrows on the "recent" pages
#       June 29, 2010           bar     paginate all of 'em in recent list
#       July 3, 2010            bar     allow nearby pictures to go out to 200 miles instead of 20
#       July 8, 2010            bar     make sure the target dir is made when there are shadow images to be written at the top of the logic
#       July 19, 2010           bar     only write the all_picture_locations file once at the end
#       August 14, 2010         bar     fix a problem with --geotracks - could not be in cfg file
#       September 18, 2010      bar     gps point altitude can now be None
#       November 28, 2010       bar     relative url for all icons icon image src
#                                       ooops. no, the url is in the config files
#       December 2, 2010        bar     big-dir htm files
#                                       --ungeotrack
#       May 27, 2011            bar     fix the picture kml insertions to geotracks
#       June 8, 2011            bar     handle when a modification time for a file is before the creation time
#       June 16, 2011           bar     fix ne corner shadow url
#       November 29, 2011       bar     pyflake cleanup
#       --eodstamps--
##      \file
#
#
#       Create thumbnail images from all images in a directory.
#       And write out an HTML file that can be used to see 'em.
#       And create a 'big' sub-dir with the original images in .jpg form.
#
#       See the help_str for more information about this script.
#
#       TODO:
#
#           Screen saver (open a frameless/minimal-framed window in the browser and update the images at random.)
#
#           Put the big pictures in a grey box instead of being stand-alone images. And put the links in.
#
#

import  glob
import  math
import  os
import  random
import  re
import  sys
import  time

have_image_module       = False
try :
    import  Image
    import  ImageColor
    have_image_module   = True
except ImportError :
    pass

import  TZCommandLineAtFile
import  geotag_pictures
import  latlon
import  make_16_arrows
import  onpath
import  output_files
import  replace_file
import  shadow_image
import  tzlib
import  tz_jpg
import  tz_gps
import  tz_google_earth
import  tz_rss


ARROW_SIZE                  = 32                            # hite and width of the arrow images in pixels
ARROW_TEMPLATE_FILE_NAME    = "arrow.png"
ARROW_DIR                   = "arrows"
ALTITUDE_PER_COLOR          = 1300 / 256.0                  # meters per color tick in colored arrows
MAX_NEARBY_PICTURE_DISTANCE = 200.0                         # maximum distance a "nearby" picture can be in nautical miles
MAX_NEARBY_PICTURES         = 32                            # maximum number of "nearby" pictures on a nearby picture page


img_ext_re      = re.compile(r"\.(?:jpg|gif|bmp)$",          re.IGNORECASE)

input_htm_re    = re.compile(r"<IMG\s+SRC\s*=\s*\"([^\"]+)\"\s*>", re.IGNORECASE + re.DOTALL)



if  sys.platform == 'win32' :
    pext = ".exe"
else :
    pext = ""

convert_program         = onpath.on_path("convert" + pext)

gimp_version            = "2.0"
gimp_program            = onpath.on_path("gimp-console" + pext)
if  not gimp_program :
    if  sys.platform == 'win32' :
        gps                 = glob.glob("C:/Program Files/Gimp-" + gimp_version + "/bin/gimp-console-" + gimp_version[0] + "*.exe")
        gps.sort()
        gimp_program        = None
        if  gps :
            gimp_program    = gps[-1]
        pass
    pass

gps                 = glob.glob(os.path.join(os.getenv("HOME", "~"), '.gimp-' + gimp_version[0] + "*"))
gps.sort()
gimp_program        = None
if  gps :
    gimp_version    = re.search(r"/\.gimp-(\d+\.\d+)", gps[-1]).group(1)
    gimp_program    = 'gimp-console'
pass


our_gimp_script = """
;; -*-scheme-*-

(define
    (thumbnail_htm ifn ofn sz alv rta tql fql cmt)
    (let*   (
                (img (car (gimp-file-load RUN-NONINTERACTIVE    ifn ifn)))
                (drw (car (gimp-image-get-active-drawable       img)))
                (qul (if  (> sz 0) tql fql))
            )

        (gimp-image-undo-group-start    img)

        (if (> (car (gimp-drawable-is-rgb drw)) 0)
            ()
            (gimp-image-convert-rgb img)
        )

        (if (>  alv 0)
            (gimp-levels-auto           drw)
        )

        (if (>= rta 0)
            (gimp-image-rotate          img rta)
        )

        (if (> sz 0)
            (let*   (
                        (w   (car (gimp-image-width     img)))
                        (h   (car (gimp-image-height    img)))
                    )

                    (if (> w sz) (gimp-image-scale  img sz (/ (* h sz) w)   ))
            )
        )

        (gimp-image-undo-group-end      img)

        (file-jpeg-save RUN-NONINTERACTIVE img drw ofn ofn qul 0.0 1 0 cmt 0 1 0 2)

        (gimp-displays-flush)
        (gimp-image-delete              img)

        (gimp-quit 1)
    )
)


;;  (gimp-image-flatten img)
;;  (gimp-display-new   img)
;;                  (if (< w h)
;;                      (if (> w sz) (gimp-image-scale  img sz (/ (* h sz) w)   ))
;;                      (if (> h sz) (gimp-image-scale  img    (/ (* w sz) h) sz))
;;                  )

;; eof
""".lstrip()

if  gimp_program :
    gp  = re.sub(r"[\\/]bin[\\/]?$", "", os.path.dirname(gimp_program))
    gp += "/share/gimp/" + gimp_version + "/scripts/thumbnail_htm.scm"
    try :
        tzlib.write_whole_text_file(gp, our_gimp_script)
    except IOError :
        gp  = os.path.join(os.getenv("HOME", "~"), ".gimp-" + gimp_version + "/scripts/thumbnail_htm.scm")
        tzlib.write_whole_text_file(gp, our_gimp_script)
    pass




if  gimp_program    :
    gimp_program    = '"' + gimp_program    + '"'
if  convert_program :
    convert_program = '"' + convert_program + '"'


#   print "Convert:", convert_program
#   print "Gimp:",    gimp_program



def _not_original(fn) :

    if  re.search(r"_original[ _0-9]*\.[^.]*$", fn) != None :
        return(False)

    return(True)




def _base_name(fn) :
    return(os.path.splitext(os.path.basename(fn))[0])



ICON_FN_END     = "__icon"


def custom_base_name(fn, x) :
    ( fn, ext ) = os.path.splitext(fn)
    return(fn + x + ext)


def icon_name(fn) :
    return(custom_base_name(fn, ICON_FN_END))


def kml_name(fn) :
    return(os.path.splitext(fn)[0] + ".kmz")


def nearby_htm_name(fn) :
    return(os.path.splitext(os.path.basename(fn))[0] + "_nearby.htm")



def track_name(t) :
    return(kml_name(tzlib.file_name_able(t.name or t.file_name or "").replace(" ", "_") + "_geotrack.ignore"))




def delete_files(fn) :
    if  os.path.isfile(fn) :
        os.unlink(fn)

    nn  = icon_name(fn)
    if  os.path.exists(nn) :
        os.unlink(nn)

    nn  = kml_name(fn)
    if  os.path.exists(nn) :
        os.unlink(nn)

    pass



def get_create_time(fn) :
    try :
        ct  = os.path.getctime(fn)
    except  ( OSError, IOError, AttributeError, ValueError ) :
        ct  = 1000000000000000000.0
        if  ct <= 0 :
            ct  = 1000000000000000000.0
        pass
    try     :
        mt  = os.path.getmtime(fn)                                  # since the modification time may have been changed to the real time for an image that's been processed, we'll use the earliest time of the two
        if  mt <= 0 :
            mt  = 1000000000000000000.0
        pass
    except  ( OSError, IOError, AttributeError, ValueError ) :
        mt  = 1000000000000000000.0
    t       = min(ct, mt)
    if  t  == 1000000000000000000.0 :
        t   = 0

    return(t)




class   a_thang(object) :


    def __init__(me, program_name) :

        me.program_name                 = program_name

        me.who                          = os.path.basename(program_name)

        me.url_base                     = ""

        me.jobs                         = []                        # multiprocessing jobs

        me.tmp_arrow_file_name          = None
        me.arrow_files                  = {}

        me.process_all_cfg_files        = True
        me.process_master_cfg_file      = True
        me.show_unused_process_opts     = False
        me.do_all                       = False
        me.do_htm                       = True
        me.ignore_underscore_file_name  = False
        me.ignore_underscore_original   = False
        me.strip_trailing_numeric       = False
        me.show_dates                   = False
        me.use_image_magick             = True
        me.do_date_sort                 = 0
        me.time_func                    = time.gmtime

        me.base_file_name_regx          = None

        me.clean                        = False

        me.input_htm_file_name          = ""
        me.input_htm                    = None

        me.htm_file_name                = "index.htm"
        me.big_sub_dir                  = "big"
        me.by_date_x                    = "_by_date"                # e.g. 'index_by_date.htm'
        me.recent_x                     = "_recent"                 # e.g. 'index_recent.htm'

        me.rss_file_name                = "rss.xml"
        me.rss_description              = "Pictures"
        me.rss_depth                    = 20
        me.rss_icon_url                 = None

        me.title                        = "Pictures"

        me.jpeg_comment                 = ""

        me.link                         = None

        me.geometry                     = 200
        me.icon_geometry                = 32
        me.thumb_quality                = 0.9
        me.big_quality                  = 0.9

        me.thumb_labels                 = "file_name"
        me.background_color             = None
        me.font_color                   = None
        me.cell_color                   = None
        me.hz_align                     = "LEFT"
        me.vrt_align                    = "TOP"
        me.cellpadding                  = "5"
        me.cellspacing                  = "0"
        me.table_cols                   = 4
        me.shadow                       = False

        me.ignores                      = {}
        me.rotate_90ccw                 = {}
        me.rotate_90cw                  = {}
        me.normalizers                  = {}
        me.equalizers                   = {}
        me.convert_options              = {}
        me.thumb_convert_options        = {}
        me.labels                       = {}

        me.geotrack_file_names          = {}
        me.near_track_distance          = 0.3                       # within a third  of a nautical mile
        me.near_picture_distance        = 0.1                       # within a 1/10th of a nautical mile

        me.max_nearby_picture_distance  = MAX_NEARBY_PICTURE_DISTANCE
        me.max_nearby_pictures          = MAX_NEARBY_PICTURES

        me.icons_htm_file_name          = "all_icons.htm"

        me.from_files                   = []

        me.kml_points                   = {}                        # indexed by the picture file name - value of the html for the picture at geo-point
        me.files_html                   = {}

        me._do_args(sys.argv)

        me.files_done                   = {}                        # don't write out the same file twice



    def _do_args(me, args) :

        while True :
            oi  = tzlib.array_find(args, "--who")
            if  oi < 0 :    break
            del args[oi]
            me.who                      = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--url_base")
            if  oi < 0 :    break
            del args[oi]
            me.url_base                 = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--no_cfg_files")
            if  oi < 0 :    break
            del args[oi]
            me.process_all_cfg_files    = False

        while True :
            oi  = tzlib.array_find(args, "--no_master_cfg_file")
            if  oi < 0 :    break
            del args[oi]
            me.process_master_cfg_file  = False

        while True :
            oi  = tzlib.array_find(args, "--show_unused_process_opts")
            if  oi < 0 :    break
            del args[oi]
            me.show_unused_process_opts = True

        while True :
            oi  = tzlib.array_find(args, "--do_all")
            if  oi < 0 :    break
            del args[oi]
            me.do_all = True

        while True :
            oi  = tzlib.array_find(args, "--no_htm")
            if  oi < 0 :    break
            del args[oi]
            me.do_htm = False


        while True :
            oi  = tzlib.array_find(args, [ "--use_image_magick", '--use_convert' ] )
            if  oi < 0 :    break
            del args[oi]
            me.use_image_magick = True

        while True :
            oi  = tzlib.array_find(args, "--use_gimp" )
            if  oi < 0 :    break
            del args[oi]
            me.use_image_magick = False


        while True :
            oi  = tzlib.array_find(args, "--clean")
            if  oi < 0 :    break
            del args[oi]
            me.clean   = True
            me.do_all  = True


        while True :
            oi  = tzlib.array_find(args, "--ignore_underscore_file_name")
            if  oi < 0 :    break
            del args[oi]
            me.ignore_underscore_file_name = True


        while True :
            oi  = tzlib.array_find(args, "--ignore_underscore_original")
            if  oi < 0 :    break
            del args[oi]
            me.ignore_underscore_original = True


        while True :
            oi  = tzlib.array_find(args, "--strip_trailing_numeric")
            if  oi < 0 :    break
            del args[oi]
            me.strip_trailing_numeric   = True


        while True :
            oi  = tzlib.array_find(args, "--rename_regx_to")
            if  oi < 0 :    break
            del args[oi]
            me.base_file_name_regx      = re.compile(args.pop(oi), re.IGNORECASE)
            me.base_file_name_to        = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--show_dates")
            if  oi < 0 :    break
            del args[oi]
            me.show_dates               = True

        while True :
            oi  = tzlib.array_find(args, "--do_date_sort")
            if  oi < 0 :    break
            del args[oi]
            me.do_date_sort             = 1

        while True :
            oi  = tzlib.array_find(args, "--rev_date_sort")
            if  oi < 0 :    break
            del args[oi]
            me.do_date_sort             = -1


        while True :
            oi  = tzlib.array_find(args, "--local_time")
            if  oi < 0 :    break
            del args[oi]
            me.time_func                = time.localtime


        while True :
            oi  = tzlib.array_find(args, "--input")
            if  oi < 0 :    break
            del args[oi]
            afn             = args.pop(oi)
            fns             = glob.glob(afn)
            if  len(fns) == 0 :
                print   "No input files:", afn
                sys.exit(103)
            me.from_files  += fns


        while True :
            oi  = tzlib.array_find(args, "--input_htm")
            if  oi < 0 :    break
            del args[oi]
            me.input_htm_file_name = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--htm_name")
            if  oi < 0 :    break
            del args[oi]
            me.htm_file_name    = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--by_date_x")
            if  oi < 0 :    break
            del args[oi]
            me.by_date_x        = args.pop(oi)
            if  me.do_date_sort == 0 :
                me.do_date_sort  = 1
            pass


        while True :
            oi  = tzlib.array_find(args, "--recent_x")
            if  oi < 0 :    break
            del args[oi]
            me.recent_x         = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--rss_name")
            if  oi < 0 :    break
            del args[oi]
            me.rss_file_name    = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--rss_description")
            if  oi < 0 :    break
            del args[oi]
            me.rss_description  = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--rss_depth")
            if  oi < 0 :    break
            del args[oi]
            me.rss_depth        = max(1, int(args.pop(oi)))


        while True :
            oi  = tzlib.array_find(args, "--rss_icon_url")
            if  oi < 0 :    break
            del args[oi]
            me.rss_icon_url     = args.pop(oi)



        while True :
            oi  = tzlib.array_find(args, "--title")
            if  oi < 0 :    break
            del args[oi]
            me.title            = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--link")
            if  oi < 0 :    break
            del args[oi]
            me.link             = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--big_dir")
            if  oi < 0 :    break
            del args[oi]
            me.big_sub_dir         = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--geometry")
            if  oi < 0 :    break
            del args[oi]
            me.geometry = int(args.pop(oi))
            if  me.geometry < 1 :
                print "Bad geometry number", me.geometry
            pass


        while True :
            oi  = tzlib.array_find(args, "--thumb_width")
            if  oi < 0 :    break
            del args[oi]
            me.geometry = int(args.pop(oi))
            if  me.geometry < 1 :
                print "Bad thumbnail width number", me.geometry
            pass


        while True :
            oi  = tzlib.array_find(args, "--thumb_quality")
            if  oi < 0 :    break
            del args[oi]
            me.thumb_quality = float(args.pop(oi))
            if  (me.thumb_quality < 0.0) or (me.thumb_quality > 1.0) :
                print "Bad thumb quality number (0 .. 1.0)", me.thumb_quality
            pass


        while True :
            oi  = tzlib.array_find(args, "--big_quality")
            if  oi < 0 :    break
            del args[oi]
            me.big_quality = float(args.pop(oi))
            if  (me.big_quality < 0.0) or (me.big_quality > 1.0) :
                print "Bad big quality number (0 .. 1.0)", me.big_quality
            pass


        while True :
            oi  = tzlib.array_find(args, "--icon_geometry")
            if  oi < 0 :    break
            del args[oi]
            me.icon_geometry = int(args.pop(oi))
            if  me.icon_geometry < 1 :
                print "Bad icon geometry number", me.icon_geometry
            pass


        while True :
            oi  = tzlib.array_find(args, "--jpeg_comment")
            if  oi < 0 :    break
            del args[oi]
            me.jpeg_comment = args.pop(oi)



        while True :
            oi  = tzlib.array_find(args, "--ignore")
            if  oi < 0 :    break
            del args[oi]
            me.ignores[img_ext_re.sub("", args.pop(oi))] = True

        while True :
            oi  = tzlib.array_find(args, "--rotate_ccw")
            if  oi < 0 :    break
            del args[oi]
            me.rotate_90ccw[img_ext_re.sub("", args.pop(oi))] = True

        while True :
            oi  = tzlib.array_find(args, "--rotate_cw")
            if  oi < 0 :    break
            del args[oi]
            me.rotate_90cw[img_ext_re.sub("", args.pop(oi))] = True

        while True :
            oi  = tzlib.array_find(args, "--normalize")
            if  oi < 0 :    break
            del args[oi]
            me.normalizers[img_ext_re.sub("", args.pop(oi))] = True

        while True :
            oi  = tzlib.array_find(args, "--equalize")
            if  oi < 0 :    break
            del args[oi]
            me.equalizers[img_ext_re.sub("", args.pop(oi))] = True

        while True :
            oi  = tzlib.array_find(args, "--convert")
            if  oi < 0 :    break
            del args[oi]
            nam    = args.pop(oi)
            me.convert_options[img_ext_re.sub("", nam)] = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--thumb_convert")
            if  oi < 0 :    break
            del args[oi]
            nam    = args.pop(oi)
            me.thumb_convert_options[img_ext_re.sub("", nam)] = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--label")
            if  oi < 0 :    break
            del args[oi]
            nam    = args.pop(oi)
            me.labels[img_ext_re.sub("", nam)] = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--background_color")
            if  oi < 0 :    break
            del args[oi]
            me.background_color = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--font_color")
            if  oi < 0 :    break
            del args[oi]
            me.font_color = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--thumb_labels")
            if  oi < 0 :    break
            del args[oi]
            me.thumb_labels = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--thumb_label")           # change meaning of this option !!!!
            if  oi < 0 :    break
            del args[oi]
            me.thumb_labels = args.pop(oi)


        while True :
            oi  = tzlib.array_find(args, "--cell_color")
            if  oi < 0 :    break
            del args[oi]
            me.cell_color = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--cell_padding")
            if  oi < 0 :    break
            del args[oi]
            me.cellpadding = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--cell_spacing")
            if  oi < 0 :    break
            del args[oi]
            me.cellspacing = args.pop(oi)

        while True :
            oi  = tzlib.array_find(args, "--cell_hz_left")
            if  oi < 0 :    break
            del args[oi]
            me.hz_align     = "LEFT"

        while True :
            oi  = tzlib.array_find(args, "--cell_hz_center")
            if  oi < 0 :    break
            del args[oi]
            me.hz_align     = "CENTER"

        while True :
            oi  = tzlib.array_find(args, "--cell_hz_middle")
            if  oi < 0 :    break
            del args[oi]
            me.hz_align     = "MIDDLE"

        while True :
            oi  = tzlib.array_find(args, "--cell_hz_right")
            if  oi < 0 :    break
            del args[oi]
            me.hz_align     = "RIGHT"

        while True :
            oi  = tzlib.array_find(args, "--cell_vrt_top")
            if  oi < 0 :    break
            del args[oi]
            me.vrt_align    = "TOP"

        while True :
            oi  = tzlib.array_find(args, "--cell_vrt_center")
            if  oi < 0 :    break
            del args[oi]
            me.vrt_align    = "CENTER"

        while True :
            oi  = tzlib.array_find(args, "--cell_vrt_middle")
            if  oi < 0 :    break
            del args[oi]
            me.vrt_align    = "MIDDLE"

        while True :
            oi  = tzlib.array_find(args, "--cell_vrt_bottom")
            if  oi < 0 :    break
            del args[oi]
            me.vrt_align    = "BOTTOM"

        while True :
            oi  = tzlib.array_find(args, "--cell_vrt_baseline")
            if  oi < 0 :    break
            del args[oi]
            me.vrt_align    = "BASELINE"

        while True :
            oi  = tzlib.array_find(args, "--columns")
            if  oi < 0 :    break
            del args[oi]
            me.table_cols   = int(args.pop(oi))
            if  me.table_cols < 1 :
                print "Bad table columns number", me.table_cols
            pass

        while True :
            oi  = tzlib.array_find(args, "--shadow")
            if  oi < 0 :    break
            del args[oi]
            me.shadow       = True and have_image_module



        while True :
            oi  = tzlib.array_find(args, "--geotrack")
            if  oi < 0 :    break
            del args[oi]
            me.geotrack_file_names[args.pop(oi)]    = True


        while True :
            oi  = tzlib.array_find(args, "--ungeotrack")
            if  oi < 0 :    break
            del args[oi]
            me.geotrack_file_names[args.pop(oi)]    = False


        while True :
            oi  = tzlib.array_find(args, "--near_track_distance")
            if  oi < 0 :    break
            del args[oi]
            me.near_track_distance                  = float(args.pop(oi))


        while True :
            oi  = tzlib.array_find(args, "--near_picture_distance")
            if  oi < 0 :    break
            del args[oi]
            me.near_picture_distance                = float(args.pop(oi))


        while True :
            oi  = tzlib.array_find(args, "--max_nearby_pictures")
            if  oi < 0 :    break
            del args[oi]
            me.max_nearby_pictures                  = int(args.pop(oi))


        while True :
            oi  = tzlib.array_find(args, "--max_nearby_picture_distance")
            if  oi < 0 :    break
            del args[oi]
            me.max_nearby_picture_distance          = float(args.pop(oi))


        pass



    def rename_base_file_name(me, base_name) :
        if  me.base_file_name_regx != None :
            base_name   = me.base_file_name_regx.sub(me.base_file_name_to, base_name)


        return(base_name)



    def add_files_from_dir(me, from_dir) :
        if  os.path.isdir(from_dir) or (len(glob.glob(os.path.join(from_dir, "*"))) > 0) :
            if  me.process_all_cfg_files :
                #
                #
                #   Read any .cfg files in the 'from' directory as command line files.
                #
                #
                cfg_fn      = os.path.join(from_dir, '*.cfg')
                if  len(glob.glob(cfg_fn)) > 0 :
                    sys.argv.insert(0, '@' + cfg_fn)
                    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)
                    me._do_args(sys.argv)

                    if  len(sys.argv) != 1 :
                        print "Unknown command line argument in", cfg_fn
                        print sys.argv
                        sys.exit(254)
                    pass
                pass
            elif  me.process_master_cfg_file :
                #
                #
                #   Read the master command line file from the 'from' directory, if the file exists.
                #
                #
                cfg_fn      = os.path.join(from_dir, 'thumbnail_htm.cfg')
                if  os.path.isfile() :
                    sys.argv.insert(0, '@' + cfg_fn)
                    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)
                    me._do_args(sys.argv)

                    if  len(sys.argv) != 1 :
                        print "Unknown command line argument in", cfg_fn
                        print sys.argv
                        sys.exit(254)
                    pass
                pass

            me.from_files += filter(lambda s : img_ext_re.search(s), glob.glob(os.path.join(from_dir, "*.*")))

        else :
            me.from_files += glob.glob(from_dir)

        pass




    def read_input_htm_file(me) :
        """
            If we are to convert an HTML file rather than generate our own, then we'll just do the images referenced in the input HTM file.
        """

        if  me.input_htm_file_name :
            try :
                fi          = open(me.input_htm_file_name)
            except :
                fi          = open(os.path.join(from_dir, os.path.normpath(me.input_htm_file_name)))

            me.input_htm    = fi.read()
            fi.close()

        pass



    def ignore_images_not_in_htm_file(me) :
        if  me.input_htm_file_name :
            g = input_htm_re.findall(me.input_htm)

            if  len(g) == 0 :
                print "No images in input HTM file:", me.input_htm_file_name
                sys.exit(105)

            me.from_files = map(lambda s : os.path.join(from_dir, os.path.normpath(s)), filter(lambda s : img_ext_re.search(s), g))
        pass



    def file_name_to_html_text(me, fn) :
        """
            Convert a file name to descriptive text that'll go in the HTML.

            That is, strip trailing numerics and such-like from the display name.
        """

        fnns = _base_name(fn)
        if  re.search(r"_\d\d$", fnns) :
            if  me.strip_trailing_numeric and not re.search(r"(19|20)\d\d_\d\d_\d\d$", fnns):
                fnns = re.sub(r"_\d\d$",        "", fnns)
                fnns = fnns.strip()
            pass
        elif    me.strip_trailing_numeric :
            fnns = re.sub(r"\b[_\d \-\.]+$",    "", fnns)
            fnns = fnns.strip()

        fnns = re.sub(r"_", " ", fnns)
        fnns = fnns.strip()

        return(fnns)







    def _get_picture_when(me, fn, jpg = None) :
        mtime           = 0.0
        if  not me.clean :

            try :
                jpg     = jpg or tz_jpg.a_jpg(fn)
                mtime   = jpg.picture_taken_time(dflt = mtime, time_func = me.time_func)
            except :
                mtime   = 0.0
            mtime       = mtime or get_create_time(fn)

        return(mtime)





    def _get_a_point_from_file(me, fn, jpg = None) :
        try :
            jpg     = jpg or tz_jpg.a_jpg(fn)

            exif    = jpg.get_exif()

            try :
                lat = exif.get_latitude()
            except :
                lat = None

            try :
                lon = exif.get_longitude()
            except :
                lon = None

            try :
                alt = exif.get_altitude()
            except :
                alt = None

            if  (lat != None) and (lon != None) :
                t   = me._get_picture_when(fn, jpg)

                return(tz_gps.a_point(name = me.file_name_to_html_text(os.path.basename(fn)), when = float(t), lat = lat, lon = lon, altitude = alt))

            pass
        except :
            # print "except point", fn
            # e       = sys.exc_info()
            # print   e[0], e[1], e[2]
            pass


        return(None)




    def _file_url(me, fn) :
        return(os.path.join(me.url_base, fn).replace("\\", "/"))





    ############################################################
    #
    #
    #       GPS track inclusion
    #
    #
    #


    class   a_geotrack_picture(object) :
        def __init__(me, name, kml = None) :
            me.name = name  or ""
            me.kml  = kml   or ""
        pass        # a_geotrack_picture


    class   a_geotrack(object) :

        def __init__(me) :
            me.ome          = me
            me.pictures     = {}                                # indexed by file name, the a_geotrack_picture's known to be near one of our tracks
            me.url          = ""                                # this will get some name later
            me.tracks       = []

        def add_track(me, track) :
            me.tracks.append(track)

        def add(me, ome) :
            ome.ome     = me                                    # point him to us
            me.tracks  += ome.tracks                            # take his tracks
            ome.tracks  = []

        def me(me) :                                            # return the a_geotrack that has this a_geotrack's tracks
            while not tzlib.same_object(me, me.ome) :
                me  = me.ome
            return(me)

        pass        # a_geotrack


    def add_geofile(me, file_name) :
        tracks  = tz_gps.tracks_from_file(file_name)

        for t in tracks :
            gts     = {}
            for p in t.points :
                fp  = me.trk_pts.find_nearest_point(p)
                if  fp and (fp.flat_distance_from(p) < me.near_track_distance) :
                    gt              = fp._a_geotrack.me()       # we've found a track with a point near one of ours, so add it to the ones we'll combine
                    gts[repr(gt)]   = gt
                pass

            if  gts     :
                gts     = gts.values()                          # get all the a_geotrack's we're near
                gt      = gts.pop()
                for ogt in gts :
                    gt.add(ogt)                                 # move the tracks of all of the a_geotrack's we bridged to one of 'em
                pass
            else        :
                gt      = me.a_geotrack()
                me.gts.append(gt)

            for p in t.points :
                cp  = me.trk_pts.include_point(p)
                if  cp :
                    cp._a_geotrack  = gt                        # reference the a_geotrack that has the point
                pass

            gt.add_track(t)                                     # put our track in the proper a_geotrack

        return(tracks)



    def add_geotrack_files(me) :
        me.trk_pts  = tz_gps.a_point_combiner(me.near_track_distance)
        me.gts      = []                                        # all the a_geotrack's we make

        fls         = {}
        ufls        = {}
        for fn in me.geotrack_file_names.keys() :
            fna     = glob.glob(fn)
            if  not fna :
                print "No file found for:", fn
            else :
                for ffn in fna :
                    if  me.geotrack_file_names[fn] :
                        fls[os.path.abspath(ffn)]    = True
                    else :
                        ufls[os.path.abspath(ffn)]   = False
                    pass
                pass
            pass

        for fn in ufls.keys() :
            if  fn in fls :
                del(fls[fn])
            pass

        kys         = fls.keys()
        kys.sort()
        for fn in kys :
            if  not me.add_geofile(fn) :
                print "No tracks found in:", fn
            pass

        gts = []
        for gt in me.gts :
            if  gt.tracks :
                gt.tracks   = tz_gps.sorted_tracks_by_when(gt.tracks)
                gt.url      = track_name(gt.tracks[0])
                gts.append(gt)
            pass
        me.gts  = gts

        #
        #   Make the look-up thing for pictures' nearby points to be found.
        #
        if  me.near_track_distance != me.near_picture_distance :
            me.trk_pts  = tz_gps.a_point_combiner(me.near_picture_distance)
            for gt in me.gts :
                for t in gt.tracks :
                    for p in t.points :
                        cp  = me.trk_pts.include_point(p)
                        if  cp :
                            cp._a_geotrack  = gt                # reference the a_geotrack that has the point
                        pass
                    pass
                pass
            pass

        pass


    def write_tracks(me) :
        for gt in me.gts :
            if  gt.pictures and gt.tracks :
                tracks  = tz_gps.color_tracks(gt.tracks)

                s       = tz_gps.kml_header(os.path.basename(me.program_name), me._file_url(os.path.basename(gt.url)), me.who)

                for p in gt.pictures.values() :
                    s  += p.kml

                ti      = 0
                for t in tracks :
                    ti += 1
                    s  += tz_gps.kml_timed_route(t.points, route_number = ti, number_of_routes = len(tracks), name = t.name)

                s      += tz_gps.kml_trailer()

                tz_gps.write_kml_or_kmz_string_to_file(os.path.join(me.to_dir, gt.url), s)
            pass
        pass


    def erase_tracks(me) :
        for gt in me.gts :
            fn  = os.path.join(me.to_dir, gt.url)
            if  os.path.exists(fn) :
                os.unlink(fn)
            pass
        pass






    ############################################################
    #
    #
    #       Image conversion
    #
    #
    #


    def _make_convert_command_line(me, fn, base_fn, to_name, geometry, convert_options) :

        if  me.jpeg_comment :
            me.jpeg_comment = ""
            print "Did you mean to use Gimp - there is a 'jpeg_comment' specified but I will not put it in the pictures!"

        cmd = convert_program + ' "%s"' % ( fn )

        if  me.rotate_90ccw.has_key(base_fn) :
            cmd += " -rotate -90"

        if  me.rotate_90cw.has_key(base_fn) :
            cmd += " -rotate 90"

        if  me.normalizers.has_key(base_fn) :
            cmd += " -normalize"

        if  me.equalizers.has_key(base_fn) :
            cmd += " -equalize"

        if                   me.convert_options.has_key(base_fn) :
            cmd += " " +     me.convert_options[base_fn]

        if  convert_options :
            if               convert_options.has_key(base_fn) :
                cmd += " " + convert_options[base_fn]

        if  geometry :
            cmd += " -geometry " + str(geometry)

        cmd += ' "' + to_name + '"'

        return(cmd)



    def _make_gimp_command_line(me, fn, base_fn, to_name, geometry, convert_options) :
        if  len(me.convert_options) > 0 :
            del(me.convert_options)
            me.convert_options = {}
            print "Did you mean to use 'convert' - there are 'convert_options'!"
        if  len(me.thumb_convert_options) > 0 :
            del(me.thumb_convert_options)
            me.thumb_convert_options = {}
            print "Did you mean to use 'convert' - there are 'thumb_convert_options'!"

        normalize   = 0
        if  me.equalizers.has_key(base_fn) :    normalize = 1
        if  me.normalizers.has_key(base_fn) :   normalize = 1

        rotation        = -1
        if      me.rotate_90ccw.has_key(base_fn) :
            rotation    = 2
        elif    me.rotate_90cw.has_key(base_fn) :
            rotation    = 0


        cmd     = gimp_program + " --no-interface"
        if  sys.platform == 'win32' :
            oqc = "\""
            sqc = "\\\""
            efn      = re.sub(r"\\", "/", fn);
            eto_name = re.sub(r"\\", "/", to_name);
        else :
            oqc = "'"
            sqc = "\""
            efn = fn
            eto_name = to_name


        cmd    += " --batch"
        #        cmd    += " %s%s%s"  % ( oqc, our_gimp_script, oqc )
        cmd    += " " + oqc
        cmd    += '(thumbnail_htm %s%s%s %s%s%s %i %i %i %.2f %.2f %s%s%s)' % ( sqc, efn, sqc, sqc, eto_name, sqc, geometry, normalize, rotation, me.thumb_quality, me.big_quality, sqc, me.jpeg_comment, sqc )
        cmd    += oqc

        # print cmd

        return(cmd)



    def _convert_image_to_destination(me, fn, base_fn, to_name, geometry, convert_options) :
        if  me.use_image_magick :

            cmd = me._make_convert_command_line(fn, base_fn, to_name, geometry, convert_options)

        else :

            cmd = me._make_gimp_command_line(   fn, base_fn, to_name, geometry, convert_options)


        if  False :
            print
            print cmd
            print
            # sys.exit(1)

        os.system(cmd)                                                          # convert the image to a thumbnail or an image-processed version of the input image

        if  not os.path.isfile(to_name) :
            print "thumbnail_htm.py could not convert", fn, "to", to_name
            sys.exit(104)

        pass




    def _create_image(me, fn, base_fn, to_dir, to_name, geometry, convert_options, mtime) :
        if  to_name not in me.files_done :
            me.files_done[to_name]  = True

            if  not os.path.isdir(to_dir) :
                os.makedirs(to_dir)

            me._convert_image_to_destination(fn, base_fn, to_name, geometry, convert_options)

            # print "mtime", time.asctime(time.localtime(mtime)), fn

            if  not me._get_a_point_from_file(to_name) :                                # convert did not preserve the geotag in a way that tz_jpg can understand, but exiftool can (it was a format==zero exif item)
                p   = me._get_a_point_from_file(fn)
                if  p :
                    jpg = tz_jpg.a_jpg(to_name)
                    geotag_pictures.geotag_jpg(jpg, p.lat, p.lon, p.altitude)
                    jpg.save(to_name)
                pass

            os.utime(to_name, ( os.path.getatime(fn), int(mtime) ) )                    # set the file date/time to that of the source file so that the files can be sorted by date
        pass




    def insure_images_exist(me, to_dict, fn, base_fn, to_name, bg_name) :
        """
            This is where we create the thumbnails, icons, and big images if they don't already exist.
        """

        cnt     = 0

        mtime   = 0

        btname  = _base_name(to_name)
        ic_name = icon_name(to_name)

        do_big  = False
        if  me.do_all or (not to_dict.has_key(btname)) :                                                                            # can really mess up if we are setting the dates to camera-GMT    or (os.path.getmtime(fn) > os.path.getmtime(to_name)) :
            to_dict[btname]     = True                                                                                              # don't make the images twice - since we are called twice for each image, at least
            do_big              = True                                                                                              # so that deleting the thumbnail before running causes the big image and icon to be remade

            # print "insuring", to_name

            mtime               = mtime or me._get_picture_when(fn) or get_create_time(fn)
            me._create_image(fn, base_fn, me.to_dir,  to_name,  me.geometry,        me.thumb_convert_options, mtime)                # make the thumbnail image we were run to make

            cnt                += 1                                                                                                 # used for quick debugging purposes


        if  me.do_all or do_big or (not os.path.exists(bg_name)) :                                                                  # can really mess up if we are setting the dates to camera-GMT    or (os.path.getmtime(fn) > os.path.getmtime(bg_name)):
            do_big      = True
            mtime       = mtime or me._get_picture_when(fn) or get_create_time(fn)
            me._create_image(fn, base_fn, me.big_dir, bg_name,  0,                  None,                     mtime)                # make the big image

        if  me.do_all or do_big or (not os.path.exists(ic_name)) :
            mtime       = mtime or me._get_picture_when(fn) or get_create_time(fn)
            me._create_image(fn, base_fn, me.to_dir,  ic_name,  me.icon_geometry,   None,                     mtime)                # make the icon image (used by kml files and by the all_icons.htm table page)

        return(cnt)





    ############################################################
    #
    #
    #
    #       HTML output
    #
    #
    #
    #


    kml_description = """<![CDATA[%s<P><A HREF="%s" ALT="%s"><IMG SRC="%s" ALT="%s"></A>]]>"""


    def _create_kml(me, ff) :

        if  not me.url_base :
            return(None)

        kfn = kml_name(ff.file_name)

        if  kfn not in me.files_done :
            me.files_done[kfn]  = True
            p   = ff.where
            if  p :
                iurl    = None
                if  os.path.isfile(icon_name(ff.file_name)) :
                    iurl    = me._file_url(icon_name(os.path.basename(ff.file_name)))

                s   = tz_gps.kml_header(os.path.basename(me.program_name), me._file_url(os.path.basename(kfn)), me.who)

                ts  = "<BR>"
                ts += p.time_description()
                if  ts :
                    ts     += "<BR>"
                ts += p.where_description()

                ds  = me.kml_description % ( me.file_name_to_html_text(ff.file_name) + ts, me._file_url(os.path.join(me.big_sub_dir, _base_name(ff.file_name) + ".jpg")), me.file_name_to_html_text(ff.file_name), me._file_url(os.path.basename(ff.file_name)), me.file_name_to_html_text(ff.file_name) )

                ps  = p.to_kml(is_waypoint = True, icon_image_url = iurl, description = ds)

                me.kml_points[ff.file_name] = ps            # remember the point's kml so it can be put in an all_picture_locations file
                s  += ps

                s  += tz_gps.kml_trailer()

                # print "p.when", time.asctime(time.localtime(p.when)), ff.file_name, p.when
                p.when  = p.when or get_create_time(ff.file_name)

                tz_gps.write_kml_or_kmz_string_to_file(kfn, s, when = p.when)
                os.utime(kfn, ( os.path.getatime(ff.file_name), int(p.when) ) )

            elif  os.path.exists(kfn) :
                os.unlink(kfn)
            pass
        pass


    def _write_kml_points(me, kfn) :

        s           = tz_gps.kml_header(os.path.basename(me.program_name), me._file_url(os.path.basename(kfn)), me.who)
        for ps in me.kml_points.values() :
            s      += ps
        s          += tz_gps.kml_trailer()

        tz_gps.write_kml_or_kmz_string_to_file(os.path.join(me.to_dir, kfn), s)



    def make_various_names(me, fn) :
        fnns    = me.file_name_to_html_text(fn)

        bfn     = os.path.basename(fn)

        bn      = ""

        ext     = "jpg"

        bsn     = _base_name(bfn)

        bjpg = os.path.join(me.big_dir,   bsn + ".jpg")
        if  os.path.exists(bjpg) :
            bn  = me.big_sub_dir + "/" +  bsn + ".jpg"

        return( fnns, bfn, bn, bjpg, bsn, ext )


    def put_image_refs_in_to_existing_html(me, htm_fn) :
        """
            Put the files in to a template .htm file.
        """

        def _img_src_html(g) :
            fn      = g.group(1)

            base_fn = _base_name(fn)

            fn      = os.path.join(me.to_dir,  base_fn + ".jpg")

            ( fnns, bfn, bn, bjpg, bsn, ext ) = me.make_various_names(fn)

            wh          = ""
            if  have_image_module :
                ii      = Image.open(fn)
                (w, h)  = ii.size
                wh      = ' WIDTH="%u" HEIGHT="%u"' % ( w, h )

            return('<A HREF="%s"><IMG SRC="%s" ALT="%s"%s></A>' % ( bn, bfn, fnns, wh ) )


        tmp_fn  = htm_fn + ".tmp"
        bak_fn  = htm_fn + ".bak"

        fo      = open(tmp_fn, "w")

        ohtml   = input_htm_re.sub(_img_src_html, me.input_htm)

        fo.write(ohtml)
        fo.close()
        replace_file.replace_file(htm_fn, tmp_fn, bak_fn)




    def html_details_for_file(me, ff, arrow_from_point = None, big = False, htm_fn = "") :
        ( fnns, bfn, bn, bjpg, bsn, ext )   = me.make_various_names(ff.file_name)

        spath       = "../" if big else ""

        txt         = "<BR>" + fnns  + "(" + ext + ")"
        if  not me.thumb_labels :
            txt     = ""

        ea          = "" if big else "</A>"
        if  me.labels.has_key(bsn) :
            txt    += ea + "<BR>" + me.labels[bsn]
        else :
            txt    += ea

        wh          = ""
        if  have_image_module :
            fn      = bjpg if big else ff.file_name
            ii      = Image.open(fn)
            (w, h)  = ii.size
            wh      = ' WIDTH="%u" HEIGHT="%u"' % ( w, h )

        me._create_kml(ff)
        p           = ff.where
        if  p       :
            txt    += '<BR>Google '

            cp      = me.trk_pts.find_nearest_point(p)                                          # is the picture near a known track?
            if  cp  :
                gt  = cp._a_geotrack.me()                                                       # find the a_geotrack that knows the track(s)
                gt.pictures[bsn]    = me.a_geotrack_picture(bsn, me.kml_points[ff.file_name])   # add the picture and its kml string to the track(s)

                txt    += " GeoTrack "

                gmurl   = "http://maps.google.com/maps?t=h&q=" + me._file_url(gt.url).replace("/", "%2f")           # &t=h (satellite)   &t=p (terrain)   &t= (map)     &z=zoom_level    &lci=lmc:panoramio (pictures) &lci=lmc:wikipedia_en  or  &lci=lmc:panoramio,lmc:wikipedia_en
                txt    += ' <A HREF="'         + gmurl   + '" ALT="Google Maps KMZ file">Maps</A>'
                txt    += ' <A HREF="' + spath + gt.url  + '" ALT="Google Earth KMZ file">Earth (.kmz)</A>'
            else :
                ll_url  = tz_google_earth.google_maps_lat_lon_url(p.lat, p.lon, description = fnns)                 # has the marker, which has the lat/lon/alt, but no picture
                ll_url  = "http://maps.google.com/maps?t=h&z=15&q=" + me._file_url(os.path.basename(kml_name(ff.file_name))).replace("/", "%2f")     # to get the picture, we must have a kml/z file to reference
                txt    += ' <A HREF="' + ll_url  + '" ALT="Google Maps Location">Maps</A>'

                kml_url = kml_name(spath + bsn + ".ignore")
                txt    += ' <A HREF="' + kml_url + '" ALT="Google Earth Location KMZ file">Earth (.kmz)</A>'
            pass


        if  ff.nearby   :
            txt        += ' <A HREF="' + spath + nearby_htm_name(ff.file_name) + '" ALT="Nearby pictures">Nearby Pictures</A>'
            if  arrow_from_point :
                d       = arrow_from_point.distance_from(ff.where) * latlon.metersPerNauticalMile
                if  d  >= 12 :                                                              # don't make arrows for pictures right here at this picture.
                    adir                = os.path.join(me.to_dir, ARROW_DIR)
                    if  not os.path.isdir(adir) :
                        os.makedirs(adir)

                    if  not me.tmp_arrow_file_name  :
                        me.tmp_arrow_file_name  = make_16_arrows.make_source_file(os.path.join(adir, ARROW_TEMPLATE_FILE_NAME))

                    #
                    #               Note: the rounding leads to 17*9*9*6=8262 possible arrow images. A lot, but doable, and unlikely to CRC collide, we hope.
                    #               !!!! Should be done in .js in browser.
                    #
                    arrow_angle         = arrow_from_point.radian_angle_to(ff.where) / latlon.rad
                    arrow_angle         = int(round(arrow_angle / 22.5) * 22.5)
                    try :
                        arrow_rgb       = [ max(0, min(255, (ff.where.altitude - arrow_from_point.altitude) / ALTITUDE_PER_COLOR)), max(0, min(255, (arrow_from_point.altitude - ff.where.altitude) / ALTITUDE_PER_COLOR)), 0 ]
                    except TypeError    :
                        arrow_rgb       = [ 0, 0, 0 ]
                    for ri, c in enumerate(arrow_rgb) :
                        arrow_rgb[ri]   = min(255, int(round(c / 32.0) * 32.0))
                    arrow_scale         = max(0.5, min(1.0, math.log(d) / math.log(16000)))
                    arrow_scale         = round(arrow_scale * 10.0) / 10.0

                    afn                     = make_16_arrows.make_arrow_file_name(os.path.join(adir, ARROW_TEMPLATE_FILE_NAME), arrow_angle, size = ARROW_SIZE, scale = arrow_scale, arrow_rgb = arrow_rgb)
                    if  not os.path.isfile(afn) and (afn not in me.arrow_files) :
                        me.arrow_files[afn] = True
                        cmd                 = make_16_arrows.get_run_convert_cmd(me.tmp_arrow_file_name, afn,                   arrow_angle, size = ARROW_SIZE, scale = arrow_scale, arrow_rgb = arrow_rgb)
                        me.jobs.append(make_16_arrows.run_cmd(cmd))
                        # print "@@@@", len(me.jobs), afn
                    txt                    += ' <img src="%s" ALT="Arrow %d" style="vertical-align:middle;">' % ( os.path.join(ARROW_DIR, os.path.basename(afn)), arrow_angle )
                pass
            pass


        date_str        = ""
        if  me.show_dates :
            date_str    = '<BR><SMALL>%s</SMALL>' % ( time.strftime("%a %x", time.localtime(ff.when)) )

        anc             = '<A HREF="%s" NAME="%s">' % ( ((spath + htm_fn + "#%s" % bsn) if big else os.path.splitext(bn)[0] + ".htm"), bsn )


        img             = '<IMG SRC="%s" ALT="%s"%s style="border-style: none" BORDER="0">' % ( bfn, fnns, wh )

        if  me.shadow :
            shadow      = """
                          <TABLE BORDER="0" CELLSPACING="0" CELLPADDING="0">

                              <TR>
                                  <TD>%s%s</A></TD>
                                  <TD WIDTH="%u"  BACKGROUND="%s%s_east%s" VALIGN="TOP" ><img src="%s%s_ne%s" WIDTH="%u" HEIGHT="%u"></TD>
                              </TR>

                              <TR HEIGHT="%u">
                                  <TD             BACKGROUND="%s%s_south%s" ALIGN="LEFT"><img src="%s%s_sw%s" WIDTH="%u" HEIGHT="%u"></TD>
                                  <TD WIDTH="%u"  BACKGROUND="%s%s_se%s"></TD>
                              </TR>

                          </TABLE>
                          """ % (
                                    anc,
                                    img,
                                           me.shadow_size,
                                    spath, me.shadow_fn,   me.shadow_ext,
                                    spath, me.shadow_fn,   me.shadow_ext,
                                           me.shadow_size, me.shadow_size,
                                           me.shadow_size,
                                    spath, me.shadow_fn,   me.shadow_ext,
                                    spath, me.shadow_fn,   me.shadow_ext,
                                           me.shadow_size, me.shadow_size,
                                           me.shadow_size,
                                    spath, me.shadow_fn,   me.shadow_ext
                                )
            img         = tzlib.multiline_strip(shadow) + anc
        else :
            img         = anc + img


        return( ( img, txt, date_str ) )



    def write_big_htms(me, htm_fn, to_files)    :
        if  to_files    :
            nearbys     = []
            for fi, ff in enumerate(to_files) :
                ( fnns, bfn, bn, bjpg, bsn, ext )   = me.make_various_names(ff.file_name)
                ( img, txt, date_str )              = me.html_details_for_file(ff, big = True, htm_fn = os.path.basename(htm_fn))

                bhtm_fn = os.path.splitext(bjpg)[0] + ".htm"

                fo      = output_files.a_file(bhtm_fn)

                fo.write("""
                         <body %s>

                         """ % me.cell_color
                        )

                fo.write(txt + "\n")
                fo.write(date_str + "\n")

                if  len(to_files) > 1 :
                    fo.write("<p>\n")
                    phtm_fn = me.make_various_names(to_files[fi - 1 if fi                     else -1].file_name)[4] + ".htm"
                    nhtm_fn = me.make_various_names(to_files[fi + 1 if fi + 1 < len(to_files) else  0].file_name)[4] + ".htm"
                    nb      = ""
                    if  ff.nearby and (len(ff.nearby) > 1) :
                        nbf = None
                        nif = -1
                        for nf in ff.nearby[1:] :
                            ni  = tzlib.array_find(nearbys, nf)
                            if  ni < 0 :
                                nbf = nf
                                break
                            if  (not nbf) or (ni < nif) :
                                nbf = nf
                                nif = ni
                            pass
                        if  nif >= 0 :
                            random.shuffle(nearbys)
                        ni  = tzlib.array_find(nearbys, nbf)
                        if  ni >= 0 :
                            del(nearbys[ni])
                        nearbys.append(nbf)
                        ni  = tzlib.array_find(nearbys, ff)
                        if  ni >= 0 :
                            del(nearbys[ni])
                        nearbys.append(ff)
                        nb  = """ <a href="%s">Close-ish</a>""" % ( me.make_various_names(nbf.file_name)[4] + ".htm",  )
                    fo.write("""
                             <p>
                             <a href="%s">Previous</a> <a href="%s">Next</a>%s
                             <p>
                             """ % ( phtm_fn, nhtm_fn, nb )
                            )
                    pass

                fo.write(img + "</A>\n")

                fo.write("""
                         </body>
                         """
                        )
                fo.close()
            pass
        pass



    def html_for_file(me, ff, arrow_from_point = None) :
        ( img, txt, date_str )  = me.html_details_for_file(ff, arrow_from_point = arrow_from_point)
        htm             = '<TD ALIGN="%s" VALIGN="%s" %s>%s%s%s</TD>' % ( me.hz_align, me.vrt_align, me.cell_color, img, txt, date_str )

        return(htm)



    def write_htm(me, htm_fn, to_files, others, rss_url, icn_url, title = None, arrow_from_point = None, tot_image_count = -1) :
        tmp_fn      = htm_fn + ".tmp"
        bak_fn      = htm_fn + ".bak"
        title       = title or me.title

        hdr         = ""
        hdr        += "<HTML>"                                                                                              + "\n"
        hdr        += "<HEAD>"                                                                                              + "\n"
        if  rss_url :
            hdr    += '<link rel="alternate" type="application/rss+xml" title="RSS" href="%s" />' % rss_url                 + "\n"
        hdr        += "<TITLE>" + title + "</TITLE>"                                                                        + "\n"
        hdr        += "</HEAD>"                                                                                             + "\n"
        hdr        += "<BODY %s>" % ( me.background_color )                                                                 + "\n"
        if  me.font_color :
            hdr    += me.font_color                                                                                         + "\n"
        hdr        += "<H2>"          + title + "</H2>"                                                                     + "\n"

        hdr        += "<HR>"                                                                                                + "\n"
        ts          = ""
        for other in others :
            hdr    += "<SMALL><A HREF='%s'>%s</A></SMALL>" % ( os.path.basename(other.file_name), other.name )              + "\n"
            ts      = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|"


        if  rss_url :
            hdr    += ts + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
            rssiurl = me.rss_icon_url or ""
            if  rssiurl :
                rssiurl = '<IMG SRC="%s" ALT="RSS Feed" style="border-style: none" BORDER="0"> ' % rssiurl
            hdr    += "<SMALL><A HREF='%s'>%sRSS Feed</A></SMALL>" % ( rss_url, rssiurl )                                   + "\n"
            ts      = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|"

        if  icn_url :
            hdr    += ts + "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
            hdr    += "<SMALL><A HREF='%s'>All Icons</A></SMALL>" % ( icn_url )                                             + "\n"
            ts      = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|"


        txt         = ""

        txt        += "<HR>"                                                                                                + "\n"

        txt        += '<TABLE CELLPADDING="%s" CELLSPACING="%s">' % ( me.cellpadding, me.cellspacing )                      + "\n"
        col         = 0
        erow        = ""

        image_count = 0

        for ff in to_files :
            if  (col % me.table_cols) == 0 :
                txt    += erow                                                                                              + "\n"
                txt    += "<TR>"                                                                                            + "\n"
                erow    = "</TR>"
            col        += 1

            me.files_html[ff.file_name]   = me.files_html.get(ff.file_name, None) or me.html_for_file(ff, arrow_from_point = arrow_from_point)

            txt        += me.files_html[ff.file_name]                                                                       + "\n"

            image_count        += 1


        txt    += erow                                                                                                      + "\n"
        txt    += "</TABLE>"                                                                                                + "\n"
        txt    += "<HR><HR>"                                                                                                + "\n"
        txt    += "<SMALL>"                                                                                                 + "\n"
        txt    += os.path.basename(htm_fn)                                                                                  + "\n"
        txt    +=                                                    "<BR>"                                                 + "\n"
        txt    += "%u images"   % ( image_count )                                                                           + "\n"
        if  tot_image_count     >   image_count :
            txt    += " of %u"  % ( tot_image_count )                                                                       + "\n"
        txt        +=                                                "<BR>"                                                 + "\n"
        if  me.link :
            txt    += me.link                                                                                               + "\n"
            txt    +=                                                "<BR>"                                                 + "\n"
        txt    += time.asctime()                                                                                            + "\n"
        txt    += "</SMALL>"                                                                                                + "\n"
        txt    += "<HR><HR>"                                                                                                + "\n"
        if  me.font_color :
            txt    += "</FONT>"                                                                                             + "\n"
        txt    += "</BODY></HTML>"                                                                                          + "\n"


        if  len(me.kml_points) :
            kfname  = kml_name("all_picture_locations.ignore")
            hdr    += ts + '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<SMALL><A HREF="%s" ALT="Google Earth KML for all geocoded pictures">GoogleEarth all picture locations</A></SMALL>' % ( kfname )   + "\n"

        txt     = hdr + txt

        fo      = open(tmp_fn, "w")
        fo.write(txt)
        fo.close()

        replace_file.replace_file(htm_fn, tmp_fn, bak_fn)




    def write_rss(me, rss_fn, to_files, htm_fn) :

        tmp_fn  = rss_fn + ".tmp"
        bak_fn  = rss_fn + ".bak"

        link    = me._file_url(os.path.basename(htm_fn))
        rss     = tz_rss.a_channel(title = me.title, link = link, description = me.rss_description, url = me._file_url(os.path.basename(rss_fn)))

        if  to_files :
            ff              = to_files[0]
            rss.image       = tz_rss.an_image(title = me.file_name_to_html_text(ff.file_name), link = link,   url = me._file_url(os.path.basename(icon_name(ff.file_name))))
            rss.pubDate     = rss.date_str(ff.when)

        shadow              = me.shadow
        me.shadow           = False
        for ff in to_files[:me.rss_depth] :
            ds              = ""
            ds             += '<TABLE CELLPADDING="%s" CELLSPACING="%s">' % ( me.cellpadding, me.cellspacing )                          + "\n"
            ds             += "<TR>"                                                                                                    + "\n"
            ds             += me.html_for_file(ff)
            ds             += "</TR>"                                                                                                   + "\n"
            ds             += "</TABLE>"                                                                                                + "\n"

            i               = tz_rss.an_item(title = me.file_name_to_html_text(ff.file_name), link = me._file_url(os.path.basename(htm_fn)) + "#" + _base_name(os.path.basename(ff.file_name)), description = ds)
            i.pubDate       = i.date_str(ff.when)
            rss.items.append(i)
        me.shadow           = shadow

        fo      = open(tmp_fn, "w")
        fo.write(str(rss))
        fo.close()

        replace_file.replace_file(rss_fn, tmp_fn, bak_fn)



    def write_icons_htm(me, icn_fn, to_files, htm_fn) :

        tmp_fn  = icn_fn + ".tmp"
        bak_fn  = icn_fn + ".bak"

        hdr     = """<HTML>
                  <HEAD>
                  <TITLE>%s Icons</TITLE>
                  </HEAD>
                  <BODY %s>%s
                  <H2>%s</H2>
                  <P><HR><P>
                  <A HREF="%s">%s</A>
                  <P><HR><P>
                  """               % ( me.title, me.background_color, me.font_color or "", me.title, os.path.basename(htm_fn), os.path.basename(htm_fn) )

        bdy         = '<TABLE CELLPADDING="%s" CELLSPACING="%s">' % ( 2, 0 )                                                + "\n"
        col         = 0
        erow        = ""
        image_count = 0

        cols        = me.table_cols * 5

        for ff in to_files :
            if  os.path.isfile(icon_name(ff.file_name)) :
                if  (col % cols) == 0 :
                    bdy    += erow                                                                                          + "\n"
                    bdy    += "<TR>"                                                                                        + "\n"
                    erow    = "</TR>"
                col        += 1

                ( fnns, bfn, bn, bjpg, bsn, ext ) = me.make_various_names(ff.file_name)

                anc         = '<A HREF="%s" NAME="%s">' % ( bn, bsn )

                bdy        += '<TD style="width:%u%%;">%s<IMG SRC="%s" ALT="%s" style="border-style:none" BORDER="0"></A></TD>\n' % ( int(100.0 / cols), anc, icon_name(os.path.basename(ff.file_name)), fnns, )

            image_count    += 1

        bdy    += erow                                                                                                      + "\n"
        bdy    += "</TABLE>"                                                                                                + "\n"



        ls      = me.link or ""
        if  ls  :
            ls += "<BR>"
        fs      = ""
        if  me.font_color :
            fs  = "</FONT>"
        trl     = """
                  <P><HR><HR>
                  <SMALL>
                  %s<BR>
                  %u images<BR>%s
                  %s
                  </SMALL>
                  <HR><HR>%s
                  </BODY></HTML>
                  """               % (os.path.basename(icn_fn), image_count, ls, time.asctime(), fs )

        fo      = open(tmp_fn, "w")
        fo.write(hdr + bdy + trl)
        fo.close()

        replace_file.replace_file(icn_fn, tmp_fn, bak_fn)




    ############################################################
    #
    #
    #       Main logic
    #
    #

    def make_files(me, to_files) :
        """
            Make the files that need making.
        """

        to_dict     = tzlib.make_dictionary([ me.rename_base_file_name(fn) for fn in  map(_base_name, to_files) ] )     # unless cmd line said otherwise, we'll not do files that already seem to exist

        fcnt        = 0
        for fn in me.from_files :

            base_fn = _base_name(fn)

            to_name = os.path.join(me.to_dir,  me.rename_base_file_name(base_fn) + ".jpg")
            bg_name = os.path.join(me.big_dir, me.rename_base_file_name(base_fn) + ".jpg")

            if  (not me.clean) and (not me.ignores.has_key(base_fn)) and ((not me.ignore_underscore_file_name) or (not base_fn.startswith("_"))) :

                fcnt   += me.insure_images_exist(to_dict, fn, base_fn, to_name, bg_name)
                fcnt   += 1
                # if  fcnt > 4 :
                #     break
                pass

            else :

                delete_files(to_name)                                                           # clean out ignored pictures that may have been previously made
                delete_files(bg_name)



            if  me.show_unused_process_opts :
                if  me.ignores.has_key(base_fn)                :   del(me.ignores[base_fn])
                if  me.equalizers.has_key(base_fn)             :   del(me.equalizers[base_fn])
                if  me.normalizers.has_key(base_fn)            :   del(me.normalizers[base_fn])
                if  me.rotate_90cw.has_key(base_fn)            :   del(me.rotate_90cw[base_fn])
                if  me.rotate_90ccw.has_key(base_fn)           :   del(me.rotate_90ccw[base_fn])

            pass
        pass



    def make_htm(me) :
        htm_fn  = os.path.join(me.to_dir, me.htm_file_name)

        dtm_fn  = os.path.join(me.to_dir, custom_base_name(me.htm_file_name, me.by_date_x))

        rct_fn  = os.path.join(me.to_dir, custom_base_name(me.htm_file_name, me.recent_x))

        rss_fn  = os.path.join(me.to_dir, me.rss_file_name)

        icn_fn  = os.path.join(me.to_dir, me.icons_htm_file_name)


        if  me.clean :

            if  os.path.exists(htm_fn) :
                os.unlink(htm_fn)
            if  os.path.exists(dtm_fn) :
                os.unlink(dtm_fn)
            if  os.path.exists(rct_fn) :
                for fn in glob.glob(os.path.splitext(rct_fn)[0] + "*" + os.path.splitext(rct_fn)[1]) :
                    os.unlink(fn)
                pass
            if  os.path.exists(rss_fn) :
                os.unlink(rss_fn)
            if  os.path.exists(icn_fn) :
                os.unlink(icn_fn)

            me.erase_tracks()

        elif  me.do_htm :
            #
            #
            #       Now, whatever new thumbnails and bigs are converted
            #
            #

            if  me.input_htm :

                me.put_image_refs_in_to_existing_html(htm_fn)

            else :
                #
                #
                #       So, make the HTML page for the target directory
                #
                #

                rss_url     = me._file_url(os.path.basename(rss_fn))
                rss_htm_fn  = htm_fn

                icn_url     = me._file_url(os.path.basename(icn_fn))



                class   a_to_file(object) :
                    def __init__(me, ob, file_name) :
                        me.file_name    = file_name
                        me.when         = ob._get_picture_when(me.file_name)
                        me.where        = ob._get_a_point_from_file(me.file_name)
                        me.nearby       = []
                    pass

                to_files    = [ a_to_file(me, fn) for fn in glob.glob(os.path.join(me.to_dir, "*.jpg")) if not fn.endswith(ICON_FN_END + ".jpg") ]

                for ( i, ff ) in enumerate(to_files) :
                    if  ff.where :
                        ff.where._to_files_idx  = i
                    pass
                points              = [ ff.where for ff in to_files if ff.where ]
                for ff in to_files  :
                    if  ff.where    :
                        ff._nearby  = tz_gps.get_nearby_tracks_points(points, ff.where, max_distance = me.max_nearby_picture_distance)[:me.max_nearby_pictures]
                    pass
                for ff in to_files  :
                    if  ff.where    :
                        ff.nearby   = [ to_files[p.point._to_files_idx] for p in ff._nearby ]
                        del(ff._nearby)         # temp data not needed now - I'm not really sure at this moment why this operation needs to be done in two steps. May have been for some unimplemented-idea reason.
                    pass


                def _cmp_names(s1, s2) :
                    return(cmp(s1.file_name.lower(), s2.file_name.lower()))

                def _cmp_whens(s1, s2) :
                    return(cmp(s1.when, s2.when))


                to_files.sort(_cmp_whens)
                me.write_big_htms(htm_fn, to_files)


                to_files.sort(_cmp_names)



                class   an_other(object) :
                    def __init__(me, name, file_name) :
                        me.name         = name
                        me.file_name    = file_name
                    pass

                others      = (me.do_date_sort and [ an_other('Most recent', rct_fn), an_other('Sort by date', dtm_fn), ]) or []
                me.write_htm(htm_fn, to_files, others, rss_url, icn_url, tot_image_count = len(to_files))


                if  me.do_date_sort != 0 :

                    to_files.sort(_cmp_whens)
                    if  me.do_date_sort < 0 :
                        to_files.reverse()

                    others      = [ an_other('Most recent', rct_fn), an_other('Sort by name', htm_fn), ]
                    me.write_htm(dtm_fn, to_files, others, rss_url, icn_url, tot_image_count = len(to_files))
                    rss_htm_fn  = dtm_fn

                    to_files.sort(_cmp_whens)
                    to_files.reverse()

                    i               = 0
                    pi              = -1
                    while   i       < len(to_files) :
                        fn          = rct_fn
                        if  i       :
                            fn      = custom_base_name(rct_fn, "_%03u" % i)

                        others      = []

                        if  i       :
                            others.append(an_other('Most recent', rct_fn))

                        if  pi      > 0 :
                            others.append(an_other("Newer",  custom_base_name(rct_fn, "_%03u" % pi)))

                        ni          = i + me.rss_depth

                        if  ni      < len(to_files) :
                            ei      = (int(len(to_files) / me.rss_depth) * me.rss_depth)
                            if  ni == ei :
                                others.append(an_other("Oldest", custom_base_name(rct_fn, "_%03u" % ei)))
                            else    :
                                others.append(an_other("Older",  custom_base_name(rct_fn, "_%03u" % ni)))
                                others.append(an_other("Oldest", custom_base_name(rct_fn, "_%03u" % ei)))
                            pass

                        others     += [ an_other('Sort by date', dtm_fn), an_other('Sort by name', htm_fn), ]

                        me.write_htm(fn, to_files[i : ni], others, rss_url, icn_url, tot_image_count = len(to_files))

                        pi          = i
                        i           = ni

                    pass

                to_files.sort(_cmp_whens)
                to_files.reverse()


                me.write_rss(rss_fn, to_files, rss_htm_fn)
                me.write_tracks()
                me.write_icons_htm(icn_fn, to_files, rss_htm_fn)

                for ff in to_files  :
                    me.files_html   = {}            # whack the cache so that the html for each file can be made anew, with a new direction image
                    if  ff.nearby   :
                        others      = (me.do_date_sort and [ an_other('Most recent',  rct_fn), an_other('Sort by date', dtm_fn), an_other('Sort by name', htm_fn), ] ) or [ an_other("All pictures", htm_fn), ]
                        me.write_htm(os.path.join(me.to_dir, nearby_htm_name(ff.file_name)), ff.nearby, others, None, None, title = "Pictures near " + os.path.splitext(os.path.basename(ff.file_name))[0], arrow_from_point = ff.where, tot_image_count = len(to_files))
                    pass

                if  len(me.kml_points) :
                    kfname  = kml_name("all_picture_locations.ignore")
                    me._write_kml_points(kfname)

                pass
            pass
        pass


    def finish(me)  :
        if  me.jobs :
            print "Finishing %u jobs" % len(me.jobs)
            for p in me.jobs :
                if  p != None :
                    p.join()
                pass

        if  me.tmp_arrow_file_name :
            os.remove(me.tmp_arrow_file_name)

        pass





    pass                # a_thang












if __name__ == '__main__' :


    program_name    = sys.argv.pop(0)

    help_str        = program_name + """ (options) source_directory_with_pictures target_directory_for_pictures

Creates thumbnails in the target directory of the .jpg .gif and .bmp files in the
source directory.

Modifies the thumbnails according to exceptions listed on command line or
in command line arg/configuration file(s).

Copies the full-size images in to target_dir/big.

Creates target_dir/index.htm containing IMG's of all thumbnails and links
to the full sized images in the "big" subdirectory.

Looks for *.cfg files in the source directory.
If any are found, command line arguments are read from them (in tgcmsg format).


Options:

--who               name            Name to put in the KML files as whose they are.

--url_base          url             Set the target directory's public URL.

--big_dir           dir_name        Name the 'big' sub-directory.

--input             amb_file_name   Input file(s). (Source directory is still required on cmd line.)

--input_htm         file_name       Convert this HTML file instead of creating a file.
                                        '<IMG SRC="image_name">' converted.

--do_date_sort                      If not --input_htm, output a _by_date.htm file and 'tween links.
--rev_date_sort                     --do_date_sort, but put new ones on top.

--local_time                        Camera's EXIF time is in this PC's local time. (Default is GMT.)

--title             text            Set the title of the web page. ("Pictures")
--title             dir             "dir"  exactly sets title to target directory name.
--title             fdir            "fdir" exactly sets title to source directory name.
--link              html_code       Put the given code at the bottom of the html.

--htm_name          file_name       Set the output HTML file name. (index.htm)
--rss_name          file_name       Set the output RSS  file name. (rss.xml)

--rss_description   description     Set the RSS description.       ("Pictures")
--rss_depth         count           Number of RSS items to list.   (20)
--rss_icon_url      url             URL to an RSS icon.

--show_dates                        Put the time/date in the output HTML (not to --input_htm).
--background_color  color           Set the HTML background color. (e.g. #ffffff)
--font_color        color           Set the foreground, font color.
--columns           count           Set how many columns of thumbnails there are.
--cell_color        color           Set the cell background color. (e.g. #c0c0c0)
--cell_hz_left                      Align cell contents to the left (default).
--cell_hz_center                    Align cell contentd to the center.
--cell_hz_middle                    Align cell contents to the middle.
--cell_hz_right                     Align cell contents to the right.
--cell_vrt_top                      Align cell contents to the top  (default).
--cell_vrt_middle                   Align cell contents vertically to the middle.
--cell_vrt_center                   Align cell contents vertically to the center.
--cell_vrt_bottom                   Align cell contents vertically to the bottom.
--cell_vrt_baseline                 Align cell contents vertically to the baseline.
--cell_padding      pixels          Set cell padding. (5)
--cell_spacing      pixels          Set cell spacing. (0)
--thumb_labels      none            Do not label the thumbnails if the label is "none" ("file_name").

--geometry          n               Set the 'convert' geometry (thumbnail size) (default 200).
--thumb_width       n               Set the maximum thumbnail width (synonym for --geometry)
--icon_geometry     n               Set the 'convert' geometry for KML icon (default 32)s.

--thumb_quality     n.n             0..1.0 if --use_gimp is true. Ignored if using convert. (default 0.9)
--big_quality       n.n             0..1.0 if --use_gimp is true. Ignored if using convert. (default 0.9)

--jpeg_comment      comment         Set the JPEG comment.         Ignored if using convert.

--ignore            image_name      Ignore the given image file        (case sensitive name).
--rotate_ccw        image           Rotate counter-clockwise the image (case sensitive name).
--rotate_cw         image           Rotate         clockwise the image (case sensitive name).
--normalize         image           Color-level correct the image. (gimp Tools|Color Tools|Levels|Auto)
--equalize          image           Equalize the image (sometimes bizarre output from convert program)

--use_gimp                          Use Gimp instead of ImageMagick to do the conversions.
--use_convert                       Use ImageMagick instead of Gimp to do the conversions.
--convert           image  options  ImageMagick convert program option(s) for thumbnail and big image.
--thumb_convert     image  options  ImageMagick convert program option(s) for thumbnail file only.

--label             image  label    Override automatic labeling to give this image the given label.

--no_cfg_files                      Don't do cmd line args in source directory's *.cfg files.
--no_master_cfg_file                Don't do cmd line args in source directory's thumbnail_htm.cfg.
--show_unused_process_opts          Print any unused 'ignore...' conversion options.
--do_all                            Do all source files whether they are already in target or not.
--no_htm                            Do not output the HTML file. Just convert the images.

--geotrack              file_name   Ambiguous file name of track file(s) (multiple --geotrack allowed).
--ungeotrack            file_name   Ambiguous file name of track file(s) to ignore.
--near_track_distance   naut_miles  How far away tracks can be from each other to be separate    (0.3).
--near_picture_distance naut_miles  How far away pictures can be from a track to be in the track (0.1).

--max_nearby_pictures         number        Maximum number of "nearby" pictures.       (default: %u)
--max_nearby_picture_distance naut_miles    Maximum distance "nearby" pictures can be. (default: %u)

--ignore_underscore_file_name       Ignore input files whose names begin with an underscore character.
--ignore_underscore_original        Ignore input files whose names end with '_original'.
--strip_trailing_numeric            Strip trailing numerics from file names in thumbnail description.

--rename_regx_to    regx    to      Rename the output base names (no dir, no ext) to output files.

--clean                             Erase the files that would normally be output. (Implies --do_all)


This script requires the ImageMagick "convert" program or Gimp to do the heavy lifting.
This script requires certain other TZ python scripts.
If the Image module is installed, this script uses Image to put image sizes in the output HTML file.

""" % ( MAX_NEARBY_PICTURES, MAX_NEARBY_PICTURE_DISTANCE, )



    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)

    if  tzlib.array_find(sys.argv, [ "--help", "-h", "-H", "-?", "/h", "/H", "/?" ] ) >= 0 :
        print help_str
        sys.exit(254)


    me  = a_thang(program_name)


    if  len(sys.argv) != 2 :
        print """

Tell me a 'from' directory and a 'to' directory.

I convert all images in the 'from' dir to small images in the 'to' dir.
  Unless told otherwise, I only do those not already done.
And I write 'index.htm' in the 'to' dir.'
And I write .jpg copies of the full-sized images in a 'big' subdir of the 'to' dir.

The 'from' dir can actually be an ambiguous file name rather than a dir name.

--help for options.

"""
        print sys.argv
        sys.exit(254)


    if  me.use_image_magick :
        if  not convert_program :
            print "Cannot find 'convert' program! Try --use_gimp option?"
            sys.exit(101)
        pass
    elif    not gimp_program :
        print "Cannot find Gimp program! Try --use_convert option?"
        sys.exit(101)


    from_dir    = os.path.normpath(sys.argv.pop(0))

    if  not os.path.isdir(from_dir) :
        from_dir = os.path.dirname(from_dir)
        if  not from_dir :
            from_dir    = "."
        if  not os.path.isdir(from_dir) :
            print   'Cannot resolve special "--title fdir" option (this directory cannot be found: [%s])!' % ( from_dir )
            sys.exit(101)
        pass

    me.add_files_from_dir(from_dir)                                     # find all the files we should include in the gallery
    me.add_geotrack_files()

    me.read_input_htm_file()                                            # in case he has a template file that we simply inject the thumbnail HTML in to
    me.ignore_images_not_in_htm_file()                                  # and, if he has such a file, we'll ignore images that aren't mentioned in the file


    me.to_dir   = os.path.normpath(sys.argv.pop(0))
    me.big_dir  = os.path.normpath(os.path.join(me.to_dir, me.big_sub_dir))
    to_files    = glob.glob(os.path.join(me.to_dir, "*.jpg"))




    #
    #
    #       Do final fixups to command line arguments
    #
    #

    if  me.title == "dir" :
        me.title  = re.sub(r"_", " ", os.path.basename(os.path.abspath(me.to_dir)))
    elif  me.title == "fdir" :
        me.title  = re.sub(r"_", " ", os.path.basename(from_dir))

    if  me.link == 'tz'     :
        me.link  = "<A HREF=\"http://www.tranzoa.com\">Tranzoa</A>"


    if  me.shadow :
        if  not os.path.isdir(me.to_dir) :
            os.makedirs(me.to_dir)

        c       = me.cell_color
        if  c  == None :
            c   = me.background_color
        if  c  == None :
            c   = "#ffffff"

        try :
            c   = ImageColor.getrgb(c)
        except ValueError :
            me.shadow   = False

        if  me.shadow :
            me.shadow_size  = 10
            me.shadow_fn    = "shadow"
            me.shadow_ext   = ".png"
            iis             = shadow_image.make_floating_images(size = me.shadow_size, red = c[0], green = c[1], blue = c[2])
            shadow_image.write_floating_files(os.path.join(me.to_dir, me.shadow_fn + me.shadow_ext), iis)
        pass


    if  me.background_color != None :
        me.background_color = 'BGCOLOR="'     + me.background_color + '"'
    else :
        me.background_color = ""

    if  me.cell_color       != None :
        me.cell_color       = 'BGCOLOR="'     + me.cell_color       + '"'
    else :
        me.cell_color       = ""

    if  me.font_color       != None :
        me.font_color       = '<FONT COLOR="' + me.font_color       + '">'
    else :
        me.font_color       = ""

    if  me.thumb_labels == "none" :
        me.thumb_labels  = ""


    if  me.url_base :
        me.url_base     = me.url_base.rstrip("/")



    if  me.ignore_underscore_original :
        me.from_files   = filter(_not_original, me.from_files)


    me.make_files(to_files)


    if  me.clean :
        try :
            os.rmdir(me.big_dir)
        except OSError, (errno, strerror) :
            print "Cannot remove 'big' directory:", me.big_dir
        pass


    if  me.show_unused_process_opts :
        if  len(me.ignores)                :   print "IGNORES",                me.ignores
        if  len(me.equalizers)             :   print "EQUALIZERS",             me.equalizers
        if  len(me.normalizers)            :   print "NORMALIZERS",            me.normalizers
        if  len(me.rotate_90cw)            :   print "ROTATE_90CW",            me.rotate_90cw
        if  len(me.rotate_90ccw)           :   print "ROTATE_90CCW",           me.rotate_90ccw


    me.make_htm()

    me.finish()


#
#
#
# eof

