Compare commits

..

No commits in common. "f7ce98a21e15cb094c772e9082796d009c61578b" and "1d3751c3fe50b203d3e2bff71d866c8c500f8288" have entirely different histories.

2 changed files with 125 additions and 203 deletions

View File

@ -148,7 +148,6 @@ def generator(test_case, tname):
try_rm(tc_filename) try_rm(tc_filename)
try_rm(tc_filename + '.part') try_rm(tc_filename + '.part')
try_rm(os.path.splitext(tc_filename)[0] + '.info.json') try_rm(os.path.splitext(tc_filename)[0] + '.info.json')
try_rm_tcs_files() try_rm_tcs_files()
try: try:
try_num = 1 try_num = 1
@ -214,15 +213,7 @@ def generator(test_case, tname):
# First, check test cases' data against extracted data alone # First, check test cases' data against extracted data alone
expect_info_dict(self, tc_res_dict, tc.get('info_dict', {})) expect_info_dict(self, tc_res_dict, tc.get('info_dict', {}))
# Now, check downloaded file consistency # Now, check downloaded file consistency
# support test-case with volatile ID, signalled by regexp value
if tc.get('info_dict', {}).get('id', '').startswith('re:'):
test_id = tc['info_dict']['id']
tc['info_dict']['id'] = tc_res_dict['id']
else:
test_id = None
tc_filename = get_tc_filename(tc) tc_filename = get_tc_filename(tc)
if test_id:
tc['info_dict']['id'] = test_id
if not test_case.get('params', {}).get('skip_download', False): if not test_case.get('params', {}).get('skip_download', False):
self.assertTrue(os.path.exists(tc_filename), msg='Missing file ' + tc_filename) self.assertTrue(os.path.exists(tc_filename), msg='Missing file ' + tc_filename)
self.assertTrue(tc_filename in finished_hook_called) self.assertTrue(tc_filename in finished_hook_called)

View File

@ -31,7 +31,6 @@ from ..utils import (
get_element_by_attribute, get_element_by_attribute,
int_or_none, int_or_none,
js_to_json, js_to_json,
merge_dicts,
mimetype2ext, mimetype2ext,
parse_codecs, parse_codecs,
parse_duration, parse_duration,
@ -401,62 +400,6 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
break break
data['continuation'] = token data['continuation'] = token
@staticmethod
def _owner_endpoints_path():
return [
Ellipsis,
lambda k, _: k.endswith('SecondaryInfoRenderer'),
('owner', 'videoOwner'), 'videoOwnerRenderer', 'title',
'runs', Ellipsis]
def _extract_channel_id(self, webpage, videodetails={}, metadata={}, renderers=[]):
channel_id = None
if any((videodetails, metadata, renderers)):
channel_id = (
traverse_obj(videodetails, 'channelId')
or traverse_obj(metadata, 'externalChannelId', 'externalId')
or traverse_obj(renderers,
self._owner_endpoints_path() + [
'navigationEndpoint', 'browseEndpoint', 'browseId'],
get_all=False)
)
return channel_id or self._html_search_meta(
'channelId', webpage, 'channel id', default=None)
def _extract_author_var(self, webpage, var_name,
videodetails={}, metadata={}, renderers=[]):
result = None
paths = {
# (HTML, videodetails, metadata, renderers)
'name': ('content', 'author', (('ownerChannelName', None), 'title'), ['text']),
'url': ('href', 'ownerProfileUrl', 'vanityChannelUrl',
['navigationEndpoint', 'browseEndpoint', 'canonicalBaseUrl'])
}
if any((videodetails, metadata, renderers)):
result = (
traverse_obj(videodetails, paths[var_name][1], get_all=False)
or traverse_obj(metadata, paths[var_name][2], get_all=False)
or traverse_obj(renderers,
self._owner_endpoints_path() + paths[var_name][3],
get_all=False)
)
return result or traverse_obj(
extract_attributes(self._search_regex(
r'''(?s)(<link\b[^>]+\bitemprop\s*=\s*("|')%s\2[^>]*>)'''
% re.escape(var_name),
get_element_by_attribute('itemprop', 'author', webpage) or '',
'author link', default='')),
paths[var_name][0])
@staticmethod
def _yt_urljoin(url_or_path):
return urljoin('https://www.youtube.com', url_or_path)
def _extract_uploader_id(self, uploader_url):
return self._search_regex(
r'/(?:(?:channel|user)/|(?=@))([^/?&#]+)', uploader_url or '',
'uploader id', default=None)
class YoutubeIE(YoutubeBaseInfoExtractor): class YoutubeIE(YoutubeBaseInfoExtractor):
IE_DESC = 'YouTube.com' IE_DESC = 'YouTube.com'
@ -573,8 +516,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'ext': 'mp4', 'ext': 'mp4',
'title': 'youtube-dl test video "\'/\\ä↭𝕐', 'title': 'youtube-dl test video "\'/\\ä↭𝕐',
'uploader': 'Philipp Hagemeister', 'uploader': 'Philipp Hagemeister',
'uploader_id': '@PhilippHagemeister', 'uploader_id': 'phihag',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@PhilippHagemeister', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/phihag',
'channel': 'Philipp Hagemeister', 'channel': 'Philipp Hagemeister',
'channel_id': 'UCLqxVugv74EIW3VWh2NOa3Q', 'channel_id': 'UCLqxVugv74EIW3VWh2NOa3Q',
'channel_url': r're:https?://(?:www\.)?youtube\.com/channel/UCLqxVugv74EIW3VWh2NOa3Q', 'channel_url': r're:https?://(?:www\.)?youtube\.com/channel/UCLqxVugv74EIW3VWh2NOa3Q',
@ -614,8 +557,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'ext': 'mp4', 'ext': 'mp4',
'title': 'youtube-dl test video "\'/\\ä↭𝕐', 'title': 'youtube-dl test video "\'/\\ä↭𝕐',
'uploader': 'Philipp Hagemeister', 'uploader': 'Philipp Hagemeister',
'uploader_id': '@PhilippHagemeister', 'uploader_id': 'phihag',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@PhilippHagemeister', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/phihag',
'upload_date': '20121002', 'upload_date': '20121002',
'description': 'test chars: "\'/\\ä↭𝕐\ntest URL: https://github.com/rg3/youtube-dl/issues/1892\n\nThis is a test video for youtube-dl.\n\nFor more information, contact phihag@phihag.de .', 'description': 'test chars: "\'/\\ä↭𝕐\ntest URL: https://github.com/rg3/youtube-dl/issues/1892\n\nThis is a test video for youtube-dl.\n\nFor more information, contact phihag@phihag.de .',
'categories': ['Science & Technology'], 'categories': ['Science & Technology'],
@ -645,7 +588,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'youtube_include_dash_manifest': True, 'youtube_include_dash_manifest': True,
'format': '141', 'format': '141',
}, },
'skip': 'format 141 not served any more', 'skip': 'format 141 not served anymore',
}, },
# DASH manifest with encrypted signature # DASH manifest with encrypted signature
{ {
@ -657,7 +600,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': 'md5:8f5e2b82460520b619ccac1f509d43bf', 'description': 'md5:8f5e2b82460520b619ccac1f509d43bf',
'duration': 244, 'duration': 244,
'uploader': 'AfrojackVEVO', 'uploader': 'AfrojackVEVO',
'uploader_id': '@AfrojackVEVO', 'uploader_id': 'AfrojackVEVO',
'upload_date': '20131011', 'upload_date': '20131011',
'abr': 129.495, 'abr': 129.495,
}, },
@ -675,8 +618,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'duration': 219, 'duration': 219,
'upload_date': '20100909', 'upload_date': '20100909',
'uploader': 'Amazing Atheist', 'uploader': 'Amazing Atheist',
'uploader_id': '@theamazingatheist', 'uploader_id': 'TheAmazingAtheist',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@theamazingatheist', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/TheAmazingAtheist',
'title': 'Burning Everyone\'s Koran', 'title': 'Burning Everyone\'s Koran',
'description': 'SUBSCRIBE: http://www.youtube.com/saturninefilms \r\n\r\nEven Obama has taken a stand against freedom on this issue: http://www.huffingtonpost.com/2010/09/09/obama-gma-interview-quran_n_710282.html', 'description': 'SUBSCRIBE: http://www.youtube.com/saturninefilms \r\n\r\nEven Obama has taken a stand against freedom on this issue: http://www.huffingtonpost.com/2010/09/09/obama-gma-interview-quran_n_710282.html',
} }
@ -692,8 +635,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': r're:(?s).{100,}About the Game\n.*?The Witcher 3: Wild Hunt.{100,}', 'description': r're:(?s).{100,}About the Game\n.*?The Witcher 3: Wild Hunt.{100,}',
'duration': 142, 'duration': 142,
'uploader': 'The Witcher', 'uploader': 'The Witcher',
'uploader_id': '@thewitcher', 'uploader_id': 'WitcherGame',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@thewitcher', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/WitcherGame',
'upload_date': '20140605', 'upload_date': '20140605',
'thumbnail': 'https://i.ytimg.com/vi/HtVdAasjOgU/maxresdefault.jpg', 'thumbnail': 'https://i.ytimg.com/vi/HtVdAasjOgU/maxresdefault.jpg',
'age_limit': 18, 'age_limit': 18,
@ -716,7 +659,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': 'md5:bf77e03fcae5529475e500129b05668a', 'description': 'md5:bf77e03fcae5529475e500129b05668a',
'duration': 177, 'duration': 177,
'uploader': 'FlyingKitty', 'uploader': 'FlyingKitty',
'uploader_id': '@FlyingKitty900', 'uploader_id': 'FlyingKitty900',
'upload_date': '20200408', 'upload_date': '20200408',
'thumbnail': 'https://i.ytimg.com/vi/HsUATh_Nc2U/maxresdefault.jpg', 'thumbnail': 'https://i.ytimg.com/vi/HsUATh_Nc2U/maxresdefault.jpg',
'age_limit': 18, 'age_limit': 18,
@ -739,7 +682,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': 'md5:17eccca93a786d51bc67646756894066', 'description': 'md5:17eccca93a786d51bc67646756894066',
'duration': 106, 'duration': 106,
'uploader': 'Projekt Melody', 'uploader': 'Projekt Melody',
'uploader_id': '@ProjektMelody', 'uploader_id': 'UC1yoRdFoFJaCY-AGfD9W0wQ',
'upload_date': '20191227', 'upload_date': '20191227',
'age_limit': 18, 'age_limit': 18,
'thumbnail': 'https://i.ytimg.com/vi/Tq92D6wQ1mg/sddefault.jpg', 'thumbnail': 'https://i.ytimg.com/vi/Tq92D6wQ1mg/sddefault.jpg',
@ -761,10 +704,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'title': 'OOMPH! - Such Mich Find Mich (Lyrics)', 'title': 'OOMPH! - Such Mich Find Mich (Lyrics)',
'description': 'Fan Video. Music & Lyrics by OOMPH!.', 'description': 'Fan Video. Music & Lyrics by OOMPH!.',
'duration': 210, 'duration': 210,
'upload_date': '20130730',
'uploader': 'Herr Lurik', 'uploader': 'Herr Lurik',
'uploader_id': '@HerrLurik', 'uploader_id': 'st3in234',
'uploader_url': 'http://www.youtube.com/@HerrLurik', 'upload_date': '20130730',
'uploader_url': 'http://www.youtube.com/user/st3in234',
'age_limit': 0, 'age_limit': 0,
'thumbnail': 'https://i.ytimg.com/vi/MeJVWBSsPAY/hqdefault.jpg', 'thumbnail': 'https://i.ytimg.com/vi/MeJVWBSsPAY/hqdefault.jpg',
'tags': ['oomph', 'such mich find mich', 'lyrics', 'german industrial', 'musica industrial'], 'tags': ['oomph', 'such mich find mich', 'lyrics', 'german industrial', 'musica industrial'],
@ -797,8 +740,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'ext': 'mp4', 'ext': 'mp4',
'duration': 266, 'duration': 266,
'upload_date': '20100430', 'upload_date': '20100430',
'uploader_id': '@deadmau5', 'uploader_id': 'deadmau5',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@deadmau5', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/deadmau5',
'creator': 'deadmau5', 'creator': 'deadmau5',
'description': 'md5:6cbcd3a92ce1bc676fc4d6ab4ace2336', 'description': 'md5:6cbcd3a92ce1bc676fc4d6ab4ace2336',
'uploader': 'deadmau5', 'uploader': 'deadmau5',
@ -819,8 +762,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': r're:(?s)(?:.+\s)?HO09 - Women - GER-AUS - Hockey - 31 July 2012 - London 2012 Olympic Games\s*', 'description': r're:(?s)(?:.+\s)?HO09 - Women - GER-AUS - Hockey - 31 July 2012 - London 2012 Olympic Games\s*',
'duration': 6085, 'duration': 6085,
'upload_date': '20150827', 'upload_date': '20150827',
'uploader_id': '@Olympics', 'uploader_id': 'olympic',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@Olympics', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/olympic',
'uploader': r're:Olympics?', 'uploader': r're:Olympics?',
'age_limit': 0, 'age_limit': 0,
'thumbnail': 'https://i.ytimg.com/vi/lqQg6PlCWgI/maxresdefault.jpg', 'thumbnail': 'https://i.ytimg.com/vi/lqQg6PlCWgI/maxresdefault.jpg',
@ -842,8 +785,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'stretched_ratio': 16 / 9., 'stretched_ratio': 16 / 9.,
'duration': 85, 'duration': 85,
'upload_date': '20110310', 'upload_date': '20110310',
'uploader_id': '@AllenMeow', 'uploader_id': 'AllenMeow',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@AllenMeow', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/AllenMeow',
'description': 'made by Wacom from Korea | 字幕&加油添醋 by TY\'s Allen | 感謝heylisa00cavey1001同學熱情提供梗及翻譯', 'description': 'made by Wacom from Korea | 字幕&加油添醋 by TY\'s Allen | 感謝heylisa00cavey1001同學熱情提供梗及翻譯',
'uploader': '孫ᄋᄅ', 'uploader': '孫ᄋᄅ',
'title': '[A-made] 變態妍字幕版 太妍 我就是這樣的人', 'title': '[A-made] 變態妍字幕版 太妍 我就是這樣的人',
@ -881,7 +824,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'uploader': 'dorappi2000', 'uploader': 'dorappi2000',
'formats': 'mincount:31', 'formats': 'mincount:31',
}, },
'skip': 'not actual any more', 'skip': 'not actual anymore',
}, },
# DASH manifest with segment_list # DASH manifest with segment_list
{ {
@ -962,7 +905,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'params': { 'params': {
'skip_download': True, 'skip_download': True,
}, },
'skip': 'Not multifeed any more',
}, },
{ {
# Multifeed video with comma in title (see https://github.com/ytdl-org/youtube-dl/issues/8536) # Multifeed video with comma in title (see https://github.com/ytdl-org/youtube-dl/issues/8536)
@ -972,7 +914,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'title': 'DevConf.cz 2016 Day 2 Workshops 1 14:00 - 15:30', 'title': 'DevConf.cz 2016 Day 2 Workshops 1 14:00 - 15:30',
}, },
'playlist_count': 2, 'playlist_count': 2,
'skip': 'Not multifeed any more', 'skip': 'Not multifeed anymore',
}, },
{ {
'url': 'https://vid.plus/FlRa-iH7PGw', 'url': 'https://vid.plus/FlRa-iH7PGw',
@ -996,8 +938,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': 'md5:8085699c11dc3f597ce0410b0dcbb34a', 'description': 'md5:8085699c11dc3f597ce0410b0dcbb34a',
'duration': 133, 'duration': 133,
'upload_date': '20151119', 'upload_date': '20151119',
'uploader_id': '@IronSoulElf', 'uploader_id': 'IronSoulElf',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@IronSoulElf', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/IronSoulElf',
'uploader': 'IronSoulElf', 'uploader': 'IronSoulElf',
'creator': r're:Todd Haberman[;,]\s+Daniel Law Heath and Aaron Kaplan', 'creator': r're:Todd Haberman[;,]\s+Daniel Law Heath and Aaron Kaplan',
'track': 'Dark Walk', 'track': 'Dark Walk',
@ -1045,8 +987,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': 'md5:a677553cf0840649b731a3024aeff4cc', 'description': 'md5:a677553cf0840649b731a3024aeff4cc',
'duration': 721, 'duration': 721,
'upload_date': '20150127', 'upload_date': '20150127',
'uploader_id': '@BKCHarvard', 'uploader_id': 'BerkmanCenter',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@BKCHarvard', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/BerkmanCenter',
'uploader': 'The Berkman Klein Center for Internet & Society', 'uploader': 'The Berkman Klein Center for Internet & Society',
'license': 'Creative Commons Attribution license (reuse allowed)', 'license': 'Creative Commons Attribution license (reuse allowed)',
}, },
@ -1065,8 +1007,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'duration': 4060, 'duration': 4060,
'upload_date': '20151119', 'upload_date': '20151119',
'uploader': 'Bernie Sanders', 'uploader': 'Bernie Sanders',
'uploader_id': '@BernieSanders', 'uploader_id': 'UCH1dpzjCEiGAt8CXkryhkZg',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@BernieSanders', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UCH1dpzjCEiGAt8CXkryhkZg',
'license': 'Creative Commons Attribution license (reuse allowed)', 'license': 'Creative Commons Attribution license (reuse allowed)',
}, },
'params': { 'params': {
@ -1112,8 +1054,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'duration': 2085, 'duration': 2085,
'upload_date': '20170118', 'upload_date': '20170118',
'uploader': 'Vsauce', 'uploader': 'Vsauce',
'uploader_id': '@Vsauce', 'uploader_id': 'Vsauce',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@Vsauce', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/Vsauce',
'series': 'Mind Field', 'series': 'Mind Field',
'season_number': 1, 'season_number': 1,
'episode_number': 1, 'episode_number': 1,
@ -1192,7 +1134,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'skip_download': True, 'skip_download': True,
'youtube_include_dash_manifest': False, 'youtube_include_dash_manifest': False,
}, },
'skip': 'not actual any more', 'skip': 'not actual anymore',
}, },
{ {
# Youtube Music Auto-generated description # Youtube Music Auto-generated description
@ -1249,8 +1191,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'title': 'IMG 3456', 'title': 'IMG 3456',
'description': '', 'description': '',
'upload_date': '20170613', 'upload_date': '20170613',
'uploader_id': 'ElevageOrVert',
'uploader': 'ElevageOrVert', 'uploader': 'ElevageOrVert',
'uploader_id': '@ElevageOrVert',
}, },
'params': { 'params': {
'skip_download': True, 'skip_download': True,
@ -1268,8 +1210,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'title': 'Part 77 Sort a list of simple types in c#', 'title': 'Part 77 Sort a list of simple types in c#',
'description': 'md5:b8746fa52e10cdbf47997903f13b20dc', 'description': 'md5:b8746fa52e10cdbf47997903f13b20dc',
'upload_date': '20130831', 'upload_date': '20130831',
'uploader_id': 'kudvenkat',
'uploader': 'kudvenkat', 'uploader': 'kudvenkat',
'uploader_id': '@Csharp-video-tutorialsBlogspot',
}, },
'params': { 'params': {
'skip_download': True, 'skip_download': True,
@ -1321,8 +1263,8 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'description': 'md5:ea770e474b7cd6722b4c95b833c03630', 'description': 'md5:ea770e474b7cd6722b4c95b833c03630',
'upload_date': '20201120', 'upload_date': '20201120',
'uploader': 'Walk around Japan', 'uploader': 'Walk around Japan',
'uploader_id': '@walkaroundjapan7124', 'uploader_id': 'UC3o_t8PzBmXf5S9b7GLx1Mw',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@walkaroundjapan7124', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/channel/UC3o_t8PzBmXf5S9b7GLx1Mw',
}, },
'params': { 'params': {
'skip_download': True, 'skip_download': True,
@ -1334,11 +1276,11 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'info_dict': { 'info_dict': {
'id': '4L2J27mJ3Dc', 'id': '4L2J27mJ3Dc',
'ext': 'mp4', 'ext': 'mp4',
'title': 'Midwest Squid Game #Shorts',
'description': 'md5:976512b8a29269b93bbd8a61edc45a6d',
'upload_date': '20211025', 'upload_date': '20211025',
'uploader': 'Charlie Berens', 'uploader': 'Charlie Berens',
'uploader_id': '@CharlieBerens', 'description': 'md5:976512b8a29269b93bbd8a61edc45a6d',
'uploader_id': 'fivedlrmilkshake',
'title': 'Midwest Squid Game #Shorts',
}, },
'params': { 'params': {
'skip_download': True, 'skip_download': True,
@ -2146,19 +2088,25 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
thumbnails = [{'url': thumbnail}] thumbnails = [{'url': thumbnail}]
category = microformat.get('category') or search_meta('genre') category = microformat.get('category') or search_meta('genre')
channel_id = self._extract_channel_id( channel_id = video_details.get('channelId') \
webpage, videodetails=video_details, metadata=microformat) or microformat.get('externalChannelId') \
or search_meta('channelId')
duration = int_or_none( duration = int_or_none(
video_details.get('lengthSeconds') video_details.get('lengthSeconds')
or microformat.get('lengthSeconds')) \ or microformat.get('lengthSeconds')) \
or parse_duration(search_meta('duration')) or parse_duration(search_meta('duration'))
is_live = video_details.get('isLive') is_live = video_details.get('isLive')
owner_profile_url = self._yt_urljoin(self._extract_author_var( def gen_owner_profile_url():
webpage, 'url', videodetails=video_details, metadata=microformat)) yield microformat.get('ownerProfileUrl')
yield extract_attributes(self._search_regex(
r'''(?s)(<link\b[^>]+\bitemprop\s*=\s*("|')url\2[^>]*>)''',
get_element_by_attribute('itemprop', 'author', webpage),
'owner_profile_url', default='')).get('href')
uploader = self._extract_author_var( owner_profile_url = next(
webpage, 'name', videodetails=video_details, metadata=microformat) (x for x in map(url_or_none, gen_owner_profile_url()) if x),
None)
if not player_url: if not player_url:
player_url = self._extract_player_url(webpage) player_url = self._extract_player_url(webpage)
@ -2173,8 +2121,13 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
'upload_date': unified_strdate( 'upload_date': unified_strdate(
microformat.get('uploadDate') microformat.get('uploadDate')
or search_meta('uploadDate')), or search_meta('uploadDate')),
'uploader': uploader, 'uploader': video_details['author'],
'uploader_id': self._search_regex(
r'/(?:channel|user)/([^/?&#]+)', owner_profile_url,
'uploader id', fatal=False) if owner_profile_url else None,
'uploader_url': owner_profile_url,
'channel_id': channel_id, 'channel_id': channel_id,
'channel_url': 'https://www.youtube.com/channel/' + channel_id if channel_id else None,
'duration': duration, 'duration': duration,
'view_count': int_or_none( 'view_count': int_or_none(
video_details.get('viewCount') video_details.get('viewCount')
@ -2304,13 +2257,6 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
initial_data, initial_data,
lambda x: x['contents']['twoColumnWatchNextResults']['results']['results']['contents'], lambda x: x['contents']['twoColumnWatchNextResults']['results']['results']['contents'],
list) or [] list) or []
if not info['channel_id']:
channel_id = self._extract_channel_id('', renderers=contents)
if not info['uploader']:
info['uploader'] = self._extract_author_var('', 'name', renderers=contents)
if not owner_profile_url:
owner_profile_url = self._yt_urljoin(self._extract_author_var('', 'url', renderers=contents))
for content in contents: for content in contents:
vpir = content.get('videoPrimaryInfoRenderer') vpir = content.get('videoPrimaryInfoRenderer')
if vpir: if vpir:
@ -2358,6 +2304,10 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
}) })
vsir = content.get('videoSecondaryInfoRenderer') vsir = content.get('videoSecondaryInfoRenderer')
if vsir: if vsir:
info['channel'] = get_text(try_get(
vsir,
lambda x: x['owner']['videoOwnerRenderer']['title'],
dict))
rows = try_get( rows = try_get(
vsir, vsir,
lambda x: x['metadataRowContainer']['metadataRowContainerRenderer']['rows'], lambda x: x['metadataRowContainer']['metadataRowContainerRenderer']['rows'],
@ -2415,14 +2365,7 @@ class YoutubeIE(YoutubeBaseInfoExtractor):
self.mark_watched(video_id, player_response) self.mark_watched(video_id, player_response)
return merge_dicts( return info
info, {
'uploader_id': self._extract_uploader_id(owner_profile_url),
'uploader_url': owner_profile_url,
'channel_id': channel_id,
'channel_url': channel_id and self._yt_urljoin('/channel/' + channel_id),
'channel': info['uploader'],
})
class YoutubeTabIE(YoutubeBaseInfoExtractor): class YoutubeTabIE(YoutubeBaseInfoExtractor):
@ -2451,8 +2394,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'description': 'Short clips from Super Cooper Sundays!', 'description': 'Short clips from Super Cooper Sundays!',
'id': 'UCKMA8kHZ8bPYpnMNaUSxfEQ', 'id': 'UCKMA8kHZ8bPYpnMNaUSxfEQ',
'title': 'Super Cooper Shorts - Shorts', 'title': 'Super Cooper Shorts - Shorts',
'uploader': 'Super Cooper Shorts',
'uploader_id': '@SuperCooperShorts',
} }
}, { }, {
# Channel that does not have a Shorts tab. Test should just download videos on Home tab instead # Channel that does not have a Shorts tab. Test should just download videos on Home tab instead
@ -2463,17 +2404,14 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'title': 'Emergency Awesome - Home', 'title': 'Emergency Awesome - Home',
}, },
'playlist_mincount': 5, 'playlist_mincount': 5,
'skip': 'new test page needed to replace `Emergency Awesome - Shorts`',
}, { }, {
# playlists, multipage # playlists, multipage
'url': 'https://www.youtube.com/c/ИгорьКлейнер/playlists?view=1&flow=grid', 'url': 'https://www.youtube.com/c/ИгорьКлейнер/playlists?view=1&flow=grid',
'playlist_mincount': 94, 'playlist_mincount': 94,
'info_dict': { 'info_dict': {
'id': 'UCqj7Cz7revf5maW9g5pgNcg', 'id': 'UCqj7Cz7revf5maW9g5pgNcg',
'title': 'Igor Kleiner - Playlists', 'title': 'Игорь Клейнер - Playlists',
'description': 'md5:be97ee0f14ee314f1f002cf187166ee2', 'description': 'md5:be97ee0f14ee314f1f002cf187166ee2',
'uploader': 'Igor Kleiner',
'uploader_id': '@IgorDataScience',
}, },
}, { }, {
# playlists, multipage, different order # playlists, multipage, different order
@ -2481,10 +2419,8 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'playlist_mincount': 94, 'playlist_mincount': 94,
'info_dict': { 'info_dict': {
'id': 'UCqj7Cz7revf5maW9g5pgNcg', 'id': 'UCqj7Cz7revf5maW9g5pgNcg',
'title': 'Igor Kleiner - Playlists', 'title': 'Игорь Клейнер - Playlists',
'description': 'md5:be97ee0f14ee314f1f002cf187166ee2', 'description': 'md5:be97ee0f14ee314f1f002cf187166ee2',
'uploader': 'Igor Kleiner',
'uploader_id': '@IgorDataScience',
}, },
}, { }, {
# playlists, series # playlists, series
@ -2494,8 +2430,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCYO_jab_esuFRV4b17AJtAw', 'id': 'UCYO_jab_esuFRV4b17AJtAw',
'title': '3Blue1Brown - Playlists', 'title': '3Blue1Brown - Playlists',
'description': 'md5:e1384e8a133307dd10edee76e875d62f', 'description': 'md5:e1384e8a133307dd10edee76e875d62f',
'uploader': '3Blue1Brown',
'uploader_id': '@3blue1brown',
}, },
}, { }, {
# playlists, singlepage # playlists, singlepage
@ -2505,8 +2439,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCAEtajcuhQ6an9WEzY9LEMQ', 'id': 'UCAEtajcuhQ6an9WEzY9LEMQ',
'title': 'ThirstForScience - Playlists', 'title': 'ThirstForScience - Playlists',
'description': 'md5:609399d937ea957b0f53cbffb747a14c', 'description': 'md5:609399d937ea957b0f53cbffb747a14c',
'uploader': 'ThirstForScience',
'uploader_id': '@ThirstForScience',
} }
}, { }, {
'url': 'https://www.youtube.com/c/ChristophLaimer/playlists', 'url': 'https://www.youtube.com/c/ChristophLaimer/playlists',
@ -2515,22 +2447,20 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
# basic, single video playlist # basic, single video playlist
'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc', 'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
'info_dict': { 'info_dict': {
'uploader_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
'uploader': 'Sergey M.',
'id': 'PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc', 'id': 'PL4lCao7KL_QFVb7Iudeipvc2BCavECqzc',
'title': 'youtube-dl public playlist', 'title': 'youtube-dl public playlist',
'uploader': 'Sergey M.',
'uploader_id': '@sergeym.6173',
'channel_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
}, },
'playlist_count': 1, 'playlist_count': 1,
}, { }, {
# empty playlist # empty playlist
'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFodcLWhDpGCYnngnHtQ-Xf', 'url': 'https://www.youtube.com/playlist?list=PL4lCao7KL_QFodcLWhDpGCYnngnHtQ-Xf',
'info_dict': { 'info_dict': {
'uploader_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
'uploader': 'Sergey M.',
'id': 'PL4lCao7KL_QFodcLWhDpGCYnngnHtQ-Xf', 'id': 'PL4lCao7KL_QFodcLWhDpGCYnngnHtQ-Xf',
'title': 'youtube-dl empty playlist', 'title': 'youtube-dl empty playlist',
'uploader': 'Sergey M.',
'uploader_id': '@sergeym.6173',
'channel_id': 'UCmlqkdCBesrv2Lak1mF_MxA',
}, },
'playlist_count': 0, 'playlist_count': 0,
}, { }, {
@ -2540,8 +2470,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w', 'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'title': 'lex will - Home', 'title': 'lex will - Home',
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488', 'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
'uploader': 'lex will',
'uploader_id': '@lexwill718',
}, },
'playlist_mincount': 2, 'playlist_mincount': 2,
}, { }, {
@ -2551,8 +2479,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w', 'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'title': 'lex will - Videos', 'title': 'lex will - Videos',
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488', 'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
'uploader': 'lex will',
'uploader_id': '@lexwill718',
}, },
'playlist_mincount': 975, 'playlist_mincount': 975,
}, { }, {
@ -2562,8 +2488,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w', 'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'title': 'lex will - Videos', 'title': 'lex will - Videos',
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488', 'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
'uploader': 'lex will',
'uploader_id': '@lexwill718',
}, },
'playlist_mincount': 199, 'playlist_mincount': 199,
}, { }, {
@ -2573,8 +2497,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w', 'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'title': 'lex will - Playlists', 'title': 'lex will - Playlists',
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488', 'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
'uploader': 'lex will',
'uploader_id': '@lexwill718',
}, },
'playlist_mincount': 17, 'playlist_mincount': 17,
}, { }, {
@ -2584,8 +2506,6 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w', 'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'title': 'lex will - Community', 'title': 'lex will - Community',
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488', 'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
'uploader': 'lex will',
'uploader_id': '@lexwill718',
}, },
'playlist_mincount': 18, 'playlist_mincount': 18,
}, { }, {
@ -2595,10 +2515,8 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'id': 'UCKfVa3S1e4PHvxWcwyMMg8w', 'id': 'UCKfVa3S1e4PHvxWcwyMMg8w',
'title': 'lex will - Channels', 'title': 'lex will - Channels',
'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488', 'description': 'md5:2163c5d0ff54ed5f598d6a7e6211e488',
'uploader': 'lex will',
'uploader_id': '@lexwill718',
}, },
'playlist_mincount': 75, 'playlist_mincount': 138,
}, { }, {
'url': 'https://invidio.us/channel/UCmlqkdCBesrv2Lak1mF_MxA', 'url': 'https://invidio.us/channel/UCmlqkdCBesrv2Lak1mF_MxA',
'only_matching': True, 'only_matching': True,
@ -2615,8 +2533,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'title': '29C3: Not my department', 'title': '29C3: Not my department',
'id': 'PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC', 'id': 'PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC',
'uploader': 'Christiaan008', 'uploader': 'Christiaan008',
'uploader_id': '@ChRiStIaAn008', 'uploader_id': 'UCEPzS1rYsrkqzSLNp76nrcg',
'channel_id': 'UCEPzS1rYsrkqzSLNp76nrcg',
}, },
'playlist_count': 96, 'playlist_count': 96,
}, { }, {
@ -2626,8 +2543,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'title': 'Uploads from Cauchemar', 'title': 'Uploads from Cauchemar',
'id': 'UUBABnxM4Ar9ten8Mdjj1j0Q', 'id': 'UUBABnxM4Ar9ten8Mdjj1j0Q',
'uploader': 'Cauchemar', 'uploader': 'Cauchemar',
'uploader_id': '@Cauchemar89', 'uploader_id': 'UCBABnxM4Ar9ten8Mdjj1j0Q',
'channel_id': 'UCBABnxM4Ar9ten8Mdjj1j0Q',
}, },
'playlist_mincount': 1123, 'playlist_mincount': 1123,
}, { }, {
@ -2641,8 +2557,7 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'title': 'Uploads from Interstellar Movie', 'title': 'Uploads from Interstellar Movie',
'id': 'UUXw-G3eDE9trcvY2sBMM_aA', 'id': 'UUXw-G3eDE9trcvY2sBMM_aA',
'uploader': 'Interstellar Movie', 'uploader': 'Interstellar Movie',
'uploader_id': '@InterstellarMovie', 'uploader_id': 'UCXw-G3eDE9trcvY2sBMM_aA',
'channel_id': 'UCXw-G3eDE9trcvY2sBMM_aA',
}, },
'playlist_mincount': 21, 'playlist_mincount': 21,
}, { }, {
@ -2651,9 +2566,8 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
'info_dict': { 'info_dict': {
'title': 'Data Analysis with Dr Mike Pound', 'title': 'Data Analysis with Dr Mike Pound',
'id': 'PLzH6n4zXuckpfMu_4Ff8E7Z1behQks5ba', 'id': 'PLzH6n4zXuckpfMu_4Ff8E7Z1behQks5ba',
'uploader_id': 'UC9-y-6csu5WGm29I7JiwpnA',
'uploader': 'Computerphile', 'uploader': 'Computerphile',
'uploader_id': '@Computerphile',
'channel_id': 'UC9-y-6csu5WGm29I7JiwpnA',
}, },
'playlist_mincount': 11, 'playlist_mincount': 11,
}, { }, {
@ -2691,14 +2605,14 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
}, { }, {
'url': 'https://www.youtube.com/channel/UCoMdktPbSTixAyNGwb-UYkQ/live', 'url': 'https://www.youtube.com/channel/UCoMdktPbSTixAyNGwb-UYkQ/live',
'info_dict': { 'info_dict': {
'id': r're:[\da-zA-Z_-]{8,}', 'id': '9Auq9mYxFEE',
'ext': 'mp4', 'ext': 'mp4',
'title': r're:(?s)[A-Z].{20,}', 'title': 'Watch Sky News live',
'uploader': 'Sky News', 'uploader': 'Sky News',
'uploader_id': '@SkyNews', 'uploader_id': 'skynews',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@SkyNews', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/skynews',
'upload_date': r're:\d{8}', 'upload_date': '20191102',
'description': r're:(?s)(?:.*\n)+SUBSCRIBE to our YouTube channel for more videos: http://www\.youtube\.com/skynews *\n.*', 'description': 'md5:78de4e1c2359d0ea3ed829678e38b662',
'categories': ['News & Politics'], 'categories': ['News & Politics'],
'tags': list, 'tags': list,
'like_count': int, 'like_count': int,
@ -2787,22 +2701,34 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
}, { }, {
'note': 'Search tab', 'note': 'Search tab',
'url': 'https://www.youtube.com/c/3blue1brown/search?query=linear%20algebra', 'url': 'https://www.youtube.com/c/3blue1brown/search?query=linear%20algebra',
'playlist_mincount': 20, 'playlist_mincount': 40,
'info_dict': { 'info_dict': {
'id': 'UCYO_jab_esuFRV4b17AJtAw', 'id': 'UCYO_jab_esuFRV4b17AJtAw',
'title': '3Blue1Brown - Search - linear algebra', 'title': '3Blue1Brown - Search - linear algebra',
'description': 'md5:e1384e8a133307dd10edee76e875d62f', 'description': 'md5:e1384e8a133307dd10edee76e875d62f',
'uploader': '3Blue1Brown', 'uploader': '3Blue1Brown',
'uploader_id': '@3blue1brown', 'uploader_id': 'UCYO_jab_esuFRV4b17AJtAw',
'channel_id': 'UCYO_jab_esuFRV4b17AJtAw',
} }
}] }]
@classmethod @classmethod
def suitable(cls, url): def suitable(cls, url):
return not YoutubeIE.suitable(url) and super( return False if YoutubeIE.suitable(url) else super(
YoutubeTabIE, cls).suitable(url) YoutubeTabIE, cls).suitable(url)
def _extract_channel_id(self, webpage):
channel_id = self._html_search_meta(
'channelId', webpage, 'channel id', default=None)
if channel_id:
return channel_id
channel_url = self._html_search_meta(
('og:url', 'al:ios:url', 'al:android:url', 'al:web:url',
'twitter:url', 'twitter:app:url:iphone', 'twitter:app:url:ipad',
'twitter:app:url:googleplay'), webpage, 'channel url')
return self._search_regex(
r'https?://(?:www\.)?youtube\.com/channel/([^/?#&])+',
channel_url, 'channel id')
@staticmethod @staticmethod
def _extract_grid_item_renderer(item): def _extract_grid_item_renderer(item):
assert isinstance(item, dict) assert isinstance(item, dict)
@ -3190,18 +3116,27 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
else: else:
raise ExtractorError('Unable to find selected tab') raise ExtractorError('Unable to find selected tab')
def _extract_uploader(self, metadata, data): @staticmethod
def _extract_uploader(data):
uploader = {} uploader = {}
renderers = traverse_obj(data, sidebar_renderer = try_get(
('sidebar', 'playlistSidebarRenderer', 'items')) data, lambda x: x['sidebar']['playlistSidebarRenderer']['items'], list)
uploader['channel_id'] = self._extract_channel_id('', metadata=metadata, renderers=renderers) if sidebar_renderer:
uploader['uploader'] = ( for item in sidebar_renderer:
self._extract_author_var('', 'name', renderers=renderers) if not isinstance(item, dict):
or self._extract_author_var('', 'name', metadata=metadata)) continue
uploader['uploader_url'] = self._yt_urljoin( renderer = item.get('playlistSidebarSecondaryInfoRenderer')
self._extract_author_var('', 'url', metadata=metadata, renderers=renderers)) if not isinstance(renderer, dict):
uploader['uploader_id'] = self._extract_uploader_id(uploader['uploader_url']) continue
uploader['channel'] = uploader['uploader'] owner = try_get(
renderer, lambda x: x['videoOwner']['videoOwnerRenderer']['title']['runs'][0], dict)
if owner:
uploader['uploader'] = owner.get('text')
uploader['uploader_id'] = try_get(
owner, lambda x: x['navigationEndpoint']['browseEndpoint']['browseId'], compat_str)
uploader['uploader_url'] = urljoin(
'https://www.youtube.com/',
try_get(owner, lambda x: x['navigationEndpoint']['browseEndpoint']['canonicalBaseUrl'], compat_str))
return uploader return uploader
@staticmethod @staticmethod
@ -3252,7 +3187,8 @@ class YoutubeTabIE(YoutubeBaseInfoExtractor):
self._entries(selected_tab, item_id, webpage), self._entries(selected_tab, item_id, webpage),
playlist_id=playlist_id, playlist_title=title, playlist_id=playlist_id, playlist_title=title,
playlist_description=description) playlist_description=description)
return merge_dicts(playlist, self._extract_uploader(renderer, data)) playlist.update(self._extract_uploader(data))
return playlist
def _extract_from_playlist(self, item_id, url, data, playlist): def _extract_from_playlist(self, item_id, url, data, playlist):
title = playlist.get('title') or try_get( title = playlist.get('title') or try_get(
@ -3339,9 +3275,8 @@ class YoutubePlaylistIE(InfoExtractor):
'info_dict': { 'info_dict': {
'title': '[OLD]Team Fortress 2 (Class-based LP)', 'title': '[OLD]Team Fortress 2 (Class-based LP)',
'id': 'PLBB231211A4F62143', 'id': 'PLBB231211A4F62143',
'uploader': 'Wickman', 'uploader': 'Wickydoo',
'uploader_id': '@WickmanVT', 'uploader_id': 'UCKSpbfbl5kRQpTdL7kMc-1Q',
'channel_id': 'UCKSpbfbl5kRQpTdL7kMc-1Q',
}, },
'playlist_mincount': 29, 'playlist_mincount': 29,
}, { }, {
@ -3355,25 +3290,21 @@ class YoutubePlaylistIE(InfoExtractor):
}, { }, {
'note': 'embedded', 'note': 'embedded',
'url': 'https://www.youtube.com/embed/videoseries?list=PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 'url': 'https://www.youtube.com/embed/videoseries?list=PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu',
# TODO: full playlist requires _reload_with_unavailable_videos() 'playlist_count': 4,
# 'playlist_count': 4,
'playlist_mincount': 1,
'info_dict': { 'info_dict': {
'title': 'JODA15', 'title': 'JODA15',
'id': 'PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 'id': 'PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu',
'uploader': 'milan', 'uploader': 'milan',
'uploader_id': '@milan5503', 'uploader_id': 'UCEI1-PVPcYXjB73Hfelbmaw',
'channel_id': 'UCEI1-PVPcYXjB73Hfelbmaw',
} }
}, { }, {
'url': 'http://www.youtube.com/embed/_xDOZElKyNU?list=PLsyOSbh5bs16vubvKePAQ1x3PhKavfBIl', 'url': 'http://www.youtube.com/embed/_xDOZElKyNU?list=PLsyOSbh5bs16vubvKePAQ1x3PhKavfBIl',
'playlist_mincount': 455, 'playlist_mincount': 982,
'info_dict': { 'info_dict': {
'title': '2018 Chinese New Singles (11/6 updated)', 'title': '2018 Chinese New Singles (11/6 updated)',
'id': 'PLsyOSbh5bs16vubvKePAQ1x3PhKavfBIl', 'id': 'PLsyOSbh5bs16vubvKePAQ1x3PhKavfBIl',
'uploader': 'LBK', 'uploader': 'LBK',
'uploader_id': '@music_king', 'uploader_id': 'UC21nz3_MesPLqtDqwdvnoxA',
'channel_id': 'UC21nz3_MesPLqtDqwdvnoxA',
} }
}, { }, {
'url': 'TLGGrESM50VT6acwMjAyMjAxNw', 'url': 'TLGGrESM50VT6acwMjAyMjAxNw',
@ -3411,8 +3342,8 @@ class YoutubeYtBeIE(InfoExtractor):
'ext': 'mp4', 'ext': 'mp4',
'title': 'Small Scale Baler and Braiding Rugs', 'title': 'Small Scale Baler and Braiding Rugs',
'uploader': 'Backus-Page House Museum', 'uploader': 'Backus-Page House Museum',
'uploader_id': '@backuspagemuseum', 'uploader_id': 'backuspagemuseum',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/@backuspagemuseum', 'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/backuspagemuseum',
'upload_date': '20161008', 'upload_date': '20161008',
'description': 'md5:800c0c78d5eb128500bffd4f0b4f2e8a', 'description': 'md5:800c0c78d5eb128500bffd4f0b4f2e8a',
'categories': ['Nonprofits & Activism'], 'categories': ['Nonprofits & Activism'],