diff --git a/youtube_dl/postprocessor/ffmpeg.py b/youtube_dl/postprocessor/ffmpeg.py index 13a7d57de..8b56a0aae 100644 --- a/youtube_dl/postprocessor/ffmpeg.py +++ b/youtube_dl/postprocessor/ffmpeg.py @@ -5,7 +5,23 @@ import os import subprocess import time import re - +import collections +from string import ascii_letters +import random +from ..utils import expand_path +# from youtube_dl import YoutubeDL +# todo fail to import youtubedl class for _NUMERIC_FIELDS +# http://python-notes.curiousefficiency.org/en/latest/python_concepts/import_traps.html +_NUMERIC_FIELDS = set(( + 'width', 'height', 'tbr', 'abr', 'asr', 'vbr', 'fps', 'filesize', 'filesize_approx', + 'timestamp', 'upload_year', 'upload_month', 'upload_day', + 'duration', 'view_count', 'like_count', 'dislike_count', 'repost_count', + 'average_rating', 'comment_count', 'age_limit', + 'start_time', 'end_time', + 'chapter_number', 'season_number', 'episode_number', + 'track_number', 'disc_number', 'release_year', + 'playlist_index', +)) from .common import AudioConversionError, PostProcessor @@ -181,7 +197,7 @@ class FFmpegPostProcessor(PostProcessor): oldest_mtime = min( os.stat(encodeFilename(path)).st_mtime for path in input_paths) - opts += map(lambda s: s % info, self._configuration_args()) + opts += map(lambda s: self._resolve_postprocessor_arg_var(s, info), self._configuration_args()) files_cmd = [] for path in input_paths: @@ -214,6 +230,83 @@ class FFmpegPostProcessor(PostProcessor): # Also leave '-' intact in order not to break streaming to stdout. return 'file:' + fn if fn != '-' else fn + def _resolve_postprocessor_arg_var(self, arg, info): + # map(lambda s: s % info, args) + if "%(" not in arg: + return arg + + template_dict = dict(info) + template_dict['epoch'] = int(time.time()) + # autonumber_size = self.params.get('autonumber_size') + # if autonumber_size is None: + autonumber_size = 5 + # + # template_dict['autonumber'] = self.params.get('autonumber_start', 1) - 1 + self._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_str(v), + # restricted=self.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) + outtmpl = arg + + # 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, outtmpl) + if mobj: + outtmpl = re.sub( + FIELD_SIZE_COMPAT_RE, + r'%%(\1)0%dd' % field_size_compat_map[mobj.group('field')], + outtmpl) + # 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 _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) + (?