[youtube|ffmpeg] Automatically correct video with non-square pixels (Fixes #4674)

This commit is contained in:
Philipp Hagemeister 2015-01-10 05:45:51 +01:00
parent fb4b030aaf
commit 6271f1cad9
7 changed files with 89 additions and 0 deletions

View File

@ -70,6 +70,7 @@
from .downloader import get_suitable_downloader from .downloader import get_suitable_downloader
from .downloader.rtmp import rtmpdump_version from .downloader.rtmp import rtmpdump_version
from .postprocessor import ( from .postprocessor import (
FFmpegFixupStretchedPP,
FFmpegMergerPP, FFmpegMergerPP,
FFmpegPostProcessor, FFmpegPostProcessor,
get_postprocessor, get_postprocessor,
@ -204,6 +205,12 @@ class YoutubeDL(object):
Progress hooks are guaranteed to be called at least once Progress hooks are guaranteed to be called at least once
(with status "finished") if the download is successful. (with status "finished") if the download is successful.
merge_output_format: Extension to use when merging formats. merge_output_format: Extension to use when merging formats.
fixup: Automatically correct known faults of the file.
One of:
- "never": do nothing
- "warn": only emit a warning
- "detect_or_warn": check whether we can do anything
about it, warn otherwise
The following parameters are not used by YoutubeDL itself, they are used by The following parameters are not used by YoutubeDL itself, they are used by
@ -924,6 +931,7 @@ def process_video_result(self, info_dict, download=True):
'fps': formats_info[0].get('fps'), 'fps': formats_info[0].get('fps'),
'vcodec': formats_info[0].get('vcodec'), 'vcodec': formats_info[0].get('vcodec'),
'vbr': formats_info[0].get('vbr'), 'vbr': formats_info[0].get('vbr'),
'stretched_ratio': formats_info[0].get('stretched_ratio'),
'acodec': formats_info[1].get('acodec'), 'acodec': formats_info[1].get('acodec'),
'abr': formats_info[1].get('abr'), 'abr': formats_info[1].get('abr'),
'ext': output_ext, 'ext': output_ext,
@ -1154,6 +1162,27 @@ def dl(name, info):
return return
if success: if success:
# Fixup content
stretched_ratio = info_dict.get('stretched_ratio')
if stretched_ratio is not None and stretched_ratio != 1:
fixup_policy = self.params.get('fixup')
if fixup_policy is None:
fixup_policy = 'detect_or_warn'
if fixup_policy == 'warn':
self.report_warning('%s: Non-uniform pixel ratio (%s)' % (
info_dict['id'], stretched_ratio))
elif fixup_policy == 'detect_or_warn':
stretched_pp = FFmpegFixupStretchedPP(self)
if stretched_pp.available:
info_dict.setdefault('__postprocessors', [])
info_dict['__postprocessors'].append(stretched_pp)
else:
self.report_warning(
'%s: Non-uniform pixel ratio (%s). Install ffmpeg or avconv to fix this automatically.' % (
info_dict['id'], stretched_ratio))
else:
assert fixup_policy == 'ignore'
try: try:
self.post_process(filename, info_dict) self.post_process(filename, info_dict)
except (PostProcessingError) as err: except (PostProcessingError) as err:

View File

@ -326,6 +326,7 @@ def _real_main(argv=None):
'extract_flat': opts.extract_flat, 'extract_flat': opts.extract_flat,
'merge_output_format': opts.merge_output_format, 'merge_output_format': opts.merge_output_format,
'postprocessors': postprocessors, 'postprocessors': postprocessors,
'fixup': opts.fixup,
} }
with YoutubeDL(ydl_opts) as ydl: with YoutubeDL(ydl_opts) as ydl:

View File

@ -114,6 +114,9 @@ class InfoExtractor(object):
to add to the request. to add to the request.
* http_post_data Additional data to send with a POST * http_post_data Additional data to send with a POST
request. request.
* stretched_ratio If given and not 1, indicates that the
video's pixels are not square.
width : height ratio as float.
url: Final video URL. url: Final video URL.
ext: Video filename extension. ext: Video filename extension.
format: The video format, defaults to ext (used for --get-format) format: The video format, defaults to ext (used for --get-format)

View File

@ -465,6 +465,20 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
'skip_download': 'requires avconv', 'skip_download': 'requires avconv',
} }
}, },
# Non-square pixels
{
'url': 'https://www.youtube.com/watch?v=_b-2C3KPAM0',
'info_dict': {
'id': '_b-2C3KPAM0',
'ext': 'mp4',
'stretched_ratio': 16 / 9.,
'upload_date': '20110310',
'uploader_id': 'AllenMeow',
'description': 'made by Wacom from Korea | 字幕&加油添醋 by TY\'s Allen | 感謝heylisa00cavey1001同學熱情提供梗及翻譯',
'uploader': '孫艾倫',
'title': '[A-made] 變態妍字幕版 太妍 我就是這樣的人',
},
}
] ]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -1051,6 +1065,16 @@ def _map_to_format_list(urlmap):
f['preference'] = f.get('preference', 0) - 10000 f['preference'] = f.get('preference', 0) - 10000
formats.extend(dash_formats) formats.extend(dash_formats)
# Check for malformed aspect ratio
stretched_m = re.search(
r'<meta\s+property="og:video:tag".*?content="yt:stretch=(?P<w>[0-9]+):(?P<h>[0-9]+)">',
video_webpage)
if stretched_m:
ratio = float(stretched_m.group('w')) / float(stretched_m.group('h'))
for f in formats:
if f.get('vcodec') != 'none':
f['stretched_ratio'] = ratio
self._sort_formats(formats) self._sort_formats(formats)
return { return {

View File

@ -631,6 +631,13 @@ def _hide_login_info(opts):
'--xattrs', '--xattrs',
action='store_true', dest='xattrs', default=False, 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)')
postproc.add_option(
'--fixup',
metavar='POLICY', dest='fixup', default='detect_or_warn',
help='(experimental) Automatically correct known faults of the file. '
'One of never (do nothing), warn (only emit a warning), '
'detect_or_warn(check whether we can do anything about it, warn '
'otherwise')
postproc.add_option( postproc.add_option(
'--prefer-avconv', '--prefer-avconv',
action='store_false', dest='prefer_ffmpeg', action='store_false', dest='prefer_ffmpeg',

View File

@ -6,6 +6,7 @@
FFmpegAudioFixPP, FFmpegAudioFixPP,
FFmpegEmbedSubtitlePP, FFmpegEmbedSubtitlePP,
FFmpegExtractAudioPP, FFmpegExtractAudioPP,
FFmpegFixupStretchedPP,
FFmpegMergerPP, FFmpegMergerPP,
FFmpegMetadataPP, FFmpegMetadataPP,
FFmpegVideoConvertorPP, FFmpegVideoConvertorPP,
@ -24,6 +25,7 @@ def get_postprocessor(key):
'FFmpegAudioFixPP', 'FFmpegAudioFixPP',
'FFmpegEmbedSubtitlePP', 'FFmpegEmbedSubtitlePP',
'FFmpegExtractAudioPP', 'FFmpegExtractAudioPP',
'FFmpegFixupStretchedPP',
'FFmpegMergerPP', 'FFmpegMergerPP',
'FFmpegMetadataPP', 'FFmpegMetadataPP',
'FFmpegPostProcessor', 'FFmpegPostProcessor',

View File

@ -50,6 +50,10 @@ def get_versions():
programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe'] programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
return dict((p, get_exe_version(p, args=['-version'])) for p in programs) return dict((p, get_exe_version(p, args=['-version'])) for p in programs)
@property
def available(self):
return self._executable is not None
@property @property
def _executable(self): def _executable(self):
if self._downloader.params.get('prefer_ffmpeg', False): if self._downloader.params.get('prefer_ffmpeg', False):
@ -540,3 +544,22 @@ def run(self, info):
os.rename(encodeFilename(temp_filename), encodeFilename(filename)) os.rename(encodeFilename(temp_filename), encodeFilename(filename))
return True, info return True, info
class FFmpegFixupStretchedPP(FFmpegPostProcessor):
def run(self, info):
stretched_ratio = info.get('stretched_ratio')
if stretched_ratio is None or stretched_ratio == 1:
return
filename = info['filepath']
temp_filename = prepend_extension(filename, 'temp')
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)
os.remove(encodeFilename(filename))
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
return True, info