Python Library Media
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

__init__.py 6.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. #
  4. """
  5. media (Media Tools)
  6. ===================
  7. **Author:**
  8. * Dirk Alders <sudo-dirk@mount-mockery.de>
  9. **Description:**
  10. This module helps on all issues with media files, like tags (e.g. exif, id3) and transformations.
  11. **Submodules:**
  12. * :func:`media.get_media_data`
  13. * :class:`media.image`
  14. **Unittest:**
  15. See also the :download:`unittest <../../media/_testresults_/unittest.pdf>` documentation.
  16. """
  17. __DEPENDENCIES__ = []
  18. import io
  19. import logging
  20. from PIL import Image, ImageEnhance, ExifTags
  21. logger_name = 'MEDIA'
  22. logger = logging.getLogger(logger_name)
  23. __DESCRIPTION__ = """The Module {\\tt %s} is designed to help on all issues with media files, like tags (e.g. exif, id3) and transformations.
  24. For more Information read the documentation.""" % __name__.replace('_', '\_')
  25. """The Module Description"""
  26. __INTERPRETER__ = (3, )
  27. """The Tested Interpreter-Versions"""
  28. KEY_ALBUM = 'album'
  29. KEY_APERTURE = 'aperture'
  30. KEY_ARTIST = 'artist'
  31. KEY_BITRATE = 'bitrate'
  32. KEY_CAMERA = 'camera'
  33. KEY_DURATION = 'duration'
  34. KEY_EXPOSURE_PROGRAM = 'exposure_program'
  35. KEY_EXPOSURE_TIME = 'exposure_time'
  36. KEY_FLASH = 'flash'
  37. KEY_FOCAL_LENGTH = 'focal_length'
  38. KEY_GENRE = 'genre'
  39. KEY_GPS = 'gps'
  40. KEY_HEIGHT = 'height'
  41. KEY_ISO = 'iso'
  42. KEY_ORIENTATION = 'orientation'
  43. KEY_RATIO = 'ratio'
  44. KEY_SIZE = 'size'
  45. KEY_TIME = 'time' # USE time.localtime(value) or datetime.fromtimestamp(value) to convert the timestamp
  46. KEY_TIME_IS_SUBSTITUTION = 'tm_is_subst'
  47. KEY_TITLE = 'title'
  48. KEY_TRACK = 'track'
  49. KEY_WIDTH = 'width'
  50. KEY_YEAR = 'year'
  51. def get_media_data(full_path):
  52. from media.metadata import get_audio_data, get_image_data, get_video_data
  53. from media.common import get_filetype, FILETYPE_AUDIO, FILETYPE_IMAGE, FILETYPE_VIDEO
  54. #
  55. ft = get_filetype(full_path)
  56. #
  57. if ft == FILETYPE_AUDIO:
  58. return get_audio_data(full_path)
  59. elif ft == FILETYPE_IMAGE:
  60. return get_image_data(full_path)
  61. elif ft == FILETYPE_VIDEO:
  62. return get_video_data(full_path)
  63. else:
  64. logger.warning('Filetype not known: %s', full_path)
  65. ORIENTATION_NORMAL = 1
  66. ORIENTATION_VERTICAL_MIRRORED = 2
  67. ORIENTATION_HALF_ROTATED = 3
  68. ORIENTATION_HORIZONTAL_MIRRORED = 4
  69. ORIENTATION_LEFT_ROTATED = 6
  70. ORIENTATION_RIGHT_ROTATED = 8
  71. JOIN_TOP_LEFT = 1
  72. JOIN_TOP_RIGHT = 2
  73. JOIN_BOT_LEFT = 3
  74. JOIN_BOT_RIGHT = 4
  75. JOIN_CENTER = 5
  76. class image(object):
  77. def __init__(self, media_instance=None):
  78. if media_instance is not None:
  79. self.load_from_file(media_instance)
  80. else:
  81. self._im = None
  82. def load_from_file(self, media_instance):
  83. from media.convert import get_pil_image
  84. #
  85. self._im = get_pil_image(media_instance)
  86. if self._im is None:
  87. return False
  88. try:
  89. self._exif = dict(self._im._getexif().items())
  90. except AttributeError:
  91. self._exif = {}
  92. if type(self._im) is not Image.Image:
  93. self._im = self._im.copy()
  94. logger.debug('loading image from %s', repr(media_instance))
  95. return True
  96. def save(self, full_path):
  97. if self._im is None:
  98. logger.warning('No image available to be saved (%s)', repr(full_path))
  99. return False
  100. else:
  101. logger.debug('Saving image to %s', repr(full_path))
  102. with open(full_path, 'w') as fh:
  103. im = self._im.convert('RGB')
  104. im.save(fh, 'JPEG')
  105. return True
  106. def image_data(self):
  107. im = self._im.copy().convert('RGB')
  108. output = io.BytesIO()
  109. im.save(output, format='JPEG')
  110. return output.getvalue()
  111. def resize(self, max_size):
  112. if self._im is None:
  113. logger.warning('No image available to be resized')
  114. return False
  115. else:
  116. logger.debug('Resizing picture to max %d pixel in whatever direction', max_size)
  117. x, y = self._im.size
  118. xy_max = max(x, y)
  119. self._im = self._im.resize((int(x * float(max_size) / xy_max), int(y * float(max_size) / xy_max)), Image.NEAREST).rotate(0)
  120. return True
  121. def rotate_by_orientation(self, orientation=None):
  122. if self._im is None:
  123. logger.warning('No image available, rotation not possible')
  124. return False
  125. if orientation is None:
  126. exif_tags = dict((v, k) for k, v in ExifTags.TAGS.items())
  127. try:
  128. orientation = self._exif[exif_tags['Orientation']]
  129. logger.debug("No orientation given, orientation %s extract from exif data", repr(orientation))
  130. except KeyError:
  131. return False
  132. if orientation == ORIENTATION_HALF_ROTATED:
  133. angle = 180
  134. elif orientation == ORIENTATION_LEFT_ROTATED:
  135. angle = 270
  136. elif orientation == ORIENTATION_RIGHT_ROTATED:
  137. angle = 90
  138. else:
  139. if type(orientation) == int and orientation > 8:
  140. logger.warning('Orientation %s unknown for rotation', repr(orientation))
  141. return False
  142. logger.debug('Rotating picture by %d (deg)', angle)
  143. self._im = self._im.rotate(angle, expand=True)
  144. return True
  145. def join(self, join_image, join_pos=JOIN_TOP_RIGHT, opacity=0.7):
  146. from media.convert import get_pil_image
  147. def rgba_copy(im):
  148. if im.mode != 'RGBA':
  149. return im.convert('RGBA')
  150. else:
  151. return im.copy()
  152. if self._im is None:
  153. logger.warning('No image available, joining not possible')
  154. return False
  155. # ensure type of join_image is PIL.Image
  156. join_image = get_pil_image(join_image)
  157. if join_image is None:
  158. logger.warning('Image to be joined is not supported %s', repr(join_image))
  159. return False
  160. im2 = rgba_copy(join_image)
  161. # change opacity of im2
  162. alpha = im2.split()[3]
  163. alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
  164. im2.putalpha(alpha)
  165. self._im = rgba_copy(self._im)
  166. # create a transparent layer
  167. layer = Image.new('RGBA', self._im.size, (0, 0, 0, 0))
  168. # draw im2 in layer
  169. if join_pos == JOIN_TOP_LEFT:
  170. layer.paste(im2, (0, 0))
  171. elif join_pos == JOIN_TOP_RIGHT:
  172. layer.paste(im2, ((self._im.size[0] - im2.size[0]), 0))
  173. elif join_pos == JOIN_BOT_LEFT:
  174. layer.paste(im2, (0, (self._im.size[1] - im2.size[1])))
  175. elif join_pos == JOIN_BOT_RIGHT:
  176. layer.paste(im2, ((self._im.size[0] - im2.size[0]), (self._im.size[1] - im2.size[1])))
  177. elif join_pos == JOIN_CENTER:
  178. layer.paste(im2, (int((self._im.size[0] - im2.size[0]) / 2), int((self._im.size[1] - im2.size[1]) / 2)))
  179. else:
  180. logger.warning("Join position value %s is not supported", join_pos)
  181. return False
  182. logger.debug('Joining two images')
  183. self._im = Image.composite(layer, self._im, layer)
  184. return True