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.

metadata.py 8.7KB

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