| 
									
										
										
										
											2014-11-26 20:01:20 +01:00
										 |  |  | from __future__ import unicode_literals | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | import os | 
					
						
							|  |  |  | import subprocess | 
					
						
							|  |  |  | import sys | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  | import errno | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | from .common import PostProcessor | 
					
						
							|  |  |  | from ..utils import ( | 
					
						
							| 
									
										
										
										
											2014-01-07 06:34:55 +01:00
										 |  |  |     check_executable, | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  |     hyphenate_date, | 
					
						
							| 
									
										
										
										
											2015-04-23 13:49:06 +08:00
										 |  |  |     version_tuple, | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |     PostProcessingError, | 
					
						
							|  |  |  |     encodeArgument, | 
					
						
							|  |  |  |     encodeFilename, | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  | class XAttrMetadataError(PostProcessingError): | 
					
						
							|  |  |  |     def __init__(self, code=None, msg='Unknown error'): | 
					
						
							|  |  |  |         super(XAttrMetadataError, self).__init__(msg) | 
					
						
							|  |  |  |         self.code = code | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Parsing code and msg | 
					
						
							|  |  |  |         if (self.code in (errno.ENOSPC, errno.EDQUOT) or | 
					
						
							|  |  |  |                 'No space left' in self.msg or 'Disk quota excedded' in self.msg): | 
					
						
							|  |  |  |             self.reason = 'NO_SPACE' | 
					
						
							| 
									
										
										
										
											2015-05-14 14:51:00 +08:00
										 |  |  |         elif self.code == errno.E2BIG or 'Argument list too long' in self.msg: | 
					
						
							|  |  |  |             self.reason = 'VALUE_TOO_LONG' | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |         else: | 
					
						
							|  |  |  |             self.reason = 'NOT_SUPPORTED' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | class XAttrMetadataPP(PostProcessor): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # More info about extended attributes for media: | 
					
						
							|  |  |  |     #   http://freedesktop.org/wiki/CommonExtendedAttributes/ | 
					
						
							|  |  |  |     #   http://www.freedesktop.org/wiki/PhreedomDraft/ | 
					
						
							|  |  |  |     #   http://dublincore.org/documents/usageguide/elements.shtml | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  |     # TODO: | 
					
						
							|  |  |  |     #  * capture youtube keywords and put them in 'user.dublincore.subject' (comma-separated) | 
					
						
							|  |  |  |     #  * figure out which xattrs can be used for 'duration', 'thumbnail', 'resolution' | 
					
						
							|  |  |  |     # | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def run(self, info): | 
					
						
							|  |  |  |         """ Set extended attributes on downloaded file (if xattr support is found). """ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # This mess below finds the best xattr tool for the job and creates a | 
					
						
							|  |  |  |         # "write_xattr" function. | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             # try the pyxattr module... | 
					
						
							|  |  |  |             import xattr | 
					
						
							| 
									
										
										
										
											2014-01-07 06:34:55 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-23 13:49:06 +08:00
										 |  |  |             # Unicode arguments are not supported in python-pyxattr until | 
					
						
							|  |  |  |             # version 0.5.0 | 
					
						
							|  |  |  |             # See https://github.com/rg3/youtube-dl/issues/5498 | 
					
						
							|  |  |  |             pyxattr_required_version = '0.5.0' | 
					
						
							|  |  |  |             if version_tuple(xattr.__version__) < version_tuple(pyxattr_required_version): | 
					
						
							|  |  |  |                 self._downloader.report_warning( | 
					
						
							|  |  |  |                     'python-pyxattr is detected but is too old. ' | 
					
						
							| 
									
										
										
										
											2015-04-23 22:11:09 +06:00
										 |  |  |                     'youtube-dl requires %s or above while your version is %s. ' | 
					
						
							| 
									
										
										
										
											2015-04-23 13:49:06 +08:00
										 |  |  |                     'Falling back to other xattr implementations' % ( | 
					
						
							|  |  |  |                         pyxattr_required_version, xattr.__version__)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 raise ImportError | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  |             def write_xattr(path, key, value): | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                 try: | 
					
						
							|  |  |  |                     xattr.set(path, key, value) | 
					
						
							|  |  |  |                 except EnvironmentError as e: | 
					
						
							|  |  |  |                     raise XAttrMetadataError(e.errno, e.strerror) | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         except ImportError: | 
					
						
							| 
									
										
										
										
											2014-01-07 06:34:55 +01:00
										 |  |  |             if os.name == 'nt': | 
					
						
							|  |  |  |                 # Write xattrs to NTFS Alternate Data Streams: | 
					
						
							|  |  |  |                 # http://en.wikipedia.org/wiki/NTFS#Alternate_data_streams_.28ADS.29 | 
					
						
							|  |  |  |                 def write_xattr(path, key, value): | 
					
						
							| 
									
										
										
										
											2014-01-07 06:50:24 +01:00
										 |  |  |                     assert ':' not in key | 
					
						
							|  |  |  |                     assert os.path.exists(path) | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-14 15:37:17 +06:00
										 |  |  |                     ads_fn = path + ':' + key | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                     try: | 
					
						
							| 
									
										
										
										
											2016-02-14 15:37:17 +06:00
										 |  |  |                         with open(ads_fn, 'wb') as f: | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                             f.write(value) | 
					
						
							|  |  |  |                     except EnvironmentError as e: | 
					
						
							|  |  |  |                         raise XAttrMetadataError(e.errno, e.strerror) | 
					
						
							| 
									
										
										
										
											2014-01-07 06:34:55 +01:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2016-02-14 15:37:17 +06:00
										 |  |  |                 user_has_setfattr = check_executable('setfattr', ['--version']) | 
					
						
							|  |  |  |                 user_has_xattr = check_executable('xattr', ['-h']) | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 if user_has_setfattr or user_has_xattr: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     def write_xattr(path, key, value): | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                         value = value.decode('utf-8') | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  |                         if user_has_setfattr: | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                             executable = 'setfattr' | 
					
						
							|  |  |  |                             opts = ['-n', key, '-v', value] | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  |                         elif user_has_xattr: | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                             executable = 'xattr' | 
					
						
							|  |  |  |                             opts = ['-w', key, value] | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                         cmd = ([encodeFilename(executable, True)] + | 
					
						
							|  |  |  |                                [encodeArgument(o) for o in opts] + | 
					
						
							|  |  |  |                                [encodeFilename(path, True)]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-14 14:51:00 +08:00
										 |  |  |                         try: | 
					
						
							|  |  |  |                             p = subprocess.Popen( | 
					
						
							|  |  |  |                                 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) | 
					
						
							|  |  |  |                         except EnvironmentError as e: | 
					
						
							|  |  |  |                             raise XAttrMetadataError(e.errno, e.strerror) | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |                         stdout, stderr = p.communicate() | 
					
						
							|  |  |  |                         stderr = stderr.decode('utf-8', 'replace') | 
					
						
							|  |  |  |                         if p.returncode != 0: | 
					
						
							|  |  |  |                             raise XAttrMetadataError(p.returncode, stderr) | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     # On Unix, and can't find pyxattr, setfattr, or xattr. | 
					
						
							|  |  |  |                     if sys.platform.startswith('linux'): | 
					
						
							| 
									
										
										
										
											2014-01-07 06:12:28 +01:00
										 |  |  |                         self._downloader.report_error( | 
					
						
							|  |  |  |                             "Couldn't find a tool to set the xattrs. " | 
					
						
							|  |  |  |                             "Install either the python 'pyxattr' or 'xattr' " | 
					
						
							|  |  |  |                             "modules, or the GNU 'attr' package " | 
					
						
							|  |  |  |                             "(which contains the 'setfattr' tool).") | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         self._downloader.report_error( | 
					
						
							|  |  |  |                             "Couldn't find a tool to set the xattrs. " | 
					
						
							|  |  |  |                             "Install either the python 'xattr' module, " | 
					
						
							|  |  |  |                             "or the 'xattr' binary.") | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Write the metadata to the file's xattrs | 
					
						
							| 
									
										
										
										
											2014-01-07 06:34:55 +01:00
										 |  |  |         self._downloader.to_screen('[metadata] Writing metadata to file\'s xattrs') | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         filename = info['filepath'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             xattr_mapping = { | 
					
						
							|  |  |  |                 'user.xdg.referrer.url': 'webpage_url', | 
					
						
							|  |  |  |                 # 'user.xdg.comment':            'description', | 
					
						
							|  |  |  |                 'user.dublincore.title': 'title', | 
					
						
							|  |  |  |                 'user.dublincore.date': 'upload_date', | 
					
						
							|  |  |  |                 'user.dublincore.description': 'description', | 
					
						
							|  |  |  |                 'user.dublincore.contributor': 'uploader', | 
					
						
							|  |  |  |                 'user.dublincore.format': 'format', | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for xattrname, infoname in xattr_mapping.items(): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 value = info.get(infoname) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if value: | 
					
						
							| 
									
										
										
										
											2016-02-14 15:37:17 +06:00
										 |  |  |                     if infoname == 'upload_date': | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  |                         value = hyphenate_date(value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-01-07 06:49:15 +01:00
										 |  |  |                     byte_value = value.encode('utf-8') | 
					
						
							| 
									
										
										
										
											2014-01-07 06:11:21 +01:00
										 |  |  |                     write_xattr(filename, xattrname, byte_value) | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-04-18 11:36:42 +02:00
										 |  |  |             return [], info | 
					
						
							| 
									
										
										
										
											2014-01-07 05:59:22 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |         except XAttrMetadataError as e: | 
					
						
							|  |  |  |             if e.reason == 'NO_SPACE': | 
					
						
							|  |  |  |                 self._downloader.report_warning( | 
					
						
							|  |  |  |                     'There\'s no disk space left or disk quota exceeded. ' + | 
					
						
							|  |  |  |                     'Extended attributes are not written.') | 
					
						
							| 
									
										
										
										
											2015-05-14 14:51:00 +08:00
										 |  |  |             elif e.reason == 'VALUE_TOO_LONG': | 
					
						
							|  |  |  |                 self._downloader.report_warning( | 
					
						
							|  |  |  |                     'Unable to write extended attributes due to too long values.') | 
					
						
							| 
									
										
										
										
											2015-05-14 14:26:47 +08:00
										 |  |  |             else: | 
					
						
							| 
									
										
										
										
											2015-05-14 16:53:10 +08:00
										 |  |  |                 msg = 'This filesystem doesn\'t support extended attributes. ' | 
					
						
							|  |  |  |                 if os.name == 'nt': | 
					
						
							|  |  |  |                     msg += 'You need to use NTFS.' | 
					
						
							|  |  |  |                 else: | 
					
						
							|  |  |  |                     msg += '(You may have to enable them in your /etc/fstab)' | 
					
						
							|  |  |  |                 self._downloader.report_error(msg) | 
					
						
							| 
									
										
										
										
											2015-04-18 11:36:42 +02:00
										 |  |  |             return [], info |