Skip to content

Commit cf501f5

Browse files
authored
Merge pull request #25 from oleksis/master
Update youtube-dl 2021.01.16
2 parents 95d73f6 + 07f0b83 commit cf501f5

File tree

15 files changed

+603
-395
lines changed

15 files changed

+603
-395
lines changed

youtube_dl/YoutubeDL.py

Lines changed: 133 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ class YoutubeDL(object):
338338
_pps = []
339339
_download_retcode = None
340340
_num_downloads = None
341+
_playlist_level = 0
342+
_playlist_urls = set()
341343
_screen_file = None
342344

343345
def __init__(self, params=None, auto_init=True):
@@ -906,115 +908,23 @@ def process_ie_result(self, ie_result, download=True, extra_info={}):
906908
return self.process_ie_result(
907909
new_result, download=download, extra_info=extra_info)
908910
elif result_type in ('playlist', 'multi_video'):
909-
# We process each entry in the playlist
910-
playlist = ie_result.get('title') or ie_result.get('id')
911-
self.to_screen('[download] Downloading playlist: %s' % playlist)
912-
913-
playlist_results = []
914-
915-
playliststart = self.params.get('playliststart', 1) - 1
916-
playlistend = self.params.get('playlistend')
917-
# For backwards compatibility, interpret -1 as whole list
918-
if playlistend == -1:
919-
playlistend = None
920-
921-
playlistitems_str = self.params.get('playlist_items')
922-
playlistitems = None
923-
if playlistitems_str is not None:
924-
def iter_playlistitems(format):
925-
for string_segment in format.split(','):
926-
if '-' in string_segment:
927-
start, end = string_segment.split('-')
928-
for item in range(int(start), int(end) + 1):
929-
yield int(item)
930-
else:
931-
yield int(string_segment)
932-
playlistitems = orderedSet(iter_playlistitems(playlistitems_str))
933-
934-
ie_entries = ie_result['entries']
935-
936-
def make_playlistitems_entries(list_ie_entries):
937-
num_entries = len(list_ie_entries)
938-
return [
939-
list_ie_entries[i - 1] for i in playlistitems
940-
if -num_entries <= i - 1 < num_entries]
941-
942-
def report_download(num_entries):
911+
# Protect from infinite recursion due to recursively nested playlists
912+
# (see https://github.com/ytdl-org/youtube-dl/issues/27833)
913+
webpage_url = ie_result['webpage_url']
914+
if webpage_url in self._playlist_urls:
943915
self.to_screen(
944-
'[%s] playlist %s: Downloading %d videos' %
945-
(ie_result['extractor'], playlist, num_entries))
946-
947-
if isinstance(ie_entries, list):
948-
n_all_entries = len(ie_entries)
949-
if playlistitems:
950-
entries = make_playlistitems_entries(ie_entries)
951-
else:
952-
entries = ie_entries[playliststart:playlistend]
953-
n_entries = len(entries)
954-
self.to_screen(
955-
'[%s] playlist %s: Collected %d video ids (downloading %d of them)' %
956-
(ie_result['extractor'], playlist, n_all_entries, n_entries))
957-
elif isinstance(ie_entries, PagedList):
958-
if playlistitems:
959-
entries = []
960-
for item in playlistitems:
961-
entries.extend(ie_entries.getslice(
962-
item - 1, item
963-
))
964-
else:
965-
entries = ie_entries.getslice(
966-
playliststart, playlistend)
967-
n_entries = len(entries)
968-
report_download(n_entries)
969-
else: # iterable
970-
if playlistitems:
971-
entries = make_playlistitems_entries(list(itertools.islice(
972-
ie_entries, 0, max(playlistitems))))
973-
else:
974-
entries = list(itertools.islice(
975-
ie_entries, playliststart, playlistend))
976-
n_entries = len(entries)
977-
report_download(n_entries)
978-
979-
if self.params.get('playlistreverse', False):
980-
entries = entries[::-1]
981-
982-
if self.params.get('playlistrandom', False):
983-
random.shuffle(entries)
984-
985-
x_forwarded_for = ie_result.get('__x_forwarded_for_ip')
986-
987-
for i, entry in enumerate(entries, 1):
988-
self.to_screen('[download] Downloading video %s of %s' % (i, n_entries))
989-
# This __x_forwarded_for_ip thing is a bit ugly but requires
990-
# minimal changes
991-
if x_forwarded_for:
992-
entry['__x_forwarded_for_ip'] = x_forwarded_for
993-
extra = {
994-
'n_entries': n_entries,
995-
'playlist': playlist,
996-
'playlist_id': ie_result.get('id'),
997-
'playlist_title': ie_result.get('title'),
998-
'playlist_uploader': ie_result.get('uploader'),
999-
'playlist_uploader_id': ie_result.get('uploader_id'),
1000-
'playlist_index': playlistitems[i - 1] if playlistitems else i + playliststart,
1001-
'extractor': ie_result['extractor'],
1002-
'webpage_url': ie_result['webpage_url'],
1003-
'webpage_url_basename': url_basename(ie_result['webpage_url']),
1004-
'extractor_key': ie_result['extractor_key'],
1005-
}
1006-
1007-
reason = self._match_entry(entry, incomplete=True)
1008-
if reason is not None:
1009-
self.to_screen('[download] ' + reason)
1010-
continue
916+
'[download] Skipping already downloaded playlist: %s'
917+
% ie_result.get('title') or ie_result.get('id'))
918+
return
1011919

1012-
entry_result = self.__process_iterable_entry(entry, download, extra)
1013-
# TODO: skip failed (empty) entries?
1014-
playlist_results.append(entry_result)
1015-
ie_result['entries'] = playlist_results
1016-
self.to_screen('[download] Finished downloading playlist: %s' % playlist)
1017-
return ie_result
920+
self._playlist_level += 1
921+
self._playlist_urls.add(webpage_url)
922+
try:
923+
return self.__process_playlist(ie_result, download)
924+
finally:
925+
self._playlist_level -= 1
926+
if not self._playlist_level:
927+
self._playlist_urls.clear()
1018928
elif result_type == 'compat_list':
1019929
self.report_warning(
1020930
'Extractor %s returned a compat_list result. '
@@ -1039,6 +949,118 @@ def _fixup(r):
1039949
else:
1040950
raise Exception('Invalid result type: %s' % result_type)
1041951

952+
def __process_playlist(self, ie_result, download):
953+
# We process each entry in the playlist
954+
playlist = ie_result.get('title') or ie_result.get('id')
955+
956+
self.to_screen('[download] Downloading playlist: %s' % playlist)
957+
958+
playlist_results = []
959+
960+
playliststart = self.params.get('playliststart', 1) - 1
961+
playlistend = self.params.get('playlistend')
962+
# For backwards compatibility, interpret -1 as whole list
963+
if playlistend == -1:
964+
playlistend = None
965+
966+
playlistitems_str = self.params.get('playlist_items')
967+
playlistitems = None
968+
if playlistitems_str is not None:
969+
def iter_playlistitems(format):
970+
for string_segment in format.split(','):
971+
if '-' in string_segment:
972+
start, end = string_segment.split('-')
973+
for item in range(int(start), int(end) + 1):
974+
yield int(item)
975+
else:
976+
yield int(string_segment)
977+
playlistitems = orderedSet(iter_playlistitems(playlistitems_str))
978+
979+
ie_entries = ie_result['entries']
980+
981+
def make_playlistitems_entries(list_ie_entries):
982+
num_entries = len(list_ie_entries)
983+
return [
984+
list_ie_entries[i - 1] for i in playlistitems
985+
if -num_entries <= i - 1 < num_entries]
986+
987+
def report_download(num_entries):
988+
self.to_screen(
989+
'[%s] playlist %s: Downloading %d videos' %
990+
(ie_result['extractor'], playlist, num_entries))
991+
992+
if isinstance(ie_entries, list):
993+
n_all_entries = len(ie_entries)
994+
if playlistitems:
995+
entries = make_playlistitems_entries(ie_entries)
996+
else:
997+
entries = ie_entries[playliststart:playlistend]
998+
n_entries = len(entries)
999+
self.to_screen(
1000+
'[%s] playlist %s: Collected %d video ids (downloading %d of them)' %
1001+
(ie_result['extractor'], playlist, n_all_entries, n_entries))
1002+
elif isinstance(ie_entries, PagedList):
1003+
if playlistitems:
1004+
entries = []
1005+
for item in playlistitems:
1006+
entries.extend(ie_entries.getslice(
1007+
item - 1, item
1008+
))
1009+
else:
1010+
entries = ie_entries.getslice(
1011+
playliststart, playlistend)
1012+
n_entries = len(entries)
1013+
report_download(n_entries)
1014+
else: # iterable
1015+
if playlistitems:
1016+
entries = make_playlistitems_entries(list(itertools.islice(
1017+
ie_entries, 0, max(playlistitems))))
1018+
else:
1019+
entries = list(itertools.islice(
1020+
ie_entries, playliststart, playlistend))
1021+
n_entries = len(entries)
1022+
report_download(n_entries)
1023+
1024+
if self.params.get('playlistreverse', False):
1025+
entries = entries[::-1]
1026+
1027+
if self.params.get('playlistrandom', False):
1028+
random.shuffle(entries)
1029+
1030+
x_forwarded_for = ie_result.get('__x_forwarded_for_ip')
1031+
1032+
for i, entry in enumerate(entries, 1):
1033+
self.to_screen('[download] Downloading video %s of %s' % (i, n_entries))
1034+
# This __x_forwarded_for_ip thing is a bit ugly but requires
1035+
# minimal changes
1036+
if x_forwarded_for:
1037+
entry['__x_forwarded_for_ip'] = x_forwarded_for
1038+
extra = {
1039+
'n_entries': n_entries,
1040+
'playlist': playlist,
1041+
'playlist_id': ie_result.get('id'),
1042+
'playlist_title': ie_result.get('title'),
1043+
'playlist_uploader': ie_result.get('uploader'),
1044+
'playlist_uploader_id': ie_result.get('uploader_id'),
1045+
'playlist_index': playlistitems[i - 1] if playlistitems else i + playliststart,
1046+
'extractor': ie_result['extractor'],
1047+
'webpage_url': ie_result['webpage_url'],
1048+
'webpage_url_basename': url_basename(ie_result['webpage_url']),
1049+
'extractor_key': ie_result['extractor_key'],
1050+
}
1051+
1052+
reason = self._match_entry(entry, incomplete=True)
1053+
if reason is not None:
1054+
self.to_screen('[download] ' + reason)
1055+
continue
1056+
1057+
entry_result = self.__process_iterable_entry(entry, download, extra)
1058+
# TODO: skip failed (empty) entries?
1059+
playlist_results.append(entry_result)
1060+
ie_result['entries'] = playlist_results
1061+
self.to_screen('[download] Finished downloading playlist: %s' % playlist)
1062+
return ie_result
1063+
10421064
@__handle_extraction_exceptions
10431065
def __process_iterable_entry(self, entry, download, extra_info):
10441066
return self.process_ie_result(
@@ -1226,6 +1248,8 @@ def _parse_format_selection(tokens, inside_merge=False, inside_choice=False, ins
12261248
group = _parse_format_selection(tokens, inside_group=True)
12271249
current_selector = FormatSelector(GROUP, group, [])
12281250
elif string == '+':
1251+
if inside_merge:
1252+
raise syntax_error('Unexpected "+"', start)
12291253
video_selector = current_selector
12301254
audio_selector = _parse_format_selection(tokens, inside_merge=True)
12311255
if not video_selector or not audio_selector:
@@ -1777,6 +1801,8 @@ def ensure_dir_exists(path):
17771801
os.makedirs(dn)
17781802
return True
17791803
except (OSError, IOError) as err:
1804+
if isinstance(err, OSError) and err.errno == errno.EEXIST:
1805+
return True
17801806
self.report_error('unable to create directory ' + error_to_compat_str(err))
17811807
return False
17821808

0 commit comments

Comments
 (0)