3 from __future__ 
import unicode_literals
 
  14 from .common 
import InfoExtractor
, SearchInfoExtractor
 
  15 from ..jsinterp 
import JSInterpreter
 
  16 from ..swfinterp 
import SWFInterpreter
 
  17 from ..compat 
import ( 
  20     compat_urllib_parse_unquote
, 
  21     compat_urllib_parse_unquote_plus
, 
  22     compat_urllib_parse_urlencode
, 
  23     compat_urllib_parse_urlparse
, 
  32     get_element_by_attribute
, 
  53 class YoutubeBaseInfoExtractor(InfoExtractor
): 
  54     """Provide base functions for Youtube extractors""" 
  55     _LOGIN_URL 
= 'https://accounts.google.com/ServiceLogin' 
  56     _TWOFACTOR_URL 
= 'https://accounts.google.com/signin/challenge' 
  57     _PASSWORD_CHALLENGE_URL 
= 'https://accounts.google.com/signin/challenge/sl/password' 
  58     _NETRC_MACHINE 
= 'youtube' 
  59     # If True it will raise an error if no login info is provided 
  60     _LOGIN_REQUIRED 
= False 
  62     def _set_language(self
): 
  64             '.youtube.com', 'PREF', 'f1=50000000&hl=en', 
  65             # YouTube sets the expire time to about two months 
  66             expire_time
=time
.time() + 2 * 30 * 24 * 3600) 
  68     def _ids_to_results(self
, ids
): 
  70             self
.url_result(vid_id
, 'Youtube', video_id
=vid_id
) 
  75         Attempt to log in to YouTube. 
  76         True is returned if successful or skipped. 
  77         False is returned if login failed. 
  79         If _LOGIN_REQUIRED is set and no authentication was provided, an error is raised. 
  81         (username
, password
) = self
._get
_login
_info
() 
  82         # No authentication to be performed 
  84             if self
._LOGIN
_REQUIRED
: 
  85                 raise ExtractorError('No login info available, needed for using %s.' % self
.IE_NAME
, expected
=True) 
  88         login_page 
= self
._download
_webpage
( 
  89             self
._LOGIN
_URL
, None, 
  90             note
='Downloading login page', 
  91             errnote
='unable to fetch login page', fatal
=False) 
  92         if login_page 
is False: 
  95         login_form 
= self
._hidden
_inputs
(login_page
) 
  98             'checkConnection': 'youtube', 
 103         login_results 
= self
._download
_webpage
( 
 104             self
._PASSWORD
_CHALLENGE
_URL
, None, 
 105             note
='Logging in', errnote
='unable to log in', fatal
=False, 
 106             data
=urlencode_postdata(login_form
)) 
 107         if login_results 
is False: 
 110         error_msg 
= self
._html
_search
_regex
( 
 111             r
'<[^>]+id="errormsg_0_Passwd"[^>]*>([^<]+)<', 
 112             login_results
, 'error message', default
=None) 
 114             raise ExtractorError('Unable to login: %s' % error_msg
, expected
=True) 
 116         if re
.search(r
'id="errormsg_0_Passwd"', login_results
) is not None: 
 117             raise ExtractorError('Please use your account password and a two-factor code instead of an application-specific password.', expected
=True) 
 120         # TODO add SMS and phone call support - these require making a request and then prompting the user 
 122         if re
.search(r
'(?i)<form[^>]+id="challenge"', login_results
) is not None: 
 123             tfa_code 
= self
._get
_tfa
_info
('2-step verification code') 
 126                 self
._downloader
.report_warning( 
 127                     'Two-factor authentication required. Provide it either interactively or with --twofactor <code>' 
 128                     '(Note that only TOTP (Google Authenticator App) codes work at this time.)') 
 131             tfa_code 
= remove_start(tfa_code
, 'G-') 
 133             tfa_form_strs 
= self
._form
_hidden
_inputs
('challenge', login_results
) 
 135             tfa_form_strs
.update({ 
 140             tfa_data 
= urlencode_postdata(tfa_form_strs
) 
 142             tfa_req 
= sanitized_Request(self
._TWOFACTOR
_URL
, tfa_data
) 
 143             tfa_results 
= self
._download
_webpage
( 
 145                 note
='Submitting TFA code', errnote
='unable to submit tfa', fatal
=False) 
 147             if tfa_results 
is False: 
 150             if re
.search(r
'(?i)<form[^>]+id="challenge"', tfa_results
) is not None: 
 151                 self
._downloader
.report_warning('Two-factor code expired or invalid. Please try again, or use a one-use backup code instead.') 
 153             if re
.search(r
'(?i)<form[^>]+id="gaia_loginform"', tfa_results
) is not None: 
 154                 self
._downloader
.report_warning('unable to log in - did the page structure change?') 
 156             if re
.search(r
'smsauth-interstitial-reviewsettings', tfa_results
) is not None: 
 157                 self
._downloader
.report_warning('Your Google account has a security notice. Please log in on your web browser, resolve the notice, and try again.') 
 160         if re
.search(r
'(?i)<form[^>]+id="gaia_loginform"', login_results
) is not None: 
 161             self
._downloader
.report_warning('unable to log in: bad username or password') 
 165     def _real_initialize(self
): 
 166         if self
._downloader 
is None: 
 169         if not self
._login
(): 
 173 class YoutubeEntryListBaseInfoExtractor(YoutubeBaseInfoExtractor
): 
 174     # Extract entries from page with "Load more" button 
 175     def _entries(self
, page
, playlist_id
): 
 176         more_widget_html 
= content_html 
= page
 
 177         for page_num 
in itertools
.count(1): 
 178             for entry 
in self
._process
_page
(content_html
): 
 181             mobj 
= re
.search(r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html
) 
 185             more 
= self
._download
_json
( 
 186                 'https://youtube.com/%s' % mobj
.group('more'), playlist_id
, 
 187                 'Downloading page #%s' % page_num
, 
 188                 transform_source
=uppercase_escape
) 
 189             content_html 
= more
['content_html'] 
 190             if not content_html
.strip(): 
 191                 # Some webpages show a "Load more" button but they don't 
 194             more_widget_html 
= more
['load_more_widget_html'] 
 197 class YoutubePlaylistBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor
): 
 198     def _process_page(self
, content
): 
 199         for video_id
, video_title 
in self
.extract_videos_from_page(content
): 
 200             yield self
.url_result(video_id
, 'Youtube', video_id
, video_title
) 
 202     def extract_videos_from_page(self
, page
): 
 205         for mobj 
in re
.finditer(self
._VIDEO
_RE
, page
): 
 206             # The link with index 0 is not the first video of the playlist (not sure if still actual) 
 207             if 'index' in mobj
.groupdict() and mobj
.group('id') == '0': 
 209             video_id 
= mobj
.group('id') 
 210             video_title 
= unescapeHTML(mobj
.group('title')) 
 212                 video_title 
= video_title
.strip() 
 214                 idx 
= ids_in_page
.index(video_id
) 
 215                 if video_title 
and not titles_in_page
[idx
]: 
 216                     titles_in_page
[idx
] = video_title
 
 218                 ids_in_page
.append(video_id
) 
 219                 titles_in_page
.append(video_title
) 
 220         return zip(ids_in_page
, titles_in_page
) 
 223 class YoutubePlaylistsBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor
): 
 224     def _process_page(self
, content
): 
 225         for playlist_id 
in orderedSet(re
.findall( 
 226                 r
'<h3[^>]+class="[^"]*yt-lockup-title[^"]*"[^>]*><a[^>]+href="/?playlist\?list=([0-9A-Za-z-_]{10,})"', 
 228             yield self
.url_result( 
 229                 'https://www.youtube.com/playlist?list=%s' % playlist_id
, 'YoutubePlaylist') 
 231     def _real_extract(self
, url
): 
 232         playlist_id 
= self
._match
_id
(url
) 
 233         webpage 
= self
._download
_webpage
(url
, playlist_id
) 
 234         title 
= self
._og
_search
_title
(webpage
, fatal
=False) 
 235         return self
.playlist_result(self
._entries
(webpage
, playlist_id
), playlist_id
, title
) 
 238 class YoutubeIE(YoutubeBaseInfoExtractor
): 
 239     IE_DESC 
= 'YouTube.com' 
 240     _VALID_URL 
= r
"""(?x)^ 
 242                          (?:https?://|//)                                    # http(s):// or protocol-independent URL 
 243                          (?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/| 
 244                             (?:www\.)?deturl\.com/www\.youtube\.com/| 
 245                             (?:www\.)?pwnyoutube\.com/| 
 246                             (?:www\.)?yourepeat\.com/| 
 247                             tube\.majestyc\.net/| 
 248                             youtube\.googleapis\.com/)                        # the various hostnames, with wildcard subdomains 
 249                          (?:.*?\#/)?                                          # handle anchor (#/) redirect urls 
 250                          (?:                                                  # the various things that can precede the ID: 
 251                              (?:(?:v|embed|e)/(?!videoseries))                # v/ or embed/ or e/ 
 252                              |(?:                                             # or the v= param in all its forms 
 253                                  (?:(?:watch|movie)(?:_popup)?(?:\.php)?/?)?  # preceding watch(_popup|.php) or nothing (like /?v=xxxx) 
 254                                  (?:\?|\#!?)                                  # the params delimiter ? or # or #! 
 255                                  (?:.*?[&;])??                                # any other preceding param (like /?s=tuff&v=xxxx or ?s=tuff&v=V36LpHqtcDY) 
 260                             youtu\.be|                                        # just youtu.be/xxxx 
 261                             vid\.plus|                                        # or vid.plus/xxxx 
 262                             zwearz\.com/watch|                                # or zwearz.com/watch/xxxx 
 264                          |(?:www\.)?cleanvideosearch\.com/media/action/yt/watch\?videoId= 
 266                      )?                                                       # all until now is optional -> you can pass the naked ID 
 267                      ([0-9A-Za-z_-]{11})                                      # here is it! the YouTube video ID 
 268                      (?!.*?\blist=)                                            # combined list/video URLs are handled by the playlist IE 
 269                      (?(1).+)?                                                # if we found the ID, everything can follow 
 271     _NEXT_URL_RE 
= r
'[\?&]next_url=([^&]+)' 
 273         '5': {'ext': 'flv', 'width': 400, 'height': 240, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'}, 
 274         '6': {'ext': 'flv', 'width': 450, 'height': 270, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'}, 
 275         '13': {'ext': '3gp', 'acodec': 'aac', 'vcodec': 'mp4v'}, 
 276         '17': {'ext': '3gp', 'width': 176, 'height': 144, 'acodec': 'aac', 'abr': 24, 'vcodec': 'mp4v'}, 
 277         '18': {'ext': 'mp4', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 96, 'vcodec': 'h264'}, 
 278         '22': {'ext': 'mp4', 'width': 1280, 'height': 720, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'}, 
 279         '34': {'ext': 'flv', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 280         '35': {'ext': 'flv', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 281         # itag 36 videos are either 320x180 (BaW_jenozKc) or 320x240 (__2ABJjxzNo), abr varies as well 
 282         '36': {'ext': '3gp', 'width': 320, 'acodec': 'aac', 'vcodec': 'mp4v'}, 
 283         '37': {'ext': 'mp4', 'width': 1920, 'height': 1080, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'}, 
 284         '38': {'ext': 'mp4', 'width': 4096, 'height': 3072, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'}, 
 285         '43': {'ext': 'webm', 'width': 640, 'height': 360, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'}, 
 286         '44': {'ext': 'webm', 'width': 854, 'height': 480, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'}, 
 287         '45': {'ext': 'webm', 'width': 1280, 'height': 720, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'}, 
 288         '46': {'ext': 'webm', 'width': 1920, 'height': 1080, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'}, 
 289         '59': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 290         '78': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 294         '82': {'ext': 'mp4', 'height': 360, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20}, 
 295         '83': {'ext': 'mp4', 'height': 480, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20}, 
 296         '84': {'ext': 'mp4', 'height': 720, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20}, 
 297         '85': {'ext': 'mp4', 'height': 1080, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20}, 
 298         '100': {'ext': 'webm', 'height': 360, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8', 'preference': -20}, 
 299         '101': {'ext': 'webm', 'height': 480, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20}, 
 300         '102': {'ext': 'webm', 'height': 720, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20}, 
 302         # Apple HTTP Live Streaming 
 303         '91': {'ext': 'mp4', 'height': 144, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10}, 
 304         '92': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10}, 
 305         '93': {'ext': 'mp4', 'height': 360, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10}, 
 306         '94': {'ext': 'mp4', 'height': 480, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10}, 
 307         '95': {'ext': 'mp4', 'height': 720, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10}, 
 308         '96': {'ext': 'mp4', 'height': 1080, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10}, 
 309         '132': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10}, 
 310         '151': {'ext': 'mp4', 'height': 72, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 24, 'vcodec': 'h264', 'preference': -10}, 
 313         '133': {'ext': 'mp4', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 314         '134': {'ext': 'mp4', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 315         '135': {'ext': 'mp4', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 316         '136': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 317         '137': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 318         '138': {'ext': 'mp4', 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40},  # Height can vary (https://github.com/rg3/youtube-dl/issues/4559) 
 319         '160': {'ext': 'mp4', 'height': 144, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 320         '212': {'ext': 'mp4', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 321         '264': {'ext': 'mp4', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 322         '298': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60, 'preference': -40}, 
 323         '299': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60, 'preference': -40}, 
 324         '266': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 327         '139': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 48, 'preference': -50, 'container': 'm4a_dash'}, 
 328         '140': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 128, 'preference': -50, 'container': 'm4a_dash'}, 
 329         '141': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 256, 'preference': -50, 'container': 'm4a_dash'}, 
 330         '256': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'preference': -50, 'container': 'm4a_dash'}, 
 331         '258': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'preference': -50, 'container': 'm4a_dash'}, 
 332         '325': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'dtse', 'preference': -50, 'container': 'm4a_dash'}, 
 333         '328': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'ec-3', 'preference': -50, 'container': 'm4a_dash'}, 
 336         '167': {'ext': 'webm', 'height': 360, 'width': 640, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 337         '168': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 338         '169': {'ext': 'webm', 'height': 720, 'width': 1280, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 339         '170': {'ext': 'webm', 'height': 1080, 'width': 1920, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 340         '218': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 341         '219': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 342         '278': {'ext': 'webm', 'height': 144, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp9', 'preference': -40}, 
 343         '242': {'ext': 'webm', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 344         '243': {'ext': 'webm', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 345         '244': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 346         '245': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 347         '246': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 348         '247': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 349         '248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 350         '271': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 351         # itag 272 videos are either 3840x2160 (e.g. RtoitU2A-3E) or 7680x4320 (sLprVF6d7Ug) 
 352         '272': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 353         '302': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 354         '303': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 355         '308': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 356         '313': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 357         '315': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 360         '171': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 128, 'preference': -50}, 
 361         '172': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 256, 'preference': -50}, 
 363         # Dash webm audio with opus inside 
 364         '249': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 50, 'preference': -50}, 
 365         '250': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 70, 'preference': -50}, 
 366         '251': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 160, 'preference': -50}, 
 369         '_rtmp': {'protocol': 'rtmp'}, 
 371     _SUBTITLE_FORMATS 
= ('ttml', 'vtt') 
 376             'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&t=1s&end=9', 
 380                 'title': 'youtube-dl test video "\'/\\Ƥāš', 
 381                 'uploader': 'Philipp Hagemeister', 
 382                 'uploader_id': 'phihag', 
 383                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/phihag', 
 384                 'upload_date': '20121002', 
 385                 'license': 'Standard YouTube License', 
 386                 '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 .', 
 387                 'categories': ['Science & Technology'], 
 388                 'tags': ['youtube-dl'], 
 391                 'dislike_count': int, 
 397             'url': 'https://www.youtube.com/watch?v=UxxajLWwzqY', 
 398             'note': 'Test generic use_cipher_signature video (#897)', 
 402                 'upload_date': '20120506', 
 403                 'title': 'Icona Pop - I Love It (feat. Charli XCX) [OFFICIAL VIDEO]', 
 404                 'alt_title': 'I Love It (feat. Charli XCX)', 
 405                 'description': 'md5:f3ceb5ef83a08d95b9d146f973157cc8', 
 406                 'tags': ['Icona Pop i love it', 'sweden', 'pop music', 'big beat records', 'big beat', 'charli', 
 407                          'xcx', 'charli xcx', 'girls', 'hbo', 'i love it', "i don't care", 'icona', 'pop', 
 408                          'iconic ep', 'iconic', 'love', 'it'], 
 410                 'uploader': 'Icona Pop', 
 411                 'uploader_id': 'IconaPop', 
 412                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/IconaPop', 
 413                 'license': 'Standard YouTube License', 
 414                 'creator': 'Icona Pop', 
 418             'url': 'https://www.youtube.com/watch?v=07FYdnEawAQ', 
 419             'note': 'Test VEVO video with age protection (#956)', 
 423                 'upload_date': '20130703', 
 424                 'title': 'Justin Timberlake - Tunnel Vision (Explicit)', 
 425                 'alt_title': 'Tunnel Vision', 
 426                 'description': 'md5:64249768eec3bc4276236606ea996373', 
 428                 'uploader': 'justintimberlakeVEVO', 
 429                 'uploader_id': 'justintimberlakeVEVO', 
 430                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/justintimberlakeVEVO', 
 431                 'license': 'Standard YouTube License', 
 432                 'creator': 'Justin Timberlake', 
 437             'url': '//www.YouTube.com/watch?v=yZIXLfi8CZQ', 
 438             'note': 'Embed-only video (#1746)', 
 442                 'upload_date': '20120608', 
 443                 'title': 'Principal Sexually Assaults A Teacher - Episode 117 - 8th June 2012', 
 444                 'description': 'md5:09b78bd971f1e3e289601dfba15ca4f7', 
 445                 'uploader': 'SET India', 
 446                 'uploader_id': 'setindia', 
 447                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/setindia', 
 448                 'license': 'Standard YouTube License', 
 453             'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&v=UxxajLWwzqY', 
 454             'note': 'Use the first video ID in the URL', 
 458                 'title': 'youtube-dl test video "\'/\\Ƥāš', 
 459                 'uploader': 'Philipp Hagemeister', 
 460                 'uploader_id': 'phihag', 
 461                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/phihag', 
 462                 'upload_date': '20121002', 
 463                 'license': 'Standard YouTube License', 
 464                 '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 .', 
 465                 'categories': ['Science & Technology'], 
 466                 'tags': ['youtube-dl'], 
 469                 'dislike_count': int, 
 472                 'skip_download': True, 
 476             'url': 'https://www.youtube.com/watch?v=a9LDPn-MO4I', 
 477             'note': '256k DASH audio (format 141) via DASH manifest', 
 481                 'upload_date': '20121002', 
 482                 'uploader_id': '8KVIDEO', 
 483                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/8KVIDEO', 
 485                 'uploader': '8KVIDEO', 
 486                 'license': 'Standard YouTube License', 
 487                 'title': 'UHDTV TEST 8K VIDEO.mp4' 
 490                 'youtube_include_dash_manifest': True, 
 493             'skip': 'format 141 not served anymore', 
 495         # DASH manifest with encrypted signature 
 497             'url': 'https://www.youtube.com/watch?v=IB3lcPjvWLA', 
 501                 'title': 'Afrojack, Spree Wilson - The Spark ft. Spree Wilson', 
 502                 'description': 'md5:12e7067fa6735a77bdcbb58cb1187d2d', 
 504                 'uploader': 'AfrojackVEVO', 
 505                 'uploader_id': 'AfrojackVEVO', 
 506                 'upload_date': '20131011', 
 507                 'license': 'Standard YouTube License', 
 510                 'youtube_include_dash_manifest': True, 
 511                 'format': '141/bestaudio[ext=m4a]', 
 514         # JS player signature function name containing $ 
 516             'url': 'https://www.youtube.com/watch?v=nfWlot6h_JM', 
 520                 'title': 'Taylor Swift - Shake It Off', 
 521                 'alt_title': 'Shake It Off', 
 522                 'description': 'md5:95f66187cd7c8b2c13eb78e1223b63c3', 
 524                 'uploader': 'TaylorSwiftVEVO', 
 525                 'uploader_id': 'TaylorSwiftVEVO', 
 526                 'upload_date': '20140818', 
 527                 'license': 'Standard YouTube License', 
 528                 'creator': 'Taylor Swift', 
 531                 'youtube_include_dash_manifest': True, 
 532                 'format': '141/bestaudio[ext=m4a]', 
 537             'url': 'https://www.youtube.com/watch?v=T4XJQO3qol8', 
 542                 'upload_date': '20100909', 
 543                 'uploader': 'The Amazing Atheist', 
 544                 'uploader_id': 'TheAmazingAtheist', 
 545                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/TheAmazingAtheist', 
 546                 'license': 'Standard YouTube License', 
 547                 'title': 'Burning Everyone\'s Koran', 
 548                 'description': 'SUBSCRIBE: http://www.youtube.com/saturninefilms\n\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', 
 551         # Normal age-gate video (No vevo, embed allowed) 
 553             'url': 'https://youtube.com/watch?v=HtVdAasjOgU', 
 557                 'title': 'The Witcher 3: Wild Hunt - The Sword Of Destiny Trailer', 
 558                 'description': r
're:(?s).{100,}About the Game\n.*?The Witcher 3: Wild Hunt.{100,}', 
 560                 'uploader': 'The Witcher', 
 561                 'uploader_id': 'WitcherGame', 
 562                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/WitcherGame', 
 563                 'upload_date': '20140605', 
 564                 'license': 'Standard YouTube License', 
 568         # Age-gate video with encrypted signature 
 570             'url': 'https://www.youtube.com/watch?v=6kLq3WMV1nU', 
 574                 'title': 'Dedication To My Ex (Miss That) (Lyric Video)', 
 575                 'description': 'md5:33765bb339e1b47e7e72b5490139bb41', 
 577                 'uploader': 'LloydVEVO', 
 578                 'uploader_id': 'LloydVEVO', 
 579                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/LloydVEVO', 
 580                 'upload_date': '20110629', 
 581                 'license': 'Standard YouTube License', 
 585         # video_info is None (https://github.com/rg3/youtube-dl/issues/4421) 
 587             'url': '__2ABJjxzNo', 
 592                 'upload_date': '20100430', 
 593                 'uploader_id': 'deadmau5', 
 594                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/deadmau5', 
 595                 'creator': 'deadmau5', 
 596                 'description': 'md5:12c56784b8032162bb936a5f76d55360', 
 597                 'uploader': 'deadmau5', 
 598                 'license': 'Standard YouTube License', 
 599                 'title': 'Deadmau5 - Some Chords (HD)', 
 600                 'alt_title': 'Some Chords', 
 602             'expected_warnings': [ 
 603                 'DASH manifest missing', 
 606         # Olympics (https://github.com/rg3/youtube-dl/issues/4431) 
 608             'url': 'lqQg6PlCWgI', 
 613                 'upload_date': '20150827', 
 614                 'uploader_id': 'olympic', 
 615                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/olympic', 
 616                 'license': 'Standard YouTube License', 
 617                 'description': 'HO09  - Women -  GER-AUS - Hockey - 31 July 2012 - London 2012 Olympic Games', 
 618                 'uploader': 'Olympic', 
 619                 'title': 'Hockey - Women -  GER-AUS - London 2012 Olympic Games', 
 622                 'skip_download': 'requires avconv', 
 627             'url': 'https://www.youtube.com/watch?v=_b-2C3KPAM0', 
 631                 'stretched_ratio': 16 / 9., 
 633                 'upload_date': '20110310', 
 634                 'uploader_id': 'AllenMeow', 
 635                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/AllenMeow', 
 636                 'description': 'made by Wacom from Korea | åå¹&å ę²¹ę·»é by TY\'s Allen | ęč¬heylisa00cavey1001ååøē±ę
ęä¾ę¢åēæ»čÆ', 
 637                 'uploader': 'å«č¾å«', 
 638                 'license': 'Standard YouTube License', 
 639                 'title': '[A-made] č®ę
å¦åå¹ē å¤Ŗå¦ ęå°±ęÆé樣ēäŗŗ', 
 642         # url_encoded_fmt_stream_map is empty string 
 644             'url': 'qEJwOuvDf7I', 
 648                 'title': 'ŠŠ±ŃŃŠ¶Š“ение ŃŃŠ“ебной ŠæŃŠ°ŠŗŃŠøŠŗŠø по Š²ŃбоŃам 14 ŃŠµŠ½ŃŃŠ±ŃŃ 2014 гоГа в ДанкŃ-ŠŠµŃŠµŃŠ±ŃŃŠ³Šµ', 
 650                 'upload_date': '20150404', 
 651                 'uploader_id': 'spbelect', 
 652                 'uploader': 'ŠŠ°Š±Š»ŃŠ“Š°ŃŠµŠ»Šø ŠŠµŃŠµŃŠ±ŃŃŠ³Š°', 
 655                 'skip_download': 'requires avconv', 
 657             'skip': 'This live event has ended.', 
 659         # Extraction from multiple DASH manifests (https://github.com/rg3/youtube-dl/pull/6097) 
 661             'url': 'https://www.youtube.com/watch?v=FIl7x6_3R5Y', 
 665                 'title': 'md5:7b81415841e02ecd4313668cde88737a', 
 666                 'description': 'md5:116377fd2963b81ec4ce64b542173306', 
 668                 'upload_date': '20150625', 
 669                 'uploader_id': 'dorappi2000', 
 670                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/dorappi2000', 
 671                 'uploader': 'dorappi2000', 
 672                 'license': 'Standard YouTube License', 
 673                 'formats': 'mincount:32', 
 676         # DASH manifest with segment_list 
 678             'url': 'https://www.youtube.com/embed/CsmdDsKjzN8', 
 679             'md5': '8ce563a1d667b599d21064e982ab9e31', 
 683                 'upload_date': '20150501',  # According to '<meta itemprop="datePublished"', but in other places it's 20150510 
 684                 'uploader': 'Airtek', 
 685                 'description': 'Retransmisión en directo de la XVIII media maratón de Zaragoza.', 
 686                 'uploader_id': 'UCzTzUmjXxxacNnL8I3m4LnQ', 
 687                 'license': 'Standard YouTube License', 
 688                 'title': 'Retransmisión XVIII Media maratón Zaragoza 2015', 
 691                 'youtube_include_dash_manifest': True, 
 692                 'format': '135',  # bestvideo 
 694             'skip': 'This live event has ended.', 
 697             # Multifeed videos (multiple cameras), URL is for Main Camera 
 698             'url': 'https://www.youtube.com/watch?v=jqWvoWXjCVs', 
 701                 'title': 'teamPGP: Rocket League Noob Stream', 
 702                 'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 708                     'title': 'teamPGP: Rocket League Noob Stream (Main Camera)', 
 709                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 711                     'upload_date': '20150721', 
 712                     'uploader': 'Beer Games Beer', 
 713                     'uploader_id': 'beergamesbeer', 
 714                     'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 715                     'license': 'Standard YouTube License', 
 721                     'title': 'teamPGP: Rocket League Noob Stream (kreestuh)', 
 722                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 724                     'upload_date': '20150721', 
 725                     'uploader': 'Beer Games Beer', 
 726                     'uploader_id': 'beergamesbeer', 
 727                     'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 728                     'license': 'Standard YouTube License', 
 734                     'title': 'teamPGP: Rocket League Noob Stream (grizzle)', 
 735                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 737                     'upload_date': '20150721', 
 738                     'uploader': 'Beer Games Beer', 
 739                     'uploader_id': 'beergamesbeer', 
 740                     'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 741                     'license': 'Standard YouTube License', 
 747                     'title': 'teamPGP: Rocket League Noob Stream (zim)', 
 748                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 750                     'upload_date': '20150721', 
 751                     'uploader': 'Beer Games Beer', 
 752                     'uploader_id': 'beergamesbeer', 
 753                     'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 754                     'license': 'Standard YouTube License', 
 758                 'skip_download': True, 
 762             # Multifeed video with comma in title (see https://github.com/rg3/youtube-dl/issues/8536) 
 763             'url': 'https://www.youtube.com/watch?v=gVfLd0zydlo', 
 766                 'title': 'DevConf.cz 2016 Day 2 Workshops 1 14:00 - 15:30', 
 769             'skip': 'Not multifeed anymore', 
 772             'url': 'https://vid.plus/FlRa-iH7PGw', 
 773             'only_matching': True, 
 776             'url': 'https://zwearz.com/watch/9lWxNJF-ufM/electra-woman-dyna-girl-official-trailer-grace-helbig.html', 
 777             'only_matching': True, 
 780             # Title with JS-like syntax "};" (see https://github.com/rg3/youtube-dl/issues/7468) 
 781             # Also tests cut-off URL expansion in video description (see 
 782             # https://github.com/rg3/youtube-dl/issues/1892, 
 783             # https://github.com/rg3/youtube-dl/issues/8164) 
 784             'url': 'https://www.youtube.com/watch?v=lsguqyKfVQg', 
 788                 'title': '{dark walk}; Loki/AC/Dishonored; collab w/Elflover21', 
 789                 'alt_title': 'Dark Walk', 
 790                 'description': 'md5:8085699c11dc3f597ce0410b0dcbb34a', 
 792                 'upload_date': '20151119', 
 793                 'uploader_id': 'IronSoulElf', 
 794                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/IronSoulElf', 
 795                 'uploader': 'IronSoulElf', 
 796                 'license': 'Standard YouTube License', 
 797                 'creator': 'Todd Haberman, Daniel Law Heath & Aaron Kaplan', 
 800                 'skip_download': True, 
 804             # Tags with '};' (see https://github.com/rg3/youtube-dl/issues/7468) 
 805             'url': 'https://www.youtube.com/watch?v=Ms7iBXnlUO8', 
 806             'only_matching': True, 
 809             # Video with yt:stretch=17:0 
 810             'url': 'https://www.youtube.com/watch?v=Q39EVAstoRM', 
 814                 'title': 'Clash Of Clans#14 Dicas De Ataque Para CV 4', 
 815                 'description': 'md5:ee18a25c350637c8faff806845bddee9', 
 816                 'upload_date': '20151107', 
 817                 'uploader_id': 'UCCr7TALkRbo3EtFzETQF1LA', 
 818                 'uploader': 'CH GAMER DROID', 
 821                 'skip_download': True, 
 823             'skip': 'This video does not exist.', 
 826             # Video licensed under Creative Commons 
 827             'url': 'https://www.youtube.com/watch?v=M4gD1WSo5mA', 
 831                 'title': 'md5:e41008789470fc2533a3252216f1c1d1', 
 832                 'description': 'md5:a677553cf0840649b731a3024aeff4cc', 
 834                 'upload_date': '20150127', 
 835                 'uploader_id': 'BerkmanCenter', 
 836                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/BerkmanCenter', 
 837                 'uploader': 'The Berkman Klein Center for Internet & Society', 
 838                 'license': 'Creative Commons Attribution license (reuse allowed)', 
 841                 'skip_download': True, 
 845             # Channel-like uploader_url 
 846             'url': 'https://www.youtube.com/watch?v=eQcmzGIKrzg', 
 850                 'title': 'Democratic Socialism and Foreign Policy | Bernie Sanders', 
 851                 'description': 'md5:dda0d780d5a6e120758d1711d062a867', 
 853                 'upload_date': '20151119', 
 854                 'uploader': 'Bernie 2016', 
 855                 'uploader_id': 'UCH1dpzjCEiGAt8CXkryhkZg', 
 856                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/channel/UCH1dpzjCEiGAt8CXkryhkZg', 
 857                 'license': 'Creative Commons Attribution license (reuse allowed)', 
 860                 'skip_download': True, 
 864             'url': 'https://www.youtube.com/watch?feature=player_embedded&amp;v=V36LpHqtcDY', 
 865             'only_matching': True, 
 868             # YouTube Red paid video (https://github.com/rg3/youtube-dl/issues/10059) 
 869             'url': 'https://www.youtube.com/watch?v=i1Ko8UG-Tdo', 
 870             'only_matching': True, 
 873             # Rental video preview 
 874             'url': 'https://www.youtube.com/watch?v=yYr8q0y5Jfg', 
 878                 'title': 'Piku - Trailer', 
 879                 'description': 'md5:c36bd60c3fd6f1954086c083c72092eb', 
 880                 'upload_date': '20150811', 
 881                 'uploader': 'FlixMatrix', 
 882                 'uploader_id': 'FlixMatrixKaravan', 
 883                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/FlixMatrixKaravan', 
 884                 'license': 'Standard YouTube License', 
 887                 'skip_download': True, 
 891             # YouTube Red video with episode data 
 892             'url': 'https://www.youtube.com/watch?v=iqKdEhx-dD4', 
 896                 'title': 'Isolation - Mind Field (Ep 1)', 
 897                 'description': 'md5:8013b7ddea787342608f63a13ddc9492', 
 899                 'upload_date': '20170118', 
 900                 'uploader': 'Vsauce', 
 901                 'uploader_id': 'Vsauce', 
 902                 'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/Vsauce', 
 903                 'license': 'Standard YouTube License', 
 904                 'series': 'Mind Field', 
 909                 'skip_download': True, 
 911             'expected_warnings': [ 
 912                 'Skipping DASH manifest', 
 917             'url': '1t24XAntNCY', 
 918             'only_matching': True, 
 922     def __init__(self
, *args
, **kwargs
): 
 923         super(YoutubeIE
, self
).__init
__(*args
, **kwargs
) 
 924         self
._player
_cache 
= {} 
 926     def report_video_info_webpage_download(self
, video_id
): 
 927         """Report attempt to download video info webpage.""" 
 928         self
.to_screen('%s: Downloading video info webpage' % video_id
) 
 930     def report_information_extraction(self
, video_id
): 
 931         """Report attempt to extract video information.""" 
 932         self
.to_screen('%s: Extracting video information' % video_id
) 
 934     def report_unavailable_format(self
, video_id
, format
): 
 935         """Report extracted video URL.""" 
 936         self
.to_screen('%s: Format %s not available' % (video_id
, format
)) 
 938     def report_rtmp_download(self
): 
 939         """Indicate the download will use the RTMP protocol.""" 
 940         self
.to_screen('RTMP download detected') 
 942     def _signature_cache_id(self
, example_sig
): 
 943         """ Return a string representation of a signature """ 
 944         return '.'.join(compat_str(len(part
)) for part 
in example_sig
.split('.')) 
 946     def _extract_signature_function(self
, video_id
, player_url
, example_sig
): 
 948             r
'.*?-(?P<id>[a-zA-Z0-9_-]+)(?:/watch_as3|/html5player(?:-new)?|/base)?\.(?P<ext>[a-z]+)$', 
 951             raise ExtractorError('Cannot identify player %r' % player_url
) 
 952         player_type 
= id_m
.group('ext') 
 953         player_id 
= id_m
.group('id') 
 955         # Read from filesystem cache 
 956         func_id 
= '%s_%s_%s' % ( 
 957             player_type
, player_id
, self
._signature
_cache
_id
(example_sig
)) 
 958         assert os
.path
.basename(func_id
) == func_id
 
 960         cache_spec 
= self
._downloader
.cache
.load('youtube-sigfuncs', func_id
) 
 961         if cache_spec 
is not None: 
 962             return lambda s
: ''.join(s
[i
] for i 
in cache_spec
) 
 965             'Downloading player %s' % player_url
 
 966             if self
._downloader
.params
.get('verbose') else 
 967             'Downloading %s player %s' % (player_type
, player_id
) 
 969         if player_type 
== 'js': 
 970             code 
= self
._download
_webpage
( 
 971                 player_url
, video_id
, 
 973                 errnote
='Download of %s failed' % player_url
) 
 974             res 
= self
._parse
_sig
_js
(code
) 
 975         elif player_type 
== 'swf': 
 976             urlh 
= self
._request
_webpage
( 
 977                 player_url
, video_id
, 
 979                 errnote
='Download of %s failed' % player_url
) 
 981             res 
= self
._parse
_sig
_swf
(code
) 
 983             assert False, 'Invalid player type %r' % player_type
 
 985         test_string 
= ''.join(map(compat_chr
, range(len(example_sig
)))) 
 986         cache_res 
= res(test_string
) 
 987         cache_spec 
= [ord(c
) for c 
in cache_res
] 
 989         self
._downloader
.cache
.store('youtube-sigfuncs', func_id
, cache_spec
) 
 992     def _print_sig_code(self
, func
, example_sig
): 
 993         def gen_sig_code(idxs
): 
 994             def _genslice(start
, end
, step
): 
 995                 starts 
= '' if start 
== 0 else str(start
) 
 996                 ends 
= (':%d' % (end 
+ step
)) if end 
+ step 
>= 0 else ':' 
 997                 steps 
= '' if step 
== 1 else (':%d' % step
) 
 998                 return 's[%s%s%s]' % (starts
, ends
, steps
) 
1001             # Quelch pyflakes warnings - start will be set when step is set 
1002             start 
= '(Never used)' 
1003             for i
, prev 
in zip(idxs
[1:], idxs
[:-1]): 
1004                 if step 
is not None: 
1005                     if i 
- prev 
== step
: 
1007                     yield _genslice(start
, prev
, step
) 
1010                 if i 
- prev 
in [-1, 1]: 
1015                     yield 's[%d]' % prev
 
1019                 yield _genslice(start
, i
, step
) 
1021         test_string 
= ''.join(map(compat_chr
, range(len(example_sig
)))) 
1022         cache_res 
= func(test_string
) 
1023         cache_spec 
= [ord(c
) for c 
in cache_res
] 
1024         expr_code 
= ' + '.join(gen_sig_code(cache_spec
)) 
1025         signature_id_tuple 
= '(%s)' % ( 
1026             ', '.join(compat_str(len(p
)) for p 
in example_sig
.split('.'))) 
1027         code 
= ('if tuple(len(p) for p in s.split(\'.\')) == %s:\n' 
1028                 '    return %s\n') % (signature_id_tuple
, expr_code
) 
1029         self
.to_screen('Extracted signature function:\n' + code
) 
1031     def _parse_sig_js(self
, jscode
): 
1032         funcname 
= self
._search
_regex
( 
1033             (r
'(["\'])signature\
1\s
*,\s
*(?P
<sig
>[a
-zA
-Z0
-9$
]+)\
(', 
1034              r'\
.sig\|\|
(?P
<sig
>[a
-zA
-Z0
-9$
]+)\
('), 
1035             jscode, 'Initial JS player signature function name
', group='sig
') 
1037         jsi = JSInterpreter(jscode) 
1038         initial_function = jsi.extract_function(funcname) 
1039         return lambda s: initial_function([s]) 
1041     def _parse_sig_swf(self, file_contents): 
1042         swfi = SWFInterpreter(file_contents) 
1043         TARGET_CLASSNAME = 'SignatureDecipher
' 
1044         searched_class = swfi.extract_class(TARGET_CLASSNAME) 
1045         initial_function = swfi.extract_function(searched_class, 'decipher
') 
1046         return lambda s: initial_function([s]) 
1048     def _decrypt_signature(self, s, video_id, player_url, age_gate=False): 
1049         """Turn the encrypted s field into a working signature""" 
1051         if player_url is None: 
1052             raise ExtractorError('Cannot decrypt signature without player_url
') 
1054         if player_url.startswith('//'): 
1055             player_url = 'https
:' + player_url 
1056         elif not re.match(r'https?
://', player_url): 
1057             player_url = compat_urlparse.urljoin( 
1058                 'https
://www
.youtube
.com
', player_url) 
1060             player_id = (player_url, self._signature_cache_id(s)) 
1061             if player_id not in self._player_cache: 
1062                 func = self._extract_signature_function( 
1063                     video_id, player_url, s 
1065                 self._player_cache[player_id] = func 
1066             func = self._player_cache[player_id] 
1067             if self._downloader.params.get('youtube_print_sig_code
'): 
1068                 self._print_sig_code(func, s) 
1070         except Exception as e: 
1071             tb = traceback.format_exc() 
1072             raise ExtractorError( 
1073                 'Signature extraction failed
: ' + tb, cause=e) 
1075     def _get_subtitles(self, video_id, webpage): 
1077             subs_doc = self._download_xml( 
1078                 'https
://video
.google
.com
/timedtext?hl
=en
&type=list&v
=%s' % video_id, 
1079                 video_id, note=False) 
1080         except ExtractorError as err: 
1081             self._downloader.report_warning('unable to download video subtitles
: %s' % error_to_compat_str(err)) 
1085         for track in subs_doc.findall('track
'): 
1086             lang = track.attrib['lang_code
'] 
1087             if lang in sub_lang_list: 
1090             for ext in self._SUBTITLE_FORMATS: 
1091                 params = compat_urllib_parse_urlencode({ 
1095                     'name
': track.attrib['name
'].encode('utf
-8'), 
1097                 sub_formats.append({ 
1098                     'url
': 'https
://www
.youtube
.com
/api
/timedtext?
' + params, 
1101             sub_lang_list[lang] = sub_formats 
1102         if not sub_lang_list: 
1103             self._downloader.report_warning('video doesn
\'t have subtitles
') 
1105         return sub_lang_list 
1107     def _get_ytplayer_config(self, video_id, webpage): 
1109             # User data may contain arbitrary character sequences that may affect 
1110             # JSON extraction with regex, e.g. when '};' is contained the second 
1111             # regex won't capture the whole JSON
. Yet working around by trying more
 
1112             # concrete regex first keeping in mind proper quoted string handling 
1113             # to be implemented in future that will replace this workaround (see 
1114             # https://github.com/rg3/youtube-dl/issues/7468, 
1115             # https://github.com/rg3/youtube-dl/pull/7599) 
1116             r
';ytplayer\.config\s*=\s*({.+?});ytplayer', 
1117             r
';ytplayer\.config\s*=\s*({.+?});', 
1119         config 
= self
._search
_regex
( 
1120             patterns
, webpage
, 'ytplayer.config', default
=None) 
1122             return self
._parse
_json
( 
1123                 uppercase_escape(config
), video_id
, fatal
=False) 
1125     def _get_automatic_captions(self
, video_id
, webpage
): 
1126         """We need the webpage for getting the captions url, pass it as an 
1127            argument to speed up the process.""" 
1128         self
.to_screen('%s: Looking for automatic captions' % video_id
) 
1129         player_config 
= self
._get
_ytplayer
_config
(video_id
, webpage
) 
1130         err_msg 
= 'Couldn\'t find automatic captions for %s' % video_id
 
1131         if not player_config
: 
1132             self
._downloader
.report_warning(err_msg
) 
1135             args 
= player_config
['args'] 
1136             caption_url 
= args
.get('ttsurl') 
1138                 timestamp 
= args
['timestamp'] 
1139                 # We get the available subtitles 
1140                 list_params 
= compat_urllib_parse_urlencode({ 
1145                 list_url 
= caption_url 
+ '&' + list_params
 
1146                 caption_list 
= self
._download
_xml
(list_url
, video_id
) 
1147                 original_lang_node 
= caption_list
.find('track') 
1148                 if original_lang_node 
is None: 
1149                     self
._downloader
.report_warning('Video doesn\'t have automatic captions') 
1151                 original_lang 
= original_lang_node
.attrib
['lang_code'] 
1152                 caption_kind 
= original_lang_node
.attrib
.get('kind', '') 
1155                 for lang_node 
in caption_list
.findall('target'): 
1156                     sub_lang 
= lang_node
.attrib
['lang_code'] 
1158                     for ext 
in self
._SUBTITLE
_FORMATS
: 
1159                         params 
= compat_urllib_parse_urlencode({ 
1160                             'lang': original_lang
, 
1164                             'kind': caption_kind
, 
1166                         sub_formats
.append({ 
1167                             'url': caption_url 
+ '&' + params
, 
1170                     sub_lang_list
[sub_lang
] = sub_formats
 
1171                 return sub_lang_list
 
1173             # Some videos don't provide ttsurl but rather caption_tracks and 
1174             # caption_translation_languages (e.g. 20LmZk1hakA) 
1175             caption_tracks 
= args
['caption_tracks'] 
1176             caption_translation_languages 
= args
['caption_translation_languages'] 
1177             caption_url 
= compat_parse_qs(caption_tracks
.split(',')[0])['u'][0] 
1178             parsed_caption_url 
= compat_urllib_parse_urlparse(caption_url
) 
1179             caption_qs 
= compat_parse_qs(parsed_caption_url
.query
) 
1182             for lang 
in caption_translation_languages
.split(','): 
1183                 lang_qs 
= compat_parse_qs(compat_urllib_parse_unquote_plus(lang
)) 
1184                 sub_lang 
= lang_qs
.get('lc', [None])[0] 
1188                 for ext 
in self
._SUBTITLE
_FORMATS
: 
1190                         'tlang': [sub_lang
], 
1193                     sub_url 
= compat_urlparse
.urlunparse(parsed_caption_url
._replace
( 
1194                         query
=compat_urllib_parse_urlencode(caption_qs
, True))) 
1195                     sub_formats
.append({ 
1199                 sub_lang_list
[sub_lang
] = sub_formats
 
1200             return sub_lang_list
 
1201         # An extractor error can be raise by the download process if there are 
1202         # no automatic captions but there are subtitles 
1203         except (KeyError, ExtractorError
): 
1204             self
._downloader
.report_warning(err_msg
) 
1207     def _mark_watched(self
, video_id
, video_info
): 
1208         playback_url 
= video_info
.get('videostats_playback_base_url', [None])[0] 
1209         if not playback_url
: 
1211         parsed_playback_url 
= compat_urlparse
.urlparse(playback_url
) 
1212         qs 
= compat_urlparse
.parse_qs(parsed_playback_url
.query
) 
1214         # cpn generation algorithm is reverse engineered from base.js. 
1215         # In fact it works even with dummy cpn. 
1216         CPN_ALPHABET 
= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_' 
1217         cpn 
= ''.join((CPN_ALPHABET
[random
.randint(0, 256) & 63] for _ 
in range(0, 16))) 
1223         playback_url 
= compat_urlparse
.urlunparse( 
1224             parsed_playback_url
._replace
(query
=compat_urllib_parse_urlencode(qs
, True))) 
1226         self
._download
_webpage
( 
1227             playback_url
, video_id
, 'Marking watched', 
1228             'Unable to mark watched', fatal
=False) 
1231     def extract_id(cls
, url
): 
1232         mobj 
= re
.match(cls
._VALID
_URL
, url
, re
.VERBOSE
) 
1234             raise ExtractorError('Invalid URL: %s' % url
) 
1235         video_id 
= mobj
.group(2) 
1238     def _extract_from_m3u8(self
, manifest_url
, video_id
): 
1241         def _get_urls(_manifest
): 
1242             lines 
= _manifest
.split('\n') 
1243             urls 
= filter(lambda l
: l 
and not l
.startswith('#'), 
1246         manifest 
= self
._download
_webpage
(manifest_url
, video_id
, 'Downloading formats manifest') 
1247         formats_urls 
= _get_urls(manifest
) 
1248         for format_url 
in formats_urls
: 
1249             itag 
= self
._search
_regex
(r
'itag/(\d+?)/', format_url
, 'itag') 
1250             url_map
[itag
] = format_url
 
1253     def _extract_annotations(self
, video_id
): 
1254         url 
= 'https://www.youtube.com/annotations_invideo?features=1&legacy=1&video_id=%s' % video_id
 
1255         return self
._download
_webpage
(url
, video_id
, note
='Searching for annotations.', errnote
='Unable to download video annotations.') 
1257     def _real_extract(self
, url
): 
1258         url
, smuggled_data 
= unsmuggle_url(url
, {}) 
1261             'http' if self
._downloader
.params
.get('prefer_insecure', False) 
1266         parsed_url 
= compat_urllib_parse_urlparse(url
) 
1267         for component 
in [parsed_url
.fragment
, parsed_url
.query
]: 
1268             query 
= compat_parse_qs(component
) 
1269             if start_time 
is None and 't' in query
: 
1270                 start_time 
= parse_duration(query
['t'][0]) 
1271             if start_time 
is None and 'start' in query
: 
1272                 start_time 
= parse_duration(query
['start'][0]) 
1273             if end_time 
is None and 'end' in query
: 
1274                 end_time 
= parse_duration(query
['end'][0]) 
1276         # Extract original video URL from URL with redirection, like age verification, using next_url parameter 
1277         mobj 
= re
.search(self
._NEXT
_URL
_RE
, url
) 
1279             url 
= proto 
+ '://www.youtube.com/' + compat_urllib_parse_unquote(mobj
.group(1)).lstrip('/') 
1280         video_id 
= self
.extract_id(url
) 
1283         url 
= proto 
+ '://www.youtube.com/watch?v=%s&gl=US&hl=en&has_verified=1&bpctr=9999999999' % video_id
 
1284         video_webpage 
= self
._download
_webpage
(url
, video_id
) 
1286         # Attempt to extract SWF player URL 
1287         mobj 
= re
.search(r
'swfConfig.*?"(https?:\\/\\/.*?watch.*?-.*?\.swf)"', video_webpage
) 
1288         if mobj 
is not None: 
1289             player_url 
= re
.sub(r
'\\(.)', r
'\1', mobj
.group(1)) 
1295         def add_dash_mpd(video_info
): 
1296             dash_mpd 
= video_info
.get('dashmpd') 
1297             if dash_mpd 
and dash_mpd
[0] not in dash_mpds
: 
1298                 dash_mpds
.append(dash_mpd
[0]) 
1301         embed_webpage 
= None 
1303         if re
.search(r
'player-age-gate-content">', video_webpage
) is not None: 
1305             # We simulate the access to the video from www.youtube.com/v/{video_id} 
1306             # this can be viewed without login into Youtube 
1307             url 
= proto 
+ '://www.youtube.com/embed/%s' % video_id
 
1308             embed_webpage 
= self
._download
_webpage
(url
, video_id
, 'Downloading embed webpage') 
1309             data 
= compat_urllib_parse_urlencode({ 
1310                 'video_id': video_id
, 
1311                 'eurl': 'https://youtube.googleapis.com/v/' + video_id
, 
1312                 'sts': self
._search
_regex
( 
1313                     r
'"sts"\s*:\s*(\d+)', embed_webpage
, 'sts', default
=''), 
1315             video_info_url 
= proto 
+ '://www.youtube.com/get_video_info?' + data
 
1316             video_info_webpage 
= self
._download
_webpage
( 
1317                 video_info_url
, video_id
, 
1318                 note
='Refetching age-gated info webpage', 
1319                 errnote
='unable to download video info webpage') 
1320             video_info 
= compat_parse_qs(video_info_webpage
) 
1321             add_dash_mpd(video_info
) 
1325             # Try looking directly into the video webpage 
1326             ytplayer_config 
= self
._get
_ytplayer
_config
(video_id
, video_webpage
) 
1328                 args 
= ytplayer_config
['args'] 
1329                 if args
.get('url_encoded_fmt_stream_map'): 
1330                     # Convert to the same format returned by compat_parse_qs 
1331                     video_info 
= dict((k
, [v
]) for k
, v 
in args
.items()) 
1332                     add_dash_mpd(video_info
) 
1333                 # Rental video is not rented but preview is available (e.g. 
1334                 # https://www.youtube.com/watch?v=yYr8q0y5Jfg, 
1335                 # https://github.com/rg3/youtube-dl/issues/10532) 
1336                 if not video_info 
and args
.get('ypc_vid'): 
1337                     return self
.url_result( 
1338                         args
['ypc_vid'], YoutubeIE
.ie_key(), video_id
=args
['ypc_vid']) 
1339                 if args
.get('livestream') == '1' or args
.get('live_playback') == 1: 
1341             if not video_info 
or self
._downloader
.params
.get('youtube_include_dash_manifest', True): 
1342                 # We also try looking in get_video_info since it may contain different dashmpd 
1343                 # URL that points to a DASH manifest with possibly different itag set (some itags 
1344                 # are missing from DASH manifest pointed by webpage's dashmpd, some - from DASH 
1345                 # manifest pointed by get_video_info's dashmpd). 
1346                 # The general idea is to take a union of itags of both DASH manifests (for example 
1347                 # video with such 'manifest behavior' see https://github.com/rg3/youtube-dl/issues/6093) 
1348                 self
.report_video_info_webpage_download(video_id
) 
1349                 for el_type 
in ['&el=info', '&el=embedded', '&el=detailpage', '&el=vevo', '']: 
1351                         '%s://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en' 
1352                         % (proto
, video_id
, el_type
)) 
1353                     video_info_webpage 
= self
._download
_webpage
( 
1355                         video_id
, note
=False, 
1356                         errnote
='unable to download video info webpage') 
1357                     get_video_info 
= compat_parse_qs(video_info_webpage
) 
1358                     if get_video_info
.get('use_cipher_signature') != ['True']: 
1359                         add_dash_mpd(get_video_info
) 
1361                         video_info 
= get_video_info
 
1362                     if 'token' in get_video_info
: 
1363                         # Different get_video_info requests may report different results, e.g. 
1364                         # some may report video unavailability, but some may serve it without 
1365                         # any complaint (see https://github.com/rg3/youtube-dl/issues/7362, 
1366                         # the original webpage as well as el=info and el=embedded get_video_info 
1367                         # requests report video unavailability due to geo restriction while 
1368                         # el=detailpage succeeds and returns valid data). This is probably 
1369                         # due to YouTube measures against IP ranges of hosting providers. 
1370                         # Working around by preferring the first succeeded video_info containing 
1371                         # the token if no such video_info yet was found. 
1372                         if 'token' not in video_info
: 
1373                             video_info 
= get_video_info
 
1375         if 'token' not in video_info
: 
1376             if 'reason' in video_info
: 
1377                 if 'The uploader has not made this video available in your country.' in video_info
['reason']: 
1378                     regions_allowed 
= self
._html
_search
_meta
('regionsAllowed', video_webpage
, default
=None) 
1380                         raise ExtractorError('YouTube said: This video is available in %s only' % ( 
1381                             ', '.join(map(ISO3166Utils
.short2full
, regions_allowed
.split(',')))), 
1383                 raise ExtractorError( 
1384                     'YouTube said: %s' % video_info
['reason'][0], 
1385                     expected
=True, video_id
=video_id
) 
1387                 raise ExtractorError( 
1388                     '"token" parameter not in video info for unknown reason', 
1392         if 'title' in video_info
: 
1393             video_title 
= video_info
['title'][0] 
1395             self
._downloader
.report_warning('Unable to extract video title') 
1399         video_description 
= get_element_by_id("eow-description", video_webpage
) 
1400         if video_description
: 
1401             video_description 
= re
.sub(r
'''(?x) 
1403                     (?:[a-zA-Z-]+="[^"]*"\s+)*? 
1404                     (?:title|href)="([^"]+)"\s+ 
1405                     (?:[a-zA-Z-]+="[^"]*"\s+)*? 
1409             ''', r
'\1', video_description
) 
1410             video_description 
= clean_html(video_description
) 
1412             fd_mobj 
= re
.search(r
'<meta name="description" content="([^"]+)"', video_webpage
) 
1414                 video_description 
= unescapeHTML(fd_mobj
.group(1)) 
1416                 video_description 
= '' 
1418         if 'multifeed_metadata_list' in video_info 
and not smuggled_data
.get('force_singlefeed', False): 
1419             if not self
._downloader
.params
.get('noplaylist'): 
1422                 multifeed_metadata_list 
= video_info
['multifeed_metadata_list'][0] 
1423                 for feed 
in multifeed_metadata_list
.split(','): 
1424                     # Unquote should take place before split on comma (,) since textual 
1425                     # fields may contain comma as well (see 
1426                     # https://github.com/rg3/youtube-dl/issues/8536) 
1427                     feed_data 
= compat_parse_qs(compat_urllib_parse_unquote_plus(feed
)) 
1429                         '_type': 'url_transparent', 
1430                         'ie_key': 'Youtube', 
1432                             '%s://www.youtube.com/watch?v=%s' % (proto
, feed_data
['id'][0]), 
1433                             {'force_singlefeed': True}), 
1434                         'title': '%s (%s)' % (video_title
, feed_data
['title'][0]), 
1436                     feed_ids
.append(feed_data
['id'][0]) 
1438                     'Downloading multifeed video (%s) - add --no-playlist to just download video %s' 
1439                     % (', '.join(feed_ids
), video_id
)) 
1440                 return self
.playlist_result(entries
, video_id
, video_title
, video_description
) 
1441             self
.to_screen('Downloading just video %s because of --no-playlist' % video_id
) 
1443         if 'view_count' in video_info
: 
1444             view_count 
= int(video_info
['view_count'][0]) 
1448         # Check for "rental" videos 
1449         if 'ypc_video_rental_bar_text' in video_info 
and 'author' not in video_info
: 
1450             raise ExtractorError('"rental" videos not supported') 
1452         # Start extracting information 
1453         self
.report_information_extraction(video_id
) 
1456         if 'author' not in video_info
: 
1457             raise ExtractorError('Unable to extract uploader name') 
1458         video_uploader 
= compat_urllib_parse_unquote_plus(video_info
['author'][0]) 
1461         video_uploader_id 
= None 
1462         video_uploader_url 
= None 
1464             r
'<link itemprop="url" href="(?P<uploader_url>https?://www.youtube.com/(?:user|channel)/(?P<uploader_id>[^"]+))">', 
1466         if mobj 
is not None: 
1467             video_uploader_id 
= mobj
.group('uploader_id') 
1468             video_uploader_url 
= mobj
.group('uploader_url') 
1470             self
._downloader
.report_warning('unable to extract uploader nickname') 
1473         # We try first to get a high quality image: 
1474         m_thumb 
= re
.search(r
'<span itemprop="thumbnail".*?href="(.*?)">', 
1475                             video_webpage
, re
.DOTALL
) 
1476         if m_thumb 
is not None: 
1477             video_thumbnail 
= m_thumb
.group(1) 
1478         elif 'thumbnail_url' not in video_info
: 
1479             self
._downloader
.report_warning('unable to extract video thumbnail') 
1480             video_thumbnail 
= None 
1481         else:   # don't panic if we can't find it 
1482             video_thumbnail 
= compat_urllib_parse_unquote_plus(video_info
['thumbnail_url'][0]) 
1485         upload_date 
= self
._html
_search
_meta
( 
1486             'datePublished', video_webpage
, 'upload date', default
=None) 
1488             upload_date 
= self
._search
_regex
( 
1489                 [r
'(?s)id="eow-date.*?>(.*?)</span>', 
1490                  r
'id="watch-uploader-info".*?>.*?(?:Published|Uploaded|Streamed live|Started) on (.+?)</strong>'], 
1491                 video_webpage
, 'upload date', default
=None) 
1493                 upload_date 
= ' '.join(re
.sub(r
'[/,-]', r
' ', mobj
.group(1)).split()) 
1494         upload_date 
= unified_strdate(upload_date
) 
1496         video_license 
= self
._html
_search
_regex
( 
1497             r
'<h4[^>]+class="title"[^>]*>\s*License\s*</h4>\s*<ul[^>]*>\s*<li>(.+?)</li', 
1498             video_webpage
, 'license', default
=None) 
1500         m_music 
= re
.search( 
1501             r
'<h4[^>]+class="title"[^>]*>\s*Music\s*</h4>\s*<ul[^>]*>\s*<li>(?P<title>.+?) by (?P<creator>.+?)(?:\(.+?\))?</li', 
1504             video_alt_title 
= remove_quotes(unescapeHTML(m_music
.group('title'))) 
1505             video_creator 
= clean_html(m_music
.group('creator')) 
1507             video_alt_title 
= video_creator 
= None 
1509         m_episode 
= re
.search( 
1510             r
'<div[^>]+id="watch7-headline"[^>]*>\s*<span[^>]*>.*?>(?P<series>[^<]+)</a></b>\s*S(?P<season>\d+)\s*ā¢\s*E(?P<episode>\d+)</span>', 
1513             series 
= m_episode
.group('series') 
1514             season_number 
= int(m_episode
.group('season')) 
1515             episode_number 
= int(m_episode
.group('episode')) 
1517             series 
= season_number 
= episode_number 
= None 
1519         m_cat_container 
= self
._search
_regex
( 
1520             r
'(?s)<h4[^>]*>\s*Category\s*</h4>\s*<ul[^>]*>(.*?)</ul>', 
1521             video_webpage
, 'categories', default
=None) 
1523             category 
= self
._html
_search
_regex
( 
1524                 r
'(?s)<a[^<]+>(.*?)</a>', m_cat_container
, 'category', 
1526             video_categories 
= None if category 
is None else [category
] 
1528             video_categories 
= None 
1531             unescapeHTML(m
.group('content')) 
1532             for m 
in re
.finditer(self
._meta
_regex
('og:video:tag'), video_webpage
)] 
1534         def _extract_count(count_name
): 
1535             return str_to_int(self
._search
_regex
( 
1536                 r
'-%s-button[^>]+><span[^>]+class="yt-uix-button-content"[^>]*>([\d,]+)</span>' 
1537                 % re
.escape(count_name
), 
1538                 video_webpage
, count_name
, default
=None)) 
1540         like_count 
= _extract_count('like') 
1541         dislike_count 
= _extract_count('dislike') 
1544         video_subtitles 
= self
.extract_subtitles(video_id
, video_webpage
) 
1545         automatic_captions 
= self
.extract_automatic_captions(video_id
, video_webpage
) 
1547         video_duration 
= try_get( 
1548             video_info
, lambda x
: int_or_none(x
['length_seconds'][0])) 
1549         if not video_duration
: 
1550             video_duration 
= parse_duration(self
._html
_search
_meta
( 
1551                 'duration', video_webpage
, 'video duration')) 
1554         video_annotations 
= None 
1555         if self
._downloader
.params
.get('writeannotations', False): 
1556             video_annotations 
= self
._extract
_annotations
(video_id
) 
1558         def _map_to_format_list(urlmap
): 
1560             for itag
, video_real_url 
in urlmap
.items(): 
1563                     'url': video_real_url
, 
1564                     'player_url': player_url
, 
1566                 if itag 
in self
._formats
: 
1567                     dct
.update(self
._formats
[itag
]) 
1571         if 'conn' in video_info 
and video_info
['conn'][0].startswith('rtmp'): 
1572             self
.report_rtmp_download() 
1574                 'format_id': '_rtmp', 
1576                 'url': video_info
['conn'][0], 
1577                 'player_url': player_url
, 
1579         elif len(video_info
.get('url_encoded_fmt_stream_map', [''])[0]) >= 1 or len(video_info
.get('adaptive_fmts', [''])[0]) >= 1: 
1580             encoded_url_map 
= video_info
.get('url_encoded_fmt_stream_map', [''])[0] + ',' + video_info
.get('adaptive_fmts', [''])[0] 
1581             if 'rtmpe%3Dyes' in encoded_url_map
: 
1582                 raise ExtractorError('rtmpe downloads are not supported, see https://github.com/rg3/youtube-dl/issues/343 for more information.', expected
=True) 
1584             fmt_list 
= video_info
.get('fmt_list', [''])[0] 
1586                 for fmt 
in fmt_list
.split(','): 
1587                     spec 
= fmt
.split('/') 
1589                         width_height 
= spec
[1].split('x') 
1590                         if len(width_height
) == 2: 
1591                             formats_spec
[spec
[0]] = { 
1592                                 'resolution': spec
[1], 
1593                                 'width': int_or_none(width_height
[0]), 
1594                                 'height': int_or_none(width_height
[1]), 
1597             for url_data_str 
in encoded_url_map
.split(','): 
1598                 url_data 
= compat_parse_qs(url_data_str
) 
1599                 if 'itag' not in url_data 
or 'url' not in url_data
: 
1601                 format_id 
= url_data
['itag'][0] 
1602                 url 
= url_data
['url'][0] 
1604                 if 'sig' in url_data
: 
1605                     url 
+= '&signature=' + url_data
['sig'][0] 
1606                 elif 's' in url_data
: 
1607                     encrypted_sig 
= url_data
['s'][0] 
1608                     ASSETS_RE 
= r
'"assets":.+?"js":\s*("[^"]+")' 
1610                     jsplayer_url_json 
= self
._search
_regex
( 
1612                         embed_webpage 
if age_gate 
else video_webpage
, 
1613                         'JS player URL (1)', default
=None) 
1614                     if not jsplayer_url_json 
and not age_gate
: 
1615                         # We need the embed website after all 
1616                         if embed_webpage 
is None: 
1617                             embed_url 
= proto 
+ '://www.youtube.com/embed/%s' % video_id
 
1618                             embed_webpage 
= self
._download
_webpage
( 
1619                                 embed_url
, video_id
, 'Downloading embed webpage') 
1620                         jsplayer_url_json 
= self
._search
_regex
( 
1621                             ASSETS_RE
, embed_webpage
, 'JS player URL') 
1623                     player_url 
= json
.loads(jsplayer_url_json
) 
1624                     if player_url 
is None: 
1625                         player_url_json 
= self
._search
_regex
( 
1626                             r
'ytplayer\.config.*?"url"\s*:\s*("[^"]+")', 
1627                             video_webpage
, 'age gate player URL') 
1628                         player_url 
= json
.loads(player_url_json
) 
1630                     if self
._downloader
.params
.get('verbose'): 
1631                         if player_url 
is None: 
1632                             player_version 
= 'unknown' 
1633                             player_desc 
= 'unknown' 
1635                             if player_url
.endswith('swf'): 
1636                                 player_version 
= self
._search
_regex
( 
1637                                     r
'-(.+?)(?:/watch_as3)?\.swf$', player_url
, 
1638                                     'flash player', fatal
=False) 
1639                                 player_desc 
= 'flash player %s' % player_version
 
1641                                 player_version 
= self
._search
_regex
( 
1642                                     [r
'html5player-([^/]+?)(?:/html5player(?:-new)?)?\.js', r
'(?:www|player)-([^/]+)/base\.js'], 
1644                                     'html5 player', fatal
=False) 
1645                                 player_desc 
= 'html5 player %s' % player_version
 
1647                         parts_sizes 
= self
._signature
_cache
_id
(encrypted_sig
) 
1648                         self
.to_screen('{%s} signature length %s, %s' % 
1649                                        (format_id
, parts_sizes
, player_desc
)) 
1651                     signature 
= self
._decrypt
_signature
( 
1652                         encrypted_sig
, video_id
, player_url
, age_gate
) 
1653                     url 
+= '&signature=' + signature
 
1654                 if 'ratebypass' not in url
: 
1655                     url 
+= '&ratebypass=yes' 
1658                     'format_id': format_id
, 
1660                     'player_url': player_url
, 
1662                 if format_id 
in self
._formats
: 
1663                     dct
.update(self
._formats
[format_id
]) 
1664                 if format_id 
in formats_spec
: 
1665                     dct
.update(formats_spec
[format_id
]) 
1667                 # Some itags are not included in DASH manifest thus corresponding formats will 
1668                 # lack metadata (see https://github.com/rg3/youtube-dl/pull/5993). 
1669                 # Trying to extract metadata from url_encoded_fmt_stream_map entry. 
1670                 mobj 
= re
.search(r
'^(?P<width>\d+)[xX](?P<height>\d+)$', url_data
.get('size', [''])[0]) 
1671                 width
, height 
= (int(mobj
.group('width')), int(mobj
.group('height'))) if mobj 
else (None, None) 
1674                     'filesize': int_or_none(url_data
.get('clen', [None])[0]), 
1675                     'tbr': float_or_none(url_data
.get('bitrate', [None])[0], 1000), 
1678                     'fps': int_or_none(url_data
.get('fps', [None])[0]), 
1679                     'format_note': url_data
.get('quality_label', [None])[0] or url_data
.get('quality', [None])[0], 
1681                 for key
, value 
in more_fields
.items(): 
1684                 type_ 
= url_data
.get('type', [None])[0] 
1686                     type_split 
= type_
.split(';') 
1687                     kind_ext 
= type_split
[0].split('/') 
1688                     if len(kind_ext
) == 2: 
1690                         dct
['ext'] = mimetype2ext(type_split
[0]) 
1691                         if kind 
in ('audio', 'video'): 
1693                             for mobj 
in re
.finditer( 
1694                                     r
'(?P<key>[a-zA-Z_-]+)=(?P<quote>["\']?
)(?P
<val
>.+?
)(?P
=quote
)(?
:;|$
)', type_): 
1695                                 if mobj.group('key
') == 'codecs
': 
1696                                     codecs = mobj.group('val
') 
1699                                 codecs = codecs.split(',') 
1700                                 if len(codecs) == 2: 
1701                                     acodec, vcodec = codecs[1], codecs[0] 
1703                                     acodec, vcodec = (codecs[0], 'none
') if kind == 'audio
' else ('none
', codecs[0]) 
1709         elif video_info.get('hlsvp
'): 
1710             manifest_url = video_info['hlsvp
'][0] 
1711             url_map = self._extract_from_m3u8(manifest_url, video_id) 
1712             formats = _map_to_format_list(url_map) 
1713             # Accept-Encoding header causes failures in live streams on Youtube and Youtube Gaming 
1714             for a_format in formats: 
1715                 a_format.setdefault('http_headers
', {})['Youtubedl
-no
-compression
'] = 'True' 
1717             unavailable_message = self._html_search_regex( 
1718                 r'(?s
)<h1
[^
>]+id="unavailable-message"[^
>]*>(.+?
)</h1
>', 
1719                 video_webpage, 'unavailable message
', default=None) 
1720             if unavailable_message: 
1721                 raise ExtractorError(unavailable_message, expected=True) 
1722             raise ExtractorError('no conn
, hlsvp 
or url_encoded_fmt_stream_map information found 
in video info
') 
1724         # Look for the DASH manifest 
1725         if self._downloader.params.get('youtube_include_dash_manifest
', True): 
1726             dash_mpd_fatal = True 
1727             for mpd_url in dash_mpds: 
1730                     def decrypt_sig(mobj): 
1732                         dec_s = self._decrypt_signature(s, video_id, player_url, age_gate) 
1733                         return '/signature
/%s' % dec_s 
1735                     mpd_url = re.sub(r'/s
/([a
-fA
-F0
-9\
.]+)', decrypt_sig, mpd_url) 
1737                     for df in self._extract_mpd_formats( 
1738                             mpd_url, video_id, fatal=dash_mpd_fatal, 
1739                             formats_dict=self._formats): 
1740                         # Do not overwrite DASH format found in some previous DASH manifest 
1741                         if df['format_id
'] not in dash_formats: 
1742                             dash_formats[df['format_id
']] = df 
1743                         # Additional DASH manifests may end up in HTTP Error 403 therefore 
1744                         # allow them to fail without bug report message if we already have 
1745                         # some DASH manifest succeeded. This is temporary workaround to reduce 
1746                         # burst of bug reports until we figure out the reason and whether it 
1747                         # can be fixed at all. 
1748                         dash_mpd_fatal = False 
1749                 except (ExtractorError, KeyError) as e: 
1750                     self.report_warning( 
1751                         'Skipping DASH manifest
: %r' % e, video_id) 
1753                     # Remove the formats we found through non-DASH, they 
1754                     # contain less info and it can be wrong, because we use 
1755                     # fixed values (for example the resolution). See 
1756                     # https://github.com/rg3/youtube-dl/issues/5774 for an 
1758                     formats = [f for f in formats if f['format_id
'] not in dash_formats.keys()] 
1759                     formats.extend(dash_formats.values()) 
1761         # Check for malformed aspect ratio 
1762         stretched_m = re.search( 
1763             r'<meta\s
+property="og:video:tag".*?content
="yt:stretch=(?P<w>[0-9]+):(?P<h>[0-9]+)">', 
1766             w = float(stretched_m.group('w
')) 
1767             h = float(stretched_m.group('h
')) 
1768             # yt:stretch may hold invalid ratio data (e.g. for Q39EVAstoRM ratio is 17:0). 
1769             # We will only process correct ratios. 
1773                     if f.get('vcodec
') != 'none
': 
1774                         f['stretched_ratio
'] = ratio 
1776         self._sort_formats(formats) 
1778         self.mark_watched(video_id, video_info) 
1782             'uploader
': video_uploader, 
1783             'uploader_id
': video_uploader_id, 
1784             'uploader_url
': video_uploader_url, 
1785             'upload_date
': upload_date, 
1786             'license
': video_license, 
1787             'creator
': video_creator, 
1788             'title
': video_title, 
1789             'alt_title
': video_alt_title, 
1790             'thumbnail
': video_thumbnail, 
1791             'description
': video_description, 
1792             'categories
': video_categories, 
1794             'subtitles
': video_subtitles, 
1795             'automatic_captions
': automatic_captions, 
1796             'duration
': video_duration, 
1797             'age_limit
': 18 if age_gate else 0, 
1798             'annotations
': video_annotations, 
1799             'webpage_url
': proto + '://www
.youtube
.com
/watch?v
=%s' % video_id, 
1800             'view_count
': view_count, 
1801             'like_count
': like_count, 
1802             'dislike_count
': dislike_count, 
1803             'average_rating
': float_or_none(video_info.get('avg_rating
', [None])[0]), 
1806             'start_time
': start_time, 
1807             'end_time
': end_time, 
1809             'season_number
': season_number, 
1810             'episode_number
': episode_number, 
1814 class YoutubeSharedVideoIE(InfoExtractor): 
1815     _VALID_URL = r'(?
:https?
:)?
//(?
:www\
.)?youtube\
.com
/shared
\?.*\bci
=(?P
<id>[0-9A
-Za
-z_
-]{11}
)' 
1816     IE_NAME = 'youtube
:shared
' 
1819         'url
': 'https
://www
.youtube
.com
/shared?ci
=1nEzmT
-M4fU
', 
1821             'id': 'uPDB5I9wfp8
', 
1823             'title
': 'Pocoyo
: 90 minutos de episódios completos Português para crianç
as - PARTE 
3', 
1824             'description
': 'md5
:d9e4d9346a2dfff4c7dc4c8cec0f546d
', 
1825             'upload_date
': '20160219', 
1826             'uploader
': 'Pocoyo 
- PortuguĆŖ
s (BR
)', 
1827             'uploader_id
': 'PocoyoBrazil
', 
1829         'add_ie
': ['Youtube
'], 
1831             # There are already too many Youtube downloads 
1832             'skip_download
': True, 
1836     def _real_extract(self, url): 
1837         video_id = self._match_id(url) 
1839         webpage = self._download_webpage(url, video_id) 
1841         real_video_id = self._html_search_meta( 
1842             'videoId
', webpage, 'YouTube video 
id', fatal=True) 
1844         return self.url_result(real_video_id, YoutubeIE.ie_key()) 
1847 class YoutubePlaylistIE(YoutubePlaylistBaseInfoExtractor): 
1848     IE_DESC = 'YouTube
.com playlists
' 
1849     _VALID_URL = r"""(?x)(?: 
1855                                (?:course|view_play_list|my_playlists|artist|playlist|watch|embed/videoseries) 
1856                                \? (?:.*?[&;])*? (?:p|a|list)= 
1859                             youtu\.be/[0-9A-Za-z_-]{11}\?.*?\blist= 
1862                             (?:PL|LL|EC|UU|FL|RD|UL|TL)?[0-9A-Za-z-_]{10,} 
1863                             # Top tracks, they can also include dots 
1868                         ((?:PL|LL|EC|UU|FL|RD|UL|TL)[0-9A-Za-z-_]{10,}) 
1870     _TEMPLATE_URL = 'https
://www
.youtube
.com
/playlist?
list=%s&disable_polymer
=true
' 
1871     _VIDEO_RE = r'href
="\s*/watch\?v=(?P<id>[0-9A-Za-z_-]{11})&[^"]*?index
=(?P
<index
>\d
+)(?
:[^
>]+>(?P
<title
>[^
<]+))?
' 
1872     IE_NAME = 'youtube
:playlist
' 
1874         'url
': 'https
://www
.youtube
.com
/playlist?
list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re
', 
1876             'title
': 'ytdl test PL
', 
1877             'id': 'PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re
', 
1879         'playlist_count
': 3, 
1881         'url
': 'https
://www
.youtube
.com
/playlist?
list=PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx
', 
1883             'id': 'PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx
', 
1884             'title
': 'YDL_Empty_List
', 
1886         'playlist_count
': 0, 
1887         'skip
': 'This playlist 
is private
', 
1889         'note
': 'Playlist 
with deleted 
videos (#651). As a bonus, the video #51 is also twice in this list.', 
1890         'url': 'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC', 
1892             'title': '29C3: Not my department', 
1893             'id': 'PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC', 
1895         'playlist_count': 95, 
1897         'note': 'issue #673', 
1898         'url': 'PLBB231211A4F62143', 
1900             'title': '[OLD]Team Fortress 2 (Class-based LP)', 
1901             'id': 'PLBB231211A4F62143', 
1903         'playlist_mincount': 26, 
1905         'note': 'Large playlist', 
1906         'url': 'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q', 
1908             'title': 'Uploads from Cauchemar', 
1909             'id': 'UUBABnxM4Ar9ten8Mdjj1j0Q', 
1911         'playlist_mincount': 799, 
1913         'url': 'PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl', 
1915             'title': 'YDL_safe_search', 
1916             'id': 'PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl', 
1918         'playlist_count': 2, 
1919         'skip': 'This playlist is private', 
1922         'url': 'https://www.youtube.com/embed/videoseries?list=PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 
1923         'playlist_count': 4, 
1926             'id': 'PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 
1929         'note': 'Embedded SWF player', 
1930         'url': 'https://www.youtube.com/p/YN5VISEtHet5D4NEvfTd0zcgFk84NqFZ?hl=en_US&fs=1&rel=0', 
1931         'playlist_count': 4, 
1934             'id': 'YN5VISEtHet5D4NEvfTd0zcgFk84NqFZ', 
1937         'note': 'Buggy playlist: the webpage has a "Load more" button but it doesn\'t have more videos', 
1938         'url': 'https://www.youtube.com/playlist?list=UUXw-G3eDE9trcvY2sBMM_aA', 
1940             'title': 'Uploads from Interstellar Movie', 
1941             'id': 'UUXw-G3eDE9trcvY2sBMM_aA', 
1943         'playlist_mincount': 21, 
1945         # Playlist URL that does not actually serve a playlist 
1946         'url': 'https://www.youtube.com/watch?v=FqZTN594JQw&list=PLMYEtVRpaqY00V9W81Cwmzp6N6vZqfUKD4', 
1948             'id': 'FqZTN594JQw', 
1950             'title': "Smiley's People 01 detective, Adventure Series, Action", 
1951             'uploader': 'STREEM', 
1952             'uploader_id': 'UCyPhqAZgwYWZfxElWVbVJng', 
1953             'uploader_url': r
're:https?://(?:www\.)?youtube\.com/channel/UCyPhqAZgwYWZfxElWVbVJng', 
1954             'upload_date': '20150526', 
1955             'license': 'Standard YouTube License', 
1956             'description': 'md5:507cdcb5a49ac0da37a920ece610be80', 
1957             'categories': ['People & Blogs'], 
1960             'dislike_count': int, 
1963             'skip_download': True, 
1965         'add_ie': [YoutubeIE
.ie_key()], 
1967         'url': 'https://youtu.be/yeWKywCrFtk?list=PL2qgrgXsNUG5ig9cat4ohreBjYLAPC0J5', 
1969             'id': 'yeWKywCrFtk', 
1971             'title': 'Small Scale Baler and Braiding Rugs', 
1972             'uploader': 'Backus-Page House Museum', 
1973             'uploader_id': 'backuspagemuseum', 
1974             'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/backuspagemuseum', 
1975             'upload_date': '20161008', 
1976             'license': 'Standard YouTube License', 
1977             'description': 'md5:800c0c78d5eb128500bffd4f0b4f2e8a', 
1978             'categories': ['Nonprofits & Activism'], 
1981             'dislike_count': int, 
1985             'skip_download': True, 
1988         'url': 'https://youtu.be/uWyaPkt-VOI?list=PL9D9FC436B881BA21', 
1989         'only_matching': True, 
1991         'url': 'TLGGrESM50VT6acwMjAyMjAxNw', 
1992         'only_matching': True, 
1995     def _real_initialize(self
): 
1998     def _extract_mix(self
, playlist_id
): 
1999         # The mixes are generated from a single video 
2000         # the id of the playlist is just 'RD' + video_id 
2002         last_id 
= playlist_id
[-11:] 
2003         for n 
in itertools
.count(1): 
2004             url 
= 'https://youtube.com/watch?v=%s&list=%s' % (last_id
, playlist_id
) 
2005             webpage 
= self
._download
_webpage
( 
2006                 url
, playlist_id
, 'Downloading page {0} of Youtube mix'.format(n
)) 
2007             new_ids 
= orderedSet(re
.findall( 
2008                 r
'''(?xs)data-video-username=".*?".*? 
2009                            href="/watch\?v=([0-9A-Za-z_-]{11})&[^"]*?list=%s''' % re
.escape(playlist_id
), 
2011             # Fetch new pages until all the videos are repeated, it seems that 
2012             # there are always 51 unique videos. 
2013             new_ids 
= [_id 
for _id 
in new_ids 
if _id 
not in ids
] 
2019         url_results 
= self
._ids
_to
_results
(ids
) 
2021         search_title 
= lambda class_name
: get_element_by_attribute('class', class_name
, webpage
) 
2023             search_title('playlist-title') or 
2024             search_title('title long-title') or 
2025             search_title('title')) 
2026         title 
= clean_html(title_span
) 
2028         return self
.playlist_result(url_results
, playlist_id
, title
) 
2030     def _extract_playlist(self
, playlist_id
): 
2031         url 
= self
._TEMPLATE
_URL 
% playlist_id
 
2032         page 
= self
._download
_webpage
(url
, playlist_id
) 
2034         # the yt-alert-message now has tabindex attribute (see https://github.com/rg3/youtube-dl/issues/11604) 
2035         for match 
in re
.findall(r
'<div class="yt-alert-message"[^>]*>([^<]+)</div>', page
): 
2036             match 
= match
.strip() 
2037             # Check if the playlist exists or is private 
2038             mobj 
= re
.match(r
'[^<]*(?:The|This) playlist (?P<reason>does not exist|is private)[^<]*', match
) 
2040                 reason 
= mobj
.group('reason') 
2041                 message 
= 'This playlist %s' % reason
 
2042                 if 'private' in reason
: 
2043                     message 
+= ', use --username or --netrc to access it' 
2045                 raise ExtractorError(message
, expected
=True) 
2046             elif re
.match(r
'[^<]*Invalid parameters[^<]*', match
): 
2047                 raise ExtractorError( 
2048                     'Invalid parameters. Maybe URL is incorrect.', 
2050             elif re
.match(r
'[^<]*Choose your language[^<]*', match
): 
2053                 self
.report_warning('Youtube gives an alert message: ' + match
) 
2055         playlist_title 
= self
._html
_search
_regex
( 
2056             r
'(?s)<h1 class="pl-header-title[^"]*"[^>]*>\s*(.*?)\s*</h1>', 
2057             page
, 'title', default
=None) 
2061         if not playlist_title
: 
2063                 # Some playlist URLs don't actually serve a playlist (e.g. 
2064                 # https://www.youtube.com/watch?v=FqZTN594JQw&list=PLMYEtVRpaqY00V9W81Cwmzp6N6vZqfUKD4) 
2065                 next(self
._entries
(page
, playlist_id
)) 
2066             except StopIteration: 
2069         return has_videos
, self
.playlist_result( 
2070             self
._entries
(page
, playlist_id
), playlist_id
, playlist_title
) 
2072     def _check_download_just_video(self
, url
, playlist_id
): 
2073         # Check if it's a video-specific URL 
2074         query_dict 
= compat_urlparse
.parse_qs(compat_urlparse
.urlparse(url
).query
) 
2075         video_id 
= query_dict
.get('v', [None])[0] or self
._search
_regex
( 
2076             r
'(?:^|//)youtu\.be/([0-9A-Za-z_-]{11})', url
, 
2077             'video id', default
=None) 
2079             if self
._downloader
.params
.get('noplaylist'): 
2080                 self
.to_screen('Downloading just video %s because of --no-playlist' % video_id
) 
2081                 return video_id
, self
.url_result(video_id
, 'Youtube', video_id
=video_id
) 
2083                 self
.to_screen('Downloading playlist %s - add --no-playlist to just download video %s' % (playlist_id
, video_id
)) 
2084                 return video_id
, None 
2087     def _real_extract(self
, url
): 
2088         # Extract playlist id 
2089         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2091             raise ExtractorError('Invalid URL: %s' % url
) 
2092         playlist_id 
= mobj
.group(1) or mobj
.group(2) 
2094         video_id
, video 
= self
._check
_download
_just
_video
(url
, playlist_id
) 
2098         if playlist_id
.startswith(('RD', 'UL', 'PU')): 
2099             # Mixes require a custom extraction process 
2100             return self
._extract
_mix
(playlist_id
) 
2102         has_videos
, playlist 
= self
._extract
_playlist
(playlist_id
) 
2103         if has_videos 
or not video_id
: 
2106         # Some playlist URLs don't actually serve a playlist (see 
2107         # https://github.com/rg3/youtube-dl/issues/10537). 
2108         # Fallback to plain video extraction if there is a video id 
2109         # along with playlist id. 
2110         return self
.url_result(video_id
, 'Youtube', video_id
=video_id
) 
2113 class YoutubeChannelIE(YoutubePlaylistBaseInfoExtractor
): 
2114     IE_DESC 
= 'YouTube.com channels' 
2115     _VALID_URL 
= r
'https?://(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/(?P<id>[0-9A-Za-z_-]+)' 
2116     _TEMPLATE_URL 
= 'https://www.youtube.com/channel/%s/videos' 
2117     _VIDEO_RE 
= r
'(?:title="(?P<title>[^"]+)"[^>]+)?href="/watch\?v=(?P<id>[0-9A-Za-z_-]+)&?' 
2118     IE_NAME 
= 'youtube:channel' 
2120         'note': 'paginated channel', 
2121         'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w', 
2122         'playlist_mincount': 91, 
2124             'id': 'UUKfVa3S1e4PHvxWcwyMMg8w', 
2125             'title': 'Uploads from lex will', 
2128         'note': 'Age restricted channel', 
2129         # from https://www.youtube.com/user/DeusExOfficial 
2130         'url': 'https://www.youtube.com/channel/UCs0ifCMCm1icqRbqhUINa0w', 
2131         'playlist_mincount': 64, 
2133             'id': 'UUs0ifCMCm1icqRbqhUINa0w', 
2134             'title': 'Uploads from Deus Ex', 
2139     def suitable(cls
, url
): 
2140         return (False if YoutubePlaylistsIE
.suitable(url
) or YoutubeLiveIE
.suitable(url
) 
2141                 else super(YoutubeChannelIE
, cls
).suitable(url
)) 
2143     def _build_template_url(self
, url
, channel_id
): 
2144         return self
._TEMPLATE
_URL 
% channel_id
 
2146     def _real_extract(self
, url
): 
2147         channel_id 
= self
._match
_id
(url
) 
2149         url 
= self
._build
_template
_url
(url
, channel_id
) 
2151         # Channel by page listing is restricted to 35 pages of 30 items, i.e. 1050 videos total (see #5778) 
2152         # Workaround by extracting as a playlist if managed to obtain channel playlist URL 
2153         # otherwise fallback on channel by page extraction 
2154         channel_page 
= self
._download
_webpage
( 
2155             url 
+ '?view=57', channel_id
, 
2156             'Downloading channel page', fatal
=False) 
2157         if channel_page 
is False: 
2158             channel_playlist_id 
= False 
2160             channel_playlist_id 
= self
._html
_search
_meta
( 
2161                 'channelId', channel_page
, 'channel id', default
=None) 
2162             if not channel_playlist_id
: 
2163                 channel_url 
= self
._html
_search
_meta
( 
2164                     ('al:ios:url', 'twitter:app:url:iphone', 'twitter:app:url:ipad'), 
2165                     channel_page
, 'channel url', default
=None) 
2167                     channel_playlist_id 
= self
._search
_regex
( 
2168                         r
'vnd\.youtube://user/([0-9A-Za-z_-]+)', 
2169                         channel_url
, 'channel id', default
=None) 
2170         if channel_playlist_id 
and channel_playlist_id
.startswith('UC'): 
2171             playlist_id 
= 'UU' + channel_playlist_id
[2:] 
2172             return self
.url_result( 
2173                 compat_urlparse
.urljoin(url
, '/playlist?list=%s' % playlist_id
), 'YoutubePlaylist') 
2175         channel_page 
= self
._download
_webpage
(url
, channel_id
, 'Downloading page #1') 
2176         autogenerated 
= re
.search(r
'''(?x) 
2178                     channel-header-autogenerated-label| 
2179                     yt-channel-title-autogenerated 
2180                 )[^"]*"''', channel_page
) is not None 
2183             # The videos are contained in a single page 
2184             # the ajax pages can't be used, they are empty 
2187                     video_id
, 'Youtube', video_id
=video_id
, 
2188                     video_title
=video_title
) 
2189                 for video_id
, video_title 
in self
.extract_videos_from_page(channel_page
)] 
2190             return self
.playlist_result(entries
, channel_id
) 
2193             next(self
._entries
(channel_page
, channel_id
)) 
2194         except StopIteration: 
2195             alert_message 
= self
._html
_search
_regex
( 
2196                 r
'(?s)<div[^>]+class=(["\']).*?
\byt
-alert
-message
\b.*?\
1[^
>]*>(?P
<alert
>[^
<]+)</div
>', 
2197                 channel_page, 'alert
', default=None, group='alert
') 
2199                 raise ExtractorError('Youtube said
: %s' % alert_message, expected=True) 
2201         return self.playlist_result(self._entries(channel_page, channel_id), channel_id) 
2204 class YoutubeUserIE(YoutubeChannelIE): 
2205     IE_DESC = 'YouTube
.com user 
videos (URL 
or "ytuser" keyword
)' 
2206     _VALID_URL = r'(?
:(?
:https?
://(?
:\w
+\
.)?youtube\
.com
/(?
:(?P
<user
>user|c
)/)?
(?
!(?
:attribution_link|watch|results
)(?
:$|
[^a
-z_A
-Z0
-9-])))|ytuser
:)(?
!feed
/)(?P
<id>[A
-Za
-z0
-9_-]+)' 
2207     _TEMPLATE_URL = 'https
://www
.youtube
.com
/%s/%s/videos
' 
2208     IE_NAME = 'youtube
:user
' 
2211         'url
': 'https
://www
.youtube
.com
/user
/TheLinuxFoundation
', 
2212         'playlist_mincount
': 320, 
2214             'id': 'UUfX55Sx5hEFjoC3cNs6mCUQ
', 
2215             'title
': 'Uploads 
from The Linux Foundation
', 
2218         # Only available via https://www.youtube.com/c/12minuteathlete/videos 
2219         # but not https://www.youtube.com/user/12minuteathlete/videos 
2220         'url
': 'https
://www
.youtube
.com
/c
/12minuteathlete
/videos
', 
2221         'playlist_mincount
': 249, 
2223             'id': 'UUVjM
-zV6_opMDx7WYxnjZiQ
', 
2224             'title
': 'Uploads 
from 12 Minute Athlete
', 
2227         'url
': 'ytuser
:phihag
', 
2228         'only_matching
': True, 
2230         'url
': 'https
://www
.youtube
.com
/c
/gametrailers
', 
2231         'only_matching
': True, 
2233         'url
': 'https
://www
.youtube
.com
/gametrailers
', 
2234         'only_matching
': True, 
2236         # This channel is not available. 
2237         'url
': 'https
://www
.youtube
.com
/user
/kananishinoSMEJ
/videos
', 
2238         'only_matching
': True, 
2242     def suitable(cls, url): 
2243         # Don't 
return True if the url can be extracted 
with other youtube
 
2244         # extractor, the regex would is too permissive and it would match. 
2245         other_yt_ies 
= iter(klass 
for (name
, klass
) in globals().items() if name
.startswith('Youtube') and name
.endswith('IE') and klass 
is not cls
) 
2246         if any(ie
.suitable(url
) for ie 
in other_yt_ies
): 
2249             return super(YoutubeUserIE
, cls
).suitable(url
) 
2251     def _build_template_url(self
, url
, channel_id
): 
2252         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2253         return self
._TEMPLATE
_URL 
% (mobj
.group('user') or 'user', mobj
.group('id')) 
2256 class YoutubeLiveIE(YoutubeBaseInfoExtractor
): 
2257     IE_DESC 
= 'YouTube.com live streams' 
2258     _VALID_URL 
= r
'(?P<base_url>https?://(?:\w+\.)?youtube\.com/(?:(?:user|channel|c)/)?(?P<id>[^/]+))/live' 
2259     IE_NAME 
= 'youtube:live' 
2262         'url': 'https://www.youtube.com/user/TheYoungTurks/live', 
2264             'id': 'a48o2S1cPoo', 
2266             'title': 'The Young Turks - Live Main Show', 
2267             'uploader': 'The Young Turks', 
2268             'uploader_id': 'TheYoungTurks', 
2269             'uploader_url': r
're:https?://(?:www\.)?youtube\.com/user/TheYoungTurks', 
2270             'upload_date': '20150715', 
2271             'license': 'Standard YouTube License', 
2272             'description': 'md5:438179573adcdff3c97ebb1ee632b891', 
2273             'categories': ['News & Politics'], 
2274             'tags': ['Cenk Uygur (TV Program Creator)', 'The Young Turks (Award-Winning Work)', 'Talk Show (TV Genre)'], 
2276             'dislike_count': int, 
2279             'skip_download': True, 
2282         'url': 'https://www.youtube.com/channel/UC1yBKRuGpC1tSM73A0ZjYjQ/live', 
2283         'only_matching': True, 
2285         'url': 'https://www.youtube.com/c/CommanderVideoHq/live', 
2286         'only_matching': True, 
2288         'url': 'https://www.youtube.com/TheYoungTurks/live', 
2289         'only_matching': True, 
2292     def _real_extract(self
, url
): 
2293         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2294         channel_id 
= mobj
.group('id') 
2295         base_url 
= mobj
.group('base_url') 
2296         webpage 
= self
._download
_webpage
(url
, channel_id
, fatal
=False) 
2298             page_type 
= self
._og
_search
_property
( 
2299                 'type', webpage
, 'page type', default
=None) 
2300             video_id 
= self
._html
_search
_meta
( 
2301                 'videoId', webpage
, 'video id', default
=None) 
2302             if page_type 
== 'video' and video_id 
and re
.match(r
'^[0-9A-Za-z_-]{11}$', video_id
): 
2303                 return self
.url_result(video_id
, YoutubeIE
.ie_key()) 
2304         return self
.url_result(base_url
) 
2307 class YoutubePlaylistsIE(YoutubePlaylistsBaseInfoExtractor
): 
2308     IE_DESC 
= 'YouTube.com user/channel playlists' 
2309     _VALID_URL 
= r
'https?://(?:\w+\.)?youtube\.com/(?:user|channel)/(?P<id>[^/]+)/playlists' 
2310     IE_NAME 
= 'youtube:playlists' 
2313         'url': 'https://www.youtube.com/user/ThirstForScience/playlists', 
2314         'playlist_mincount': 4, 
2316             'id': 'ThirstForScience', 
2317             'title': 'Thirst for Science', 
2320         # with "Load more" button 
2321         'url': 'https://www.youtube.com/user/igorkle1/playlists?view=1&sort=dd', 
2322         'playlist_mincount': 70, 
2325             'title': 'ŠŠ³Š¾ŃŃ ŠŠ»ŠµŠ¹Š½ŠµŃ', 
2328         'url': 'https://www.youtube.com/channel/UCiU1dHvZObB2iP6xkJ__Icw/playlists', 
2329         'playlist_mincount': 17, 
2331             'id': 'UCiU1dHvZObB2iP6xkJ__Icw', 
2332             'title': 'Chem Player', 
2337 class YoutubeSearchIE(SearchInfoExtractor
, YoutubePlaylistIE
): 
2338     IE_DESC 
= 'YouTube.com searches' 
2339     # there doesn't appear to be a real limit, for example if you search for 
2340     # 'python' you get more than 8.000.000 results 
2341     _MAX_RESULTS 
= float('inf') 
2342     IE_NAME 
= 'youtube:search' 
2343     _SEARCH_KEY 
= 'ytsearch' 
2344     _EXTRA_QUERY_ARGS 
= {} 
2347     def _get_n_results(self
, query
, n
): 
2348         """Get a specified number of results for a query""" 
2354             'search_query': query
.encode('utf-8'), 
2356         url_query
.update(self
._EXTRA
_QUERY
_ARGS
) 
2357         result_url 
= 'https://www.youtube.com/results?' + compat_urllib_parse_urlencode(url_query
) 
2359         for pagenum 
in itertools
.count(1): 
2360             data 
= self
._download
_json
( 
2361                 result_url
, video_id
='query "%s"' % query
, 
2362                 note
='Downloading page %s' % pagenum
, 
2363                 errnote
='Unable to download API page', 
2364                 query
={'spf': 'navigate'}) 
2365             html_content 
= data
[1]['body']['content'] 
2367             if 'class="search-message' in html_content
: 
2368                 raise ExtractorError( 
2369                     '[youtube] No video results', expected
=True) 
2371             new_videos 
= self
._ids
_to
_results
(orderedSet(re
.findall( 
2372                 r
'href="/watch\?v=(.{11})', html_content
))) 
2373             videos 
+= new_videos
 
2374             if not new_videos 
or len(videos
) > limit
: 
2376             next_link 
= self
._html
_search
_regex
( 
2377                 r
'href="(/results\?[^"]*\bsp=[^"]+)"[^>]*>\s*<span[^>]+class="[^"]*\byt-uix-button-content\b[^"]*"[^>]*>Next', 
2378                 html_content
, 'next link', default
=None) 
2379             if next_link 
is None: 
2381             result_url 
= compat_urlparse
.urljoin('https://www.youtube.com/', next_link
) 
2385         return self
.playlist_result(videos
, query
) 
2388 class YoutubeSearchDateIE(YoutubeSearchIE
): 
2389     IE_NAME 
= YoutubeSearchIE
.IE_NAME 
+ ':date' 
2390     _SEARCH_KEY 
= 'ytsearchdate' 
2391     IE_DESC 
= 'YouTube.com searches, newest videos first' 
2392     _EXTRA_QUERY_ARGS 
= {'search_sort': 'video_date_uploaded'} 
2395 class YoutubeSearchURLIE(YoutubePlaylistBaseInfoExtractor
): 
2396     IE_DESC 
= 'YouTube.com search URLs' 
2397     IE_NAME 
= 'youtube:search_url' 
2398     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/results\?(.*?&)?(?:search_query|q)=(?P<query>[^&]+)(?:[&]|$)' 
2399     _VIDEO_RE 
= r
'href="\s*/watch\?v=(?P<id>[0-9A-Za-z_-]{11})(?:[^"]*"[^>]+\btitle="(?P<title>[^"]+))?' 
2401         'url': 'https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', 
2402         'playlist_mincount': 5, 
2404             'title': 'youtube-dl test video', 
2407         'url': 'https://www.youtube.com/results?q=test&sp=EgQIBBgB', 
2408         'only_matching': True, 
2411     def _real_extract(self
, url
): 
2412         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2413         query 
= compat_urllib_parse_unquote_plus(mobj
.group('query')) 
2414         webpage 
= self
._download
_webpage
(url
, query
) 
2415         return self
.playlist_result(self
._process
_page
(webpage
), playlist_title
=query
) 
2418 class YoutubeShowIE(YoutubePlaylistsBaseInfoExtractor
): 
2419     IE_DESC 
= 'YouTube.com (multi-season) shows' 
2420     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/show/(?P<id>[^?#]*)' 
2421     IE_NAME 
= 'youtube:show' 
2423         'url': 'https://www.youtube.com/show/airdisasters', 
2424         'playlist_mincount': 5, 
2426             'id': 'airdisasters', 
2427             'title': 'Air Disasters', 
2431     def _real_extract(self
, url
): 
2432         playlist_id 
= self
._match
_id
(url
) 
2433         return super(YoutubeShowIE
, self
)._real
_extract
( 
2434             'https://www.youtube.com/show/%s/playlists' % playlist_id
) 
2437 class YoutubeFeedsInfoExtractor(YoutubeBaseInfoExtractor
): 
2439     Base class for feed extractors 
2440     Subclasses must define the _FEED_NAME and _PLAYLIST_TITLE properties. 
2442     _LOGIN_REQUIRED 
= True 
2446         return 'youtube:%s' % self
._FEED
_NAME
 
2448     def _real_initialize(self
): 
2451     def _real_extract(self
, url
): 
2452         page 
= self
._download
_webpage
( 
2453             'https://www.youtube.com/feed/%s' % self
._FEED
_NAME
, self
._PLAYLIST
_TITLE
) 
2455         # The extraction process is the same as for playlists, but the regex 
2456         # for the video ids doesn't contain an index 
2458         more_widget_html 
= content_html 
= page
 
2459         for page_num 
in itertools
.count(1): 
2460             matches 
= re
.findall(r
'href="\s*/watch\?v=([0-9A-Za-z_-]{11})', content_html
) 
2462             # 'recommended' feed has infinite 'load more' and each new portion spins 
2463             # the same videos in (sometimes) slightly different order, so we'll check 
2464             # for unicity and break when portion has no new videos 
2465             new_ids 
= filter(lambda video_id
: video_id 
not in ids
, orderedSet(matches
)) 
2471             mobj 
= re
.search(r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html
) 
2475             more 
= self
._download
_json
( 
2476                 'https://youtube.com/%s' % mobj
.group('more'), self
._PLAYLIST
_TITLE
, 
2477                 'Downloading page #%s' % page_num
, 
2478                 transform_source
=uppercase_escape
) 
2479             content_html 
= more
['content_html'] 
2480             more_widget_html 
= more
['load_more_widget_html'] 
2482         return self
.playlist_result( 
2483             self
._ids
_to
_results
(ids
), playlist_title
=self
._PLAYLIST
_TITLE
) 
2486 class YoutubeWatchLaterIE(YoutubePlaylistIE
): 
2487     IE_NAME 
= 'youtube:watchlater' 
2488     IE_DESC 
= 'Youtube watch later list, ":ytwatchlater" for short (requires authentication)' 
2489     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/(?:feed/watch_later|(?:playlist|watch)\?(?:.+&)?list=WL)|:ytwatchlater' 
2492         'url': 'https://www.youtube.com/playlist?list=WL', 
2493         'only_matching': True, 
2495         'url': 'https://www.youtube.com/watch?v=bCNU9TrbiRk&index=1&list=WL', 
2496         'only_matching': True, 
2499     def _real_extract(self
, url
): 
2500         _
, video 
= self
._check
_download
_just
_video
(url
, 'WL') 
2503         _
, playlist 
= self
._extract
_playlist
('WL') 
2507 class YoutubeFavouritesIE(YoutubeBaseInfoExtractor
): 
2508     IE_NAME 
= 'youtube:favorites' 
2509     IE_DESC 
= 'YouTube.com favourite videos, ":ytfav" for short (requires authentication)' 
2510     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/my_favorites|:ytfav(?:ou?rites)?' 
2511     _LOGIN_REQUIRED 
= True 
2513     def _real_extract(self
, url
): 
2514         webpage 
= self
._download
_webpage
('https://www.youtube.com/my_favorites', 'Youtube Favourites videos') 
2515         playlist_id 
= self
._search
_regex
(r
'list=(.+?)["&]', webpage
, 'favourites playlist id') 
2516         return self
.url_result(playlist_id
, 'YoutubePlaylist') 
2519 class YoutubeRecommendedIE(YoutubeFeedsInfoExtractor
): 
2520     IE_DESC 
= 'YouTube.com recommended videos, ":ytrec" for short (requires authentication)' 
2521     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/feed/recommended|:ytrec(?:ommended)?' 
2522     _FEED_NAME 
= 'recommended' 
2523     _PLAYLIST_TITLE 
= 'Youtube Recommended videos' 
2526 class YoutubeSubscriptionsIE(YoutubeFeedsInfoExtractor
): 
2527     IE_DESC 
= 'YouTube.com subscriptions feed, "ytsubs" keyword (requires authentication)' 
2528     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/feed/subscriptions|:ytsubs(?:criptions)?' 
2529     _FEED_NAME 
= 'subscriptions' 
2530     _PLAYLIST_TITLE 
= 'Youtube Subscriptions' 
2533 class YoutubeHistoryIE(YoutubeFeedsInfoExtractor
): 
2534     IE_DESC 
= 'Youtube watch history, ":ythistory" for short (requires authentication)' 
2535     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/feed/history|:ythistory' 
2536     _FEED_NAME 
= 'history' 
2537     _PLAYLIST_TITLE 
= 'Youtube History' 
2540 class YoutubeTruncatedURLIE(InfoExtractor
): 
2541     IE_NAME 
= 'youtube:truncated_url' 
2542     IE_DESC 
= False  # Do not list 
2543     _VALID_URL 
= r
'''(?x) 
2545         (?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/ 
2548             annotation_id=annotation_[^&]+| 
2554             attribution_link\?a=[^&]+ 
2560         'url': 'https://www.youtube.com/watch?annotation_id=annotation_3951667041', 
2561         'only_matching': True, 
2563         'url': 'https://www.youtube.com/watch?', 
2564         'only_matching': True, 
2566         'url': 'https://www.youtube.com/watch?x-yt-cl=84503534', 
2567         'only_matching': True, 
2569         'url': 'https://www.youtube.com/watch?feature=foo', 
2570         'only_matching': True, 
2572         'url': 'https://www.youtube.com/watch?hl=en-GB', 
2573         'only_matching': True, 
2575         'url': 'https://www.youtube.com/watch?t=2372', 
2576         'only_matching': True, 
2579     def _real_extract(self
, url
): 
2580         raise ExtractorError( 
2581             'Did you forget to quote the URL? Remember that & is a meta ' 
2582             'character in most shells, so you want to put the URL in quotes, ' 
2584             '"https://www.youtube.com/watch?feature=foo&v=BaW_jenozKc" ' 
2585             ' or simply  youtube-dl BaW_jenozKc  .', 
2589 class YoutubeTruncatedIDIE(InfoExtractor
): 
2590     IE_NAME 
= 'youtube:truncated_id' 
2591     IE_DESC 
= False  # Do not list 
2592     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/watch\?v=(?P<id>[0-9A-Za-z_-]{1,10})$' 
2595         'url': 'https://www.youtube.com/watch?v=N_708QY7Ob', 
2596         'only_matching': True, 
2599     def _real_extract(self
, url
): 
2600         video_id 
= self
._match
_id
(url
) 
2601         raise ExtractorError( 
2602             'Incomplete YouTube ID %s. URL %s looks truncated.' % (video_id
, url
),