add new exif.py to abstract between different exif libraries, and add partially working support for pyexiv2 and EXIFpy
--- 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) :
--- 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 <santtu@pajukanta.fi>
# XXX: import from dexif?
--- /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
+
--- 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
--- 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")
--- 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
)),
]
--- 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