mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-27 06:10:12 +01: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
26
README.md
26
README.md
|
@ -333,16 +333,16 @@ ## Filesystem Options:
|
||||||
comments and ignored
|
comments and ignored
|
||||||
-P, --paths TYPE:PATH The paths where the files should be
|
-P, --paths TYPE:PATH The paths where the files should be
|
||||||
downloaded. Specify the type of file and
|
downloaded. Specify the type of file and
|
||||||
the path separated by a colon ":"
|
the path separated by a colon ":". All the
|
||||||
(supported: description|annotation|subtitle
|
same types as --output are supported.
|
||||||
|infojson|thumbnail). Additionally, you can
|
Additionally, you can also provide "home"
|
||||||
also provide "home" and "temp" paths. All
|
and "temp" paths. All intermediary files
|
||||||
intermediary files are first downloaded to
|
are first downloaded to the temp path and
|
||||||
the temp path and then the final files are
|
then the final files are moved over to the
|
||||||
moved over to the home path after download
|
home path after download is finished. This
|
||||||
is finished. Note that this option is
|
option is ignored if --output is an
|
||||||
ignored if --output is an absolute path
|
absolute path
|
||||||
-o, --output TEMPLATE Output filename template, see "OUTPUT
|
-o, --output [TYPE:]TEMPLATE Output filename template, see "OUTPUT
|
||||||
TEMPLATE" for details
|
TEMPLATE" for details
|
||||||
--output-na-placeholder TEXT Placeholder value for unavailable meta
|
--output-na-placeholder TEXT Placeholder value for unavailable meta
|
||||||
fields in output filename template
|
fields in output filename template
|
||||||
|
@ -751,7 +751,9 @@ # OUTPUT TEMPLATE
|
||||||
|
|
||||||
**tl;dr:** [navigate me to examples](#output-template-examples).
|
**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:
|
The available fields are:
|
||||||
|
|
||||||
|
@ -860,7 +862,7 @@ #### Output template and Windows batch files
|
||||||
|
|
||||||
#### Output template examples
|
#### 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
|
```bash
|
||||||
$ youtube-dlc --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
|
$ youtube-dlc --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
date_from_str,
|
date_from_str,
|
||||||
DateRange,
|
DateRange,
|
||||||
DEFAULT_OUTTMPL,
|
DEFAULT_OUTTMPL,
|
||||||
|
OUTTMPL_TYPES,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
determine_protocol,
|
determine_protocol,
|
||||||
DOT_DESKTOP_LINK_TEMPLATE,
|
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.
|
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_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
|
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.
|
outtmpl_na_placeholder: Placeholder for unavailable meta fields.
|
||||||
restrictfilenames: Do not allow "&" and spaces in file names
|
restrictfilenames: Do not allow "&" and spaces in file names
|
||||||
trim_file_name: Limit length of filename (extension excluded)
|
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.')
|
'Set the LC_ALL environment variable to fix this.')
|
||||||
self.params['restrictfilenames'] = True
|
self.params['restrictfilenames'] = True
|
||||||
|
|
||||||
if isinstance(params.get('outtmpl'), bytes):
|
self.outtmpl_dict = self.parse_outtmpl()
|
||||||
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._setup_opener()
|
self._setup_opener()
|
||||||
|
|
||||||
|
@ -732,8 +731,21 @@ def report_file_delete(self, file_name):
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
self.to_screen('Deleting already existent file')
|
self.to_screen('Deleting already existent file')
|
||||||
|
|
||||||
def prepare_filename(self, info_dict, warn=False):
|
def parse_outtmpl(self):
|
||||||
"""Generate the output filename."""
|
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:
|
try:
|
||||||
template_dict = dict(info_dict)
|
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')
|
na = self.params.get('outtmpl_na_placeholder', 'NA')
|
||||||
template_dict = collections.defaultdict(lambda: na, template_dict)
|
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
|
# For fields playlist_index and autonumber convert all occurrences
|
||||||
# of %(field)s to %(field)0Nd for backward compatibility
|
# 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.
|
# title "Hello $PATH", we don't want `$PATH` to be expanded.
|
||||||
filename = expand_path(outtmpl).replace(sep, '') % template_dict
|
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
|
# https://github.com/blackjack4494/youtube-dlc/issues/85
|
||||||
trim_file_name = self.params.get('trim_file_name', False)
|
trim_file_name = self.params.get('trim_file_name', False)
|
||||||
if trim_file_name:
|
if trim_file_name:
|
||||||
|
@ -852,25 +868,28 @@ def prepare_filename(self, info_dict, warn=False):
|
||||||
filename = encodeFilename(filename, True).decode(preferredencoding())
|
filename = encodeFilename(filename, True).decode(preferredencoding())
|
||||||
filename = sanitize_path(filename)
|
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
|
return filename
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
|
self.report_error('Error in output template: ' + str(err) + ' (encoding: ' + repr(preferredencoding()) + ')')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def prepare_filepath(self, filename, dir_type=''):
|
def prepare_filename(self, info_dict, dir_type='', warn=False):
|
||||||
if filename == '-':
|
"""Generate the output filename."""
|
||||||
return filename
|
|
||||||
paths = self.params.get('paths', {})
|
paths = self.params.get('paths', {})
|
||||||
assert isinstance(paths, dict)
|
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())
|
homepath = expand_path(paths.get('home', '').strip())
|
||||||
assert isinstance(homepath, compat_str)
|
assert isinstance(homepath, compat_str)
|
||||||
subdir = expand_path(paths.get(dir_type, '').strip()) if dir_type else ''
|
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)
|
extract_flat = self.params.get('extract_flat', False)
|
||||||
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info)
|
if ((extract_flat == 'in_playlist' and 'playlist' in extra_info)
|
||||||
or extract_flat is True):
|
or extract_flat is True):
|
||||||
self.__forced_printings(
|
self.__forced_printings(ie_result, self.prepare_filename(ie_result), incomplete=True)
|
||||||
ie_result,
|
|
||||||
self.prepare_filepath(self.prepare_filename(ie_result)),
|
|
||||||
incomplete=True)
|
|
||||||
return ie_result
|
return ie_result
|
||||||
|
|
||||||
if result_type == 'video':
|
if result_type == 'video':
|
||||||
|
@ -1150,9 +1166,7 @@ def ensure_dir_exists(path):
|
||||||
return make_dir(path, self.report_error)
|
return make_dir(path, self.report_error)
|
||||||
|
|
||||||
if self.params.get('writeinfojson', False):
|
if self.params.get('writeinfojson', False):
|
||||||
infofn = replace_extension(
|
infofn = self.prepare_filename(ie_copy, 'pl_infojson')
|
||||||
self.prepare_filepath(self.prepare_filename(ie_copy), 'infojson'),
|
|
||||||
'info.json', ie_result.get('ext'))
|
|
||||||
if not ensure_dir_exists(encodeFilename(infofn)):
|
if not ensure_dir_exists(encodeFilename(infofn)):
|
||||||
return
|
return
|
||||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)):
|
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)
|
self.report_error('Cannot write playlist metadata to JSON file ' + infofn)
|
||||||
|
|
||||||
if self.params.get('writedescription', False):
|
if self.params.get('writedescription', False):
|
||||||
descfn = replace_extension(
|
descfn = self.prepare_filename(ie_copy, 'pl_description')
|
||||||
self.prepare_filepath(self.prepare_filename(ie_copy), 'description'),
|
|
||||||
'description', ie_result.get('ext'))
|
|
||||||
if not ensure_dir_exists(encodeFilename(descfn)):
|
if not ensure_dir_exists(encodeFilename(descfn)):
|
||||||
return
|
return
|
||||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
|
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
|
||||||
|
@ -1370,7 +1382,7 @@ def can_merge():
|
||||||
and (
|
and (
|
||||||
not can_merge()
|
not can_merge()
|
||||||
or info_dict.get('is_live', False)
|
or info_dict.get('is_live', False)
|
||||||
or self.params.get('outtmpl', DEFAULT_OUTTMPL) == '-'))
|
or self.outtmpl_dict['default'] == '-'))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
'best/bestvideo+bestaudio'
|
'best/bestvideo+bestaudio'
|
||||||
|
@ -2032,10 +2044,10 @@ def process_info(self, info_dict):
|
||||||
|
|
||||||
info_dict = self.pre_process(info_dict)
|
info_dict = self.pre_process(info_dict)
|
||||||
|
|
||||||
filename = self.prepare_filename(info_dict, warn=True)
|
info_dict['_filename'] = full_filename = self.prepare_filename(info_dict, warn=True)
|
||||||
info_dict['_filename'] = full_filename = self.prepare_filepath(filename)
|
temp_filename = self.prepare_filename(info_dict, 'temp')
|
||||||
temp_filename = self.prepare_filepath(filename, 'temp')
|
|
||||||
files_to_move = {}
|
files_to_move = {}
|
||||||
|
skip_dl = self.params.get('skip_download', False)
|
||||||
|
|
||||||
# Forced printings
|
# Forced printings
|
||||||
self.__forced_printings(info_dict, full_filename, incomplete=False)
|
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
|
# Do nothing else if in simulate mode
|
||||||
return
|
return
|
||||||
|
|
||||||
if filename is None:
|
if full_filename is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
def ensure_dir_exists(path):
|
def ensure_dir_exists(path):
|
||||||
|
@ -2059,9 +2071,7 @@ def ensure_dir_exists(path):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writedescription', False):
|
if self.params.get('writedescription', False):
|
||||||
descfn = replace_extension(
|
descfn = self.prepare_filename(info_dict, 'description')
|
||||||
self.prepare_filepath(filename, 'description'),
|
|
||||||
'description', info_dict.get('ext'))
|
|
||||||
if not ensure_dir_exists(encodeFilename(descfn)):
|
if not ensure_dir_exists(encodeFilename(descfn)):
|
||||||
return
|
return
|
||||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
|
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(descfn)):
|
||||||
|
@ -2078,9 +2088,7 @@ def ensure_dir_exists(path):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writeannotations', False):
|
if self.params.get('writeannotations', False):
|
||||||
annofn = replace_extension(
|
annofn = self.prepare_filename(info_dict, 'annotation')
|
||||||
self.prepare_filepath(filename, 'annotation'),
|
|
||||||
'annotations.xml', info_dict.get('ext'))
|
|
||||||
if not ensure_dir_exists(encodeFilename(annofn)):
|
if not 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)):
|
||||||
|
@ -2116,10 +2124,11 @@ def dl(name, info, subtitle=False):
|
||||||
# ie = self.get_info_extractor(info_dict['extractor_key'])
|
# ie = self.get_info_extractor(info_dict['extractor_key'])
|
||||||
for sub_lang, sub_info in subtitles.items():
|
for sub_lang, sub_info in subtitles.items():
|
||||||
sub_format = sub_info['ext']
|
sub_format = sub_info['ext']
|
||||||
sub_filename = subtitles_filename(temp_filename, sub_lang, sub_format, info_dict.get('ext'))
|
sub_fn = self.prepare_filename(info_dict, 'subtitle')
|
||||||
sub_filename_final = subtitles_filename(
|
sub_filename = subtitles_filename(
|
||||||
self.prepare_filepath(filename, 'subtitle'),
|
temp_filename if not skip_dl else sub_fn,
|
||||||
sub_lang, sub_format, info_dict.get('ext'))
|
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)):
|
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))
|
self.to_screen('[info] Video subtitle %s.%s is already present' % (sub_lang, sub_format))
|
||||||
files_to_move[sub_filename] = sub_filename_final
|
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)))
|
(sub_lang, error_to_compat_str(err)))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.params.get('skip_download', False):
|
if skip_dl:
|
||||||
if self.params.get('convertsubtitles', False):
|
if self.params.get('convertsubtitles', False):
|
||||||
# subconv = FFmpegSubtitlesConvertorPP(self, format=self.params.get('convertsubtitles'))
|
# 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 = (
|
filename_wo_ext = (
|
||||||
os.path.splitext(full_filename)[0]
|
os.path.splitext(full_filename)[0]
|
||||||
if filename_real_ext == info_dict['ext']
|
if filename_real_ext == info_dict['ext']
|
||||||
|
@ -2176,9 +2185,7 @@ def dl(name, info, subtitle=False):
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writeinfojson', False):
|
if self.params.get('writeinfojson', False):
|
||||||
infofn = replace_extension(
|
infofn = self.prepare_filename(info_dict, 'infojson')
|
||||||
self.prepare_filepath(filename, 'infojson'),
|
|
||||||
'info.json', info_dict.get('ext'))
|
|
||||||
if not ensure_dir_exists(encodeFilename(infofn)):
|
if not ensure_dir_exists(encodeFilename(infofn)):
|
||||||
return
|
return
|
||||||
if not self.params.get('overwrites', True) and os.path.exists(encodeFilename(infofn)):
|
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):
|
except (OSError, IOError):
|
||||||
self.report_error('Cannot write video metadata to JSON file ' + infofn)
|
self.report_error('Cannot write video metadata to JSON file ' + infofn)
|
||||||
return
|
return
|
||||||
info_dict['__infojson_filepath'] = infofn
|
info_dict['__infojson_filename'] = infofn
|
||||||
|
|
||||||
thumbdir = os.path.dirname(self.prepare_filepath(filename, 'thumbnail'))
|
thumbfn = self.prepare_filename(info_dict, 'thumbnail')
|
||||||
for thumbfn in self._write_thumbnails(info_dict, temp_filename):
|
thumb_fn_temp = temp_filename if not skip_dl else thumbfn
|
||||||
files_to_move[thumbfn] = os.path.join(thumbdir, os.path.basename(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
|
# Write internet shortcut files
|
||||||
url_link = webloc_link = desktop_link = False
|
url_link = webloc_link = desktop_link = False
|
||||||
|
@ -2247,7 +2257,7 @@ def _write_link_file(extension, template, newline, embed_filename):
|
||||||
|
|
||||||
# Download
|
# Download
|
||||||
must_record_download_archive = False
|
must_record_download_archive = False
|
||||||
if not self.params.get('skip_download', False):
|
if not skip_dl:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
def existing_file(*filepaths):
|
def existing_file(*filepaths):
|
||||||
|
@ -2327,7 +2337,7 @@ def correct_ext(filename):
|
||||||
new_info = dict(info_dict)
|
new_info = dict(info_dict)
|
||||||
new_info.update(f)
|
new_info.update(f)
|
||||||
fname = prepend_extension(
|
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'])
|
'f%s' % f['format_id'], new_info['ext'])
|
||||||
if not ensure_dir_exists(fname):
|
if not ensure_dir_exists(fname):
|
||||||
return
|
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))
|
self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
||||||
return
|
return
|
||||||
|
|
||||||
if success and filename != '-':
|
if success and full_filename != '-':
|
||||||
# Fixup content
|
# Fixup content
|
||||||
fixup_policy = self.params.get('fixup')
|
fixup_policy = self.params.get('fixup')
|
||||||
if fixup_policy is None:
|
if fixup_policy is None:
|
||||||
|
@ -2439,7 +2449,7 @@ def correct_ext(filename):
|
||||||
|
|
||||||
def download(self, url_list):
|
def download(self, url_list):
|
||||||
"""Download a given list of URLs."""
|
"""Download a given list of URLs."""
|
||||||
outtmpl = self.params.get('outtmpl', DEFAULT_OUTTMPL)
|
outtmpl = self.outtmpl_dict['default']
|
||||||
if (len(url_list) > 1
|
if (len(url_list) > 1
|
||||||
and outtmpl != '-'
|
and outtmpl != '-'
|
||||||
and '%' not in 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."""
|
"""Run all the postprocessors on the given file."""
|
||||||
info = dict(ie_info)
|
info = dict(ie_info)
|
||||||
info['filepath'] = filename
|
info['filepath'] = filename
|
||||||
|
info['__files_to_move'] = {}
|
||||||
|
|
||||||
for pp in ie_info.get('__postprocessors', []) + self._pps['normal']:
|
for pp in ie_info.get('__postprocessors', []) + self._pps['normal']:
|
||||||
files_to_move, info = self.run_pp(pp, info, files_to_move)
|
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']:
|
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):
|
def _make_archive_id(self, info_dict):
|
||||||
video_id = info_dict.get('id')
|
video_id = info_dict.get('id')
|
||||||
|
@ -2878,7 +2889,7 @@ def get_encoding(self):
|
||||||
encoding = preferredencoding()
|
encoding = preferredencoding()
|
||||||
return encoding
|
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):
|
if self.params.get('writethumbnail', False):
|
||||||
thumbnails = info_dict.get('thumbnails')
|
thumbnails = info_dict.get('thumbnails')
|
||||||
if thumbnails:
|
if thumbnails:
|
||||||
|
@ -2891,12 +2902,12 @@ def _write_thumbnails(self, info_dict, filename):
|
||||||
ret = []
|
ret = []
|
||||||
for t in thumbnails:
|
for t in thumbnails:
|
||||||
thumb_ext = determine_ext(t['url'], 'jpg')
|
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 ''
|
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)):
|
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' %
|
self.to_screen('[%s] %s: Thumbnail %sis already present' %
|
||||||
(info_dict['extractor'], info_dict['id'], thumb_display_id))
|
(info_dict['extractor'], info_dict['id'], thumb_display_id))
|
||||||
else:
|
else:
|
||||||
|
@ -2906,7 +2917,7 @@ def _write_thumbnails(self, info_dict, filename):
|
||||||
uf = self.urlopen(t['url'])
|
uf = self.urlopen(t['url'])
|
||||||
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(thumb_filename)
|
ret.append(suffix + thumb_ext)
|
||||||
self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
|
self.to_screen('[%s] %s: Writing thumbnail %sto: %s' %
|
||||||
(info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
|
(info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
|
||||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
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:
|
if opts.allsubtitles and not opts.writeautomaticsub:
|
||||||
opts.writesubtitles = True
|
opts.writesubtitles = True
|
||||||
|
|
||||||
outtmpl = ((opts.outtmpl is not None and opts.outtmpl)
|
outtmpl = opts.outtmpl
|
||||||
or (opts.format == '-1' and opts.usetitle and '%(title)s-%(id)s-%(format)s.%(ext)s')
|
if not outtmpl:
|
||||||
or (opts.format == '-1' and '%(id)s-%(format)s.%(ext)s')
|
outtmpl = {'default': (
|
||||||
or (opts.usetitle and opts.autonumber and '%(autonumber)s-%(title)s-%(id)s.%(ext)s')
|
'%(title)s-%(id)s-%(format)s.%(ext)s' if opts.format == '-1' and opts.usetitle
|
||||||
or (opts.usetitle and '%(title)s-%(id)s.%(ext)s')
|
else '%(id)s-%(format)s.%(ext)s' if opts.format == '-1'
|
||||||
or (opts.useid and '%(id)s.%(ext)s')
|
else '%(autonumber)s-%(title)s-%(id)s.%(ext)s' if opts.usetitle and opts.autonumber
|
||||||
or (opts.autonumber and '%(autonumber)s-%(id)s.%(ext)s')
|
else '%(title)s-%(id)s.%(ext)s' if opts.usetitle
|
||||||
or DEFAULT_OUTTMPL)
|
else '%(id)s.%(ext)s' if opts.useid
|
||||||
if not os.path.splitext(outtmpl)[1] and opts.extractaudio:
|
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'
|
parser.error('Cannot download a video and extract audio into the same'
|
||||||
' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
' file! Use "{0}.%(ext)s" instead of "{0}" as the output'
|
||||||
' template'.format(outtmpl))
|
' template'.format(outtmpl_default))
|
||||||
|
|
||||||
for f in opts.format_sort:
|
for f in opts.format_sort:
|
||||||
if re.match(InfoExtractor.FormatSort.regex, f) is None:
|
if re.match(InfoExtractor.FormatSort.regex, f) is None:
|
||||||
|
@ -413,7 +416,7 @@ def parse_retries(retries):
|
||||||
'playlistreverse': opts.playlist_reverse,
|
'playlistreverse': opts.playlist_reverse,
|
||||||
'playlistrandom': opts.playlist_random,
|
'playlistrandom': opts.playlist_random,
|
||||||
'noplaylist': opts.noplaylist,
|
'noplaylist': opts.noplaylist,
|
||||||
'logtostderr': opts.outtmpl == '-',
|
'logtostderr': outtmpl_default == '-',
|
||||||
'consoletitle': opts.consoletitle,
|
'consoletitle': opts.consoletitle,
|
||||||
'nopart': opts.nopart,
|
'nopart': opts.nopart,
|
||||||
'updatetime': opts.updatetime,
|
'updatetime': opts.updatetime,
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
from .utils import (
|
from .utils import (
|
||||||
expand_path,
|
expand_path,
|
||||||
get_executable_path,
|
get_executable_path,
|
||||||
|
OUTTMPL_TYPES,
|
||||||
preferredencoding,
|
preferredencoding,
|
||||||
write_string,
|
write_string,
|
||||||
)
|
)
|
||||||
|
@ -831,19 +832,23 @@ def _dict_from_multiple_values_options_callback(
|
||||||
metavar='TYPE:PATH', dest='paths', default={}, type='str',
|
metavar='TYPE:PATH', dest='paths', default={}, type='str',
|
||||||
action='callback', callback=_dict_from_multiple_values_options_callback,
|
action='callback', callback=_dict_from_multiple_values_options_callback,
|
||||||
callback_kwargs={
|
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()},
|
'process': lambda x: x.strip()},
|
||||||
help=(
|
help=(
|
||||||
'The paths where the files should be downloaded. '
|
'The paths where the files should be downloaded. '
|
||||||
'Specify the type of file and the path separated by a colon ":" '
|
'Specify the type of file and the path separated by a colon ":". '
|
||||||
'(supported: description|annotation|subtitle|infojson|thumbnail). '
|
'All the same types as --output are supported. '
|
||||||
'Additionally, you can also provide "home" and "temp" paths. '
|
'Additionally, you can also provide "home" and "temp" paths. '
|
||||||
'All intermediary files are first downloaded to the temp path and '
|
'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. '
|
'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(
|
filesystem.add_option(
|
||||||
'-o', '--output',
|
'-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')
|
help='Output filename template, see "OUTPUT TEMPLATE" for details')
|
||||||
filesystem.add_option(
|
filesystem.add_option(
|
||||||
'--output-na-placeholder',
|
'--output-na-placeholder',
|
||||||
|
|
|
@ -42,6 +42,7 @@ def __init__(self, downloader=None, already_have_thumbnail=False):
|
||||||
def run(self, info):
|
def run(self, info):
|
||||||
filename = info['filepath']
|
filename = info['filepath']
|
||||||
temp_filename = prepend_extension(filename, 'temp')
|
temp_filename = prepend_extension(filename, 'temp')
|
||||||
|
files_to_delete = []
|
||||||
|
|
||||||
if not info.get('thumbnails'):
|
if not info.get('thumbnails'):
|
||||||
self.to_screen('There aren\'t any thumbnails to embed')
|
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')
|
escaped_thumbnail_jpg_filename = replace_extension(escaped_thumbnail_filename, 'jpg')
|
||||||
self.to_screen('Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename)
|
self.to_screen('Converting thumbnail "%s" to JPEG' % escaped_thumbnail_filename)
|
||||||
self.run_ffmpeg(escaped_thumbnail_filename, escaped_thumbnail_jpg_filename, ['-bsf:v', 'mjpeg2jpeg'])
|
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')
|
thumbnail_jpg_filename = replace_extension(thumbnail_filename, 'jpg')
|
||||||
# Rename back to unescaped for further processing
|
# Rename back to unescaped for further processing
|
||||||
os.rename(encodeFilename(escaped_thumbnail_jpg_filename), encodeFilename(thumbnail_jpg_filename))
|
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:
|
if success and temp_filename != filename:
|
||||||
os.remove(encodeFilename(filename))
|
os.remove(encodeFilename(filename))
|
||||||
os.rename(encodeFilename(temp_filename), 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
|
return files_to_delete, info
|
||||||
|
|
|
@ -578,7 +578,7 @@ def ffmpeg_escape(text):
|
||||||
in_filenames.append(metadata_filename)
|
in_filenames.append(metadata_filename)
|
||||||
options.extend(['-map_metadata', '1'])
|
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(
|
old_stream, new_stream = self.get_stream_number(
|
||||||
filename, ('tags', 'mimetype'), 'application/json')
|
filename, ('tags', 'mimetype'), 'application/json')
|
||||||
if old_stream is not None:
|
if old_stream is not None:
|
||||||
|
@ -586,7 +586,7 @@ def ffmpeg_escape(text):
|
||||||
new_stream -= 1
|
new_stream -= 1
|
||||||
|
|
||||||
options.extend([
|
options.extend([
|
||||||
'-attach', info['__infojson_filepath'],
|
'-attach', info['__infojson_filename'],
|
||||||
'-metadata:s:%d' % new_stream, 'mimetype=application/json'
|
'-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']))
|
dl_path, dl_name = os.path.split(encodeFilename(info['filepath']))
|
||||||
finaldir = info.get('__finaldir', dl_path)
|
finaldir = info.get('__finaldir', dl_path)
|
||||||
finalpath = os.path.join(finaldir, dl_name)
|
finalpath = os.path.join(finaldir, dl_name)
|
||||||
|
self.files_to_move.update(info['__files_to_move'])
|
||||||
self.files_to_move[info['filepath']] = finalpath
|
self.files_to_move[info['filepath']] = finalpath
|
||||||
|
|
||||||
for oldfile, newfile in self.files_to_move.items():
|
for oldfile, newfile in self.files_to_move.items():
|
||||||
|
@ -39,7 +40,7 @@ def run(self, info):
|
||||||
if os.path.exists(encodeFilename(newfile)):
|
if os.path.exists(encodeFilename(newfile)):
|
||||||
if self.get_param('overwrites', True):
|
if self.get_param('overwrites', True):
|
||||||
self.report_warning('Replacing existing file "%s"' % newfile)
|
self.report_warning('Replacing existing file "%s"' % newfile)
|
||||||
os.path.remove(encodeFilename(newfile))
|
os.remove(encodeFilename(newfile))
|
||||||
else:
|
else:
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'Cannot move file "%s" out of temporary directory since "%s" already exists. '
|
'Cannot move file "%s" out of temporary directory since "%s" already exists. '
|
||||||
|
|
|
@ -4169,7 +4169,18 @@ def q(qid):
|
||||||
return q
|
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):
|
def limit_length(s, length):
|
||||||
|
|
Loading…
Reference in New Issue
Block a user