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
, 
  52 class YoutubeBaseInfoExtractor(InfoExtractor
): 
  53     """Provide base functions for Youtube extractors""" 
  54     _LOGIN_URL 
= 'https://accounts.google.com/ServiceLogin' 
  55     _TWOFACTOR_URL 
= 'https://accounts.google.com/signin/challenge' 
  56     _PASSWORD_CHALLENGE_URL 
= 'https://accounts.google.com/signin/challenge/sl/password' 
  57     _NETRC_MACHINE 
= 'youtube' 
  58     # If True it will raise an error if no login info is provided 
  59     _LOGIN_REQUIRED 
= False 
  61     def _set_language(self
): 
  63             '.youtube.com', 'PREF', 'f1=50000000&hl=en', 
  64             # YouTube sets the expire time to about two months 
  65             expire_time
=time
.time() + 2 * 30 * 24 * 3600) 
  67     def _ids_to_results(self
, ids
): 
  69             self
.url_result(vid_id
, 'Youtube', video_id
=vid_id
) 
  74         Attempt to log in to YouTube. 
  75         True is returned if successful or skipped. 
  76         False is returned if login failed. 
  78         If _LOGIN_REQUIRED is set and no authentication was provided, an error is raised. 
  80         (username
, password
) = self
._get
_login
_info
() 
  81         # No authentication to be performed 
  83             if self
._LOGIN
_REQUIRED
: 
  84                 raise ExtractorError('No login info available, needed for using %s.' % self
.IE_NAME
, expected
=True) 
  87         login_page 
= self
._download
_webpage
( 
  88             self
._LOGIN
_URL
, None, 
  89             note
='Downloading login page', 
  90             errnote
='unable to fetch login page', fatal
=False) 
  91         if login_page 
is False: 
  94         login_form 
= self
._hidden
_inputs
(login_page
) 
  97             'checkConnection': 'youtube', 
 102         login_results 
= self
._download
_webpage
( 
 103             self
._PASSWORD
_CHALLENGE
_URL
, None, 
 104             note
='Logging in', errnote
='unable to log in', fatal
=False, 
 105             data
=urlencode_postdata(login_form
)) 
 106         if login_results 
is False: 
 109         error_msg 
= self
._html
_search
_regex
( 
 110             r
'<[^>]+id="errormsg_0_Passwd"[^>]*>([^<]+)<', 
 111             login_results
, 'error message', default
=None) 
 113             raise ExtractorError('Unable to login: %s' % error_msg
, expected
=True) 
 115         if re
.search(r
'id="errormsg_0_Passwd"', login_results
) is not None: 
 116             raise ExtractorError('Please use your account password and a two-factor code instead of an application-specific password.', expected
=True) 
 119         # TODO add SMS and phone call support - these require making a request and then prompting the user 
 121         if re
.search(r
'(?i)<form[^>]+id="challenge"', login_results
) is not None: 
 122             tfa_code 
= self
._get
_tfa
_info
('2-step verification code') 
 125                 self
._downloader
.report_warning( 
 126                     'Two-factor authentication required. Provide it either interactively or with --twofactor <code>' 
 127                     '(Note that only TOTP (Google Authenticator App) codes work at this time.)') 
 130             tfa_code 
= remove_start(tfa_code
, 'G-') 
 132             tfa_form_strs 
= self
._form
_hidden
_inputs
('challenge', login_results
) 
 134             tfa_form_strs
.update({ 
 139             tfa_data 
= urlencode_postdata(tfa_form_strs
) 
 141             tfa_req 
= sanitized_Request(self
._TWOFACTOR
_URL
, tfa_data
) 
 142             tfa_results 
= self
._download
_webpage
( 
 144                 note
='Submitting TFA code', errnote
='unable to submit tfa', fatal
=False) 
 146             if tfa_results 
is False: 
 149             if re
.search(r
'(?i)<form[^>]+id="challenge"', tfa_results
) is not None: 
 150                 self
._downloader
.report_warning('Two-factor code expired or invalid. Please try again, or use a one-use backup code instead.') 
 152             if re
.search(r
'(?i)<form[^>]+id="gaia_loginform"', tfa_results
) is not None: 
 153                 self
._downloader
.report_warning('unable to log in - did the page structure change?') 
 155             if re
.search(r
'smsauth-interstitial-reviewsettings', tfa_results
) is not None: 
 156                 self
._downloader
.report_warning('Your Google account has a security notice. Please log in on your web browser, resolve the notice, and try again.') 
 159         if re
.search(r
'(?i)<form[^>]+id="gaia_loginform"', login_results
) is not None: 
 160             self
._downloader
.report_warning('unable to log in: bad username or password') 
 164     def _real_initialize(self
): 
 165         if self
._downloader 
is None: 
 168         if not self
._login
(): 
 172 class YoutubeEntryListBaseInfoExtractor(YoutubeBaseInfoExtractor
): 
 173     # Extract entries from page with "Load more" button 
 174     def _entries(self
, page
, playlist_id
): 
 175         more_widget_html 
= content_html 
= page
 
 176         for page_num 
in itertools
.count(1): 
 177             for entry 
in self
._process
_page
(content_html
): 
 180             mobj 
= re
.search(r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html
) 
 184             more 
= self
._download
_json
( 
 185                 'https://youtube.com/%s' % mobj
.group('more'), playlist_id
, 
 186                 'Downloading page #%s' % page_num
, 
 187                 transform_source
=uppercase_escape
) 
 188             content_html 
= more
['content_html'] 
 189             if not content_html
.strip(): 
 190                 # Some webpages show a "Load more" button but they don't 
 193             more_widget_html 
= more
['load_more_widget_html'] 
 196 class YoutubePlaylistBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor
): 
 197     def _process_page(self
, content
): 
 198         for video_id
, video_title 
in self
.extract_videos_from_page(content
): 
 199             yield self
.url_result(video_id
, 'Youtube', video_id
, video_title
) 
 201     def extract_videos_from_page(self
, page
): 
 204         for mobj 
in re
.finditer(self
._VIDEO
_RE
, page
): 
 205             # The link with index 0 is not the first video of the playlist (not sure if still actual) 
 206             if 'index' in mobj
.groupdict() and mobj
.group('id') == '0': 
 208             video_id 
= mobj
.group('id') 
 209             video_title 
= unescapeHTML(mobj
.group('title')) 
 211                 video_title 
= video_title
.strip() 
 213                 idx 
= ids_in_page
.index(video_id
) 
 214                 if video_title 
and not titles_in_page
[idx
]: 
 215                     titles_in_page
[idx
] = video_title
 
 217                 ids_in_page
.append(video_id
) 
 218                 titles_in_page
.append(video_title
) 
 219         return zip(ids_in_page
, titles_in_page
) 
 222 class YoutubePlaylistsBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor
): 
 223     def _process_page(self
, content
): 
 224         for playlist_id 
in orderedSet(re
.findall( 
 225                 r
'<h3[^>]+class="[^"]*yt-lockup-title[^"]*"[^>]*><a[^>]+href="/?playlist\?list=([0-9A-Za-z-_]{10,})"', 
 227             yield self
.url_result( 
 228                 'https://www.youtube.com/playlist?list=%s' % playlist_id
, 'YoutubePlaylist') 
 230     def _real_extract(self
, url
): 
 231         playlist_id 
= self
._match
_id
(url
) 
 232         webpage 
= self
._download
_webpage
(url
, playlist_id
) 
 233         title 
= self
._og
_search
_title
(webpage
, fatal
=False) 
 234         return self
.playlist_result(self
._entries
(webpage
, playlist_id
), playlist_id
, title
) 
 237 class YoutubeIE(YoutubeBaseInfoExtractor
): 
 238     IE_DESC 
= 'YouTube.com' 
 239     _VALID_URL 
= r
"""(?x)^ 
 241                          (?:https?://|//)                                    # http(s):// or protocol-independent URL 
 242                          (?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/| 
 243                             (?:www\.)?deturl\.com/www\.youtube\.com/| 
 244                             (?:www\.)?pwnyoutube\.com/| 
 245                             (?:www\.)?yourepeat\.com/| 
 246                             tube\.majestyc\.net/| 
 247                             youtube\.googleapis\.com/)                        # the various hostnames, with wildcard subdomains 
 248                          (?:.*?\#/)?                                          # handle anchor (#/) redirect urls 
 249                          (?:                                                  # the various things that can precede the ID: 
 250                              (?:(?:v|embed|e)/(?!videoseries))                # v/ or embed/ or e/ 
 251                              |(?:                                             # or the v= param in all its forms 
 252                                  (?:(?:watch|movie)(?:_popup)?(?:\.php)?/?)?  # preceding watch(_popup|.php) or nothing (like /?v=xxxx) 
 253                                  (?:\?|\#!?)                                  # the params delimiter ? or # or #! 
 254                                  (?:.*?[&;])??                                # any other preceding param (like /?s=tuff&v=xxxx or ?s=tuff&v=V36LpHqtcDY) 
 259                             youtu\.be|                                        # just youtu.be/xxxx 
 260                             vid\.plus|                                        # or vid.plus/xxxx 
 261                             zwearz\.com/watch|                                # or zwearz.com/watch/xxxx 
 263                          |(?:www\.)?cleanvideosearch\.com/media/action/yt/watch\?videoId= 
 265                      )?                                                       # all until now is optional -> you can pass the naked ID 
 266                      ([0-9A-Za-z_-]{11})                                      # here is it! the YouTube video ID 
 267                      (?!.*?\blist=)                                            # combined list/video URLs are handled by the playlist IE 
 268                      (?(1).+)?                                                # if we found the ID, everything can follow 
 270     _NEXT_URL_RE 
= r
'[\?&]next_url=([^&]+)' 
 272         '5': {'ext': 'flv', 'width': 400, 'height': 240, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'}, 
 273         '6': {'ext': 'flv', 'width': 450, 'height': 270, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'}, 
 274         '13': {'ext': '3gp', 'acodec': 'aac', 'vcodec': 'mp4v'}, 
 275         '17': {'ext': '3gp', 'width': 176, 'height': 144, 'acodec': 'aac', 'abr': 24, 'vcodec': 'mp4v'}, 
 276         '18': {'ext': 'mp4', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 96, 'vcodec': 'h264'}, 
 277         '22': {'ext': 'mp4', 'width': 1280, 'height': 720, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'}, 
 278         '34': {'ext': 'flv', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 279         '35': {'ext': 'flv', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 280         # itag 36 videos are either 320x180 (BaW_jenozKc) or 320x240 (__2ABJjxzNo), abr varies as well 
 281         '36': {'ext': '3gp', 'width': 320, 'acodec': 'aac', 'vcodec': 'mp4v'}, 
 282         '37': {'ext': 'mp4', 'width': 1920, 'height': 1080, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'}, 
 283         '38': {'ext': 'mp4', 'width': 4096, 'height': 3072, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'}, 
 284         '43': {'ext': 'webm', 'width': 640, 'height': 360, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'}, 
 285         '44': {'ext': 'webm', 'width': 854, 'height': 480, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'}, 
 286         '45': {'ext': 'webm', 'width': 1280, 'height': 720, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'}, 
 287         '46': {'ext': 'webm', 'width': 1920, 'height': 1080, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'}, 
 288         '59': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 289         '78': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'}, 
 293         '82': {'ext': 'mp4', 'height': 360, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20}, 
 294         '83': {'ext': 'mp4', 'height': 480, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20}, 
 295         '84': {'ext': 'mp4', 'height': 720, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20}, 
 296         '85': {'ext': 'mp4', 'height': 1080, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20}, 
 297         '100': {'ext': 'webm', 'height': 360, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8', 'preference': -20}, 
 298         '101': {'ext': 'webm', 'height': 480, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20}, 
 299         '102': {'ext': 'webm', 'height': 720, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20}, 
 301         # Apple HTTP Live Streaming 
 302         '91': {'ext': 'mp4', 'height': 144, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10}, 
 303         '92': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10}, 
 304         '93': {'ext': 'mp4', 'height': 360, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10}, 
 305         '94': {'ext': 'mp4', 'height': 480, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10}, 
 306         '95': {'ext': 'mp4', 'height': 720, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10}, 
 307         '96': {'ext': 'mp4', 'height': 1080, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10}, 
 308         '132': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10}, 
 309         '151': {'ext': 'mp4', 'height': 72, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 24, 'vcodec': 'h264', 'preference': -10}, 
 312         '133': {'ext': 'mp4', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 313         '134': {'ext': 'mp4', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 314         '135': {'ext': 'mp4', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 315         '136': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 316         '137': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 317         '138': {'ext': 'mp4', 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40},  # Height can vary (https://github.com/rg3/youtube-dl/issues/4559) 
 318         '160': {'ext': 'mp4', 'height': 144, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 319         '264': {'ext': 'mp4', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 320         '298': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60, 'preference': -40}, 
 321         '299': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60, 'preference': -40}, 
 322         '266': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'h264', 'preference': -40}, 
 325         '139': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 48, 'preference': -50, 'container': 'm4a_dash'}, 
 326         '140': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 128, 'preference': -50, 'container': 'm4a_dash'}, 
 327         '141': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 256, 'preference': -50, 'container': 'm4a_dash'}, 
 328         '256': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'preference': -50, 'container': 'm4a_dash'}, 
 329         '258': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'preference': -50, 'container': 'm4a_dash'}, 
 332         '167': {'ext': 'webm', 'height': 360, 'width': 640, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 333         '168': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 334         '169': {'ext': 'webm', 'height': 720, 'width': 1280, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 335         '170': {'ext': 'webm', 'height': 1080, 'width': 1920, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 336         '218': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 337         '219': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8', 'preference': -40}, 
 338         '278': {'ext': 'webm', 'height': 144, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp9', 'preference': -40}, 
 339         '242': {'ext': 'webm', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 340         '243': {'ext': 'webm', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 341         '244': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 342         '245': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 343         '246': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 344         '247': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 345         '248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 346         '271': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 347         # itag 272 videos are either 3840x2160 (e.g. RtoitU2A-3E) or 7680x4320 (sLprVF6d7Ug) 
 348         '272': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 349         '302': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 350         '303': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 351         '308': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 352         '313': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'preference': -40}, 
 353         '315': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60, 'preference': -40}, 
 356         '171': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 128, 'preference': -50}, 
 357         '172': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 256, 'preference': -50}, 
 359         # Dash webm audio with opus inside 
 360         '249': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 50, 'preference': -50}, 
 361         '250': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 70, 'preference': -50}, 
 362         '251': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 160, 'preference': -50}, 
 365         '_rtmp': {'protocol': 'rtmp'}, 
 367     _SUBTITLE_FORMATS 
= ('ttml', 'vtt') 
 372             'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&t=1s&end=9', 
 376                 'title': 'youtube-dl test video "\'/\\Ƥāš', 
 377                 'uploader': 'Philipp Hagemeister', 
 378                 'uploader_id': 'phihag', 
 379                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/phihag', 
 380                 'upload_date': '20121002', 
 381                 'license': 'Standard YouTube License', 
 382                 '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 .', 
 383                 'categories': ['Science & Technology'], 
 384                 'tags': ['youtube-dl'], 
 386                 'dislike_count': int, 
 392             'url': 'https://www.youtube.com/watch?v=UxxajLWwzqY', 
 393             'note': 'Test generic use_cipher_signature video (#897)', 
 397                 'upload_date': '20120506', 
 398                 'title': 'Icona Pop - I Love It (feat. Charli XCX) [OFFICIAL VIDEO]', 
 399                 'alt_title': 'I Love It (feat. Charli XCX)', 
 400                 'description': 'md5:f3ceb5ef83a08d95b9d146f973157cc8', 
 401                 'tags': ['Icona Pop i love it', 'sweden', 'pop music', 'big beat records', 'big beat', 'charli', 
 402                          'xcx', 'charli xcx', 'girls', 'hbo', 'i love it', "i don't care", 'icona', 'pop', 
 403                          'iconic ep', 'iconic', 'love', 'it'], 
 404                 'uploader': 'Icona Pop', 
 405                 'uploader_id': 'IconaPop', 
 406                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/IconaPop', 
 407                 'license': 'Standard YouTube License', 
 408                 'creator': 'Icona Pop', 
 412             'url': 'https://www.youtube.com/watch?v=07FYdnEawAQ', 
 413             'note': 'Test VEVO video with age protection (#956)', 
 417                 'upload_date': '20130703', 
 418                 'title': 'Justin Timberlake - Tunnel Vision (Explicit)', 
 419                 'alt_title': 'Tunnel Vision', 
 420                 'description': 'md5:64249768eec3bc4276236606ea996373', 
 421                 'uploader': 'justintimberlakeVEVO', 
 422                 'uploader_id': 'justintimberlakeVEVO', 
 423                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/justintimberlakeVEVO', 
 424                 'license': 'Standard YouTube License', 
 425                 'creator': 'Justin Timberlake', 
 430             'url': '//www.YouTube.com/watch?v=yZIXLfi8CZQ', 
 431             'note': 'Embed-only video (#1746)', 
 435                 'upload_date': '20120608', 
 436                 'title': 'Principal Sexually Assaults A Teacher - Episode 117 - 8th June 2012', 
 437                 'description': 'md5:09b78bd971f1e3e289601dfba15ca4f7', 
 438                 'uploader': 'SET India', 
 439                 'uploader_id': 'setindia', 
 440                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/setindia', 
 441                 'license': 'Standard YouTube License', 
 446             'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&v=UxxajLWwzqY', 
 447             'note': 'Use the first video ID in the URL', 
 451                 'title': 'youtube-dl test video "\'/\\Ƥāš', 
 452                 'uploader': 'Philipp Hagemeister', 
 453                 'uploader_id': 'phihag', 
 454                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/phihag', 
 455                 'upload_date': '20121002', 
 456                 'license': 'Standard YouTube License', 
 457                 '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 .', 
 458                 'categories': ['Science & Technology'], 
 459                 'tags': ['youtube-dl'], 
 461                 'dislike_count': int, 
 464                 'skip_download': True, 
 468             'url': 'https://www.youtube.com/watch?v=a9LDPn-MO4I', 
 469             'note': '256k DASH audio (format 141) via DASH manifest', 
 473                 'upload_date': '20121002', 
 474                 'uploader_id': '8KVIDEO', 
 475                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/8KVIDEO', 
 477                 'uploader': '8KVIDEO', 
 478                 'license': 'Standard YouTube License', 
 479                 'title': 'UHDTV TEST 8K VIDEO.mp4' 
 482                 'youtube_include_dash_manifest': True, 
 485             'skip': 'format 141 not served anymore', 
 487         # DASH manifest with encrypted signature 
 489             'url': 'https://www.youtube.com/watch?v=IB3lcPjvWLA', 
 493                 'title': 'Afrojack, Spree Wilson - The Spark ft. Spree Wilson', 
 494                 'description': 'md5:12e7067fa6735a77bdcbb58cb1187d2d', 
 495                 'uploader': 'AfrojackVEVO', 
 496                 'uploader_id': 'AfrojackVEVO', 
 497                 'upload_date': '20131011', 
 498                 'license': 'Standard YouTube License', 
 501                 'youtube_include_dash_manifest': True, 
 502                 'format': '141/bestaudio[ext=m4a]', 
 505         # JS player signature function name containing $ 
 507             'url': 'https://www.youtube.com/watch?v=nfWlot6h_JM', 
 511                 'title': 'Taylor Swift - Shake It Off', 
 512                 'alt_title': 'Shake It Off', 
 513                 'description': 'md5:95f66187cd7c8b2c13eb78e1223b63c3', 
 514                 'uploader': 'TaylorSwiftVEVO', 
 515                 'uploader_id': 'TaylorSwiftVEVO', 
 516                 'upload_date': '20140818', 
 517                 'license': 'Standard YouTube License', 
 518                 'creator': 'Taylor Swift', 
 521                 'youtube_include_dash_manifest': True, 
 522                 'format': '141/bestaudio[ext=m4a]', 
 527             'url': 'https://www.youtube.com/watch?v=T4XJQO3qol8', 
 531                 'upload_date': '20100909', 
 532                 'uploader': 'The Amazing Atheist', 
 533                 'uploader_id': 'TheAmazingAtheist', 
 534                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/TheAmazingAtheist', 
 535                 'license': 'Standard YouTube License', 
 536                 'title': 'Burning Everyone\'s Koran', 
 537                 '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', 
 540         # Normal age-gate video (No vevo, embed allowed) 
 542             'url': 'https://youtube.com/watch?v=HtVdAasjOgU', 
 546                 'title': 'The Witcher 3: Wild Hunt - The Sword Of Destiny Trailer', 
 547                 'description': 're:(?s).{100,}About the Game\n.*?The Witcher 3: Wild Hunt.{100,}', 
 548                 'uploader': 'The Witcher', 
 549                 'uploader_id': 'WitcherGame', 
 550                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/WitcherGame', 
 551                 'upload_date': '20140605', 
 552                 'license': 'Standard YouTube License', 
 556         # Age-gate video with encrypted signature 
 558             'url': 'https://www.youtube.com/watch?v=6kLq3WMV1nU', 
 562                 'title': 'Dedication To My Ex (Miss That) (Lyric Video)', 
 563                 'description': 'md5:33765bb339e1b47e7e72b5490139bb41', 
 564                 'uploader': 'LloydVEVO', 
 565                 'uploader_id': 'LloydVEVO', 
 566                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/LloydVEVO', 
 567                 'upload_date': '20110629', 
 568                 'license': 'Standard YouTube License', 
 572         # video_info is None (https://github.com/rg3/youtube-dl/issues/4421) 
 574             'url': '__2ABJjxzNo', 
 578                 'upload_date': '20100430', 
 579                 'uploader_id': 'deadmau5', 
 580                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/deadmau5', 
 581                 'creator': 'deadmau5', 
 582                 'description': 'md5:12c56784b8032162bb936a5f76d55360', 
 583                 'uploader': 'deadmau5', 
 584                 'license': 'Standard YouTube License', 
 585                 'title': 'Deadmau5 - Some Chords (HD)', 
 586                 'alt_title': 'Some Chords', 
 588             'expected_warnings': [ 
 589                 'DASH manifest missing', 
 592         # Olympics (https://github.com/rg3/youtube-dl/issues/4431) 
 594             'url': 'lqQg6PlCWgI', 
 598                 'upload_date': '20150827', 
 599                 'uploader_id': 'olympic', 
 600                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/olympic', 
 601                 'license': 'Standard YouTube License', 
 602                 'description': 'HO09  - Women -  GER-AUS - Hockey - 31 July 2012 - London 2012 Olympic Games', 
 603                 'uploader': 'Olympic', 
 604                 'title': 'Hockey - Women -  GER-AUS - London 2012 Olympic Games', 
 607                 'skip_download': 'requires avconv', 
 612             'url': 'https://www.youtube.com/watch?v=_b-2C3KPAM0', 
 616                 'stretched_ratio': 16 / 9., 
 617                 'upload_date': '20110310', 
 618                 'uploader_id': 'AllenMeow', 
 619                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/AllenMeow', 
 620                 'description': 'made by Wacom from Korea | åå¹&å ę²¹ę·»é by TY\'s Allen | ęč¬heylisa00cavey1001ååøē±ę
ęä¾ę¢åēæ»čÆ', 
 621                 'uploader': 'å«č¾å«', 
 622                 'license': 'Standard YouTube License', 
 623                 'title': '[A-made] č®ę
å¦åå¹ē å¤Ŗå¦ ęå°±ęÆé樣ēäŗŗ', 
 626         # url_encoded_fmt_stream_map is empty string 
 628             'url': 'qEJwOuvDf7I', 
 632                 'title': 'ŠŠ±ŃŃŠ¶Š“ение ŃŃŠ“ебной ŠæŃŠ°ŠŗŃŠøŠŗŠø по Š²ŃбоŃам 14 ŃŠµŠ½ŃŃŠ±ŃŃ 2014 гоГа в ДанкŃ-ŠŠµŃŠµŃŠ±ŃŃŠ³Šµ', 
 634                 'upload_date': '20150404', 
 635                 'uploader_id': 'spbelect', 
 636                 'uploader': 'ŠŠ°Š±Š»ŃŠ“Š°ŃŠµŠ»Šø ŠŠµŃŠµŃŠ±ŃŃŠ³Š°', 
 639                 'skip_download': 'requires avconv', 
 641             'skip': 'This live event has ended.', 
 643         # Extraction from multiple DASH manifests (https://github.com/rg3/youtube-dl/pull/6097) 
 645             'url': 'https://www.youtube.com/watch?v=FIl7x6_3R5Y', 
 649                 'title': 'md5:7b81415841e02ecd4313668cde88737a', 
 650                 'description': 'md5:116377fd2963b81ec4ce64b542173306', 
 651                 'upload_date': '20150625', 
 652                 'uploader_id': 'dorappi2000', 
 653                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/dorappi2000', 
 654                 'uploader': 'dorappi2000', 
 655                 'license': 'Standard YouTube License', 
 656                 'formats': 'mincount:32', 
 659         # DASH manifest with segment_list 
 661             'url': 'https://www.youtube.com/embed/CsmdDsKjzN8', 
 662             'md5': '8ce563a1d667b599d21064e982ab9e31', 
 666                 'upload_date': '20150501',  # According to '<meta itemprop="datePublished"', but in other places it's 20150510 
 667                 'uploader': 'Airtek', 
 668                 'description': 'Retransmisión en directo de la XVIII media maratón de Zaragoza.', 
 669                 'uploader_id': 'UCzTzUmjXxxacNnL8I3m4LnQ', 
 670                 'license': 'Standard YouTube License', 
 671                 'title': 'Retransmisión XVIII Media maratón Zaragoza 2015', 
 674                 'youtube_include_dash_manifest': True, 
 675                 'format': '135',  # bestvideo 
 677             'skip': 'This live event has ended.', 
 680             # Multifeed videos (multiple cameras), URL is for Main Camera 
 681             'url': 'https://www.youtube.com/watch?v=jqWvoWXjCVs', 
 684                 'title': 'teamPGP: Rocket League Noob Stream', 
 685                 'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 691                     'title': 'teamPGP: Rocket League Noob Stream (Main Camera)', 
 692                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 693                     'upload_date': '20150721', 
 694                     'uploader': 'Beer Games Beer', 
 695                     'uploader_id': 'beergamesbeer', 
 696                     'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 697                     'license': 'Standard YouTube License', 
 703                     'title': 'teamPGP: Rocket League Noob Stream (kreestuh)', 
 704                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 705                     'upload_date': '20150721', 
 706                     'uploader': 'Beer Games Beer', 
 707                     'uploader_id': 'beergamesbeer', 
 708                     'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 709                     'license': 'Standard YouTube License', 
 715                     'title': 'teamPGP: Rocket League Noob Stream (grizzle)', 
 716                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 717                     'upload_date': '20150721', 
 718                     'uploader': 'Beer Games Beer', 
 719                     'uploader_id': 'beergamesbeer', 
 720                     'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 721                     'license': 'Standard YouTube License', 
 727                     'title': 'teamPGP: Rocket League Noob Stream (zim)', 
 728                     'description': 'md5:dc7872fb300e143831327f1bae3af010', 
 729                     'upload_date': '20150721', 
 730                     'uploader': 'Beer Games Beer', 
 731                     'uploader_id': 'beergamesbeer', 
 732                     'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/beergamesbeer', 
 733                     'license': 'Standard YouTube License', 
 737                 'skip_download': True, 
 741             # Multifeed video with comma in title (see https://github.com/rg3/youtube-dl/issues/8536) 
 742             'url': 'https://www.youtube.com/watch?v=gVfLd0zydlo', 
 745                 'title': 'DevConf.cz 2016 Day 2 Workshops 1 14:00 - 15:30', 
 748             'skip': 'Not multifeed anymore', 
 751             'url': 'https://vid.plus/FlRa-iH7PGw', 
 752             'only_matching': True, 
 755             'url': 'https://zwearz.com/watch/9lWxNJF-ufM/electra-woman-dyna-girl-official-trailer-grace-helbig.html', 
 756             'only_matching': True, 
 759             # Title with JS-like syntax "};" (see https://github.com/rg3/youtube-dl/issues/7468) 
 760             # Also tests cut-off URL expansion in video description (see 
 761             # https://github.com/rg3/youtube-dl/issues/1892, 
 762             # https://github.com/rg3/youtube-dl/issues/8164) 
 763             'url': 'https://www.youtube.com/watch?v=lsguqyKfVQg', 
 767                 'title': '{dark walk}; Loki/AC/Dishonored; collab w/Elflover21', 
 768                 'alt_title': 'Dark Walk', 
 769                 'description': 'md5:8085699c11dc3f597ce0410b0dcbb34a', 
 770                 'upload_date': '20151119', 
 771                 'uploader_id': 'IronSoulElf', 
 772                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/IronSoulElf', 
 773                 'uploader': 'IronSoulElf', 
 774                 'license': 'Standard YouTube License', 
 775                 'creator': 'Todd Haberman, Daniel Law Heath & Aaron Kaplan', 
 778                 'skip_download': True, 
 782             # Tags with '};' (see https://github.com/rg3/youtube-dl/issues/7468) 
 783             'url': 'https://www.youtube.com/watch?v=Ms7iBXnlUO8', 
 784             'only_matching': True, 
 787             # Video with yt:stretch=17:0 
 788             'url': 'https://www.youtube.com/watch?v=Q39EVAstoRM', 
 792                 'title': 'Clash Of Clans#14 Dicas De Ataque Para CV 4', 
 793                 'description': 'md5:ee18a25c350637c8faff806845bddee9', 
 794                 'upload_date': '20151107', 
 795                 'uploader_id': 'UCCr7TALkRbo3EtFzETQF1LA', 
 796                 'uploader': 'CH GAMER DROID', 
 799                 'skip_download': True, 
 801             'skip': 'This video does not exist.', 
 804             # Video licensed under Creative Commons 
 805             'url': 'https://www.youtube.com/watch?v=M4gD1WSo5mA', 
 809                 'title': 'md5:e41008789470fc2533a3252216f1c1d1', 
 810                 'description': 'md5:a677553cf0840649b731a3024aeff4cc', 
 811                 'upload_date': '20150127', 
 812                 'uploader_id': 'BerkmanCenter', 
 813                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/BerkmanCenter', 
 814                 'uploader': 'BerkmanCenter', 
 815                 'license': 'Creative Commons Attribution license (reuse allowed)', 
 818                 'skip_download': True, 
 822             # Channel-like uploader_url 
 823             'url': 'https://www.youtube.com/watch?v=eQcmzGIKrzg', 
 827                 'title': 'Democratic Socialism and Foreign Policy | Bernie Sanders', 
 828                 'description': 'md5:dda0d780d5a6e120758d1711d062a867', 
 829                 'upload_date': '20151119', 
 830                 'uploader': 'Bernie 2016', 
 831                 'uploader_id': 'UCH1dpzjCEiGAt8CXkryhkZg', 
 832                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/channel/UCH1dpzjCEiGAt8CXkryhkZg', 
 833                 'license': 'Creative Commons Attribution license (reuse allowed)', 
 836                 'skip_download': True, 
 840             'url': 'https://www.youtube.com/watch?feature=player_embedded&amp;v=V36LpHqtcDY', 
 841             'only_matching': True, 
 844             # YouTube Red paid video (https://github.com/rg3/youtube-dl/issues/10059) 
 845             'url': 'https://www.youtube.com/watch?v=i1Ko8UG-Tdo', 
 846             'only_matching': True, 
 849             # Rental video preview 
 850             'url': 'https://www.youtube.com/watch?v=yYr8q0y5Jfg', 
 854                 'title': 'Piku - Trailer', 
 855                 'description': 'md5:c36bd60c3fd6f1954086c083c72092eb', 
 856                 'upload_date': '20150811', 
 857                 'uploader': 'FlixMatrix', 
 858                 'uploader_id': 'FlixMatrixKaravan', 
 859                 'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/FlixMatrixKaravan', 
 860                 'license': 'Standard YouTube License', 
 863                 'skip_download': True, 
 868     def __init__(self
, *args
, **kwargs
): 
 869         super(YoutubeIE
, self
).__init
__(*args
, **kwargs
) 
 870         self
._player
_cache 
= {} 
 872     def report_video_info_webpage_download(self
, video_id
): 
 873         """Report attempt to download video info webpage.""" 
 874         self
.to_screen('%s: Downloading video info webpage' % video_id
) 
 876     def report_information_extraction(self
, video_id
): 
 877         """Report attempt to extract video information.""" 
 878         self
.to_screen('%s: Extracting video information' % video_id
) 
 880     def report_unavailable_format(self
, video_id
, format
): 
 881         """Report extracted video URL.""" 
 882         self
.to_screen('%s: Format %s not available' % (video_id
, format
)) 
 884     def report_rtmp_download(self
): 
 885         """Indicate the download will use the RTMP protocol.""" 
 886         self
.to_screen('RTMP download detected') 
 888     def _signature_cache_id(self
, example_sig
): 
 889         """ Return a string representation of a signature """ 
 890         return '.'.join(compat_str(len(part
)) for part 
in example_sig
.split('.')) 
 892     def _extract_signature_function(self
, video_id
, player_url
, example_sig
): 
 894             r
'.*?-(?P<id>[a-zA-Z0-9_-]+)(?:/watch_as3|/html5player(?:-new)?|/base)?\.(?P<ext>[a-z]+)$', 
 897             raise ExtractorError('Cannot identify player %r' % player_url
) 
 898         player_type 
= id_m
.group('ext') 
 899         player_id 
= id_m
.group('id') 
 901         # Read from filesystem cache 
 902         func_id 
= '%s_%s_%s' % ( 
 903             player_type
, player_id
, self
._signature
_cache
_id
(example_sig
)) 
 904         assert os
.path
.basename(func_id
) == func_id
 
 906         cache_spec 
= self
._downloader
.cache
.load('youtube-sigfuncs', func_id
) 
 907         if cache_spec 
is not None: 
 908             return lambda s
: ''.join(s
[i
] for i 
in cache_spec
) 
 911             'Downloading player %s' % player_url
 
 912             if self
._downloader
.params
.get('verbose') else 
 913             'Downloading %s player %s' % (player_type
, player_id
) 
 915         if player_type 
== 'js': 
 916             code 
= self
._download
_webpage
( 
 917                 player_url
, video_id
, 
 919                 errnote
='Download of %s failed' % player_url
) 
 920             res 
= self
._parse
_sig
_js
(code
) 
 921         elif player_type 
== 'swf': 
 922             urlh 
= self
._request
_webpage
( 
 923                 player_url
, video_id
, 
 925                 errnote
='Download of %s failed' % player_url
) 
 927             res 
= self
._parse
_sig
_swf
(code
) 
 929             assert False, 'Invalid player type %r' % player_type
 
 931         test_string 
= ''.join(map(compat_chr
, range(len(example_sig
)))) 
 932         cache_res 
= res(test_string
) 
 933         cache_spec 
= [ord(c
) for c 
in cache_res
] 
 935         self
._downloader
.cache
.store('youtube-sigfuncs', func_id
, cache_spec
) 
 938     def _print_sig_code(self
, func
, example_sig
): 
 939         def gen_sig_code(idxs
): 
 940             def _genslice(start
, end
, step
): 
 941                 starts 
= '' if start 
== 0 else str(start
) 
 942                 ends 
= (':%d' % (end 
+ step
)) if end 
+ step 
>= 0 else ':' 
 943                 steps 
= '' if step 
== 1 else (':%d' % step
) 
 944                 return 's[%s%s%s]' % (starts
, ends
, steps
) 
 947             # Quelch pyflakes warnings - start will be set when step is set 
 948             start 
= '(Never used)' 
 949             for i
, prev 
in zip(idxs
[1:], idxs
[:-1]): 
 953                     yield _genslice(start
, prev
, step
) 
 956                 if i 
- prev 
in [-1, 1]: 
 965                 yield _genslice(start
, i
, step
) 
 967         test_string 
= ''.join(map(compat_chr
, range(len(example_sig
)))) 
 968         cache_res 
= func(test_string
) 
 969         cache_spec 
= [ord(c
) for c 
in cache_res
] 
 970         expr_code 
= ' + '.join(gen_sig_code(cache_spec
)) 
 971         signature_id_tuple 
= '(%s)' % ( 
 972             ', '.join(compat_str(len(p
)) for p 
in example_sig
.split('.'))) 
 973         code 
= ('if tuple(len(p) for p in s.split(\'.\')) == %s:\n' 
 974                 '    return %s\n') % (signature_id_tuple
, expr_code
) 
 975         self
.to_screen('Extracted signature function:\n' + code
) 
 977     def _parse_sig_js(self
, jscode
): 
 978         funcname 
= self
._search
_regex
( 
 979             r
'\.sig\|\|([a-zA-Z0-9$]+)\(', jscode
, 
 980             'Initial JS player signature function name') 
 982         jsi 
= JSInterpreter(jscode
) 
 983         initial_function 
= jsi
.extract_function(funcname
) 
 984         return lambda s
: initial_function([s
]) 
 986     def _parse_sig_swf(self
, file_contents
): 
 987         swfi 
= SWFInterpreter(file_contents
) 
 988         TARGET_CLASSNAME 
= 'SignatureDecipher' 
 989         searched_class 
= swfi
.extract_class(TARGET_CLASSNAME
) 
 990         initial_function 
= swfi
.extract_function(searched_class
, 'decipher') 
 991         return lambda s
: initial_function([s
]) 
 993     def _decrypt_signature(self
, s
, video_id
, player_url
, age_gate
=False): 
 994         """Turn the encrypted s field into a working signature""" 
 996         if player_url 
is None: 
 997             raise ExtractorError('Cannot decrypt signature without player_url') 
 999         if player_url
.startswith('//'): 
1000             player_url 
= 'https:' + player_url
 
1002             player_id 
= (player_url
, self
._signature
_cache
_id
(s
)) 
1003             if player_id 
not in self
._player
_cache
: 
1004                 func 
= self
._extract
_signature
_function
( 
1005                     video_id
, player_url
, s
 
1007                 self
._player
_cache
[player_id
] = func
 
1008             func 
= self
._player
_cache
[player_id
] 
1009             if self
._downloader
.params
.get('youtube_print_sig_code'): 
1010                 self
._print
_sig
_code
(func
, s
) 
1012         except Exception as e
: 
1013             tb 
= traceback
.format_exc() 
1014             raise ExtractorError( 
1015                 'Signature extraction failed: ' + tb
, cause
=e
) 
1017     def _get_subtitles(self
, video_id
, webpage
): 
1019             subs_doc 
= self
._download
_xml
( 
1020                 'https://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id
, 
1021                 video_id
, note
=False) 
1022         except ExtractorError 
as err
: 
1023             self
._downloader
.report_warning('unable to download video subtitles: %s' % error_to_compat_str(err
)) 
1027         for track 
in subs_doc
.findall('track'): 
1028             lang 
= track
.attrib
['lang_code'] 
1029             if lang 
in sub_lang_list
: 
1032             for ext 
in self
._SUBTITLE
_FORMATS
: 
1033                 params 
= compat_urllib_parse_urlencode({ 
1037                     'name': track
.attrib
['name'].encode('utf-8'), 
1039                 sub_formats
.append({ 
1040                     'url': 'https://www.youtube.com/api/timedtext?' + params
, 
1043             sub_lang_list
[lang
] = sub_formats
 
1044         if not sub_lang_list
: 
1045             self
._downloader
.report_warning('video doesn\'t have subtitles') 
1047         return sub_lang_list
 
1049     def _get_ytplayer_config(self
, video_id
, webpage
): 
1051             # User data may contain arbitrary character sequences that may affect 
1052             # JSON extraction with regex, e.g. when '};' is contained the second 
1053             # regex won't capture the whole JSON. Yet working around by trying more 
1054             # concrete regex first keeping in mind proper quoted string handling 
1055             # to be implemented in future that will replace this workaround (see 
1056             # https://github.com/rg3/youtube-dl/issues/7468, 
1057             # https://github.com/rg3/youtube-dl/pull/7599) 
1058             r
';ytplayer\.config\s*=\s*({.+?});ytplayer', 
1059             r
';ytplayer\.config\s*=\s*({.+?});', 
1061         config 
= self
._search
_regex
( 
1062             patterns
, webpage
, 'ytplayer.config', default
=None) 
1064             return self
._parse
_json
( 
1065                 uppercase_escape(config
), video_id
, fatal
=False) 
1067     def _get_automatic_captions(self
, video_id
, webpage
): 
1068         """We need the webpage for getting the captions url, pass it as an 
1069            argument to speed up the process.""" 
1070         self
.to_screen('%s: Looking for automatic captions' % video_id
) 
1071         player_config 
= self
._get
_ytplayer
_config
(video_id
, webpage
) 
1072         err_msg 
= 'Couldn\'t find automatic captions for %s' % video_id
 
1073         if not player_config
: 
1074             self
._downloader
.report_warning(err_msg
) 
1077             args 
= player_config
['args'] 
1078             caption_url 
= args
.get('ttsurl') 
1080                 timestamp 
= args
['timestamp'] 
1081                 # We get the available subtitles 
1082                 list_params 
= compat_urllib_parse_urlencode({ 
1087                 list_url 
= caption_url 
+ '&' + list_params
 
1088                 caption_list 
= self
._download
_xml
(list_url
, video_id
) 
1089                 original_lang_node 
= caption_list
.find('track') 
1090                 if original_lang_node 
is None: 
1091                     self
._downloader
.report_warning('Video doesn\'t have automatic captions') 
1093                 original_lang 
= original_lang_node
.attrib
['lang_code'] 
1094                 caption_kind 
= original_lang_node
.attrib
.get('kind', '') 
1097                 for lang_node 
in caption_list
.findall('target'): 
1098                     sub_lang 
= lang_node
.attrib
['lang_code'] 
1100                     for ext 
in self
._SUBTITLE
_FORMATS
: 
1101                         params 
= compat_urllib_parse_urlencode({ 
1102                             'lang': original_lang
, 
1106                             'kind': caption_kind
, 
1108                         sub_formats
.append({ 
1109                             'url': caption_url 
+ '&' + params
, 
1112                     sub_lang_list
[sub_lang
] = sub_formats
 
1113                 return sub_lang_list
 
1115             # Some videos don't provide ttsurl but rather caption_tracks and 
1116             # caption_translation_languages (e.g. 20LmZk1hakA) 
1117             caption_tracks 
= args
['caption_tracks'] 
1118             caption_translation_languages 
= args
['caption_translation_languages'] 
1119             caption_url 
= compat_parse_qs(caption_tracks
.split(',')[0])['u'][0] 
1120             parsed_caption_url 
= compat_urllib_parse_urlparse(caption_url
) 
1121             caption_qs 
= compat_parse_qs(parsed_caption_url
.query
) 
1124             for lang 
in caption_translation_languages
.split(','): 
1125                 lang_qs 
= compat_parse_qs(compat_urllib_parse_unquote_plus(lang
)) 
1126                 sub_lang 
= lang_qs
.get('lc', [None])[0] 
1130                 for ext 
in self
._SUBTITLE
_FORMATS
: 
1132                         'tlang': [sub_lang
], 
1135                     sub_url 
= compat_urlparse
.urlunparse(parsed_caption_url
._replace
( 
1136                         query
=compat_urllib_parse_urlencode(caption_qs
, True))) 
1137                     sub_formats
.append({ 
1141                 sub_lang_list
[sub_lang
] = sub_formats
 
1142             return sub_lang_list
 
1143         # An extractor error can be raise by the download process if there are 
1144         # no automatic captions but there are subtitles 
1145         except (KeyError, ExtractorError
): 
1146             self
._downloader
.report_warning(err_msg
) 
1149     def _mark_watched(self
, video_id
, video_info
): 
1150         playback_url 
= video_info
.get('videostats_playback_base_url', [None])[0] 
1151         if not playback_url
: 
1153         parsed_playback_url 
= compat_urlparse
.urlparse(playback_url
) 
1154         qs 
= compat_urlparse
.parse_qs(parsed_playback_url
.query
) 
1156         # cpn generation algorithm is reverse engineered from base.js. 
1157         # In fact it works even with dummy cpn. 
1158         CPN_ALPHABET 
= 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_' 
1159         cpn 
= ''.join((CPN_ALPHABET
[random
.randint(0, 256) & 63] for _ 
in range(0, 16))) 
1165         playback_url 
= compat_urlparse
.urlunparse( 
1166             parsed_playback_url
._replace
(query
=compat_urllib_parse_urlencode(qs
, True))) 
1168         self
._download
_webpage
( 
1169             playback_url
, video_id
, 'Marking watched', 
1170             'Unable to mark watched', fatal
=False) 
1173     def extract_id(cls
, url
): 
1174         mobj 
= re
.match(cls
._VALID
_URL
, url
, re
.VERBOSE
) 
1176             raise ExtractorError('Invalid URL: %s' % url
) 
1177         video_id 
= mobj
.group(2) 
1180     def _extract_from_m3u8(self
, manifest_url
, video_id
): 
1183         def _get_urls(_manifest
): 
1184             lines 
= _manifest
.split('\n') 
1185             urls 
= filter(lambda l
: l 
and not l
.startswith('#'), 
1188         manifest 
= self
._download
_webpage
(manifest_url
, video_id
, 'Downloading formats manifest') 
1189         formats_urls 
= _get_urls(manifest
) 
1190         for format_url 
in formats_urls
: 
1191             itag 
= self
._search
_regex
(r
'itag/(\d+?)/', format_url
, 'itag') 
1192             url_map
[itag
] = format_url
 
1195     def _extract_annotations(self
, video_id
): 
1196         url 
= 'https://www.youtube.com/annotations_invideo?features=1&legacy=1&video_id=%s' % video_id
 
1197         return self
._download
_webpage
(url
, video_id
, note
='Searching for annotations.', errnote
='Unable to download video annotations.') 
1199     def _real_extract(self
, url
): 
1200         url
, smuggled_data 
= unsmuggle_url(url
, {}) 
1203             'http' if self
._downloader
.params
.get('prefer_insecure', False) 
1208         parsed_url 
= compat_urllib_parse_urlparse(url
) 
1209         for component 
in [parsed_url
.fragment
, parsed_url
.query
]: 
1210             query 
= compat_parse_qs(component
) 
1211             if start_time 
is None and 't' in query
: 
1212                 start_time 
= parse_duration(query
['t'][0]) 
1213             if start_time 
is None and 'start' in query
: 
1214                 start_time 
= parse_duration(query
['start'][0]) 
1215             if end_time 
is None and 'end' in query
: 
1216                 end_time 
= parse_duration(query
['end'][0]) 
1218         # Extract original video URL from URL with redirection, like age verification, using next_url parameter 
1219         mobj 
= re
.search(self
._NEXT
_URL
_RE
, url
) 
1221             url 
= proto 
+ '://www.youtube.com/' + compat_urllib_parse_unquote(mobj
.group(1)).lstrip('/') 
1222         video_id 
= self
.extract_id(url
) 
1225         url 
= proto 
+ '://www.youtube.com/watch?v=%s&gl=US&hl=en&has_verified=1&bpctr=9999999999' % video_id
 
1226         video_webpage 
= self
._download
_webpage
(url
, video_id
) 
1228         # Attempt to extract SWF player URL 
1229         mobj 
= re
.search(r
'swfConfig.*?"(https?:\\/\\/.*?watch.*?-.*?\.swf)"', video_webpage
) 
1230         if mobj 
is not None: 
1231             player_url 
= re
.sub(r
'\\(.)', r
'\1', mobj
.group(1)) 
1237         def add_dash_mpd(video_info
): 
1238             dash_mpd 
= video_info
.get('dashmpd') 
1239             if dash_mpd 
and dash_mpd
[0] not in dash_mpds
: 
1240                 dash_mpds
.append(dash_mpd
[0]) 
1243         embed_webpage 
= None 
1245         if re
.search(r
'player-age-gate-content">', video_webpage
) is not None: 
1247             # We simulate the access to the video from www.youtube.com/v/{video_id} 
1248             # this can be viewed without login into Youtube 
1249             url 
= proto 
+ '://www.youtube.com/embed/%s' % video_id
 
1250             embed_webpage 
= self
._download
_webpage
(url
, video_id
, 'Downloading embed webpage') 
1251             data 
= compat_urllib_parse_urlencode({ 
1252                 'video_id': video_id
, 
1253                 'eurl': 'https://youtube.googleapis.com/v/' + video_id
, 
1254                 'sts': self
._search
_regex
( 
1255                     r
'"sts"\s*:\s*(\d+)', embed_webpage
, 'sts', default
=''), 
1257             video_info_url 
= proto 
+ '://www.youtube.com/get_video_info?' + data
 
1258             video_info_webpage 
= self
._download
_webpage
( 
1259                 video_info_url
, video_id
, 
1260                 note
='Refetching age-gated info webpage', 
1261                 errnote
='unable to download video info webpage') 
1262             video_info 
= compat_parse_qs(video_info_webpage
) 
1263             add_dash_mpd(video_info
) 
1267             # Try looking directly into the video webpage 
1268             ytplayer_config 
= self
._get
_ytplayer
_config
(video_id
, video_webpage
) 
1270                 args 
= ytplayer_config
['args'] 
1271                 if args
.get('url_encoded_fmt_stream_map'): 
1272                     # Convert to the same format returned by compat_parse_qs 
1273                     video_info 
= dict((k
, [v
]) for k
, v 
in args
.items()) 
1274                     add_dash_mpd(video_info
) 
1275                 # Rental video is not rented but preview is available (e.g. 
1276                 # https://www.youtube.com/watch?v=yYr8q0y5Jfg, 
1277                 # https://github.com/rg3/youtube-dl/issues/10532) 
1278                 if not video_info 
and args
.get('ypc_vid'): 
1279                     return self
.url_result( 
1280                         args
['ypc_vid'], YoutubeIE
.ie_key(), video_id
=args
['ypc_vid']) 
1281                 if args
.get('livestream') == '1' or args
.get('live_playback') == 1: 
1283             if not video_info 
or self
._downloader
.params
.get('youtube_include_dash_manifest', True): 
1284                 # We also try looking in get_video_info since it may contain different dashmpd 
1285                 # URL that points to a DASH manifest with possibly different itag set (some itags 
1286                 # are missing from DASH manifest pointed by webpage's dashmpd, some - from DASH 
1287                 # manifest pointed by get_video_info's dashmpd). 
1288                 # The general idea is to take a union of itags of both DASH manifests (for example 
1289                 # video with such 'manifest behavior' see https://github.com/rg3/youtube-dl/issues/6093) 
1290                 self
.report_video_info_webpage_download(video_id
) 
1291                 for el_type 
in ['&el=info', '&el=embedded', '&el=detailpage', '&el=vevo', '']: 
1293                         '%s://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en' 
1294                         % (proto
, video_id
, el_type
)) 
1295                     video_info_webpage 
= self
._download
_webpage
( 
1297                         video_id
, note
=False, 
1298                         errnote
='unable to download video info webpage') 
1299                     get_video_info 
= compat_parse_qs(video_info_webpage
) 
1300                     if get_video_info
.get('use_cipher_signature') != ['True']: 
1301                         add_dash_mpd(get_video_info
) 
1303                         video_info 
= get_video_info
 
1304                     if 'token' in get_video_info
: 
1305                         # Different get_video_info requests may report different results, e.g. 
1306                         # some may report video unavailability, but some may serve it without 
1307                         # any complaint (see https://github.com/rg3/youtube-dl/issues/7362, 
1308                         # the original webpage as well as el=info and el=embedded get_video_info 
1309                         # requests report video unavailability due to geo restriction while 
1310                         # el=detailpage succeeds and returns valid data). This is probably 
1311                         # due to YouTube measures against IP ranges of hosting providers. 
1312                         # Working around by preferring the first succeeded video_info containing 
1313                         # the token if no such video_info yet was found. 
1314                         if 'token' not in video_info
: 
1315                             video_info 
= get_video_info
 
1317         if 'token' not in video_info
: 
1318             if 'reason' in video_info
: 
1319                 if 'The uploader has not made this video available in your country.' in video_info
['reason']: 
1320                     regions_allowed 
= self
._html
_search
_meta
('regionsAllowed', video_webpage
, default
=None) 
1322                         raise ExtractorError('YouTube said: This video is available in %s only' % ( 
1323                             ', '.join(map(ISO3166Utils
.short2full
, regions_allowed
.split(',')))), 
1325                 raise ExtractorError( 
1326                     'YouTube said: %s' % video_info
['reason'][0], 
1327                     expected
=True, video_id
=video_id
) 
1329                 raise ExtractorError( 
1330                     '"token" parameter not in video info for unknown reason', 
1334         if 'title' in video_info
: 
1335             video_title 
= video_info
['title'][0] 
1337             self
._downloader
.report_warning('Unable to extract video title') 
1341         video_description 
= get_element_by_id("eow-description", video_webpage
) 
1342         if video_description
: 
1343             video_description 
= re
.sub(r
'''(?x) 
1345                     (?:[a-zA-Z-]+="[^"]*"\s+)*? 
1346                     (?:title|href)="([^"]+)"\s+ 
1347                     (?:[a-zA-Z-]+="[^"]*"\s+)*? 
1351             ''', r
'\1', video_description
) 
1352             video_description 
= clean_html(video_description
) 
1354             fd_mobj 
= re
.search(r
'<meta name="description" content="([^"]+)"', video_webpage
) 
1356                 video_description 
= unescapeHTML(fd_mobj
.group(1)) 
1358                 video_description 
= '' 
1360         if 'multifeed_metadata_list' in video_info 
and not smuggled_data
.get('force_singlefeed', False): 
1361             if not self
._downloader
.params
.get('noplaylist'): 
1364                 multifeed_metadata_list 
= video_info
['multifeed_metadata_list'][0] 
1365                 for feed 
in multifeed_metadata_list
.split(','): 
1366                     # Unquote should take place before split on comma (,) since textual 
1367                     # fields may contain comma as well (see 
1368                     # https://github.com/rg3/youtube-dl/issues/8536) 
1369                     feed_data 
= compat_parse_qs(compat_urllib_parse_unquote_plus(feed
)) 
1371                         '_type': 'url_transparent', 
1372                         'ie_key': 'Youtube', 
1374                             '%s://www.youtube.com/watch?v=%s' % (proto
, feed_data
['id'][0]), 
1375                             {'force_singlefeed': True}), 
1376                         'title': '%s (%s)' % (video_title
, feed_data
['title'][0]), 
1378                     feed_ids
.append(feed_data
['id'][0]) 
1380                     'Downloading multifeed video (%s) - add --no-playlist to just download video %s' 
1381                     % (', '.join(feed_ids
), video_id
)) 
1382                 return self
.playlist_result(entries
, video_id
, video_title
, video_description
) 
1383             self
.to_screen('Downloading just video %s because of --no-playlist' % video_id
) 
1385         if 'view_count' in video_info
: 
1386             view_count 
= int(video_info
['view_count'][0]) 
1390         # Check for "rental" videos 
1391         if 'ypc_video_rental_bar_text' in video_info 
and 'author' not in video_info
: 
1392             raise ExtractorError('"rental" videos not supported') 
1394         # Start extracting information 
1395         self
.report_information_extraction(video_id
) 
1398         if 'author' not in video_info
: 
1399             raise ExtractorError('Unable to extract uploader name') 
1400         video_uploader 
= compat_urllib_parse_unquote_plus(video_info
['author'][0]) 
1403         video_uploader_id 
= None 
1404         video_uploader_url 
= None 
1406             r
'<link itemprop="url" href="(?P<uploader_url>https?://www.youtube.com/(?:user|channel)/(?P<uploader_id>[^"]+))">', 
1408         if mobj 
is not None: 
1409             video_uploader_id 
= mobj
.group('uploader_id') 
1410             video_uploader_url 
= mobj
.group('uploader_url') 
1412             self
._downloader
.report_warning('unable to extract uploader nickname') 
1415         # We try first to get a high quality image: 
1416         m_thumb 
= re
.search(r
'<span itemprop="thumbnail".*?href="(.*?)">', 
1417                             video_webpage
, re
.DOTALL
) 
1418         if m_thumb 
is not None: 
1419             video_thumbnail 
= m_thumb
.group(1) 
1420         elif 'thumbnail_url' not in video_info
: 
1421             self
._downloader
.report_warning('unable to extract video thumbnail') 
1422             video_thumbnail 
= None 
1423         else:   # don't panic if we can't find it 
1424             video_thumbnail 
= compat_urllib_parse_unquote_plus(video_info
['thumbnail_url'][0]) 
1427         upload_date 
= self
._html
_search
_meta
( 
1428             'datePublished', video_webpage
, 'upload date', default
=None) 
1430             upload_date 
= self
._search
_regex
( 
1431                 [r
'(?s)id="eow-date.*?>(.*?)</span>', 
1432                  r
'id="watch-uploader-info".*?>.*?(?:Published|Uploaded|Streamed live|Started) on (.+?)</strong>'], 
1433                 video_webpage
, 'upload date', default
=None) 
1435                 upload_date 
= ' '.join(re
.sub(r
'[/,-]', r
' ', mobj
.group(1)).split()) 
1436         upload_date 
= unified_strdate(upload_date
) 
1438         video_license 
= self
._html
_search
_regex
( 
1439             r
'<h4[^>]+class="title"[^>]*>\s*License\s*</h4>\s*<ul[^>]*>\s*<li>(.+?)</li', 
1440             video_webpage
, 'license', default
=None) 
1442         m_music 
= re
.search( 
1443             r
'<h4[^>]+class="title"[^>]*>\s*Music\s*</h4>\s*<ul[^>]*>\s*<li>(?P<title>.+?) by (?P<creator>.+?)(?:\(.+?\))?</li', 
1446             video_alt_title 
= remove_quotes(unescapeHTML(m_music
.group('title'))) 
1447             video_creator 
= clean_html(m_music
.group('creator')) 
1449             video_alt_title 
= video_creator 
= None 
1451         m_cat_container 
= self
._search
_regex
( 
1452             r
'(?s)<h4[^>]*>\s*Category\s*</h4>\s*<ul[^>]*>(.*?)</ul>', 
1453             video_webpage
, 'categories', default
=None) 
1455             category 
= self
._html
_search
_regex
( 
1456                 r
'(?s)<a[^<]+>(.*?)</a>', m_cat_container
, 'category', 
1458             video_categories 
= None if category 
is None else [category
] 
1460             video_categories 
= None 
1463             unescapeHTML(m
.group('content')) 
1464             for m 
in re
.finditer(self
._meta
_regex
('og:video:tag'), video_webpage
)] 
1466         def _extract_count(count_name
): 
1467             return str_to_int(self
._search
_regex
( 
1468                 r
'-%s-button[^>]+><span[^>]+class="yt-uix-button-content"[^>]*>([\d,]+)</span>' 
1469                 % re
.escape(count_name
), 
1470                 video_webpage
, count_name
, default
=None)) 
1472         like_count 
= _extract_count('like') 
1473         dislike_count 
= _extract_count('dislike') 
1476         video_subtitles 
= self
.extract_subtitles(video_id
, video_webpage
) 
1477         automatic_captions 
= self
.extract_automatic_captions(video_id
, video_webpage
) 
1479         if 'length_seconds' not in video_info
: 
1480             self
._downloader
.report_warning('unable to extract video duration') 
1481             video_duration 
= None 
1483             video_duration 
= int(compat_urllib_parse_unquote_plus(video_info
['length_seconds'][0])) 
1486         video_annotations 
= None 
1487         if self
._downloader
.params
.get('writeannotations', False): 
1488             video_annotations 
= self
._extract
_annotations
(video_id
) 
1490         def _map_to_format_list(urlmap
): 
1492             for itag
, video_real_url 
in urlmap
.items(): 
1495                     'url': video_real_url
, 
1496                     'player_url': player_url
, 
1498                 if itag 
in self
._formats
: 
1499                     dct
.update(self
._formats
[itag
]) 
1503         if 'conn' in video_info 
and video_info
['conn'][0].startswith('rtmp'): 
1504             self
.report_rtmp_download() 
1506                 'format_id': '_rtmp', 
1508                 'url': video_info
['conn'][0], 
1509                 'player_url': player_url
, 
1511         elif len(video_info
.get('url_encoded_fmt_stream_map', [''])[0]) >= 1 or len(video_info
.get('adaptive_fmts', [''])[0]) >= 1: 
1512             encoded_url_map 
= video_info
.get('url_encoded_fmt_stream_map', [''])[0] + ',' + video_info
.get('adaptive_fmts', [''])[0] 
1513             if 'rtmpe%3Dyes' in encoded_url_map
: 
1514                 raise ExtractorError('rtmpe downloads are not supported, see https://github.com/rg3/youtube-dl/issues/343 for more information.', expected
=True) 
1516             fmt_list 
= video_info
.get('fmt_list', [''])[0] 
1518                 for fmt 
in fmt_list
.split(','): 
1519                     spec 
= fmt
.split('/') 
1521                         width_height 
= spec
[1].split('x') 
1522                         if len(width_height
) == 2: 
1523                             formats_spec
[spec
[0]] = { 
1524                                 'resolution': spec
[1], 
1525                                 'width': int_or_none(width_height
[0]), 
1526                                 'height': int_or_none(width_height
[1]), 
1529             for url_data_str 
in encoded_url_map
.split(','): 
1530                 url_data 
= compat_parse_qs(url_data_str
) 
1531                 if 'itag' not in url_data 
or 'url' not in url_data
: 
1533                 format_id 
= url_data
['itag'][0] 
1534                 url 
= url_data
['url'][0] 
1536                 if 'sig' in url_data
: 
1537                     url 
+= '&signature=' + url_data
['sig'][0] 
1538                 elif 's' in url_data
: 
1539                     encrypted_sig 
= url_data
['s'][0] 
1540                     ASSETS_RE 
= r
'"assets":.+?"js":\s*("[^"]+")' 
1542                     jsplayer_url_json 
= self
._search
_regex
( 
1544                         embed_webpage 
if age_gate 
else video_webpage
, 
1545                         'JS player URL (1)', default
=None) 
1546                     if not jsplayer_url_json 
and not age_gate
: 
1547                         # We need the embed website after all 
1548                         if embed_webpage 
is None: 
1549                             embed_url 
= proto 
+ '://www.youtube.com/embed/%s' % video_id
 
1550                             embed_webpage 
= self
._download
_webpage
( 
1551                                 embed_url
, video_id
, 'Downloading embed webpage') 
1552                         jsplayer_url_json 
= self
._search
_regex
( 
1553                             ASSETS_RE
, embed_webpage
, 'JS player URL') 
1555                     player_url 
= json
.loads(jsplayer_url_json
) 
1556                     if player_url 
is None: 
1557                         player_url_json 
= self
._search
_regex
( 
1558                             r
'ytplayer\.config.*?"url"\s*:\s*("[^"]+")', 
1559                             video_webpage
, 'age gate player URL') 
1560                         player_url 
= json
.loads(player_url_json
) 
1562                     if self
._downloader
.params
.get('verbose'): 
1563                         if player_url 
is None: 
1564                             player_version 
= 'unknown' 
1565                             player_desc 
= 'unknown' 
1567                             if player_url
.endswith('swf'): 
1568                                 player_version 
= self
._search
_regex
( 
1569                                     r
'-(.+?)(?:/watch_as3)?\.swf$', player_url
, 
1570                                     'flash player', fatal
=False) 
1571                                 player_desc 
= 'flash player %s' % player_version
 
1573                                 player_version 
= self
._search
_regex
( 
1574                                     [r
'html5player-([^/]+?)(?:/html5player(?:-new)?)?\.js', r
'(?:www|player)-([^/]+)/base\.js'], 
1576                                     'html5 player', fatal
=False) 
1577                                 player_desc 
= 'html5 player %s' % player_version
 
1579                         parts_sizes 
= self
._signature
_cache
_id
(encrypted_sig
) 
1580                         self
.to_screen('{%s} signature length %s, %s' % 
1581                                        (format_id
, parts_sizes
, player_desc
)) 
1583                     signature 
= self
._decrypt
_signature
( 
1584                         encrypted_sig
, video_id
, player_url
, age_gate
) 
1585                     url 
+= '&signature=' + signature
 
1586                 if 'ratebypass' not in url
: 
1587                     url 
+= '&ratebypass=yes' 
1590                     'format_id': format_id
, 
1592                     'player_url': player_url
, 
1594                 if format_id 
in self
._formats
: 
1595                     dct
.update(self
._formats
[format_id
]) 
1596                 if format_id 
in formats_spec
: 
1597                     dct
.update(formats_spec
[format_id
]) 
1599                 # Some itags are not included in DASH manifest thus corresponding formats will 
1600                 # lack metadata (see https://github.com/rg3/youtube-dl/pull/5993). 
1601                 # Trying to extract metadata from url_encoded_fmt_stream_map entry. 
1602                 mobj 
= re
.search(r
'^(?P<width>\d+)[xX](?P<height>\d+)$', url_data
.get('size', [''])[0]) 
1603                 width
, height 
= (int(mobj
.group('width')), int(mobj
.group('height'))) if mobj 
else (None, None) 
1606                     'filesize': int_or_none(url_data
.get('clen', [None])[0]), 
1607                     'tbr': float_or_none(url_data
.get('bitrate', [None])[0], 1000), 
1610                     'fps': int_or_none(url_data
.get('fps', [None])[0]), 
1611                     'format_note': url_data
.get('quality_label', [None])[0] or url_data
.get('quality', [None])[0], 
1613                 for key
, value 
in more_fields
.items(): 
1616                 type_ 
= url_data
.get('type', [None])[0] 
1618                     type_split 
= type_
.split(';') 
1619                     kind_ext 
= type_split
[0].split('/') 
1620                     if len(kind_ext
) == 2: 
1622                         dct
['ext'] = mimetype2ext(type_split
[0]) 
1623                         if kind 
in ('audio', 'video'): 
1625                             for mobj 
in re
.finditer( 
1626                                     r
'(?P<key>[a-zA-Z_-]+)=(?P<quote>["\']?
)(?P
<val
>.+?
)(?P
=quote
)(?
:;|$
)', type_): 
1627                                 if mobj.group('key
') == 'codecs
': 
1628                                     codecs = mobj.group('val
') 
1631                                 codecs = codecs.split(',') 
1632                                 if len(codecs) == 2: 
1633                                     acodec, vcodec = codecs[1], codecs[0] 
1635                                     acodec, vcodec = (codecs[0], 'none
') if kind == 'audio
' else ('none
', codecs[0]) 
1641         elif video_info.get('hlsvp
'): 
1642             manifest_url = video_info['hlsvp
'][0] 
1643             url_map = self._extract_from_m3u8(manifest_url, video_id) 
1644             formats = _map_to_format_list(url_map) 
1645             # Accept-Encoding header causes failures in live streams on Youtube and Youtube Gaming 
1646             for a_format in formats: 
1647                 a_format.setdefault('http_headers
', {})['Youtubedl
-no
-compression
'] = 'True' 
1649             unavailable_message = self._html_search_regex( 
1650                 r'(?s
)<h1
[^
>]+id="unavailable-message"[^
>]*>(.+?
)</h1
>', 
1651                 video_webpage, 'unavailable message
', default=None) 
1652             if unavailable_message: 
1653                 raise ExtractorError(unavailable_message, expected=True) 
1654             raise ExtractorError('no conn
, hlsvp 
or url_encoded_fmt_stream_map information found 
in video info
') 
1656         # Look for the DASH manifest 
1657         if self._downloader.params.get('youtube_include_dash_manifest
', True): 
1658             dash_mpd_fatal = True 
1659             for mpd_url in dash_mpds: 
1662                     def decrypt_sig(mobj): 
1664                         dec_s = self._decrypt_signature(s, video_id, player_url, age_gate) 
1665                         return '/signature
/%s' % dec_s 
1667                     mpd_url = re.sub(r'/s
/([a
-fA
-F0
-9\
.]+)', decrypt_sig, mpd_url) 
1669                     for df in self._extract_mpd_formats( 
1670                             mpd_url, video_id, fatal=dash_mpd_fatal, 
1671                             formats_dict=self._formats): 
1672                         # Do not overwrite DASH format found in some previous DASH manifest 
1673                         if df['format_id
'] not in dash_formats: 
1674                             dash_formats[df['format_id
']] = df 
1675                         # Additional DASH manifests may end up in HTTP Error 403 therefore 
1676                         # allow them to fail without bug report message if we already have 
1677                         # some DASH manifest succeeded. This is temporary workaround to reduce 
1678                         # burst of bug reports until we figure out the reason and whether it 
1679                         # can be fixed at all. 
1680                         dash_mpd_fatal = False 
1681                 except (ExtractorError, KeyError) as e: 
1682                     self.report_warning( 
1683                         'Skipping DASH manifest
: %r' % e, video_id) 
1685                     # Remove the formats we found through non-DASH, they 
1686                     # contain less info and it can be wrong, because we use 
1687                     # fixed values (for example the resolution). See 
1688                     # https://github.com/rg3/youtube-dl/issues/5774 for an 
1690                     formats = [f for f in formats if f['format_id
'] not in dash_formats.keys()] 
1691                     formats.extend(dash_formats.values()) 
1693         # Check for malformed aspect ratio 
1694         stretched_m = re.search( 
1695             r'<meta\s
+property="og:video:tag".*?content
="yt:stretch=(?P<w>[0-9]+):(?P<h>[0-9]+)">', 
1698             w = float(stretched_m.group('w
')) 
1699             h = float(stretched_m.group('h
')) 
1700             # yt:stretch may hold invalid ratio data (e.g. for Q39EVAstoRM ratio is 17:0). 
1701             # We will only process correct ratios. 
1705                     if f.get('vcodec
') != 'none
': 
1706                         f['stretched_ratio
'] = ratio 
1708         self._sort_formats(formats) 
1710         self.mark_watched(video_id, video_info) 
1714             'uploader
': video_uploader, 
1715             'uploader_id
': video_uploader_id, 
1716             'uploader_url
': video_uploader_url, 
1717             'upload_date
': upload_date, 
1718             'license
': video_license, 
1719             'creator
': video_creator, 
1720             'title
': video_title, 
1721             'alt_title
': video_alt_title, 
1722             'thumbnail
': video_thumbnail, 
1723             'description
': video_description, 
1724             'categories
': video_categories, 
1726             'subtitles
': video_subtitles, 
1727             'automatic_captions
': automatic_captions, 
1728             'duration
': video_duration, 
1729             'age_limit
': 18 if age_gate else 0, 
1730             'annotations
': video_annotations, 
1731             'webpage_url
': proto + '://www
.youtube
.com
/watch?v
=%s' % video_id, 
1732             'view_count
': view_count, 
1733             'like_count
': like_count, 
1734             'dislike_count
': dislike_count, 
1735             'average_rating
': float_or_none(video_info.get('avg_rating
', [None])[0]), 
1738             'start_time
': start_time, 
1739             'end_time
': end_time, 
1743 class YoutubeSharedVideoIE(InfoExtractor): 
1744     _VALID_URL = r'(?
:https?
:)?
//(?
:www\
.)?youtube\
.com
/shared
\?.*\bci
=(?P
<id>[0-9A
-Za
-z_
-]{11}
)' 
1745     IE_NAME = 'youtube
:shared
' 
1748         'url
': 'https
://www
.youtube
.com
/shared?ci
=1nEzmT
-M4fU
', 
1750             'id': 'uPDB5I9wfp8
', 
1752             'title
': 'Pocoyo
: 90 minutos de episódios completos Português para crianç
as - PARTE 
3', 
1753             'description
': 'md5
:d9e4d9346a2dfff4c7dc4c8cec0f546d
', 
1754             'upload_date
': '20160219', 
1755             'uploader
': 'Pocoyo 
- PortuguĆŖ
s (BR
)', 
1756             'uploader_id
': 'PocoyoBrazil
', 
1758         'add_ie
': ['Youtube
'], 
1760             # There are already too many Youtube downloads 
1761             'skip_download
': True, 
1765     def _real_extract(self, url): 
1766         video_id = self._match_id(url) 
1768         webpage = self._download_webpage(url, video_id) 
1770         real_video_id = self._html_search_meta( 
1771             'videoId
', webpage, 'YouTube video 
id', fatal=True) 
1773         return self.url_result(real_video_id, YoutubeIE.ie_key()) 
1776 class YoutubePlaylistIE(YoutubePlaylistBaseInfoExtractor): 
1777     IE_DESC = 'YouTube
.com playlists
' 
1778     _VALID_URL = r"""(?x)(?: 
1784                                (?:course|view_play_list|my_playlists|artist|playlist|watch|embed/videoseries) 
1785                                \? (?:.*?[&;])*? (?:p|a|list)= 
1788                             youtu\.be/[0-9A-Za-z_-]{11}\?.*?\blist= 
1791                             (?:PL|LL|EC|UU|FL|RD|UL)?[0-9A-Za-z-_]{10,} 
1792                             # Top tracks, they can also include dots 
1797                         ((?:PL|LL|EC|UU|FL|RD|UL)[0-9A-Za-z-_]{10,}) 
1799     _TEMPLATE_URL = 'https
://www
.youtube
.com
/playlist?
list=%s&disable_polymer
=true
' 
1800     _VIDEO_RE = r'href
="\s*/watch\?v=(?P<id>[0-9A-Za-z_-]{11})&[^"]*?index
=(?P
<index
>\d
+)(?
:[^
>]+>(?P
<title
>[^
<]+))?
' 
1801     IE_NAME = 'youtube
:playlist
' 
1803         'url
': 'https
://www
.youtube
.com
/playlist?
list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re
', 
1805             'title
': 'ytdl test PL
', 
1806             'id': 'PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re
', 
1808         'playlist_count
': 3, 
1810         'url
': 'https
://www
.youtube
.com
/playlist?
list=PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx
', 
1812             'id': 'PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx
', 
1813             'title
': 'YDL_Empty_List
', 
1815         'playlist_count
': 0, 
1817         'note
': 'Playlist 
with deleted 
videos (#651). As a bonus, the video #51 is also twice in this list.', 
1818         'url': 'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC', 
1820             'title': '29C3: Not my department', 
1821             'id': 'PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC', 
1823         'playlist_count': 95, 
1825         'note': 'issue #673', 
1826         'url': 'PLBB231211A4F62143', 
1828             'title': '[OLD]Team Fortress 2 (Class-based LP)', 
1829             'id': 'PLBB231211A4F62143', 
1831         'playlist_mincount': 26, 
1833         'note': 'Large playlist', 
1834         'url': 'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q', 
1836             'title': 'Uploads from Cauchemar', 
1837             'id': 'UUBABnxM4Ar9ten8Mdjj1j0Q', 
1839         'playlist_mincount': 799, 
1841         'url': 'PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl', 
1843             'title': 'YDL_safe_search', 
1844             'id': 'PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl', 
1846         'playlist_count': 2, 
1849         'url': 'https://www.youtube.com/embed/videoseries?list=PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 
1850         'playlist_count': 4, 
1853             'id': 'PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 
1856         'note': 'Embedded SWF player', 
1857         'url': 'https://www.youtube.com/p/YN5VISEtHet5D4NEvfTd0zcgFk84NqFZ?hl=en_US&fs=1&rel=0', 
1858         'playlist_count': 4, 
1861             'id': 'YN5VISEtHet5D4NEvfTd0zcgFk84NqFZ', 
1864         'note': 'Buggy playlist: the webpage has a "Load more" button but it doesn\'t have more videos', 
1865         'url': 'https://www.youtube.com/playlist?list=UUXw-G3eDE9trcvY2sBMM_aA', 
1867             'title': 'Uploads from Interstellar Movie', 
1868             'id': 'UUXw-G3eDE9trcvY2sBMM_aA', 
1870         'playlist_mincount': 21, 
1872         # Playlist URL that does not actually serve a playlist 
1873         'url': 'https://www.youtube.com/watch?v=FqZTN594JQw&list=PLMYEtVRpaqY00V9W81Cwmzp6N6vZqfUKD4', 
1875             'id': 'FqZTN594JQw', 
1877             'title': "Smiley's People 01 detective, Adventure Series, Action", 
1878             'uploader': 'STREEM', 
1879             'uploader_id': 'UCyPhqAZgwYWZfxElWVbVJng', 
1880             'uploader_url': 're:https?://(?:www\.)?youtube\.com/channel/UCyPhqAZgwYWZfxElWVbVJng', 
1881             'upload_date': '20150526', 
1882             'license': 'Standard YouTube License', 
1883             'description': 'md5:507cdcb5a49ac0da37a920ece610be80', 
1884             'categories': ['People & Blogs'], 
1887             'dislike_count': int, 
1890             'skip_download': True, 
1892         'add_ie': [YoutubeIE
.ie_key()], 
1894         'url': 'https://youtu.be/yeWKywCrFtk?list=PL2qgrgXsNUG5ig9cat4ohreBjYLAPC0J5', 
1896             'id': 'yeWKywCrFtk', 
1898             'title': 'Small Scale Baler and Braiding Rugs', 
1899             'uploader': 'Backus-Page House Museum', 
1900             'uploader_id': 'backuspagemuseum', 
1901             'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/backuspagemuseum', 
1902             'upload_date': '20161008', 
1903             'license': 'Standard YouTube License', 
1904             'description': 'md5:800c0c78d5eb128500bffd4f0b4f2e8a', 
1905             'categories': ['Nonprofits & Activism'], 
1908             'dislike_count': int, 
1912             'skip_download': True, 
1915         'url': 'https://youtu.be/uWyaPkt-VOI?list=PL9D9FC436B881BA21', 
1916         'only_matching': True, 
1919     def _real_initialize(self
): 
1922     def _extract_mix(self
, playlist_id
): 
1923         # The mixes are generated from a single video 
1924         # the id of the playlist is just 'RD' + video_id 
1926         last_id 
= playlist_id
[-11:] 
1927         for n 
in itertools
.count(1): 
1928             url 
= 'https://youtube.com/watch?v=%s&list=%s' % (last_id
, playlist_id
) 
1929             webpage 
= self
._download
_webpage
( 
1930                 url
, playlist_id
, 'Downloading page {0} of Youtube mix'.format(n
)) 
1931             new_ids 
= orderedSet(re
.findall( 
1932                 r
'''(?xs)data-video-username=".*?".*? 
1933                            href="/watch\?v=([0-9A-Za-z_-]{11})&[^"]*?list=%s''' % re
.escape(playlist_id
), 
1935             # Fetch new pages until all the videos are repeated, it seems that 
1936             # there are always 51 unique videos. 
1937             new_ids 
= [_id 
for _id 
in new_ids 
if _id 
not in ids
] 
1943         url_results 
= self
._ids
_to
_results
(ids
) 
1945         search_title 
= lambda class_name
: get_element_by_attribute('class', class_name
, webpage
) 
1947             search_title('playlist-title') or 
1948             search_title('title long-title') or 
1949             search_title('title')) 
1950         title 
= clean_html(title_span
) 
1952         return self
.playlist_result(url_results
, playlist_id
, title
) 
1954     def _extract_playlist(self
, playlist_id
): 
1955         url 
= self
._TEMPLATE
_URL 
% playlist_id
 
1956         page 
= self
._download
_webpage
(url
, playlist_id
) 
1958         for match 
in re
.findall(r
'<div class="yt-alert-message">([^<]+)</div>', page
): 
1959             match 
= match
.strip() 
1960             # Check if the playlist exists or is private 
1961             if re
.match(r
'[^<]*(The|This) playlist (does not exist|is private)[^<]*', match
): 
1962                 raise ExtractorError( 
1963                     'The playlist doesn\'t exist or is private, use --username or ' 
1964                     '--netrc to access it.', 
1966             elif re
.match(r
'[^<]*Invalid parameters[^<]*', match
): 
1967                 raise ExtractorError( 
1968                     'Invalid parameters. Maybe URL is incorrect.', 
1970             elif re
.match(r
'[^<]*Choose your language[^<]*', match
): 
1973                 self
.report_warning('Youtube gives an alert message: ' + match
) 
1975         playlist_title 
= self
._html
_search
_regex
( 
1976             r
'(?s)<h1 class="pl-header-title[^"]*"[^>]*>\s*(.*?)\s*</h1>', 
1977             page
, 'title', default
=None) 
1981         if not playlist_title
: 
1983                 # Some playlist URLs don't actually serve a playlist (e.g. 
1984                 # https://www.youtube.com/watch?v=FqZTN594JQw&list=PLMYEtVRpaqY00V9W81Cwmzp6N6vZqfUKD4) 
1985                 next(self
._entries
(page
, playlist_id
)) 
1986             except StopIteration: 
1989         return has_videos
, self
.playlist_result( 
1990             self
._entries
(page
, playlist_id
), playlist_id
, playlist_title
) 
1992     def _check_download_just_video(self
, url
, playlist_id
): 
1993         # Check if it's a video-specific URL 
1994         query_dict 
= compat_urlparse
.parse_qs(compat_urlparse
.urlparse(url
).query
) 
1995         video_id 
= query_dict
.get('v', [None])[0] or self
._search
_regex
( 
1996             r
'(?:^|//)youtu\.be/([0-9A-Za-z_-]{11})', url
, 
1997             'video id', default
=None) 
1999             if self
._downloader
.params
.get('noplaylist'): 
2000                 self
.to_screen('Downloading just video %s because of --no-playlist' % video_id
) 
2001                 return video_id
, self
.url_result(video_id
, 'Youtube', video_id
=video_id
) 
2003                 self
.to_screen('Downloading playlist %s - add --no-playlist to just download video %s' % (playlist_id
, video_id
)) 
2004                 return video_id
, None 
2007     def _real_extract(self
, url
): 
2008         # Extract playlist id 
2009         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2011             raise ExtractorError('Invalid URL: %s' % url
) 
2012         playlist_id 
= mobj
.group(1) or mobj
.group(2) 
2014         video_id
, video 
= self
._check
_download
_just
_video
(url
, playlist_id
) 
2018         if playlist_id
.startswith(('RD', 'UL', 'PU')): 
2019             # Mixes require a custom extraction process 
2020             return self
._extract
_mix
(playlist_id
) 
2022         has_videos
, playlist 
= self
._extract
_playlist
(playlist_id
) 
2023         if has_videos 
or not video_id
: 
2026         # Some playlist URLs don't actually serve a playlist (see 
2027         # https://github.com/rg3/youtube-dl/issues/10537). 
2028         # Fallback to plain video extraction if there is a video id 
2029         # along with playlist id. 
2030         return self
.url_result(video_id
, 'Youtube', video_id
=video_id
) 
2033 class YoutubeChannelIE(YoutubePlaylistBaseInfoExtractor
): 
2034     IE_DESC 
= 'YouTube.com channels' 
2035     _VALID_URL 
= r
'https?://(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/(?P<id>[0-9A-Za-z_-]+)' 
2036     _TEMPLATE_URL 
= 'https://www.youtube.com/channel/%s/videos' 
2037     _VIDEO_RE 
= r
'(?:title="(?P<title>[^"]+)"[^>]+)?href="/watch\?v=(?P<id>[0-9A-Za-z_-]+)&?' 
2038     IE_NAME 
= 'youtube:channel' 
2040         'note': 'paginated channel', 
2041         'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w', 
2042         'playlist_mincount': 91, 
2044             'id': 'UUKfVa3S1e4PHvxWcwyMMg8w', 
2045             'title': 'Uploads from lex will', 
2048         'note': 'Age restricted channel', 
2049         # from https://www.youtube.com/user/DeusExOfficial 
2050         'url': 'https://www.youtube.com/channel/UCs0ifCMCm1icqRbqhUINa0w', 
2051         'playlist_mincount': 64, 
2053             'id': 'UUs0ifCMCm1icqRbqhUINa0w', 
2054             'title': 'Uploads from Deus Ex', 
2059     def suitable(cls
, url
): 
2060         return (False if YoutubePlaylistsIE
.suitable(url
) or YoutubeLiveIE
.suitable(url
) 
2061                 else super(YoutubeChannelIE
, cls
).suitable(url
)) 
2063     def _build_template_url(self
, url
, channel_id
): 
2064         return self
._TEMPLATE
_URL 
% channel_id
 
2066     def _real_extract(self
, url
): 
2067         channel_id 
= self
._match
_id
(url
) 
2069         url 
= self
._build
_template
_url
(url
, channel_id
) 
2071         # Channel by page listing is restricted to 35 pages of 30 items, i.e. 1050 videos total (see #5778) 
2072         # Workaround by extracting as a playlist if managed to obtain channel playlist URL 
2073         # otherwise fallback on channel by page extraction 
2074         channel_page 
= self
._download
_webpage
( 
2075             url 
+ '?view=57', channel_id
, 
2076             'Downloading channel page', fatal
=False) 
2077         if channel_page 
is False: 
2078             channel_playlist_id 
= False 
2080             channel_playlist_id 
= self
._html
_search
_meta
( 
2081                 'channelId', channel_page
, 'channel id', default
=None) 
2082             if not channel_playlist_id
: 
2083                 channel_url 
= self
._html
_search
_meta
( 
2084                     ('al:ios:url', 'twitter:app:url:iphone', 'twitter:app:url:ipad'), 
2085                     channel_page
, 'channel url', default
=None) 
2087                     channel_playlist_id 
= self
._search
_regex
( 
2088                         r
'vnd\.youtube://user/([0-9A-Za-z_-]+)', 
2089                         channel_url
, 'channel id', default
=None) 
2090         if channel_playlist_id 
and channel_playlist_id
.startswith('UC'): 
2091             playlist_id 
= 'UU' + channel_playlist_id
[2:] 
2092             return self
.url_result( 
2093                 compat_urlparse
.urljoin(url
, '/playlist?list=%s' % playlist_id
), 'YoutubePlaylist') 
2095         channel_page 
= self
._download
_webpage
(url
, channel_id
, 'Downloading page #1') 
2096         autogenerated 
= re
.search(r
'''(?x) 
2098                     channel-header-autogenerated-label| 
2099                     yt-channel-title-autogenerated 
2100                 )[^"]*"''', channel_page
) is not None 
2103             # The videos are contained in a single page 
2104             # the ajax pages can't be used, they are empty 
2107                     video_id
, 'Youtube', video_id
=video_id
, 
2108                     video_title
=video_title
) 
2109                 for video_id
, video_title 
in self
.extract_videos_from_page(channel_page
)] 
2110             return self
.playlist_result(entries
, channel_id
) 
2113             next(self
._entries
(channel_page
, channel_id
)) 
2114         except StopIteration: 
2115             alert_message 
= self
._html
_search
_regex
( 
2116                 r
'(?s)<div[^>]+class=(["\']).*?
\byt
-alert
-message
\b.*?\
1[^
>]*>(?P
<alert
>[^
<]+)</div
>', 
2117                 channel_page, 'alert
', default=None, group='alert
') 
2119                 raise ExtractorError('Youtube said
: %s' % alert_message, expected=True) 
2121         return self.playlist_result(self._entries(channel_page, channel_id), channel_id) 
2124 class YoutubeUserIE(YoutubeChannelIE): 
2125     IE_DESC = 'YouTube
.com user 
videos (URL 
or "ytuser" keyword
)' 
2126     _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_-]+)' 
2127     _TEMPLATE_URL = 'https
://www
.youtube
.com
/%s/%s/videos
' 
2128     IE_NAME = 'youtube
:user
' 
2131         'url
': 'https
://www
.youtube
.com
/user
/TheLinuxFoundation
', 
2132         'playlist_mincount
': 320, 
2134             'id': 'UUfX55Sx5hEFjoC3cNs6mCUQ
', 
2135             'title
': 'Uploads 
from The Linux Foundation
', 
2138         # Only available via https://www.youtube.com/c/12minuteathlete/videos 
2139         # but not https://www.youtube.com/user/12minuteathlete/videos 
2140         'url
': 'https
://www
.youtube
.com
/c
/12minuteathlete
/videos
', 
2141         'playlist_mincount
': 249, 
2143             'id': 'UUVjM
-zV6_opMDx7WYxnjZiQ
', 
2144             'title
': 'Uploads 
from 12 Minute Athlete
', 
2147         'url
': 'ytuser
:phihag
', 
2148         'only_matching
': True, 
2150         'url
': 'https
://www
.youtube
.com
/c
/gametrailers
', 
2151         'only_matching
': True, 
2153         'url
': 'https
://www
.youtube
.com
/gametrailers
', 
2154         'only_matching
': True, 
2156         # This channel is not available. 
2157         'url
': 'https
://www
.youtube
.com
/user
/kananishinoSMEJ
/videos
', 
2158         'only_matching
': True, 
2162     def suitable(cls, url): 
2163         # Don't 
return True if the url can be extracted 
with other youtube
 
2164         # extractor, the regex would is too permissive and it would match. 
2165         other_yt_ies 
= iter(klass 
for (name
, klass
) in globals().items() if name
.startswith('Youtube') and name
.endswith('IE') and klass 
is not cls
) 
2166         if any(ie
.suitable(url
) for ie 
in other_yt_ies
): 
2169             return super(YoutubeUserIE
, cls
).suitable(url
) 
2171     def _build_template_url(self
, url
, channel_id
): 
2172         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2173         return self
._TEMPLATE
_URL 
% (mobj
.group('user') or 'user', mobj
.group('id')) 
2176 class YoutubeLiveIE(YoutubeBaseInfoExtractor
): 
2177     IE_DESC 
= 'YouTube.com live streams' 
2178     _VALID_URL 
= r
'(?P<base_url>https?://(?:\w+\.)?youtube\.com/(?:(?:user|channel|c)/)?(?P<id>[^/]+))/live' 
2179     IE_NAME 
= 'youtube:live' 
2182         'url': 'https://www.youtube.com/user/TheYoungTurks/live', 
2184             'id': 'a48o2S1cPoo', 
2186             'title': 'The Young Turks - Live Main Show', 
2187             'uploader': 'The Young Turks', 
2188             'uploader_id': 'TheYoungTurks', 
2189             'uploader_url': 're:https?://(?:www\.)?youtube\.com/user/TheYoungTurks', 
2190             'upload_date': '20150715', 
2191             'license': 'Standard YouTube License', 
2192             'description': 'md5:438179573adcdff3c97ebb1ee632b891', 
2193             'categories': ['News & Politics'], 
2194             'tags': ['Cenk Uygur (TV Program Creator)', 'The Young Turks (Award-Winning Work)', 'Talk Show (TV Genre)'], 
2196             'dislike_count': int, 
2199             'skip_download': True, 
2202         'url': 'https://www.youtube.com/channel/UC1yBKRuGpC1tSM73A0ZjYjQ/live', 
2203         'only_matching': True, 
2205         'url': 'https://www.youtube.com/c/CommanderVideoHq/live', 
2206         'only_matching': True, 
2208         'url': 'https://www.youtube.com/TheYoungTurks/live', 
2209         'only_matching': True, 
2212     def _real_extract(self
, url
): 
2213         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2214         channel_id 
= mobj
.group('id') 
2215         base_url 
= mobj
.group('base_url') 
2216         webpage 
= self
._download
_webpage
(url
, channel_id
, fatal
=False) 
2218             page_type 
= self
._og
_search
_property
( 
2219                 'type', webpage
, 'page type', default
=None) 
2220             video_id 
= self
._html
_search
_meta
( 
2221                 'videoId', webpage
, 'video id', default
=None) 
2222             if page_type 
== 'video' and video_id 
and re
.match(r
'^[0-9A-Za-z_-]{11}$', video_id
): 
2223                 return self
.url_result(video_id
, YoutubeIE
.ie_key()) 
2224         return self
.url_result(base_url
) 
2227 class YoutubePlaylistsIE(YoutubePlaylistsBaseInfoExtractor
): 
2228     IE_DESC 
= 'YouTube.com user/channel playlists' 
2229     _VALID_URL 
= r
'https?://(?:\w+\.)?youtube\.com/(?:user|channel)/(?P<id>[^/]+)/playlists' 
2230     IE_NAME 
= 'youtube:playlists' 
2233         'url': 'https://www.youtube.com/user/ThirstForScience/playlists', 
2234         'playlist_mincount': 4, 
2236             'id': 'ThirstForScience', 
2237             'title': 'Thirst for Science', 
2240         # with "Load more" button 
2241         'url': 'https://www.youtube.com/user/igorkle1/playlists?view=1&sort=dd', 
2242         'playlist_mincount': 70, 
2245             'title': 'ŠŠ³Š¾ŃŃ ŠŠ»ŠµŠ¹Š½ŠµŃ', 
2248         'url': 'https://www.youtube.com/channel/UCiU1dHvZObB2iP6xkJ__Icw/playlists', 
2249         'playlist_mincount': 17, 
2251             'id': 'UCiU1dHvZObB2iP6xkJ__Icw', 
2252             'title': 'Chem Player', 
2257 class YoutubeSearchIE(SearchInfoExtractor
, YoutubePlaylistIE
): 
2258     IE_DESC 
= 'YouTube.com searches' 
2259     # there doesn't appear to be a real limit, for example if you search for 
2260     # 'python' you get more than 8.000.000 results 
2261     _MAX_RESULTS 
= float('inf') 
2262     IE_NAME 
= 'youtube:search' 
2263     _SEARCH_KEY 
= 'ytsearch' 
2264     _EXTRA_QUERY_ARGS 
= {} 
2267     def _get_n_results(self
, query
, n
): 
2268         """Get a specified number of results for a query""" 
2273         for pagenum 
in itertools
.count(1): 
2275                 'search_query': query
.encode('utf-8'), 
2279             url_query
.update(self
._EXTRA
_QUERY
_ARGS
) 
2280             result_url 
= 'https://www.youtube.com/results?' + compat_urllib_parse_urlencode(url_query
) 
2281             data 
= self
._download
_json
( 
2282                 result_url
, video_id
='query "%s"' % query
, 
2283                 note
='Downloading page %s' % pagenum
, 
2284                 errnote
='Unable to download API page') 
2285             html_content 
= data
[1]['body']['content'] 
2287             if 'class="search-message' in html_content
: 
2288                 raise ExtractorError( 
2289                     '[youtube] No video results', expected
=True) 
2291             new_videos 
= self
._ids
_to
_results
(orderedSet(re
.findall( 
2292                 r
'href="/watch\?v=(.{11})', html_content
))) 
2293             videos 
+= new_videos
 
2294             if not new_videos 
or len(videos
) > limit
: 
2299         return self
.playlist_result(videos
, query
) 
2302 class YoutubeSearchDateIE(YoutubeSearchIE
): 
2303     IE_NAME 
= YoutubeSearchIE
.IE_NAME 
+ ':date' 
2304     _SEARCH_KEY 
= 'ytsearchdate' 
2305     IE_DESC 
= 'YouTube.com searches, newest videos first' 
2306     _EXTRA_QUERY_ARGS 
= {'search_sort': 'video_date_uploaded'} 
2309 class YoutubeSearchURLIE(YoutubePlaylistBaseInfoExtractor
): 
2310     IE_DESC 
= 'YouTube.com search URLs' 
2311     IE_NAME 
= 'youtube:search_url' 
2312     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/results\?(.*?&)?(?:search_query|q)=(?P<query>[^&]+)(?:[&]|$)' 
2313     _VIDEO_RE 
= r
'href="\s*/watch\?v=(?P<id>[0-9A-Za-z_-]{11})(?:[^"]*"[^>]+\btitle="(?P<title>[^"]+))?' 
2315         'url': 'https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', 
2316         'playlist_mincount': 5, 
2318             'title': 'youtube-dl test video', 
2321         'url': 'https://www.youtube.com/results?q=test&sp=EgQIBBgB', 
2322         'only_matching': True, 
2325     def _real_extract(self
, url
): 
2326         mobj 
= re
.match(self
._VALID
_URL
, url
) 
2327         query 
= compat_urllib_parse_unquote_plus(mobj
.group('query')) 
2328         webpage 
= self
._download
_webpage
(url
, query
) 
2329         return self
.playlist_result(self
._process
_page
(webpage
), playlist_title
=query
) 
2332 class YoutubeShowIE(YoutubePlaylistsBaseInfoExtractor
): 
2333     IE_DESC 
= 'YouTube.com (multi-season) shows' 
2334     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/show/(?P<id>[^?#]*)' 
2335     IE_NAME 
= 'youtube:show' 
2337         'url': 'https://www.youtube.com/show/airdisasters', 
2338         'playlist_mincount': 5, 
2340             'id': 'airdisasters', 
2341             'title': 'Air Disasters', 
2345     def _real_extract(self
, url
): 
2346         playlist_id 
= self
._match
_id
(url
) 
2347         return super(YoutubeShowIE
, self
)._real
_extract
( 
2348             'https://www.youtube.com/show/%s/playlists' % playlist_id
) 
2351 class YoutubeFeedsInfoExtractor(YoutubeBaseInfoExtractor
): 
2353     Base class for feed extractors 
2354     Subclasses must define the _FEED_NAME and _PLAYLIST_TITLE properties. 
2356     _LOGIN_REQUIRED 
= True 
2360         return 'youtube:%s' % self
._FEED
_NAME
 
2362     def _real_initialize(self
): 
2365     def _real_extract(self
, url
): 
2366         page 
= self
._download
_webpage
( 
2367             'https://www.youtube.com/feed/%s' % self
._FEED
_NAME
, self
._PLAYLIST
_TITLE
) 
2369         # The extraction process is the same as for playlists, but the regex 
2370         # for the video ids doesn't contain an index 
2372         more_widget_html 
= content_html 
= page
 
2373         for page_num 
in itertools
.count(1): 
2374             matches 
= re
.findall(r
'href="\s*/watch\?v=([0-9A-Za-z_-]{11})', content_html
) 
2376             # 'recommended' feed has infinite 'load more' and each new portion spins 
2377             # the same videos in (sometimes) slightly different order, so we'll check 
2378             # for unicity and break when portion has no new videos 
2379             new_ids 
= filter(lambda video_id
: video_id 
not in ids
, orderedSet(matches
)) 
2385             mobj 
= re
.search(r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html
) 
2389             more 
= self
._download
_json
( 
2390                 'https://youtube.com/%s' % mobj
.group('more'), self
._PLAYLIST
_TITLE
, 
2391                 'Downloading page #%s' % page_num
, 
2392                 transform_source
=uppercase_escape
) 
2393             content_html 
= more
['content_html'] 
2394             more_widget_html 
= more
['load_more_widget_html'] 
2396         return self
.playlist_result( 
2397             self
._ids
_to
_results
(ids
), playlist_title
=self
._PLAYLIST
_TITLE
) 
2400 class YoutubeWatchLaterIE(YoutubePlaylistIE
): 
2401     IE_NAME 
= 'youtube:watchlater' 
2402     IE_DESC 
= 'Youtube watch later list, ":ytwatchlater" for short (requires authentication)' 
2403     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/(?:feed/watch_later|(?:playlist|watch)\?(?:.+&)?list=WL)|:ytwatchlater' 
2406         'url': 'https://www.youtube.com/playlist?list=WL', 
2407         'only_matching': True, 
2409         'url': 'https://www.youtube.com/watch?v=bCNU9TrbiRk&index=1&list=WL', 
2410         'only_matching': True, 
2413     def _real_extract(self
, url
): 
2414         _
, video 
= self
._check
_download
_just
_video
(url
, 'WL') 
2417         _
, playlist 
= self
._extract
_playlist
('WL') 
2421 class YoutubeFavouritesIE(YoutubeBaseInfoExtractor
): 
2422     IE_NAME 
= 'youtube:favorites' 
2423     IE_DESC 
= 'YouTube.com favourite videos, ":ytfav" for short (requires authentication)' 
2424     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/my_favorites|:ytfav(?:ou?rites)?' 
2425     _LOGIN_REQUIRED 
= True 
2427     def _real_extract(self
, url
): 
2428         webpage 
= self
._download
_webpage
('https://www.youtube.com/my_favorites', 'Youtube Favourites videos') 
2429         playlist_id 
= self
._search
_regex
(r
'list=(.+?)["&]', webpage
, 'favourites playlist id') 
2430         return self
.url_result(playlist_id
, 'YoutubePlaylist') 
2433 class YoutubeRecommendedIE(YoutubeFeedsInfoExtractor
): 
2434     IE_DESC 
= 'YouTube.com recommended videos, ":ytrec" for short (requires authentication)' 
2435     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/feed/recommended|:ytrec(?:ommended)?' 
2436     _FEED_NAME 
= 'recommended' 
2437     _PLAYLIST_TITLE 
= 'Youtube Recommended videos' 
2440 class YoutubeSubscriptionsIE(YoutubeFeedsInfoExtractor
): 
2441     IE_DESC 
= 'YouTube.com subscriptions feed, "ytsubs" keyword (requires authentication)' 
2442     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/feed/subscriptions|:ytsubs(?:criptions)?' 
2443     _FEED_NAME 
= 'subscriptions' 
2444     _PLAYLIST_TITLE 
= 'Youtube Subscriptions' 
2447 class YoutubeHistoryIE(YoutubeFeedsInfoExtractor
): 
2448     IE_DESC 
= 'Youtube watch history, ":ythistory" for short (requires authentication)' 
2449     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/feed/history|:ythistory' 
2450     _FEED_NAME 
= 'history' 
2451     _PLAYLIST_TITLE 
= 'Youtube History' 
2454 class YoutubeTruncatedURLIE(InfoExtractor
): 
2455     IE_NAME 
= 'youtube:truncated_url' 
2456     IE_DESC 
= False  # Do not list 
2457     _VALID_URL 
= r
'''(?x) 
2459         (?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/ 
2462             annotation_id=annotation_[^&]+| 
2468             attribution_link\?a=[^&]+ 
2474         'url': 'https://www.youtube.com/watch?annotation_id=annotation_3951667041', 
2475         'only_matching': True, 
2477         'url': 'https://www.youtube.com/watch?', 
2478         'only_matching': True, 
2480         'url': 'https://www.youtube.com/watch?x-yt-cl=84503534', 
2481         'only_matching': True, 
2483         'url': 'https://www.youtube.com/watch?feature=foo', 
2484         'only_matching': True, 
2486         'url': 'https://www.youtube.com/watch?hl=en-GB', 
2487         'only_matching': True, 
2489         'url': 'https://www.youtube.com/watch?t=2372', 
2490         'only_matching': True, 
2493     def _real_extract(self
, url
): 
2494         raise ExtractorError( 
2495             'Did you forget to quote the URL? Remember that & is a meta ' 
2496             'character in most shells, so you want to put the URL in quotes, ' 
2498             '"https://www.youtube.com/watch?feature=foo&v=BaW_jenozKc" ' 
2499             ' or simply  youtube-dl BaW_jenozKc  .', 
2503 class YoutubeTruncatedIDIE(InfoExtractor
): 
2504     IE_NAME 
= 'youtube:truncated_id' 
2505     IE_DESC 
= False  # Do not list 
2506     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/watch\?v=(?P<id>[0-9A-Za-z_-]{1,10})$' 
2509         'url': 'https://www.youtube.com/watch?v=N_708QY7Ob', 
2510         'only_matching': True, 
2513     def _real_extract(self
, url
): 
2514         video_id 
= self
._match
_id
(url
) 
2515         raise ExtractorError( 
2516             'Incomplete YouTube ID %s. URL %s looks truncated.' % (video_id
, url
),