xattrpp.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import os
  2. import subprocess
  3. import sys
  4. from .common import PostProcessor
  5. from ..utils import (
  6. hyphenate_date,
  7. preferredencoding,
  8. )
  9. class XAttrMetadataPP(PostProcessor):
  10. #
  11. # More info about extended attributes for media:
  12. # http://freedesktop.org/wiki/CommonExtendedAttributes/
  13. # http://www.freedesktop.org/wiki/PhreedomDraft/
  14. # http://dublincore.org/documents/usageguide/elements.shtml
  15. #
  16. # TODO:
  17. # * capture youtube keywords and put them in 'user.dublincore.subject' (comma-separated)
  18. # * figure out which xattrs can be used for 'duration', 'thumbnail', 'resolution'
  19. #
  20. def run(self, info):
  21. """ Set extended attributes on downloaded file (if xattr support is found). """
  22. # This mess below finds the best xattr tool for the job and creates a
  23. # "write_xattr" function.
  24. try:
  25. # try the pyxattr module...
  26. import xattr
  27. def write_xattr(path, key, value):
  28. return xattr.setxattr(path, key, value)
  29. except ImportError:
  30. if os.name == 'posix':
  31. def which(bin):
  32. for dir in os.environ["PATH"].split(":"):
  33. path = os.path.join(dir, bin)
  34. if os.path.exists(path):
  35. return path
  36. user_has_setfattr = which("setfattr")
  37. user_has_xattr = which("xattr")
  38. if user_has_setfattr or user_has_xattr:
  39. def write_xattr(path, key, value):
  40. import errno
  41. potential_errors = {
  42. # setfattr: /tmp/blah: Operation not supported
  43. "Operation not supported": errno.EOPNOTSUPP,
  44. # setfattr: ~/blah: No such file or directory
  45. # xattr: No such file: ~/blah
  46. "No such file": errno.ENOENT,
  47. }
  48. if user_has_setfattr:
  49. cmd = ['setfattr', '-n', key, '-v', value, path]
  50. elif user_has_xattr:
  51. cmd = ['xattr', '-w', key, value, path]
  52. try:
  53. subprocess.check_output(cmd, stderr=subprocess.STDOUT)
  54. except subprocess.CalledProcessError as e:
  55. errorstr = e.output.strip().decode()
  56. for potential_errorstr, potential_errno in potential_errors.items():
  57. if errorstr.find(potential_errorstr) > -1:
  58. e = OSError(potential_errno, potential_errorstr)
  59. e.__cause__ = None
  60. raise e
  61. raise # Reraise unhandled error
  62. else:
  63. # On Unix, and can't find pyxattr, setfattr, or xattr.
  64. if sys.platform.startswith('linux'):
  65. self._downloader.report_error("Couldn't find a tool to set the xattrs. Install either the python 'pyxattr' or 'xattr' modules, or the GNU 'attr' package (which contains the 'setfattr' tool).")
  66. elif sys.platform == 'darwin':
  67. self._downloader.report_error("Couldn't find a tool to set the xattrs. Install either the python 'xattr' module, or the 'xattr' binary.")
  68. else:
  69. # Write xattrs to NTFS Alternate Data Streams: http://en.wikipedia.org/wiki/NTFS#Alternate_data_streams_.28ADS.29
  70. def write_xattr(path, key, value):
  71. assert(key.find(":") < 0)
  72. assert(path.find(":") < 0)
  73. assert(os.path.exists(path))
  74. ads_fn = path + ":" + key
  75. with open(ads_fn, "w") as f:
  76. f.write(value)
  77. # Write the metadata to the file's xattrs
  78. self._downloader.to_screen('[metadata] Writing metadata to file\'s xattrs...')
  79. filename = info['filepath']
  80. try:
  81. xattr_mapping = {
  82. 'user.xdg.referrer.url': 'webpage_url',
  83. # 'user.xdg.comment': 'description',
  84. 'user.dublincore.title': 'title',
  85. 'user.dublincore.date': 'upload_date',
  86. 'user.dublincore.description': 'description',
  87. 'user.dublincore.contributor': 'uploader',
  88. 'user.dublincore.format': 'format',
  89. }
  90. for xattrname, infoname in xattr_mapping.items():
  91. value = info.get(infoname)
  92. if value:
  93. if infoname == "upload_date":
  94. value = hyphenate_date(value)
  95. byte_value = value.encode(preferredencoding())
  96. write_xattr(filename, xattrname, byte_value)
  97. return True, info
  98. except OSError:
  99. self._downloader.report_error("This filesystem doesn't support extended attributes. (You may have to enable them in your /etc/fstab)")
  100. return False, info