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.

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