#!/usr/local/bin/python2.0
# EVG - ECLiPt Virtual Gallery Creator   -*- Python -*-
# (c) 1999 by Emanuel Pirker <epirker@edu.uni-klu.ac.at>
# Distributed under the GNU GPL. 
# See http://www.gnu.org/copyleft/gpl.html for the full licence

import os, sys, signal, string, getopt
from eclipthtml import *

# ----------------------------------------------------------------------
# Constants

CONVERTCMD = '/usr/X11R6/bin/convert'
VIEWERCMD = '/usr/X11R6/bin/xv'
THUMBNAILDIR = 'thumbnails'
THUMBPERCENT = '20%'
SMP_NUM_CPUS = 1
HIGHRESDIR = 'highres'
LOWRESDIR = 'lowres'
LOWRESPERCENT = '50%'
KODAKPERCENT = '50%'
SERDBFILE = "serdb"
PICDBFILE = "picdb"
INDEXINFO = "index.html"
INDEXTHUMB = "index_thumb.html"
INDEXLIST = "index_list.html"
ROXENALBUM = "index_roxen.html"
UPLINK = "../"
IMAGEEXT = ['jpg', 'gif', 'png', 'tif']
USEZOPE = 1                # Use standard Zope Headers (0 = False, 1 = True)
MAINBORDER = 0             # Borderwidth of table on main page
TABLEFGHEADER = "#ffffff"
TABLEBGHEADER = "#003366"
TABLEFGFOOTER = "#000000"
TABLEBGFOOTER = "#eeeeee"
THUMBCOLUMNS = 4

# ----------------------------------------------------------------------
# Global variables

threadcount = 0

# ----------------------------------------------------------------------
# Web-content generating classes

class EVGWeb:

    def LoadEVGDefaults(self):
	self.bgimage = None           # Background Image for WebPages
	self.text = "#ffffff"         # Default Text Color for WebPages
	self.bgcolor = "#000000"      # Default Background Color for WebPages
	self.link = "#ccccff"         # Default color for links
	self.alink = "#ff0000"        # Default color for activated links
	self.vlink = "#acacee"        # Default color for visited links

    def CreateNavBar( self, elems ):
	code = Spacer("vertical", 10) + CenterHead() + '[ '

	for i in range( len(elems) ):
	    text, target = elems[i]
	    if (USEZOPE):
                if target == "None.html":
                    code = code + text
                else:
                    code = code + Link(text, target)
                if i < ( len(elems) - 1 ):
                    code = code + ' | '
	    else:
	        if target == "None.html":
		    code = code + Font(text, color = self.text)
	        else:
		    code = code + Link(text, target)
	        if i < ( len(elems) - 1 ):
		    code = code + Font(' | ', color = self.text)

	code = code + " ] " + CenterTail() + Spacer("vertical", 20)

	return code

    def CreateStdHeader( self ):
        if USEZOPE:
            return '<!--#var standard_html_header-->'
        else:
            head = Head("Photo Gallery", "A:link { text-decoration: none; }\nA:active { text-decoration: none; } \nA:visited { text-decoration: none; } \ntable,tr,td,body,p,th,a,center { font-family:Helvetica; }")
            bodystart = BodyStart( self.text, self.bgcolor, self.link, 
                                   self.alink, self.vlink, self.bgimage)
            return '<html>\n' + head + bodystart

    def CreateStdFooter( self ):
	copyright = '<!-- This document was created with the EVG virtual gallery creator -->\n'
        if USEZOPE:
            return copyright + '<!--#var standard_html_footer-->'
        else:
            endtags = '</body>\n</html>'
            return copyright + endtags

    def CreateWebPage (self, filename, content ):
	f = open( filename, 'w' )
	f.write( self.CreateStdHeader() + content + self.CreateStdFooter() )
	f.close()

class ImagePages(EVGWeb):

    def __init__(self,picdb):
	self.LoadEVGDefaults()
	self.picdb = picdb

    def CreateFile(self, filename, descr, first, prev, up, next, last):
	NavBarList = [ ( 'First', "%s.html" % first ), 
		       ( 'Prev', "%s.html" % prev ),  
		       ( 'Up', up ), ('Next', "%s.html" % next ), 
		       ( 'Last', "%s.html" % last ) ]

	navbar = self.CreateNavBar( NavBarList )
        imagetag = Image( filename, "%s" % descr, "border=0" )
        
        if os.path.exists(HIGHRESDIR + '/'+ filename):
            imgline = Link( imagetag, HIGHRESDIR + '/'+ filename )
        else:
            imgline = imagetag
            
	img = Center( imgline + P() + descr + P() )

	self.CreateWebPage('%s.html' % filename, navbar + img + navbar)

        # now generate low-res version
	NavBarList = [ ( 'First', "%s_r.html" % first ), 
		       ( 'Prev', "%s_r.html" % prev ),  
		       ( 'Up', up ), ('Next', "%s_r.html" % next ), 
		       ( 'Last', "%s_r.html" % last ) ]

	navbar = self.CreateNavBar( NavBarList )
        imagetag = Image( LOWRESDIR + '/' + filename, "%s" % descr, "border=0" )
        
        imgline = Link( imagetag, filename )
	img = Center( imgline + P() + descr + P() )

	self.CreateWebPage('%s_r.html' % filename, navbar + img + navbar)
        

    def GenerateImagePages( self ):
	# Generates a Web page for each file in picdb, nicely linked with 
	# First/Last/Prev/Next buttons

	uplink = 'index.html'

	i = 0  
	keys = self.picdb.keys()
	keys.sort()

	for pic in keys:
	    if i == 0:
		self.CreateFile( pic, self.picdb[pic], None, None, uplink, 
				 keys[i+1], keys[-1])
	    elif i == len(self.picdb) - 1:
		self.CreateFile( pic, self.picdb[pic], keys[0], keys[i-1], 
				 uplink, None, None)
	    else:
		self.CreateFile( pic, self.picdb[pic], keys[0], keys[i-1],
				 uplink, keys[i+1], keys[-1])

	    i = i + 1


class IndexPage( EVGWeb ):

    def __init__( self, picdb, serdb, uplink ):
	self.LoadEVGDefaults()
	self.picdb = picdb
	self.serdb = serdb
	self.up = uplink
	self.keys = self.picdb.keys()
	self.keys.sort()

    def CreateNavBar( self ):
	return EVGWeb.CreateNavBar( self, [ ('Info',INDEXINFO), 
					 ('Images', "%s.html" % self.keys[0]),
					 ('Low-Res', "%s_r.html" % self.keys[0]),
					 ('List', INDEXLIST), 
					 ('Thumbnails', INDEXTHUMB), 
					 ('Up', self.up) ] )

    def CreateStdHeader(self):
	inheritedHeader = EVGWeb.CreateStdHeader(self)

	title = "<h1>" + self.serdb[ 'Name' ] + "</h1>"
	header = self.CreateNavBar() + Center(title) + Spacer( "vertical", 15 )

	return inheritedHeader + header

    def CreateThumbIndex( self ):
	keys = self.picdb.keys()
	keys.sort()
	number = len(keys)
	columns = []
	rows= []
	for i in keys:
	    column = TD(Link( Image( "%s/%s" % (THUMBNAILDIR, i), self.picdb[ i ], 'border=1'), "%s.html" % i ),
			      align="center", valign="center")
	    columns.append(column)
	    if (len(columns) == THUMBCOLUMNS):
	        rows.append(TR(columns))
		columns = []

	if (len(columns) > 0):
	    rows.append(TR(columns))

        content = Table(rows, border=MAINBORDER)

	self.CreateWebPage( INDEXTHUMB, content )

    def CreateRoxenAlbum( self ):
	content = '<album title="%s" nails="%s">\n' % ( 
	    self.serdb[ 'Name' ], THUMBNAILDIR )

	keys = self.picdb.keys()
	keys.sort()
	for i in keys:
	    content = content + '<photo file="%s">%s</photo>\n' % (
		i, self.picdb[i] )

	content = content + '</album>\n\n'

	self.CreateWebPage( ROXENALBUM, content )

    def CreateListIndex( self ):
	keys = self.picdb.keys()
	keys.sort()

	trows = []
	for i in keys:
            image = Link( Image( "%s/%s" % (THUMBNAILDIR, i),
                                 i, "border=1" ), "%s.html" % i )
	    trows.append( TR( [ TD(image, align="center"), TD(self.picdb[i]) ] ) )

	self.CreateWebPage( INDEXLIST, Table( trows, border = MAINBORDER )  )


    def CreateIndex( self ):
	trows = []

	if self.serdb['Date'] != '':
	    trows = [ TR( [ TD(Font('Date:', color=TABLEFGHEADER), align="right", bgcolor=TABLEBGHEADER), TD(Font(self.serdb['Date'], color=TABLEFGFOOTER), bgcolor=TABLEBGFOOTER) ] ) ]

	if self.serdb['Description'] != '':
	    trows.append( TR( [ TD(Font('Description:', color=TABLEFGHEADER), align="right", bgcolor=TABLEBGHEADER), TD(Font(self.serdb[ 'Description'], color=TABLEFGFOOTER), bgcolor=TABLEBGFOOTER) ] ) )

	if self.serdb['Photographer'] != '':
	    trows.append( TR( [ TD(Font('Photographer:', color=TABLEFGHEADER), align="right", bgcolor=TABLEBGHEADER), TD(Font(self.serdb['Photographer'], color=TABLEFGFOOTER), bgcolor=TABLEBGFOOTER) ] ) )

	if self.serdb['Region'] != '':
	    trows.append( TR( [ TD(Font('Region:', color=TABLEFGHEADER), align="right", bgcolor=TABLEBGHEADER), TD(Font(self.serdb[ 'Region'], color=TABLEFGFOOTER), bgcolor=TABLEBGFOOTER) ] ) )
	    
	if self.serdb['Locations'] != '':
	    trows.append( TR( [ TD(Font('Locations', color=TABLEFGHEADER), align="right", bgcolor=TABLEBGHEADER) , TD(Font(self.serdb['Locations'], color=TABLEFGFOOTER), bgcolor=TABLEBGFOOTER) ] ) )

	table = Table(trows, border = MAINBORDER)
	
	if self.serdb['MapImage'] != '':
	    help = "<p><b>Click on the image below to start viewing the photos or on the top navigation bar to view a photo index...</b><p>"
	    img = Link( Image( self.serdb[ 'MapImage' ], 
			       'Map Image %s' % self.serdb[ 'MapImage' ], 
			       "border=0" ), "%s.html" % self.keys[0] )

	else:
	    help = "<p><b>Click on the top navigation bar to begin viewing images or see a thumbnail index...</b><p>"
	    img = ""

	self.CreateWebPage( INDEXINFO, table + help + img )

# ----------------------------------------------------------------------------
# Procedural code

def GetFilesFromCWD():
    print "You didn't provide any image file names on the command line."
    print "I will use all pictures %s in the current directory!" % IMAGEEXT
    files = []
    allfiles = os.listdir(os.getcwd())
    for i in allfiles:
        path,ext = os.path.splitext(i)
        if string.lower(ext)[1:] in IMAGEEXT:
            files.append(i)
	    print files
    if len(files) == 0:
        print "No files found in the current directory. Read the README file!"
        sys.exit(1)
    return files
    
def ReadOrCreateDatabase(files):
    # If a database is here, use it. If not, let the user create it 
    # interactively. Database means image comments (picdb), photo series 
    # location, photographer, date (serdb)

    if not os.path.exists(PICDBFILE):
	picdb = {}
	for i in files:
	    vpid = os.fork()
	    if vpid == 0:
		os.execl( VIEWERCMD, VIEWERCMD, i )
	    print "Descr of %s:" % i
	    descr = raw_input()
            if descr <> 'EXCLUDE':
                picdb[ i ] = descr
	    os.kill( vpid, signal.SIGTERM )
	    os.waitpid( vpid, 0 )
	file = open( PICDBFILE, "w" )
	file.write( repr( picdb ) )
	file.close()

    if not os.path.exists(SERDBFILE):
	serdb = {}
	print "Name of the photo series:"
	serdb['Name'] = raw_input()
	while serdb['Name'] == '':
	    print "You must enter a name... What else should appear as title?"
	    serdb['Name'] = raw_input()
	print "Description (What is shown on this image/photo series):"
	serdb['Description'] = raw_input()
	print "Date or date range when these photos were taken:"
	serdb['Date'] = raw_input()
	print "Photographer:"
	serdb['Photographer'] = raw_input()
	print "World Region (e.g. California, USA):"
	serdb['Region'] = raw_input()
	print "Intro Image (one 'typical' image you want to have on the main page):"
	serdb['MapImage'] = raw_input()
	print "Detailed Locations (e.g. L.A., San Francisco):"
	serdb['Locations'] = raw_input()

	file = open( SERDBFILE, "w" )
	file.write( repr(serdb) )
	file.close()

    f = open( PICDBFILE, 'r')
    dbstring = ""
    line = f.readline()
    while line<>'':
	dbstring = dbstring + line
	line = f.readline()
    f.close()

    picdb = eval(dbstring)

    f = open( SERDBFILE, 'r')
    dbstring = ""
    line = f.readline()
    while line<>'':
	dbstring = dbstring + line
	line = f.readline()
    f.close()

    serdb = eval(dbstring)

    return ( picdb, serdb )

# You shouldn't call this directly. Use rescaleImages instead which makes advantage of
# multiple CPUs
def __rescaleImages(filelist, percentage, srcdir, targetdir):
    for filename in filelist:
        os.system( "%s -geometry %sx%s %s/%s %s/%s" % 
                   ( CONVERTCMD, percentage, percentage, srcdir, filename, 
                     targetdir, filename ) )
    
def rescaleImages(filelist, percentage, srcdir, targetdir):
    global threadcount
    if SMP_NUM_CPUS == 1:
        __rescaleImages( filelist, percentage, srcdir, targetdir)
    else:
        try:
            import threading
        except ImportError:
            print 'SMP selected but no threading module. Going single-threaded.'
            __rescaleImages( filelist, percentage, srcdir, targetdir)
            return

        tids = []

        pics = len(filelist)
        unassigned_pics = pics
    
        for i in range( SMP_NUM_CPUS ):

            remaining_cpus = SMP_NUM_CPUS - i
            this = unassigned_pics/remaining_cpus

            if unassigned_pics % remaining_cpus > 0:
                this = this + 1

            item = (pics - unassigned_pics, pics - unassigned_pics + this - 1)

            newthread = threading.Thread( target = __rescaleImages,
                                          args = ( filelist[ pics - unassigned_pics :
                                                             pics - unassigned_pics + this  ],
                                                   percentage, srcdir, targetdir ))
            newthread.start()
            unassigned_pics = unassigned_pics - this
            tids.append(newthread)

                
        # wait for all threads to exit
        for i in tids:
            i.join()
        

def preparekodak(directories):

    # iterate over kodak cd's pictures directories...
    # since you don't have so many CDROM drives, you'll
    # copy them to your hard drive
    filelist={}
    for i in directories:
        filelist[i] = os.listdir(i)

    if not os.path.exists( HIGHRESDIR ):
        os.mkdir( HIGHRESDIR )
    
    # rename files (reverse order)
    pic_ctr = 0
    for dir in directories:
        filelist[dir].reverse()
        for i in range( len(filelist[dir])):
            os.rename( dir + "/" + filelist[dir][i], "%s/pic%s.jpg" % (HIGHRESDIR, string.zfill(str(pic_ctr), 3)) )
            pic_ctr = pic_ctr + 1

    # create standard-size pics
    files = os.listdir(HIGHRESDIR)

    print 'Creating standard sizes images. This may take a while...'
    rescaleImages( files, KODAKPERCENT, HIGHRESDIR, '.')


def main():

    make_gallery = 1
    
    # Parse arguments
    optlist, args = getopt.getopt( sys.argv[1:], 'kgs', ['kodak','no-gallery','from-scratch'])
    files = args
    for i in optlist:
        sw = i[0]
        if sw=='-k' or sw=='--kodak':
            if len(args) == 0:
                print 'You must specify the Kodak Picture CD directory or directories'
            preparekodak(args)
            files = ''
        elif sw=='-g' or sw=='--no-gallery':
            make_gallery = 0
        elif sw=='-s' or sw=='--from-scratch':
            os.system('rm -f %s %s' % (picdb, serdb))

    if len(files) == 0:
        files = GetFilesFromCWD()
        
    # Generate thumbnails by using ImageMagick's convert program
    if not os.path.exists( THUMBNAILDIR ):
	os.mkdir( THUMBNAILDIR )
        print 'Creating thumbnails. This may take a while...'
        rescaleImages(files, THUMBPERCENT, '.', THUMBNAILDIR)

    # Generate low resolution images
    if not os.path.exists( LOWRESDIR ):
        os.mkdir( LOWRESDIR )
        print 'Creating low-resolution images. This may take a while...'
        rescaleImages(files, LOWRESPERCENT, '.', LOWRESDIR)

    if make_gallery == 0:
        sys.exit(0)
        
    # Read Databases (image descriptions, etc)
    # If they don't exist, let the user create it interactively
    ( picdb, serdb ) = ReadOrCreateDatabase(files)

    # Generate image pages; one Web page for each image
    imgPages = ImagePages( picdb )
    imgPages.GenerateImagePages()

    # Generate various index pages; Thumbnail-Index, List-Index, Roxen-Album
    # and Main-Info-Page
    indexGen = IndexPage( picdb, serdb, UPLINK )
    indexGen.CreateThumbIndex()
    indexGen.CreateListIndex()
    indexGen.CreateRoxenAlbum()
    indexGen.CreateIndex()

main()
