# HG changeset patch # User Tero Marttila # Date 1245012220 -10800 # Node ID 55cb7fc9c8fbc950fab0939f1a62036fb0aa077c # Parent e7855eefb4c7f0c749c22a6fd7465c923404848c add new exif.py to abstract between different exif libraries, and add partially working support for pyexiv2 and EXIFpy diff -r e7855eefb4c7 -r 55cb7fc9c8fb degal/command.py --- a/degal/command.py Sun Jun 14 22:59:29 2009 +0300 +++ b/degal/command.py Sun Jun 14 23:43:40 2009 +0300 @@ -91,6 +91,7 @@ except : # dump traceback + # XXX: skip all crap up to the actual function self.handle_error() def log_msg (self, level, msg, *args, **kwargs) : diff -r e7855eefb4c7 -r 55cb7fc9c8fb degal/config.py --- a/degal/config.py Sun Jun 14 22:59:29 2009 +0300 +++ b/degal/config.py Sun Jun 14 23:43:40 2009 +0300 @@ -109,6 +109,12 @@ # load Exif data for images, this may be slow with_exif = False + # name of Exif handler to use + exif_handler_name = None + + # explicit Exif handler class + exif_handler = None + # exif tags used in output # Copyright (C) 2008, Santtu Pajukanta # XXX: import from dexif? diff -r e7855eefb4c7 -r 55cb7fc9c8fb degal/exif.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/degal/exif.py Sun Jun 14 23:43:40 2009 +0300 @@ -0,0 +1,137 @@ +""" + The various Exif backends that we have +""" + +from __future__ import with_statement + +import utils + +class ExifHandler (object) : + """ + Our generic interface for Exif data + """ + + def __init__ (self, file) : + """ + Load Exif data from the given File. + """ + + self.file = file + + def image_tag (self, tag, default=None) : + """ + Load and return the human-readable unicode-safe value for the given main image tag. + """ + + abstract + + def image_tags (self, tags) : + """ + Load and return the given tags for the main image as a sequence of (tag, value) tuples , following the same + rules as image_tag, except there should mainly be only one call to this function, encompassing all the + relevant tags. + + The returned list should omit those values which were not found. + """ + + for tag in tags : + # try and fetch value + value = self.image_tag(tag) + + # filter out default not-found + if value is not None : + yield tag, value + +try : + import pyexiv2 + +except ImportError : + PyExiv2_Handler = None + +else : + class PyExiv2_Handler (ExifHandler) : + NAME = 'pyexiv2' + + def __init__ (self, file) : + """ + Load as pyexiv2.Image + """ + + # create + self.image = pyexiv2.Image(file.path) + + # load + self.image.readMetadata() + + def image_tag (self, tag, default=None) : + try : + # XXX: this is wrong + return self.image.interpretedExifValue("Exif.Photo.%s" % tag) + + except (IndexError, KeyError) : + return default + +try : + from lib import EXIF + +except ImportError : + EXIF_Handler = None + +else : + class EXIF_Handler (ExifHandler) : + NAME = 'EXIFpy' + + def __init__ (self, file) : + """ + Load using EXIF.process_file + """ + + with file.open('rb') as fh : + self.tags = EXIF.process_file(fh) + + def image_tag (self, tag, default=None) : + # XXX: this is wrong + return self.tags.get('Image %s' % (tag, ), default) + +# ExifHandler implementations to use, in preference order +EXIF_HANDLERS = [ + # reasonably fast, but requires the native library and extension module + PyExiv2_Handler, + + # pure-python, but very, very slow + EXIF_Handler, +] + +# default ExifHandler to use +EXIF_HANDLER = utils.first(EXIF_HANDLERS) + +# ExifHandler implementations by name +EXIF_HANDLERS_BY_NAME = dict((h.NAME, h) for h in EXIF_HANDLERS) + +def load (config, file) : + """ + Load Exif data using the configured Exif handler, and return the instance. + + Returns None if there wasn't any Exif handler available for use. + """ + + if config.exif_handler : + # explicit + exif_handler = config.exif_handler + + elif config.exif_handler_name : + # by name + exif_handler = EXIF_HANDLERS_BY_NAME[config.exif_handler_name] + + else : + # default + exif_handler = EXIF_HANDLER + + if exif_handler : + # found one, use it + return exif_handler(file) + + else : + # nothing :( + return None + diff -r e7855eefb4c7 -r 55cb7fc9c8fb degal/image.py --- a/degal/image.py Sun Jun 14 22:59:29 2009 +0300 +++ b/degal/image.py Sun Jun 14 23:43:40 2009 +0300 @@ -2,13 +2,10 @@ Per-image gallery state """ -from __future__ import with_statement - -import filesystem, format, thumbnail +import filesystem, format, thumbnail, exif from utils import lazy_load import PIL.Image -from lib import EXIF class Image (filesystem.File) : """ @@ -57,22 +54,13 @@ @lazy_load def exif (self) : """ - Loads the EXIF data for the image and returns as a dict of EXIF tag names -> values. + Loads the ExifHandler object for this image. Returns None if no exif data was found """ - # load - with self.open('rb') as fh : - # XXX: details=False? - exif = EXIF.process_file(fh) - - # empty dict -> no exif data - if exif : - return exif - - else : - return None + # look up using exif + return exif.load(self.config, self) @lazy_load def metadata (self) : @@ -87,20 +75,21 @@ size = self.img_size # build - metadata = dict({ - "File name": self.name, - "Resolution": "%dx%d" % size, - "File size": format.filesize(stat.st_size), - "Last modified": format.filetime(stat.st_mtime), - }) + metadata = [ + ("File name", self.name), + ("Resolution", "%dx%d" % size), + ("File size", format.filesize(stat.st_size)), + ("Last modified", format.filetime(stat.st_mtime)), + ] # optionally load Exif metadata - if self.config.with_exif : - exif = self.exif + if self.config.with_exif and self.exif : + exif_tag_name = dict(self.config.exif_tags) + exif_tags = (tag for tag, name in self.config.exif_tags) - # Get the wanted tags - metadata.update(dict( - (name, exif[tag]) for tag, name in self.config.exif_tags if exif and tag in exif + # merge the wanted tags + metadata.extend(( + (exif_tag_name[tag], value) for tag, value in self.exif.image_tags(exif_tags) )) return metadata diff -r e7855eefb4c7 -r 55cb7fc9c8fb degal/main.py --- a/degal/main.py Sun Jun 14 22:59:29 2009 +0300 +++ b/degal/main.py Sun Jun 14 23:43:40 2009 +0300 @@ -38,6 +38,9 @@ parser.add_option("--with-exif", action="store_true", help="Include Exif metadata in updated .html files") + + parser.add_option("--exif-handler", metavar='NAME', dest="exif_handler_name", + help="Use named Exif handler: pyexiv2, EXIFpy") parser.add_option('-c', "--thread-count", metavar='COUNT', type="int", help="Use COUNT threads for concurrent tasks") diff -r e7855eefb4c7 -r 55cb7fc9c8fb degal/templates.py --- a/degal/templates.py Sun Jun 14 22:59:29 2009 +0300 +++ b/degal/templates.py Sun Jun 14 23:43:40 2009 +0300 @@ -51,7 +51,7 @@ # extended info, metadata tags.div(id_='info')(*( - tags.p(("%s: " % name), value) for name, value in image.metadata.iteritems() + tags.p(("%s: " % name), value) for name, value in image.metadata )), ] diff -r e7855eefb4c7 -r 55cb7fc9c8fb degal/utils.py --- a/degal/utils.py Sun Jun 14 22:59:29 2009 +0300 +++ b/degal/utils.py Sun Jun 14 23:43:40 2009 +0300 @@ -77,6 +77,26 @@ lazy_load = LazyProperty lazy_load_iter = LazyIteratorProperty +def first (iterable) : + """ + Returns the first item from the iterable that evaluates to True, otherwise None. + + >>> first((0, 1)) + 1 + >>> first("abc") + 'a' + >>> first(('', list(), (), False)) + None + """ + + for item in iterable : + if item : + return item + + else : + return None + + # testing if __name__ == '__main__' : import doctest