from django.conf import settings import fstools import io import logging import mimetypes from ..models import get_item_type, TYPE_IMAGE, TYPE_VIDEO import os from PIL import Image, ImageEnhance, ExifTags import platform import pygal import subprocess # Get a logger instance logger = logging.getLogger("CACHING") def get_image_class(full_path): return { TYPE_IMAGE: image, TYPE_VIDEO: video, }.get(get_item_type(full_path), other) def get_image_instance(full_path, request): return get_image_class(full_path)(full_path, request) class mm_image(object): JOIN_TOP_LEFT = 1 JOIN_TOP_RIGHT = 2 JOIN_BOT_LEFT = 3 JOIN_BOT_RIGHT = 4 JOIN_CENTER = 5 def __init__(self, imagepath_handle_image): self.imagepath_handle_image = imagepath_handle_image self.__image__ = None # def __init_image__(self): if self.__image__ is None: if type(self.imagepath_handle_image) is mm_image: self.__image__ = self.imagepath_handle_image.__image__ elif type(self.imagepath_handle_image) is Image.Image: self.__image__ = self.imagepath_handle_image else: self.__image__ = Image.open(self.imagepath_handle_image) def orientation(self): self.__init_image__() exif_tags = dict((v, k) for k, v in ExifTags.TAGS.items()) try: return dict(self.__image__._getexif().items())[exif_tags['Orientation']] except AttributeError: return 1 except KeyError: return 1 def copy(self): self.__init_image__() return mm_image(self.__image__.copy()) def save(self, *args, **kwargs): self.__init_image__() im = self.__image__.copy().convert('RGB') im.save(*args, **kwargs) def resize(self, max_size): self.__init_image__() # # resize # x, y = self.__image__.size xy_max = max(x, y) self.__image__ = self.__image__.resize((int(x * float(max_size) / xy_max), int(y * float(max_size) / xy_max)), Image.NEAREST).rotate(0) def rotate_by_orientation(self, orientation): self.__init_image__() # # rotate # angle = {3: 180, 6: 270, 8: 90}.get(orientation) if angle is not None: self.__image__ = self.__image__.rotate(angle, expand=True) def __rgba_copy__(self): self.__init_image__() if self.__image__.mode != 'RGBA': return self.__image__.convert('RGBA') else: return self.__image__.copy() def join(self, image, joint_pos=JOIN_TOP_RIGHT, opacity=0.7): """ This joins another picture to this one. :param picture_edit picture: The picture to be joint. :param joint_pos: The position of picture in this picture. See also self.JOIN_* :param float opacity: The opacity of picture when joint (value between 0 and 1). .. note:: joint_pos makes only sense if picture is smaller than this picture. """ self.__init_image__() # im2 = image.__rgba_copy__() # change opacity of im2 alpha = im2.split()[3] alpha = ImageEnhance.Brightness(alpha).enhance(opacity) im2.putalpha(alpha) self.__image__ = self.__rgba_copy__() # create a transparent layer layer = Image.new('RGBA', self.__image__.size, (0, 0, 0, 0)) # draw im2 in layer if joint_pos == self.JOIN_TOP_LEFT: layer.paste(im2, (0, 0)) elif joint_pos == self.JOIN_TOP_RIGHT: layer.paste(im2, ((self.__image__.size[0] - im2.size[0]), 0)) elif joint_pos == self.JOIN_BOT_LEFT: layer.paste(im2, (0, (self.__image__.size[1] - im2.size[1]))) elif joint_pos == self.JOIN_BOT_RIGHT: layer.paste(im2, ((self.__image__.size[0] - im2.size[0]), (self.__image__.size[1] - im2.size[1]))) elif joint_pos == self.JOIN_CENTER: layer.paste(im2, (int((self.__image__.size[0] - im2.size[0]) / 2), int((self.__image__.size[1] - im2.size[1]) / 2))) self.__image__ = Image.composite(layer, self.__image__, layer) def image_data(self): self.__init_image__() # # create return value # im = self.__image__.copy().convert('RGB') output = io.BytesIO() im.save(output, format='JPEG') return output.getvalue() class mm_video(object): def __init__(self, full_path): self.full_path = full_path def image(self): if platform.system() == 'Linux': cmd = 'ffmpeg -ss 0.5 -i "' + self.full_path + '" -vframes 1 -f image2pipe pipe:1 2> /dev/null' else: cmd = 'ffmpeg -ss 0.5 -i "' + self.full_path + '" -vframes 1 -f image2pipe pipe:1 2> NULL' data = subprocess.check_output(cmd, shell=True) ffmpeg_handle = io.BytesIO(data) im = Image.open(ffmpeg_handle) return mm_image(im.copy()) class base_item(object): MIME_TYPES = { '.ada': 'text/x/adasrc', '.hex': 'text/x/hex', '.jpg': 'image/x/generic', '.jpeg': 'image/x/generic', '.jpe': 'image/x/generic', '.png': 'image/x/generic', '.tif': 'image/x/generic', '.tiff': 'image/x/generic', '.gif': 'image/x/generic', '.avi': 'video/x/generic', '.mpg': 'video/x/generic', '.mpeg': 'video/x/generic', '.mpe': 'video/x/generic', '.mov': 'video/x/generic', '.qt': 'video/x/generic', '.mp4': 'video/x/generic', '.webm': 'video/x/generic', '.ogv': 'video/x/generic', '.flv': 'video/x/generic', '.3gp': 'video/x/generic', } def __init__(self, full_path, request): self.full_path = full_path self.request = request self.rel_path = pygal.get_rel_path(full_path) # ext = os.path.splitext(self.full_path)[1].lower() self.mime_type = self.MIME_TYPES.get(ext, mimetypes.types_map.get(ext, 'unknown')) def __cache_image_folder__(self, rel_path): return os.path.join(settings.XNAIL_ROOT, rel_path.replace('_', '__').replace('/', '_')) def __cache_image_name__(self, max_size): filename = '%04d_%02x_%s.jpg' % (max_size, self.XNAIL_VERSION_NUMBER, fstools.uid(self.full_path, None)) return os.path.join(self.__cache_image_folder__(self.rel_path), filename) def __delete_cache_image__(self, max_size): for fn in fstools.filelist(self.__cache_image_folder__(self.rel_path), '%04d*' % max_size): try: os.remove(fn) except OSError: pass # possibly file is already removed by another process class image(base_item, mm_image): XNAIL_VERSION_NUMBER = 1 def __init__(self, *args, **kwargs): base_item.__init__(self, *args, **kwargs) mm_image.__init__(self, self.full_path) self.mime_type_xnails = mimetypes.types_map.get('.jpg', 'unknown') def get_resized_image_data(self, max_size): cache_filename = self.__cache_image_name__(max_size) if not os.path.exists(cache_filename): logger.info('Creating xnail-%d for %s', max_size, self.rel_path) self.__delete_cache_image__(max_size) im = self.copy() im.resize(max_size) im.rotate_by_orientation(self.orientation()) # # create cache file # fstools.mkdir(os.path.dirname(cache_filename)) with open(cache_filename, 'wb') as fh: im.save(fh, format='JPEG') # return im.image_data() else: return open(cache_filename, 'rb').read() def thumbnail_picture(self): return self.get_resized_image_data(pygal.get_thumbnail_max_size(self.request)) def webnail_picture(self): return self.get_resized_image_data(pygal.get_webnail_size(self.request)) class video(base_item, mm_video): XNAIL_VERSION_NUMBER = 1 def __init__(self, *args, **kwargs): base_item.__init__(self, *args, **kwargs) mm_video.__init__(self, self.full_path) self.mime_type_xnails = mimetypes.types_map.get('.jpg', 'unknown') def get_resized_image_data(self, max_size): cache_filename = self.__cache_image_name__(max_size) if not os.path.exists(cache_filename): logger.info('Creating xnail-%d for %s', max_size, self.rel_path) self.__delete_cache_image__(max_size) im = self.image() im.resize(max_size) overlay = mm_image(os.path.join(os.path.dirname(__file__), 'video.png')) im.join(overlay) # # create cache file # fstools.mkdir(os.path.dirname(cache_filename)) with open(cache_filename, 'wb') as fh: im.save(fh, format='JPEG') # return im.image_data() else: return open(cache_filename, 'rb').read() def thumbnail_picture(self): return image.thumbnail_picture(self) def webnail_picture(self): return image.webnail_picture(self) class other(base_item): def __init__(self, *args, **kwargs): base_item.__init__(self, *args, **kwargs) self.mime_type_xnails = mimetypes.types_map.get('.png', 'unknown') def thumbnail_picture(self): fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'mimetype_icons', '%s.png' % (self.mime_type).replace('/', '-')) if not os.path.exists(fn): fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'mimetype_icons', 'unknown.png') return open(fn, 'rb').read() def webnail_picture(self): return self.thumbnail_picture()