mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-07 20:30:41 -05:00
Multiple output templates for different file types
Syntax: -o common_template -o type:type_template Types supported: subtitle|thumbnail|description|annotation|infojson|pl_description|pl_infojson
This commit is contained in:
parent
ff88a05cff
commit
de6000d913
8 changed files with 136 additions and 98 deletions
26
README.md
26
README.md
|
@ -333,16 +333,16 @@ ## Filesystem Options:
|
|||
comments and ignored
|
||||
-P, --paths TYPE:PATH The paths where the files should be
|
||||
downloaded. Specify the type of file and
|
||||
the path separated by a colon ":"
|
||||
(supported: description|annotation|subtitle
|
||||
|infojson|thumbnail). Additionally, you can
|
||||
also provide "home" and "temp" paths. All
|
||||
intermediary files are first downloaded to
|
||||
the temp path and then the final files are
|
||||
moved over to the home path after download
|
||||
is finished. Note that this option is
|
||||
ignored if --output is an absolute path
|
||||
-o, --output TEMPLATE Output filename template, see "OUTPUT
|
||||
the path separated by a colon ":". All the
|
||||
same types as --output are supported.
|
||||
Additionally, you can also provide "home"
|
||||
and "temp" paths. All intermediary files
|
||||
are first downloaded to the temp path and
|
||||
then the final files are moved over to the
|
||||
home path after download is finished. This
|
||||
option is ignored if --output is an
|
||||
absolute path
|
||||
-o, --output [TYPE:]TEMPLATE Output filename template, see "OUTPUT
|
||||
TEMPLATE" for details
|
||||
--output-na-placeholder TEXT Placeholder value for unavailable meta
|
||||
fields in output filename template
|
||||
|
@ -751,7 +751,9 @@ # OUTPUT TEMPLATE
|
|||
|
||||
**tl;dr:** [navigate me to examples](#output-template-examples).
|
||||
|
||||
The basic usage of `-o` is not to set any template arguments when downloading a single file, like in `youtube-dlc -o funny_video.flv "https://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations. Additionally, date/time fields can be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it inside the parantheses seperated from the field name using a `>`. For example, `%(duration>%H-%M-%S)s`.
|
||||
The basic usage of `-o` is not to set any template arguments when downloading a single file, like in `youtube-dlc -o funny_video.flv "https://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences may be formatted according to [python string formatting operations](https://docs.python.org/2/library/stdtypes.html#string-formatting). For example, `%(NAME)s` or `%(NAME)05d`. To clarify, that is a percent symbol followed by a name in parentheses, followed by formatting operations. Date/time fields can also be formatted according to [strftime formatting](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) by specifying it inside the parantheses seperated from the field name using a `>`. For example, `%(duration>%H-%M-%S)s`.
|
||||
|
||||
Additionally, you can set different output templates for the various metadata files seperately from the general output template by specifying the type of file followed by the template seperated by a colon ":". The different filetypes supported are subtitle|thumbnail|description|annotation|infojson|pl_description|pl_infojson. 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.
|
||||
|
||||
The available fields are:
|
||||
|
||||
|
@ -860,7 +862,7 @@ #### Output template and Windows batch files
|
|||
|
||||
#### Output template examples
|
||||
|
||||
Note that on Windows you may need to use double quotes instead of single.
|
||||
Note that on Windows you need to use double quotes instead of single.
|
||||
|
||||
```bash
|
||||
$ youtube-dlc --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
date_from_str,
|
||||
DateRange,
|
||||
DEFAULT_OUTTMPL,
|
||||
OUTTMPL_TYPES,
|
||||
determine_ext,
|
||||
determine_protocol,
|
||||
DOT_DESKTOP_LINK_TEMPLATE,
|
||||
|
@ -182,7 +183,8 @@ class YoutubeDL(object):
|
|||
format_sort_force: Force the given format_sort. see "Sorting Formats" for more details.
|
||||
allow_multiple_video_streams: Allow multiple video streams to be merged into a single file
|
||||
allow_multiple_audio_streams: Allow multiple audio streams to be merged into a single file
|
||||
outtmpl: Template for output names.
|
||||
outtmpl: Dictionary of templates for output names. Allowed keys
|
||||
are 'default' and the keys of OUTTMPL_TYPES (in utils.py)
|
||||
outtmpl_na_placeholder: Placeholder for unavailable meta fields.
|
||||
restrictfilenames: Do not allow "&" and spaces in file names
|
||||
trim_file_name: Limit length of filename (extension excluded)
|
||||
|
@ -493,10 +495,7 @@ def check_deprecated(param, option, suggestion):
|
|||
'Set the LC_ALL environment variable to fix this.')
|
||||
self.params['restrictfilenames'] = True
|
||||
|
||||
if isinstance(params.get('outtmpl'), bytes):
|
||||
self.report_warning(
|
||||
'Parameter outtmpl is bytes, but should be a unicode string. '
|
||||
'Put from __future__ import unicode_literals at the top of your code file or consider switching to Python 3.x.')
|
||||
self.outtmpl_dict = self.parse_outtmpl()
|
||||
|
||||
self._setup_opener()
|
||||
|
||||
|
@ -732,8 +731,21 @@ def report_file_delete(self, file_name):
|
|||
except UnicodeEncodeError:
|
||||
self.to_screen('Deleting already existent file')
|
||||
|
||||
def prepare_filename(self, info_dict, warn=False):
|
||||
"""Generate the output filename."""
|
||||
def parse_outtmpl(self):
|
||||
outtmpl_dict = self.params.get('outtmpl', {})
|
||||
if not isinstance(outtmpl_dict, dict):
|
||||
outtmpl_dict = {'default': outtmpl_dict}
|
||||
outtmpl_dict.update({
|
||||
k: v for k, v in DEFAULT_OUTTMPL.items()
|
||||
if not outtmpl_dict.get(k)})
|
||||
for key, val in outtmpl_dict.items():
|
||||
if isinstance(val, bytes):
|
||||
self.report_warning(
|
||||
'Parameter outtmpl is bytes, but should be a unicode string. '
|
||||
'Put from __future__ import unicode_literals at the top of your code file or consider switching to Python 3.x.')
|
||||
return outtmpl_dict
|
||||
|
||||
def _prepare_filename(self, info_dict, tmpl_type='default'):
|
||||
try:
|
||||
template_dict = dict(info_dict)
|
||||
|
||||
|
@ -765,7 +777,8 @@ def prepare_filename(self, info_dict, warn=False):
|
|||
na = self.params.get('outtmpl_na_placeholder', 'NA')
|
||||
template_dict = collections.defaultdict(lambda: na, template_dict)
|
||||
|
||||
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||
outtmpl = self.outtmpl_dict.get(tmpl_type, self.outtmpl_dict['default'])
|
||||
force_ext = OUTTMPL_TYPES.get(tmpl_type)
|
||||
|
||||
# For fields playlist_index and autonumber convert all occurrences
|
||||
# of %(field)s to %(field)0Nd for backward compatibility
|
||||
|
@ -835,6 +848,9 @@ def prepare_filename(self, info_dict, warn=False):
|
|||
# title "Hello $PATH", we don't want `$PATH` to be expanded.
|
||||
filename = expand_path(outtmpl).replace(sep, '') % template_dict
|
||||
|
||||
if force_ext is not None:
|
||||
filename = replace_extension(filename, force_ext, template_dict.get('ext'))
|
||||
|
||||
# https://github.com/blackjack4494/youtube-dlc/issues/85
|
||||
trim_file_name = self.params.get('trim_file_name', False)
|
||||
if trim_file_name:
|
||||
|
@ -852,25 +868,28 @@ def prepare_filename(self, info_dict, warn=False):
|
|||
filename = encodeFilename(filename, True).decode(preferredencoding())
|
||||
filename = sanitize_path(filename)
|
||||
|
||||
if warn and not self.__prepare_filename_warned:
|
||||
if not self.params.get('paths'):
|
||||
pass
|
||||
elif filename == '-':
|
||||
self.report_warning('--paths is ignored when an outputting to stdout')
|
||||
elif os.path.isabs(filename):
|
||||
self.report_warning('--paths is ignored since an absolute path is given in output template')
|
||||
self.__prepare_filename_warned = True
|
||||
|
||||
return filename
|
||||
except ValueError as err:
|
||||
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
|
||||
return None
|
||||
|
||||
def prepare_filepath(self, filename, dir_type=''):
|
||||
if filename == '-':
|
||||
return filename
|
||||
def prepare_filename(self, info_dict, dir_type='', warn=False):
|
||||
"""Generate the output filename."""
|
||||
paths = self.params.get('paths', {})
|
||||
assert isinstance(paths, dict)
|
||||
filename = self._prepare_filename(info_dict, dir_type or 'default')
|
||||
|
||||
if warn and not self.__prepare_filename_warned:
|
||||
if not paths:
|
||||
pass
|
||||
elif filename == '-':
|
||||
self.report_warning('--paths is ignored when an outputting to stdout')
|
||||
elif os.path.isabs(filename):
|
||||
self.report_warning('--paths is ignored since an absolute path is given in output template')
|
||||
self.__prepare_filename_warned = True
|
||||
if filename == '-' or not filename:
|
||||
return filename
|
||||
|
||||
homepath = expand_path(paths.get('home', '').strip())
|
||||
assert isinstance(homepath, compat_str)
|
||||
subdir = expand_path(paths.get(dir_type, '').strip()) if dir_type else ''
|
||||
|
@ -1041,10 +1060,7 @@ def process_ie_result(self, ie_result, download=True, extra_info={}):
|
|||
extract_flat = self.params.get('extract_flat', False)
|
||||
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info)
|
||||
or extract_flat is True):
|
||||
self.__forced_printings(
|
||||
ie_result,
|
||||
self.prepare_filepath(self.prepare_filename(ie_result)),
|
||||
incomplete=True)
|
||||
self.__forced_printings(ie_result, self.prepare_filename(ie_result), incomplete=True)
|
||||
return ie_result
|
||||
|
||||
if result_type == 'video':
|
||||
|
@ -1150,9 +1166,7 @@ def ensure_dir_exists(path):
|
|||
return make_dir(path, self.report_error)
|
||||
|
||||
if self.params.get('writeinfojson', False):
|
||||
infofn = replace_extension(
|
||||
self.prepare_filepath(self.prepare_filename(ie_copy), 'infojson'),
|
||||
'info.json', ie_result.get('ext'))
|
||||
infofn = self.prepare_filename(ie_copy, 'pl_infojson')
|
||||
if not ensure_dir_exists(encodeFilename(infofn)):
|
||||
return
|
||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)):
|
||||
|
@ -1168,9 +1182,7 @@ def ensure_dir_exists(path):
|
|||
self.report_error('Cannot write playlist metadata to JSON file ' + infofn)
|
||||
|
||||
if self.params.get('writedescription', False):
|
||||
descfn = replace_extension(
|
||||
self.prepare_filepath(self.prepare_filename(ie_copy), 'description'),
|
||||
'description', ie_result.get('ext'))
|
||||
descfn = self.prepare_filename(ie_copy, 'pl_description')
|
||||
if not ensure_dir_exists(encodeFilename(descfn)):
|
||||
return
|
||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
|
||||
|
@ -1370,7 +1382,7 @@ def can_merge():
|
|||
and (
|
||||
not can_merge()
|
||||
or info_dict.get('is_live', False)
|
||||
or self.params.get('outtmpl', DEFAULT_OUTTMPL) == '-'))
|
||||
or self.outtmpl_dict['default'] == '-'))
|
||||
|
||||
return (
|
||||
'best/bestvideo+bestaudio'
|
||||
|
@ -2032,10 +2044,10 @@ def process_info(self, info_dict):
|
|||
|
||||
info_dict = self.pre_process(info_dict)
|
||||
|
||||
filename = self.prepare_filename(info_dict, warn=True)
|
||||
info_dict['_filename'] = full_filename = self.prepare_filepath(filename)
|
||||
temp_filename = self.prepare_filepath(filename, 'temp')
|
||||
info_dict['_filename'] = full_filename = self.prepare_filename(info_dict, warn=True)
|
||||
temp_filename = self.prepare_filename(info_dict, 'temp')
|
||||
files_to_move = {}
|
||||
skip_dl = self.params.get('skip_download', False)
|
||||
|
||||
# Forced printings
|
||||
self.__forced_printings(info_dict, full_filename, incomplete=False)
|
||||
|
@ -2047,7 +2059,7 @@ def process_info(self, info_dict):
|
|||
# Do nothing else if in simulate mode
|
||||
return
|
||||
|
||||
if filename is None:
|
||||
if full_filename is None:
|
||||
return
|
||||
|
||||
def ensure_dir_exists(path):
|
||||
|
@ -2059,9 +2071,7 @@ def ensure_dir_exists(path):
|
|||
return
|
||||
|
||||
if self.params.get('writedescription', False):
|
||||
descfn = replace_extension(
|
||||
self.prepare_filepath(filename, 'description'),
|
||||
'description', info_dict.get('ext'))
|
||||
descfn = self.prepare_filename(info_dict, 'description')
|
||||
if not ensure_dir_exists(encodeFilename(descfn)):
|
||||
return
|
||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
|
||||
|
@ -2078,9 +2088,7 @@ def ensure_dir_exists(path):
|
|||
return
|
||||
|
||||
if self.params.get('writeannotations', False):
|
||||
annofn = replace_extension(
|
||||
self.prepare_filepath(filename, 'annotation'),
|
||||
'annotations.xml', info_dict.get('ext'))
|
||||
annofn = self.prepare_filename(info_dict, 'annotation')
|
||||
if not ensure_dir_exists(encodeFilename(annofn)):
|
||||
return
|
||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(annofn)):
|
||||
|
@ -2116,10 +2124,11 @@ def dl(name, info, subtitle=False):
|
|||
# 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_filepath(filename, 'subtitle'),
|
||||
sub_fn = self.prepare_filename(info_dict, 'subtitle')
|
||||
sub_filename = subtitles_filename(
|
||||
temp_filename if not skip_dl else sub_fn,
|
||||
sub_lang, sub_format, info_dict.get('ext'))
|
||||
sub_filename_final = subtitles_filename(sub_fn, 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))
|
||||
files_to_move[sub_filename] = sub_filename_final
|
||||
|
@ -2153,10 +2162,10 @@ def dl(name, info, subtitle=False):
|
|||
(sub_lang, error_to_compat_str(err)))
|
||||
continue
|
||||
|
||||
if self.params.get('skip_download', False):
|
||||
if skip_dl:
|
||||
if self.params.get('convertsubtitles', False):
|
||||
# subconv = FFmpegSubtitlesConvertorPP(self, format=self.params.get('convertsubtitles'))
|
||||
filename_real_ext = os.path.splitext(filename)[1][1:]
|
||||
filename_real_ext = os.path.splitext(full_filename)[1][1:]
|
||||
filename_wo_ext = (
|
||||
os.path.splitext(full_filename)[0]
|
||||
if filename_real_ext == info_dict['ext']
|
||||
|
@ -2176,9 +2185,7 @@ def dl(name, info, subtitle=False):
|
|||
return
|
||||
|
||||
if self.params.get('writeinfojson', False):
|
||||
infofn = replace_extension(
|
||||
self.prepare_filepath(filename, 'infojson'),
|
||||
'info.json', info_dict.get('ext'))
|
||||
infofn = self.prepare_filename(info_dict, 'infojson')
|
||||
if not ensure_dir_exists(encodeFilename(infofn)):
|
||||
return
|
||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)):
|
||||
|
@ -2190,11 +2197,14 @@ def dl(name, info, subtitle=False):
|
|||
except (OSError, IOError):
|
||||
self.report_error('Cannot write video metadata to JSON file ' + infofn)
|
||||
return
|
||||
info_dict['__infojson_filepath'] = infofn
|
||||
info_dict['__infojson_filename'] = infofn
|
||||
|
||||
thumbdir = os.path.dirname(self.prepare_filepath(filename, 'thumbnail'))
|
||||
for thumbfn in self._write_thumbnails(info_dict, temp_filename):
|
||||
files_to_move[thumbfn] = os.path.join(thumbdir, os.path.basename(thumbfn))
|
||||
thumbfn = self.prepare_filename(info_dict, 'thumbnail')
|
||||
thumb_fn_temp = temp_filename if not skip_dl else thumbfn
|
||||
for thumb_ext in self._write_thumbnails(info_dict, thumb_fn_temp):
|
||||
thumb_filename_temp = replace_extension(thumb_fn_temp, thumb_ext, info_dict.get('ext'))
|
||||
thumb_filename = replace_extension(thumbfn, thumb_ext, info_dict.get('ext'))
|
||||
files_to_move[thumb_filename_temp] = info_dict['__thumbnail_filename'] = thumb_filename
|
||||
|
||||
# Write internet shortcut files
|
||||
url_link = webloc_link = desktop_link = False
|
||||
|
@ -2247,7 +2257,7 @@ def _write_link_file(extension, template, newline, embed_filename):
|
|||
|
||||
# Download
|
||||
must_record_download_archive = False
|
||||
if not self.params.get('skip_download', False):
|
||||
if not skip_dl:
|
||||
try:
|
||||
|
||||
def existing_file(*filepaths):
|
||||
|
@ -2327,7 +2337,7 @@ def correct_ext(filename):
|
|||
new_info = dict(info_dict)
|
||||
new_info.update(f)
|
||||
fname = prepend_extension(
|
||||
self.prepare_filepath(self.prepare_filename(new_info), 'temp'),
|
||||
self.prepare_filename(new_info, 'temp'),
|
||||
'f%s' % f['format_id'], new_info['ext'])
|
||||
if not ensure_dir_exists(fname):
|
||||
return
|
||||
|
@ -2357,7 +2367,7 @@ def correct_ext(filename):
|
|||
self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
||||
return
|
||||
|
||||
if success and filename != '-':
|
||||
if success and full_filename != '-':
|
||||
# Fixup content
|
||||
fixup_policy = self.params.get('fixup')
|
||||
if fixup_policy is None:
|
||||
|
@ -2439,7 +2449,7 @@ def correct_ext(filename):
|
|||
|
||||
def download(self, url_list):
|
||||
"""Download a given list of URLs."""
|
||||
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
||||
outtmpl = self.outtmpl_dict['default']
|
||||
if (len(url_list) > 1
|
||||
and outtmpl != '-'
|
||||
and '%' not in outtmpl
|
||||
|
@ -2522,12 +2532,13 @@ def post_process(self, filename, ie_info, files_to_move={}):
|
|||
"""Run all the postprocessors on the given file."""
|
||||
info = dict(ie_info)
|
||||
info['filepath'] = filename
|
||||
info['__files_to_move'] = {}
|
||||
|
||||
for pp in ie_info.get('__postprocessors', []) + self._pps['normal']:
|
||||
files_to_move, info = self.run_pp(pp, info, files_to_move)
|
||||
info = self.run_pp(MoveFilesAfterDownloadPP(self, files_to_move), info, files_to_move)[1]
|
||||
info = self.run_pp(MoveFilesAfterDownloadPP(self, files_to_move), info)[1]
|
||||
for pp in self._pps['aftermove']:
|
||||
files_to_move, info = self.run_pp(pp, info, {})
|
||||
info = self.run_pp(pp, info, {})[1]
|
||||
|
||||
def _make_archive_id(self, info_dict):
|
||||
video_id = info_dict.get('id')
|
||||
|
@ -2878,7 +2889,7 @@ def get_encoding(self):
|
|||
encoding = preferredencoding()
|
||||
return encoding
|
||||
|
||||
def _write_thumbnails(self, info_dict, filename):
|
||||
def _write_thumbnails(self, info_dict, filename): # return the extensions
|
||||
if self.params.get('writethumbnail', False):
|
||||
thumbnails = info_dict.get('thumbnails')
|
||||
if thumbnails:
|
||||
|
@ -2891,12 +2902,12 @@ def _write_thumbnails(self, info_dict, filename):
|
|||
ret = []
|
||||
for t in thumbnails:
|
||||
thumb_ext = determine_ext(t['url'], 'jpg')
|
||||
suffix = '_%s' % t['id'] if len(thumbnails) > 1 else ''
|
||||
suffix = '%s.' % t['id'] if len(thumbnails) > 1 else ''
|
||||
thumb_display_id = '%s ' % t['id'] if len(thumbnails) > 1 else ''
|
||||
t['filename'] = thumb_filename = replace_extension(filename + suffix, thumb_ext, info_dict.get('ext'))
|
||||
t['filename'] = 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)):
|
||||
ret.append(thumb_filename)
|
||||
ret.append(suffix + thumb_ext)
|
||||
self.to_screen('[%s] %s: Thumbnail %sis already present' %
|
||||
(info_dict['extractor'], info_dict['id'], thumb_display_id))
|
||||
else:
|
||||
|
@ -2906,7 +2917,7 @@ def _write_thumbnails(self, info_dict, filename):
|
|||
uf = self.urlopen(t['url'])
|
||||
with open(encodeFilename(thumb_filename), 'wb') as thumbf:
|
||||
shutil.copyfileobj(uf, thumbf)
|
||||
ret.append(thumb_filename)
|
||||
ret.append(suffix + thumb_ext)
|
||||
self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
|
||||
(info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
|
||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
||||
|
|
|
@ -237,18 +237,21 @@ def parse_retries(retries):
|
|||
if opts.allsubtitles and not opts.writeautomaticsub:
|
||||
opts.writesubtitles = True
|
||||
|
||||
outtmpl = ((opts.outtmpl is not None and opts.outtmpl)
|
||||
or (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s')
|
||||
or (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s')
|
||||
or (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s')
|
||||
or (opts.usetitle and '%(title)s-%(id)s.%(ext)s')
|
||||
or (opts.useid and '%(id)s.%(ext)s')
|
||||
or (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s')
|
||||
or DEFAULT_OUTTMPL)
|
||||
if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
|
||||
outtmpl = opts.outtmpl
|
||||
if not outtmpl:
|
||||
outtmpl = {'default': (
|
||||
'%(title)s-%(id)s-%(format)s.%(ext)s' if opts.format == '-1' and opts.usetitle
|
||||
else '%(id)s-%(format)s.%(ext)s' if opts.format == '-1'
|
||||
else '%(autonumber)s-%(title)s-%(id)s.%(ext)s' if opts.usetitle and opts.autonumber
|
||||
else '%(title)s-%(id)s.%(ext)s' if opts.usetitle
|
||||
else '%(id)s.%(ext)s' if opts.useid
|
||||
else '%(autonumber)s-%(id)s.%(ext)s' if opts.autonumber
|
||||
else None)}
|
||||
outtmpl_default = outtmpl.get('default')
|
||||
if outtmpl_default is not None and not os.path.splitext(outtmpl_default)[1] and opts.extractaudio:
|
||||
parser.error('Cannot download a video and extract audio into the same'
|
||||
' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
||||
' template'.format(outtmpl))
|
||||
' template'.format(outtmpl_default))
|
||||
|
||||
for f in opts.format_sort:
|
||||
if re.match(InfoExtractor.FormatSort.regex, f) is None:
|
||||
|
@ -413,7 +416,7 @@ def parse_retries(retries):
|
|||
'playlistreverse': opts.playlist_reverse,
|
||||
'playlistrandom': opts.playlist_random,
|
||||
'noplaylist': opts.noplaylist,
|
||||
'logtostderr': opts.outtmpl == '-',
|
||||
'logtostderr': outtmpl_default == '-',
|
||||
'consoletitle': opts.consoletitle,
|
||||
'nopart': opts.nopart,
|
||||
'updatetime': opts.updatetime,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
from .utils import (
|
||||
expand_path,
|
||||
get_executable_path,
|
||||
OUTTMPL_TYPES,
|
||||
preferredencoding,
|
||||
write_string,
|
||||
)
|
||||
|
@ -831,19 +832,23 @@ def _dict_from_multiple_values_options_callback(
|
|||
metavar='TYPE:PATH', dest='paths', default={}, type='str',
|
||||
action='callback', callback=_dict_from_multiple_values_options_callback,
|
||||
callback_kwargs={
|
||||
'allowed_keys': 'home|temp|config|description|annotation|subtitle|infojson|thumbnail',
|
||||
'allowed_keys': 'home|temp|%s' % '|'.join(OUTTMPL_TYPES.keys()),
|
||||
'process': lambda x: x.strip()},
|
||||
help=(
|
||||
'The paths where the files should be downloaded. '
|
||||
'Specify the type of file and the path separated by a colon ":" '
|
||||
'(supported: description|annotation|subtitle|infojson|thumbnail). '
|
||||
'Specify the type of file and the path separated by a colon ":". '
|
||||
'All the same types as --output are supported. '
|
||||
'Additionally, you can also provide "home" and "temp" paths. '
|
||||
'All intermediary files are first downloaded to the temp path and '
|
||||
'then the final files are moved over to the home path after download is finished. '
|
||||
'Note that this option is ignored if --output is an absolute path'))
|
||||
'This option is ignored if --output is an absolute path'))
|
||||
filesystem.add_option(
|
||||
'-o', '--output',
|
||||
dest='outtmpl', metavar='TEMPLATE',
|
||||
metavar='[TYPE:]TEMPLATE', dest='outtmpl', default={}, type='str',
|
||||
action='callback', callback=_dict_from_multiple_values_options_callback,
|
||||
callback_kwargs={
|
||||
'allowed_keys': '|'.join(OUTTMPL_TYPES.keys()),
|
||||
'default_key': 'default', 'process': lambda x: x.strip()},
|
||||
help='Output filename template, see "OUTPUT TEMPLATE" for details')
|
||||
filesystem.add_option(
|
||||
'--output-na-placeholder',
|
||||
|
|
|
@ -42,6 +42,7 @@ def __init__(self, downloader=None, already_have_thumbnail=False):
|
|||
def run(self, info):
|
||||
filename = info['filepath']
|
||||
temp_filename = prepend_extension(filename, 'temp')
|
||||
files_to_delete = []
|
||||
|
||||
if not info.get('thumbnails'):
|
||||
self.to_screen('There aren\'t any thumbnails to embed')
|
||||
|
@ -78,7 +79,7 @@ def is_webp(path):
|
|||
escaped_thumbnail_jpg_filename = replace_extension(escaped_thumbnail_filename, 'jpg')
|
||||
self.to_screen('Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename)
|
||||
self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_jpg_filename, ['-bsf:v', 'mjpeg2jpeg'])
|
||||
os.remove(encodeFilename(escaped_thumbnail_filename))
|
||||
files_to_delete.append(escaped_thumbnail_filename)
|
||||
thumbnail_jpg_filename = replace_extension(thumbnail_filename, 'jpg')
|
||||
# Rename back to unescaped for further processing
|
||||
os.rename(encodeFilename(escaped_thumbnail_jpg_filename), encodeFilename(thumbnail_jpg_filename))
|
||||
|
@ -183,5 +184,9 @@ def is_webp(path):
|
|||
if success and temp_filename != filename:
|
||||
os.remove(encodeFilename(filename))
|
||||
os.rename(encodeFilename(temp_filename), encodeFilename(filename))
|
||||
files_to_delete = [] if self._already_have_thumbnail else [thumbnail_filename]
|
||||
if self._already_have_thumbnail:
|
||||
info['__files_to_move'][thumbnail_filename] = replace_extension(
|
||||
info['__thumbnail_filename'], os.path.splitext(thumbnail_filename)[1][1:])
|
||||
else:
|
||||
files_to_delete.append(thumbnail_filename)
|
||||
return files_to_delete, info
|
||||
|
|
|
@ -578,7 +578,7 @@ def ffmpeg_escape(text):
|
|||
in_filenames.append(metadata_filename)
|
||||
options.extend(['-map_metadata', '1'])
|
||||
|
||||
if '__infojson_filepath' in info and info['ext'] in ('mkv', 'mka'):
|
||||
if '__infojson_filename' in info and info['ext'] in ('mkv', 'mka'):
|
||||
old_stream, new_stream = self.get_stream_number(
|
||||
filename, ('tags', 'mimetype'), 'application/json')
|
||||
if old_stream is not None:
|
||||
|
@ -586,7 +586,7 @@ def ffmpeg_escape(text):
|
|||
new_stream -= 1
|
||||
|
||||
options.extend([
|
||||
'-attach', info['__infojson_filepath'],
|
||||
'-attach', info['__infojson_filename'],
|
||||
'-metadata:s:%d' % new_stream, 'mimetype=application/json'
|
||||
])
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ def run(self, info):
|
|||
dl_path, dl_name = os.path.split(encodeFilename(info['filepath']))
|
||||
finaldir = info.get('__finaldir', dl_path)
|
||||
finalpath = os.path.join(finaldir, dl_name)
|
||||
self.files_to_move.update(info['__files_to_move'])
|
||||
self.files_to_move[info['filepath']] = finalpath
|
||||
|
||||
for oldfile, newfile in self.files_to_move.items():
|
||||
|
@ -39,7 +40,7 @@ def run(self, info):
|
|||
if os.path.exists(encodeFilename(newfile)):
|
||||
if self.get_param('overwrites', True):
|
||||
self.report_warning('Replacing existing file "%s"' % newfile)
|
||||
os.path.remove(encodeFilename(newfile))
|
||||
os.remove(encodeFilename(newfile))
|
||||
else:
|
||||
self.report_warning(
|
||||
'Cannot move file "%s" out of temporary directory since "%s" already exists. '
|
||||
|
|
|
@ -4169,7 +4169,18 @@ def q(qid):
|
|||
return q
|
||||
|
||||
|
||||
DEFAULT_OUTTMPL = '%(title)s [%(id)s].%(ext)s'
|
||||
DEFAULT_OUTTMPL = {
|
||||
'default': '%(title)s [%(id)s].%(ext)s',
|
||||
}
|
||||
OUTTMPL_TYPES = {
|
||||
'subtitle': None,
|
||||
'thumbnail': None,
|
||||
'description': 'description',
|
||||
'annotation': 'annotations.xml',
|
||||
'infojson': 'info.json',
|
||||
'pl_description': 'description',
|
||||
'pl_infojson': 'info.json',
|
||||
}
|
||||
|
||||
|
||||
def limit_length(s, length):
|
||||
|
|
Loading…
Reference in a new issue