2
0
Philipp Hagemeister 10 жил өмнө
parent
commit
cfb56d1af3

+ 11 - 0
test/test_utils.py

@@ -52,6 +52,7 @@ from youtube_dl.utils import (
     urlencode_postdata,
     urlencode_postdata,
     version_tuple,
     version_tuple,
     xpath_with_ns,
     xpath_with_ns,
+    render_table,
 )
 )
 
 
 
 
@@ -434,5 +435,15 @@ ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
         self.assertTrue(is_html(  # UTF-32-LE
         self.assertTrue(is_html(  # UTF-32-LE
             b'\xFF\xFE\x00\x00<\x00\x00\x00h\x00\x00\x00t\x00\x00\x00m\x00\x00\x00l\x00\x00\x00>\x00\x00\x00\xe4\x00\x00\x00'))
             b'\xFF\xFE\x00\x00<\x00\x00\x00h\x00\x00\x00t\x00\x00\x00m\x00\x00\x00l\x00\x00\x00>\x00\x00\x00\xe4\x00\x00\x00'))
 
 
+    def test_render_table(self):
+        self.assertEqual(
+            render_table(
+                ['a', 'bcd'],
+                [[123, 4], [9999, 51]]),
+            'a    bcd\n'
+            '123  4\n'
+            '9999 51')
+
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 33 - 4
youtube_dl/YoutubeDL.py

@@ -54,6 +54,7 @@ from .utils import (
     PostProcessingError,
     PostProcessingError,
     platform_name,
     platform_name,
     preferredencoding,
     preferredencoding,
+    render_table,
     SameFileError,
     SameFileError,
     sanitize_filename,
     sanitize_filename,
     std_headers,
     std_headers,
@@ -221,6 +222,8 @@ class YoutubeDL(object):
                        youtube-dl servers for debugging.
                        youtube-dl servers for debugging.
     sleep_interval:    Number of seconds to sleep before each download.
     sleep_interval:    Number of seconds to sleep before each download.
     external_downloader:  Executable of the external downloader to call.
     external_downloader:  Executable of the external downloader to call.
+    listformats:       Print an overview of available video formats and exit.
+    list_thumbnails:   Print a table of all thumbnails and exit.
 
 
 
 
     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
@@ -916,9 +919,14 @@ class YoutubeDL(object):
             info_dict['playlist_index'] = None
             info_dict['playlist_index'] = None
 
 
         thumbnails = info_dict.get('thumbnails')
         thumbnails = info_dict.get('thumbnails')
+        if thumbnails is None:
+            thumbnail = info_dict.get('thumbnail')
+            if thumbnail:
+                thumbnails = [{'url': thumbnail}]
         if thumbnails:
         if thumbnails:
             thumbnails.sort(key=lambda t: (
             thumbnails.sort(key=lambda t: (
-                t.get('width'), t.get('height'), t.get('url')))
+                t.get('preference'), t.get('width'), t.get('height'),
+                t.get('id'), t.get('url')))
             for t in thumbnails:
             for t in thumbnails:
                 if 'width' in t and 'height' in t:
                 if 'width' in t and 'height' in t:
                     t['resolution'] = '%dx%d' % (t['width'], t['height'])
                     t['resolution'] = '%dx%d' % (t['width'], t['height'])
@@ -990,9 +998,12 @@ class YoutubeDL(object):
             # element in the 'formats' field in info_dict is info_dict itself,
             # element in the 'formats' field in info_dict is info_dict itself,
             # wich can't be exported to json
             # wich can't be exported to json
             info_dict['formats'] = formats
             info_dict['formats'] = formats
-        if self.params.get('listformats', None):
+        if self.params.get('listformats'):
             self.list_formats(info_dict)
             self.list_formats(info_dict)
             return
             return
+        if self.params.get('list_thumbnails'):
+            self.list_thumbnails(info_dict)
+            return
 
 
         req_format = self.params.get('format')
         req_format = self.params.get('format')
         if req_format is None:
         if req_format is None:
@@ -1500,8 +1511,26 @@ class YoutubeDL(object):
         header_line = line({
         header_line = line({
             'format_id': 'format code', 'ext': 'extension',
             'format_id': 'format code', 'ext': 'extension',
             'resolution': 'resolution', 'format_note': 'note'}, idlen=idlen)
             'resolution': 'resolution', 'format_note': 'note'}, idlen=idlen)
-        self.to_screen('[info] Available formats for %s:\n%s\n%s' %
-                       (info_dict['id'], header_line, '\n'.join(formats_s)))
+        self.to_screen(
+            '[info] Available formats for %s:\n%s\n%s' %
+            (info_dict['id'], header_line, '\n'.join(formats_s)))
+
+    def list_thumbnails(self, info_dict):
+        thumbnails = info_dict.get('thumbnails')
+        if not thumbnails:
+            tn_url = info_dict.get('thumbnail')
+            if tn_url:
+                thumbnails = [{'id': '0', 'url': tn_url}]
+            else:
+                self.to_screen(
+                    '[info] No thumbnails present for %s' % info_dict['id'])
+                return
+
+        self.to_screen(
+            '[info] Thumbnails for %s:' % info_dict['id'])
+        self.to_screen(render_table(
+            ['ID', 'width', 'height', 'URL'],
+            [[t['id'], t.get('width', 'unknown'), t.get('height', 'unknown'), t['url']] for t in thumbnails]))
 
 
     def urlopen(self, req):
     def urlopen(self, req):
         """ Start an HTTP download """
         """ Start an HTTP download """

+ 1 - 0
youtube_dl/__init__.py

@@ -331,6 +331,7 @@ def _real_main(argv=None):
         'call_home': opts.call_home,
         'call_home': opts.call_home,
         'sleep_interval': opts.sleep_interval,
         'sleep_interval': opts.sleep_interval,
         'external_downloader': opts.external_downloader,
         'external_downloader': opts.external_downloader,
+        'list_thumbnails': opts.list_thumbnails,
     }
     }
 
 
     with YoutubeDL(ydl_opts) as ydl:
     with YoutubeDL(ydl_opts) as ydl:

+ 2 - 0
youtube_dl/extractor/common.py

@@ -129,7 +129,9 @@ class InfoExtractor(object):
                     something like "4234987", title "Dancing naked mole rats",
                     something like "4234987", title "Dancing naked mole rats",
                     and display_id "dancing-naked-mole-rats"
                     and display_id "dancing-naked-mole-rats"
     thumbnails:     A list of dictionaries, with the following entries:
     thumbnails:     A list of dictionaries, with the following entries:
+                        * "id" (optional, string) - Thumbnail format ID
                         * "url"
                         * "url"
+                        * "preference" (optional, int) - quality of the image
                         * "width" (optional, int)
                         * "width" (optional, int)
                         * "height" (optional, int)
                         * "height" (optional, int)
                         * "resolution" (optional, string "{width}x{height"},
                         * "resolution" (optional, string "{width}x{height"},

+ 14 - 2
youtube_dl/extractor/testtube.py

@@ -1,7 +1,10 @@
 from __future__ import unicode_literals
 from __future__ import unicode_literals
 
 
 from .common import InfoExtractor
 from .common import InfoExtractor
-from ..utils import int_or_none
+from ..utils import (
+    int_or_none,
+    qualities,
+)
 
 
 
 
 class TestTubeIE(InfoExtractor):
 class TestTubeIE(InfoExtractor):
@@ -46,13 +49,22 @@ class TestTubeIE(InfoExtractor):
         self._sort_formats(formats)
         self._sort_formats(formats)
 
 
         duration = int_or_none(info.get('duration'))
         duration = int_or_none(info.get('duration'))
+        images = info.get('images')
+        thumbnails = None
+        preference = qualities(['mini', 'small', 'medium', 'large'])
+        if images:
+            thumbnails = [{
+                'id': thumbnail_id,
+                'url': img_url,
+                'preference': preference(thumbnail_id)
+            } for thumbnail_id, img_url in images.items()]
 
 
         return {
         return {
             'id': video_id,
             'id': video_id,
             'display_id': display_id,
             'display_id': display_id,
             'title': info['title'],
             'title': info['title'],
             'description': info.get('summary'),
             'description': info.get('summary'),
-            'thumbnail': info.get('images', {}).get('large'),
+            'thumbnails': thumbnails,
             'uploader': info.get('show', {}).get('name'),
             'uploader': info.get('show', {}).get('name'),
             'uploader_id': info.get('show', {}).get('slug'),
             'uploader_id': info.get('show', {}).get('slug'),
             'duration': duration,
             'duration': duration,

+ 11 - 4
youtube_dl/options.py

@@ -614,10 +614,6 @@ def parseOpts(overrideArguments=None):
         '--write-annotations',
         '--write-annotations',
         action='store_true', dest='writeannotations', default=False,
         action='store_true', dest='writeannotations', default=False,
         help='write video annotations to a .annotation file')
         help='write video annotations to a .annotation file')
-    filesystem.add_option(
-        '--write-thumbnail',
-        action='store_true', dest='writethumbnail', default=False,
-        help='write thumbnail image to disk')
     filesystem.add_option(
     filesystem.add_option(
         '--load-info',
         '--load-info',
         dest='load_info_filename', metavar='FILE',
         dest='load_info_filename', metavar='FILE',
@@ -637,6 +633,16 @@ def parseOpts(overrideArguments=None):
         action='store_true', dest='rm_cachedir',
         action='store_true', dest='rm_cachedir',
         help='Delete all filesystem cache files')
         help='Delete all filesystem cache files')
 
 
+    thumbnail = optparse.OptionGroup(parser, 'Thumbnail images')
+    thumbnail.add_option(
+        '--write-thumbnail',
+        action='store_true', dest='writethumbnail', default=False,
+        help='write thumbnail image to disk')
+    thumbnail.add_option(
+        '--list-thumbnails',
+        action='store_true', dest='list_thumbnails', default=False,
+        help='Simulate and list all available thumbnail formats')
+
     postproc = optparse.OptionGroup(parser, 'Post-processing Options')
     postproc = optparse.OptionGroup(parser, 'Post-processing Options')
     postproc.add_option(
     postproc.add_option(
         '-x', '--extract-audio',
         '-x', '--extract-audio',
@@ -702,6 +708,7 @@ def parseOpts(overrideArguments=None):
     parser.add_option_group(selection)
     parser.add_option_group(selection)
     parser.add_option_group(downloader)
     parser.add_option_group(downloader)
     parser.add_option_group(filesystem)
     parser.add_option_group(filesystem)
+    parser.add_option_group(thumbnail)
     parser.add_option_group(verbosity)
     parser.add_option_group(verbosity)
     parser.add_option_group(workarounds)
     parser.add_option_group(workarounds)
     parser.add_option_group(video_format)
     parser.add_option_group(video_format)

+ 8 - 0
youtube_dl/utils.py

@@ -1659,3 +1659,11 @@ def determine_protocol(info_dict):
         return 'f4m'
         return 'f4m'
 
 
     return compat_urllib_parse_urlparse(url).scheme
     return compat_urllib_parse_urlparse(url).scheme
+
+
+def render_table(header_row, data):
+    """ Render a list of rows, each as a list of values """
+    table = [header_row] + data
+    max_lens = [max(len(compat_str(v)) for v in col) for col in zip(*table)]
+    format_str = ' '.join('%-' + compat_str(ml + 1) + 's' for ml in max_lens[:-1]) + '%s'
+    return '\n'.join(format_str % tuple(row) for row in table)