diff --git a/youtube_dl/YoutubeDL.py b/youtube_dl/YoutubeDL.py index 523dd1f7d..29f183892 100755 --- a/youtube_dl/YoutubeDL.py +++ b/youtube_dl/YoutubeDL.py @@ -2025,6 +2025,11 @@ class YoutubeDL(object): """Run all the postprocessors on the given file.""" info = dict(ie_info) info['filepath'] = filename + info['pp_extras'] = { + 'params': dict(self.params), + '_num_downloads': self._num_downloads, + '_NUMERIC_FIELDS': self._NUMERIC_FIELDS, + } pps_chain = [] if ie_info.get('__postprocessors') is not None: pps_chain.extend(ie_info['__postprocessors']) diff --git a/youtube_dl/postprocessor/execafterdownload.py b/youtube_dl/postprocessor/execafterdownload.py index dc13cf5cc..2c1e340d6 100644 --- a/youtube_dl/postprocessor/execafterdownload.py +++ b/youtube_dl/postprocessor/execafterdownload.py @@ -1,12 +1,25 @@ from __future__ import unicode_literals +import collections +import random +import re import subprocess import sys +import time from .common import PostProcessor -from ..compat import compat_shlex_quote +from ..compat import ( + compat_numeric_types, + compat_shlex_quote, + compat_str, +) +from string import ascii_letters from ..utils import ( encodeArgument, + encodeFilename, + expand_path, + preferredencoding, + sanitize_filename, PostProcessingError, ) @@ -16,20 +29,102 @@ class ExecAfterDownloadPP(PostProcessor): super(ExecAfterDownloadPP, self).__init__(downloader) self.exec_cmd = exec_cmd + def prepare_cmd(self, info_dict, extras): + """Generate the command.""" + try: + template_dict = dict(info_dict) + + template_dict['epoch'] = int(time.time()) + autonumber_size = extras['params'].get('autonumber_size') + if autonumber_size is None: + autonumber_size = 5 + template_dict['autonumber'] = extras['params'].get('autonumber_start', 1) - 1 + extras['_num_downloads'] + if template_dict.get('resolution') is None: + if template_dict.get('width') and template_dict.get('height'): + template_dict['resolution'] = '%dx%d' % (template_dict['width'], template_dict['height']) + elif template_dict.get('height'): + template_dict['resolution'] = '%sp' % template_dict['height'] + elif template_dict.get('width'): + template_dict['resolution'] = '%dx?' % template_dict['width'] + + sanitize = lambda k, v: sanitize_filename( + compat_shlex_quote(compat_str(v)), + restricted=extras['params'].get('restrictfilenames'), + is_id=(k == 'id' or k.endswith('_id'))) + template_dict = dict((k, v if isinstance(v, compat_numeric_types) else sanitize(k, v)) + for k, v in template_dict.items() + if v is not None and not isinstance(v, (list, tuple, dict))) + template_dict = collections.defaultdict(lambda: 'NA', template_dict) + + cmdtmpl = self.exec_cmd + cmdtmpl = cmdtmpl.replace('{}', '%(filepath)s') + if '%(filepath)s' not in cmdtmpl: + cmdtmpl += ' %(filepath)s' + + # For fields playlist_index and autonumber convert all occurrences + # of %(field)s to %(field)0Nd for backward compatibility + field_size_compat_map = { + 'playlist_index': len(str(template_dict['n_entries'])), + 'autonumber': autonumber_size, + } + FIELD_SIZE_COMPAT_RE = r'(?autonumber|playlist_index)\)s' + mobj = re.search(FIELD_SIZE_COMPAT_RE, cmdtmpl) + if mobj: + cmdtmpl = re.sub( + FIELD_SIZE_COMPAT_RE, + r'%%(\1)0%dd' % field_size_compat_map[mobj.group('field')], + cmdtmpl) + + # Missing numeric fields used together with integer presentation types + # in format specification will break the argument substitution since + # string 'NA' is returned for missing fields. We will patch output + # template for missing fields to meet string presentation type. + for numeric_field in extras['_NUMERIC_FIELDS']: + if numeric_field not in template_dict: + # As of [1] format syntax is: + # %[mapping_key][conversion_flags][minimum_width][.precision][length_modifier]type + # 1. https://docs.python.org/2/library/stdtypes.html#string-formatting + FORMAT_RE = r'''(?x) + (? 2 else (str, unicode) - info = {} + extras = dict(information['pp_extras']) + del information['pp_extras'] - cmd = cmd.replace('{}', '%(filepath)s') - if '%(filepath)s' not in cmd: - cmd += ' %(filepath)s' - - for key in information: - value = information[key] - info[key] = compat_shlex_quote(value) if isinstance(value, str_types) else value - - cmd = cmd % info + cmd = self.prepare_cmd(information, extras) self._downloader.to_screen('[exec] Executing command: %s' % cmd) retCode = subprocess.call(encodeArgument(cmd), shell=True)