|
@@ -46,6 +46,10 @@ from ..utils import (
|
|
|
)
|
|
|
|
|
|
|
|
|
+def parse_qs(url):
|
|
|
+ return compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
|
|
|
+
|
|
|
+
|
|
|
class YoutubeBaseInfoExtractor(InfoExtractor):
|
|
|
"""Provide base functions for Youtube extractors"""
|
|
|
_LOGIN_URL = 'https://accounts.google.com/ServiceLogin'
|
|
@@ -413,16 +417,9 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|
|
|(?:www\.)?cleanvideosearch\.com/media/action/yt/watch\?videoId=
|
|
|
)
|
|
|
)? # all until now is optional -> you can pass the naked ID
|
|
|
- (?P<id>[0-9A-Za-z_-]{11}) # here is it! the YouTube video ID
|
|
|
- (?!.*?\blist=
|
|
|
- (?:
|
|
|
- %(playlist_id)s| # combined list/video URLs are handled by the playlist IE
|
|
|
- WL # WL are handled by the watch later IE
|
|
|
- )
|
|
|
- )
|
|
|
+ (?P<id>[0-9A-Za-z_-]{11}) # here is it! the YouTube video ID
|
|
|
(?(1).+)? # if we found the ID, everything can follow
|
|
|
$""" % {
|
|
|
- 'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE,
|
|
|
'invidious': '|'.join(_INVIDIOUS_SITES),
|
|
|
}
|
|
|
_PLAYER_INFO_RE = (
|
|
@@ -1208,6 +1205,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
|
|
|
'397': {'acodec': 'none', 'vcodec': 'av01.0.05M.08'},
|
|
|
}
|
|
|
|
|
|
+ @classmethod
|
|
|
+ def suitable(cls, url):
|
|
|
+ qs = parse_qs(url)
|
|
|
+ if qs.get('list', [None])[0]:
|
|
|
+ return False
|
|
|
+ return super(YoutubeIE, cls).suitable(url)
|
|
|
+
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
super(YoutubeIE, self).__init__(*args, **kwargs)
|
|
|
self._code_cache = {}
|
|
@@ -2275,6 +2279,9 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|
|
'title': '#cctv9',
|
|
|
},
|
|
|
'playlist_mincount': 350,
|
|
|
+ }, {
|
|
|
+ 'url': 'https://www.youtube.com/watch?list=PLW4dVinRY435CBE_JD3t-0SRXKfnZHS1P&feature=youtu.be&v=M9cJMXmQ_ZU',
|
|
|
+ 'only_matching': True,
|
|
|
}]
|
|
|
|
|
|
@classmethod
|
|
@@ -2764,7 +2771,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
|
|
|
url = compat_urlparse.urlunparse(
|
|
|
compat_urlparse.urlparse(url)._replace(netloc='www.youtube.com'))
|
|
|
# Handle both video/playlist URLs
|
|
|
- qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
|
|
|
+ qs = parse_qs(url)
|
|
|
video_id = qs.get('v', [None])[0]
|
|
|
playlist_id = qs.get('list', [None])[0]
|
|
|
if video_id and playlist_id:
|
|
@@ -2860,12 +2867,16 @@ class YoutubePlaylistIE(InfoExtractor):
|
|
|
|
|
|
@classmethod
|
|
|
def suitable(cls, url):
|
|
|
- return False if YoutubeTabIE.suitable(url) else super(
|
|
|
- YoutubePlaylistIE, cls).suitable(url)
|
|
|
+ if YoutubeTabIE.suitable(url):
|
|
|
+ return False
|
|
|
+ qs = parse_qs(url)
|
|
|
+ if qs.get('v', [None])[0]:
|
|
|
+ return False
|
|
|
+ return super(YoutubePlaylistIE, cls).suitable(url)
|
|
|
|
|
|
def _real_extract(self, url):
|
|
|
playlist_id = self._match_id(url)
|
|
|
- qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
|
|
|
+ qs = parse_qs(url)
|
|
|
if not qs:
|
|
|
qs = {'list': playlist_id}
|
|
|
return self.url_result(
|