# This script parses through a given folder, # and find the most recent NxM images and creates a web page showing # them all off in an HTML file # # If there are more than NxM images in the folder, we'll delete the oldest # ones. # # Place the script and the contents of the img folder in the same folder. # Feel free to place all of this outside the public_html, just will have to # set the -diOut setting here to correctly reference the public_html # # @author Konstantin Shkurko (kshkurko@cs.utah.edu) # @date 02.08.12 # @updated 01.30.13 # @version 0.14 # # imports import argparse, os, glob, datetime, time, shutil import Image, ImageFont, ImageDraw, PngImagePlugin # # try to parse out the date/time from file name # we assume that the filename has the following # organization: "%Y.%m.%d_%H.%M.%S_", # where %<> corresponds to the Python time # # @param inFile - input file name (string) # @return datetime, if name parsing succeeded, otherwise - False # def parseDateTimeFromFileName( inFile ): # try splitting our filename try: tmp = os.path.basename( inFile ) tmp = os.path.splitext( tmp )[0] tmp = tmp.split( '_' ) except: return False # try parsing 1st item as date try: #print( "parsed time: " + tmp[0] + ", " + tmp[1] ) d = time.strptime( tmp[0]+" "+tmp[1], "%Y.%m.%d %H.%M.%S" ) return time.mktime( d ) except: return False # # Gets the PNG images in the directory sorted by modified date # # The date is extraced in the following order (until one succeeds) # 1. Based on the "Creation Time" within image meta-data # 2. Using parseDateTimeFromFileName function above # 3. Using the os file system, file creation time (Windows) or # last metadata modification time (*nix) # # @param dir - directory which to check (default ".") # @return list of PNG filenames sorted on timestamp in descending order # def getImagesInDir( dir = "." ): if( not os.path.exists( dir ) ): print "ERROR: output path (" + dir + ")doesn't exist!" # init file info outInfo = []; # open the directory files = glob.glob( os.path.join( dir, '*.png' ) ); for file in files: # get image tags to resave later (and parse out date) img = Image.open( file ) imgTags = img.info if( imgTags.has_key('Creation Time') ): imgCTime = time.strptime( imgTags['Creation Time'], "%d %b %Y %H:%M:%S +0000" ) outInfo.append( [file, time.mktime( imgCTime )] ) #print "found: {0}, {1}".format( file, fileTimestamp ) else: tmp = parseDateTimeFromFileName( file ) if( tmp != False ): outInfo.append( [file, tmp] ) #print "parse: {0}, {1}".format( file, tmp ) else: fInfo = os.stat( file ) outInfo.append( [file, fInfo.st_ctime] ) #print "NOT: {0}, {1}".format( file, fInfo.st_ctime ) del img # sort in descending order based on created timestamp (first NxM images) outInfo.sort( key=lambda tmp: tmp[1], reverse=True ) return outInfo # # Generates the output file based on all passed in parameters. See the README file # which describes in detail meaning of each parameter in greater detail # # @param inDir - input directory with images (this is where original image files are located, # and then moved to output directory. Original images are NOT modified) # @param outDir - output directory with images (this is where HTML file will be placed) # If inDir is NOT passed in, this directory will house the original images # which will be modified in place # @param outFile - name of the HTML file # @param N - N in NxM matrix of thumbnails # @param M - M in NxM matrix of thumbnails # @param iw - image width of the thumbnail (generated from original image) # @param ih - image height of the thumbnail (generated from original image) # @param rTime - refresh time (milliseconds) for the web page. If 0, then page will not have # any refreshing and the introductory text is different. Also no # image timestamps will be shown underneath thumbnails. # Otherwise, page will refresh in rTime. Time stamps will be placed # underneath thumbnails. # NOTE: use rTime=0 for gallery, rTime>0 for live feed. # def genOutputHTML( inDir, outDir, outFile, N, M, iw, ih, rTime ): # generate the header for the file reloadTimeInMSec = rTime; outStr = """ MASC Showcase: Snowflakes in Freefall """ if( reloadTimeInMSec!=0 ): outStr += """""" else: outStr += """""" outStr += """

MASC Showcase: Snowflakes in Freefall


This is a live showcase of snowflake images captured in freefall at Alta Ski Area using the
University of Utah MASC (Multi Angle Snowflake Camera). For more information about
this University of Utah and National Science Foundation project, or a gallery of snowflake
highlights, please visit the Snowflake Stereography and Fallspeed home page or email Tim
Garrett
.
""" # if we are doing a live web-page, then we have one set of text, including Auto Refresh parts. if( reloadTimeInMSec!=0 ): outStr += """
Images are taken at f/5.6 with an exposure of 1/25,000th of a second using
a 1.2MP industrial camera with a 16 mm lens. The image width is 33 mm (1 1/3 inches).

Click on any image to see it in full resolution. Note that all times are in MDT.
Auto Refresh (in """ + str(reloadTimeInMSec/1000) + """ sec): Off | On


\n""" # now the part that's for a static showcase! else: outStr += """
When it is snowing, images of snowflakes captured live in free fall can be found
at Alta's Snowflake Showcase.

Images are taken at f/5.6 with an exposure of at least 1/25,000th of a second using
1.2MP and 5MP industrial cameras with lenses ranging from 12 mm to 35 mm.
The image resolution ranges from 9 micrometers to 40 micrometers.

Click on any image to see it in full resolution and to play a slide show. Note that all times are in MDT.
""" # original message: #This is a live showcase of the freefall snowflake images captured using the University of Utah
#MASC (Multi Angle Snowflake Camera) at Alta Ski Area, #a project led by Associate Professor of Atmospheric #
Sciences Tim Garrett at the University of Utah. For more information about the project, please visit #
the Snowflake Stereography and Fallspeed home page.

# change 1: #This is a live showcase of snowflake images captured in freefall at Alta Ski Area using the
#University of Utah MASC (Multi Angle Snowflake Camera). For more information about
#this University of Utah and National Science Foundation project, #please visit the Snowflake
#Stereography and Fallspeed home page
or email Tim Garrett.
# compare the input and output directories, only if inDir is set! # if different, copy only images that aren't in outDirectory if( inDir != "" ): fullInPath = os.path.abspath( inDir ) fullOutPath = os.path.abspath( outDir ) if( not os.path.exists( fullInPath ) ): print "ERROR: input path (" + fullInPath + ")doesn't exist!" if( not os.path.exists( fullOutPath ) ): print "ERROR: output path (" + fullOutPath + ")doesn't exist!" if( fullInPath != fullOutPath ): filesIn = glob.glob( os.path.join( fullInPath, '*.png' ) ) filesOut = glob.glob( os.path.join( fullOutPath, '*.png' ) ) filesToMove = list( set(filesIn) - set(filesOut) ); for f in filesToMove: # try copy for now, but might not work quite too well... bname = os.path.basename( f ); #print "- copying file " + f + " to " + os.path.join( fullOutPath, bname ) shutil.copy2( f, os.path.join( fullOutPath, bname ) ) # get images in folder + clean up the oldest ones numImgs = N*M imgs = getImagesInDir( outDir ) if( len(imgs)>numImgs ): for i in range(numImgs, len(imgs) ): os.remove( imgs[i][0] ) os.remove( os.path.splitext(imgs[i][0])[0] + "_s.jpg" ) #print "- removing file " + imgs[i][0] imgs = imgs[0:numImgs] # date range # outStr += "
\nAcquisition Date Range: {0} to {1}

".format( datetime.datetime.fromtimestamp(int(imgs[-1][1])).strftime('%Y.%m.%d, %H:%M:%S'), # datetime.datetime.fromtimestamp(int(imgs[0][1])).strftime('%Y.%m.%d, %H:%M:%S') ) # define font for overlay (just in case font I got is malicious, use it as last resort) fontSizeB = 16 fontSizeS = 8 try: overFontB = ImageFont.truetype( "Verdana.ttf", fontSizeB ) overFontS = ImageFont.truetype( "Verdana.ttf", fontSizeS ) except: overFontB = ImageFont.truetype( "img/Verdana.ttf", fontSizeB ) overFontS = ImageFont.truetype( "img/Verdana.ttf", fontSizeS ) copyMsg = "Tim Garrett, University of Utah, " reservedTags = ('interlace', 'gamma', 'dpi', 'transparency', 'aspect', 'icc_profile' ) # get two images for overlay - larger versions #overNSFL = Image.open( 'img/ssl_NSF_desat.png' ) ##overNSFL = Image.open( 'img/ssl_NSF.png' ) #overALTAL = Image.open( 'img/ssl_ALTA.png' ) #overNSFLw, overNSFLh = overNSFL.size #overALTALw, overALTALh = overALTAL.size #overCombWL = overNSFLw + overALTALw + 5 # get two images for overlay - smaller versions overNSF = Image.open( 'img/ss_NSF_desat.png' ) #overNSF = Image.open( 'img/ss_NSF.png' ) overALTA = Image.open( 'img/ss_ALTA.png' ) overNSFw, overNSFh = overNSF.size overALTAw, overALTAh = overALTA.size overCombW = overNSFw + overALTAw + 5 # insert the table with images c = 1 lImgs = len(imgs) outStr += "\n\t\n" for f in imgs: #print "- file: " + f[0] # get the correct image dimensions tmpw = iw tmph = ih relp = f[0] thumbFileInfo = os.path.splitext( f[0] ) trelp= thumbFileInfo[0] + "_s.jpg"; img = Image.open( f[0] ) w, h = img.size if( w>iw or h>ih ): if( w > h ): tmph = int( tmpw * float( h ) / w ) else: tmpw = int( tmph * float( w ) / h ) else: tmpw = w tmph = h # do we need to generate a thumbnail (and create overlay)? # if need to do thumbnail -> newly added image timestp = datetime.datetime.fromtimestamp( f[1] ) if( os.path.isfile( trelp )==False ): imgTags = img.info img2 = img.convert("RGB") img3 = img2.copy() # add copyright text + sponsor logos yearStr = timestp.strftime( '%Y' ) draw = ImageDraw.Draw( img2 ) tmpStr = copyMsg + yearStr txtBW, txtBH = overFontB.getsize( tmpStr ) # - largest logo + large font #if( w-overCombWL-10-txtBW > 0 ): # img2.paste( overNSFL, (w-overNSFLw,h-overNSFLh) ) # img2.paste( overALTAL, (w-overCombWL,h-overALTALh) ) # txtDim = (w-txtBW-10-overCombWL, (int)(h-1.5*fontSizeB)) # draw.text( txtDim, tmpStr, (255,255,255), font=overFontB ) # - small logo + large font #elif( w-overCombW-10-txtBW > 0 ): if( w-overCombW-10-txtBW > 0 ): img2.paste( overNSF, (w-overNSFw,h-overNSFh) ) img2.paste( overALTA, (w-overCombW,h-overALTAh) ) txtDim = (w-txtBW-10-overCombW, (int)(h-1.5*fontSizeB)) draw.text( txtDim, tmpStr, (255,255,255), font=overFontB ) # - no logo + large font elif( w-txtBW-5 > 0 ): txtDim = (w-txtBW-5, (int)(h-1.5*fontSizeB)) draw.text( txtDim, tmpStr, (255,255,255), font=overFontB ) # - no logo + small font else: txtSW, txtSH = overFontS.getsize( tmpStr ) draw.text( (w-txtSW-5,h-1.5*fontSizeS), tmpStr, (255,255,255), font=overFontS ) meta = PngImagePlugin.PngInfo() mc = 0 for k,v in imgTags.iteritems(): if k in reservedTags: meta.add( k, v ) continue meta.add_text( k, v, 0 ) #print "found meta {0}".format( k ) mc = mc+1 if( mc>0 ): img2.save( thumbFileInfo[0] + ".png", "PNG", pnginfo=meta ) else: img2.save( thumbFileInfo[0] + ".png", "PNG" ) # save full res + thumbnail (no overlay) img3.thumbnail( (tmpw,tmph), Image.ANTIALIAS ) #draw2 = ImageDraw.Draw( img3 ) #txtSW, txtSH = overFontS.getsize( tmpStr ) #draw2.text( (tmpw-txtSW-5,tmph-1.5*fontSizeS), tmpStr, (255,255,255), font=overFontS ) img3.save( trelp, "JPEG" ) #del draw, img2, draw2, img3, meta, imgTags del draw, img2, img3, meta, imgTags # dump file into table # note, we'll keep the time stamp only for the live feed generation if( reloadTimeInMSec!=0 ): outStr += ("\t\t\n" % { 's0':os.path.basename(relp), 's1':os.path.basename(trelp), 's2':timestp.strftime('%Y.%m.%d, %H:%M:%S'), 's3':tmpw, 's4':tmph }) else: outStr += ("\t\t\n" % { 's0':os.path.basename(relp), 's1':os.path.basename(trelp), 's2':timestp.strftime('%Y.%m.%d, %H:%M:%S'), 's3':tmpw, 's4':tmph }) if( c%M==0 and lImgs>c ): outStr += "\t\n\t\n" c += 1 outStr += "\t\n
Snowflake from %(s2)s
%(s2)s
Snowflake from %(s2)s
\n" # finalize file now = datetime.datetime.now() outStr += "

\nUpdated on: " + now.strftime( "%Y.%m.%d, %H:%M" ) + "




" outStr += "\"NSF\"\"Alta\"" # outStr += "Copyright, Tim Garrett, " outStr += "Copyright, Tim Garrett, " outStr += "Konstantin Shkurko, " outStr += "Cale Fallgatter, " outStr += "Daniel \"Howie\" Howlett" outStr += ", University of Utah, " outStr += now.strftime( "%Y" ) + "\n" # print outStr outFile1 = os.path.join( outDir, outFile ) fOut = open( outFile1, "w" ) fOut.write( outStr ) fOut.close() # # MAIN - parses the command line options # def main(): # set-up parser # from: http://www.doughellmann.com/PyMOTW/argparse/ parser = argparse.ArgumentParser( description = 'Creation of Snowflake Showcase HTML', add_help = True ) # output parameters parser.add_argument( '-v', action='store_true', default=False, help="Verbose output (default: %(default)s)" ) parser.add_argument( '-n', type=int, default=8, help="N in Image Matrix NxM (default: %(default)s)" ) parser.add_argument( '-m', type=int, default=5, help="M in Image Matrix NxM (default: %(default)s)" ) parser.add_argument( '-pw', type=int, default=120, help="Shown image width (default: %(default)s)" ) parser.add_argument( '-ph', type=int, default=120, help="Shown image height (default: %(default)s)" ) parser.add_argument( '-din', default="", help="Input image directory (default: %(default)s)" ) parser.add_argument( '-dout', default=".", help="Output directory (default: %(default)s)" ) parser.add_argument( '-fout', default="snowflakes.html", help="Output HTML file (default: %(default)s)" ) parser.add_argument( '-rtime', type=int, default=5000, help="Time before each page refreshes in msec, 0=never (default: %(default)s)" ) # attempt to parse the command line and process program try: parsedVal = parser.parse_args() # dump the parsed info if( parsedVal.v ): print( "\nThe following parameters will be used for generation:" ) print( " -input directory: %s" % (parsedVal.din) ) print( " -output directory: %s" % (parsedVal.dout) ) print( " -output file: %s" % (parsedVal.fout) ) print( " -image matrix dim: %sx%s" % (parsedVal.n, parsedVal.m) ) print( " -shown image dim: %sx%s" % (parsedVal.pw, parsedVal.ph) ) print( " -refresh time (ms): %s" % (parsedVal.rtime) ) print( "" ) # do the work genOutputHTML( parsedVal.din, parsedVal.dout, parsedVal.fout, parsedVal.n, parsedVal.m, parsedVal.pw, parsedVal.ph, parsedVal.rtime ) except IOError, msg: parser.error( str(msg) ) if __name__ == "__main__": main()