Release: d4bf62e70e70b47431f7471f25637ecf

This commit is contained in:
Dirk Alders 2020-02-01 20:12:25 +01:00
parent 488c70884b
commit aa39140bc8
7 changed files with 4773 additions and 716 deletions

View File

@ -16,6 +16,7 @@ media (Media Tools)
**Submodules:**
* :func:`media.get_media_data`
* :class:`media.image`
**Unittest:**
@ -24,7 +25,7 @@ media (Media Tools)
__DEPENDENCIES__ = []
import logging
from media import metadata
from PIL import Image, ImageEnhance
logger_name = 'MEDIA'
logger = logging.getLogger(logger_name)
@ -37,14 +38,161 @@ __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):
ft = metadata.get_filetype(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
#
if ft == metadata.FILETYPE_AUDIO:
return metadata.get_audio_data(full_path)
elif ft == metadata.FILETYPE_IMAGE:
return metadata.get_image_data(full_path)
elif ft == metadata.FILETYPE_VIDEO:
return metadata.get_video_data(full_path)
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
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 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):
if self._im is None:
logger.warning('No image available, rotation not possible')
return False
if orientation == ORIENTATION_HALF_ROTATED:
angle = 180
elif orientation == ORIENTATION_LEFT_ROTATED:
angle = 270
elif orientation == ORIENTATION_RIGHT_ROTATED:
angle = 90
else:
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

View File

@ -1,34 +1,168 @@
<?xml version="1.0" ?>
<coverage branch-rate="0.9762" branches-covered="41" branches-valid="42" complexity="0" line-rate="0.9857" lines-covered="207" lines-valid="210" timestamp="1580454618005" version="4.5">
<coverage branch-rate="0.9643" branches-covered="81" branches-valid="84" complexity="0" line-rate="0.9759" lines-covered="324" lines-valid="332" timestamp="1580584174451" version="4.5">
<!-- Generated by coverage.py: https://coverage.readthedocs.io -->
<!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
<sources/>
<packages>
<package branch-rate="0.9762" complexity="0" line-rate="0.9857" name=".user_data.data.dirk.prj.unittest.media.pylibs.media">
<package branch-rate="0.9643" complexity="0" line-rate="0.9759" name=".user_data.data.dirk.prj.unittest.media.pylibs.media">
<classes>
<class branch-rate="1" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/__init__.py" line-rate="1" name="__init__.py">
<class branch-rate="0.9737" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/__init__.py" line-rate="0.9924" name="__init__.py">
<methods/>
<lines>
<line hits="1" number="4"/>
<line hits="1" number="24"/>
<line hits="1" number="26"/>
<line hits="1" number="25"/>
<line hits="1" number="27"/>
<line hits="1" number="29"/>
<line hits="1" number="28"/>
<line hits="1" number="30"/>
<line hits="1" number="33"/>
<line hits="1" number="36"/>
<line hits="1" number="40"/>
<line hits="1" number="31"/>
<line hits="1" number="34"/>
<line hits="1" number="37"/>
<line hits="1" number="41"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="43"/>
<line hits="1" number="42"/>
<line hits="1" number="43"/>
<line hits="1" number="44"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="45"/>
<line hits="1" number="45"/>
<line hits="1" number="46"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="47"/>
<line hits="1" number="47"/>
<line hits="1" number="48"/>
<line hits="1" number="49"/>
<line hits="1" number="50"/>
<line hits="1" number="51"/>
<line hits="1" number="52"/>
<line hits="1" number="53"/>
<line hits="1" number="54"/>
<line hits="1" number="55"/>
<line hits="1" number="56"/>
<line hits="1" number="57"/>
<line hits="1" number="58"/>
<line hits="1" number="59"/>
<line hits="1" number="60"/>
<line hits="1" number="61"/>
<line hits="1" number="62"/>
<line hits="1" number="63"/>
<line hits="1" number="66"/>
<line hits="1" number="67"/>
<line hits="1" number="68"/>
<line hits="1" number="70"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="72"/>
<line hits="1" number="73"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="74"/>
<line hits="1" number="75"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="76"/>
<line hits="1" number="77"/>
<line hits="1" number="79"/>
<line hits="1" number="82"/>
<line hits="1" number="83"/>
<line hits="1" number="84"/>
<line hits="1" number="85"/>
<line hits="1" number="86"/>
<line hits="1" number="87"/>
<line hits="1" number="89"/>
<line hits="1" number="90"/>
<line hits="1" number="91"/>
<line hits="1" number="92"/>
<line hits="1" number="93"/>
<line hits="1" number="96"/>
<line hits="1" number="97"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="98"/>
<line hits="1" number="99"/>
<line hits="1" number="101"/>
<line hits="1" number="103"/>
<line hits="1" number="104"/>
<line hits="1" number="106"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="107"/>
<line hits="1" number="108"/>
<line hits="1" number="109"/>
<line hits="1" number="111"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="112"/>
<line hits="1" number="113"/>
<line hits="1" number="114"/>
<line hits="1" number="116"/>
<line hits="1" number="117"/>
<line hits="1" number="118"/>
<line hits="1" number="119"/>
<line hits="1" number="120"/>
<line hits="1" number="122"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="123"/>
<line hits="1" number="124"/>
<line hits="1" number="125"/>
<line hits="1" number="127"/>
<line hits="1" number="128"/>
<line hits="1" number="129"/>
<line hits="1" number="130"/>
<line hits="1" number="131"/>
<line hits="1" number="133"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="134"/>
<line hits="1" number="135"/>
<line hits="1" number="136"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="138"/>
<line hits="1" number="139"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="140"/>
<line hits="1" number="141"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="142"/>
<line hits="1" number="143"/>
<line hits="1" number="145"/>
<line hits="1" number="146"/>
<line hits="1" number="147"/>
<line hits="1" number="148"/>
<line hits="1" number="149"/>
<line hits="1" number="151"/>
<line hits="1" number="152"/>
<line hits="1" number="154"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="158" number="155"/>
<line hits="1" number="156"/>
<line hits="0" number="158"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="160"/>
<line hits="1" number="161"/>
<line hits="1" number="162"/>
<line hits="1" number="165"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="166"/>
<line hits="1" number="167"/>
<line hits="1" number="168"/>
<line hits="1" number="170"/>
<line hits="1" number="172"/>
<line hits="1" number="173"/>
<line hits="1" number="174"/>
<line hits="1" number="176"/>
<line hits="1" number="179"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="181"/>
<line hits="1" number="182"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="183"/>
<line hits="1" number="184"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="185"/>
<line hits="1" number="186"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="187"/>
<line hits="1" number="188"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="189"/>
<line hits="1" number="190"/>
<line hits="1" number="192"/>
<line hits="1" number="193"/>
<line hits="1" number="195"/>
<line hits="1" number="196"/>
<line hits="1" number="198"/>
</lines>
</class>
<class branch-rate="0.9722" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/metadata.py" line-rate="0.9845" name="metadata.py">
<class branch-rate="1" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/common.py" line-rate="1" name="common.py">
<methods/>
<lines>
<line hits="1" number="1"/>
<line hits="1" number="3"/>
<line hits="1" number="4"/>
<line hits="1" number="5"/>
<line hits="1" number="7"/>
<line hits="1" number="8"/>
<line hits="1" number="9"/>
<line hits="1" number="12"/>
<line hits="1" number="13"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="14"/>
<line hits="1" number="15"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="16"/>
<line hits="1" number="17"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="18"/>
<line hits="1" number="19"/>
</lines>
</class>
<class branch-rate="0.9" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/convert.py" line-rate="0.8667" name="convert.py">
<methods/>
<lines>
<line hits="1" number="1"/>
@ -38,12 +172,46 @@
<line hits="1" number="5"/>
<line hits="1" number="8"/>
<line hits="1" number="9"/>
<line hits="1" number="10"/>
<line hits="1" number="11"/>
<line hits="1" number="12"/>
<line hits="1" number="13"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="14"/>
<line hits="1" number="15"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="16"/>
<line hits="1" number="17"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="18"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="22" number="19"/>
<line hits="1" number="20"/>
<line hits="0" number="22"/>
<line hits="1" number="23"/>
<line hits="1" number="24"/>
<line hits="0" number="25"/>
<line hits="0" number="26"/>
<line hits="0" number="27"/>
<line hits="1" number="28"/>
<line hits="1" number="29"/>
<line hits="1" number="30"/>
<line hits="1" number="31"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="32"/>
<line hits="1" number="33"/>
<line hits="1" number="35"/>
</lines>
</class>
<class branch-rate="0.9667" complexity="0" filename="/user_data/data/dirk/prj/unittest/media/pylibs/media/metadata.py" line-rate="0.9808" name="metadata.py">
<methods/>
<lines>
<line hits="1" number="1"/>
<line hits="1" number="2"/>
<line hits="1" number="3"/>
<line hits="1" number="4"/>
<line hits="1" number="5"/>
<line hits="1" number="6"/>
<line hits="1" number="9"/>
<line hits="1" number="12"/>
<line hits="1" number="13"/>
<line hits="1" number="16"/>
<line hits="1" number="17"/>
<line hits="1" number="18"/>
<line hits="1" number="19"/>
<line hits="1" number="20"/>
<line hits="1" number="21"/>
@ -58,8 +226,6 @@
<line hits="1" number="30"/>
<line hits="1" number="31"/>
<line hits="1" number="32"/>
<line hits="1" number="33"/>
<line hits="1" number="34"/>
<line hits="1" number="35"/>
<line hits="1" number="36"/>
<line hits="1" number="37"/>
@ -67,163 +233,129 @@
<line hits="1" number="39"/>
<line hits="1" number="40"/>
<line hits="1" number="41"/>
<line hits="1" number="42"/>
<line hits="1" number="43"/>
<line hits="1" number="44"/>
<line hits="1" number="47"/>
<line hits="1" number="48"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="49"/>
<line hits="1" number="50"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="51"/>
<line hits="1" number="51"/>
<line hits="1" number="52"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="53"/>
<line hits="1" number="54"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="54"/>
<line hits="1" number="55"/>
<line hits="1" number="56"/>
<line hits="1" number="57"/>
<line hits="1" number="58"/>
<line hits="1" number="59"/>
<line hits="1" number="60"/>
<line hits="1" number="61"/>
<line hits="1" number="62"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="59"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="60"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="61"/>
<line hits="1" number="63"/>
<line hits="1" number="64"/>
<line hits="1" number="65"/>
<line hits="1" number="66"/>
<line hits="1" number="67"/>
<line hits="1" number="68"/>
<line hits="1" number="69"/>
<line hits="1" number="70"/>
<line hits="1" number="71"/>
<line hits="1" number="72"/>
<line hits="1" number="73"/>
<line hits="1" number="74"/>
<line hits="1" number="75"/>
<line hits="1" number="76"/>
<line hits="1" number="77"/>
<line hits="1" number="78"/>
<line hits="1" number="79"/>
<line hits="1" number="80"/>
<line hits="1" number="81"/>
<line hits="1" number="82"/>
<line hits="1" number="83"/>
<line hits="1" number="84"/>
<line hits="1" number="85"/>
<line hits="1" number="88"/>
<line hits="1" number="89"/>
<line hits="0" number="86"/>
<line hits="0" number="87"/>
<line hits="0" number="88"/>
<line hits="1" number="90"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="91"/>
<line hits="1" number="92"/>
<line hits="1" number="93"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="95"/>
<line hits="1" number="96"/>
<line hits="1" number="97"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="93"/>
<line hits="1" number="94"/>
<line hits="1" number="95"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="97"/>
<line hits="1" number="98"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="100"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="101"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="102"/>
<line hits="1" number="104"/>
<line hits="1" number="105"/>
<line hits="1" number="99"/>
<line hits="1" number="100"/>
<line hits="1" number="101"/>
<line hits="1" number="102"/>
<line hits="1" number="103"/>
<line hits="1" number="106"/>
<line hits="1" number="107"/>
<line hits="1" number="108"/>
<line hits="1" number="109"/>
<line hits="1" number="110"/>
<line hits="1" number="111"/>
<line hits="1" number="112"/>
<line hits="1" number="115"/>
<line hits="1" number="114"/>
<line hits="1" number="116"/>
<line hits="1" number="117"/>
<line hits="1" number="118"/>
<line hits="1" number="119"/>
<line hits="1" number="120"/>
<line hits="1" number="121"/>
<line hits="1" number="122"/>
<line hits="1" number="123"/>
<line hits="1" number="124"/>
<line hits="1" number="125"/>
<line hits="1" number="126"/>
<line hits="0" number="127"/>
<line hits="0" number="128"/>
<line hits="0" number="129"/>
<line hits="1" number="127"/>
<line hits="1" number="128"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="129"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="130"/>
<line hits="1" number="131"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="132"/>
<line hits="1" number="133"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="134"/>
<line hits="1" number="132"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="133"/>
<line hits="1" number="134"/>
<line hits="1" number="135"/>
<line hits="1" number="136"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="138"/>
<line hits="1" number="139"/>
<line hits="1" number="140"/>
<line hits="1" number="141"/>
<line hits="1" number="142"/>
<line hits="1" number="143"/>
<line hits="1" number="144"/>
<line hits="1" number="145"/>
<line hits="1" number="146"/>
<line hits="1" number="147"/>
<line hits="1" number="148"/>
<line hits="1" number="149"/>
<line hits="1" number="150"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="150"/>
<line hits="1" number="151"/>
<line hits="1" number="152"/>
<line hits="1" number="153"/>
<line hits="1" number="155"/>
<line hits="1" number="156"/>
<line hits="1" number="157"/>
<line hits="1" number="158"/>
<line hits="1" number="159"/>
<line hits="1" number="160"/>
<line hits="1" number="161"/>
<line hits="1" number="162"/>
<line hits="1" number="163"/>
<line hits="1" number="164"/>
<line hits="1" number="165"/>
<line hits="1" number="166"/>
<line hits="1" number="167"/>
<line hits="1" number="168"/>
<line hits="1" number="169"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="170"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="171"/>
<line hits="1" number="172"/>
<line hits="1" number="173"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="174"/>
<line hits="1" number="175"/>
<line hits="1" number="176"/>
<line hits="1" number="180"/>
<line hits="1" number="181"/>
<line hits="1" number="182"/>
<line hits="1" number="185"/>
<line hits="1" number="186"/>
<line hits="1" number="187"/>
<line hits="1" number="170"/>
<line hits="1" number="171"/>
<line hits="1" number="188"/>
<line hits="1" number="189"/>
<line hits="1" number="190"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="191"/>
<line hits="1" number="192"/>
<line hits="1" number="191"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="192"/>
<line hits="1" number="193"/>
<line hits="1" number="194"/>
<line hits="1" number="197"/>
<line hits="1" number="198"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="194"/>
<line hits="1" number="195"/>
<line hits="1" number="196"/>
<line hits="1" number="199"/>
<line hits="1" number="200"/>
<line hits="1" number="201"/>
<line hits="1" number="204"/>
<line hits="1" number="205"/>
<line hits="1" number="206"/>
<line hits="1" number="207"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="208"/>
<line hits="1" number="209"/>
<line hits="1" number="210"/>
<line hits="1" number="211"/>
<line hits="1" number="212"/>
<line hits="1" number="229"/>
<line hits="1" number="230"/>
<line hits="1" number="231"/>
<line hits="1" number="232"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="233"/>
<line hits="1" number="234"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="235"/>
<line hits="1" number="236"/>
<line hits="1" number="237"/>
<line hits="1" number="240"/>
<line hits="1" number="241"/>
<line hits="1" number="242"/>
<line hits="1" number="245"/>
<line hits="1" number="246"/>
<line hits="1" number="247"/>
<line hits="1" number="248"/>
<line branch="true" condition-coverage="100% (2/2)" hits="1" number="249"/>
<line hits="1" number="250"/>
<line hits="1" number="251"/>
<line hits="1" number="252"/>
<line hits="1" number="253"/>
<line hits="1" number="254"/>
<line hits="1" number="255"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="exit" number="256"/>
<line hits="1" number="257"/>
<line hits="1" number="258"/>
<line hits="1" number="259"/>
<line hits="1" number="262"/>
<line hits="1" number="263"/>
<line hits="1" number="264"/>
<line hits="1" number="265"/>
<line hits="1" number="213"/>
<line hits="1" number="214"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" missing-branches="exit" number="215"/>
<line hits="1" number="216"/>
<line hits="1" number="217"/>
<line hits="1" number="218"/>
<line hits="1" number="221"/>
<line hits="1" number="222"/>
<line hits="1" number="223"/>
<line hits="1" number="224"/>
</lines>
</class>
</classes>

File diff suppressed because it is too large Load Diff

Binary file not shown.

19
common.py Normal file
View File

@ -0,0 +1,19 @@
import os
FILETYPE_AUDIO = 'audio'
FILETYPE_IMAGE = 'image'
FILETYPE_VIDEO = 'video'
EXTENTIONS_AUDIO = ['.mp3', ]
EXTENTIONS_IMAGE = ['.jpg', '.jpeg', '.jpe', '.png', '.tif', '.tiff', '.gif', ]
EXTENTIONS_VIDEO = ['.avi', '.mpg', '.mpeg', '.mpe', '.mov', '.qt', '.mp4', '.webm', '.ogv', '.flv', '.3gp', ]
def get_filetype(full_path):
ext = os.path.splitext(full_path.lower())[1]
if ext in EXTENTIONS_AUDIO:
return FILETYPE_AUDIO
elif ext in EXTENTIONS_IMAGE:
return FILETYPE_IMAGE
elif ext in EXTENTIONS_VIDEO:
return FILETYPE_VIDEO

35
convert.py Normal file
View File

@ -0,0 +1,35 @@
import io
from media import common, logger
from PIL import Image
import subprocess
import platform
def get_pil_image(media_instance):
try:
media_instance = media_instance._im
except AttributeError:
pass
#
if type(media_instance) == str:
ft = common.get_filetype(media_instance)
if ft == common.FILETYPE_IMAGE:
return Image.open(media_instance).copy()
elif ft == common.FILETYPE_VIDEO:
if platform.system() == 'Linux':
cmd = 'ffmpeg -ss 0.5 -i "' + media_instance + '" -vframes 1 -f image2pipe pipe:1 2> /dev/null'
else:
cmd = 'ffmpeg -ss 0.5 -i "' + media_instance + '" -vframes 1 -f image2pipe pipe:1 2> NULL'
try:
data = subprocess.check_output(cmd, shell=True)
except subprocess.CalledProcessError:
logger.warning('ffmpeg seems to be not installed')
return None
ffmpeg_handle = io.BytesIO(data)
im = Image.open(ffmpeg_handle)
return im.copy()
logger.warning('Filetype is not supported (%s)', media_instance)
elif type(media_instance) == Image.Image:
return media_instance.copy()
else:
logger.warning('Instance type is not supported: %s' % type(media_instance))

View File

@ -1,87 +1,46 @@
import logging
import os
from PIL import Image
import media
import subprocess
import time
logger_name = 'MEDIA'
logger = logging.getLogger(logger_name)
logger = media.logger
FILETYPE_AUDIO = 'audio'
FILETYPE_IMAGE = 'image'
FILETYPE_VIDEO = 'video'
EXTENTIONS_AUDIO = ['.mp3', ]
EXTENTIONS_IMAGE = ['.jpg', '.jpeg', '.jpe', '.png', '.tif', '.tiff', '.gif', ]
EXTENTIONS_VIDEO = ['.avi', '.mpg', '.mpeg', '.mpe', '.mov', '.qt', '.mp4', '.webm', '.ogv', '.flv', '.3gp', ]
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'
__KEY_CAMERA_VENDOR__ = 'camera_vendor'
__KEY_CAMERA_MODEL__ = 'camera_model'
def get_filetype(full_path):
ext = os.path.splitext(full_path.lower())[1]
if ext in EXTENTIONS_AUDIO:
return FILETYPE_AUDIO
elif ext in EXTENTIONS_IMAGE:
return FILETYPE_IMAGE
elif ext in EXTENTIONS_VIDEO:
return FILETYPE_VIDEO
def get_audio_data(full_path):
conv_key_dict = {}
conv_key_dict['album'] = (str, KEY_ALBUM)
conv_key_dict['TAG:album'] = (str, KEY_ALBUM)
conv_key_dict['TAG:artist'] = (str, KEY_ARTIST)
conv_key_dict['artist'] = (str, KEY_ARTIST)
conv_key_dict['bit_rate'] = (__int_conv__, KEY_BITRATE)
conv_key_dict['duration'] = (float, KEY_DURATION)
conv_key_dict['TAG:genre'] = (str, KEY_GENRE)
conv_key_dict['genre'] = (str, KEY_GENRE)
conv_key_dict['TAG:title'] = (str, KEY_TITLE)
conv_key_dict['title'] = (str, KEY_TITLE)
conv_key_dict['TAG:track'] = (__int_conv__, KEY_TRACK)
conv_key_dict['track'] = (__int_conv__, KEY_TRACK)
conv_key_dict['TAG:date'] = (__int_conv__, KEY_YEAR)
conv_key_dict['date'] = (__int_conv__, KEY_YEAR)
conv_key_dict['album'] = (str, media.KEY_ALBUM)
conv_key_dict['TAG:album'] = (str, media.KEY_ALBUM)
conv_key_dict['TAG:artist'] = (str, media.KEY_ARTIST)
conv_key_dict['artist'] = (str, media.KEY_ARTIST)
conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
conv_key_dict['duration'] = (float, media.KEY_DURATION)
conv_key_dict['TAG:genre'] = (str, media.KEY_GENRE)
conv_key_dict['genre'] = (str, media.KEY_GENRE)
conv_key_dict['TAG:title'] = (str, media.KEY_TITLE)
conv_key_dict['title'] = (str, media.KEY_TITLE)
conv_key_dict['TAG:track'] = (__int_conv__, media.KEY_TRACK)
conv_key_dict['track'] = (__int_conv__, media.KEY_TRACK)
conv_key_dict['TAG:date'] = (__int_conv__, media.KEY_YEAR)
conv_key_dict['date'] = (__int_conv__, media.KEY_YEAR)
return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
def get_video_data(full_path):
conv_key_dict = {}
conv_key_dict['creation_time'] = (__vid_datetime_conv__, KEY_TIME)
conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, KEY_TIME)
conv_key_dict['bit_rate'] = (__int_conv__, KEY_BITRATE)
conv_key_dict['duration'] = (float, KEY_DURATION)
conv_key_dict['height'] = (__int_conv__, KEY_HEIGHT)
conv_key_dict['width'] = (__int_conv__, KEY_WIDTH)
conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, KEY_RATIO)
conv_key_dict['creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
conv_key_dict['duration'] = (float, media.KEY_DURATION)
conv_key_dict['height'] = (__int_conv__, media.KEY_HEIGHT)
conv_key_dict['width'] = (__int_conv__, media.KEY_WIDTH)
conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, media.KEY_RATIO)
return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
@ -90,25 +49,25 @@ def get_image_data(full_path):
def __adapt__data__(data, full_path):
data[KEY_SIZE] = os.path.getsize(full_path)
data[media.KEY_SIZE] = os.path.getsize(full_path)
# Join Camera Vendor and Camera Model
if __KEY_CAMERA_MODEL__ in data and __KEY_CAMERA_VENDOR__ in data:
model = data.pop(__KEY_CAMERA_MODEL__)
vendor = data.pop(__KEY_CAMERA_VENDOR__)
data[KEY_CAMERA] = '%s: %s' % (vendor, model)
data[media.KEY_CAMERA] = '%s: %s' % (vendor, model)
# Add time if not exists
if KEY_TIME not in data:
if KEY_YEAR in data and KEY_TRACK in data:
if data[KEY_YEAR] != 0: # ignore year 0 - must be wrong
if media.KEY_TIME not in data:
if media.KEY_YEAR in data and media.KEY_TRACK in data:
if data[media.KEY_YEAR] != 0: # ignore year 0 - must be wrong
# Use a date where track 1 is the newest in the given year
minute = int(data[KEY_TRACK] / 60)
second = (data[KEY_TRACK] - 60 * minute) % 60
minute = int(data[media.KEY_TRACK] / 60)
second = (data[media.KEY_TRACK] - 60 * minute) % 60
#
data[KEY_TIME] = int(time.mktime((data[KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
data[KEY_TIME_IS_SUBSTITUTION] = True
data[media.KEY_TIME] = int(time.mktime((data[media.KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
data[media.KEY_TIME_IS_SUBSTITUTION] = True
else:
data[KEY_TIME] = int(os.path.getmtime(full_path))
data[KEY_TIME_IS_SUBSTITUTION] = True
data[media.KEY_TIME] = int(os.path.getmtime(full_path))
data[media.KEY_TIME_IS_SUBSTITUTION] = True
return data
@ -154,19 +113,19 @@ def __get_exif_data__(full_path):
else:
conv_key_dict = {}
# IMAGE
conv_key_dict[0x9003] = (__datetime_conv__, KEY_TIME)
conv_key_dict[0x8822] = (__exposure_program_conv__, KEY_EXPOSURE_PROGRAM)
conv_key_dict[0x829A] = (__num_denum_conv__, KEY_EXPOSURE_TIME)
conv_key_dict[0x9209] = (__flash_conv__, KEY_FLASH)
conv_key_dict[0x829D] = (__num_denum_conv__, KEY_APERTURE)
conv_key_dict[0x920A] = (__num_denum_conv__, KEY_FOCAL_LENGTH)
conv_key_dict[0x8825] = (__gps_conv__, KEY_GPS)
conv_key_dict[0xA003] = (__int_conv__, KEY_HEIGHT)
conv_key_dict[0x8827] = (__int_conv__, KEY_ISO)
conv_key_dict[0x9003] = (__datetime_conv__, media.KEY_TIME)
conv_key_dict[0x8822] = (__exposure_program_conv__, media.KEY_EXPOSURE_PROGRAM)
conv_key_dict[0x829A] = (__num_denum_conv__, media.KEY_EXPOSURE_TIME)
conv_key_dict[0x9209] = (__flash_conv__, media.KEY_FLASH)
conv_key_dict[0x829D] = (__num_denum_conv__, media.KEY_APERTURE)
conv_key_dict[0x920A] = (__num_denum_conv__, media.KEY_FOCAL_LENGTH)
conv_key_dict[0x8825] = (__gps_conv__, media.KEY_GPS)
conv_key_dict[0xA003] = (__int_conv__, media.KEY_HEIGHT)
conv_key_dict[0x8827] = (__int_conv__, media.KEY_ISO)
conv_key_dict[0x010F] = (str, __KEY_CAMERA_VENDOR__)
conv_key_dict[0x0110] = (str, __KEY_CAMERA_MODEL__)
conv_key_dict[0x0112] = (__int_conv__, KEY_ORIENTATION)
conv_key_dict[0xA002] = (__int_conv__, KEY_WIDTH)
conv_key_dict[0x0112] = (__int_conv__, media.KEY_ORIENTATION)
conv_key_dict[0xA002] = (__int_conv__, media.KEY_WIDTH)
for key in conv_key_dict:
if key in exif:
tp, name = conv_key_dict[key]