1
0
mirror of https://github.com/l1ving/youtube-dl synced 2025-03-11 01:07:25 +08:00

Before passing info to pp.run, an extra field pp_extras is added. This dict contains some variables needed for the code within prepare_filename, for which prepare_cmd is almost an exact copy. Before returning info from pp.run we assign the value of info['pp_extras'] to a regular variable extras and call del info['pp_extras'].

This commit is contained in:
joncody 2018-04-08 19:57:10 -07:00
parent 1416f2e7bc
commit 167ca6abf9
2 changed files with 113 additions and 13 deletions

View File

@ -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'])

View File

@ -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'(?<!%)%\((?P<field>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)
(?<!%)
%
\({0}\) # mapping key
(?:[#0\-+ ]+)? # conversion flags (optional)
(?:\d+)? # minimum field width (optional)
(?:\.\d+)? # precision (optional)
[hlL]? # length modifier (optional)
[diouxXeEfFgGcrs%] # conversion type
'''
cmdtmpl = re.sub(
FORMAT_RE.format(numeric_field),
r'%({0})s'.format(numeric_field), cmdtmpl)
# expand_path translates '%%' into '%' and '$$' into '$'
# correspondingly that is not what we want since we need to keep
# '%%' intact for template dict substitution step. Working around
# with boundary-alike separator hack.
sep = ''.join([random.choice(ascii_letters) for _ in range(32)])
cmdtmpl = cmdtmpl.replace('%%', '%{0}%'.format(sep)).replace('$$', '${0}$'.format(sep))
# cmdtmpl should be expand_path'ed before template dict substitution
# because meta fields may contain env variables we don't want to
# be expanded. For example, for cmdtmpl "%(title)s.%(ext)s" and
# title "Hello $PATH", we don't want `$PATH` to be expanded.
cmd = expand_path(cmdtmpl).replace(sep, '') % template_dict
# Temporary fix for #4787
# 'Treat' all problem characters by passing filename through preferredencoding
# to workaround encoding issues with subprocess on python2 @ Windows
if sys.version_info < (3, 0) and sys.platform == 'win32':
cmd = encodeFilename(cmd, True).decode(preferredencoding())
return cmd
except ValueError as err:
raise PostProcessingError(err)
def run(self, information):
cmd = self.exec_cmd
str_types = (str) if sys.version_info.major > 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)