#!/usr/bin/env python # -*- coding: utf-8 -*- # """ media (Media Tools) =================== **Author:** * Dirk Alders **Description:** This module helps on all issues with media files, like tags (e.g. exif, id3) and transformations. **Submodules:** * :func:`media.get_media_data` * :class:`media.image` **Unittest:** See also the :download:`unittest <../../media/_testresults_/unittest.pdf>` documentation. """ __DEPENDENCIES__ = [] import io import logging from PIL import Image, ImageEnhance, ExifTags logger_name = 'MEDIA' logger = logging.getLogger(logger_name) __DESCRIPTION__ = """The Module {\\tt %s} is designed to help on all issues with media files, like tags (e.g. exif, id3) and transformations. For more Information read the documentation.""" % __name__.replace('_', '\_') """The Module Description""" __INTERPRETER__ = (3, ) """The Tested Interpreter-Versions""" KEY_ALBUM = 'album' KEY_APERTURE = 'aperture' KEY_ARTIST = 'artist' KEY_BITRATE = 'bitrate' KEY_CAMERA = 'camera' KEY_DURATION = 'duration' KEY_EXPOSURE_PROGRAM = 'exposure_program' KEY_EXPOSURE_TIME = 'exposure_time' KEY_FLASH = 'flash' KEY_FOCAL_LENGTH = 'focal_length' KEY_GENRE = 'genre' KEY_GPS = 'gps' KEY_HEIGHT = 'height' KEY_ISO = 'iso' KEY_ORIENTATION = 'orientation' KEY_RATIO = 'ratio' KEY_SIZE = 'size' KEY_TIME = 'time' # USE time.localtime(value) or datetime.fromtimestamp(value) to convert the timestamp KEY_TIME_IS_SUBSTITUTION = 'tm_is_subst' KEY_TITLE = 'title' KEY_TRACK = 'track' KEY_WIDTH = 'width' KEY_YEAR = 'year' def get_media_data(full_path): from media.metadata import get_audio_data, get_image_data, get_video_data from media.common import get_filetype, FILETYPE_AUDIO, FILETYPE_IMAGE, FILETYPE_VIDEO # ft = get_filetype(full_path) # if ft == FILETYPE_AUDIO: return get_audio_data(full_path) elif ft == FILETYPE_IMAGE: return get_image_data(full_path) elif ft == FILETYPE_VIDEO: return get_video_data(full_path) else: logger.warning('Filetype not known: %s', full_path) ORIENTATION_NORMAL = 1 ORIENTATION_VERTICAL_MIRRORED = 2 ORIENTATION_HALF_ROTATED = 3 ORIENTATION_HORIZONTAL_MIRRORED = 4 ORIENTATION_LEFT_ROTATED = 6 ORIENTATION_RIGHT_ROTATED = 8 JOIN_TOP_LEFT = 1 JOIN_TOP_RIGHT = 2 JOIN_BOT_LEFT = 3 JOIN_BOT_RIGHT = 4 JOIN_CENTER = 5 class image(object): def __init__(self, media_instance=None): if media_instance is not None: self.load_from_file(media_instance) else: self._im = None def load_from_file(self, media_instance): from media.convert import get_pil_image # self._im = get_pil_image(media_instance) if self._im is None: return False try: self._exif = dict(self._im._getexif().items()) except AttributeError: self._exif = {} if type(self._im) is not Image.Image: self._im = self._im.copy() logger.debug('loading image from %s', repr(media_instance)) return True def save(self, full_path): if self._im is None: logger.warning('No image available to be saved (%s)', repr(full_path)) return False else: logger.debug('Saving image to %s', repr(full_path)) with open(full_path, 'w') as fh: im = self._im.convert('RGB') im.save(fh, 'JPEG') return True def image_data(self): im = self._im.copy().convert('RGB') output = io.BytesIO() im.save(output, format='JPEG') return output.getvalue() def resize(self, max_size): if self._im is None: logger.warning('No image available to be resized') return False else: logger.debug('Resizing picture to max %d pixel in whatever direction', max_size) x, y = self._im.size xy_max = max(x, y) self._im = self._im.resize((int(x * float(max_size) / xy_max), int(y * float(max_size) / xy_max)), Image.NEAREST).rotate(0) return True def rotate_by_orientation(self, orientation=None): if self._im is None: logger.warning('No image available, rotation not possible') return False if orientation is None: exif_tags = dict((v, k) for k, v in ExifTags.TAGS.items()) try: orientation = self._exif[exif_tags['Orientation']] logger.debug("No orientation given, orientation %s extract from exif data", repr(orientation)) except KeyError: return False if orientation == ORIENTATION_HALF_ROTATED: angle = 180 elif orientation == ORIENTATION_LEFT_ROTATED: angle = 270 elif orientation == ORIENTATION_RIGHT_ROTATED: angle = 90 else: if type(orientation) == int and orientation > 8: logger.warning('Orientation %s unknown for rotation', repr(orientation)) return False logger.debug('Rotating picture by %d (deg)', angle) self._im = self._im.rotate(angle, expand=True) return True def join(self, join_image, join_pos=JOIN_TOP_RIGHT, opacity=0.7): from media.convert import get_pil_image def rgba_copy(im): if im.mode != 'RGBA': return im.convert('RGBA') else: return im.copy() if self._im is None: logger.warning('No image available, joining not possible') return False # ensure type of join_image is PIL.Image join_image = get_pil_image(join_image) if join_image is None: logger.warning('Image to be joined is not supported %s', repr(join_image)) return False im2 = rgba_copy(join_image) # change opacity of im2 alpha = im2.split()[3] alpha = ImageEnhance.Brightness(alpha).enhance(opacity) im2.putalpha(alpha) self._im = rgba_copy(self._im) # create a transparent layer layer = Image.new('RGBA', self._im.size, (0, 0, 0, 0)) # draw im2 in layer if join_pos == JOIN_TOP_LEFT: layer.paste(im2, (0, 0)) elif join_pos == JOIN_TOP_RIGHT: layer.paste(im2, ((self._im.size[0] - im2.size[0]), 0)) elif join_pos == JOIN_BOT_LEFT: layer.paste(im2, (0, (self._im.size[1] - im2.size[1]))) elif join_pos == JOIN_BOT_RIGHT: layer.paste(im2, ((self._im.size[0] - im2.size[0]), (self._im.size[1] - im2.size[1]))) elif join_pos == JOIN_CENTER: layer.paste(im2, (int((self._im.size[0] - im2.size[0]) / 2), int((self._im.size[1] - im2.size[1]) / 2))) else: logger.warning("Join position value %s is not supported", join_pos) return False logger.debug('Joining two images') self._im = Image.composite(layer, self._im, layer) return True