瀏覽代碼

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

Philipp Hagemeister 10 年之前
父節點
當前提交
6271f1cad9

+ 29 - 0
youtube_dl/YoutubeDL.py

@@ -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:

+ 1 - 0
youtube_dl/__init__.py

@@ -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:

+ 3 - 0
youtube_dl/extractor/common.py

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

+ 24 - 0
youtube_dl/extractor/youtube.py

@@ -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 {

+ 7 - 0
youtube_dl/options.py

@@ -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',

+ 2 - 0
youtube_dl/postprocessor/__init__.py

@@ -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',

+ 23 - 0
youtube_dl/postprocessor/ffmpeg.py

@@ -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