degal/exif.py
author Tero Marttila <terom@fixme.fi>
Mon, 15 Jun 2009 00:23:55 +0300
changeset 122 292aaba6d6ec
parent 121 3bed35e79f41
child 130 94888270dae0
permissions -rw-r--r--
auto-orientation, although no mirroring support yet
"""
    The various Exif backends that we have
"""

from __future__ import with_statement

import utils

MIRROR_NONE         = 0
MIRROR_HORIZONTAL   = 1
MIRROR_VERTICAL     = 2

ROTATE_NONE         = 0
ROTATE_90           = 90
ROTATE_180          = 180
ROTATE_270          = 270

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_value (self, tag, default=None) :
        """
            Load and return the raw binary value for the given main image tag.
        """

        return default

    def image_tag (self, tag, default=None) :
        """
            Load and return the human-readable unicode-safe value for the given main image tag.
        """

        return default

    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
    

    def get_orientation (self) :
        """
            Get the orientation of the image in terms of mirroring and rotation in integer degrees clockwise.
            
            Returns a (MIRROR_*, ROTATE_*) tuple, or None if the Orientation tag could not be found.

            Returns None if it could not be found.
        """
        
        # load value
        orientation = self.image_tag_value('Orientation')

        # map to tuple or None
        # XXX: these are from EXIFpy, verify
        return {
            1:  (MIRROR_NONE,       ROTATE_NONE ),  # Horizontal (normal)
            2:  (MIRROR_HORIZONTAL, ROTATE_NONE ),  # Mirrored horizontal
            3:  (MIRROR_NONE,       ROTATE_180  ),  # Rotated 180
            4:  (MIRROR_VERTICAL,   ROTATE_NONE ),  # Mirrored vertical
            5:  (MIRROR_HORIZONTAL, ROTATE_270  ),  # Mirrored horizontal then rotated 90 CCW
            6:  (MIRROR_NONE,       ROTATE_90   ),  # Rotated 90 CW
            7:  (MIRROR_HORIZONTAL, ROTATE_90   ),  # Mirrored horizontal then rotated 90 CW
            8:  (MIRROR_NONE,       ROTATE_270  ),  # Rotated 90 CCW
        }.get(orientation, None)

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_value (self, tag, default=None) :
            # try with likely prefixes
            for prefix in ('Exif.Photo', 'Exif.Image') :
                try :
                    return self.image["%s.%s" % (prefix, tag)]
                
                except (IndexError, KeyError) :
                    # nope...
                    continue

            else :
                # not ofund
                return default

        def image_tag (self, tag, default=None) :
            # try with likely prefixes
            for prefix in ('Exif.Photo', 'Exif.Image') :
                try :
                    return self.image.interpretedExifValue("%s.%s" % (prefix, tag))
                
                except (IndexError, KeyError) :
                    # nope...
                    continue

            else :
                # not ofund
                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) :
            # try with likely prefixes
            for prefix in ('Image', 'EXIF') :
                name = prefix + ' ' + tag

                if name in self.tags :
                    return self.tags[name]

            else :
                # not found
                return 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