media/__init__.py

222 lines
6.9 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
"""
media (Media Tools)
===================
**Author:**
* Dirk Alders <sudo-dirk@mount-mockery.de>
**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°', 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