From 3e9d146a37e33be6d6d3056a9db500e25259d96a Mon Sep 17 00:00:00 2001 From: pukkandan Date: Fri, 6 Nov 2020 01:00:19 +0530 Subject: [PATCH] Option to present -F output to a more tabular form --- README.md | 1 + youtube_dlc/YoutubeDL.py | 55 +++++++++++++++++++++++++++++++++++----- youtube_dlc/__init__.py | 1 + youtube_dlc/options.py | 4 +++ youtube_dlc/utils.py | 13 ++++++++-- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 170c85c48..3b065b89d 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,7 @@ I will add some memorable short links to the binaries so you can download them e one is requested -F, --list-formats List all available formats of requested videos + --list-formats-as-table Present the output of -F in a more tabular form --youtube-skip-dash-manifest Do not download the DASH manifests and related data on YouTube videos --youtube-skip-hls-manifest Do not download the HLS manifests and diff --git a/youtube_dlc/YoutubeDL.py b/youtube_dlc/YoutubeDL.py index bf02192eb..ea55b36ea 100644 --- a/youtube_dlc/YoutubeDL.py +++ b/youtube_dlc/YoutubeDL.py @@ -58,6 +58,7 @@ from .utils import ( expand_path, ExtractorError, format_bytes, + format_field, formatSeconds, GeoRestrictedError, int_or_none, @@ -280,6 +281,7 @@ class YoutubeDL(object): Actual sleep time will be a random float from range [sleep_interval; max_sleep_interval]. listformats: Print an overview of available video formats and exit. + listformats_table: Present the output of listformats in a more tabular form list_thumbnails: Print a table of all thumbnails and exit. match_filter: A function that gets called with the info_dict of every video. @@ -2287,20 +2289,59 @@ class YoutubeDL(object): res += ', ' res += '~' + format_bytes(fdict['filesize_approx']) return res + + def _format_note_table(self, f): + def join_fields(*vargs): + return ', '.join((val for val in vargs if val != '')) + + return join_fields( + 'UNSUPPORTED' if f.get('ext') in ('f4f', 'f4m') else '', + format_field(f, 'language', '[%s]'), + format_field(f, 'format_note'), + format_field(f, 'container', ignore=(None, f.get('ext'))), + format_field(f, 'asr', '%5dHz')) def list_formats(self, info_dict): formats = info_dict.get('formats', [info_dict]) - table = [ - [f['format_id'], f['ext'], self.format_resolution(f), self._format_note(f)] - for f in formats - if f.get('preference') is None or f['preference'] >= -1000] + new_format = self.params.get('listformats_table', False) + if new_format: + table = [ + [ + format_field(f, 'format_id'), + format_field(f, 'ext'), + self.format_resolution(f), + format_field(f, 'fps', '%d'), + '|', + format_field(f, 'filesize', ' %s', func=format_bytes) + format_field(f, 'filesize_approx', '~%s', func=format_bytes), + format_field(f, 'tbr', '%4dk'), + f.get('protocol').replace('http_dash_segments', 'dash').replace("native", "n"), + '|', + format_field(f, 'vcodec', default='unknown').replace('none', ''), + format_field(f, 'vbr', '%4dk'), + format_field(f, 'acodec', default='unknown').replace('none', ''), + format_field(f, 'abr', '%3dk'), + format_field(f, 'asr', '%5dHz'), + self._format_note_table(f)] + for f in formats + if f.get('preference') is None or f['preference'] >= -1000] + header_line = ['ID', 'EXT', 'RESOLUTION', 'FPS', '|', ' FILESIZE', ' TBR', 'PROTO', + '|', 'VIDEO CODEC', ' VBR', 'AUDIO CODEC', ' ABR', ' ASR', 'NOTE'] + else: + table = [ + [ + format_field(f, 'format_id'), + format_field(f, 'ext'), + self.format_resolution(f), + self._format_note(f)] + for f in formats + if f.get('preference') is None or f['preference'] >= -1000] + header_line = ['format code', 'extension', 'resolution', 'note'] + if len(formats) > 1: table[-1][-1] += (' ' if table[-1][-1] else '') + '(best)' - - header_line = ['format code', 'extension', 'resolution', 'note'] self.to_screen( '[info] Available formats for %s:\n%s' % - (info_dict['id'], render_table(header_line, table))) + (info_dict['id'], render_table(header_line, table, new_format))) def list_thumbnails(self, info_dict): thumbnails = info_dict.get('thumbnails') diff --git a/youtube_dlc/__init__.py b/youtube_dlc/__init__.py index 7d72ab985..9be61b72d 100644 --- a/youtube_dlc/__init__.py +++ b/youtube_dlc/__init__.py @@ -348,6 +348,7 @@ def _real_main(argv=None): 'skip_download': opts.skip_download, 'format': opts.format, 'listformats': opts.listformats, + 'listformats_table': opts.listformats_table, 'outtmpl': outtmpl, 'autonumber_size': opts.autonumber_size, 'autonumber_start': opts.autonumber_start, diff --git a/youtube_dlc/options.py b/youtube_dlc/options.py index 9ad8a6ddd..1478fb79a 100644 --- a/youtube_dlc/options.py +++ b/youtube_dlc/options.py @@ -410,6 +410,10 @@ def parseOpts(overrideArguments=None): '-F', '--list-formats', action='store_true', dest='listformats', help='List all available formats of requested videos') + video_format.add_option( + '--list-formats-as-table', + action='store_true', dest='listformats_table', default=False, + help='Present the output of -F in a more tabular form') video_format.add_option( '--youtube-include-dash-manifest', action='store_true', dest='youtube_include_dash_manifest', default=True, diff --git a/youtube_dlc/utils.py b/youtube_dlc/utils.py index f5dc1bdaf..959af8e13 100644 --- a/youtube_dlc/utils.py +++ b/youtube_dlc/utils.py @@ -4311,11 +4311,13 @@ def determine_protocol(info_dict): return compat_urllib_parse_urlparse(url).scheme -def render_table(header_row, data): +def render_table(header_row, data, delim=False): """ 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' + if delim: + table = [header_row] + [['-' * ml for ml in max_lens]] + data + format_str = ' '.join('%-' + compat_str(ml) + 's' for ml in max_lens[:-1]) + ' %s' return '\n'.join(format_str % tuple(row) for row in table) @@ -5713,3 +5715,10 @@ def random_birthday(year_field, month_field, day_field): month_field: str(random_date.month), day_field: str(random_date.day), } + + +def format_field(obj, field, template='%s', ignore=(None,''), default='', func=None): + val = obj.get(field, default) + if func and val not in ignore: + val = func(val) + return template % val if val not in ignore else default \ No newline at end of file