Python Library Media
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

metadata.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. import media.CDDB
  2. import time
  3. import subprocess
  4. from media import common
  5. import math
  6. from PIL import Image
  7. import os
  8. import logging
  9. import sys
  10. try:
  11. from config import APP_NAME as ROOT_LOGGER_NAME
  12. except ImportError:
  13. ROOT_LOGGER_NAME = 'root'
  14. logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
  15. try:
  16. import discid
  17. except OSError:
  18. logger.exception("You might install python3-libdiscid")
  19. __KEY_CAMERA_VENDOR__ = 'camera_vendor'
  20. __KEY_CAMERA_MODEL__ = 'camera_model'
  21. def get_media_data(full_path, user_callback=None):
  22. #
  23. ft = common.get_filetype(full_path)
  24. #
  25. if ft == common.FILETYPE_AUDIO:
  26. return get_audio_data(full_path)
  27. elif ft == common.FILETYPE_IMAGE:
  28. return get_image_data(full_path)
  29. elif ft == common.FILETYPE_VIDEO:
  30. return get_video_data(full_path)
  31. elif ft == common.FILETYPE_DISC:
  32. return get_disc_data(full_path, user_callback)
  33. else:
  34. logger.warning('Filetype not known: %s', full_path)
  35. def get_audio_data(full_path):
  36. conv_key_dict = {}
  37. conv_key_dict['album'] = (str, common.KEY_ALBUM)
  38. conv_key_dict['TAG:album'] = (str, common.KEY_ALBUM)
  39. conv_key_dict['TAG:artist'] = (str, common.KEY_ARTIST)
  40. conv_key_dict['artist'] = (str, common.KEY_ARTIST)
  41. conv_key_dict['bit_rate'] = (__int_conv__, common.KEY_BITRATE)
  42. conv_key_dict['duration'] = (float, common.KEY_DURATION)
  43. conv_key_dict['TAG:genre'] = (str, common.KEY_GENRE)
  44. conv_key_dict['genre'] = (str, common.KEY_GENRE)
  45. conv_key_dict['TAG:title'] = (str, common.KEY_TITLE)
  46. conv_key_dict['title'] = (str, common.KEY_TITLE)
  47. conv_key_dict['TAG:track'] = (__int_conv__, common.KEY_TRACK)
  48. conv_key_dict['track'] = (__int_conv__, common.KEY_TRACK)
  49. conv_key_dict['TAG:date'] = (__int_conv__, common.KEY_YEAR)
  50. conv_key_dict['date'] = (__int_conv__, common.KEY_YEAR)
  51. return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
  52. def get_disc_data(full_path, user_callback):
  53. #
  54. # Read Information from CDDB database
  55. #
  56. did = media.CDDB.discid()
  57. if did is None:
  58. logger.error("Could not determine disc id...")
  59. sys.exit(1)
  60. q = media.CDDB.query(did)
  61. if q is None:
  62. data = {
  63. common.KEY_ARTIST: None,
  64. common.KEY_ALBUM: None,
  65. common.KEY_YEAR: None,
  66. common.KEY_GENRE: None
  67. }
  68. for i in range(0, int(did.split('+')[1])):
  69. data["track_%02d" % (i + 1)] = None
  70. data = user_callback(common.CALLBACK_MAN_INPUT, data)
  71. return media.CDDB.my_disc_metadata(**data)
  72. if len(q) == 1:
  73. # Only one entry
  74. did = tuple(q.keys())[0]
  75. else:
  76. # multiple entries (choose)
  77. if user_callback is None:
  78. logger.warning("No usercallback to handle multiple cddb choices...")
  79. sys.exit(1)
  80. did = user_callback(common.CALLBACK_CDDB_CHOICE, q)
  81. return media.CDDB.cddb(did)
  82. """
  83. musicbrainzngs.set_useragent("pyrip", "0.1", "your@mail")
  84. disc = discid.read()
  85. disc_id = disc.id
  86. disc_data = {}
  87. try:
  88. result = musicbrainzngs.get_releases_by_discid(disc_id, includes=["artists", "recordings"])
  89. except musicbrainzngs.ResponseError:
  90. logger.exception("disc not found or bad response")
  91. sys.exit(1)
  92. else:
  93. disc_data[common.KEY_ARTIST] = result["disc"]["release-list"][0]["artist-credit-phrase"]
  94. disc_data[common.KEY_ALBUM] = result["disc"]["release-list"][0]["title"]
  95. disc_data[common.KEY_YEAR] = int(result["disc"]["release-list"][0]["date"][:4])
  96. data_copy = dict(disc_data)
  97. disc_data["id"] = result["disc"]["release-list"][0]["id"]
  98. disc_data["tracks"] = []
  99. # get tracklist
  100. for entry in result["disc"]["release-list"][0]["medium-list"][0]["track-list"]:
  101. track = dict(data_copy)
  102. track[common.KEY_TITLE] = entry["recording"]["title"]
  103. track[common.KEY_TRACK] = int(entry['number'])
  104. disc_data["tracks"].append(track)
  105. return disc_data
  106. """
  107. def get_video_data(full_path):
  108. conv_key_dict = {}
  109. conv_key_dict['creation_time'] = (__vid_datetime_conv__, common.KEY_TIME)
  110. conv_key_dict['TAG:creation_time'] = (__vid_datetime_conv__, common.KEY_TIME)
  111. conv_key_dict['bit_rate'] = (__int_conv__, common.KEY_BITRATE)
  112. conv_key_dict['duration'] = (float, common.KEY_DURATION)
  113. conv_key_dict['height'] = (__int_conv__, common.KEY_HEIGHT)
  114. conv_key_dict['width'] = (__int_conv__, common.KEY_WIDTH)
  115. conv_key_dict['display_aspect_ratio'] = (__ratio_conv__, common.KEY_RATIO)
  116. return __adapt__data__(__get_xxprobe_data__(full_path, conv_key_dict), full_path)
  117. def get_image_data(full_path):
  118. return __adapt__data__(__get_exif_data__(full_path), full_path)
  119. def __adapt__data__(data, full_path):
  120. data[common.KEY_SIZE] = os.path.getsize(full_path)
  121. # Join Camera Vendor and Camera Model
  122. if __KEY_CAMERA_MODEL__ in data and __KEY_CAMERA_VENDOR__ in data:
  123. model = data.pop(__KEY_CAMERA_MODEL__)
  124. vendor = data.pop(__KEY_CAMERA_VENDOR__)
  125. data[common.KEY_CAMERA] = '%s: %s' % (vendor, model)
  126. # Add time if not exists
  127. if common.KEY_TIME not in data:
  128. if common.KEY_YEAR in data and common.KEY_TRACK in data:
  129. if data[common.KEY_YEAR] != 0: # ignore year 0 - must be wrong
  130. # Use a date where track 1 is the newest in the given year
  131. minute = int(data[common.KEY_TRACK] / 60)
  132. second = (data[common.KEY_TRACK] - 60 * minute) % 60
  133. #
  134. data[common.KEY_TIME] = int(time.mktime((data[common.KEY_YEAR], 1, 1, 0, 59 - minute, 59 - second, 0, 0, 0)))
  135. data[common.KEY_TIME_IS_SUBSTITUTION] = True
  136. else:
  137. data[common.KEY_TIME] = int(os.path.getmtime(full_path))
  138. data[common.KEY_TIME_IS_SUBSTITUTION] = True
  139. return data
  140. def __get_xxprobe_data__(full_path, conv_key_dict):
  141. def _ffprobe_command(full_path):
  142. return ['ffprobe', '-v', 'quiet', '-show_format', '-show_streams', full_path]
  143. def _avprobe_command(full_path):
  144. return ['avprobe', '-v', 'quiet', '-show_format', '-show_streams', full_path]
  145. try:
  146. xxprobe_text = subprocess.check_output(_avprobe_command(full_path))
  147. except FileNotFoundError:
  148. try:
  149. xxprobe_text = subprocess.check_output(_ffprobe_command(full_path))
  150. except FileNotFoundError:
  151. logger.warning('ffprobe and avprobe seem to be not installed')
  152. return {}
  153. #
  154. rv = {}
  155. for line in xxprobe_text.decode('utf-8').splitlines():
  156. try:
  157. key, val = [snippet.strip() for snippet in line.split('=')]
  158. except ValueError:
  159. continue
  160. else:
  161. if key in conv_key_dict:
  162. tp, name = conv_key_dict[key]
  163. try:
  164. rv[name] = tp(val)
  165. except ValueError:
  166. logger.log(logging.WARNING if val else logger.INFO, 'Can\'t convert %s (%s) for %s', repr(val), name, name)
  167. return rv
  168. def __get_exif_data__(full_path):
  169. rv = {}
  170. im = Image.open(full_path)
  171. try:
  172. exif = dict(im._getexif().items())
  173. except AttributeError:
  174. logger.debug('%s does not have any exif information', full_path)
  175. else:
  176. conv_key_dict = {}
  177. # IMAGE
  178. conv_key_dict[0x9003] = (__datetime_conv__, common.KEY_TIME)
  179. conv_key_dict[0x8822] = (__exposure_program_conv__, common.KEY_EXPOSURE_PROGRAM)
  180. conv_key_dict[0x829A] = (__num_denum_conv__, common.KEY_EXPOSURE_TIME)
  181. conv_key_dict[0x9209] = (__flash_conv__, common.KEY_FLASH)
  182. conv_key_dict[0x829D] = (__num_denum_conv__, common.KEY_APERTURE)
  183. conv_key_dict[0x920A] = (__num_denum_conv__, common.KEY_FOCAL_LENGTH)
  184. conv_key_dict[0x8825] = (__gps_conv__, common.KEY_GPS)
  185. conv_key_dict[0xA003] = (__int_conv__, common.KEY_HEIGHT)
  186. conv_key_dict[0x8827] = (__int_conv__, common.KEY_ISO)
  187. conv_key_dict[0x010F] = (str, __KEY_CAMERA_VENDOR__)
  188. conv_key_dict[0x0110] = (str, __KEY_CAMERA_MODEL__)
  189. conv_key_dict[0x0112] = (__int_conv__, common.KEY_ORIENTATION)
  190. conv_key_dict[0xA002] = (__int_conv__, common.KEY_WIDTH)
  191. for key in conv_key_dict:
  192. if key in exif:
  193. tp, name = conv_key_dict[key]
  194. raw_value = exif[key]
  195. logger.debug("Converting %s out of %s", name, repr(raw_value))
  196. value = tp(raw_value)
  197. if value is not None:
  198. rv[name] = value
  199. return rv
  200. # TODO: Join datetime converter __datetime_conv__ and __vid_datetime_conv_
  201. def __datetime_conv__(dt):
  202. format_string = "%Y:%m:%d %H:%M:%S"
  203. return int(time.mktime(time.strptime(dt, format_string)))
  204. def __vid_datetime_conv__(dt):
  205. try:
  206. dt = dt[:dt.index('.')]
  207. except ValueError:
  208. pass # time string seems to have no '.'
  209. dt = dt.replace('T', ' ').replace('/', '').replace('\\', '')
  210. if len(dt) == 16:
  211. dt += ':00'
  212. format_string = '%Y-%m-%d %H:%M:%S'
  213. return int(time.mktime(time.strptime(dt, format_string)))
  214. def __exposure_program_conv__(n):
  215. return {
  216. 0: 'Unidentified',
  217. 1: 'Manual',
  218. 2: 'Program Normal',
  219. 3: 'Aperture Priority',
  220. 4: 'Shutter Priority',
  221. 5: 'Program Creative',
  222. 6: 'Program Action',
  223. 7: 'Portrait Mode',
  224. 8: 'Landscape Mode'
  225. }.get(n, None)
  226. def __flash_conv__(n):
  227. return {
  228. 0: 'No',
  229. 1: 'Fired',
  230. 5: 'Fired (?)', # no return sensed
  231. 7: 'Fired (!)', # return sensed
  232. 9: 'Fill Fired',
  233. 13: 'Fill Fired (?)',
  234. 15: 'Fill Fired (!)',
  235. 16: 'Off',
  236. 24: 'Auto Off',
  237. 25: 'Auto Fired',
  238. 29: 'Auto Fired (?)',
  239. 31: 'Auto Fired (!)',
  240. 32: 'Not Available'
  241. }.get(n, None)
  242. def __int_conv__(value):
  243. try:
  244. return int(value)
  245. except ValueError:
  246. for c in ['.', '/', '-']:
  247. p = value.find(c)
  248. if p >= 0:
  249. value = value[:p]
  250. try:
  251. return int(value)
  252. except ValueError:
  253. return None
  254. def __num_denum_conv__(data):
  255. try:
  256. return float(data)
  257. except TypeError:
  258. num, denum = data
  259. return num / denum
  260. def __gps_conv__(data):
  261. def lat_lon_cal(lon_or_lat):
  262. lon_lat = 0.
  263. fac = 1.
  264. for data in lon_or_lat:
  265. try:
  266. lon_lat += float(data[0]) / float(data[1]) * fac
  267. except TypeError:
  268. lon_lat += data * fac
  269. except ZeroDivisionError:
  270. return 0.
  271. fac *= 1. / 60.
  272. if math.isnan(lon_lat):
  273. return 0.
  274. return lon_lat
  275. try:
  276. lon = lat_lon_cal(data[0x0004])
  277. lat = lat_lon_cal(data[0x0002])
  278. if lon != 0 or lat != 0: # do not use lon and lat equal 0, caused by motorola gps weakness
  279. return {'lon': lon, 'lat': lat}
  280. except KeyError:
  281. logger.warning('GPS data extraction failed for %s', repr(data))
  282. def __ratio_conv__(ratio):
  283. ratio = ratio.replace('\\', '')
  284. num, denum = ratio.split(':')
  285. return float(num) / float(denum)