Merge 2e45a51464
into d65d89183f
This commit is contained in:
commit
d672093d9e
|
@ -81,7 +81,7 @@ class EmbedThumbnailPP(FFmpegPostProcessor):
|
|||
|
||||
self._downloader.to_screen('[ffmpeg] Adding thumbnail to "%s"' % filename)
|
||||
|
||||
self.run_ffmpeg_multiple_files([filename, thumbnail_filename], temp_filename, options)
|
||||
self.run_ffmpeg_multiple_files([filename, thumbnail_filename], temp_filename, options, info)
|
||||
|
||||
if not self._already_have_thumbnail:
|
||||
os.remove(encodeFilename(thumbnail_filename))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
@ -203,13 +219,13 @@ class FFmpegPostProcessor(PostProcessor):
|
|||
return mobj.group(1)
|
||||
return None
|
||||
|
||||
def run_ffmpeg_multiple_files(self, input_paths, out_path, opts):
|
||||
def run_ffmpeg_multiple_files(self, input_paths, out_path, opts, info):
|
||||
self.check_version()
|
||||
|
||||
oldest_mtime = min(
|
||||
os.stat(encodeFilename(path)).st_mtime for path in input_paths)
|
||||
|
||||
opts += self._configuration_args()
|
||||
opts += map(lambda s: self._resolve_postprocessor_arg_var(s, info), self._configuration_args())
|
||||
|
||||
files_cmd = []
|
||||
for path in input_paths:
|
||||
|
@ -235,8 +251,8 @@ class FFmpegPostProcessor(PostProcessor):
|
|||
raise FFmpegPostProcessorError(msg)
|
||||
self.try_utime(out_path, oldest_mtime, oldest_mtime)
|
||||
|
||||
def run_ffmpeg(self, path, out_path, opts):
|
||||
self.run_ffmpeg_multiple_files([path], out_path, opts)
|
||||
def run_ffmpeg(self, path, out_path, opts, info):
|
||||
self.run_ffmpeg_multiple_files([path], out_path, opts, info)
|
||||
|
||||
def _ffmpeg_filename_argument(self, fn):
|
||||
# Always use 'file:' because the filename may contain ':' (ffmpeg
|
||||
|
@ -245,6 +261,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'(?<!%)%\((?P<field>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)
|
||||
(?<!%)
|
||||
%
|
||||
\({0}\) # mapping key
|
||||
(?:[#0\-+ ]+)? # conversion flags (optional)
|
||||
(?:\d+)? # minimum field width (optional)
|
||||
(?:\.\d+)? # precision (optional)
|
||||
[hlL]? # length modifier (optional)
|
||||
[diouxXeEfFgGcrs%] # conversion type
|
||||
'''
|
||||
outtmpl = re.sub(
|
||||
FORMAT_RE.format(numeric_field),
|
||||
r'%({0})s'.format(numeric_field), outtmpl)
|
||||
# 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)])
|
||||
outtmpl = outtmpl.replace('%%', '%{0}%'.format(sep)).replace('$$', '${0}$'.format(sep))
|
||||
# outtmpl 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 outtmpl "%(title)s.%(ext)s" and
|
||||
# title "Hello $PATH", we don't want `$PATH` to be expanded.
|
||||
# todo need encoding & sanitize_path ?
|
||||
return expand_path(outtmpl).replace(sep, '') % template_dict
|
||||
|
||||
|
||||
class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
||||
def __init__(self, downloader=None, preferredcodec=None, preferredquality=None, nopostoverwrites=False):
|
||||
|
@ -255,14 +348,14 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
|||
self._preferredquality = preferredquality
|
||||
self._nopostoverwrites = nopostoverwrites
|
||||
|
||||
def run_ffmpeg(self, path, out_path, codec, more_opts):
|
||||
def run_ffmpeg(self, path, out_path, codec, more_opts, info):
|
||||
if codec is None:
|
||||
acodec_opts = []
|
||||
else:
|
||||
acodec_opts = ['-acodec', codec]
|
||||
opts = ['-vn'] + acodec_opts + more_opts
|
||||
try:
|
||||
FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts)
|
||||
FFmpegPostProcessor.run_ffmpeg(self, path, out_path, opts, info)
|
||||
except FFmpegPostProcessorError as err:
|
||||
raise AudioConversionError(err.msg)
|
||||
|
||||
|
@ -333,7 +426,7 @@ class FFmpegExtractAudioPP(FFmpegPostProcessor):
|
|||
|
||||
try:
|
||||
self._downloader.to_screen('[ffmpeg] Destination: ' + new_path)
|
||||
self.run_ffmpeg(path, new_path, acodec, more_opts)
|
||||
self.run_ffmpeg(path, new_path, acodec, more_opts, information)
|
||||
except AudioConversionError as e:
|
||||
raise PostProcessingError(
|
||||
'audio conversion failed: ' + e.msg)
|
||||
|
@ -365,7 +458,7 @@ class FFmpegVideoConvertorPP(FFmpegPostProcessor):
|
|||
prefix, sep, ext = path.rpartition('.')
|
||||
outpath = prefix + sep + self._preferedformat
|
||||
self._downloader.to_screen('[' + 'ffmpeg' + '] Converting video from %s to %s, Destination: ' % (information['ext'], self._preferedformat) + outpath)
|
||||
self.run_ffmpeg(path, outpath, options)
|
||||
self.run_ffmpeg(path, outpath, options, information)
|
||||
information['filepath'] = outpath
|
||||
information['format'] = self._preferedformat
|
||||
information['ext'] = self._preferedformat
|
||||
|
@ -423,7 +516,7 @@ class FFmpegEmbedSubtitlePP(FFmpegPostProcessor):
|
|||
|
||||
temp_filename = prepend_extension(filename, 'temp')
|
||||
self._downloader.to_screen('[ffmpeg] Embedding subtitles in \'%s\'' % filename)
|
||||
self.run_ffmpeg_multiple_files(input_files, temp_filename, opts)
|
||||
self.run_ffmpeg_multiple_files(input_files, temp_filename, opts, information)
|
||||
os.remove(encodeFilename(filename))
|
||||
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
|
||||
|
||||
|
@ -506,7 +599,7 @@ class FFmpegMetadataPP(FFmpegPostProcessor):
|
|||
options.extend(['-map_metadata', '1'])
|
||||
|
||||
self._downloader.to_screen('[ffmpeg] Adding metadata to \'%s\'' % filename)
|
||||
self.run_ffmpeg_multiple_files(in_filenames, temp_filename, options)
|
||||
self.run_ffmpeg_multiple_files(in_filenames, temp_filename, options, info)
|
||||
if chapters:
|
||||
os.remove(metadata_filename)
|
||||
os.remove(encodeFilename(filename))
|
||||
|
@ -520,7 +613,7 @@ class FFmpegMergerPP(FFmpegPostProcessor):
|
|||
temp_filename = prepend_extension(filename, 'temp')
|
||||
args = ['-c', 'copy', '-map', '0:v:0', '-map', '1:a:0']
|
||||
self._downloader.to_screen('[ffmpeg] Merging formats into "%s"' % filename)
|
||||
self.run_ffmpeg_multiple_files(info['__files_to_merge'], temp_filename, args)
|
||||
self.run_ffmpeg_multiple_files(info['__files_to_merge'], temp_filename, args, info)
|
||||
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
|
||||
return info['__files_to_merge'], info
|
||||
|
||||
|
@ -553,7 +646,7 @@ class FFmpegFixupStretchedPP(FFmpegPostProcessor):
|
|||
|
||||
options = ['-c', 'copy', '-aspect', '%f' % stretched_ratio]
|
||||
self._downloader.to_screen('[ffmpeg] Fixing aspect ratio in "%s"' % filename)
|
||||
self.run_ffmpeg(filename, temp_filename, options)
|
||||
self.run_ffmpeg(filename, temp_filename, options, info)
|
||||
|
||||
os.remove(encodeFilename(filename))
|
||||
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
|
||||
|
@ -571,7 +664,7 @@ class FFmpegFixupM4aPP(FFmpegPostProcessor):
|
|||
|
||||
options = ['-c', 'copy', '-f', 'mp4']
|
||||
self._downloader.to_screen('[ffmpeg] Correcting container in "%s"' % filename)
|
||||
self.run_ffmpeg(filename, temp_filename, options)
|
||||
self.run_ffmpeg(filename, temp_filename, options, info)
|
||||
|
||||
os.remove(encodeFilename(filename))
|
||||
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
|
||||
|
@ -587,7 +680,7 @@ class FFmpegFixupM3u8PP(FFmpegPostProcessor):
|
|||
|
||||
options = ['-c', 'copy', '-f', 'mp4', '-bsf:a', 'aac_adtstoasc']
|
||||
self._downloader.to_screen('[ffmpeg] Fixing malformed AAC bitstream in "%s"' % filename)
|
||||
self.run_ffmpeg(filename, temp_filename, options)
|
||||
self.run_ffmpeg(filename, temp_filename, options, info)
|
||||
|
||||
os.remove(encodeFilename(filename))
|
||||
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
|
||||
|
@ -646,7 +739,7 @@ class FFmpegSubtitlesConvertorPP(FFmpegPostProcessor):
|
|||
else:
|
||||
sub_filenames.append(srt_file)
|
||||
|
||||
self.run_ffmpeg(old_file, new_file, ['-f', new_format])
|
||||
self.run_ffmpeg(old_file, new_file, ['-f', new_format], info)
|
||||
|
||||
with io.open(new_file, 'rt', encoding='utf-8') as f:
|
||||
subs[lang] = {
|
||||
|
|
Loading…
Reference in New Issue