Add --list-thumbnails

This commit is contained in:
Philipp Hagemeister 2015-01-25 02:38:47 +01:00
parent 1e10802990
commit cfb56d1af3
7 changed files with 80 additions and 10 deletions

View File

@ -52,6 +52,7 @@
urlencode_postdata, urlencode_postdata,
version_tuple, version_tuple,
xpath_with_ns, xpath_with_ns,
render_table,
) )
@ -434,5 +435,15 @@ def test_is_html(self):
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()

View File

@ -54,6 +54,7 @@
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 @@ def process_video_result(self, info_dict, download=True):
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 @@ def process_video_result(self, info_dict, download=True):
# 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 @@ def line(format, idlen=20):
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' % self.to_screen(
(info_dict['id'], header_line, '\n'.join(formats_s))) '[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 """

View File

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

View File

@ -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"},

View File

@ -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 @@ def _real_extract(self, url):
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,

View File

@ -614,10 +614,6 @@ def _hide_login_info(opts):
'--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 _hide_login_info(opts):
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 _hide_login_info(opts):
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)

View File

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