|
|
@ -31,6 +31,7 @@ from ..utils import ( |
|
|
|
clean_html, |
|
|
|
dict_get, |
|
|
|
error_to_compat_str, |
|
|
|
extract_attributes, |
|
|
|
ExtractorError, |
|
|
|
float_or_none, |
|
|
|
get_element_by_attribute, |
|
|
@ -324,17 +325,18 @@ class YoutubePlaylistBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor): |
|
|
|
for video_id, video_title in self.extract_videos_from_page(content): |
|
|
|
yield self.url_result(video_id, 'Youtube', video_id, video_title) |
|
|
|
|
|
|
|
def extract_videos_from_page(self, page): |
|
|
|
ids_in_page = [] |
|
|
|
titles_in_page = [] |
|
|
|
for mobj in re.finditer(self._VIDEO_RE, page): |
|
|
|
def extract_videos_from_page_impl(self, video_re, page, ids_in_page, titles_in_page): |
|
|
|
for mobj in re.finditer(video_re, page): |
|
|
|
# The link with index 0 is not the first video of the playlist (not sure if still actual) |
|
|
|
if 'index' in mobj.groupdict() and mobj.group('id') == '0': |
|
|
|
continue |
|
|
|
video_id = mobj.group('id') |
|
|
|
video_title = unescapeHTML(mobj.group('title')) |
|
|
|
video_title = unescapeHTML( |
|
|
|
mobj.group('title')) if 'title' in mobj.groupdict() else None |
|
|
|
if video_title: |
|
|
|
video_title = video_title.strip() |
|
|
|
if video_title == '► Play all': |
|
|
|
video_title = None |
|
|
|
try: |
|
|
|
idx = ids_in_page.index(video_id) |
|
|
|
if video_title and not titles_in_page[idx]: |
|
|
@ -342,6 +344,12 @@ class YoutubePlaylistBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor): |
|
|
|
except ValueError: |
|
|
|
ids_in_page.append(video_id) |
|
|
|
titles_in_page.append(video_title) |
|
|
|
|
|
|
|
def extract_videos_from_page(self, page): |
|
|
|
ids_in_page = [] |
|
|
|
titles_in_page = [] |
|
|
|
self.extract_videos_from_page_impl( |
|
|
|
self._VIDEO_RE, page, ids_in_page, titles_in_page) |
|
|
|
return zip(ids_in_page, titles_in_page) |
|
|
|
|
|
|
|
|
|
|
@ -2438,7 +2446,8 @@ class YoutubePlaylistIE(YoutubePlaylistBaseInfoExtractor): |
|
|
|
(%(playlist_id)s) |
|
|
|
)""" % {'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE} |
|
|
|
_TEMPLATE_URL = 'https://www.youtube.com/playlist?list=%s' |
|
|
|
_VIDEO_RE = r'href="\s*/watch\?v=(?P<id>[0-9A-Za-z_-]{11})(?:&(?:[^"]*?index=(?P<index>\d+))?(?:[^>]+>(?P<title>[^<]+))?)?' |
|
|
|
_VIDEO_RE_TPL = r'href="\s*/watch\?v=%s(?:&(?:[^"]*?index=(?P<index>\d+))?(?:[^>]+>(?P<title>[^<]+))?)?' |
|
|
|
_VIDEO_RE = _VIDEO_RE_TPL % r'(?P<id>[0-9A-Za-z_-]{11})' |
|
|
|
IE_NAME = 'youtube:playlist' |
|
|
|
_TESTS = [{ |
|
|
|
'url': 'https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re', |
|
|
@ -2603,6 +2612,34 @@ class YoutubePlaylistIE(YoutubePlaylistBaseInfoExtractor): |
|
|
|
def _real_initialize(self): |
|
|
|
self._login() |
|
|
|
|
|
|
|
def extract_videos_from_page(self, page): |
|
|
|
ids_in_page = [] |
|
|
|
titles_in_page = [] |
|
|
|
|
|
|
|
for item in re.findall( |
|
|
|
r'(<[^>]*\bdata-video-id\s*=\s*["\'][0-9A-Za-z_-]{11}[^>]+>)', page): |
|
|
|
attrs = extract_attributes(item) |
|
|
|
video_id = attrs['data-video-id'] |
|
|
|
video_title = unescapeHTML(attrs.get('data-title')) |
|
|
|
if video_title: |
|
|
|
video_title = video_title.strip() |
|
|
|
ids_in_page.append(video_id) |
|
|
|
titles_in_page.append(video_title) |
|
|
|
|
|
|
|
# Fallback with old _VIDEO_RE |
|
|
|
self.extract_videos_from_page_impl( |
|
|
|
self._VIDEO_RE, page, ids_in_page, titles_in_page) |
|
|
|
|
|
|
|
# Relaxed fallbacks |
|
|
|
self.extract_videos_from_page_impl( |
|
|
|
r'href="\s*/watch\?v\s*=\s*(?P<id>[0-9A-Za-z_-]{11})', page, |
|
|
|
ids_in_page, titles_in_page) |
|
|
|
self.extract_videos_from_page_impl( |
|
|
|
r'data-video-ids\s*=\s*["\'](?P<id>[0-9A-Za-z_-]{11})', page, |
|
|
|
ids_in_page, titles_in_page) |
|
|
|
|
|
|
|
return zip(ids_in_page, titles_in_page) |
|
|
|
|
|
|
|
def _extract_mix(self, playlist_id): |
|
|
|
# The mixes are generated from a single video |
|
|
|
# the id of the playlist is just 'RD' + video_id |
|
|
|