mirror of
				https://github.com/ytdl-org/youtube-dl.git
				synced 2025-11-04 06:24:50 +00:00 
			
		
		
		
	Write --xattrs metadata as macOS Spotlight metadata
This commit is contained in:
		
							parent
							
								
									379f52a495
								
							
						
					
					
						commit
						6879cf950d
					
				| 
						 | 
				
			
			@ -835,7 +835,7 @@ def parseOpts(overrideArguments=None):
 | 
			
		|||
    postproc.add_option(
 | 
			
		||||
        '--xattrs',
 | 
			
		||||
        action='store_true', dest='xattrs', default=False,
 | 
			
		||||
        help='Write metadata to the video file\'s xattrs (using dublin core and xdg standards)')
 | 
			
		||||
        help='Write metadata to the video file\'s xattrs (using dublin core and xdg standards, or macOS Spotlight)')
 | 
			
		||||
    postproc.add_option(
 | 
			
		||||
        '--fixup',
 | 
			
		||||
        metavar='POLICY', dest='fixup', default='detect_or_warn',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,17 @@
 | 
			
		|||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import plistlib
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
from xml.sax.saxutils import escape
 | 
			
		||||
 | 
			
		||||
from .common import PostProcessor
 | 
			
		||||
from ..compat import compat_os_name
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    check_executable,
 | 
			
		||||
    encodeArgument,
 | 
			
		||||
    encodeFilename,
 | 
			
		||||
    hyphenate_date,
 | 
			
		||||
    write_xattr,
 | 
			
		||||
    XAttrMetadataError,
 | 
			
		||||
| 
						 | 
				
			
			@ -32,15 +41,26 @@ class XAttrMetadataPP(PostProcessor):
 | 
			
		|||
        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',
 | 
			
		||||
            }
 | 
			
		||||
            if sys.platform != 'darwin':  # other than macOS
 | 
			
		||||
                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',
 | 
			
		||||
                }
 | 
			
		||||
            else:  # macOS
 | 
			
		||||
                xattr_mapping = {
 | 
			
		||||
                    'com.apple.metadata:kMDItemWhereFroms': 'webpage_url',
 | 
			
		||||
                    # 'user.xdg.comment': 'description',
 | 
			
		||||
                    'com.apple.metadata:kMDItemTitle': 'title',
 | 
			
		||||
                    'user.dublincore.date': 'upload_date',  # no corresponding attr
 | 
			
		||||
                    'com.apple.metadata:kMDItemDescription': 'description',
 | 
			
		||||
                    'com.apple.metadata:kMDItemContributors': 'uploader',
 | 
			
		||||
                    'user.dublincore.format': 'format',  # no corresponding attr
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            num_written = 0
 | 
			
		||||
            for xattrname, infoname in xattr_mapping.items():
 | 
			
		||||
| 
						 | 
				
			
			@ -48,10 +68,15 @@ class XAttrMetadataPP(PostProcessor):
 | 
			
		|||
                value = info.get(infoname)
 | 
			
		||||
 | 
			
		||||
                if value:
 | 
			
		||||
                    if infoname == 'upload_date':
 | 
			
		||||
                        value = hyphenate_date(value)
 | 
			
		||||
                    if not xattrname.startswith('com.apple.metadata:'):
 | 
			
		||||
                        if infoname == 'upload_date':
 | 
			
		||||
                            value = hyphenate_date(value)
 | 
			
		||||
 | 
			
		||||
                        byte_value = value.encode('utf-8')
 | 
			
		||||
 | 
			
		||||
                    else:  # macOS Spotlight metadata
 | 
			
		||||
                        byte_value = self.make_mditem(xattrname, value)
 | 
			
		||||
 | 
			
		||||
                    byte_value = value.encode('utf-8')
 | 
			
		||||
                    write_xattr(filename, xattrname, byte_value)
 | 
			
		||||
                    num_written += 1
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -77,3 +102,59 @@ class XAttrMetadataPP(PostProcessor):
 | 
			
		|||
                    msg += '(You may have to enable them in your /etc/fstab)'
 | 
			
		||||
                self._downloader.report_error(msg)
 | 
			
		||||
            return [], info
 | 
			
		||||
 | 
			
		||||
    def make_mditem(self, attrname, value):
 | 
			
		||||
        # Info about macOS Spotlight metadata:
 | 
			
		||||
        #   https://developer.apple.com/library/archive/documentation/CoreServices/Reference/MetadataAttributesRef/Reference/CommonAttrs.html
 | 
			
		||||
 | 
			
		||||
        attr_is_cfarray = attrname in (
 | 
			
		||||
            'com.apple.metadata:kMDItemContributors',
 | 
			
		||||
            'com.apple.metadata:kMDItemWhereFroms')
 | 
			
		||||
 | 
			
		||||
        if hasattr(plistlib, 'dumps'):  # Python >= 3.4, need new api to make binary plist
 | 
			
		||||
            if attr_is_cfarray:
 | 
			
		||||
                value = [value]
 | 
			
		||||
            return plistlib.dumps(value, fmt=plistlib.FMT_BINARY)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            # try PyObjC (or pyobjc-framework-Cocoa)
 | 
			
		||||
            try:
 | 
			
		||||
                from Foundation import NSPropertyListSerialization, NSPropertyListBinaryFormat_v1_0
 | 
			
		||||
 | 
			
		||||
                if attr_is_cfarray:
 | 
			
		||||
                    data = [value]
 | 
			
		||||
                else:
 | 
			
		||||
                    data = value
 | 
			
		||||
                plist, err = NSPropertyListSerialization.dataWithPropertyList_format_options_error_(
 | 
			
		||||
                    data, NSPropertyListBinaryFormat_v1_0, 0, None)
 | 
			
		||||
                if not err and plist:
 | 
			
		||||
                    return bytes(plist)
 | 
			
		||||
            except (ImportError, ValueError):
 | 
			
		||||
                pass  # go on to try plutil command
 | 
			
		||||
 | 
			
		||||
            # make xml plist first to convert to binary plist with plutil command,
 | 
			
		||||
            # or to use as a fallback if conversion failed
 | 
			
		||||
            plist = '<string>' + escape(value) + '</string>\n'
 | 
			
		||||
            if attr_is_cfarray:
 | 
			
		||||
                plist = '<array>\n\t' + plist + '</array>\n'
 | 
			
		||||
            plist = (
 | 
			
		||||
                '<?xml version="1.0" encoding="UTF-8"?>\n'
 | 
			
		||||
                '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
 | 
			
		||||
                '<plist version="1.0">\n') + plist + '</plist>'
 | 
			
		||||
            xmlplist = plist.encode('utf-8')
 | 
			
		||||
 | 
			
		||||
            # try plutil command (like `cat xmlplist | plutil -convert binary1 -o - -`)
 | 
			
		||||
            plutil = check_executable('plutil', ['-help'])
 | 
			
		||||
            if plutil:
 | 
			
		||||
                cmd = ([encodeFilename(plutil, True)]
 | 
			
		||||
                       + [encodeArgument(o) for o in ['-convert', 'binary1', '-o', '-', '-']])
 | 
			
		||||
                try:
 | 
			
		||||
                    p = subprocess.Popen(
 | 
			
		||||
                        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
 | 
			
		||||
                    stdout, stderr = p.communicate(input=xmlplist)
 | 
			
		||||
                    if p.returncode == 0:
 | 
			
		||||
                        return bytes(stdout)
 | 
			
		||||
                except EnvironmentError:
 | 
			
		||||
                    pass  # fallback to xml plist
 | 
			
		||||
 | 
			
		||||
            return xmlplist
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5702,7 +5702,9 @@ def write_xattr(path, key, value):
 | 
			
		|||
                    f.write(value)
 | 
			
		||||
            except EnvironmentError as e:
 | 
			
		||||
                raise XAttrMetadataError(e.errno, e.strerror)
 | 
			
		||||
        else:
 | 
			
		||||
        elif not (key.startswith('com.apple.metadata:') and value[:8] == b'bplist00'):
 | 
			
		||||
            # other than macOS binary plist
 | 
			
		||||
 | 
			
		||||
            user_has_setfattr = check_executable('setfattr', ['--version'])
 | 
			
		||||
            user_has_xattr = check_executable('xattr', ['-h'])
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -5743,6 +5745,49 @@ def write_xattr(path, key, value):
 | 
			
		|||
                        "Couldn't find a tool to set the xattrs. "
 | 
			
		||||
                        "Install either the python 'xattr' module, "
 | 
			
		||||
                        "or the 'xattr' binary.")
 | 
			
		||||
        else:
 | 
			
		||||
            # macOS binary plist
 | 
			
		||||
 | 
			
		||||
            # find Apple version xattr command to set binary data in hex string
 | 
			
		||||
            # original xattr project's xattr command doesn't have this feature
 | 
			
		||||
            xattr_bin = None
 | 
			
		||||
            for _bin in ('xattr', '/usr/bin/xattr'):
 | 
			
		||||
                cmd = [encodeFilename(_bin, True), encodeArgument('-h')]
 | 
			
		||||
                try:
 | 
			
		||||
                    p = subprocess.Popen(
 | 
			
		||||
                        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
			
		||||
                except EnvironmentError:
 | 
			
		||||
                    continue
 | 
			
		||||
                stdout, stderr = p.communicate()
 | 
			
		||||
                if p.returncode != 0:
 | 
			
		||||
                    continue
 | 
			
		||||
                stdout = stdout.decode('utf-8', 'replace')
 | 
			
		||||
                # help text must contain '-x: ... hex string for input' line
 | 
			
		||||
                if re.search('-x: .*? hex string for input', stdout):
 | 
			
		||||
                    xattr_bin = _bin
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            if xattr_bin:
 | 
			
		||||
                hexvalue = binascii.hexlify(value)
 | 
			
		||||
                opts = ['-w', '-x', key, hexvalue]
 | 
			
		||||
                cmd = ([encodeFilename(xattr_bin, True)]
 | 
			
		||||
                       + [encodeArgument(o) for o in opts]
 | 
			
		||||
                       + [encodeFilename(path, True)])
 | 
			
		||||
                try:
 | 
			
		||||
                    p = subprocess.Popen(
 | 
			
		||||
                        cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 | 
			
		||||
                except EnvironmentError as e:
 | 
			
		||||
                    raise XAttrMetadataError(e.errno, e.strerror)
 | 
			
		||||
                stdout, stderr = p.communicate()
 | 
			
		||||
                if p.returncode != 0:
 | 
			
		||||
                    stderr = stderr.decode('utf-8', 'replace')
 | 
			
		||||
                    raise XAttrMetadataError(p.returncode, stderr)
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                raise XAttrUnavailableError(
 | 
			
		||||
                    "Couldn't find a tool to set the xattrs. "
 | 
			
		||||
                    "Install either the python 'xattr' module, "
 | 
			
		||||
                    "or the Apple version 'xattr' command.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def random_birthday(year_field, month_field, day_field):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue