mirror of
https://github.com/ytdl-org/youtube-dl
synced 2025-12-05 11:05:22 +00:00
Compare commits
4 Commits
82552faba6
...
1e109aaee1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e109aaee1 | ||
|
|
efb4011211 | ||
|
|
c1f5c3274a | ||
|
|
e21ff28f6f |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -365,7 +365,7 @@ jobs:
|
|||||||
python -m ensurepip || python -m pip --version || { \
|
python -m ensurepip || python -m pip --version || { \
|
||||||
get_pip="${{ contains(needs.select.outputs.own-pip-versions, matrix.python-version) && format('{0}/', matrix.python-version) || '' }}"; \
|
get_pip="${{ contains(needs.select.outputs.own-pip-versions, matrix.python-version) && format('{0}/', matrix.python-version) || '' }}"; \
|
||||||
curl -L -O "https://bootstrap.pypa.io/pip/${get_pip}get-pip.py"; \
|
curl -L -O "https://bootstrap.pypa.io/pip/${get_pip}get-pip.py"; \
|
||||||
python get-pip.py; }
|
python get-pip.py --no-setuptools --no-wheel; }
|
||||||
- name: Set up Python 2.6 pip
|
- name: Set up Python 2.6 pip
|
||||||
if: ${{ matrix.python-version == '2.6' }}
|
if: ${{ matrix.python-version == '2.6' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@ -100,8 +100,8 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
'INNERTUBE_CONTEXT_CLIENT_NAME': 5,
|
'INNERTUBE_CONTEXT_CLIENT_NAME': 5,
|
||||||
|
'REQUIRE_PO_TOKEN': False,
|
||||||
'REQUIRE_JS_PLAYER': False,
|
'REQUIRE_JS_PLAYER': False,
|
||||||
'REQUIRE_PO_TOKEN': True,
|
|
||||||
},
|
},
|
||||||
# mweb has 'ultralow' formats
|
# mweb has 'ultralow' formats
|
||||||
# See: https://github.com/yt-dlp/yt-dlp/pull/557
|
# See: https://github.com/yt-dlp/yt-dlp/pull/557
|
||||||
@ -478,6 +478,7 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
def _extract_thumbnails(data, *path_list, **kw_final_key):
|
def _extract_thumbnails(data, *path_list, **kw_final_key):
|
||||||
"""
|
"""
|
||||||
Extract thumbnails from thumbnails dict
|
Extract thumbnails from thumbnails dict
|
||||||
|
|
||||||
@param path_list: path list to level that contains 'thumbnails' key
|
@param path_list: path list to level that contains 'thumbnails' key
|
||||||
"""
|
"""
|
||||||
final_key = kw_final_key.get('final_key', 'thumbnails')
|
final_key = kw_final_key.get('final_key', 'thumbnails')
|
||||||
@ -520,34 +521,26 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
|||||||
headers={'content-type': 'application/json'})
|
headers={'content-type': 'application/json'})
|
||||||
if not search:
|
if not search:
|
||||||
break
|
break
|
||||||
slr_contents = try_get(
|
slr_contents = traverse_obj(
|
||||||
search,
|
search,
|
||||||
(lambda x: x['contents']['twoColumnSearchResultsRenderer']['primaryContents']['sectionListRenderer']['contents'],
|
('contents', 'twoColumnSearchResultsRenderer', 'primaryContents',
|
||||||
lambda x: x['onResponseReceivedCommands'][0]['appendContinuationItemsAction']['continuationItems']),
|
'sectionListRenderer', 'contents'),
|
||||||
list)
|
('onResponseReceivedCommands', 0, 'appendContinuationItemsAction',
|
||||||
|
'continuationItems'),
|
||||||
|
expected_type=list)
|
||||||
if not slr_contents:
|
if not slr_contents:
|
||||||
break
|
break
|
||||||
for slr_content in slr_contents:
|
for video in traverse_obj(
|
||||||
isr_contents = try_get(
|
|
||||||
slr_content,
|
|
||||||
lambda x: x['itemSectionRenderer']['contents'],
|
|
||||||
list)
|
|
||||||
if not isr_contents:
|
|
||||||
continue
|
|
||||||
for content in isr_contents:
|
|
||||||
if not isinstance(content, dict):
|
|
||||||
continue
|
|
||||||
video = content.get('videoRenderer')
|
|
||||||
if not isinstance(video, dict):
|
|
||||||
continue
|
|
||||||
video_id = video.get('videoId')
|
|
||||||
if not video_id:
|
|
||||||
continue
|
|
||||||
yield self._extract_video(video)
|
|
||||||
token = try_get(
|
|
||||||
slr_contents,
|
slr_contents,
|
||||||
lambda x: x[-1]['continuationItemRenderer']['continuationEndpoint']['continuationCommand']['token'],
|
(Ellipsis, 'itemSectionRenderer', 'contents',
|
||||||
compat_str)
|
Ellipsis, 'videoRenderer',
|
||||||
|
T(lambda v: v if v.get('videoId') else None))):
|
||||||
|
yield self._extract_video(video)
|
||||||
|
|
||||||
|
token = traverse_obj(
|
||||||
|
slr_contents,
|
||||||
|
(-1, 'continuationItemRenderer', 'continuationEndpoint',
|
||||||
|
'continuationCommand', 'token', T(compat_str)))
|
||||||
if not token:
|
if not token:
|
||||||
break
|
break
|
||||||
data['continuation'] = token
|
data['continuation'] = token
|
||||||
@ -2176,7 +2169,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
raise ExtractorError('Invalid URL: %s' % url)
|
raise ExtractorError('Invalid URL: %s' % url)
|
||||||
return mobj.group(2)
|
return mobj.group(2)
|
||||||
|
|
||||||
def _extract_chapters_from_json(self, data, video_id, duration):
|
@staticmethod
|
||||||
|
def _extract_chapters_from_json(data, video_id, duration):
|
||||||
chapters_list = try_get(
|
chapters_list = try_get(
|
||||||
data,
|
data,
|
||||||
lambda x: x['playerOverlays']
|
lambda x: x['playerOverlays']
|
||||||
@ -2472,7 +2466,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
return LazyList({
|
return LazyList({
|
||||||
'url': update_url_query(f['url'], {
|
'url': update_url_query(f['url'], {
|
||||||
'range': '{0}-{1}'.format(range_start, min(range_start + CHUNK_SIZE - 1, f['filesize'])),
|
'range': '{0}-{1}'.format(range_start, min(range_start + CHUNK_SIZE - 1, f['filesize'])),
|
||||||
})
|
}),
|
||||||
} for range_start in range(0, f['filesize'], CHUNK_SIZE))
|
} for range_start in range(0, f['filesize'], CHUNK_SIZE))
|
||||||
|
|
||||||
lower = lambda s: s.lower()
|
lower = lambda s: s.lower()
|
||||||
@ -2778,7 +2772,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|||||||
'fmt': fmt,
|
'fmt': fmt,
|
||||||
# xosf=1 causes undesirable text position data for vtt, json3 & srv* subtitles
|
# xosf=1 causes undesirable text position data for vtt, json3 & srv* subtitles
|
||||||
# See: https://github.com/yt-dlp/yt-dlp/issues/13654
|
# See: https://github.com/yt-dlp/yt-dlp/issues/13654
|
||||||
'xosf': []
|
'xosf': [],
|
||||||
})
|
})
|
||||||
lang_subs.append({
|
lang_subs.append({
|
||||||
'ext': fmt,
|
'ext': fmt,
|
||||||
@ -3426,13 +3420,9 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_grid_item_renderer(item):
|
def _extract_grid_item_renderer(item):
|
||||||
assert isinstance(item, dict)
|
return traverse_obj(item, (
|
||||||
for key, renderer in item.items():
|
T(dict.items), lambda _, k_v: k_v[0].startswith('grid') and k_v[0].endswith('Renderer'),
|
||||||
if not key.startswith('grid') or not key.endswith('Renderer'):
|
1, T(dict)), get_all=False)
|
||||||
continue
|
|
||||||
if not isinstance(renderer, dict):
|
|
||||||
continue
|
|
||||||
return renderer
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_text(r, k):
|
def _get_text(r, k):
|
||||||
@ -3515,8 +3505,8 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
shelf_renderer, lambda x: x['title']['runs'][0]['text'], compat_str)
|
shelf_renderer, lambda x: x['title']['runs'][0]['text'], compat_str)
|
||||||
yield self.url_result(shelf_url, video_title=title)
|
yield self.url_result(shelf_url, video_title=title)
|
||||||
# Shelf may not contain shelf URL, fallback to extraction from content
|
# Shelf may not contain shelf URL, fallback to extraction from content
|
||||||
for entry in self._shelf_entries_from_content(shelf_renderer):
|
for from_ in self._shelf_entries_from_content(shelf_renderer):
|
||||||
yield entry
|
yield from_
|
||||||
|
|
||||||
def _playlist_entries(self, video_list_renderer):
|
def _playlist_entries(self, video_list_renderer):
|
||||||
for content in video_list_renderer['contents']:
|
for content in video_list_renderer['contents']:
|
||||||
@ -3538,12 +3528,12 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
if content_type == 'LOCKUP_CONTENT_TYPE_VIDEO':
|
if content_type == 'LOCKUP_CONTENT_TYPE_VIDEO':
|
||||||
ie = YoutubeIE
|
ie = YoutubeIE
|
||||||
url = update_url_query(
|
url = update_url_query(
|
||||||
'https://www.youtube.com/watch', {'v': content_id}),
|
'https://www.youtube.com/watch', {'v': content_id})
|
||||||
thumb_keys = (None,)
|
thumb_keys = (None,)
|
||||||
elif content_type in ('LOCKUP_CONTENT_TYPE_PLAYLIST', 'LOCKUP_CONTENT_TYPE_PODCAST'):
|
elif content_type in ('LOCKUP_CONTENT_TYPE_PLAYLIST', 'LOCKUP_CONTENT_TYPE_PODCAST'):
|
||||||
ie = YoutubeTabIE
|
ie = YoutubeTabIE
|
||||||
url = update_url_query(
|
url = update_url_query(
|
||||||
'https://www.youtube.com/playlist', {'list': content_id}),
|
'https://www.youtube.com/playlist', {'list': content_id})
|
||||||
thumb_keys = ('collectionThumbnailViewModel', 'primaryThumbnail')
|
thumb_keys = ('collectionThumbnailViewModel', 'primaryThumbnail')
|
||||||
else:
|
else:
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
@ -3606,15 +3596,10 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
yield self.url_result(ep_url, ie=YoutubeIE.ie_key(), video_id=video_id)
|
yield self.url_result(ep_url, ie=YoutubeIE.ie_key(), video_id=video_id)
|
||||||
|
|
||||||
def _post_thread_continuation_entries(self, post_thread_continuation):
|
def _post_thread_continuation_entries(self, post_thread_continuation):
|
||||||
contents = post_thread_continuation.get('contents')
|
for renderer in traverse_obj(post_thread_continuation, (
|
||||||
if not isinstance(contents, list):
|
'contents', Ellipsis, 'backstagePostThreadRenderer', T(dict))):
|
||||||
return
|
for from_ in self._post_thread_entries(renderer):
|
||||||
for content in contents:
|
yield from_
|
||||||
renderer = content.get('backstagePostThreadRenderer')
|
|
||||||
if not isinstance(renderer, dict):
|
|
||||||
continue
|
|
||||||
for entry in self._post_thread_entries(renderer):
|
|
||||||
yield entry
|
|
||||||
|
|
||||||
def _rich_grid_entries(self, contents):
|
def _rich_grid_entries(self, contents):
|
||||||
for content in traverse_obj(
|
for content in traverse_obj(
|
||||||
@ -3689,17 +3674,10 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
if slr_renderer:
|
if slr_renderer:
|
||||||
is_channels_tab = tab.get('title') == 'Channels'
|
is_channels_tab = tab.get('title') == 'Channels'
|
||||||
continuation = None
|
continuation = None
|
||||||
slr_contents = try_get(slr_renderer, lambda x: x['contents'], list) or []
|
for is_renderer in traverse_obj(slr_renderer, (
|
||||||
for slr_content in slr_contents:
|
'contents', Ellipsis, 'itemSectionRenderer', T(dict))):
|
||||||
if not isinstance(slr_content, dict):
|
for isr_content in traverse_obj(slr_renderer, (
|
||||||
continue
|
'contents', Ellipsis, T(dict))):
|
||||||
is_renderer = try_get(slr_content, lambda x: x['itemSectionRenderer'], dict)
|
|
||||||
if not is_renderer:
|
|
||||||
continue
|
|
||||||
isr_contents = try_get(is_renderer, lambda x: x['contents'], list) or []
|
|
||||||
for isr_content in isr_contents:
|
|
||||||
if not isinstance(isr_content, dict):
|
|
||||||
continue
|
|
||||||
renderer = isr_content.get('playlistVideoListRenderer')
|
renderer = isr_content.get('playlistVideoListRenderer')
|
||||||
if renderer:
|
if renderer:
|
||||||
for entry in self._playlist_entries(renderer):
|
for entry in self._playlist_entries(renderer):
|
||||||
@ -3894,18 +3872,34 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
uploader['channel'] = uploader['uploader']
|
uploader['channel'] = uploader['uploader']
|
||||||
return uploader
|
return uploader
|
||||||
|
|
||||||
@classmethod
|
def _extract_and_report_alerts(self, data, expected=True, fatal=True, only_once=False):
|
||||||
def _extract_alert(cls, data):
|
|
||||||
alerts = []
|
def alerts():
|
||||||
for alert in traverse_obj(data, ('alerts', Ellipsis), expected_type=dict):
|
for alert in traverse_obj(data, ('alerts', Ellipsis), expected_type=dict):
|
||||||
alert_text = traverse_obj(
|
alert_dict = traverse_obj(
|
||||||
alert, (None, lambda x: x['alertRenderer']['text']), get_all=False)
|
alert, 'alertRenderer', None, expected_type=dict, get_all=False)
|
||||||
if not alert_text:
|
alert_type = traverse_obj(alert_dict, 'type')
|
||||||
|
if not alert_type:
|
||||||
continue
|
continue
|
||||||
text = cls._get_text(alert_text, 'text')
|
message = self._get_text(alert_dict, 'text')
|
||||||
if text:
|
if message:
|
||||||
alerts.append(text)
|
yield alert_type, message
|
||||||
return '\n'.join(alerts)
|
|
||||||
|
errors, warnings = [], []
|
||||||
|
_IGNORED_WARNINGS = T('Unavailable videos will be hidden during playback')
|
||||||
|
for alert_type, alert_message in alerts():
|
||||||
|
if alert_type.lower() == 'error' and fatal:
|
||||||
|
errors.append([alert_type, alert_message])
|
||||||
|
elif alert_message not in _IGNORED_WARNINGS:
|
||||||
|
warnings.append([alert_type, alert_message])
|
||||||
|
|
||||||
|
for alert_type, alert_message in itertools.chain(warnings, errors[:-1]):
|
||||||
|
self.report_warning(
|
||||||
|
'YouTube said: %s - %s' % (alert_type, alert_message),
|
||||||
|
only_once=only_once)
|
||||||
|
if errors:
|
||||||
|
raise ExtractorError(
|
||||||
|
'YouTube said: %s' % (errors[-1][1],), expected=expected)
|
||||||
|
|
||||||
def _extract_from_tabs(self, item_id, webpage, data, tabs):
|
def _extract_from_tabs(self, item_id, webpage, data, tabs):
|
||||||
selected_tab = self._extract_selected_tab(tabs)
|
selected_tab = self._extract_selected_tab(tabs)
|
||||||
@ -4005,10 +3999,10 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|||||||
compat_str) or video_id
|
compat_str) or video_id
|
||||||
if video_id:
|
if video_id:
|
||||||
return self.url_result(video_id, ie=YoutubeIE.ie_key(), video_id=video_id)
|
return self.url_result(video_id, ie=YoutubeIE.ie_key(), video_id=video_id)
|
||||||
|
|
||||||
# Capture and output alerts
|
# Capture and output alerts
|
||||||
alert = self._extract_alert(data)
|
self._extract_and_report_alerts(data)
|
||||||
if alert:
|
|
||||||
raise ExtractorError(alert, expected=True)
|
|
||||||
# Failed to recognize
|
# Failed to recognize
|
||||||
raise ExtractorError('Unable to recognize tab page')
|
raise ExtractorError('Unable to recognize tab page')
|
||||||
|
|
||||||
@ -4162,7 +4156,7 @@ class YoutubeFavouritesIE(YoutubeBaseInfoExtractor):
|
|||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, _):
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
'https://www.youtube.com/playlist?list=LL',
|
'https://www.youtube.com/playlist?list=LL',
|
||||||
ie=YoutubeTabIE.ie_key())
|
ie=YoutubeTabIE.ie_key())
|
||||||
@ -4244,7 +4238,7 @@ class YoutubeFeedsInfoExtractor(YoutubeTabIE):
|
|||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, _):
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
'https://www.youtube.com/feed/%s' % self._FEED_NAME,
|
'https://www.youtube.com/feed/%s' % self._FEED_NAME,
|
||||||
ie=YoutubeTabIE.ie_key())
|
ie=YoutubeTabIE.ie_key())
|
||||||
@ -4259,7 +4253,7 @@ class YoutubeWatchLaterIE(InfoExtractor):
|
|||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, _):
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
'https://www.youtube.com/playlist?list=WL', ie=YoutubeTabIE.ie_key())
|
'https://www.youtube.com/playlist?list=WL', ie=YoutubeTabIE.ie_key())
|
||||||
|
|
||||||
@ -4339,7 +4333,7 @@ class YoutubeTruncatedURLIE(InfoExtractor):
|
|||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, _):
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'Did you forget to quote the URL? Remember that & is a meta '
|
'Did you forget to quote the URL? Remember that & is a meta '
|
||||||
'character in most shells, so you want to put the URL in quotes, '
|
'character in most shells, so you want to put the URL in quotes, '
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user