#!/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
#       May 27, 2012            bar     doxygen namespace
#       May 28, 2012            bar     media queary, etc. comment
#                                       window-scaling big picture htm
#       May 9, 2013             bar     able to run with 64-bit Windows' pillow subbing for PIL
#       January 26, 2015        bar     use gmaps.htm - which has the google maps v3 api stuff in it to get the .kmz files to the screen on a terrain map
#       January 18, 2016        bar     except syntax change
#       July 25, 2016           bar     change the google maps scheme to https
#       August 13, 2016         bar     time-stamp the .htm files to that of their images
#       August 14, 2016         bar     default link to tz alex
#       September 12, 2016      bar     work with new gmaps.htm to put a red bubble marker at each picture's location in the Google maps web page for the combined tracks
#       August 17, 2017         bar     put navigation at the end of the thumbnail pages, too
#       April 8, 2018           bar     able to use PIL and numpy to tweak the images (especially for --normalize to not be dependent on slow Gimp console and our .scm script)
#       May 6, 2018             bar     when using  PIL and numpy to tweak the images, do the orientation the original image has in its exif data
#       June 4, 2018            bar     spin the auto-adjust-colors logic out to tzlib
#       April 28, 2019          bar     put more absolute URLs in the rss (and, inadvertantly, index.htm)
#                                       do the rss differently, without messing with the index.htm file
#                                       fix big .htm files to keep the image scaled to the browser window. Does it by assuming the "text" - the html text and links - aren't more than 20% of the window hite.
#                                       make the bottom, All Pictures, link to the picture page visible at the end of the big .htm files
#                                       make the big .htm links bigger (for phones)
#       April 29, 2019          bar     stop a PIL warning for big images
#       June 26, 2019           bar     point kml urls for the images at the big-dir .htm files rather than the hard-coded .jpg
#                                       --verbose
#       October 8, 2019         bar     open street map comment
#       April 26, 2020          bar     sort the geotrack files case-insensitive for no real purpose
#       April 27, 2020          bar     clean up the combine-geotrack logic and move it to tz_gps
#       May 12, 2020            bar     no_gps_inside logic from hikify's --exclude_near, etc.
#       May 13, 2020            bar     little cleanups
#                                       del PIL images we open (not doing so has never triggered the PIL (multi-tasking?) bug, but why chance it)
#       May 14, 2020            bar     get new geotrack logic working
#       May 15, 2020            bar     clean up html generation code
#       May 16, 2020            bar     improve some ALT tags
#                                       put the lat/lon out to html for the ./big/, full-sized pictures pages
#       July 16, 2020           bar     fix typo bug in image orientation by 180
#       --eodstamps--
##      \file
#       \namespace              tzpython.thumbnail_htm
#
#
#       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.
#
#       Docs regarding showing KMZ files in Google maps:
#           https://developers.google.com/maps/documentation/javascript/kmllayer
#           Bottom line: max of 20,000 points.
#                        But Mt. Ranier's "Naches..." combined geotrack was 430k KMZ, 6.6m KML, and <18000 points and it didn't fly.
#                        "Green Lake and Ranger Falls" worked and was the next smallest at only 32k kmz, 1700 points.
#
#       TODO:
#
#           Screen saver (open a frameless/minimal-framed window in the browser and update the images at random.)
#
#           Fit the big images to the browser windoe better
#               Notes:
#                   http://responsejs.com/labs/dimensions/
#                       document.documentElement.clientWidth
#                       window.screen.availWidth
#                   http://www.quirksmode.org/blog/archives/2010/09/combining_meta.html
#                       <meta name="viewport" content="width=device-width">
#                       CSS:
#                           @media all and (max-width: 600px) {
#                                   body { ... }}
#                   https://developer.mozilla.org/en/CSS/Media_queries
#               Javascript onload and onsize scale the image using the hite of max(100, (window_hite - table_Y - (end_table_Y - page_hite)))
#
#

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

from    types   import  UnicodeType

have_image_module       = False
try :
    from    PIL     import  Image
    from    PIL     import  ImageColor
    from    PIL     import  ImageOps
    have_image_module       = True
    Image.MAX_IMAGE_PIXELS  = 200000000                     # stop the up-to-105Meg images in rb from generating warnings from Pillow
except ImportError :
    pass

have_numpy_module       = False
try :
    import  numpy
    have_numpy_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

NEAR_TRACK_DISTANCE         = 0.3                           # maximum separation between geotracks that are to be combined in to one track
NEAR_PICTURE_DISTANCE       = 0.1                           # maximum distance pictures can be from geotracks to be included in a track

MAX_GEOTRACK_POINTS         = 2500                          # maximum number of points we can put in a_geotrack
MAX_ON_MAP_PICTURE_DISTANCE = 4.0                           # maximum distance a picture can be from the geotrack's picture to be put in the geotrack

MARKER_LAT_OFFSET           = 0.0                           # how far to put the red marker icon off from the picture's latitude
MARKER_LON_OFFSET           = 0.0                           # how far to put the red marker icon off from the picture's longitude

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


#   Note: This script (with some comments added April 7, 2018) is in /tzpython/.
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)


def set_file_datetime_to_images(fn) :
    """
        Try to set the given file's modify time to that of an image file it is associated with.

        The idea is to let AWS's S3 sync command update all the .htm files every time r.sh has been run.

        This logic assumes the "to" image file this file is associated with has been written and time-stamped already.

    """
    ifn = os.path.splitext(fn)[0] + ".jpg"
    if  not os.path.exists(ifn) :
        ifn = re.sub(r'_nearby$', '', os.path.splitext(fn)[0]) + ".jpg"
    if      os.path.exists(ifn) :
        os.utime(fn, ( os.path.getatime(ifn), os.path.getmtime(ifn) ))
    pass




FAR_NEAR_MULT       = 0.9           # how much weight to put on how close a prospective nearby point is to the closest of the already-chosen near points - to decide how good it would be to put the prospective point in the nearby points
FAR_ADD_COUNT       = 8             # how many extra fuzzy-far images to add to the nearby images

def find_far_nearby_images(point, nears, fars, how_many = FAR_ADD_COUNT) :
    """ Given a center point, move some images from the fars array to the end of nears if they would be nice to see on the nearby_images web page. """
    cnt             = 0
    dist_from_ctr   = [ point.distance_from(np.point) for np in fars ]            # find how far all the 'fars' points are from the center
    closest_nears   = [ tzlib.argmin([ np.point.distance_from(npo.point) for npo in nears ]) for np in fars ]       # find the closest near point index in 'nears' to each of the 'fars' points
    while len(fars) and (cnt < how_many) :
        close_dists = []                                                                # note: don't use array comprehension so we can leave the better comments
        for fi, fp in enumerate(fars) :
            d       = (nears[closest_nears[fi]].point.distance_from(fp.point) * -FAR_NEAR_MULT) + dist_from_ctr[fi]     # compute this point's metric - we want the lowest of the distance to the center minus some < 1.0 multiple of the closest near-point distance to the far point
            close_dists.append(d)                                                       # remember this far point's metric
        bi          = tzlib.argmin(close_dists)                                         # find the best far point
        nears.append(fars.pop(bi))                                                      # move the best point
        dist_from_ctr.pop(bi)                                                           # and also get rid of how far this point is from the center
        closest_nears.pop(bi)                                                           # and also get rid of how the closest near point to this far point
        ni          = len(nears) - 1
        for fi, fp in enumerate(fars) :
            if  nears[ni].point.distance_from(fp.point) < nears[closest_nears[fi]].point.distance_from(fp.point) :
                closest_nears[fi]   = ni                                                # the newly added near point is closer to this far point's closest near point, so keep that far point's closest_nears index up to date
            pass
        cnt        += 1
    pass



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.verbose                      = 0

        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                      # may be True, False or -1
        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          = {}                            # keyed by ambiguous file name, valued True - GPS track files that should be combined in nearby-to-each-other webs
        me.near_track_distance          = NEAR_TRACK_DISTANCE           # how near tracks need to be to be combined in geo-track webs   - within a third  of a nautical mile
        me.near_picture_distance        = NEAR_PICTURE_DISTANCE         # how near pictures must be to be to a geo-track                - 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.max_geotrack_points          = MAX_GEOTRACK_POINTS           # maximum number of points we can put in a_geotrack
        me.max_on_map_picture_distance  = MAX_ON_MAP_PICTURE_DISTANCE   # maximum distance a picture can be from the geotrack's picture to be put in the geotrack

        me.icons_htm_file_name          = "all_icons.htm"

        me.no_gps_inside                = []                            # tz_gps areas inside of which we strip gps data from images

        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 image or image's simple kml-kmz file twice



    def _do_args(me, args) :

        while True :
            oi  = tzlib.find_argi_and_del(args, [ "--verbose", "-v", ])
            if  oi < 0 :    break
            me.verbose                 += 1


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


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


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

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

        while True :
            oi  = tzlib.find_argi_and_del(args, "--show_unused_process_opts")
            if  oi < 0 :    break
            me.show_unused_process_opts = True

        while True :
            oi  = tzlib.find_argi_and_del(args, "--do_all")
            if  oi < 0 :    break
            me.do_all = True

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


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

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

        while True :
            oi  = tzlib.find_argi_and_del(args, [ "--use_pil", ] )
            if  oi < 0 :    break
            me.use_image_magick = -1


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


        while True :
            oi  = tzlib.find_argi_and_del(args, "--ignore_underscore_file_name")
            if  oi < 0 :    break
            me.ignore_underscore_file_name = True


        while True :
            oi  = tzlib.find_argi_and_del(args, "--ignore_underscore_original")
            if  oi < 0 :    break
            me.ignore_underscore_original = True


        while True :
            oi  = tzlib.find_argi_and_del(args, "--strip_trailing_numeric")
            if  oi < 0 :    break
            me.strip_trailing_numeric   = True


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


        while True :
            oi  = tzlib.find_argi_and_del(args, "--show_dates")
            if  oi < 0 :    break
            me.show_dates               = True

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

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


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


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


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


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

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


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


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


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


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


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



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


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


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


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


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


        while True :
            oi  = tzlib.find_argi_and_del(args, "--thumb_quality")
            if  oi < 0 :    break
            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.find_argi_and_del(args, "--big_quality")
            if  oi < 0 :    break
            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.find_argi_and_del(args, "--icon_geometry")
            if  oi < 0 :    break
            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.find_argi_and_del(args, "--jpeg_comment")
            if  oi < 0 :    break
            me.jpeg_comment = args.pop(oi)



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

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

        while True :
            oi  = tzlib.find_argi_and_del(args, "--columns")
            if  oi < 0 :    break
            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.find_argi_and_del(args, "--shadow")
            if  oi < 0 :    break
            me.shadow       = True and have_image_module



        while True :
            oi  = tzlib.find_argi_and_del(args, "--geotrack")
            if  oi < 0 :    break
            afn             = args.pop(oi)
            fns             = glob.glob(tzlib.expand_user_vars(afn))
            if  len(fns) == 0 :
                print "No --geotrack file found for:", afn
            else :
                me.geotrack_file_names.update(tzlib.make_dictionary(fns))
            pass

        while True :
            oi  = tzlib.find_argi_and_del(args, "--ungeotrack")
            if  oi < 0 :    break
            afn             = args.pop(oi)
            fns             = glob.glob(tzlib.expand_user_vars(afn))
            if  len(fns) == 0 :
                print "No --ungeotrack file found for:", afn
            else :
                for fn in fns :
                    me.geotrack_file_names.pop(fn, None)        # get rid of the ignored tracks
                pass
            pass


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


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


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


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


        while True :
            oi  = tzlib.find_argi_and_del(args, "--max_geotrack_points")
            if  oi < 0 :    break
            me.max_geotrack_points                  = int(args.pop(oi))

        while True :
            oi  = tzlib.find_argi_and_del(args, "--max_on_map_picture_distance")
            if  oi < 0 :    break
            me.max_on_map_picture_distance          = float(args.pop(oi))


        while True :
            oi  = tzlib.find_argi_and_del(args, [ "--no_gps_rect", "--no_gps_rect_inside", "--no_gps_rectangle", "--no_gps_rectangle_inside", "--exclude",  ] )
            if  oi < 0 :    break
            s                   = args.pop(oi)
            ( lat, lon )        = latlon.parse_lat_lon(s)
            if  (lat == None) or (lon == None) :
                print "I cannot understand no_gps_rect 1st corner %s" % s
            else                :
                p               = tz_gps.a_rectangle(lat, lon)
                s               = args.pop(oi)
                ( lat, lon )    = latlon.parse_lat_lon(s)
                if  (lat == None) or (lon == None) :
                    print "I cannot understand no_gps_rect 2nd corner %s" % s
                else            :
                    p.include_latlon(lat, lon)
                    me.no_gps_inside.append(p)
                pass
            pass

        while True :
            oi  = tzlib.find_argi_and_del(args, [ "--no_gps_circle", "--no_gps_circle_inside", "--exclude_near", ] )
            if  oi < 0 :    break
            s                   = args.pop(oi)
            ( lat, lon )        = latlon.parse_lat_lon(s)
            if  (lat == None) or (lon == None) :
                print "I cannot understand no_gps_circle at %s" % s
            else                :
                p   = tz_gps.a_circle(lat, lon, float(args.pop(oi)))
                me.no_gps_inside.append(p)
            pass


        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(cfg_fn) :
                    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  ( OSError, IOError, AttributeError, ValueError ) :
                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("\\", "/"))





    ############################################################
    #
    #
    #       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 _do_image_conversion(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'!"

        rot         = []
        try :
            jpg     = tz_jpg.a_jpg(fn)
            exif    = jpg.get_exif()

            rot     = exif.get_orientation()
        except  ( OSError, IOError, AttributeError, ValueError ) :
            pass

        img = Image.open(fn)
        img = img.convert('RGB')

        for r in rot :
            if  r.vt        :
                img = ImageOps.flip(img)
            if  r.hz        :
                img = ImageOps.mirror(img)
            if  r.rot == 90 :                       # this means 90 degrees clockwise
                img = img.transpose(Image.ROTATE_270)
            if  r.rot == 180 :
                img = img.transpose(Image.ROTATE_180)
            if  r.rot == 270 :
                img = img.transpose(Image.ROTATE_90 )
            pass

        if  me.rotate_90ccw.has_key(base_fn) :
            img = img.transpose(Image.ROTATE_90)

        if  me.rotate_90cw.has_key(base_fn) :
            img = img.transpose(Image.ROTATE_270)

        if  me.normalizers.has_key(base_fn) or me.equalizers.has_key(base_fn) :
            img = numpy.asarray(img)                    # note: numpy_image is read only
            img = tzlib.do_like_gimps_auto_adjust_color_levels(img)
            img = Image.fromarray(img)                  # back to PIL form

        if  geometry :
            img = img.resize( ( int(geometry), int(round(img.size[1] * (float(geometry) / img.size[0]))) ), Image.ANTIALIAS)

        img.save(to_name, quality = int((max(me.big_quality, me.thumb_quality) or .9) * 100))
        del(img)

        if  me.jpeg_comment :
            fd  = tzlib.read_whole_binary_file(to_name)
            if  fd[-2:] == '\xff\xd9' :
                cm      = me.jpeg_comment
                if  isinstance(cm, UnicodeType) :
                    cm  = cm.encode('utf8')
                cln     = len(cm) + 2
                if  cln < 0xfff0 :
                    fd  = fd[:-2] + '\xff\xfe' + chr(cln >> 8) + chr(cln & 0xff) + cm + '\xff\xd9'
                tzlib.write_whole_binary_file(to_name, fd)
            pass
        pass


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

            cmd = me._make_gimp_command_line(   fn, base_fn, to_name, geometry, convert_options)
            os.system(cmd)                                                          # convert the image to a thumbnail or an image-processed version of the input image

        elif (me.use_image_magick is True) or (not have_image_module) or (not have_numpy_module) :

            cmd = me._make_convert_command_line(fn, base_fn, to_name, geometry, convert_options)
            os.system(cmd)                                                          # convert the image to a thumbnail or an image-processed version of the input image

        else    :
            me._do_image_conversion(            fn, base_fn, to_name, geometry, convert_options)

        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, to_name

            p       = None
            put_gps = True
            if  len(me.no_gps_inside) :
                p   = me._get_a_point_from_file(fn)
                if  p :
                    for a in me.no_gps_inside :
                        if  a.is_inside(p) :
                            put_gps = False                                             # the command line told us to not geotag this image
                            break
                        pass
                    pass
                pass

            if  put_gps     :
                if  not me._get_a_point_from_file(to_name) :                            # imagemagick's 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       = p or 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
                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)

        if  False   :
            if  not me.do_all and to_dict.has_key(btname) :
                try :
                    jpg     = tz_jpg.a_jpg(fn)
                    exif    = jpg.get_exif()
                    rot     = exif.get_orientation()
                    if  len(rot) and (rot[0].v > 1) :
                        del(to_dict[btname])                                                                                        # don't skip over the wrongly rotated (from our bug) images
                        print "@@@@ rotated", rot[0], fn
                    pass
                except  ( OSError, IOError, AttributeError, ValueError ) :
                    pass
                pass
            pass

        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

            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 and KML output
    #
    #
    #
    #


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


    def _create_kml(me, ff) :
        """
            Write a KML/KMZ file for the given image, and only the given image, named by the image's file name,
            if the image is geocoded, putting the meat of the KML string in our dictionary,
            me.kml_points, for later inclusion in the geotrack KML/KMZ files.

            This KML/KMZ file is referenced in the image's HTML if the image has no geotrack KML/KMZ file
            because the image is not anywhere near a geotrack.

        """

        if  not me.url_base :
            return

        kfn = kml_name(ff.file_name)

        if  kfn not in me.files_done :
            me.files_done[kfn]  = True
            p   = ff.where
            if  not p       :
                tzlib.whack_file(kfn)
                tzlib.whack_file(re.sub(r'_geotrack$', '', os.path.splitext(kfn)[0]) + "km?")
            else            :
                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()

                fns = ''                                        # redundant on Google Maps - the name is in the marker's name and shows in bold by GM
                fns = me.file_name_to_html_text(ff.file_name)   # GE doesn't show the redundant info, so let's go with doubling the name

                hfn = me._file_url(os.path.join(me.big_sub_dir, _base_name(ff.file_name) + ".htm"))
                ifn = me._file_url(os.path.basename(ff.file_name))
                ds  = me.kml_description % ( "<B>" + fns + "</B>" + ts, hfn, hfn, ifn, ifn, )

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

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

                s  += tz_gps.kml_trailer()

                if  me.verbose > 2 :
                    print "p.when", time.asctime(time.localtime(p.when)), ff.file_name, p.when, kfn
                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)           # write the KML to either a KMZ or KML file, depending on file name
                os.utime(kfn, ( os.path.getatime(ff.file_name), int(p.when) ) )         # set the file date/time to the picture's date/time
            pass
        pass


    def _write_kml_points(me, kfn) :
        """ Write an all-image, big KML/KMZ file with all the KML snippets we've created for all the images. """

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

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

        dt          = max([ os.path.getmtime(ffn) for ffn in me.kml_points.keys() ] + [ 0 ])
        if  dt      :
            os.utime(fn, ( int(dt), int(dt) ) )             # set the file date/time to the newest picture's date/time
        pass



    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) :                      # from the regex-found object's found string, return what it should be changed to
            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 )
                del(ii)

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


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

        tmp_fn  = htm_fn + ".tmp"
        bak_fn  = htm_fn + ".bak"
        fo      = open(tmp_fn, "w")
        fo.write(ohtml)
        fo.close()
        replace_file.replace_file(htm_fn, tmp_fn, bak_fn)
        set_file_datetime_to_images(htm_fn)




    def image_geocode_html(me, ff, spath, fnns, bsn) :
        """ Return HTML for the given image. """

        htm             = ''
        p               = ff.where
        if  p           :

            htm        += ' <BR>Google '

            if  ff.geotrack :
                gt      = ff.geotrack

                htm    += " GeoTrack "

                gmurl   = "https://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
                gmurl   = "https://www.tranzoa.net/alex/gps/gmaps.htm?track=%s&lat=%f&lon=%f" % ( me._file_url(gt.url), p.lat - MARKER_LAT_OFFSET, p.lon - MARKER_LON_OFFSET, )
                htm    += ' <A HREF="'         +  gmurl  + '" ALT="Google Maps KMZ file">Maps</A>'
                htm    += ' <A HREF="' + spath + gt.url  + '" ALT="Google Earth KMZ file">Earth (.kmz)</A>'
            else        :                                                           # the image has a geo-point, but is not near any track, so the HTML will be simpler and reference the KML/KMZ created by _create_kml().
                #
                #
                #   OpenStreetMap long URL (October 8, 2019):
                #
                #       https://www.openstreetmap.org/?mlat=46.02179&mlon=-117#map=19/45.9311/-116.9453&layers=M    # normal/default overlay
                #       https://www.openstreetmap.org/?mlat=46.02179&mlon=-117#map=19/45.9311/-116.9453&layers=C    # topo
                #
                #       the mlat= and mlon= are the marker's location
                #       the map=LEVEL 19 is zoomed in as tight as it goes
                #       the map=LEVEL/LAT/LON   LAT/LON are the map center
                #       the layers can have M for normal map and C for topo and T for transport and H for humnanitarian
                #       with these, the layers can have N for notes, D for data, and G for public GPS tractes
                #       so, layers=CNDG for the cycling/topo map with notes, data, and public traces.
                #           See: https://wiki.openstreetmap.org/wiki/Layer_URL_parameter
                #
                #       They have some kind of "short" URL that's supposed to be permanently supported in cases where the format of these "long" URLs change. I don't know how to convert/get a short URL.
                #           https://wiki.openstreetmap.org/wiki/Shortlink
                #
                #   geojson.io
                #       Can take a .geojson file from a CORS-setup web server (see asuka/alex/public_html/.htaccess)
                #       geojson format is at:
                #           https://tools.ietf.org/html/rfc7946
                #           Does not do things like color or line thickness.
                #       But geojson.io certain things (click on a marker made by this to edit different sizes (small/medium/large) and symbols:
                #           {
                #             "type": "Feature",
                #             "properties": {
                #               "marker-color": "#ff00ff",
                #               "marker-size": "large",
                #               "marker-symbol": "triangle-stroked"
                #             },
                #             "geometry": {
                #               "type": "Point",
                #               "coordinates": [
                #                 -111.2125825881958,
                #                 38.26714605253118
                #               ]
                #             }
                #           }
                #
                #           properties for LineString (and others?)
                #               "stroke": "#ffff00"
                #               "stroke": "red"                 html/css color names
                #               "stroke-width": 3               0..? in pixels
                #               "stroke-opacity": 0.5           0.0 ... 1.0 with 0.5 (not .5) being half
                #
                #           Note that geojson locations are lon,lat(,ignored_alt_in_meters)
                #
                #
                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  = "https://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
                ll_url  = "https://www.tranzoa.net/alex/gps/gmaps.htm?track=%s&lat=%f&lon=%f" % ( me._file_url(os.path.basename(kml_name(ff.file_name))), p.lat - MARKER_LAT_OFFSET, p.lon - MARKER_LON_OFFSET, )

                htm    += ' <A HREF="' + ll_url                            + '" ALT="Google Maps Location">Maps</A>'
                htm    += ' <A HREF="' + kml_name(spath + bsn + ".ignore") + '" ALT="Google Earth Location KMZ file">Earth (.kmz)</A>'

            pass

        return(htm)


    def nearby_image_arrow_html(me, ff, spath, arrow_from_point = None) :
        """ Return the HTML for the image when it's a nearby image. The HTML has the arrow to show what direction the image is from the 1st image on the web page. """
        htm             = ''
        if  ff.nearby   :
            htm        += ' <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
                    htm                    += ' <img src="%s" ALT="Arrow %d" style="vertical-align:middle;">' % ( os.path.join(ARROW_DIR, os.path.basename(afn)), arrow_angle )
                pass
            pass

        return(htm)


    def image_html(me, fnns, bfn, bn, bsn, spath, wh, big = False, htm_fn = "") :
        """ Return the image HTML including shadowing and HTML table wrapper, if they are wanted. """
        anc         = '<A HREF="%s" NAME="%s">' % ( ((spath + htm_fn + "#%s" % bsn) if big else spath + os.path.splitext(bn)[0] + ".htm"), bsn )

        htm         = '<IMG SRC="%s" ALT="%s"%s style="border-style: none" BORDER="0">' % ( (((not big) and spath) or "") + bfn, fnns, wh )

        if  not me.shadow :
            htm     = anc + htm
        else        :
            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,
                                  htm,
                                         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
                              )
            htm = tzlib.multiline_strip(shadow) + anc

        return(htm)


    def html_details_for_file(me, ff, arrow_from_point = None, big = False, htm_fn = "") :
        """ Return HTML for the maybe-shadowed image, for the information about the image, and for an image date. """

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

        spath       = "../" if big else me._file_url("").rstrip("/") + "/"          # RSS guys don't like relative URLs in the DESCRIPTION text, so we must put absulute (e.g. to content.tranzoa.net) urls in this HTM we're creating (in rss.xml, but not in index.htm)

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

        ea          = "" if big else "</A>"
        if  me.labels.has_key(bsn) :
            htm    += ea + " <BR>" + me.labels[bsn]
        else :
            htm    += 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 )
            del(ii)

        htm        += me.image_geocode_html(     ff, spath, fnns, bsn)
        htm        += me.nearby_image_arrow_html(ff, spath, arrow_from_point = arrow_from_point)

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

        img_htm     = me.image_html(fnns, bfn, bn, bsn, spath, wh, big = big, htm_fn = htm_fn)

        return( ( img_htm, htm, 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_htm, htm, 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("""
                         <head>
                             <meta name="viewport" content="width=device-width;height=device-height;">
                         </head>
                         <body %s>

                         """ % me.cell_color
                        )

                fo.write(htm + "\n")
                fo.write("%s\n" % re.sub(r'(?i)</SMALL>', ((ff.where and (" " + ff.where.where_description())) or "") + '</SMALL>', date_str))

                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  = """&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="%s"><font size="+2">Close-ish</font></a>""" % ( me.make_various_names(nbf.file_name)[4] + ".htm",  )
                    fo.write("""
                             <p>
                             <a href="%s"><font size="+2">Previous</font></a> <a href="%s" style="float:right;"><font size="+2">Next</font></a>%s
                             <p>
                             """ % ( phtm_fn, nhtm_fn, nb )
                            )
                    pass

                img_htm = re.sub(r'WIDTH="\d+"\s+HEIGHT="\d+"\s+style="border-style:\s+none"', 'style="border-style:none;max-height:80vh;max-width:100%;object-fit:contain;"', img_htm)
                # img_htm = re.sub(r'(<TABLE[^>]*)>', r'\1 style="width:100%;">', img_htm)

                fo.write(img_htm + '<font size="+2">All Pictures</font></A>\n')

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



    def html_for_file(me, ff, arrow_from_point = None) :
        ( img_htm, htm, 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_htm, htm, 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"

        ts          = ""

        trl         = "<HR>"
        for other in others :
            trl    += "<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 :
            trl    += 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
            trl    += "<SMALL><A HREF='%s'>%sRSS Feed</A></SMALL>" % ( rss_url, rssiurl )                                   + "\n"
            ts      = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;|"

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


        htm         = ""

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

        htm        += '<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 :
                htm    += erow                                                                                              + "\n"
                htm    += "<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)

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

            image_count        += 1


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

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


        if  len(me.kml_points) :
            kfname  = kml_name("all_picture_locations.ignore")
            trl    += 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"

        htm     = hdr + trl + htm + trl + ftr

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

        replace_file.replace_file(htm_fn, tmp_fn, bak_fn)
        set_file_datetime_to_images(htm_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.image       = tz_rss.an_image(title = me.title, 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_image_files(me, existing_files) :
        """
            Make the files that need making.

            It makes the image thumbnails, icons, and the big-image files for all the me.from_files - the images we are told to make available on the web.

        """

        to_dict     = tzlib.make_dictionary([ me.rename_base_file_name(fn) for fn in  map(_base_name, existing_files) ] )       # unless cmd line said otherwise, we'll not do files that already seem to exist, so this dictionary has all the existing images' base file names

        fcnt        = 0
        for fn in me.from_files :
            if  me.verbose > 1 :
                print "Making from file:", fn

            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                                                                     # use the variable so pyflakes doesn't fuss
                # 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 get_picture_geotracks(me, ff) :
        """ If this picture is geocoded, return its (near-distance and/or near-time) a_geotrack()s, found in me.gts. """
        gts         = []
        if  ff.where :
            gt      = tz_gps.find_nearest_geotrack(me.trk_pts, ff.where)            # is the picture near a known, combined, a_geotrack? (array of all tracks in a_geotrack form if we didn't combine the geotracks)
            if  gt  :
                gts = [ gt ]                                                        # we've found a combined geotrack
            elif not me.trk_pts.points :                                            # note: if is a cheat to speed up the program if we do, in fact, combine the tracks
                gts = tz_gps.make_geotracks_from_point(me.gts, ff.where)            # the new logic to avoid the Little_Si...kmz problem of having too many tracks for a picture for Google maps to use the KMZ file. me.gts is an array of a_geotrack()s, one for each track we have been told of.
            pass
        return(gts)

    def set_all_picture_geotracks(me, to_files) :
        """ Set all the output files' .geotrack, if appropriate. And put the KML for all map-near images that make sense to show the user. """

        to_files    = [ ff for ff in to_files if ff.where ]                                         # we're only concerned with geocoded pictures

        for ff in to_files  :
            ff._geotracks   = me.get_picture_geotracks(ff)                                          # remember which geotracks this picture wants (keeping in mind these geotracks may have other pictures in them)

        for ff in to_files  :
            ff._gt          = tz_gps.make_one_geotrack(ff._geotracks, ff.file_name)                 # merge the ._geocodes in to one a_geocode even if there are no tracks (so images off-track can be shown with other images on google maps)

        for ff in to_files  :
            ( fnns, bfn, bn, bjpg, bsn, ext )   = me.make_various_names(ff.file_name)

            ff.geotrack     = ff._gt.copy()                                                         # make the final a_geocode copy for this picture (don't change the ._gt because it'll be added into other final a_geotrack()s)
            da              = [ [ ff.where.distance_from(ffo.where), ffo, ] for ffo in to_files ]
            da.sort()
            for d, ffo in da :                                                                      # doing the closest pictures first (including the one we're doing)
                if  d and (d > me.max_on_map_picture_distance) :                                    # keep trying to put images on until we get a long way from the center point
                    break
                ff.geotrack.add_kml_picture(os.path.join(bsn, ffo.file_name), me.kml_points[ffo.file_name])                             # make the picture appear in any case
                if  (not len(ff.geotrack.tracks)) or (ff.geotrack.point_count() + ffo._gt.point_count() <= me.max_geotrack_points) :    # don't put too many points in the geotrack
                    ff.geotrack.add_into(ffo._gt)
                    ff.geotrack.remove_dupe_tracks()                                                # so the point count is up-to-date and accurate and so we don't have our own track twice
                pass
            ff.geotrack.remove_dupe_tracks()                                                        # maybe needed if making combined tracks? Anyway, no biggy. Just do it.

            if  (not len(ff.geotrack.tracks)) and (len(ff.geotrack.pictures) < 2) :
                ff.geotrack = None  # use the simple KML file for the picture - no geotrack because there's nothing in it other than the picture
            pass

        for ff in to_files :
            del(ff._geotracks)      # both ._geotracks and ._gt must exist because the one must be intact until all of the other are made
            del(ff._gt)             # and ._gt must exist while we're building the final .geotrack because we build it from ._gt's

        pass



    def set_all_nearby_pictures(me, to_files) :
        """ Set each a_to_file()'s .nearby to a list of nearby pictures. """

        to_files            = [ ff       for ff in to_files if ff.where ]

        for ff in to_files  :
            ff.where._ff    =   ff
        points              = [ ff.where for ff in to_files ]
        for ff in to_files  :
            near_pts        = tz_gps.get_nearby_tracks_points(points, ff.where, max_distance = me.max_nearby_picture_distance)      # note, this list probably starts with the picture, itself - which is good because we want it at the top of the web page
            far_files       = near_pts[ me.max_nearby_pictures:]
            near_files      = near_pts[:me.max_nearby_pictures ]
            find_far_nearby_images(ff.where, near_files, far_files)
            ff.nearby       = [ p.point._ff for p in near_files ]
        for ff in to_files  :
            del(ff.where._ff)               # temp data not needed now
        pass


    def make_htm(me) :
        """
            Given the (thumbnail) JPEG images in the me.to_dir (excepting those that are our icons), write:
                an index.htm type file for all the images, etc., sorted by name,
                multi-page index.htm type files for rss-file-length blocks of images sorted by date-time, most recent first,
                me.to_dir/big/ html files for each of the images,
                an rss file for the me.to_dir on the web,
                KML/KMZ files for each image (geotracks given by me.gts),
                an html file to show all the image icons (which are used by the KML/KMZ files) on one page,
                html files for each image showing nearby images with arrows pointing to those nearby images, geographically,
                a KML/KMZ file for each image.

        """

        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)

            tz_gps.erase_geotrack_files(me.gts, me.to_dir)              # this really doesn't do anything because me.gts isn't populated yet in the new logic

            for fn in glob.glob(os.path.join(me.to_dir, "*.kmz")) + glob.glob(os.path.join(me.to_dir, "*.kml")) :
                tzlib.whack_file(fn)                # too bad we're so aggressive, but we'll make .kmz files with weird, combinations-of-currently-existing-image-file names soon, I'm thinking - we should just whack me.to_dir and be done with it, like we do the me.big_dir, except users can do that themselves
            pass

        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) :
                    """
                    This little thing is the key struct for the program.
                    Each one is an output picture/image file we've already written.
                    An array of these learned from the .to_dir's .jpg files tells what we produce
                    inside the html, rss, kmz, etc output files we create in this make_htm() routine.

                    """

                    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)       # none or a tz_gps.a_point()
                        me.nearby       = []                                            # list of other a_to_files() pictures that are "near" this one
                        me.geotrack     = None                                          # the final a_geotrack() to be written
                    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") ]      # the image files are already in the me.to_dir, so we use them to drive the HTML file we make (note the shadows are in PNG form)

                for ff in to_files :
                    me._create_kml(ff)                          # if the image is geo-coded, then write a simple KML/KMZ file that has the image in it (which will be referenced if the image isn't near a track)

                me.set_all_picture_geotracks(to_files)          # set all the .geotrack's

                me.set_all_nearby_pictures(to_files)            # set all the .nearby lists


                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   another_html_output_file(object) :
                    def __init__(me, name, file_name) :
                        me.name         = name
                        me.file_name    = file_name
                    pass


                others  = (me.do_date_sort and [ another_html_output_file('Most recent', rct_fn), another_html_output_file('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      = [ another_html_output_file('Most recent', rct_fn), another_html_output_file('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(another_html_output_file('Most recent', rct_fn))

                        if  pi      > 0 :
                            others.append(another_html_output_file("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(another_html_output_file("Oldest", custom_base_name(rct_fn, "_%03u" % ei)))
                            else    :
                                others.append(another_html_output_file("Older",  custom_base_name(rct_fn, "_%03u" % ni)))
                                others.append(another_html_output_file("Oldest", custom_base_name(rct_fn, "_%03u" % ei)))
                            pass

                        others     += [ another_html_output_file('Sort by date', dtm_fn), another_html_output_file('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)

                gts  = [ ff.geotrack for ff in to_files if ff.geotrack and ((len(ff.geotrack.pictures) > 1) or len(ff.geotrack.tracks)) ]       # don't bother if there are not pictures in the geotrack, because that's what these combined-nearby geotracks are used for
                tz_gps.write_geotrack_files(gts, me.to_dir, program_name = os.path.basename(me.program_name), who = me.who, url_rtn = lambda url : me._file_url(os.path.basename(url)))

                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 [ another_html_output_file('Most recent',  rct_fn), another_html_output_file('Sort by date', dtm_fn), another_html_output_file('Sort by name', htm_fn), ] ) or [ another_html_output_file("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 or --use_pil. Ignored otherwise. (default 0.9)
--big_quality       n.n             0..1.0 if --use_gimp or --use_pil. Ignored otherwise. (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_pil                           Use PIL and numpy instead of ImageMagick or Gimp to do conversions.
--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    (%.1f).
--near_picture_distance naut_miles  How far away pictures can be from a track to be in the track (%.1f).

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

--max_geotrack_points           number      Maximum number of mapped geotrack points.  (default: %u)
--max_on_map_picture_distance   naut_miles  Maximum distance mapped pictures can be from
                                            the mapped point.                          (default: %.1f)

--no_gps_rect   latlon latlon               Ignore any image with a point inside the given NW/SE or NE/SW rectangle.
--no_gps_circle latlon nautical_miles       Ignore any image with a point within the given distance to the given point.

--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.

""" % ( NEAR_TRACK_DISTANCE, NEAR_PICTURE_DISTANCE, MAX_NEARBY_PICTURES, MAX_NEARBY_PICTURE_DISTANCE, MAX_GEOTRACK_POINTS, MAX_ON_MAP_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 is True :
        if  not convert_program :
            print "Cannot find 'convert' program! Try --use_gimp option?"
            sys.exit(101)
        pass
    elif    (not me.use_image_magick) and (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.gts, me.trk_pts  = tz_gps.make_uncombined_geotracks(me.geotrack_file_names.keys(), cutoff_distance = me.near_track_distance)     # don't combine tracks near each other in to a_geotrack()s, but create arrays and lookup's compatible with if we did so
    me.trk_pts          = tz_gps.change_geotrack_lookup_distance(me.trk_pts, me.gts, me.near_picture_distance)                          # set up our 'trk_pts' to find a_geotrack()'s near pictures later on in the code's execution path - in make_html()

    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(tzlib.expand_user_vars(sys.argv.pop(0)))
    me.big_dir      = os.path.normpath(os.path.join(me.to_dir, me.big_sub_dir))
    existing_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="https://www.tranzoa.net/alex">Tranzoa - Alex</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_image_files(existing_files)     # make the thumbnails, icons, and big-images


    if  me.clean :
        if  not tzlib.whack_full_dir(me.big_dir) :
            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
