Python Library Media
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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