#!/usr/local/bin/python #================================================================ # archx.py: Index one archive directory of bird images. # For documentation, see: # http://www.nmt.edu/~john/slides/archx/ #---------------------------------------------------------------- PROGRAM_NAME = "archx.py" EXTERNAL_VERSION = "0.0" #================================================================ # Imports #---------------------------------------------------------------- import sys import os import Image import xml4create as xc import birdimages from rnc_archx import * #================================================================ # Manifest constants #---------------------------------------------------------------- ARCHIVE_PREFIX = "bird-" BIRD_CATALOG_NAME = "birdimages.xml" INDEX_DIR = "indices/" THUMB_DIR = "thumb/" THUMB_WIDE = 200 THUMB_HIGH = 200 THUMB_EXTENSION = ".jpg" #================================================================ # Functions and classes #---------------------------------------------------------------- # - - - m a i n - - - def main(): """Main program. [ let archive-set == set of archive directories named in command line arguments file-set == set of image files in archive directories named in command line arguments in: sys.stdout +:= report listing files in file-set that appear to be bird images but are not in the bird image catalog thumbnail directory := thumbnail directory with thumbnail images made from file-set index directory := index directory with arch-NNN.xml files added describing archive-set ] """ #-- 1 -- # [ BIRD_CATALOG_NAME is a readable file valid against # birdimages.rnc -> # imageCatalog := an ImageCatalog object representing # BIRD_CATALOG_NAME ] imageCatalog = birdimages.ImageCatalog.readFile ( BIRD_CATALOG_NAME ) #-- 2 -- for archNo in sys.argv[1:]: #-- 2 body -- # [ sys.stdout +:= report listing image files in # archive (archNo) that appear to be bird images # but are not in the bird image catalog # thumbnail directory := thumbnail directory with # thumbnail images made image files in archive (archNo) # index directory := index directory with arch-(archNo).xml # files added describing image files in archive (archNo) ] processArchive ( imageCatalog, archNo ) # - - - p r o c e s s A r c h i v e - - - def processArchive ( imageCatalog, archNo ): """Process one archive directory. [ (imageCatalog is a birdimages.ImageCatalog instance) and (archNo is the numeric part of an archive directory name) -> sys.stdout +:= report listing image files in archive (archNo) that appear to be bird images but are not in imageCatalog thumbnail directory := thumbnail directory with thumbnail images made image files in archive (archNo) index directory := index directory with an arch-(archNo).xml file added describing image files in archive (archNo) ] """ #-- 1 -- # [ archDir := directory name for archive (archNo ) # imagexList := a new, empty list ] archDir = ARCHIVE_PREFIX + archNo imagexList = [] #-- 2 -- # [ fileList := list of files in directory (archDir), sorted fileList = os.listdir ( archDir ) fileList.sort() #-- 3 -- # [ imagexList +:= Imagex objects representing the # files in fileList that are valid bird images and # indexed in imageCatalog # sys.stdout +:= report of bird images in fileList that are # not in imageCatalog ] for fileName in fileList: #-- 3 loop -- # [ if (archDir+fileName) is a bird image in imageCatalog -> # imagexList +:= an Imagex object representing # that image # thumbnail directory +:= a thumbnail of that image # else if (archDir+fileName) is a bird image but not # in imageCatalog -> # sys.stdout +:= (message about uncataloged image) # else -> I ] #-- 3.1 -- # [ pathName := archDir + fileName ] pathName = os.path.join ( archDir, fileName ) #-- 3.2 -- # [ if pathName is a bird image in imageCatalog -> # thumbnail directory +:= a thumbnail of that # image # result := an Imagex object representing that # image # else if pathName is a bird image not in imageCatalog # or not a bird image -> # sys.stdout +:= error message # result := None # else -> # result := None ] result = processFile ( imageCatalog, pathName ) #-- 3.3 -- if result is not None: imagexList.append ( result ) #-- 4 -- # [ imagexList is a list of Imagex objects -> # index directory := index directory with an # (archDir+".xml") file added representing imagexList ] writeIndex ( archDir, imagexList ) # - - - p r o c e s s F i l e - - - def processFile ( imageCatalog, pathName ): """Check one file and, if valid, return an Imagex object. [ (imageCatalog is an ImageCatalog) and (pathName is a nonempty string) -> if (pathName looks like a bird image name) and (pathName is a catalog number in ImageCatalog) and (pathname names a readable image file) -> thumbnail directory +:= a thumbnail of that image return an Imagex object representing that image else if (pathName looks like a bird image name) and ((pathName is not a catalog number in ImageCatalog) or (pathname does not name a readable image file)) -> sys.stdout +:= error message return None else -> return None ] """ #-- 1 -- # [ baseName := pathName minus its path component and # file extension ] dirPath, fileName = os.path.split ( pathName ) baseName, extension = os.path.splitext ( fileName ) #-- 2 -- # [ if baseName is nonempty and starts with a letter -> # I # else -> return None ] if ( ( len(baseName) == 0 ) or ( not ( baseName[0].isdigit() ) ) ): return None #-- 3 -- # [ if baseName is a catalog number in imageCatalog -> # I # else -> # sys.stdout +:= error message # return None ] try: orig = imageCatalog.getOriginal ( baseName ) except KeyError: print "*** Uncataloged: %s" % pathName return None #-- 4 -- # [ if pathName names a readable, valid image file -> # result := an Image object representing that image # else -> raise IOError ] result = Imagex ( pathName ) #-- 5 -- # [ thumbPath := THUMB_DIR + baseName + THUMB_EXTENSION # result := result with its image replaced by a # thumbnail no larger than (THUMB_WIDE, THUMB_HIGH) ] thumbPath = "%s%s%s" % (THUMB_DIR, baseName, THUMB_EXTENSION) result.image.thumbnail ( (THUMB_WIDE, THUMB_HIGH) ) #-- 6 -- # [ if thumbPath names a file that can be created new -> # that file := result.image with its type determined # by thumbPath's extension # else -> raise IOError ] result.image.save ( thumbPath ) #-- 7 -- return result # - - - w r i t e I n d e x - - - def writeIndex ( archDir, imagexList ): """Generate the XML index file. [ (archNo is an archive directory name as a string) and (imagexList is a list of Imagex objects) -> index directory := index directory with an arch-(archNo).xml file added representing imagexList ] """ #-- 1 -- # [ doc := a new DOM Document object with root element # of type RNC_ARCHIVE_INDEX_N ] doc = xc.Document ( RNC_ARCHIVE_INDEX_N ) #-- 2 -- # [ imagexList is a list of Imagex objects -> # doc.root := doc.root with nodes added representing # those objects ] for imagex in imagexList: imagex.writeNode ( doc.root ) #-- 3 -- # [ index directory := index directory with an # (archDir+".xml") file added representing doc ] fileName = "%s%s.xml" % (INDEX_DIR, archDir) try: indexFile = open ( fileName, "w" ) except IOError, detail: print ( "*** Can't open index file '%s' for writing." % fileName ) return doc.write ( indexFile ) indexFile.close() # - - - - - c l a s s I m a g e x - - - - - class Imagex: """Represents information about one bird image. Exports: Imagex ( pathName ): [ (pathName is a string) -> if pathName names a readable, valid image file -> return a new Imagex object representing the image else -> raise IOError ] .pathName: [ as passed to constructor, read-only ] .baseName: [ self.pathName, stripped of its directory part and extension ] .image: [ the image as an Image.Image object ] .wide: [ width in pixels as an integer ] .high: [ height in pixels as an integer ] .writeNode ( parent ): [ parent is an xmlcreate.Element -> parent := parent with a new RNC_IMAGE_N node added representing self ] """ # - - - I m a g e x . _ _ i n i t _ _ - - - def __init__ ( self, pathName ): """Constructor for Imagex. """ #-- 1 -- # [ self.pathName = pathName # self.baseName = pathName, stripped of its directory # part and extension ] self.pathName = pathName discard, fileName = os.path.split ( pathName ) self.baseName, discard = os.path.splitext ( fileName ) #-- 2 -- # [ if pathName names a readable, valid image file -> # pic := an Image object representing that image # else -> # raise IOError ] self.image = Image.open ( pathName ) #-- 3 -- # [ self.size gives (width,height) in pixels -> # self.wide := that width as mm # self.high := that height as mm ] self.wide, self.high = self.image.size # - - - I m a g e x . w r i t e N o d e - - - def writeNode ( self, parent ): """Write an RNC_IMAGE_N node representing self. [ parent is an xmlcreate.Element object -> parent := parent with a new RNC_IMAGE_N node added representing self ] """ attrs = { RNC_CAT_NO_A: self.baseName, RNC_WIDE_A: str ( self.wide ), RNC_HIGH_A: str ( self.high ) } child = xc.Element ( parent, RNC_IMAGE_N, **attrs ) #================================================================ # Epilogue #---------------------------------------------------------------- if __name__ == "__main__": main()