]>
Commit | Line | Data |
---|---|---|
dcddc10a PH |
1 | from __future__ import unicode_literals |
2 | ||
496c1923 PH |
3 | import os |
4 | import subprocess | |
5 | import sys | |
86c7fdb1 | 6 | import errno |
496c1923 PH |
7 | |
8 | from .common import PostProcessor | |
9 | from ..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 |
19 | class 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 |
34 | class 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 |