<reponame>bbinet/PyGall<filename>pygall/lib/imageprocessing.py<gh_stars>1-10
# -*- coding: utf-8 -*-
import os
import errno
from datetime import datetime
import shutil
import logging
from types import StringType, UnicodeType

import Image
import ExifTags

from pygall.lib.helpers import img_md5, seek, get_size

log = logging.getLogger(__name__)

# CONSTANTS
ORIG = "orig"
SCALED = "scaled"


def get_exif(info):
    ret = {}
    if info == None:
        raise Exception("can't get exif")
    for tag, value in info.items():
        decoded = ExifTags.TAGS.get(tag, tag)
        ret[decoded] = value
    return ret


def get_info(img, info=None):
    """
    Get infos about the given image
    """
    if info is None: info = {}
    loc = seek(img, 0)
    im = Image.open(img)
    if 'date' not in info:
        try:
            exif = get_exif(im._getexif())
            info['date'] = datetime.strptime(
                    exif['DateTimeOriginal'], '%Y:%m:%d %H:%M:%S')
        except:
            info['date'] = None
    if 'md5sum' not in info:
        info['md5sum'] = img_md5(im)
    if 'ext' not in info:
        info['ext'] = im.format.lower()
    if 'size' not in info:
        info['size'] = get_size(img)
    if loc is not None: seek(img, loc)

    return info


class ImageProcessing:
    def __init__(self,
                 dest_dir=None,
                 crop_dimension=700,
                 crop_quality=80):
        self.set_dest_dir(dest_dir)
        self.dimension = crop_dimension
        self.quality = crop_quality

    def set_dest_dir(self, dest_dir):
        if dest_dir is None:
            self.dest_dir = None
            self.abs_orig_dest_dir = None
            self.abs_scaled_dest_dir = None
        else:
            self.dest_dir = dest_dir
            self.abs_orig_dest_dir = os.path.join(self.dest_dir, ORIG)
            self.abs_scaled_dest_dir = os.path.join(self.dest_dir, SCALED)

    def copy_orig(self, src, uri):
        """
        Copy the original image to orig dest directory
        - src can be either a string or a file-like object
        """
        dest = os.path.join(self.abs_orig_dest_dir, uri)
        if os.path.exists(dest):
            # dest already exists: failed silently
            return False

        dirpath = os.path.dirname(dest)
        if not os.path.exists(dirpath):
            os.makedirs(dirpath, 0755)
        if isinstance(src, (StringType, UnicodeType)):
            shutil.copyfile(src, dest)
        else:
            with open(dest, 'wb') as f:
                loc = seek(src, 0)
                shutil.copyfileobj(src, f)
                src.seek(loc)
        log.info("Copied: %s" % dest)


    def copy_scaled(self, src, uri):
        """
        Rotate and scale image.
        Copy the processed image to scaled dest directory
        - src can be either a string or a file-like object
        """
        dest = os.path.join(self.abs_scaled_dest_dir, uri)
        if os.path.exists(dest):
            # dest already exists: failed silently
            return False

        loc = seek(src, 0)
        im = Image.open(src)
        try:
            orientation = get_exif(im._getexif())['Orientation']
        except:
            orientation = 0

        dirpath = os.path.dirname(dest)
        if not os.path.exists(dirpath):
            os.makedirs(dirpath, 0755)

        # auto rotate if needed
        if orientation == 6:
            im=im.rotate(270)
        if orientation == 8:
            im=im.rotate(90)

        width_src, height_src = im.size
        if width_src <= self.dimension and height_src <= self.dimension:
            log.info("Scale won't be processed: photo is to small.")
        else:
            if width_src > height_src:
                height_dest = self.dimension * height_src / width_src
                width_dest = self.dimension
            else:
                width_dest = self.dimension * width_src / height_src
                height_dest = self.dimension

            # Si on redimmensionne selon une taille paire, on force la
            # largeur et hauteur finales de l'image a etre egalement
            # paires.
            if self.dimension % 2 == 0:
                height_dest = height_dest - height_dest % 2
                width_dest = width_dest - width_dest % 2

            im=im.resize((width_dest, height_dest), Image.ANTIALIAS)
            log.info("Processed: %s" % dest)

        # save processed image
        im.save(dest, quality=self.quality)
        if loc is not None: seek(src, loc)


    def remove_image(self, uri):
        """
        Remove scaled and orig images associated with the given src image
        """
        for imgdir in (self.abs_scaled_dest_dir, self.abs_orig_dest_dir,):
            # remove image from imgdir
            try:
                os.unlink(os.path.join(imgdir, uri))
            except OSError, e:
                if e.errno != errno.ENOENT:
                    raise
            head, tail = os.path.split(uri)
            while head and tail:
                try:
                    os.rmdir(os.path.join(imgdir, head))
                except OSError:
                    break
                head, tail = os.path.split(head)


    def process_image(self, src, md5sum=None):
        """
        Standard processing for the given image:
        Built the destination relative path based on image timestamp
        Copy the original image to orig dest directory
        Copy the scaled and rotated image to scaled dest directory
        Remove the original image from disk
        """
        info = {}
        if md5sum is not None:
            info['md5sum'] = md5sum
        info = get_info(src, info)
        date = info['date'] if info['date'] is not None else datetime.now()
        uri = os.path.join(
            date.strftime("%Y"),
            date.strftime("%m"),
            date.strftime("%d"),
            info['md5sum'] + '.' + info['ext'])
        info['uri'] = uri
        try:
            self.copy_orig(src, uri)
            self.copy_scaled(src, uri)
        except Exception, e:
            # clean up if an exception occured during import
            self.remove_image(uri)
            raise e
        return info



ip = ImageProcessing()

# -*- coding: utf-8 -*-
import os
import errno
from datetime import datetime
import shutil
import logging
from types import StringType, UnicodeType

import Image
import ExifTags

from pygall.lib.helpers import img_md5, seek, get_size

log = logging.getLogger(__name__)

# CONSTANTS
ORIG = "orig"
SCALED = "scaled"


def get_exif(info):
    ret = {}
    if info == None:
        raise Exception("can't get exif")
    for tag, value in info.items():
        decoded = ExifTags.TAGS.get(tag, tag)
        ret[decoded] = value
    return ret


def get_info(img, info=None):
    """
    Get infos about the given image
    """
    if info is None: info = {}
    loc = seek(img, 0)
    im = Image.open(img)
    if 'date' not in info:
        try:
            exif = get_exif(im._getexif())
            info['date'] = datetime.strptime(
                    exif['DateTimeOriginal'], '%Y:%m:%d %H:%M:%S')
        except:
            info['date'] = None
    if 'md5sum' not in info:
        info['md5sum'] = img_md5(im)
    if 'ext' not in info:
        info['ext'] = im.format.lower()
    if 'size' not in info:
        info['size'] = get_size(img)
    if loc is not None: seek(img, loc)

    return info


class ImageProcessing:
    def __init__(self,
                 dest_dir=None,
                 crop_dimension=700,
                 crop_quality=80):
        self.set_dest_dir(dest_dir)
        self.dimension = crop_dimension
        self.quality = crop_quality

    def set_dest_dir(self, dest_dir):
        if dest_dir is None:
            self.dest_dir = None
            self.abs_orig_dest_dir = None
            self.abs_scaled_dest_dir = None
        else:
            self.dest_dir = dest_dir
            self.abs_orig_dest_dir = os.path.join(self.dest_dir, ORIG)
            self.abs_scaled_dest_dir = os.path.join(self.dest_dir, SCALED)

    def copy_orig(self, src, uri):
        """
        Copy the original image to orig dest directory
        - src can be either a string or a file-like object
        """
        dest = os.path.join(self.abs_orig_dest_dir, uri)
        if os.path.exists(dest):
            # dest already exists: failed silently
            return False

        dirpath = os.path.dirname(dest)
        if not os.path.exists(dirpath):
            os.makedirs(dirpath, 0755)
        if isinstance(src, (StringType, UnicodeType)):
            shutil.copyfile(src, dest)
        else:
            with open(dest, 'wb') as f:
                loc = seek(src, 0)
                shutil.copyfileobj(src, f)
                src.seek(loc)
        log.info("Copied: %s" % dest)


    def copy_scaled(self, src, uri):
        """
        Rotate and scale image.
        Copy the processed image to scaled dest directory
        - src can be either a string or a file-like object
        """
        dest = os.path.join(self.abs_scaled_dest_dir, uri)
        if os.path.exists(dest):
            # dest already exists: failed silently
            return False

        loc = seek(src, 0)
        im = Image.open(src)
        try:
            orientation = get_exif(im._getexif())['Orientation']
        except:
            orientation = 0

        dirpath = os.path.dirname(dest)
        if not os.path.exists(dirpath):
            os.makedirs(dirpath, 0755)

        # auto rotate if needed
        if orientation == 6:
            im=im.rotate(270)
        if orientation == 8:
            im=im.rotate(90)

        width_src, height_src = im.size
        if width_src <= self.dimension and height_src <= self.dimension:
            log.info("Scale won't be processed: photo is to small.")
        else:
            if width_src > height_src:
                height_dest = self.dimension * height_src / width_src
                width_dest = self.dimension
            else:
                width_dest = self.dimension * width_src / height_src
                height_dest = self.dimension

            # Si on redimmensionne selon une taille paire, on force la
            # largeur et hauteur finales de l'image a etre egalement
            # paires.
            if self.dimension % 2 == 0:
                height_dest = height_dest - height_dest % 2
                width_dest = width_dest - width_dest % 2

            im=im.resize((width_dest, height_dest), Image.ANTIALIAS)
            log.info("Processed: %s" % dest)

        # save processed image
        im.save(dest, quality=self.quality)
        if loc is not None: seek(src, loc)


    def remove_image(self, uri):
        """
        Remove scaled and orig images associated with the given src image
        """
        for imgdir in (self.abs_scaled_dest_dir, self.abs_orig_dest_dir,):
            # remove image from imgdir
            try:
                os.unlink(os.path.join(imgdir, uri))
            except OSError, e:
                if e.errno != errno.ENOENT:
                    raise
            head, tail = os.path.split(uri)
            while head and tail:
                try:
                    os.rmdir(os.path.join(imgdir, head))
                except OSError:
                    break
                head, tail = os.path.split(head)


    def process_image(self, src, md5sum=None):
        """
        Standard processing for the given image:
        Built the destination relative path based on image timestamp
        Copy the original image to orig dest directory
        Copy the scaled and rotated image to scaled dest directory
        Remove the original image from disk
        """
        info = {}
        if md5sum is not None:
            info['md5sum'] = md5sum
        info = get_info(src, info)
        date = info['date'] if info['date'] is not None else datetime.now()
        uri = os.path.join(
            date.strftime("%Y"),
            date.strftime("%m"),
            date.strftime("%d"),
            info['md5sum'] + '.' + info['ext'])
        info['uri'] = uri
        try:
            self.copy_orig(src, uri)
            self.copy_scaled(src, uri)
        except Exception, e:
            # clean up if an exception occured during import
            self.remove_image(uri)
            raise e
        return info



ip = ImageProcessing()
# -*- coding: utf-8 -*- # CONSTANTS Get infos about the given image Copy the original image to orig dest directory         - src can be either a string or a file-like object # dest already exists: failed silently Rotate and scale image.         Copy the processed image to scaled dest directory         - src can be either a string or a file-like object # dest already exists: failed silently # auto rotate if needed # Si on redimmensionne selon une taille paire, on force la # largeur et hauteur finales de l'image a etre egalement # paires. # save processed image Remove scaled and orig images associated with the given src image # remove image from imgdir Standard processing for the given image:         Built the destination relative path based on image timestamp         Copy the original image to orig dest directory         Copy the scaled and rotated image to scaled dest directory         Remove the original image from disk # clean up if an exception occured during import