from .update import detect_variant
from .utils import (
DEFAULT_OUTTMPL,
+ IDENTITY,
LINK_TEMPLATES,
NO_DEFAULT,
NUMBER_RE,
for type_, stream in self._out_files.items_ if type_ != 'console'
})
- if sys.version_info < (3, 6):
- self.report_warning(
- 'Python version %d.%d is not supported! Please update to Python 3.6 or above' % sys.version_info[:2])
+ MIN_SUPPORTED, MIN_RECOMMENDED = (3, 6), (3, 7)
+ current_version = sys.version_info[:2]
+ if current_version < MIN_RECOMMENDED:
+ msg = 'Support for Python version %d.%d has been deprecated and will break in future versions of yt-dlp'
+ if current_version < MIN_SUPPORTED:
+ msg = 'Python version %d.%d is no longer supported'
+ self.deprecation_warning(
+ f'{msg}! Please update to Python %d.%d or above' % (*current_version, *MIN_RECOMMENDED))
if self.params.get('allow_unplayable_formats'):
self.report_warning(
'Set the LC_ALL environment variable to fix this.')
self.params['restrictfilenames'] = True
- self.outtmpl_dict = self.parse_outtmpl()
+ self._parse_outtmpl()
# Creating format selector here allows us to catch syntax errors before the extraction
self.format_selector = (
def add_post_processor(self, pp, when='post_process'):
"""Add a PostProcessor object to the end of the chain."""
+ assert when in POSTPROCESS_WHEN, f'Invalid when={when}'
self._pps[when].append(pp)
pp.set_downloader(self)
self.report_warning(msg)
def parse_outtmpl(self):
- outtmpl_dict = self.params.get('outtmpl', {})
- if not isinstance(outtmpl_dict, dict):
- outtmpl_dict = {'default': outtmpl_dict}
- # Remove spaces in the default template
- if self.params.get('restrictfilenames'):
+ self.deprecation_warning('"YoutubeDL.parse_outtmpl" is deprecated and may be removed in a future version')
+ self._parse_outtmpl()
+ return self.params['outtmpl']
+
+ def _parse_outtmpl(self):
+ sanitize = IDENTITY
+ if self.params.get('restrictfilenames'): # Remove spaces in the default template
sanitize = lambda x: x.replace(' - ', ' ').replace(' ', '-')
- else:
- sanitize = lambda x: x
- outtmpl_dict.update({
- k: sanitize(v) for k, v in DEFAULT_OUTTMPL.items()
- if outtmpl_dict.get(k) is None})
- for _, val in outtmpl_dict.items():
- if isinstance(val, bytes):
- self.report_warning('Parameter outtmpl is bytes, but should be a unicode string')
- return outtmpl_dict
+
+ outtmpl = self.params.setdefault('outtmpl', {})
+ if not isinstance(outtmpl, dict):
+ self.params['outtmpl'] = outtmpl = {'default': outtmpl}
+ outtmpl.update({k: sanitize(v) for k, v in DEFAULT_OUTTMPL.items() if outtmpl.get(k) is None})
def get_output_path(self, dir_type='', filename=None):
paths = self.params.get('paths', {})
def _prepare_filename(self, info_dict, *, outtmpl=None, tmpl_type=None):
assert None in (outtmpl, tmpl_type), 'outtmpl and tmpl_type are mutually exclusive'
if outtmpl is None:
- outtmpl = self.outtmpl_dict.get(tmpl_type or 'default', self.outtmpl_dict['default'])
+ outtmpl = self.params['outtmpl'].get(tmpl_type or 'default', self.params['outtmpl']['default'])
try:
outtmpl = self._outtmpl_expandpath(outtmpl)
filename = self.evaluate_outtmpl(outtmpl, info_dict, True)
if not info:
return info
+ exempted_fields = {'_type', 'url', 'ie_key'}
+ if not ie_result.get('section_end') and ie_result.get('section_start') is None:
+ # For video clips, the id etc of the clip extractor should be used
+ exempted_fields |= {'id', 'extractor', 'extractor_key'}
+
new_result = info.copy()
- new_result.update(filter_dict(ie_result, lambda k, v: (
- v is not None and k not in {'_type', 'url', 'id', 'extractor', 'extractor_key', 'ie_key'})))
+ new_result.update(filter_dict(ie_result, lambda k, v: v is not None and k not in exempted_fields))
# Extracted info may not be a video result (i.e.
# info.get('_type', 'video') != video) but rather an url or
resolved_entries.append((playlist_index, entry))
# TODO: Add auto-generated fields
- if self._match_entry(entry, incomplete=True) is not None:
+ if not entry or self._match_entry(entry, incomplete=True) is not None:
continue
self.to_screen('[download] Downloading video %s of %s' % (
and (
not can_merge()
or info_dict.get('is_live') and not self.params.get('live_from_start')
- or self.outtmpl_dict['default'] == '-'))
+ or self.params['outtmpl']['default'] == '-'))
compat = (
prefer_best
or self.params.get('allow_multiple_audio_streams', False)
sanitize_string_field(info_dict, 'id')
sanitize_numeric_fields(info_dict)
+ if info_dict.get('section_end') and info_dict.get('section_start') is not None:
+ info_dict['duration'] = round(info_dict['section_end'] - info_dict['section_start'], 3)
if (info_dict.get('duration') or 0) <= 0 and info_dict.pop('duration', None):
self.report_warning('"duration" field is negative, there is an error in extractor')
for fmt, chapter in itertools.product(formats_to_download, requested_ranges or [{}]):
new_info = self._copy_infodict(info_dict)
new_info.update(fmt)
- if chapter:
+ offset, duration = info_dict.get('section_start') or 0, info_dict.get('duration') or float('inf')
+ if chapter or offset:
new_info.update({
- 'section_start': chapter.get('start_time'),
- 'section_end': chapter.get('end_time', 0),
+ 'section_start': offset + chapter.get('start_time', 0),
+ 'section_end': offset + min(chapter.get('end_time', 0), duration),
'section_title': chapter.get('title'),
'section_number': chapter.get('index'),
})
info_dict['ext'] = os.path.splitext(file)[1][1:]
return file
- success = True
- merger, fd = FFmpegMergerPP(self), None
+ fd, success = None, True
if info_dict.get('protocol') or info_dict.get('url'):
fd = get_suitable_downloader(info_dict, self.params, to_stdout=temp_filename == '-')
if fd is not FFmpegFD and (
info_dict.get('section_start') or info_dict.get('section_end')):
- msg = ('This format cannot be partially downloaded' if merger.available
+ msg = ('This format cannot be partially downloaded' if FFmpegFD.available()
else 'You have requested downloading the video partially, but ffmpeg is not installed')
self.report_error(f'{msg}. Aborting')
return
dl_filename = existing_video_file(full_filename, temp_filename)
info_dict['__real_download'] = False
+ merger = FFmpegMergerPP(self)
downloaded = []
if dl_filename is not None:
self.report_file_already_downloaded(dl_filename)
def download(self, url_list):
"""Download a given list of URLs."""
url_list = variadic(url_list) # Passing a single URL is a common mistake
- outtmpl = self.outtmpl_dict['default']
+ outtmpl = self.params['outtmpl']['default']
if (len(url_list) > 1
and outtmpl != '-'
and '%' not in outtmpl