xattrpp.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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(
  66. "Couldn't find a tool to set the xattrs. "
  67. "Install either the python 'pyxattr' or 'xattr' "
  68. "modules, or the GNU 'attr' package "
  69. "(which contains the 'setfattr' tool).")
  70. else:
  71. self._downloader.report_error(
  72. "Couldn't find a tool to set the xattrs. "
  73. "Install either the python 'xattr' module, "
  74. "or the 'xattr' binary.")
  75. else:
  76. # Write xattrs to NTFS Alternate Data Streams: http://en.wikipedia.org/wiki/NTFS#Alternate_data_streams_.28ADS.29
  77. def write_xattr(path, key, value):
  78. assert(key.find(":") < 0)
  79. assert(path.find(":") < 0)
  80. assert(os.path.exists(path))
  81. ads_fn = path + ":" + key
  82. with open(ads_fn, "w") as f:
  83. f.write(value)
  84. # Write the metadata to the file's xattrs
  85. self._downloader.to_screen('[metadata] Writing metadata to file\'s xattrs...')
  86. filename = info['filepath']
  87. try:
  88. xattr_mapping = {
  89. 'user.xdg.referrer.url': 'webpage_url',
  90. # 'user.xdg.comment': 'description',
  91. 'user.dublincore.title': 'title',
  92. 'user.dublincore.date': 'upload_date',
  93. 'user.dublincore.description': 'description',
  94. 'user.dublincore.contributor': 'uploader',
  95. 'user.dublincore.format': 'format',
  96. }
  97. for xattrname, infoname in xattr_mapping.items():
  98. value = info.get(infoname)
  99. if value:
  100. if infoname == "upload_date":
  101. value = hyphenate_date(value)
  102. byte_value = value.encode(preferredencoding())
  103. write_xattr(filename, xattrname, byte_value)
  104. return True, info
  105. except OSError:
  106. self._downloader.report_error("This filesystem doesn't support extended attributes. (You may have to enable them in your /etc/fstab)")
  107. return False, info