<interpreter definition 1.2> <banner 1.3> <basic usage information 1.4> <todos 1.5> <imports 1.6> <initialization 1.7,1.8,1.9,1.10,1.11,3.1,7.1> <define miscellaneous subroutines 1.12,1.13> <the addToList routine definition 5.1> <the makeImage routine definition 6.1> <the makeSubImages routine definition 7.2> <the addToIndex routine definition 4.1> <the visit routine definition 2.1> <the retrieve routine definition 8.2> <collect the command line options 1.14> <perform the top level visit 1.15> <write list file 1.16> **** Chunk omitted!
######################################################################## # # # p h o t o . p y # # # ########################################################################
# A program to build photo album web pages. # # John Hurst # version 0.10, 06 Mar 2006 # # This program recursively scans a set of directories (given as # command line parameters) for images that comprise a photo album to # be rendered into a set of web pages. A file 'index.xml' is created # in each (sub)directory, containing a list of images and # subdirectories. This file can be rendered by an XML stylesheet # 'album.xsl' into HTML pages.
# TODO # 20060306 flag descriptions that do not have full details (and auto # copy this to a 'NOT COMPLETE' line in higher level # album.xml data). #
import cgi import EXIF import getopt import md5 import os import os.path import re import stat import sys import time import xml.dom from xml.dom import Node from xml.dom.minidom import parse from xml.parsers.expat import ExpatError
lists = {}
Initialize the variable 'lists'. This is a python directory, where the keys are (os) directories paths rooted at the album subdirectory (the cli parameters), and the entries are lists of image file names within that directory. This is used to build a list of images within each (os) directory traversed. The list/directory is initially empty.
<initialization 1.8> =descriptions={} sounds={}
Initialize the variable 'descriptions'. This is a python directory, where the keys are image file names, and the entries are descriptions/captions for the image concerned. It is built from the eponymously named file 'descriptions' in the directory currently being scanned.
<initialization 1.9> =ALBUMXSL="file:///home/ajh/lib/xsl/album.xsl"
Define the XSL script used to render all XML files built by this program.
<initialization 1.10> =<initialization 1.11> =ignorePat=re.compile("(.*(_\d+x\d+).JPG)|(.xvpics)") medmatch=re.compile(".*_640x480.JPG") bigmatch=re.compile(".*_1600x1200.JPG") thumbmatch=re.compile(".*_128x128.JPG") identifyPat = re.compile(".* (\d+)x(\d+)( |\+).*") treepat=re.compile('.*/(200[/\d]+)')
maxmissing = 0 maxdir = ''
maxmissing, maxdir are variables that track the directory with the maximum number of missing descriptions. This is purely a convenient device to point the user to where most documenting effort is required!
<define miscellaneous subroutines 1.13> =jpgPat=re.compile('(.*)\.(j|J)(p|P)(g|G)$') def trimJPG(name): res=jpgPat.match(name) if res: name=res.group(1) return name
def attrString(el): if el.hasAttributes(): str='' nl=el.attributes #print "looking at attributes of length %d" % (nl.length) for i in range(nl.length): attr=nl.item(i) str+=' %s="%s"' % (attr.name,attr.value) return str else: return '' def flatString(elem): if elem.nodeType==Node.TEXT_NODE: return elem.nodeValue elif elem.nodeType==Node.ELEMENT_NODE: attrs=attrString(elem) str="" for el in elem.childNodes: str+=str+flatString(el) return "<%s%s>%s</%s>" % (elem.tagName,attrs,str,elem.tagName) else: return "[unknown nodeType]" def flatStringNodes(nodelist): str='' for node in nodelist: str+=flatString(node) return str
There are four command line options:
(opts,args) = getopt.getopt(sys.argv[1:],'flrt') forceXmls=recurse=thumbsOnly=False; large=True for (option,value) in opts: if option == '-f': forceXmls=True elif option == '-r': recurse=True elif option == '-l': large=False elif option == '-t': thumbsOnly=True
visitdirs = args # if there are no directories to visit, include at least current directory if len(visitdirs)==0: visitdirs = ['.'] for dir in visitdirs: x = os.path.abspath(dir) (h,t) = os.path.split(x) (titl,nph,nalb,thm,descr,nmiss)=visit(0,x,lists) print "Directory %s has %d images and %d missing descriptions" % (dir,nph,nmiss) try: highername=x+'/../index.xml' higherindex=open(highername) higherdom=parse(higherindex) higherindex.close() #print "parsed %s" % (highername) direlements=higherdom.getElementsByTagName('directory') for dir in direlements: if dir.getAttribute('name')==t: #print "found directory %s!" % (t) dir.setAttribute('title',titl) dir.setAttribute('albums',"%d" % nalb) dir.setAttribute('images',"%d" % nph) dir.setAttribute('thumb',t+'/'+thm) dir.setAttribute('description',descr) if nmiss==0: if dir.hasAttribute('missing'): dir.removeAttribute('missing') else: dir.setAttribute('missing',"%d" % nmiss) newxml=higherdom.toxml() #print newxml higherindex=open(highername,'w') higherindex.write(newxml) higherindex.close() except IOError: print "No higher level index.xml file" except ExpatError: print "XML error in higher level index.xml file"
keys=lists.keys() for k in keys: try: listn=k+"/list" listf=open(listn,"w") except: print "Cannot open %s" % (listn) continue for a in lists[k]: listf.write(a+"\n") listf.close()
Open the file list in each of the directories visited, and write a list of images found in that directory to the file. This may not be entirely accurate, and needs to be confirmed that it does work correctly. It has not been used recently, and may be superflous to needs. It has now been commented out (v1.2.3).
In order to develop this program further, it is suggested that a full data structure for each image and album be developed. This data structure would be populated in some way, and would then provide the data for writing the index.xml file. How is it populated?
There would be a list of such data components for each directory visited (hence a local variable to routine visit). This list could be sorted on one or more of its attributes to provide a range of views.
Components of the new data structure:
| name | the name of the image or album |
| title | a title or caption for the image/album |
| date/time | the date and time of the image |
| threads | (new theme) a list of thread names |
| shutter/aperture | shutter speed and aperture opening |
| film speed | |
| flash used | |
def visit(level,dirname, lists): global descriptions, sounds <initialize variables for visit routine 2.2> <read descriptions file and create dictionary 3.2> fnames=os.listdir(".") fnames.sort() dirs=[]; <sort fnames into directories and images 2.6> <open index.xml and write header 2.3> <get information from album XML file 2.4> print ' '*level,'Scanning directory "%s" ... (%s)' % (dirname,title) <write tree element to index.xml 2.5> if not thumb: if len(images)>0: thumb=images[0] if thumb[-4:len(thumb)].lower()=='.jpg': thumb=thumb[0:-4] <visit all directories 2.7> indexout+=' <thumbnail>%s</thumbnail>\n' % (thumb) #sys.stdout.write("%d" % (len(images))) <scan all images and process them 2.8> indexout+=' <summary photos="%d" albums="%d" missing="%d" maxmissing="%d" maxdir="%s"/>\n' % \ (nphotos,nalbums,missingdescrs,maxmissing,maxdir) <wind up index file 2.9> <wind up descriptions file 3.3> os.chdir(saved) return (title,nphotos,nalbums,thumb,description,missingdescrs)
nphotos=nalbums=missingdescrs=gotdescrfile=0 listmissingimages=[] albumdom=None; title=""; thumb=description="" maxmissing=0; maxdir="" saved = os.getcwd() os.chdir(dirname) mdescr=-1 images=[]
try: indexf=open("index.xml","r") indexin=indexf.read() except: indexin='' try: albumdom=parse("album.xml") except: print "Cannot parse album.xml in directory %s" % dirname indexout='' indexout+='<?xml version="1.0" ?>\n' indexout+='<?xml-stylesheet type="text/xsl" ' indexout+='href="%s"?>\n' % ALBUMXSL indexout+='<album>\n'
if albumdom: indexout+=' <title>' try: titles=albumdom.getElementsByTagName('title') title=titles[0].firstChild.nodeValue except: title="" indexout+=title indexout+='</title>\n' thumbs=albumdom.getElementsByTagName('thumbnail') fc=thumbs[0].firstChild if fc: thumb=fc.nodeValue else: print ' '*level,"There is no thumbnail defined in %s/album.xml" % (dirname) albumdescr=albumdom.getElementsByTagName('description') if not albumdescr: try: albumdescr=albumdom.getElementsByTagName('shortdesc') except: albumdescr="" try: description=albumdescr[0].firstChild.nodeValue description=description.strip() except: description="" indexout+=' <description>' indexout+=description indexout+='</description>\n' commentary=albumdom.getElementsByTagName('commentary') if not commentary: commentary=albumdom.getElementsByTagName('longdesc') if commentary: commentary=commentary[0] indexout+=' <commentary>' indexout+=commentary.toprettyxml() indexout+='</commentary>\n'
indexout+=' <tree>' treepath=os.getcwd() res=treepat.match(treepath) if res: treepath=res.group(1)+'/' #print "treepath=%s" % treepath indexout+=treepath indexout+='</tree>\n'
dirIgnores=re.compile("(movies)|(sounds)") for f in fnames: res=ignorePat.match(f) if res: continue if os.path.isdir(f): if not dirIgnores.match(f): dirs.append(f) else: print ' '*level,"skipping subdirectory %s" % (f) continue if os.path.isfile(f): (r,e)=os.path.splitext(f) if e.lower() in ['.jpg','.avi']: if f not in images: images.append(f) listmissingimages.append(f) continue
Sort the list of all files in this directory into directories and images. We detect the first from an os.path.isdir call, and the second by ckecking its extension, which must be either .JPG or .jpg (or capitalisations in between).
for d in dirs: missd=0 if recurse: #print "in dir %s, about to visit %s ..." % (dirname,d) ad=os.path.abspath(d) (dtitle,nimages,ndirs,thumbn,ddescr,missd)=visit(level+1,d,lists) else: res=retrieve(d) if res: (dtitle,nimages,ndirs,thumbn,ddescr,missd)=res else: continue nphotos+=nimages; nalbums+=ndirs+1 missingdescrs+=missd if missd>maxmissing: maxmissing = missd maxdir = d if not thumb: thumb=d+"/"+thumbn if thumbn[-4:len(thumbn)].lower()=='.jpg': thumbn=thumbn[0:-4] indexout+=' <directory title="%s" name="%s"\n' % (dtitle,d) indexout+=' albums="%d"\n' % (ndirs) indexout+=' images="%d"\n' % (nimages) if missd: indexout+=' missing="%d"\n' % (missd) indexout+=' thumb="%s"\n' % (d+"/"+thumbn) indexout+=' description="%s"/>\n' % (ddescr)
inum=0 lastimage=len(images)-1 for i in images: iprev=images[0]; inext=images[lastimage] if inum>0: iprev=images[inum-1] if inum<lastimage: inext=images[inum+1] descr=makeSubImages(level,lists,i,iprev,inext,mdescr) if descr==-1:{Note 2.8.1} continue if not descr: missingdescrs+=1 addToList(lists,i) indexout+=addToIndex(lists,i,descr) inum+=1 nphotos+=len(images)
indexout+='</album>\n' if indexout != indexin: indexof = open("index.xml","w") indexof.write(indexout) indexof.close() print ' '*level,"... index.xml"
This is a revision of the literate structure to bring all descriptions related material together.
<initialization 3.1> =descrpat = re.compile("([^ ]+?)(\.JPG)? (.*?)( # (.*))?$")
descrpat is a pattern to match image names. Note that the first pattern must be non-greedy, or the '.JPG' gets swallowed, and the third pattern likewise, or the ' # ' gets swallowed.
try: descrf=open("descriptions","r") mdescr=os.stat("descriptions")[stat.ST_MTIME] gotdescrfile=1 for l in descrf.readlines(): res=descrpat.match(l) filen=res.group(1) ext=res.group(2) if not ext: ext=".JPG" imgtitle=res.group(3) comment=res.group(4) soundname=res.group(5) #print filen,ext,trail,comment,soundname if res: descriptions[filen]=imgtitle # this is silly. We removed the '.JPG' in descrpat, # but now reattach it, because we want these to be # proper filenames. Needs more thought. if comment: sounds[filen]=soundname print ' '*level,"Got a sound file:%s" % (soundname) images.append(filen+ext) else: images.append(l.rstrip()) except IOError: print "cannot open descriptions file in directory %s" % dirname descriptions={} sounds={}
Build the python dictionary of descriptions. We attempt to open a file in the current directory called descriptions, and use that to initialize the dictionary. The file has the format of one entry per line for each photo, with the name of the photo at the start of the line (with or without the .JPG extension), followed by at least one blank, followed by the descritive text, terminated by the end of line.
if not gotdescrfile: if os.path.exists('descriptions'): print "OOPS!! attempt to overwrite existing descriptions file" else: descr=open('descriptions','w') for i in images: descr.write(trimJPG(i)+' \n') descr.close() elif listmissingimages: print "There are images not mentioned in the descriptions file:" print "appending them to the descriptions file." descr=open('descriptions','a') for i in listmissingimages: descr.write(trimJPG(i)+' \n') descr.close()
We finalise the descriptions file. Two scenarios are relevant: either a) the descriptions file does not exist, in which case we build a new one, with just the image names of those images found in the directory scan, or b) the descriptions file exists, but there are images found not mentioned in it. In this latter case, we append image names to the file. No attempt is made to insert them in their 'correct' position.
If there are no changes required, the descriptions file is untouched. The existence of the descriptions file is determined by whether the flag gotdescrfile is set.
def addToIndex(lists,imagename,descr): (r,e)=os.path.splitext(imagename) # this was inserted to try an control the length of captions. It # is dangerous, however, as it can munge the well-formedness of # any contained XML. #if len(descr)>200: # descr=descr[0:200] + ' ...' return ' <image name="%s">%s</image>\n' % (r,descr) # was cgi.escape(descr)
I've changed my mind a couple of times about escaping the description string. Currently the string is not escaped, meaning that any special XML characters (ampersand, less than, etc.) must be entered in escaped form. But this model does allow URLs and the like to appear in the description captions.
# 'addToList' is called to add an image name to the list of images. # See above for the format of the data structure 'list'. It is # assumed that the image name is a full path from the album root ((one # of) the CLI parameter(s)). The directory part is removed and used # to match against the keys of 'lists'. A new entry is created if # this path has not previously been seen. The image name is then # added to this list, if it is not already there. def addToList(lists,imagefile): (base,tail)=os.path.split(os.path.abspath(imagefile)) (name,ext)=os.path.splitext(tail) if lists.has_key(base): a = lists[base] else: a = [] lists[base] = a if not name in a: a.append(name)
def makeImage(level,orgwd,orght,newwd,newht,newname,orgname,orgmod): # orgwd: original image width # orght: original image height # newwd: new image width # newht: new image height # newname: new image file name # orgname: original image file name # orgmod: original image modification date/time # #print "makeImage(%d,%d,%d,%d,%s,%s,%s)" % \ # (orgwd,orght,newwd,newht,newname,orgname,orgmod) if orght>orgwd: # portrait mode, swap height and width t=newht newht=newwd newwd=t if orght>newht: # check that resolution is large enough newmod=0 if os.path.isfile(newname): newmod=os.stat(newname)[stat.ST_MTIME] if orgmod>newmod: print ' '*level,"Writing %s at %dx%d ..." % (newname,newht,newwd) size=" -size %dx%d " % (newht,newwd) resize=" -resize %dx%d " % (newht,newwd) #print size+orgname+resize+newname os.system("/sw/bin/convert"+size+orgname+resize+newname) pass return
This routine is responsible for building all the smaller images from the master image.
We define here data structures to handle generalized sub-image size definitions. Not yet active.
<initialization 7.1> =imageSizeDefs=[(128,128,"thumb"),(384,288,"small"),\ (640,480,"medium"),(1200,900,"large"),(0,0,"original")] imageSizes=[]; derivedImageMatch=[] for (wd,ht,sizeName) in imageSizeDefs: imageSizes.append("%dx%d" % (wd,ht)) derivedImageMatch.append(re.compile(".*_%dx%d.JPG" % (wd,ht)))
Initialize the image size parameters. imageSizeDefs is normative, and defines all required image sizes. The first entry is deemed to be the thumbnail size, and an entry of (0,0) refers to the original size. imageSizes is an equivalent list of strings in the form %dx%d, and derivedImageMatch is an equivalent list of patterns to match image names of those sizes. (Except the last, which fix!)
<the makeSubImages routine definition 7.2> =def makeSubImages(level,lists,imagename,iprev,inext,mdescr): global descriptions,forceXmls (r,e)=os.path.splitext(imagename) thumbn=r+"_128x128"+e smalln=r+"_384x288"+e medn=r+"_640x480"+e bign=r+"_1200x900"+e viewn=r+".xml" <return on missing image file 7.3> <get all modification times 7.4> <makeSubImages: make any sub images required 7.5> # check if there is a new description olddescr=descr="" try: oldxmldom=parse(viewn) olddescr=oldxmldom.getElementsByTagName('description') olddescr=flatStringNodes(olddescr[0].childNodes) olddescr=olddescr.strip() except: print "Could not get old description for %s" % viewn pass if descriptions.has_key(r): descr=descriptions[r].strip() # create the new IMAGE.xml if the descriptions file exists # (mdescr>0) and is more recent than IMAGE.xml, or the IMAGE.JPG # is more recent than IMAGE.xml, and there is a new description #print "mdescr=%d, mview=%d, mfile=%d" % (mdescr,mview,mfile) if forceXmls or \ (mview==0) or \ ( (olddescr!=descr) and \ ( (mdescr>0 and mdescr>mview) or \ (mfile>mview)\ )\ ): <makeSubImages: write image xml file 7.6> return descr
if not os.path.isfile(imagename): if descriptions.has_key(r): descr=descriptions[r].strip() return descr else: print "descriptions entry '%s' : image not found" % (imagename) return -1
Check that the image file exists. If it doesn't, then there are two possible reasons. Firstly, the image file has been removed for space reasons, in which case there will be an entry in the descriptions database, and we return that without attempting to make any sub images.
Alternatively, no such descriptions entry means the image is genuinely missing, and therefore we should print a warning message, and return a missing image flag.
<get all modification times 7.4> =mfile=os.stat(imagename)[stat.ST_MTIME] mthumb=msmall=mmed=mbig=mfile-1 mview=0 if os.path.isfile(thumbn): mthumb=os.stat(thumbn)[stat.ST_MTIME] if os.path.isfile(smalln): msmall=os.stat(smalln)[stat.ST_MTIME] if os.path.isfile(medn): mmed=os.stat(medn)[stat.ST_MTIME] if os.path.isfile(bign): mbig=os.stat(bign)[stat.ST_MTIME] if os.path.isfile(viewn): mview=os.stat(viewn)[stat.ST_MTIME] #print "%d > (%d,%d,%d)" % (mfile,mthumb,mmed,mbig)
Get all the modification times. The default modification times are that mview is the oldest, mfile is the youngest, and all the others are in between.
<makeSubImages: make any sub images required 7.5> =if mfile>min(mthumb,msmall,mmed,mbig): (cmd_stdin,cmd_stdout)=os.popen4("/sw/bin/identify "+imagename) cmd_out="" for line in cmd_stdout.readline(): cmd_out=cmd_out+line names=[thumbn,medn,bign,imagename] (b,t)=os.path.split(imagename) #print "base=%s, tail=%s" % (b,t) #print "Making images for %s" % (imagename) #print "checking %s ... (%s)" % (imagename,cmd_out) res=identifyPat.match(cmd_out) if res: ht=int(res.group(1)) wd=int(res.group(2)) makeImage(level,wd,ht,128,128,thumbn,imagename,mfile) if not thumbsOnly: makeImage(level,wd,ht,384,288,smalln,imagename,mfile) makeImage(level,wd,ht,640,480,medn,imagename,mfile) if large: makeImage(level,wd,ht,1200,900,bign,imagename,mfile) count=1
print ' '*level,"Writing %s ..." % (viewn) imagef=open(imagename,'rb') exiftags={} try: exiftags=EXIF.process_file(imagef) except ValueError: print "cannot extract exif data from %s" % imagename #for tag in exiftags.keys(): #print "%s = %s" % (tag,exiftags[tag]) imagexmlf = open(viewn,"w") imagexmlf.write('<?xml version="1.0" ?>\n') imagexmlf.write('<?xml-stylesheet type="text/xsl" ') imagexmlf.write('href="%s"?>\n' % ALBUMXSL) imagexmlf.write('<album>\n') (rp,ep)=os.path.splitext(iprev) (rn,en)=os.path.splitext(inext) treepath=os.getcwd()+'/' res=treepat.match(treepath) if res: imagexmlf.write(' <tree>%s</tree>\n' % (res.group(1))) imagexmlf.write(' <view name="%s" \n' % (r)) imagexmlf.write(' prev="%s" next="%s"\n' % (rp,rn)) if sounds.has_key(r): snd=sounds[r] print " Adding sound: %s" % (snd) imagexmlf.write(' audio="sounds/SND_%s.WAV">\n' % (snd)) else: imagexmlf.write('>\n') imagexmlf.write(' <description>\n') imagexmlf.write(' %s\n' % descr) # was cgi.escape(descr) imagexmlf.write(' </description>\n') if exiftags.has_key('EXIF DateTimeOriginal'): datetime=exiftags['EXIF DateTimeOriginal'] imagexmlf.write(' <datetime>%s</datetime>\n' % datetime) if exiftags.has_key('EXIF ExposureTime'): shutter=exiftags['EXIF ExposureTime'] imagexmlf.write(' <shutter>%s</shutter>\n' % shutter) if exiftags.has_key('EXIF FNumber'): aperture=exiftags['EXIF FNumber'] try: aperture=str(eval(str(aperture)+'.0')) except: pass imagexmlf.write(' <aperture>%s</aperture>\n' % aperture) imagexmlf.write(' </view>\n') imagexmlf.write('</album>\n') imagexmlf.close()
See comment under <the addToIndex routine definition 4.1> regarding escaping the description string.
def getNodeValue(dom,field,missing): try: val = dom.getElementsByTagName(field).item(0).firstChild.nodeValue except: val=missing return val def retrieveField(field): try: val = index.getElementsByTagName(field).item(0).firstChild.nodeValue except: val="[could not retrieve %s]" % (field) return val
These two support routines for retrieve encapsulate data extraction from the DOM model. They perform the same basic operation, differeing only in the default parameters required.
<the retrieve routine definition 8.2> =def retrieve(d): <the retrieve support routines definition 8.1> missing=0 try: index = parse(d+'/index.xml') except: (etype,eval,etrace)=sys.exc_info() print "Cannot open index.xml in directory %s because %s" % (d,etype) return None #print "Successfully parsed index.xml in directory %s" % (d) try: album = parse(d+'/album.xml') except: (etype,eval,etrace)=sys.exc_info() print "Cannot open album.xml in directory %s because %s" % (d,etype) album=None title = retrieveField('title') description = retrieveField('description') imageElems=index.getElementsByTagName('image') summary=index.getElementsByTagName('summary').item(0) photos=int(summary.getAttribute('photos')) albums=int(summary.getAttribute('albums')) missing=int(summary.getAttribute('missing')) # sort out the vexed problem of the thumbnail if album: albumThumbs=album.getElementsByTagName('thumbnail') else: albumThumbs='' thumbDefault=getNodeValue(index,'thumbnail','') thumb=thumbDefault if not thumbDefault: thumbDefault=getNodeValue(album,'thumbnail','') #print " albumThumb: %s" % (thumbDefault) if not thumbDefault: thumb=thumbDefault miss=index.getElementsByTagName('missingdescriptions') if miss: missing=int(miss.item(0).firstChild.nodeValue) if 0: print "retrieved:" print " title: %s" % (title) print " photos: %d" % (photos) print " albums: %d" % (albums) print " thumb: %s" % (thumb) print " description: %s" % (description) return (title,photos,albums,thumb,description,missing)
retrieve is called when we do not wish to recursively visit a subdirectory. Instead, relevant details from the subdirectory are extracted ('retrieved') from an index.xml file, which has been constructed when the relevant subdirectory was visited.
There is a slight problem with identifying a thumbnail for this album, since the definite source is that in the album.xml file. However, this may be blank, in which has we need to promote the first thumbnail within the directory (or subdirectories where the directory has no images of its own).
| File Name | Defined in |
|---|---|
| photo.py | 1.1 |
| Chunk Name | Defined in | Used in |
|---|---|---|
| banner | 1.3 | 1.1 |
| basic usage information | 1.4 | 1.1 |
| collect the command line options | 1.14 | 1.1 |
| current date | 10.2 | |
| current version | 10.1 | |
| define miscellaneous subroutines | 1.12, 1.13 | 1.1 |
| get all modification times | 7.4 | 7.2 |
| get information from album XML file | 2.4 | 2.1 |
| imports | 1.6 | 1.1 |
| initialization | 1.7, 1.8, 1.9, 1.10, 1.11, 3.1, 7.1 | 1.1 |
| initialization | 1.7, 1.8, 1.9, 1.10, 1.11, 3.1, 7.1 | 1.1 |
| initialization | 1.7, 1.8, 1.9, 1.10, 1.11, 3.1, 7.1 | 1.1 |
| initialize variables for visit routine | 2.2 | 2.1 |
| interpreter definition | 1.2 | 1.1 |
| makeSubImages: make any sub images required | 7.5 | 7.2 |
| makeSubImages: write image xml file | 7.6 | 7.2 |
| open index.xml and write header | 2.3 | 2.1 |
| perform the top level visit | 1.15 | 1.1 |
| read descriptions file and create dictionary | 3.2 | 2.1 |
| return on missing image file | 7.3 | 7.2 |
| scan all images and process them | 2.8 | 2.1 |
| sort fnames into directories and images | 2.6 | 2.1 |
| the addToIndex routine definition | 4.1 | 1.1 |
| the addToList routine definition | 5.1 | 1.1 |
| the makeImage routine definition | 6.1 | 1.1 |
| the makeSubImages routine definition | 7.2 | 1.1 |
| the retrieve routine definition | 8.2 | 1.1 |
| the retrieve support routines definition | 8.1 | 8.2 |
| the visit routine definition | 2.1 | 1.1 |
| todos | 1.5 | 1.1 |
| visit all directories | 2.7 | 2.1 |
| wind up descriptions file | 3.3 | 2.1 |
| wind up index file | 2.9 | 2.1 |
| write list file | 1.16 | 1.1 |
| write tree element to index.xml | 2.5 | 2.1 |
| Identifier | Defined in | Used in |
|---|---|---|
| addToIndex | 4.1 | |
| addToList | 5.1 | |
| descriptions | 1.8 | 2.1, 3.2, 3.2, 7.2, 7.2, 7.2 |
| descrpat | 3.1 | 3.2 |
| getNodeValue | 8.1 | |
| ignorePat | 1.10 | 2.6 |
| listmissingimages | 2.2 | 2.6, 3.3, 3.3 |
| lists | 1.7 | |
| makeImage | 6.1 | 7.5, 7.5, 7.5, 7.5 |
| makeSubImages | 7.2 | 2.8 |
| retrieve | 8.2 | 2.7 |
| retrieveField | 8.1 | |
| visit | 2.1 | 1.15, 2.7 |
| 20060314:183617 | ajh | 1.0.0 | add single directory pass as default, -r to recursively scan subdirectories (major revision) |
| 20060418:170216 | ajh | 1.0.1 | add exception handling to a number of XML accesses |
| 20060502:161718 | ajh | 1.1.0 | add various options to omit processing all sizes |
| 20060512:174933 | ajh | 1.1.1 | add image names to descriptions file if not already there |
| 20060515:064337 | ajh | 1.1.2 | fixed bug with entry in decriptions not having an image file |
| 20060601:165937 | ajh | 1.1.3 | add tree field, and -f option to force generation of .xml files |
| 20060603:231034 | ajh | 1.2.0 | remove double previous/next, since now done dynamically in XSLT sheet. |
| 20060608:175540 | ajh | 1.2.1 | fix bug in descriptions with http escape chars |
| 20060611:184930 | ajh | 1.2.2 | fix bug when missing images |
| 20060616:160512 | ajh | 1.2.2 | clean up some description handling |
| 20060628:114452 | ajh | 1.2.3 | start non-destructive update of index files |
| 20060630:065926 | ajh | 1.2.4 | fix bug in scoping of retrieve support routines |
| 20061022:102528 | ajh | 1.2.5 | return non-escaping of description strings |
| 20070419:153749 | ajh | 1.2.6 | ignore movies in (recursive) scanning for directories |
| 20070812:092952 | ajh | 1.2.7 | missing files are not omitted from album if a description exists |
| 20080210:224120 | ajh | 1.2.8 | minor bug in generating sound links: restructured descriptions section |