mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-03-09 12:50:23 -05:00
Implement nested --playlist-items
This commit is contained in:
parent
0b6b7742c2
commit
ec7250f145
3 changed files with 74 additions and 12 deletions
|
@ -635,6 +635,7 @@ def __init__(self, params=None, auto_init=True):
|
|||
self._num_downloads = 0
|
||||
self._num_videos = 0
|
||||
self._playlist_level = 0
|
||||
self._nested_playlist_index = ()
|
||||
self._playlist_urls = set()
|
||||
self.cache = Cache(self)
|
||||
self.__header_cookies = []
|
||||
|
@ -1987,7 +1988,7 @@ def __process_playlist(self, ie_result, download):
|
|||
self.to_screen(f'[download] Downloading {ie_result["_type"]}: {title}')
|
||||
|
||||
all_entries = PlaylistEntries(self, ie_result)
|
||||
entries = orderedSet(all_entries.get_requested_items(), lazy=True)
|
||||
entries = orderedSet(all_entries.get_requested_items(self._nested_playlist_index), lazy=True)
|
||||
|
||||
lazy = self.params.get('lazy_playlist')
|
||||
if lazy:
|
||||
|
@ -2064,10 +2065,13 @@ def __process_playlist(self, ie_result, download):
|
|||
f'[download] Downloading item {self._format_screen(i + 1, self.Styles.ID)} '
|
||||
f'of {self._format_screen(n_entries, self.Styles.EMPHASIS)}')
|
||||
|
||||
self._nested_playlist_index = (*self._nested_playlist_index, playlist_index)
|
||||
entry_result = self.__process_iterable_entry(entry, download, collections.ChainMap({
|
||||
'playlist_index': playlist_index,
|
||||
'playlist_autonumber': i + 1,
|
||||
}, extra))
|
||||
self._nested_playlist_index = self._nested_playlist_index[:-1]
|
||||
|
||||
if not entry_result:
|
||||
failures += 1
|
||||
if failures >= max_failures:
|
||||
|
|
|
@ -431,7 +431,7 @@ def metadataparser_actions(f):
|
|||
# Other options
|
||||
if opts.playlist_items is not None:
|
||||
try:
|
||||
tuple(PlaylistEntries.parse_playlist_items(opts.playlist_items))
|
||||
tuple(PlaylistEntries.parse_playlist_items(opts.playlist_items, ()))
|
||||
except Exception as err:
|
||||
raise ValueError(f'Invalid playlist-items {opts.playlist_items!r}: {err}')
|
||||
|
||||
|
|
|
@ -2390,6 +2390,23 @@ def _getslice(self, start, end):
|
|||
yield from page_results
|
||||
|
||||
|
||||
def index_in_slice_inclusive(idx: int, slice_: slice):
|
||||
start, step, stop = slice_.start, slice_.step, slice_.stop
|
||||
if start is None:
|
||||
start = 0
|
||||
if step is None:
|
||||
step = 1
|
||||
if stop is None or stop == math.inf or stop == -math.inf:
|
||||
if (idx - start) % step != 0:
|
||||
return False
|
||||
if step > 0:
|
||||
return idx >= start and stop != -math.inf
|
||||
else:
|
||||
return idx <= start and stop != math.inf
|
||||
else:
|
||||
return idx in range(start, int(stop) + 1, step)
|
||||
|
||||
|
||||
class PlaylistEntries:
|
||||
MissingEntry = object()
|
||||
is_exhausted = False
|
||||
|
@ -2423,20 +2440,61 @@ def __init__(self, ydl, info_dict):
|
|||
(?::(?P<step>[+-]?\d+))?
|
||||
)?''')
|
||||
|
||||
NESTED_PLAYLIST_RE = re.compile(r'''(?x)
|
||||
(?:\[
|
||||
(?:[+-]?\d+)?
|
||||
(?:[:-]
|
||||
(?:[+-]?\d+|inf(?:inite)?)?
|
||||
(?::(?:[+-]?\d+))?
|
||||
)?
|
||||
\])+''')
|
||||
|
||||
NESTED_PLAYLIST_SEGMENT_RE = re.compile(r'''(?x)
|
||||
\[
|
||||
(?P<start>[+-]?\d+)?
|
||||
(?P<range>[:-]
|
||||
(?P<end>[+-]?\d+|inf(?:inite)?)?
|
||||
(?::(?P<step>[+-]?\d+))?
|
||||
)?
|
||||
\]''')
|
||||
|
||||
@classmethod
|
||||
def parse_playlist_items(cls, string):
|
||||
def parse_playlist_items(cls, string, playlist_index):
|
||||
for segment in string.split(','):
|
||||
if not segment:
|
||||
raise ValueError('There is two or more consecutive commas')
|
||||
raise ValueError('There are two or more consecutive commas')
|
||||
mobj = cls.PLAYLIST_ITEMS_RE.fullmatch(segment)
|
||||
if not mobj:
|
||||
raise ValueError(f'{segment!r} is not a valid specification')
|
||||
start, end, step, has_range = mobj.group('start', 'end', 'step', 'range')
|
||||
if int_or_none(step) == 0:
|
||||
raise ValueError(f'Step in {segment!r} cannot be zero')
|
||||
yield slice(int_or_none(start), float_or_none(end), int_or_none(step)) if has_range else int(start)
|
||||
if mobj:
|
||||
start, end, step, has_range = mobj.group('start', 'end', 'step', 'range')
|
||||
if int_or_none(step) == 0:
|
||||
raise ValueError(f'Step in {segment!r} cannot be zero')
|
||||
yield slice(int_or_none(start), float_or_none(end), int_or_none(step)) if has_range else int(start)
|
||||
continue
|
||||
|
||||
def get_requested_items(self):
|
||||
if not cls.NESTED_PLAYLIST_RE.fullmatch(segment):
|
||||
raise ValueError(f'{segment!r} is not a valid specification')
|
||||
|
||||
for depth, mobj in enumerate(cls.NESTED_PLAYLIST_SEGMENT_RE.finditer(segment)):
|
||||
start, end, step, has_range = mobj.group('start', 'end', 'step', 'range')
|
||||
if int_or_none(step) == 0:
|
||||
raise ValueError(f'Step in {segment!r} cannot be zero')
|
||||
|
||||
slice_ = (
|
||||
slice(int_or_none(start), float_or_none(end), int_or_none(step))
|
||||
if has_range
|
||||
else slice(int(start), int(start))
|
||||
)
|
||||
|
||||
if depth == len(playlist_index):
|
||||
yield slice_
|
||||
break
|
||||
|
||||
if not index_in_slice_inclusive(playlist_index[depth], slice_):
|
||||
break
|
||||
else:
|
||||
yield slice(None)
|
||||
|
||||
def get_requested_items(self, playlist_index):
|
||||
playlist_items = self.ydl.params.get('playlist_items')
|
||||
playlist_start = self.ydl.params.get('playliststart', 1)
|
||||
playlist_end = self.ydl.params.get('playlistend')
|
||||
|
@ -2448,7 +2506,7 @@ def get_requested_items(self):
|
|||
elif playlist_start != 1 or playlist_end:
|
||||
self.ydl.report_warning('Ignoring playliststart and playlistend because playlistitems was given', only_once=True)
|
||||
|
||||
for index in self.parse_playlist_items(playlist_items):
|
||||
for index in self.parse_playlist_items(playlist_items, playlist_index):
|
||||
for i, entry in self[index]:
|
||||
yield i, entry
|
||||
if not entry:
|
||||
|
|
Loading…
Reference in a new issue