[youtube|ffmpeg] Automatically correct video with non-square pixels (Fixes #4674)
This commit is contained in:
parent
fb4b030aaf
commit
6271f1cad9
@ -70,6 +70,7 @@ from .extractor import get_info_extractor, gen_extractors
|
|||||||
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 @@ class YoutubeDL(object):
|
|||||||
'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 @@ class YoutubeDL(object):
|
|||||||
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:
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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 @@ class YoutubeIE(YoutubeBaseInfoExtractor, SubtitlesInfoExtractor):
|
|||||||
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 {
|
||||||
|
@ -631,6 +631,13 @@ def parseOpts(overrideArguments=None):
|
|||||||
'--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',
|
||||||
|
@ -6,6 +6,7 @@ from .ffmpeg import (
|
|||||||
FFmpegAudioFixPP,
|
FFmpegAudioFixPP,
|
||||||
FFmpegEmbedSubtitlePP,
|
FFmpegEmbedSubtitlePP,
|
||||||
FFmpegExtractAudioPP,
|
FFmpegExtractAudioPP,
|
||||||
|
FFmpegFixupStretchedPP,
|
||||||
FFmpegMergerPP,
|
FFmpegMergerPP,
|
||||||
FFmpegMetadataPP,
|
FFmpegMetadataPP,
|
||||||
FFmpegVideoConvertorPP,
|
FFmpegVideoConvertorPP,
|
||||||
@ -24,6 +25,7 @@ __all__ = [
|
|||||||
'FFmpegAudioFixPP',
|
'FFmpegAudioFixPP',
|
||||||
'FFmpegEmbedSubtitlePP',
|
'FFmpegEmbedSubtitlePP',
|
||||||
'FFmpegExtractAudioPP',
|
'FFmpegExtractAudioPP',
|
||||||
|
'FFmpegFixupStretchedPP',
|
||||||
'FFmpegMergerPP',
|
'FFmpegMergerPP',
|
||||||
'FFmpegMetadataPP',
|
'FFmpegMetadataPP',
|
||||||
'FFmpegPostProcessor',
|
'FFmpegPostProcessor',
|
||||||
|
@ -50,6 +50,10 @@ class FFmpegPostProcessor(PostProcessor):
|
|||||||
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 @@ class FFmpegAudioFixPP(FFmpegPostProcessor):
|
|||||||
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
|
||||||
|
Loading…
Reference in New Issue
Block a user