Allow empty output template to skip a type of file

Closes #760, #1111
This commit is contained in:
pukkandan 2021-09-30 02:14:42 +05:30
parent 1f2a268bd3
commit 80c03fa98f
No known key found for this signature in database
GPG key ID: 0F00D95A001F4698
3 changed files with 150 additions and 134 deletions

View file

@ -971,7 +971,7 @@ # OUTPUT TEMPLATE
%(name[.keys][addition][>strf][,alternate][|default])[flags][width][.precision][length]type %(name[.keys][addition][>strf][,alternate][|default])[flags][width][.precision][length]type
``` ```
Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation` (deprecated), `infojson`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video. Additionally, you can set different output templates for the various metadata files separately from the general output template by specifying the type of file followed by the template separated by a colon `:`. The different file types supported are `subtitle`, `thumbnail`, `description`, `annotation` (deprecated), `infojson`, `pl_thumbnail`, `pl_description`, `pl_infojson`, `chapter`. For example, `-o '%(title)s.%(ext)s' -o 'thumbnail:%(title)s\%(title)s.%(ext)s'` will put the thumbnails in a folder with the same name as the video. If any of the templates (except default) is empty, that type of file will not be written. Eg: `--write-thumbnail -o "thumbnail:"` will write thumbnails only for playlists and not for video.
The available fields are: The available fields are:

View file

@ -859,7 +859,7 @@ def parse_outtmpl(self):
outtmpl_dict = {'default': outtmpl_dict} outtmpl_dict = {'default': outtmpl_dict}
outtmpl_dict.update({ outtmpl_dict.update({
k: v for k, v in DEFAULT_OUTTMPL.items() k: v for k, v in DEFAULT_OUTTMPL.items()
if not outtmpl_dict.get(k)}) if outtmpl_dict.get(k) is None})
for key, val in outtmpl_dict.items(): for key, val in outtmpl_dict.items():
if isinstance(val, bytes): if isinstance(val, bytes):
self.report_warning( self.report_warning(
@ -1084,7 +1084,7 @@ def _prepare_filename(self, info_dict, tmpl_type='default'):
filename = outtmpl % template_dict filename = outtmpl % template_dict
force_ext = OUTTMPL_TYPES.get(tmpl_type) force_ext = OUTTMPL_TYPES.get(tmpl_type)
if force_ext is not None: if filename and force_ext is not None:
filename = replace_extension(filename, force_ext, info_dict.get('ext')) filename = replace_extension(filename, force_ext, info_dict.get('ext'))
# https://github.com/blackjack4494/youtube-dlc/issues/85 # https://github.com/blackjack4494/youtube-dlc/issues/85
@ -1106,6 +1106,8 @@ def prepare_filename(self, info_dict, dir_type='', warn=False):
"""Generate the output filename.""" """Generate the output filename."""
filename = self._prepare_filename(info_dict, dir_type or 'default') filename = self._prepare_filename(info_dict, dir_type or 'default')
if not filename and dir_type not in ('', 'temp'):
return ''
if warn: if warn:
if not self.params.get('paths'): if not self.params.get('paths'):
@ -1517,38 +1519,14 @@ def get_entry(i):
} }
ie_copy.update(dict(ie_result)) ie_copy.update(dict(ie_result))
if self.params.get('writeinfojson', False): if self._write_info_json('playlist', ie_result,
infofn = self.prepare_filename(ie_copy, 'pl_infojson') self.prepare_filename(ie_copy, 'pl_infojson')) is None:
if not self._ensure_dir_exists(encodeFilename(infofn)): return
return if self._write_description('playlist', ie_result,
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)): self.prepare_filename(ie_copy, 'pl_description')) is None:
self.to_screen('[info] Playlist metadata is already present') return
else:
self.to_screen('[info] Writing playlist metadata as JSON to: ' + infofn)
try:
write_json_file(self.sanitize_info(ie_result, self.params.get('clean_infojson', True)), infofn)
except (OSError, IOError):
self.report_error('Cannot write playlist metadata to JSON file ' + infofn)
# TODO: This should be passed to ThumbnailsConvertor if necessary # TODO: This should be passed to ThumbnailsConvertor if necessary
self._write_thumbnails(ie_copy, self.prepare_filename(ie_copy, 'pl_thumbnail')) self._write_thumbnails('playlist', ie_copy, self.prepare_filename(ie_copy, 'pl_thumbnail'))
if self.params.get('writedescription', False):
descfn = self.prepare_filename(ie_copy, 'pl_description')
if not self._ensure_dir_exists(encodeFilename(descfn)):
return
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
self.to_screen('[info] Playlist description is already present')
elif ie_result.get('description') is None:
self.report_warning('There\'s no playlist description to write.')
else:
try:
self.to_screen('[info] Writing playlist description to: ' + descfn)
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
descfile.write(ie_result['description'])
except (OSError, IOError):
self.report_error('Cannot write playlist description file ' + descfn)
return
if self.params.get('playlistreverse', False): if self.params.get('playlistreverse', False):
entries = entries[::-1] entries = entries[::-1]
@ -2528,37 +2506,43 @@ def process_info(self, info_dict):
if self.params.get('simulate'): if self.params.get('simulate'):
if self.params.get('force_write_download_archive', False): if self.params.get('force_write_download_archive', False):
self.record_download_archive(info_dict) self.record_download_archive(info_dict)
# Do nothing else if in simulate mode # Do nothing else if in simulate mode
return return
if full_filename is None: if full_filename is None:
return return
if not self._ensure_dir_exists(encodeFilename(full_filename)): if not self._ensure_dir_exists(encodeFilename(full_filename)):
return return
if not self._ensure_dir_exists(encodeFilename(temp_filename)): if not self._ensure_dir_exists(encodeFilename(temp_filename)):
return return
if self.params.get('writedescription', False): if self._write_description('video', info_dict,
descfn = self.prepare_filename(info_dict, 'description') self.prepare_filename(info_dict, 'description')) is None:
if not self._ensure_dir_exists(encodeFilename(descfn)): return
return
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
self.to_screen('[info] Video description is already present')
elif info_dict.get('description') is None:
self.report_warning('There\'s no description to write.')
else:
try:
self.to_screen('[info] Writing video description to: ' + descfn)
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
descfile.write(info_dict['description'])
except (OSError, IOError):
self.report_error('Cannot write description file ' + descfn)
return
sub_files = self._write_subtitles(info_dict, temp_filename)
if sub_files is None:
return
files_to_move.update(dict(sub_files))
thumb_files = self._write_thumbnails(
'video', info_dict, temp_filename, self.prepare_filename(info_dict, 'thumbnail'))
if thumb_files is None:
return
files_to_move.update(dict(thumb_files))
infofn = self.prepare_filename(info_dict, 'infojson')
_infojson_written = self._write_info_json('video', info_dict, infofn)
if _infojson_written:
info_dict['__infojson_filename'] = infofn
elif _infojson_written is None:
return
# Note: Annotations are deprecated
annofn = None
if self.params.get('writeannotations', False): if self.params.get('writeannotations', False):
annofn = self.prepare_filename(info_dict, 'annotation') annofn = self.prepare_filename(info_dict, 'annotation')
if annofn:
if not self._ensure_dir_exists(encodeFilename(annofn)): if not self._ensure_dir_exists(encodeFilename(annofn)):
return return
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(annofn)): if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(annofn)):
@ -2576,69 +2560,6 @@ def process_info(self, info_dict):
self.report_error('Cannot write annotations file: ' + annofn) self.report_error('Cannot write annotations file: ' + annofn)
return return
subtitles_are_requested = any([self.params.get('writesubtitles', False),
self.params.get('writeautomaticsub')])
if subtitles_are_requested and info_dict.get('requested_subtitles'):
# subtitles download errors are already managed as troubles in relevant IE
# that way it will silently go on when used with unsupporting IE
subtitles = info_dict['requested_subtitles']
# ie = self.get_info_extractor(info_dict['extractor_key'])
for sub_lang, sub_info in subtitles.items():
sub_format = sub_info['ext']
sub_filename = subtitles_filename(temp_filename, sub_lang, sub_format, info_dict.get('ext'))
sub_filename_final = subtitles_filename(
self.prepare_filename(info_dict, 'subtitle'), sub_lang, sub_format, info_dict.get('ext'))
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(sub_filename)):
self.to_screen('[info] Video subtitle %s.%s is already present' % (sub_lang, sub_format))
sub_info['filepath'] = sub_filename
files_to_move[sub_filename] = sub_filename_final
else:
self.to_screen('[info] Writing video subtitles to: ' + sub_filename)
if sub_info.get('data') is not None:
try:
# Use newline='' to prevent conversion of newline characters
# See https://github.com/ytdl-org/youtube-dl/issues/10268
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8', newline='') as subfile:
subfile.write(sub_info['data'])
sub_info['filepath'] = sub_filename
files_to_move[sub_filename] = sub_filename_final
except (OSError, IOError):
self.report_error('Cannot write subtitles file ' + sub_filename)
return
else:
try:
sub_copy = sub_info.copy()
sub_copy.setdefault('http_headers', info_dict.get('http_headers'))
self.dl(sub_filename, sub_copy, subtitle=True)
sub_info['filepath'] = sub_filename
files_to_move[sub_filename] = sub_filename_final
except (ExtractorError, IOError, OSError, ValueError) + network_exceptions as err:
self.report_warning('Unable to download subtitle for "%s": %s' %
(sub_lang, error_to_compat_str(err)))
continue
if self.params.get('writeinfojson', False):
infofn = self.prepare_filename(info_dict, 'infojson')
if not self._ensure_dir_exists(encodeFilename(infofn)):
return
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)):
self.to_screen('[info] Video metadata is already present')
else:
self.to_screen('[info] Writing video metadata as JSON to: ' + infofn)
try:
write_json_file(self.sanitize_info(info_dict, self.params.get('clean_infojson', True)), infofn)
except (OSError, IOError):
self.report_error('Cannot write video metadata to JSON file ' + infofn)
return
info_dict['__infojson_filename'] = infofn
for thumb_ext in self._write_thumbnails(info_dict, temp_filename):
thumb_filename_temp = replace_extension(temp_filename, thumb_ext, info_dict.get('ext'))
thumb_filename = replace_extension(
self.prepare_filename(info_dict, 'thumbnail'), thumb_ext, info_dict.get('ext'))
files_to_move[thumb_filename_temp] = thumb_filename
# Write internet shortcut files # Write internet shortcut files
url_link = webloc_link = desktop_link = False url_link = webloc_link = desktop_link = False
if self.params.get('writelink', False): if self.params.get('writelink', False):
@ -3416,39 +3337,133 @@ def get_encoding(self):
encoding = preferredencoding() encoding = preferredencoding()
return encoding return encoding
def _write_thumbnails(self, info_dict, filename): # return the extensions def _write_info_json(self, label, ie_result, infofn):
''' Write infojson and returns True = written, False = skip, None = error '''
if not self.params.get('writeinfojson'):
return False
elif not infofn:
self.write_debug(f'Skipping writing {label} infojson')
return False
elif not self._ensure_dir_exists(infofn):
return None
elif not self.params.get('overwrites', True) and os.path.exists(infofn):
self.to_screen(f'[info] {label.title()} metadata is already present')
else:
self.to_screen(f'[info] Writing {label} metadata as JSON to: {infofn}')
try:
write_json_file(self.sanitize_info(ie_result, self.params.get('clean_infojson', True)), infofn)
except (OSError, IOError):
self.report_error(f'Cannot write {label} metadata to JSON file {infofn}')
return None
return True
def _write_description(self, label, ie_result, descfn):
''' Write description and returns True = written, False = skip, None = error '''
if not self.params.get('writedescription'):
return False
elif not descfn:
self.write_debug(f'Skipping writing {label} description')
return False
elif not self._ensure_dir_exists(descfn):
return None
elif not self.params.get('overwrites', True) and os.path.exists(descfn):
self.to_screen(f'[info] {label.title()} description is already present')
elif ie_result.get('description') is None:
self.report_warning(f'There\'s no {label} description to write')
return False
else:
try:
self.to_screen(f'[info] Writing {label} description to: {descfn}')
with io.open(encodeFilename(descfn), 'w', encoding='utf-8') as descfile:
descfile.write(ie_result['description'])
except (OSError, IOError):
self.report_error(f'Cannot write {label} description file {descfn}')
return None
return True
def _write_subtitles(self, info_dict, filename):
''' Write subtitles to file and return list of (sub_filename, final_sub_filename); or None if error'''
ret = []
subtitles = info_dict.get('requested_subtitles')
if not subtitles or not (self.params.get('writesubtitles') or self.params.get('writeautomaticsub')):
# subtitles download errors are already managed as troubles in relevant IE
# that way it will silently go on when used with unsupporting IE
return ret
sub_filename_base = self.prepare_filename(info_dict, 'subtitle')
if not sub_filename_base:
self.to_screen('[info] Skipping writing video subtitles')
return ret
for sub_lang, sub_info in subtitles.items():
sub_format = sub_info['ext']
sub_filename = subtitles_filename(filename, sub_lang, sub_format, info_dict.get('ext'))
sub_filename_final = subtitles_filename(sub_filename_base, sub_lang, sub_format, info_dict.get('ext'))
if not self.params.get('overwrites', True) and os.path.exists(sub_filename):
self.to_screen(f'[info] Video subtitle {sub_lang}.{sub_format} is already present')
sub_info['filepath'] = sub_filename
ret.append((sub_filename, sub_filename_final))
continue
self.to_screen(f'[info] Writing video subtitles to: {sub_filename}')
if sub_info.get('data') is not None:
try:
# Use newline='' to prevent conversion of newline characters
# See https://github.com/ytdl-org/youtube-dl/issues/10268
with io.open(sub_filename, 'w', encoding='utf-8', newline='') as subfile:
subfile.write(sub_info['data'])
sub_info['filepath'] = sub_filename
ret.append((sub_filename, sub_filename_final))
continue
except (OSError, IOError):
self.report_error(f'Cannot write video subtitles file {sub_filename}')
return None
try:
sub_copy = sub_info.copy()
sub_copy.setdefault('http_headers', info_dict.get('http_headers'))
self.dl(sub_filename, sub_copy, subtitle=True)
sub_info['filepath'] = sub_filename
ret.append((sub_filename, sub_filename_final))
except (ExtractorError, IOError, OSError, ValueError) + network_exceptions as err:
self.report_warning(f'Unable to download video subtitles for {sub_lang!r}: {err}')
continue
return ret
def _write_thumbnails(self, label, info_dict, filename, thumb_filename_base=None):
''' Write thumbnails to file and return list of (thumb_filename, final_thumb_filename) '''
write_all = self.params.get('write_all_thumbnails', False) write_all = self.params.get('write_all_thumbnails', False)
thumbnails = [] thumbnails, ret = [], []
if write_all or self.params.get('writethumbnail', False): if write_all or self.params.get('writethumbnail', False):
thumbnails = info_dict.get('thumbnails') or [] thumbnails = info_dict.get('thumbnails') or []
multiple = write_all and len(thumbnails) > 1 multiple = write_all and len(thumbnails) > 1
ret = [] if thumb_filename_base is None:
for t in thumbnails[::-1]: thumb_filename_base = filename
thumb_ext = determine_ext(t['url'], 'jpg') if thumbnails and not thumb_filename_base:
suffix = '%s.' % t['id'] if multiple else '' self.write_debug(f'Skipping writing {label} thumbnail')
thumb_display_id = '%s ' % t['id'] if multiple else '' return ret
thumb_filename = replace_extension(filename, suffix + thumb_ext, info_dict.get('ext'))
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(thumb_filename)): for t in thumbnails[::-1]:
ret.append(suffix + thumb_ext) thumb_ext = (f'{t["id"]}.' if multiple else '') + determine_ext(t['url'], 'jpg')
thumb_display_id = f'{label} thumbnail' + (f' {t["id"]}' if multiple else '')
thumb_filename = replace_extension(filename, thumb_ext, info_dict.get('ext'))
thumb_filename_final = replace_extension(thumb_filename_base, thumb_ext, info_dict.get('ext'))
if not self.params.get('overwrites', True) and os.path.exists(thumb_filename):
ret.append((thumb_filename, thumb_filename_final))
t['filepath'] = thumb_filename t['filepath'] = thumb_filename
self.to_screen('[%s] %s: Thumbnail %sis already present' % self.to_screen(f'[info] {thumb_display_id.title()} is already present')
(info_dict['extractor'], info_dict['id'], thumb_display_id))
else: else:
self.to_screen('[%s] %s: Downloading thumbnail %s ...' % self.to_screen(f'[info] Downloading {thumb_display_id} ...')
(info_dict['extractor'], info_dict['id'], thumb_display_id))
try: try:
uf = self.urlopen(t['url']) uf = self.urlopen(t['url'])
self.to_screen(f'[info] Writing {thumb_display_id} to: {thumb_filename}')
with open(encodeFilename(thumb_filename), 'wb') as thumbf: with open(encodeFilename(thumb_filename), 'wb') as thumbf:
shutil.copyfileobj(uf, thumbf) shutil.copyfileobj(uf, thumbf)
ret.append(suffix + thumb_ext) ret.append((thumb_filename, thumb_filename_final))
self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
(info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
t['filepath'] = thumb_filename t['filepath'] = thumb_filename
except network_exceptions as err: except network_exceptions as err:
self.report_warning('Unable to download thumbnail "%s": %s' % self.report_warning(f'Unable to download {thumb_display_id}: {err}')
(t['url'], error_to_compat_str(err)))
if ret and not write_all: if ret and not write_all:
break break
return ret return ret

View file

@ -535,6 +535,7 @@ def report_conflict(arg1, arg2):
}) })
if not already_have_thumbnail: if not already_have_thumbnail:
opts.writethumbnail = True opts.writethumbnail = True
opts.outtmpl['pl_thumbnail'] = ''
if opts.split_chapters: if opts.split_chapters:
postprocessors.append({ postprocessors.append({
'key': 'FFmpegSplitChapters', 'key': 'FFmpegSplitChapters',