From ca30f449a187addcdb99f4c39333e7a292756597 Mon Sep 17 00:00:00 2001 From: pukkandan Date: Sun, 2 Jan 2022 16:22:00 +0530 Subject: [PATCH] Add `--print playlist:` to print fields per playlist --- README.md | 10 ++++++---- yt_dlp/YoutubeDL.py | 31 ++++++++++++++++++++++--------- yt_dlp/__init__.py | 11 +++++++---- yt_dlp/options.py | 22 +++++++++++++++------- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 0ebd0594a..e032ea6e6 100644 --- a/README.md +++ b/README.md @@ -662,10 +662,12 @@ You can also fork the project on github and run your fork's [build workflow](.gi formats are found (default) --skip-download Do not download the video but write all related files (Alias: --no-download) - -O, --print TEMPLATE Quiet, but print the given fields for each - video. Simulate unless --no-simulate is - used. Either a field name or same syntax as - the output template can be used + -O, --print [WHEN:]TEMPLATE Field name or output template to print to + screen per video. Prefix the template with + "playlist:" to print it once per playlist + instead. Implies --quiet and --simulate + (unless --no-simulate is used). This option + can be used multiple times -j, --dump-json Quiet, but print JSON information for each video. Simulate unless --no-simulate is used. See "OUTPUT TEMPLATE" for a diff --git a/yt_dlp/YoutubeDL.py b/yt_dlp/YoutubeDL.py index 9ab8d7f08..faea85485 100644 --- a/yt_dlp/YoutubeDL.py +++ b/yt_dlp/YoutubeDL.py @@ -199,7 +199,9 @@ class YoutubeDL(object): verbose: Print additional info to stdout. quiet: Do not print messages to stdout. no_warnings: Do not print out anything for warnings. - forceprint: A list of templates to force print + forceprint: A dict with keys video/playlist mapped to + a list of templates to force print to stdout + For compatibility, a single list is also accepted forceurl: Force printing final URL. (Deprecated) forcetitle: Force printing title. (Deprecated) forceid: Force printing ID. (Deprecated) @@ -585,6 +587,11 @@ class YoutubeDL(object): else: self.params['nooverwrites'] = not self.params['overwrites'] + # Compatibility with older syntax + params.setdefault('forceprint', {}) + if not isinstance(params['forceprint'], dict): + params['forceprint'] = {'video': params['forceprint']} + if params.get('bidi_workaround', False): try: import pty @@ -1755,6 +1762,9 @@ class YoutubeDL(object): 'updated playlist', ie_result, self.prepare_filename(ie_copy, 'pl_infojson'), overwrite=True) is None: return + + for tmpl in self.params['forceprint'].get('playlist', []): + self._forceprint(tmpl, ie_result) self.to_screen('[download] Finished downloading playlist: %s' % playlist) return ie_result @@ -2626,6 +2636,14 @@ class YoutubeDL(object): subs[lang] = f return subs + def _forceprint(self, tmpl, info_dict): + mobj = re.match(r'\w+(=?)$', tmpl) + if mobj and mobj.group(1): + tmpl = f'{tmpl[:-1]} = %({tmpl[:-1]})s' + elif mobj: + tmpl = '%({})s'.format(tmpl) + self.to_stdout(self.evaluate_outtmpl(tmpl, info_dict)) + def __forced_printings(self, info_dict, filename, incomplete): def print_mandatory(field, actual_field=None): if actual_field is None: @@ -2648,15 +2666,10 @@ class YoutubeDL(object): elif 'url' in info_dict: info_dict['urls'] = info_dict['url'] + info_dict.get('play_path', '') - if self.params.get('forceprint') or self.params.get('forcejson'): + if self.params['forceprint'].get('video') or self.params.get('forcejson'): self.post_extract(info_dict) - for tmpl in self.params.get('forceprint', []): - mobj = re.match(r'\w+(=?)$', tmpl) - if mobj and mobj.group(1): - tmpl = f'{tmpl[:-1]} = %({tmpl[:-1]})s' - elif mobj: - tmpl = '%({})s'.format(tmpl) - self.to_stdout(self.evaluate_outtmpl(tmpl, info_dict)) + for tmpl in self.params['forceprint'].get('video', []): + self._forceprint(tmpl, info_dict) print_mandatory('title') print_mandatory('id') diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py index a03961c1b..af7a4e195 100644 --- a/yt_dlp/__init__.py +++ b/yt_dlp/__init__.py @@ -351,9 +351,9 @@ def _real_main(argv=None): for k, tmpl in opts.outtmpl.items(): validate_outtmpl(tmpl, f'{k} output template') - opts.forceprint = opts.forceprint or [] - for tmpl in opts.forceprint or []: - validate_outtmpl(tmpl, 'print template') + for type_, tmpl_list in opts.forceprint.items(): + for tmpl in tmpl_list: + validate_outtmpl(tmpl, f'{type_} print template') validate_outtmpl(opts.sponsorblock_chapter_title, 'SponsorBlock chapter title') for k, tmpl in opts.progress_template.items(): k = f'{k[:-6]} console title' if '-title' in k else f'{k} progress' @@ -395,7 +395,10 @@ def _real_main(argv=None): opts.parse_metadata.append('title:%s' % opts.metafromtitle) opts.parse_metadata = list(itertools.chain(*map(metadataparser_actions, opts.parse_metadata))) - any_getting = opts.forceprint or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration or opts.dumpjson or opts.dump_single_json + any_getting = (any(opts.forceprint.values()) or opts.dumpjson or opts.dump_single_json + or opts.geturl or opts.gettitle or opts.getid or opts.getthumbnail + or opts.getdescription or opts.getfilename or opts.getformat or opts.getduration) + any_printing = opts.print_json download_archive_fn = expand_path(opts.download_archive) if opts.download_archive is not None else opts.download_archive diff --git a/yt_dlp/options.py b/yt_dlp/options.py index 83531497c..d48cd1457 100644 --- a/yt_dlp/options.py +++ b/yt_dlp/options.py @@ -152,9 +152,9 @@ def parseOpts(overrideArguments=None): def _dict_from_options_callback( option, opt_str, value, parser, allowed_keys=r'[\w-]+', delimiter=':', default_key=None, process=None, multiple_keys=True, - process_key=str.lower): + process_key=str.lower, append=False): - out_dict = getattr(parser.values, option.dest) + out_dict = dict(getattr(parser.values, option.dest)) if multiple_keys: allowed_keys = r'(%s)(,(%s))*' % (allowed_keys, allowed_keys) mobj = re.match(r'(?i)(?P%s)%s(?P.*)$' % (allowed_keys, delimiter), value) @@ -171,7 +171,8 @@ def parseOpts(overrideArguments=None): except Exception as err: raise optparse.OptionValueError(f'wrong {opt_str} formatting; {err}') for key in keys: - out_dict[key] = val + out_dict[key] = out_dict.get(key, []) + [val] if append else val + setattr(parser.values, option.dest, out_dict) # No need to wrap help messages if we're on a wide console columns = compat_get_terminal_size().columns @@ -882,10 +883,17 @@ def parseOpts(overrideArguments=None): help='Do not download the video but write all related files (Alias: --no-download)') verbosity.add_option( '-O', '--print', - metavar='TEMPLATE', action='append', dest='forceprint', - help=( - 'Quiet, but print the given fields for each video. Simulate unless --no-simulate is used. ' - 'Either a field name or same syntax as the output template can be used')) + metavar='[WHEN:]TEMPLATE', dest='forceprint', default={}, type='str', + action='callback', callback=_dict_from_options_callback, + callback_kwargs={ + 'allowed_keys': 'video|playlist', + 'default_key': 'video', + 'multiple_keys': False, + 'append': True, + }, help=( + 'Field name or output template to print to screen per video. ' + 'Prefix the template with "playlist:" to print it once per playlist instead. ' + 'Implies --quiet and --simulate (unless --no-simulate is used). This option can be used multiple times')) verbosity.add_option( '-g', '--get-url', action='store_true', dest='geturl', default=False,