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