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.

177 lines
7.1 KiB

  1. from __future__ import unicode_literals
  2. import os
  3. import subprocess
  4. import sys
  5. import errno
  6. from .common import PostProcessor
  7. from ..compat import compat_os_name
  8. from ..utils import (
  9. check_executable,
  10. hyphenate_date,
  11. version_tuple,
  12. PostProcessingError,
  13. encodeArgument,
  14. encodeFilename,
  15. )
  16. class XAttrMetadataError(PostProcessingError):
  17. def __init__(self, code=None, msg='Unknown error'):
  18. super(XAttrMetadataError, self).__init__(msg)
  19. self.code = code
  20. # Parsing code and msg
  21. if (self.code in (errno.ENOSPC, errno.EDQUOT) or
  22. 'No space left' in self.msg or 'Disk quota excedded' in self.msg):
  23. self.reason = 'NO_SPACE'
  24. elif self.code == errno.E2BIG or 'Argument list too long' in self.msg:
  25. self.reason = 'VALUE_TOO_LONG'
  26. else:
  27. self.reason = 'NOT_SUPPORTED'
  28. class XAttrMetadataPP(PostProcessor):
  29. #
  30. # More info about extended attributes for media:
  31. # http://freedesktop.org/wiki/CommonExtendedAttributes/
  32. # http://www.freedesktop.org/wiki/PhreedomDraft/
  33. # http://dublincore.org/documents/usageguide/elements.shtml
  34. #
  35. # TODO:
  36. # * capture youtube keywords and put them in 'user.dublincore.subject' (comma-separated)
  37. # * figure out which xattrs can be used for 'duration', 'thumbnail', 'resolution'
  38. #
  39. def run(self, info):
  40. """ Set extended attributes on downloaded file (if xattr support is found). """
  41. # This mess below finds the best xattr tool for the job and creates a
  42. # "write_xattr" function.
  43. try:
  44. # try the pyxattr module...
  45. import xattr
  46. # Unicode arguments are not supported in python-pyxattr until
  47. # version 0.5.0
  48. # See https://github.com/rg3/youtube-dl/issues/5498
  49. pyxattr_required_version = '0.5.0'
  50. if version_tuple(xattr.__version__) < version_tuple(pyxattr_required_version):
  51. self._downloader.report_warning(
  52. 'python-pyxattr is detected but is too old. '
  53. 'youtube-dl requires %s or above while your version is %s. '
  54. 'Falling back to other xattr implementations' % (
  55. pyxattr_required_version, xattr.__version__))
  56. raise ImportError
  57. def write_xattr(path, key, value):
  58. try:
  59. xattr.set(path, key, value)
  60. except EnvironmentError as e:
  61. raise XAttrMetadataError(e.errno, e.strerror)
  62. except ImportError:
  63. if compat_os_name == 'nt':
  64. # Write xattrs to NTFS Alternate Data Streams:
  65. # http://en.wikipedia.org/wiki/NTFS#Alternate_data_streams_.28ADS.29
  66. def write_xattr(path, key, value):
  67. assert ':' not in key
  68. assert os.path.exists(path)
  69. ads_fn = path + ':' + key
  70. try:
  71. with open(ads_fn, 'wb') as f:
  72. f.write(value)
  73. except EnvironmentError as e:
  74. raise XAttrMetadataError(e.errno, e.strerror)
  75. else:
  76. user_has_setfattr = check_executable('setfattr', ['--version'])
  77. user_has_xattr = check_executable('xattr', ['-h'])
  78. if user_has_setfattr or user_has_xattr:
  79. def write_xattr(path, key, value):
  80. value = value.decode('utf-8')
  81. if user_has_setfattr:
  82. executable = 'setfattr'
  83. opts = ['-n', key, '-v', value]
  84. elif user_has_xattr:
  85. executable = 'xattr'
  86. opts = ['-w', key, value]
  87. cmd = ([encodeFilename(executable, True)] +
  88. [encodeArgument(o) for o in opts] +
  89. [encodeFilename(path, True)])
  90. try:
  91. p = subprocess.Popen(
  92. cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
  93. except EnvironmentError as e:
  94. raise XAttrMetadataError(e.errno, e.strerror)
  95. stdout, stderr = p.communicate()
  96. stderr = stderr.decode('utf-8', 'replace')
  97. if p.returncode != 0:
  98. raise XAttrMetadataError(p.returncode, stderr)
  99. else:
  100. # On Unix, and can't find pyxattr, setfattr, or xattr.
  101. if sys.platform.startswith('linux'):
  102. self._downloader.report_error(
  103. "Couldn't find a tool to set the xattrs. "
  104. "Install either the python 'pyxattr' or 'xattr' "
  105. "modules, or the GNU 'attr' package "
  106. "(which contains the 'setfattr' tool).")
  107. else:
  108. self._downloader.report_error(
  109. "Couldn't find a tool to set the xattrs. "
  110. "Install either the python 'xattr' module, "
  111. "or the 'xattr' binary.")
  112. # Write the metadata to the file's xattrs
  113. self._downloader.to_screen('[metadata] Writing metadata to file\'s xattrs')
  114. filename = info['filepath']
  115. try:
  116. xattr_mapping = {
  117. 'user.xdg.referrer.url': 'webpage_url',
  118. # 'user.xdg.comment': 'description',
  119. 'user.dublincore.title': 'title',
  120. 'user.dublincore.date': 'upload_date',
  121. 'user.dublincore.description': 'description',
  122. 'user.dublincore.contributor': 'uploader',
  123. 'user.dublincore.format': 'format',
  124. }
  125. for xattrname, infoname in xattr_mapping.items():
  126. value = info.get(infoname)
  127. if value:
  128. if infoname == 'upload_date':
  129. value = hyphenate_date(value)
  130. byte_value = value.encode('utf-8')
  131. write_xattr(filename, xattrname, byte_value)
  132. return [], info
  133. except XAttrMetadataError as e:
  134. if e.reason == 'NO_SPACE':
  135. self._downloader.report_warning(
  136. 'There\'s no disk space left or disk quota exceeded. ' +
  137. 'Extended attributes are not written.')
  138. elif e.reason == 'VALUE_TOO_LONG':
  139. self._downloader.report_warning(
  140. 'Unable to write extended attributes due to too long values.')
  141. else:
  142. msg = 'This filesystem doesn\'t support extended attributes. '
  143. if compat_os_name == 'nt':
  144. msg += 'You need to use NTFS.'
  145. else:
  146. msg += '(You may have to enable them in your /etc/fstab)'
  147. self._downloader.report_error(msg)
  148. return [], info