#! /usr/bin/env python
"""
$Id: gallery.py,v 1.23 2004/04/19 18:58:30 pj Exp $

Description: Create a gallery of the pictures in the current directory.
             Gallery is located in subdir "gallery"

License: GNU GPL 2 - see http://www.gnu.org/licenses/gpl.txt for details

Synopsis: gallery.py [--title|-t "title"] [--columns|-c #] [--fullsize|-f]
                     [--recursive|-r] [--from|-F "name <URL>"]
                     [--width|-w #] [--thumbwidth|-u #] [--padding|-p #]
                     [--fgColor foregroundColor] [--bgColor backgroundcolor]
                     [--lnColor linkColor] [--lnVisitColor followedLinkColor]
                     [--add|-a]
                     [--help|-h] [--version|-v]

Options:
--title      | -t title            assign title to index page
--columns    | -c # of columns     # of columns in table on index page (default 4)
--fullsize   | -f                  include fullsize pictures (default no)
--recursive  | -r                  process all none gallery directories, put gallery below
--from       | -F "name <URL>"     add creator's name/link to footer,
                                   <URL> may be ommited
                                   e.g.: -F "me <mailto:me@somewhere.org>" | -F me
--width      | -w                  width of resized pictures (default 640)
--thumbwidth | -u                  width of thumbnails (default 160)
--padding    | -p                  cellpadding in table (default 5 pixels)
--fgColor foregroundColor          Set foreground color
--bgColor backgroundColor          Set background color
--lnColor linkColor                Set link color
--lnVisitColor visitedLinkColor    Set link color on already followed link
--add        | -a                  add pictures to existing gallery
                                   (must be executed in gallery directory)
--help       | -h                  display this help page
--version    | -v                  display version number
"""
#
# Copyright:      Per Jensen <gallery@net-es.dk>
# Contributions:  Hans-Peter "FrisPete" Jansen <hpj@urpla.net>
#
# Changelog:
# unknown     0.0.0-1.8.3  Per
# 2001/11/16  1.8.4        FrisPete   --from option, title handling
#                                     don't overwrite stylesheet
# 2001/11/17  1.8.5        FrisPete   tidy up, redundancy removed
#
# 2001/11/25  1.8.6        Per/James  set colors on pages. James' idea
#                                     and color scheme. Navigation bar
#                                     below picture.
#
# 2001/11/25  1.8.7        Per        Added some try/excepts and dotprint
#                                     printing when generating gallery
#
# 2001/12/08  1.8.8        Per        - Fixed wrong fullsizelink on picture pages introduced
#                                       with the navigationbar method.
#                                     - Removed colored border on picturelinks
#                                     - Stylesheet missed tag, some formatting
#                                     - Heidi found IE bug. A few spaces (whitespace) before
#                                       a <br> makes IE place "Fullsize" link way below picture
#                                       We must comply with IE :-) Tested with IE 5.5 SP2
#
# 2002/05/16  1.8.9        Per        - New option '-r', recurse directories
#                                     - import traceback module for printTraceback function....
#                                     - If no title is given, use name of directory
#                                     - set title from file 'gallery.title.txt' if present in directory
#
# 2002/05/29  1.8.10       Per        - Don't rename files. Accept jpg, JPG, jpeg and JPEG extensions.
#                                     - Progress indicator less verbose.
#                                     - Reorganized help screen, added --recursive and --add options.
#                                     - Use shutil.copyfile in copyFullsize().
#                                     - Fixed spelling error as noted by Peter Dyson and Rick Robino, thanks.
#                                     - Performance enhancement, don't process directories where gallery/index.html
#                                       is younger than the pictures
#
# 2002/06/09  1.8.11       Kees       - Dublicate files on Window platform due to erroneous file globbing. Fixed
#                                       by Kees Serier (Kees.Serier@ordina.nl). Thanks.
#
# 2002/06/20  1.8.12       Kees       - Erroneous URLs produced on Windows platform.
#
# 2002/11/27  1.8.13	   Mark	      - gallery.py works with Python 1.5 with this fix. R. Mark Adams (rmadams@epotential.com)
# 2004/02/29  1.8.14       Kees       - Bugfix for not matching <tr> and </tr>. Fix in stylesheet for link foreground color, added (v)align=center for TD tags. Don't use "\" in link url's on windows. Added fgColor and bgColor to TD tag in stylesheet.
#
# 2004/03/24               Per        - Fix for link font color, FIX for "\" in link url's in windows!. Thanks to Serier Kees for testing. An explcit color is set on the image border
# vim:set et ts=4 sw=4:

import glob, os, os.path, sys, getopt, traceback
import shutil
import string,struct
import re
import Image


VERSION="1.8.15"


backslashconverter = re.compile(r'\\')


def usage(full = 0):
    "Print the help in the top of this file"
    if full:
        print __doc__
    else:
        match = 0
        for l in string.split(__doc__, '\n'):
            if l[:11] == "Synopsis:" or match:
                match = 1
                if not string.strip(l):
                    break
                print l[12:]
    sys.exit()



def version():
    print "gallery.py version: " + VERSION


def htmlfilename(file):
    "strip picure extension and add .html"
    index = file.rfind(".")
    if index > -1:
        return file[0:index] + ".html"
    else:
        return file
    



def resize(directory, file, width, dictionary):
    "Save resized picture in specified directory and put size tuple in dictionary"
    if os.path.exists(directory) == 0:
        os.mkdir(directory)
    try:
        outfile = os.path.join(directory, file)
        image = Image.open(file)
        x = image.size[0]
        y = image.size[1]
        if x > y:
            ratio = x * 1.0 / width
            height = int(image.size[1] / ratio)
            newsize = (int(width), height)
        else:
            "If portrait - use width as height, must calculate width"
            ratio = y * 1.0 / width
            calWidth = int(image.size[0] / ratio)
            newsize = (calWidth, int(width))
        dictionary[file] = newsize
        image.resize(newsize).save(outfile, "JPEG")
    except:
        printTraceback()
        sys.exit()


def copyFullSize(directory, file):
    "Copy fullsize pictures to sub directory"
    try:
        if os.path.exists(directory) == 0:
            os.mkdir(directory)
        outfile = os.path.join(directory, file)
        shutil.copyfile(file, outfile)
    except:
        printTraceback()
        sys.exit()


def createIndexFile(columns, fullSize, thumb_dic, resized_dic, files, addPictures):
    "Generate index file with thumbnails and links to the large images"
    try:
        next = ""
        previous = ""

        # set title from file gallery.title.txt if present in directory
        if os.path.isfile("gallery.title.txt"):
            file = open("gallery.title.txt")
            dirTitle = file.read()
            file.close()
        else:
            dirTitle = title

        if addPictures:
            html = readIndex()
            previous = findPreviousPicture(html)

        else:
            html = header(title)

            html.append("<hr>\n<H1>" + dirTitle + "</H1>\n<hr>\n<div><br><br></div>\n")
            html.append("  <table>\n    <tr>")
        x = 0
        filelist = list(files)
        filelist.sort()
        for y in range(len(filelist)):

            if y > 0:
                previous = filelist[y-1]
            else:
                if not addPictures:
                    previous = None
                else:
                    patchPreviousHTML(previous, filelist[0])
            if y < len(filelist)-1:
                next = filelist[y+1]
            else:
                next = None

            file = filelist[y]
            thumb_width = str(thumb_dic[file][0])
            thumb_height = str(thumb_dic[file][1])

            printDot() #shows html generation....

            createPicturePage(file, str(resized_dic[file][0]), str(resized_dic[file][1]),
                                next, previous, fullSize, addPictures, dirTitle)
            if x == columns:
                html.append("\n    </tr>\n\n    <tr>")
                x = 0
            filehtml = htmlfilename(file)
            filehtml = backslashconverter.sub("/", filehtml)
            html.append("\n      <td>\n        <a href=\"" + filehtml + "\"><img src=\"thumbnails/" + file  + "\" height=\"" + thumb_height + "\" width=\"" + thumb_width + "\" alt=\"" + file+ "\"></a>" + getFullSizeLink(file)) 
            x = x + 1 
        html.append("\n    </tr>")
        html.append("\n    <tr>\n")
        html.append("<td></td>      <!--   ##Marker - dont delete##   -->\n")
        html.append("    </tr>\n  </table>\n")
        footer(html, dirTitle)
        if addPictures:
            indexfile = open("index.html","w")
        else:
            indexfile = open(os.path.join("gallery", "index.html"),"w")
        indexfile.write(string.joinfields(html))
        indexfile.close()
    except:
        printTraceback()
        sys.exit()


def getFullSizeLink(filename):
    "Return link to full size picture if needed"
    result = ""
    if fullSize:
        result ="<br><a href=\"fullsize/" + backslashconverter.sub("/", filename) + "\">Full size</a>\n      </td>"
    else:
        result = "\n      </td>"
    return result


def createPicturePage(filename, width, height, next, previous, fullSize, addPictures, title):
    "Create htmlfile for a picture - facilitates slideshow"
    try:
        html = header(title)
        html.append("<p>\n")
        html.append(navigationBar(filename, next, previous, fullSize))
        html.append("\n</p>")
        html.append("<hr>")
        html.append("<img src=\"resized/"
                    + filename
                    + "\" height=" + height + " width=" + width
                    + " border=0>")
        html.append("\n<hr>\n<p>\n" + navigationBar(filename,next,previous,fullSize) + "\n</p>\n")
        footer(html,title)
        if addPictures:
            htmlfile = open(htmlfilename(filename),"w")
        else:
            htmlfile = open(htmlfilename(os.path.join("gallery", filename)),"w")
        htmlfile.write(string.joinfields(html))
        htmlfile.close()
    except:
        printTraceback()
        sys.exit()



def navigationBar(filename, next, previous, fullsize):
    "Return navigation bar"
    html = [""]
    try:
        if next:
            html.append("<a href=\"" + htmlfilename(next)+ "\">Next</a>")
        if previous:
            html.append("\n      <a href=\"" + htmlfilename(previous) + "\">Previous</a>" )
        if fullsize:
            html.append("\n      <a href=\"fullsize/" + filename + "\">Fullsize</a>")
        html.append("\n      <a href=\"index.html\">Gallery</a>")
        return string.joinfields(html)
    except:
        printTraceback()()
        sys.exit()



def header(title):
    "Construct standard header"
    html = [ "" ]
    html.append(r'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"  "http://www.w3.org/TR/html4/strict.dtd">');
    html.append("\n<html>\n")
    html.append("    <head>\n")
    html.append("    <title>" + title + "</title>\n")
    html.append("""<link rel="stylesheet" type="text/css" href="stylesheet.css"> \n  </head>\n   <body>""")
    return html


def footer(html,title):
    if creator:
        html.append("<hr><p class=\"footer\">%s created by %s with %s </p>\n<hr>\n</body>\n</html>"
                    % (title, creator, glink))
    else:
        html.append("<hr><p class=\"footer\">%s created with %s </p>\n<hr>\n</body>\n</html>"
                    % (title, glink))


def createStylesheet(directory):
    "Construct stylesheet unconditionally"
    try:
        html = [ "" ]
        html.append("H1 { font-family: verdana, helvetica, univers, sans-serif; }\n")
        html.append("H1 { color: " + fgColor + ";}")
        html.append("""
H1 { font-size: x-large;}
H1 { font-weight: bold;}
H1 { text-align: center;}

P { font-family: verdana, helvetica, univers, sans-serif; }""")
        html.append("\nP { color: " + fgColor + "; }")
        html.append("""
P { font-size: 10pt; }
P { font-weight: normal; }
P { text-align: left; }
        
P.footer { font-family: verdana, helvetica, univers, sans-serif; }""")

        html.append("\nP.footer { color: " + fgColor + "; }")
        html.append("""
P.footer { font-size: 8pt; }
P.footer { font-style: italic; }
P.footer { text-align: left; }

        """)

        html.append("\nTABLE { border-width: 0;}")
        html.append("\nTABLE { padding: " + padding + "px;}")
        html.append("\nTD { color: " + fgColor + ";}\n")
        html.append("\nTD { background-color: " + bgColor + ";}\n")
        

        html.append("\nA {color: " + fgColor + ";}\n")
        html.append("A { background-color: " + bgColor + ";}\n")

        html.append("A:link { color: " + lnColor + ";}\n")
        html.append("A:visited { color: " + lnVisitColor + ";}\n")

        html.append("IMG { border-style: solid;\n")
        html.append("      border-color: #D4D6D2;}\n")
        
        
        html.append("\nBODY { background-color: " + bgColor + ";}\n")
        
        "Now write unconditionally, stylesheet may have changed..."
        stylesheet = open(os.path.join(directory, "stylesheet.css"), "w")
        stylesheet.write(string.joinfields(html))
        stylesheet.close()
    except:
        printTraceback()
        sys.exit()
        


def readIndex():
    try:
        html = [ "" ]
        index =  open("index.html","r")
        for line in index.readlines():
            if line.find("##Marker - dont delete##") == -1:
                html.append(line)
            else:
                break
        index.close()
        return html
    except:
        printTraceback()
        sys.exit()



def findPreviousPicture(html):
    "Read index.html backwards and find first picture link"
    try:
        x = -1
        result = ""
        while 1:
            line = html[x]
            position = line.find("href=\"")
            if  position > -1:
                slice = line[position+6:]
                result = slice[:slice.find(".html")]
                break
            x = x - 1
        return result + ".jpg"
    except:
        printTraceback()
        sys.exit()

def patchPreviousHTML(previous, file):
    "Insert a forward link to new picture in the prior last file"
    try:
        oldhtml =  open(htmlfilename(previous),"rw")
        newhtml = [ "" ]
        linkFound = None
        for line in oldhtml.readlines():
            if line.find("<a href=") > -1 and not linkFound:
                newhtml.append("<a href=\"" + htmlfilename(file) + "\">Next</a>" + line)
            else:
                newhtml.append(line)
        oldhtml.close()
        newfile = open(htmlfilename(previous),"w")
        newfile.write(string.joinfields(newhtml))
        newfile.close()
    except:
        printTraceback()
        sys.exit()


def constructUniqueFilename(fileNameDictionary, file):
    "When adding pictures, nameclashes may arise. If so, construct unique name here"
    try:
        counter = 1
        slice = ""
        underscorePos = file.rfind("_")
        if underscorePos > -1:
            slice = file[:underscorePos]
        else:
            slice = file[:-4]
        while 1:
            try:
                "use fact that dictionary retrieval causes exception if key is not found"
                fileNameDictionary[slice + "_" + str(counter) + ".jpg"]
                counter = counter + 1
            except:
                break
        newFileName = slice + "_" + str(counter) + ".jpg"
        return newFileName
    except:
        printTraceback()
        sys.exit()


def sanityCheck():
    "When adding files, make sure we are in the right directory"
    result = 1
    cwd = os.getcwd()
    if not cwd[-7:]  == "gallery":
        print "Not in gallery directory, exiting"
        sys.exit(1)
    if os.path.exists("thumbnails") == 0:
        print "Thumbnails directory not found, exiting"
        sys.exit(1)
    return result



def printTraceback():
    print '-'*60
    print "Error in application"
    traceback.print_exc(file=sys.stdout)
    print '-'*60


def printDot():
    sys.stdout.write(".")
    sys.stdout.flush()



def globFiles(target):
    "Glob all jpg, JPG, jpeg and JPEG in target directory. Return list with filenames"
    # find all files to process
    files = []

    files1 = glob.glob(os.path.join(target, "*.jpg"))
    files3 = glob.glob(os.path.join(target, "*.jpeg"))
    if not sys.platform == 'win32':
        files2 = glob.glob(os.path.join(target, "*.JPG"))
        files4 = glob.glob(os.path.join(target, "*.JPEG"))
    else:
        files2 = files4 = '' 

    if files1 != None:
        files.extend(files1)
    if files2 != None:
        files.extend(files2)
    if files3 != None:
        files.extend(files3)
    if files4 != None:
        files.extend(files4)

    regex = re.compile(r"^\.[\\/]")
    for i in range(len(files)):
        files[i] = regex.sub("", files[i])  # replace '.\' or './' with empty string

    return files


def processDir(dummy, directory, dummy2):
    "Process directory, may be called from os.path.walk"

    global title

    if string.find(directory,"/gallery") == -1:
        curdir = os.getcwd() # save current directory and restore it when done

        os.chdir(topdir)
        os.chdir(directory)

        #renameFiles()

        if recursive:
            head,tail = os.path.split(os.getcwd())
            title = tail

        files = globFiles(".")

        if len(files) == 0:
            print "'" + directory + "', no files to process."
        else:
            sys.stdout.write("Dir: '" + directory + "', ")
            if addPictures:
                # check if new filenames already exist
                oldfiles = globFiles("thumbnails")
                olddict = {}
                for oldfile in oldfiles:
                    olddict[oldfile[11:]] = 1

                for file in files:
                    try:
                        olddict[file]  # exception if not found
                        newFilename = constructUniqueFilename(olddict, file)
                        os.rename(file, newFilename)
                        files.remove(file)
                        files.append(newFilename)
                        file = newFilename
                    except:
                        "no dublet..."
                    printDot()
                    resize("thumbnails", file, thumbWidth, thumb_dic)
                    resize("resized", file, width, resized_dic)
                    if fullSize:
                        copyFullSize("fullsize", file)
                        
                createIndexFile(columns, fullSize, thumb_dic, resized_dic, files, addPictures)
            else:
                if os.path.exists("gallery") == 0:
                    os.mkdir("gallery")
                createStylesheet("gallery")

                youngestFile = 0
                for file in files:
                    mtime = os.path.getmtime(file) 
                    if mtime > youngestFile:
                        youngestFile = mtime
                if os.path.exists("gallery/index.html"):
                    galleryTime =  os.path.getmtime("gallery/index.html")
                else:
                    galleryTime = 0
                if youngestFile > galleryTime:
                    for file in files:
                        printDot()
                        resize(os.path.join("gallery", "thumbnails"), file, thumbWidth, thumb_dic)
                        resize(os.path.join("gallery", "resized"), file, width, resized_dic)
                        if fullSize:
                            copyFullSize(os.path.join("gallery", "fullsize"), file)
                    createIndexFile(columns, fullSize, thumb_dic, resized_dic, files, addPictures)
                else:
                    print "No changes - skipping"
            print
        os.chdir(curdir) # restore current directory, don't confuse os.path.walk
    else:
        # don't process galleries....
        xdummy=1


#  Main
try:
    optlist, args = getopt.getopt(sys.argv[1:],'rhfvac:p:t:u:w:F:',
        ['recursive', 'help', 'padding=', 'fullsize','version','add', 'title=',
         'columns=', 'thumbwidth', 'width', 'from',
         'fgColor=', 'bgColor=', 'lnColor=', 'lnVisitColor='])
except getopt.GetoptError:
    usage()

# default values for options
glink="<a href=\"http://www.net-es.dk/~pj/python/\">gallery.py</a> version " + VERSION
creator=None
title=""
columns=4
fullSize=None
recursive=None
width=640.0
thumbWidth=160.0
padding="5"
addPictures=None
fgColor="#EAEAEA"
bgColor="#34577A"
lnColor="#C0C0C0"
lnVisitColor="#C0C0C0"

# dictionaries
thumb_dic = {}
resized_dic = {}

# get options
for o, a in optlist:
    if o in ("-t", "--title"):
        title = a;
    elif o in ("-h", "--help"):
        usage(full = 1)
    elif o in ("-v", "--version"):
        version()
        sys.exit()
    elif o in ("-c", "--columns"):
        columns = int(a)
    elif o in ("-r", "--recursive"):
        recursive=1
    elif o in ("-f", "--fullsize"):
        fullSize=1
    elif o in ("-w", "--width"):
        width = int(a) * 1.0
    elif o in ("-u", "--thumbwidth"):
        thumbWidth = int(a) * 1.0
    elif o in ("-F", "--from"):
        creator = a
    elif o in ("-p", "--padding"):
        padding = a
    elif o in ("-a", "--add"):
        sanityCheck()
        addPictures = 1
    elif o == "--fgColor":
        fgColor = a
    elif o == "--bgColor":
        bgColor = a
    elif o =="--lnColor":
        lnColor = a
    elif o == "--lnVisitColor":
        lnVisitColor = a
        
if creator:
    i = string.find(creator, "<")
    if i >= 0:
        c = string.strip(creator[:i])
        l = string.strip(creator[i+1:])
        l = string.replace(l, ">", "")
        creator = "<a href=\"%s\">%s</a>" % (l, c)




#
# Start processing here
#
topdir = os.getcwd()
if recursive:
    os.path.walk('.', processDir, None)
else:
    if title == "":
       head,title = os.path.split(os.getcwd())
    processDir(None, ".", None)
    print
