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