123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- 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()
|