#
# This python program will create a thumbnail page given a directory
# which contains a set of JPG images.
#
# To run from the command line, pass in 1-4 parameters as follows:
# (1) The directory which contains the thumbnails.
# (2) The output file name. If missing it will be the name of the
# directory appended with "_thumb.gon"
# (3) The title for the thumbnail page. If missing it will be the
# directory name (w/o path) appended with "Pictures"
# (4) The relative path to the html file which contains the text. If
# this is blank then no backlink will be created. If missing, it
# will be the name of the directory appended with '.html' (unless
# the file with the .go extension does not exist; in which case
# the relative path will be blank.)
#
# To run from another Python script, execute the subroutine "run" with the
# same parameters.
#
# All the images must be named in the Gould standard image naming convention.
#
# 990604_1949_BethPlaying.jpg or
# 990604_BethPlaying.jpg or
# _BethPlaying.jpg
#
# The file name consists of three fields, separated with underscores. The
# first field is the date the picture was taken (yymmdd). The second field is
# the time the picture was taken (hhmm). The last field is the title of the
# picture which each separate word capitalized.
#
# From the orginals, we will create two other derived images. A thumbnail
# (100x100) which will have the same name except for a prefix of "s" and
# a 700x440 image optimized for easy viewing which will have the same name
# except for a prefix of "l".
#
# 990604_1949_BethPlaying.jpg full size
# s990604_1949_BethPlaying.jpg 100x100 thumbnail
# l990604_1949_BethPlaying.jpg 700x440 scaled version
#
# Note 1: sometimes the images are already less than 700x440. In that case we
# do not create a scaled version and only support a fullsize version in the
# thumbnail page.
#
# When this Python programn is run, all the thumbnail and scaled versions of
# each image will be (re)created from the original, full size images. Then
# a single HTML-like output file will be created.
#
# The output file will contain thumbnails and links for all the images in the
# directory. There will be a maximum of 5 images per line and all the
# thumbnail images will be organized in a table.
#
# To keep the page from getting too large, we put a maximum of 40 thumbnails
# on a single page. If there are more than 40 images, we create multiple
# pages. The subsiquent pages are given the same name append with a number
# (ex: 'pages\savoy_thumb.gon', 'pages\savoy_thumb2.gon', ...)
#
# When there is more than one page, we create a simple navigation block
# which lists the page numbers as links except for the current page which
# is bold. For example:
#
# Page 1 | Page 2 | Page 3
#
# -------------
#
# This script has another entry point called convertNameIntoTitle. This
# entry point is used by the makePage.py script to convert filenames into
# page titles. For example:
#
# convertNameIntoTitle('donnaWedding99') -> 'Donna Wedding 99'
#
import os
import re
import sys
import glob
import string
import traceback
import Image
maxRow = 5
maxPage = 40
MyError = 'MyError'
#---------------------------------------------------------------------------
def run(baseDir,outName=None,pageTitle=None,textLink=None):
# remove the trailing slash if present
if baseDir[-1:] == '\\':
baseDir = baseDir[:-1]
# Compute the defaults for the parameters
if outName==None:
outName = baseDir+'_thumb.gon'
if pageTitle==None:
pageTitle = convertNameIntoTitle(getBaseName(baseDir))+' Pictures'
if textLink==None:
if os.access(baseDir+'.go',0) or os.access(baseDir+'.gon',0):
textLink = getBaseName(baseDir)+'.html'
else:
textLink = ''
# Get a list of all the source images. We ignore any files which do not
# begin with a digit or an underscore to make sure that we do not find
# previously created thumbnails and scaled images.
images = glob.glob(baseDir+'\\[0-9_]*.jpg')
if not len(images):
raise MyError,'No properly named images were found in the directory '+baseDir
# Sort the image list to make sure that the images are in date order.
# Note that this will not properly handle directories which wrap across
# between the year 1999 and the year 2000.
images.sort()
# Before we proceed, we parse each image filename. We parse first to make
# sure that the filenames are in the proper format. When we parse an image
# filename we return a tuple which contains the following fields:
# (0) base image name ('990604_1949_BethPlaying.jpg')
# (1) image date/time ('Jun 4, 7:49pm')
# (2) image title ('Beth Playing')
newImages = []
for image in images:
baseName = image[len(baseDir)+1:]
if hasDateTime(baseName):
dateTime = formatDateTime(baseName)
title = convertNameIntoTitle(baseName[12:-4])
elif hasDateOnly(baseName):
dateTime = formatDateOnly(baseName)
title = convertNameIntoTitle(baseName[7:-4])
elif baseName[0] == '_':
dateTime = ''
title = convertNameIntoTitle(baseName[1:-4])
else:
dateTime = ''
title = convertNameIntoTitle(baseName[0:-4])
newImages.append( (baseName,dateTime,title) )
images = newImages
# Compute the relative directory path to the images. This will throw
# an exception of there is no relative path; for example, if the images
# are located on a different disk drive than the output file.
relDir = computeRelative(baseDir,outName)
# Here we create the names for the thumbnail pages (there will be more
# than one when there are more than 40 images). We create an array
# called outNames which contains the names of each page.
#
# outNames[0] is an empty string.
# outNames[1] is the name of the first page (savoy_thumb.gon)
# outNames[2] is the name of the second page (savoy_thumb2.gon)
# ...
# outNames[N] is the name of the last page
# outNames[N+1] is an empty string.
#
# We also create a parallel array with the htmlNames
imageCount = len(images)
numPages = (imageCount+maxPage-1)/maxPage
lastDot = string.rfind(outName,'.')
if lastDot == -1:
raise MyError,'No extension found on output filename'
outNameN = outName[:lastDot] + '%d' + outName[lastDot:]
htmlName = getBaseName(outName[:lastDot]) + '.html'
htmlNameN = getBaseName(outName[:lastDot]) + '%d' + '.html'
outNames = ['',outName]
htmlNames = ['',htmlName]
for page in range(2,numPages+1):
outNames.append(outNameN % page)
htmlNames.append(htmlNameN % page)
outNames.append('')
htmlNames.append('')
# Now we create a thumbnail and scale image for every image in the list
# of images.
count = len(images)
print 'Making thumbnails for %s... %5d left' % (getBaseName(baseDir),count),
for image in images:
makeSmallJpg(baseDir,image[0])
if not oneSizeOnly(baseDir+'\\'+image[0]):
makeLargeJpg(baseDir,image[0])
count = count - 1
print '\b\b\b\b\b\b\b\b\b\b\b%5d left' % count,
print '\b\b\b\b\b\b\b\b\b\b\b '
# When we create the html, we create one page for each 42 images.
for pageNum in range(1,numPages+1):
outFile = open(outNames[pageNum],'w')
insertHeader(outFile,pageNum,numPages,pageTitle,textLink,htmlNames)
if pageNum == numPages:
createTable(outFile,images,baseDir,relDir)
else:
createTable(outFile,images[:maxPage],baseDir,relDir)
images = images[maxPage:]
insertFooter(outFile,pageNum,numPages,pageTitle,textLink,htmlNames)
print 'Finished - Created %s with %d images on %d page(s)' % (getBaseName(outName),imageCount,pageNum)
return outNames[1:-1]
#---------------------------------------------------------------------------
# Returns the filename part of a complete path.
def getBaseName(pathName):
return pathName[string.rfind(pathName,'\\')+1:]
#---------------------------------------------------------------------------
# test for a valid date/time or just date in gould standard format.
testPat1 = re.compile(r'[0-9]{6,6}_[0-9]{4,4}_[^\.]')
testPat2 = re.compile(r'[0-9]{6,6}_[^\.]')
def hasDateTime(image):
if testPat1.match(image): return 1
else: return 0
def hasDateOnly(image):
if testPat2.match(image): return 1
else: return 0
#---------------------------------------------------------------------------
# Return TRUE if the image is already so small that we do not need a
# scaled version of the image
def oneSizeOnly(fileName):
im = Image.open(fileName)
if im.size[0] <= 700 and im.size[1] <= 440: return 1
else: return 0
#---------------------------------------------------------------------------
# We return a string which is the date and time that a picture was taken,
# extracted from the picture name. A sample date and time string is:
#
# May 31, 2:05pm
monthNames = ('','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec')
def formatDateTime(imageName):
year = imageName[0:2]
month = imageName[2:4]
day = int(imageName[4:6])
hour = int(imageName[7:9])
minute = imageName[9:11]
if hour > 12: hour,dayHalf=hour-12,'pm'
else: dayHalf='am'
return monthNames[int(month)]+' '+repr(day)+', '+repr(hour)+':'+minute+dayHalf
def formatDateOnly(imageName):
year = imageName[0:2]
month = imageName[2:4]
day = int(imageName[4:6])
if year[0] == '9': year = '19'+year
else: year = '20'+year
return monthNames[int(month)]+' '+repr(day)+', '+year
#---------------------------------------------------------------------------
# We return a string which is the title of the image. We start with the
# text which follows the date and add blanks before every capitalized
# letter. Then we lowercase any words which should be lowercase in book
# titles.
lowerWords = [ 'on', 'the', 'from', 'and', 'down', 'up', 'of', 'in', 'off',
'under', 'behind', 'with', 'over', 'below', 'near', 'at', 'out', 'above',
'along', 'for' ]
def convertNameIntoTitle(fileName):
# the first character is always uppercase even if the image name does not
# captialize it
title = string.upper(fileName[0])
# copy one character at a time, adding a space instead of any underscore
# and before any capital letter or number
prevLetter = fileName[0]
for letter in fileName[1:]:
if letter == '_':
title = title + ' '
elif letter in string.uppercase:
title = title + ' ' + letter
elif letter in string.digits and prevLetter not in string.digits:
title = title + ' ' + letter
elif letter not in string.digits and prevLetter in string.digits:
title = title + ' ' + letter
else:
title = title + letter
prevLetter = letter
# Now lowercase the special title words. We do this by converting the
# title into an array of words and then back into a string.
words = string.split(title)
for i in range(1,len(words)):
lowerForm = string.lower(words[i])
if lowerForm in lowerWords:
words[i] = lowerForm
else:
words[i] = string.capitalize(words[i])
title = string.join(words)
return title
#---------------------------------------------------------------------------
# These make the thumbnail and scaled versions of a full size image.
def makeSmallJpg(filePath,fileName):
im = Image.open(filePath+'\\'+fileName)
im.thumbnail((100,100))
im.save(filePath+'\\s'+fileName)
def makeLargeJpg(filePath,fileName):
im = Image.open(filePath+'\\'+fileName)
im.thumbnail((700,440))
im.save(filePath+'\\l'+fileName)
#---------------------------------------------------------------------------
# Compute the relative path which gets us from the output html file to
# the images. For example:
#
# baseDir = 'c:\GouldHome\images\savoy'
# outName = 'c:\GouldHome\pages\savoy.htm'
# =>
# relDir = '../../images/savoy
def computeRelative(baseDir,outName):
# The filenames will either begin with a drive letter or with a
# double slash. Make sure these strings match.
if baseDir[0:2] == '\\\\':
baseDir = baseDir[2:]
outName = outName[2:]
elif baseDir[1] == ':':
if baseDir[0:3] != outName[0:3]:
raise MyError,'The images and the output file must be located on the same disk'
baseDir = baseDir[3:]
outName = outName[3:]
else:
raise MyError,'The input directory must be fully specified'
# Separate out all the intermediate directory names. Remember to drop
# the actual filename at the end of outName.
basePath = string.split(baseDir,'\\')
outPath = string.split(outName,'\\')[:-1]
# Skip over every directory name which is the same in both paths
while len(basePath) and len(outPath) and basePath[0] == outPath[0]:
basePath = basePath[1:]
outPath = outPath[1:]
# The final directory will have a ".." for every remaining directory
# in the outPath list, plus the name of every directory in the basePath
relDir = string.join( ['..',]*len(outPath)+basePath, '/' )
if relDir == '': relDir = '.'
return relDir
#---------------------------------------------------------------------------
# Create a thumbnail table
def createTable(outFile,images,baseDir,relDir):
# we make sure that we put no more than N pictures across on a page, to
# ensure this we take our one level array of pictures and turn it into
# a two level array of rows of pictures
rows = []
count = maxRow
for image in images:
if count == maxRow:
lastRow = []
rows.append(lastRow)
count = 0
lastRow.append(image)
count = count + 1
for row in rows:
outFile.write('
\n')
# first we write the images
outFile.write('\n')
for image in row:
width,height = getImageSize(baseDir+'\\s'+image[0])
outFile.write('\n')
if oneSizeOnly(baseDir+'\\'+image[0]):
outFile.write('\n' % (relDir+'\\'+image[0]))
else:
outFile.write('\n' % (relDir+'\\l'+image[0]))
outFile.write('\n' % (relDir+'\\s'+image[0],width,height))
outFile.write(' | \n')
outFile.write('
\n')
# then we write the captions
outFile.write('\n')
for image in row:
outFile.write('\n')
outFile.write(''+image[1]+' \n')
outFile.write(image[2]+' \n')
outFile.write(formatSize(baseDir,relDir,image[0])+'\n')
outFile.write(' | \n')
outFile.write('
\n')
outFile.write('
\n')
#---------------------------------------------------------------------------
# Returns a tuple which is the width and height of a bitmap
def getImageSize(fileName):
im = Image.open(fileName)
return im.size
#---------------------------------------------------------------------------
# This returns a string which is the third line of a picture caption. On
# the screen, the string prints like a pair of file sizes:
#
# (45Kb) (133Kb)
#
# The first size is the size of the full screen (large) image and it links
# to the large image file.
#
# The second size is the size of the original image and it links to the
# original image file.
#
# Note: If the image has only one size, we only return one size/
def formatSize(baseDir,relDir,fileName):
smallName = '\\l'+fileName
largeName = '\\'+fileName
if oneSizeOnly(baseDir+largeName):
return '(%dKb)' % (relDir+largeName,fileSize(baseDir+largeName))
else:
return '(%dKb) (%dKb)' % (relDir+smallName,fileSize(baseDir+smallName),relDir+largeName,fileSize(baseDir+largeName))
def fileSize(fileName):
rawSize = os.stat(fileName)[6]
return (rawSize+512)/1024
#---------------------------------------------------------------------------
# This creates a navigation block which looks like:
#
# Page 1 | Page 2 | Page 3
#
# Where the current page is bold and the other pages are links.
def insertNavigation(outFile,pageNum,numPages,htmlNames):
for page in range(1,numPages+1):
if page != 1:
outFile.write(' | ')
if page == pageNum:
outFile.write('Page %d'%page)
else:
outFile.write('Page %d'%(htmlNames[page],page))
#---------------------------------------------------------------------------
# Inserts the html code for the page header.
def insertHeader(outFile,pageNum,numPages,pageTitle,textLink,htmlNames):
if numPages > 1:
pageTitle = '%s - Page %d' % (pageTitle,pageNum)
pageWidth = 113*maxRow
if textLink:
textLink = 'Return to Text' % textLink
outFile.write(pageHeader % (pageWidth,pageTitle,textLink) )
if numPages > 1:
insertNavigation(outFile,pageNum,numPages,htmlNames)
outFile.write('\n')
pageHeader="""
%s
|
%s
|
"""
#---------------------------------------------------------------------------
# Inserts the html code for the page footer.
def insertFooter(outFile,pageNum,numPages,pageTitle,textLink,htmlNames):
if pageNum == 1:
prevPageRef = ''
else:
prevPageRef = ' | Back to Page %d | ' % (htmlNames[pageNum-1],pageNum-1)
if pageNum == numPages:
nextPageRef = ''
else:
nextPageRef = 'Goto Page %d | ' % (htmlNames[pageNum+1],pageNum+1)
if textLink:
textLink = 'Return to Text' % textLink
outFile.write(pageFooter % (113*maxRow,prevPageRef,nextPageRef,textLink))
pageFooter = """
"""
#---------------------------------------------------------------------------
if __name__=='__main__':
try:
if len(sys.argv) < 2:
print 'Run this program by typing the following command at a DOS prompt:'
print ' makeThumb.py '
else:
argv = sys.argv[1:]
argv.append(None)
argv.append(None)
argv.append(None)
run(argv[0],argv[1],argv[2],argv[3])
except MyError:
print 'Error:'
print sys.exc_value
except:
traceback.print_exc()
raw_input('Press Enter to exit')