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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import logging
  2. import os
  3. from PIL import Image
  4. import math
  5. import media
  6. import subprocess
  7. import time
  8. logger = media.logger
  9. __KEY_CAMERA_VENDOR__ = 'camera_vendor'
  10. __KEY_CAMERA_MODEL__ = 'camera_model'
  11. def get_audio_data(full_path):
  12. conv_key_dict = {}
  13. conv_key_dict['album'] = (str, media.KEY_ALBUM)
  14. conv_key_dict['TAG:album'] = (str, media.KEY_ALBUM)
  15. conv_key_dict['TAG:artist'] = (str, media.KEY_ARTIST)
  16. conv_key_dict['artist'] = (str, media.KEY_ARTIST)
  17. conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
  18. conv_key_dict['duration'] = (float, media.KEY_DURATION)
  19. conv_key_dict['TAG:genre'] = (str, media.KEY_GENRE)
  20. conv_key_dict['genre'] = (str, media.KEY_GENRE)
  21. conv_key_dict['TAG:title'] = (str, media.KEY_TITLE)
  22. conv_key_dict['title'] = (str, media.KEY_TITLE)
  23. conv_key_dict['TAG:track'] = (__int_conv__, media.KEY_TRACK)
  24. conv_key_dict['track'] = (__int_conv__, media.KEY_TRACK)
  25. conv_key_dict['TAG:date'] = (__int_conv__, media.KEY_YEAR)
  26. conv_key_dict['date'] = (__int_conv__, media.KEY_YEAR)
  27. return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
  28. def get_video_data(full_path):
  29. conv_key_dict = {}
  30. conv_key_dict['creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
  31. conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, media.KEY_TIME)
  32. conv_key_dict['bit_rate'] = (__int_conv__, media.KEY_BITRATE)
  33. conv_key_dict['duration'] = (float, media.KEY_DURATION)
  34. conv_key_dict['height'] = (__int_conv__, media.KEY_HEIGHT)
  35. conv_key_dict['width'] = (__int_conv__, media.KEY_WIDTH)
  36. conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, media.KEY_RATIO)
  37. return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
  38. def get_image_data(full_path):
  39. return __adapt__data__(__get_exif_data__(full_path), full_path)
  40. def __adapt__data__(data, full_path):
  41. data[media.KEY_SIZE] = os.path.getsize(full_path)
  42. # Join Camera Vendor and Camera Model
  43. if __KEY_CAMERA_MODEL__ in data and __KEY_CAMERA_VENDOR__ in data:
  44. model = data.pop(__KEY_CAMERA_MODEL__)
  45. vendor = data.pop(__KEY_CAMERA_VENDOR__)
  46. data[media.KEY_CAMERA] = '%s: %s' % (vendor, model)
  47. # Add time if not exists
  48. if media.KEY_TIME not in data:
  49. if media.KEY_YEAR in data and media.KEY_TRACK in data:
  50. if data[media.KEY_YEAR] != 0: # ignore year 0 - must be wrong
  51. # Use a date where track 1 is the newest in the given year
  52. minute = int(data[media.KEY_TRACK] / 60)
  53. second = (data[media.KEY_TRACK] - 60 * minute) % 60
  54. #
  55. data[media.KEY_TIME] = int(time.mktime((data[media.KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
  56. data[media.KEY_TIME_IS_SUBSTITUTION] = True
  57. else:
  58. data[media.KEY_TIME] = int(os.path.getmtime(full_path))
  59. data[media.KEY_TIME_IS_SUBSTITUTION] = True
  60. return data
  61. def __get_xxprobe_data__(full_path, conv_key_dict):
  62. def _ffprobe_command(full_path):
  63. return ['ffprobe', '-v', 'quiet', '-show_format', '-show_streams', full_path]
  64. def _avprobe_command(full_path):
  65. return ['avprobe', '-v', 'quiet', '-show_format', '-show_streams', full_path]
  66. try:
  67. xxprobe_text = subprocess.check_output(_avprobe_command(full_path))
  68. except FileNotFoundError:
  69. try:
  70. xxprobe_text = subprocess.check_output(_ffprobe_command(full_path))
  71. except FileNotFoundError:
  72. logger.warning('ffprobe and avprobe seem to be not installed')
  73. return {}
  74. #
  75. rv = {}
  76. for line in xxprobe_text.decode('utf-8').splitlines():
  77. try:
  78. key, val = [snippet.strip() for snippet in line.split('=')]
  79. except ValueError:
  80. continue
  81. else:
  82. if key in conv_key_dict:
  83. tp, name = conv_key_dict[key]
  84. try:
  85. rv[name] = tp(val)
  86. except ValueError:
  87. logger.log(logging.WARNING if val else logger.INFO, 'Can\'t convert %s (%s) for %s', repr(val), name, name)
  88. return rv
  89. def __get_exif_data__(full_path):
  90. rv = {}
  91. im = Image.open(full_path)
  92. try:
  93. exif = dict(im._getexif().items())
  94. except AttributeError:
  95. logger.debug('%s does not have any exif information', full_path)
  96. else:
  97. conv_key_dict = {}
  98. # IMAGE
  99. conv_key_dict[0x9003] = (__datetime_conv__, media.KEY_TIME)
  100. conv_key_dict[0x8822] = (__exposure_program_conv__, media.KEY_EXPOSURE_PROGRAM)
  101. conv_key_dict[0x829A] = (__num_denum_conv__, media.KEY_EXPOSURE_TIME)
  102. conv_key_dict[0x9209] = (__flash_conv__, media.KEY_FLASH)
  103. conv_key_dict[0x829D] = (__num_denum_conv__, media.KEY_APERTURE)
  104. conv_key_dict[0x920A] = (__num_denum_conv__, media.KEY_FOCAL_LENGTH)
  105. conv_key_dict[0x8825] = (__gps_conv__, media.KEY_GPS)
  106. conv_key_dict[0xA003] = (__int_conv__, media.KEY_HEIGHT)
  107. conv_key_dict[0x8827] = (__int_conv__, media.KEY_ISO)
  108. conv_key_dict[0x010F] = (str, __KEY_CAMERA_VENDOR__)
  109. conv_key_dict[0x0110] = (str, __KEY_CAMERA_MODEL__)
  110. conv_key_dict[0x0112] = (__int_conv__, media.KEY_ORIENTATION)
  111. conv_key_dict[0xA002] = (__int_conv__, media.KEY_WIDTH)
  112. for key in conv_key_dict:
  113. if key in exif:
  114. tp, name = conv_key_dict[key]
  115. raw_value = exif[key]
  116. logger.debug("Converting %s out of %s", name, repr(raw_value))
  117. value = tp(raw_value)
  118. if value is not None:
  119. rv[name] = value
  120. return rv
  121. # TODO: Join datetime converter __datetime_conv__ and __vid_datetime_conv_
  122. def __datetime_conv__(dt):
  123. format_string = "%Y:%m:%d %H:%M:%S"
  124. return int(time.mktime(time.strptime(dt, format_string)))
  125. def __vid_datetime_conv__(dt):
  126. try:
  127. dt = dt[:dt.index('.')]
  128. except ValueError:
  129. pass # time string seems to have no '.'
  130. dt = dt.replace('T', ' ').replace('/', '').replace('\\', '')
  131. if len(dt) == 16:
  132. dt += ':00'
  133. format_string = '%Y-%m-%d %H:%M:%S'
  134. return int(time.mktime(time.strptime(dt, format_string)))
  135. def __exposure_program_conv__(n):
  136. return {
  137. 0: 'Unidentified',
  138. 1: 'Manual',
  139. 2: 'Program Normal',
  140. 3: 'Aperture Priority',
  141. 4: 'Shutter Priority',
  142. 5: 'Program Creative',
  143. 6: 'Program Action',
  144. 7: 'Portrait Mode',
  145. 8: 'Landscape Mode'
  146. }.get(n, None)
  147. def __flash_conv__(n):
  148. return {
  149. 0: 'No',
  150. 1: 'Fired',
  151. 5: 'Fired (?)', # no return sensed
  152. 7: 'Fired (!)', # return sensed
  153. 9: 'Fill Fired',
  154. 13: 'Fill Fired (?)',
  155. 15: 'Fill Fired (!)',
  156. 16: 'Off',
  157. 24: 'Auto Off',
  158. 25: 'Auto Fired',
  159. 29: 'Auto Fired (?)',
  160. 31: 'Auto Fired (!)',
  161. 32: 'Not Available'
  162. }.get(n, None)
  163. def __int_conv__(value):
  164. try:
  165. return int(value)
  166. except ValueError:
  167. for c in ['.', '/', '-']:
  168. p = value.find(c)
  169. if p >= 0:
  170. value = value[:p]
  171. try:
  172. return int(value)
  173. except ValueError:
  174. return None
  175. def __num_denum_conv__(data):
  176. try:
  177. return float(data)
  178. except TypeError:
  179. num, denum = data
  180. return num / denum
  181. def __gps_conv__(data):
  182. def lat_lon_cal(lon_or_lat):
  183. lon_lat = 0.
  184. fac = 1.
  185. for data in lon_or_lat:
  186. try:
  187. lon_lat += float(data[0]) / float(data[1]) * fac
  188. except TypeError:
  189. lon_lat += data * fac
  190. except ZeroDivisionError:
  191. return 0.
  192. fac *= 1. / 60.
  193. if math.isnan(lon_lat):
  194. return 0.
  195. return lon_lat
  196. try:
  197. lon = lat_lon_cal(data[0x0004])
  198. lat = lat_lon_cal(data[0x0002])
  199. if lon != 0 or lat != 0: # do not use lon and lat equal 0, caused by motorola gps weakness
  200. return {'lon': lon, 'lat': lat}
  201. except KeyError:
  202. logger.warning('GPS data extraction failed for %s', repr(data))
  203. def __ratio_conv__(ratio):
  204. ratio = ratio.replace('\\', '')
  205. num, denum = ratio.split(':')
  206. return float(num) / float(denum)