3 from __future__ 
import unicode_literals
 
  13 from .common 
import InfoExtractor
, SearchInfoExtractor
 
  14 from ..jsinterp 
import JSInterpreter
 
  15 from ..swfinterp 
import SWFInterpreter
 
  16 from ..compat 
import ( 
  20     compat_urllib_request
, 
  28     get_element_by_attribute
, 
  38 class YoutubeBaseInfoExtractor(InfoExtractor
): 
  39     """Provide base functions for Youtube extractors""" 
  40     _LOGIN_URL 
= 'https://accounts.google.com/ServiceLogin' 
  41     _TWOFACTOR_URL 
= 'https://accounts.google.com/SecondFactor' 
  42     _NETRC_MACHINE 
= 'youtube' 
  43     # If True it will raise an error if no login info is provided 
  44     _LOGIN_REQUIRED 
= False 
  46     def _set_language(self
): 
  48             '.youtube.com', 'PREF', 'f1=50000000&hl=en', 
  49             # YouTube sets the expire time to about two months 
  50             expire_time
=time
.time() + 2 * 30 * 24 * 3600) 
  54         Attempt to log in to YouTube. 
  55         True is returned if successful or skipped. 
  56         False is returned if login failed. 
  58         If _LOGIN_REQUIRED is set and no authentication was provided, an error is raised. 
  60         (username
, password
) = self
._get
_login
_info
() 
  61         # No authentication to be performed 
  63             if self
._LOGIN
_REQUIRED
: 
  64                 raise ExtractorError('No login info available, needed for using %s.' % self
.IE_NAME
, expected
=True) 
  67         login_page 
= self
._download
_webpage
( 
  68             self
._LOGIN
_URL
, None, 
  69             note
='Downloading login page', 
  70             errnote
='unable to fetch login page', fatal
=False) 
  71         if login_page 
is False: 
  74         galx 
= self
._search
_regex
(r
'(?s)<input.+?name="GALX".+?value="(.+?)"', 
  75                                   login_page
, 'Login GALX parameter') 
  79             'continue': 'https://www.youtube.com/signin?action_handle_signin=true&feature=sign_in_button&hl=en_US&nomobiletemp=1', 
  84             'PersistentCookie': 'yes', 
  86             'bgresponse': 'js_disabled', 
  87             'checkConnection': '', 
  88             'checkedDomains': 'youtube', 
 100         # Convert to UTF-8 *before* urlencode because Python 2.x's urlencode 
 102         login_form 
= dict((k
.encode('utf-8'), v
.encode('utf-8')) for k
, v 
in login_form_strs
.items()) 
 103         login_data 
= compat_urllib_parse
.urlencode(login_form
).encode('ascii') 
 105         req 
= compat_urllib_request
.Request(self
._LOGIN
_URL
, login_data
) 
 106         login_results 
= self
._download
_webpage
( 
 108             note
='Logging in', errnote
='unable to log in', fatal
=False) 
 109         if login_results 
is False: 
 112         if re
.search(r
'id="errormsg_0_Passwd"', login_results
) is not None: 
 113             raise ExtractorError('Please use your account password and a two-factor code instead of an application-specific password.', expected
=True) 
 116         # TODO add SMS and phone call support - these require making a request and then prompting the user 
 118         if re
.search(r
'(?i)<form[^>]* id="gaia_secondfactorform"', login_results
) is not None: 
 119             tfa_code 
= self
._get
_tfa
_info
() 
 122                 self
._downloader
.report_warning('Two-factor authentication required. Provide it with --twofactor <code>') 
 123                 self
._downloader
.report_warning('(Note that only TOTP (Google Authenticator App) codes work at this time.)') 
 126             # Unlike the first login form, secTok and timeStmp are both required for the TFA form 
 128             match 
= re
.search(r
'id="secTok"\n\s+value=\'(.+)\'/>', login_results, re.M | re.U) 
 130                 self._downloader.report_warning('Failed to get secTok 
- did the page structure change?
') 
 131             secTok = match.group(1) 
 132             match = re.search(r'id="timeStmp"\n\s
+value
=\'(.+)\'/>', login_results, re.M | re.U) 
 134                 self._downloader.report_warning('Failed to get timeStmp 
- did the page structure change?
') 
 135             timeStmp = match.group(1) 
 138                 'continue': 'https
://www
.youtube
.com
/signin?action_handle_signin
=true
&feature
=sign_in_button
&hl
=en_US
&nomobiletemp
=1', 
 140                 'smsUserPin
': tfa_code, 
 141                 'smsVerifyPin
': 'Verify
', 
 143                 'PersistentCookie
': 'yes
', 
 144                 'checkConnection
': '', 
 145                 'checkedDomains
': 'youtube
', 
 148                 'timeStmp
': timeStmp, 
 149                 'service
': 'youtube
', 
 152             tfa_form = dict((k.encode('utf
-8'), v.encode('utf
-8')) for k, v in tfa_form_strs.items()) 
 153             tfa_data = compat_urllib_parse.urlencode(tfa_form).encode('ascii
') 
 155             tfa_req = compat_urllib_request.Request(self._TWOFACTOR_URL, tfa_data) 
 156             tfa_results = self._download_webpage( 
 158                 note='Submitting TFA code
', errnote='unable to submit tfa
', fatal=False) 
 160             if tfa_results is False: 
 163             if re.search(r'(?i
)<form
[^
>]* id="gaia_secondfactorform"', tfa_results) is not None: 
 164                 self._downloader.report_warning('Two
-factor code expired
. Please 
try again
, or use a one
-use backup code instead
.') 
 166             if re.search(r'(?i
)<form
[^
>]* id="gaia_loginform"', tfa_results) is not None: 
 167                 self._downloader.report_warning('unable to log 
in - did the page structure change?
') 
 169             if re.search(r'smsauth
-interstitial
-reviewsettings
', tfa_results) is not None: 
 170                 self._downloader.report_warning('Your Google account has a security notice
. Please log 
in on your web browser
, resolve the notice
, and try again
.') 
 173         if re.search(r'(?i
)<form
[^
>]* id="gaia_loginform"', login_results) is not None: 
 174             self._downloader.report_warning('unable to log 
in: bad username 
or password
') 
 178     def _real_initialize(self): 
 179         if self._downloader is None: 
 182         if not self._login(): 
 186 class YoutubeIE(YoutubeBaseInfoExtractor): 
 187     IE_DESC = 'YouTube
.com
' 
 188     _VALID_URL = r"""(?x)^ 
 190                          (?:https?://|//)                                    # http(s):// or protocol-independent URL 
 191                          (?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/| 
 192                             (?:www\.)?deturl\.com/www\.youtube\.com/| 
 193                             (?:www\.)?pwnyoutube\.com/| 
 194                             (?:www\.)?yourepeat\.com/| 
 195                             tube\.majestyc\.net/| 
 196                             youtube\.googleapis\.com/)                        # the various hostnames, with wildcard subdomains 
 197                          (?:.*?\#/)?                                          # handle anchor (#/) redirect urls 
 198                          (?:                                                  # the various things that can precede the ID: 
 199                              (?:(?:v|embed|e)/(?!videoseries))                # v/ or embed/ or e/ 
 200                              |(?:                                             # or the v= param in all its forms 
 201                                  (?:(?:watch|movie)(?:_popup)?(?:\.php)?/?)?  # preceding watch(_popup|.php) or nothing (like /?v=xxxx) 
 202                                  (?:\?|\#!?)                                  # the params delimiter ? or # or #! 
 203                                  (?:.*?&)?                                    # any other preceding param (like /?s=tuff&v=xxxx) 
 207                          |youtu\.be/                                          # just youtu.be/xxxx 
 208                          |(?:www\.)?cleanvideosearch\.com/media/action/yt/watch\?videoId= 
 210                      )?                                                       # all until now is optional -> you can pass the naked ID 
 211                      ([0-9A-Za-z_-]{11})                                      # here is it! the YouTube video ID 
 212                      (?!.*?&list=)                                            # combined list/video URLs are handled by the playlist IE 
 213                      (?(1).+)?                                                # if we found the ID, everything can follow 
 215     _NEXT_URL_RE = r'[\?&]next_url
=([^
&]+)' 
 217         '5': {'ext
': 'flv
', 'width
': 400, 'height
': 240}, 
 218         '6': {'ext
': 'flv
', 'width
': 450, 'height
': 270}, 
 219         '13': {'ext
': '3gp
'}, 
 220         '17': {'ext
': '3gp
', 'width
': 176, 'height
': 144}, 
 221         '18': {'ext
': 'mp4
', 'width
': 640, 'height
': 360}, 
 222         '22': {'ext
': 'mp4
', 'width
': 1280, 'height
': 720}, 
 223         '34': {'ext
': 'flv
', 'width
': 640, 'height
': 360}, 
 224         '35': {'ext
': 'flv
', 'width
': 854, 'height
': 480}, 
 225         '36': {'ext
': '3gp
', 'width
': 320, 'height
': 240}, 
 226         '37': {'ext
': 'mp4
', 'width
': 1920, 'height
': 1080}, 
 227         '38': {'ext
': 'mp4
', 'width
': 4096, 'height
': 3072}, 
 228         '43': {'ext
': 'webm
', 'width
': 640, 'height
': 360}, 
 229         '44': {'ext
': 'webm
', 'width
': 854, 'height
': 480}, 
 230         '45': {'ext
': 'webm
', 'width
': 1280, 'height
': 720}, 
 231         '46': {'ext
': 'webm
', 'width
': 1920, 'height
': 1080}, 
 235         '82': {'ext
': 'mp4
', 'height
': 360, 'format_note
': '3D
', 'preference
': -20}, 
 236         '83': {'ext
': 'mp4
', 'height
': 480, 'format_note
': '3D
', 'preference
': -20}, 
 237         '84': {'ext
': 'mp4
', 'height
': 720, 'format_note
': '3D
', 'preference
': -20}, 
 238         '85': {'ext
': 'mp4
', 'height
': 1080, 'format_note
': '3D
', 'preference
': -20}, 
 239         '100': {'ext
': 'webm
', 'height
': 360, 'format_note
': '3D
', 'preference
': -20}, 
 240         '101': {'ext
': 'webm
', 'height
': 480, 'format_note
': '3D
', 'preference
': -20}, 
 241         '102': {'ext
': 'webm
', 'height
': 720, 'format_note
': '3D
', 'preference
': -20}, 
 243         # Apple HTTP Live Streaming 
 244         '92': {'ext
': 'mp4
', 'height
': 240, 'format_note
': 'HLS
', 'preference
': -10}, 
 245         '93': {'ext
': 'mp4
', 'height
': 360, 'format_note
': 'HLS
', 'preference
': -10}, 
 246         '94': {'ext
': 'mp4
', 'height
': 480, 'format_note
': 'HLS
', 'preference
': -10}, 
 247         '95': {'ext
': 'mp4
', 'height
': 720, 'format_note
': 'HLS
', 'preference
': -10}, 
 248         '96': {'ext
': 'mp4
', 'height
': 1080, 'format_note
': 'HLS
', 'preference
': -10}, 
 249         '132': {'ext
': 'mp4
', 'height
': 240, 'format_note
': 'HLS
', 'preference
': -10}, 
 250         '151': {'ext
': 'mp4
', 'height
': 72, 'format_note
': 'HLS
', 'preference
': -10}, 
 253         '133': {'ext
': 'mp4
', 'height
': 240, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 254         '134': {'ext
': 'mp4
', 'height
': 360, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 255         '135': {'ext
': 'mp4
', 'height
': 480, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 256         '136': {'ext
': 'mp4
', 'height
': 720, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 257         '137': {'ext
': 'mp4
', 'height
': 1080, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 258         '138': {'ext
': 'mp4
', 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40},  # Height can vary (https://github.com/rg3/youtube-dl/issues/4559) 
 259         '160': {'ext
': 'mp4
', 'height
': 144, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 260         '264': {'ext
': 'mp4
', 'height
': 1440, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 261         '298': {'ext
': 'mp4
', 'height
': 720, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'fps
': 60, 'vcodec
': 'h264
'}, 
 262         '299': {'ext
': 'mp4
', 'height
': 1080, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'fps
': 60, 'vcodec
': 'h264
'}, 
 263         '266': {'ext
': 'mp4
', 'height
': 2160, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'vcodec
': 'h264
'}, 
 266         '139': {'ext
': 'm4a
', 'format_note
': 'DASH audio
', 'acodec
': 'aac
', 'vcodec
': 'none
', 'abr
': 48, 'preference
': -50, 'container
': 'm4a_dash
'}, 
 267         '140': {'ext
': 'm4a
', 'format_note
': 'DASH audio
', 'acodec
': 'aac
', 'vcodec
': 'none
', 'abr
': 128, 'preference
': -50, 'container
': 'm4a_dash
'}, 
 268         '141': {'ext
': 'm4a
', 'format_note
': 'DASH audio
', 'acodec
': 'aac
', 'vcodec
': 'none
', 'abr
': 256, 'preference
': -50, 'container
': 'm4a_dash
'}, 
 271         '167': {'ext
': 'webm
', 'height
': 360, 'width
': 640, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'container
': 'webm
', 'vcodec
': 'VP8
', 'preference
': -40}, 
 272         '168': {'ext
': 'webm
', 'height
': 480, 'width
': 854, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'container
': 'webm
', 'vcodec
': 'VP8
', 'preference
': -40}, 
 273         '169': {'ext
': 'webm
', 'height
': 720, 'width
': 1280, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'container
': 'webm
', 'vcodec
': 'VP8
', 'preference
': -40}, 
 274         '170': {'ext
': 'webm
', 'height
': 1080, 'width
': 1920, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'container
': 'webm
', 'vcodec
': 'VP8
', 'preference
': -40}, 
 275         '218': {'ext
': 'webm
', 'height
': 480, 'width
': 854, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'container
': 'webm
', 'vcodec
': 'VP8
', 'preference
': -40}, 
 276         '219': {'ext
': 'webm
', 'height
': 480, 'width
': 854, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'container
': 'webm
', 'vcodec
': 'VP8
', 'preference
': -40}, 
 277         '278': {'ext
': 'webm
', 'height
': 144, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'container
': 'webm
', 'vcodec
': 'VP9
'}, 
 278         '242': {'ext
': 'webm
', 'height
': 240, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 279         '243': {'ext
': 'webm
', 'height
': 360, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 280         '244': {'ext
': 'webm
', 'height
': 480, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 281         '245': {'ext
': 'webm
', 'height
': 480, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 282         '246': {'ext
': 'webm
', 'height
': 480, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 283         '247': {'ext
': 'webm
', 'height
': 720, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 284         '248': {'ext
': 'webm
', 'height
': 1080, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 285         '271': {'ext
': 'webm
', 'height
': 1440, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 286         '272': {'ext
': 'webm
', 'height
': 2160, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40}, 
 287         '302': {'ext
': 'webm
', 'height
': 720, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'fps
': 60, 'vcodec
': 'VP9
'}, 
 288         '303': {'ext
': 'webm
', 'height
': 1080, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'fps
': 60, 'vcodec
': 'VP9
'}, 
 289         '308': {'ext
': 'webm
', 'height
': 1440, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'fps
': 60, 'vcodec
': 'VP9
'}, 
 290         '313': {'ext
': 'webm
', 'height
': 2160, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'vcodec
': 'VP9
'}, 
 291         '315': {'ext
': 'webm
', 'height
': 2160, 'format_note
': 'DASH video
', 'acodec
': 'none
', 'preference
': -40, 'fps
': 60, 'vcodec
': 'VP9
'}, 
 294         '171': {'ext
': 'webm
', 'vcodec
': 'none
', 'format_note
': 'DASH audio
', 'abr
': 128, 'preference
': -50}, 
 295         '172': {'ext
': 'webm
', 'vcodec
': 'none
', 'format_note
': 'DASH audio
', 'abr
': 256, 'preference
': -50}, 
 297         # Dash webm audio with opus inside 
 298         '249': {'ext
': 'webm
', 'vcodec
': 'none
', 'format_note
': 'DASH audio
', 'acodec
': 'opus
', 'abr
': 50, 'preference
': -50}, 
 299         '250': {'ext
': 'webm
', 'vcodec
': 'none
', 'format_note
': 'DASH audio
', 'acodec
': 'opus
', 'abr
': 70, 'preference
': -50}, 
 300         '251': {'ext
': 'webm
', 'vcodec
': 'none
', 'format_note
': 'DASH audio
', 'acodec
': 'opus
', 'abr
': 160, 'preference
': -50}, 
 303         '_rtmp
': {'protocol
': 'rtmp
'}, 
 309             'url
': 'http
://www
.youtube
.com
/watch?v
=BaW_jenozKc
', 
 313                 'title
': 'youtube
-dl test video 
"\'/\\ä↭𝕐', 
 314                 'uploader': 'Philipp Hagemeister', 
 315                 'uploader_id': 'phihag', 
 316                 'upload_date': '20121002', 
 317                 '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 .', 
 318                 'categories
': ['Science 
& Technology
'], 
 320                 'dislike_count
': int, 
 324             'url
': 'http
://www
.youtube
.com
/watch?v
=UxxajLWwzqY
', 
 325             'note
': 'Test generic use_cipher_signature 
video (#897)', 
 329                 'upload_date': '20120506', 
 330                 'title': 'Icona Pop - I Love It (feat. Charli XCX) [OFFICIAL VIDEO]', 
 331                 'description': 'md5:fea86fda2d5a5784273df5c7cc994d9f', 
 332                 'uploader': 'Icona Pop', 
 333                 'uploader_id': 'IconaPop', 
 337             'url': 'https://www.youtube.com/watch?v=07FYdnEawAQ', 
 338             'note': 'Test VEVO video with age protection (#956)', 
 342                 'upload_date': '20130703', 
 343                 'title': 'Justin Timberlake - Tunnel Vision (Explicit)', 
 344                 'description': 'md5:64249768eec3bc4276236606ea996373', 
 345                 'uploader': 'justintimberlakeVEVO', 
 346                 'uploader_id': 'justintimberlakeVEVO', 
 350             'url': '//www.YouTube.com/watch?v=yZIXLfi8CZQ', 
 351             'note': 'Embed-only video (#1746)', 
 355                 'upload_date': '20120608', 
 356                 'title': 'Principal Sexually Assaults A Teacher - Episode 117 - 8th June 2012', 
 357                 'description': 'md5:09b78bd971f1e3e289601dfba15ca4f7', 
 358                 'uploader': 'SET India', 
 359                 'uploader_id': 'setindia' 
 363             'url': 'http://www.youtube.com/watch?v=a9LDPn-MO4I', 
 364             'note': '256k DASH audio (format 141) via DASH manifest', 
 368                 'upload_date': '20121002', 
 369                 'uploader_id': '8KVIDEO', 
 371                 'uploader': '8KVIDEO', 
 372                 'title': 'UHDTV TEST 8K VIDEO.mp4' 
 375                 'youtube_include_dash_manifest': True, 
 379         # DASH manifest with encrypted signature 
 381             'url': 'https://www.youtube.com/watch?v=IB3lcPjvWLA', 
 385                 'title': 'Afrojack, Spree Wilson - The Spark ft. Spree Wilson', 
 386                 'description': 'md5:12e7067fa6735a77bdcbb58cb1187d2d', 
 387                 'uploader': 'AfrojackVEVO', 
 388                 'uploader_id': 'AfrojackVEVO', 
 389                 'upload_date': '20131011', 
 392                 'youtube_include_dash_manifest': True, 
 396         # JS player signature function name containing $ 
 398             'url': 'https://www.youtube.com/watch?v=nfWlot6h_JM', 
 402                 'title': 'Taylor Swift - Shake It Off', 
 403                 'description': 'md5:2acfda1b285bdd478ccec22f9918199d', 
 404                 'uploader': 'TaylorSwiftVEVO', 
 405                 'uploader_id': 'TaylorSwiftVEVO', 
 406                 'upload_date': '20140818', 
 409                 'youtube_include_dash_manifest': True, 
 415             'url': 'https://www.youtube.com/watch?v=T4XJQO3qol8', 
 419                 'upload_date': '20100909', 
 420                 'uploader': 'The Amazing Atheist', 
 421                 'uploader_id': 'TheAmazingAtheist', 
 422                 'title': 'Burning Everyone\'s Koran', 
 423                 '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', 
 426         # Normal age-gate video (No vevo, embed allowed) 
 428             'url': 'http://youtube.com/watch?v=HtVdAasjOgU', 
 432                 'title': 'The Witcher 3: Wild Hunt - The Sword Of Destiny Trailer', 
 433                 'description': 're:(?s).{100,}About the Game\n.*?The Witcher 3: Wild Hunt.{100,}', 
 434                 'uploader': 'The Witcher', 
 435                 'uploader_id': 'WitcherGame', 
 436                 'upload_date': '20140605', 
 439         # Age-gate video with encrypted signature 
 441             'url': 'http://www.youtube.com/watch?v=6kLq3WMV1nU', 
 445                 'title': 'Dedication To My Ex (Miss That) (Lyric Video)', 
 446                 'description': 'md5:33765bb339e1b47e7e72b5490139bb41', 
 447                 'uploader': 'LloydVEVO', 
 448                 'uploader_id': 'LloydVEVO', 
 449                 'upload_date': '20110629', 
 452         # video_info is None (https://github.com/rg3/youtube-dl/issues/4421) 
 454             'url': '__2ABJjxzNo', 
 458                 'upload_date': '20100430', 
 459                 'uploader_id': 'deadmau5', 
 460                 'description': 'md5:12c56784b8032162bb936a5f76d55360', 
 461                 'uploader': 'deadmau5', 
 462                 'title': 'Deadmau5 - Some Chords (HD)', 
 464             'expected_warnings': [ 
 465                 'DASH manifest missing', 
 468         # Olympics (https://github.com/rg3/youtube-dl/issues/4431) 
 470             'url': 'lqQg6PlCWgI', 
 474                 'upload_date': '20120731', 
 475                 'uploader_id': 'olympic', 
 476                 'description': 'HO09  - Women -  GER-AUS - Hockey - 31 July 2012 - London 2012 Olympic Games', 
 477                 'uploader': 'Olympics', 
 478                 'title': 'Hockey - Women -  GER-AUS - London 2012 Olympic Games', 
 481                 'skip_download': 'requires avconv', 
 486             'url': 'https://www.youtube.com/watch?v=_b-2C3KPAM0', 
 490                 'stretched_ratio': 16 / 9., 
 491                 'upload_date': '20110310', 
 492                 'uploader_id': 'AllenMeow', 
 493                 'description': 'made by Wacom from Korea | 字幕&加油添醋 by TY\'s Allen | 感謝heylisa00cavey1001同學熱情提供梗及翻譯', 
 495                 'title': '[A-made] 變態妍字幕版 太妍 我就是這樣的人', 
 498         # url_encoded_fmt_stream_map is empty string 
 500             'url': 'qEJwOuvDf7I', 
 504                 'title': 'Обсуждение судебной практики по выборам 14 сентября 2014 года в Санкт-Петербурге', 
 506                 'upload_date': '20150404', 
 507                 'uploader_id': 'spbelect', 
 508                 'uploader': 'Наблюдатели Петербурга', 
 511                 'skip_download': 'requires avconv', 
 516     def __init__(self
, *args
, **kwargs
): 
 517         super(YoutubeIE
, self
).__init
__(*args
, **kwargs
) 
 518         self
._player
_cache 
= {} 
 520     def report_video_info_webpage_download(self
, video_id
): 
 521         """Report attempt to download video info webpage.""" 
 522         self
.to_screen('%s: Downloading video info webpage' % video_id
) 
 524     def report_information_extraction(self
, video_id
): 
 525         """Report attempt to extract video information.""" 
 526         self
.to_screen('%s: Extracting video information' % video_id
) 
 528     def report_unavailable_format(self
, video_id
, format
): 
 529         """Report extracted video URL.""" 
 530         self
.to_screen('%s: Format %s not available' % (video_id
, format
)) 
 532     def report_rtmp_download(self
): 
 533         """Indicate the download will use the RTMP protocol.""" 
 534         self
.to_screen('RTMP download detected') 
 536     def _signature_cache_id(self
, example_sig
): 
 537         """ Return a string representation of a signature """ 
 538         return '.'.join(compat_str(len(part
)) for part 
in example_sig
.split('.')) 
 540     def _extract_signature_function(self
, video_id
, player_url
, example_sig
): 
 542             r
'.*?-(?P<id>[a-zA-Z0-9_-]+)(?:/watch_as3|/html5player)?\.(?P<ext>[a-z]+)$', 
 545             raise ExtractorError('Cannot identify player %r' % player_url
) 
 546         player_type 
= id_m
.group('ext') 
 547         player_id 
= id_m
.group('id') 
 549         # Read from filesystem cache 
 550         func_id 
= '%s_%s_%s' % ( 
 551             player_type
, player_id
, self
._signature
_cache
_id
(example_sig
)) 
 552         assert os
.path
.basename(func_id
) == func_id
 
 554         cache_spec 
= self
._downloader
.cache
.load('youtube-sigfuncs', func_id
) 
 555         if cache_spec 
is not None: 
 556             return lambda s
: ''.join(s
[i
] for i 
in cache_spec
) 
 559             'Downloading player %s' % player_url
 
 560             if self
._downloader
.params
.get('verbose') else 
 561             'Downloading %s player %s' % (player_type
, player_id
) 
 563         if player_type 
== 'js': 
 564             code 
= self
._download
_webpage
( 
 565                 player_url
, video_id
, 
 567                 errnote
='Download of %s failed' % player_url
) 
 568             res 
= self
._parse
_sig
_js
(code
) 
 569         elif player_type 
== 'swf': 
 570             urlh 
= self
._request
_webpage
( 
 571                 player_url
, video_id
, 
 573                 errnote
='Download of %s failed' % player_url
) 
 575             res 
= self
._parse
_sig
_swf
(code
) 
 577             assert False, 'Invalid player type %r' % player_type
 
 579         test_string 
= ''.join(map(compat_chr
, range(len(example_sig
)))) 
 580         cache_res 
= res(test_string
) 
 581         cache_spec 
= [ord(c
) for c 
in cache_res
] 
 583         self
._downloader
.cache
.store('youtube-sigfuncs', func_id
, cache_spec
) 
 586     def _print_sig_code(self
, func
, example_sig
): 
 587         def gen_sig_code(idxs
): 
 588             def _genslice(start
, end
, step
): 
 589                 starts 
= '' if start 
== 0 else str(start
) 
 590                 ends 
= (':%d' % (end 
+ step
)) if end 
+ step 
>= 0 else ':' 
 591                 steps 
= '' if step 
== 1 else (':%d' % step
) 
 592                 return 's[%s%s%s]' % (starts
, ends
, steps
) 
 595             # Quelch pyflakes warnings - start will be set when step is set 
 596             start 
= '(Never used)' 
 597             for i
, prev 
in zip(idxs
[1:], idxs
[:-1]): 
 601                     yield _genslice(start
, prev
, step
) 
 604                 if i 
- prev 
in [-1, 1]: 
 613                 yield _genslice(start
, i
, step
) 
 615         test_string 
= ''.join(map(compat_chr
, range(len(example_sig
)))) 
 616         cache_res 
= func(test_string
) 
 617         cache_spec 
= [ord(c
) for c 
in cache_res
] 
 618         expr_code 
= ' + '.join(gen_sig_code(cache_spec
)) 
 619         signature_id_tuple 
= '(%s)' % ( 
 620             ', '.join(compat_str(len(p
)) for p 
in example_sig
.split('.'))) 
 621         code 
= ('if tuple(len(p) for p in s.split(\'.\')) == %s:\n' 
 622                 '    return %s\n') % (signature_id_tuple
, expr_code
) 
 623         self
.to_screen('Extracted signature function:\n' + code
) 
 625     def _parse_sig_js(self
, jscode
): 
 626         funcname 
= self
._search
_regex
( 
 627             r
'\.sig\|\|([a-zA-Z0-9$]+)\(', jscode
, 
 628             'Initial JS player signature function name') 
 630         jsi 
= JSInterpreter(jscode
) 
 631         initial_function 
= jsi
.extract_function(funcname
) 
 632         return lambda s
: initial_function([s
]) 
 634     def _parse_sig_swf(self
, file_contents
): 
 635         swfi 
= SWFInterpreter(file_contents
) 
 636         TARGET_CLASSNAME 
= 'SignatureDecipher' 
 637         searched_class 
= swfi
.extract_class(TARGET_CLASSNAME
) 
 638         initial_function 
= swfi
.extract_function(searched_class
, 'decipher') 
 639         return lambda s
: initial_function([s
]) 
 641     def _decrypt_signature(self
, s
, video_id
, player_url
, age_gate
=False): 
 642         """Turn the encrypted s field into a working signature""" 
 644         if player_url 
is None: 
 645             raise ExtractorError('Cannot decrypt signature without player_url') 
 647         if player_url
.startswith('//'): 
 648             player_url 
= 'https:' + player_url
 
 650             player_id 
= (player_url
, self
._signature
_cache
_id
(s
)) 
 651             if player_id 
not in self
._player
_cache
: 
 652                 func 
= self
._extract
_signature
_function
( 
 653                     video_id
, player_url
, s
 
 655                 self
._player
_cache
[player_id
] = func
 
 656             func 
= self
._player
_cache
[player_id
] 
 657             if self
._downloader
.params
.get('youtube_print_sig_code'): 
 658                 self
._print
_sig
_code
(func
, s
) 
 660         except Exception as e
: 
 661             tb 
= traceback
.format_exc() 
 662             raise ExtractorError( 
 663                 'Signature extraction failed: ' + tb
, cause
=e
) 
 665     def _get_subtitles(self
, video_id
, webpage
): 
 667             subs_doc 
= self
._download
_xml
( 
 668                 'https://video.google.com/timedtext?hl=en&type=list&v=%s' % video_id
, 
 669                 video_id
, note
=False) 
 670         except ExtractorError 
as err
: 
 671             self
._downloader
.report_warning('unable to download video subtitles: %s' % compat_str(err
)) 
 675         for track 
in subs_doc
.findall('track'): 
 676             lang 
= track
.attrib
['lang_code'] 
 677             if lang 
in sub_lang_list
: 
 680             for ext 
in ['sbv', 'vtt', 'srt']: 
 681                 params 
= compat_urllib_parse
.urlencode({ 
 685                     'name': track
.attrib
['name'].encode('utf-8'), 
 688                     'url': 'https://www.youtube.com/api/timedtext?' + params
, 
 691             sub_lang_list
[lang
] = sub_formats
 
 692         if not sub_lang_list
: 
 693             self
._downloader
.report_warning('video doesn\'t have subtitles') 
 697     def _get_automatic_captions(self
, video_id
, webpage
): 
 698         """We need the webpage for getting the captions url, pass it as an 
 699            argument to speed up the process.""" 
 700         self
.to_screen('%s: Looking for automatic captions' % video_id
) 
 701         mobj 
= re
.search(r
';ytplayer.config = ({.*?});', webpage
) 
 702         err_msg 
= 'Couldn\'t find automatic captions for %s' % video_id
 
 704             self
._downloader
.report_warning(err_msg
) 
 706         player_config 
= json
.loads(mobj
.group(1)) 
 708             args 
= player_config
['args'] 
 709             caption_url 
= args
['ttsurl'] 
 710             timestamp 
= args
['timestamp'] 
 711             # We get the available subtitles 
 712             list_params 
= compat_urllib_parse
.urlencode({ 
 717             list_url 
= caption_url 
+ '&' + list_params
 
 718             caption_list 
= self
._download
_xml
(list_url
, video_id
) 
 719             original_lang_node 
= caption_list
.find('track') 
 720             if original_lang_node 
is None: 
 721                 self
._downloader
.report_warning('Video doesn\'t have automatic captions') 
 723             original_lang 
= original_lang_node
.attrib
['lang_code'] 
 724             caption_kind 
= original_lang_node
.attrib
.get('kind', '') 
 727             for lang_node 
in caption_list
.findall('target'): 
 728                 sub_lang 
= lang_node
.attrib
['lang_code'] 
 730                 for ext 
in ['sbv', 'vtt', 'srt']: 
 731                     params 
= compat_urllib_parse
.urlencode({ 
 732                         'lang': original_lang
, 
 736                         'kind': caption_kind
, 
 739                         'url': caption_url 
+ '&' + params
, 
 742                 sub_lang_list
[sub_lang
] = sub_formats
 
 744         # An extractor error can be raise by the download process if there are 
 745         # no automatic captions but there are subtitles 
 746         except (KeyError, ExtractorError
): 
 747             self
._downloader
.report_warning(err_msg
) 
 751     def extract_id(cls
, url
): 
 752         mobj 
= re
.match(cls
._VALID
_URL
, url
, re
.VERBOSE
) 
 754             raise ExtractorError('Invalid URL: %s' % url
) 
 755         video_id 
= mobj
.group(2) 
 758     def _extract_from_m3u8(self
, manifest_url
, video_id
): 
 761         def _get_urls(_manifest
): 
 762             lines 
= _manifest
.split('\n') 
 763             urls 
= filter(lambda l
: l 
and not l
.startswith('#'), 
 766         manifest 
= self
._download
_webpage
(manifest_url
, video_id
, 'Downloading formats manifest') 
 767         formats_urls 
= _get_urls(manifest
) 
 768         for format_url 
in formats_urls
: 
 769             itag 
= self
._search
_regex
(r
'itag/(\d+?)/', format_url
, 'itag') 
 770             url_map
[itag
] = format_url
 
 773     def _extract_annotations(self
, video_id
): 
 774         url 
= 'https://www.youtube.com/annotations_invideo?features=1&legacy=1&video_id=%s' % video_id
 
 775         return self
._download
_webpage
(url
, video_id
, note
='Searching for annotations.', errnote
='Unable to download video annotations.') 
 777     def _parse_dash_manifest( 
 778             self
, video_id
, dash_manifest_url
, player_url
, age_gate
): 
 779         def decrypt_sig(mobj
): 
 781             dec_s 
= self
._decrypt
_signature
(s
, video_id
, player_url
, age_gate
) 
 782             return '/signature/%s' % dec_s
 
 783         dash_manifest_url 
= re
.sub(r
'/s/([\w\.]+)', decrypt_sig
, dash_manifest_url
) 
 784         dash_doc 
= self
._download
_xml
( 
 785             dash_manifest_url
, video_id
, 
 786             note
='Downloading DASH manifest', 
 787             errnote
='Could not download DASH manifest') 
 790         for a 
in dash_doc
.findall('.//{urn:mpeg:DASH:schema:MPD:2011}AdaptationSet'): 
 791             mime_type 
= a
.attrib
.get('mimeType') 
 792             for r 
in a
.findall('{urn:mpeg:DASH:schema:MPD:2011}Representation'): 
 793                 url_el 
= r
.find('{urn:mpeg:DASH:schema:MPD:2011}BaseURL') 
 796                 if mime_type 
== 'text/vtt': 
 797                     # TODO implement WebVTT downloading 
 799                 elif mime_type
.startswith('audio/') or mime_type
.startswith('video/'): 
 800                     format_id 
= r
.attrib
['id'] 
 801                     video_url 
= url_el
.text
 
 802                     filesize 
= int_or_none(url_el
.attrib
.get('{http://youtube.com/yt/2012/10/10}contentLength')) 
 804                         'format_id': format_id
, 
 806                         'width': int_or_none(r
.attrib
.get('width')), 
 807                         'height': int_or_none(r
.attrib
.get('height')), 
 808                         'tbr': int_or_none(r
.attrib
.get('bandwidth'), 1000), 
 809                         'asr': int_or_none(r
.attrib
.get('audioSamplingRate')), 
 810                         'filesize': filesize
, 
 811                         'fps': int_or_none(r
.attrib
.get('frameRate')), 
 814                         existing_format 
= next( 
 816                             if fo
['format_id'] == format_id
) 
 817                     except StopIteration: 
 818                         full_info 
= self
._formats
.get(format_id
, {}).copy() 
 820                         formats
.append(full_info
) 
 822                         existing_format
.update(f
) 
 824                     self
.report_warning('Unknown MIME type %s in DASH manifest' % mime_type
) 
 827     def _real_extract(self
, url
): 
 829             'http' if self
._downloader
.params
.get('prefer_insecure', False) 
 832         # Extract original video URL from URL with redirection, like age verification, using next_url parameter 
 833         mobj 
= re
.search(self
._NEXT
_URL
_RE
, url
) 
 835             url 
= proto 
+ '://www.youtube.com/' + compat_urllib_parse
.unquote(mobj
.group(1)).lstrip('/') 
 836         video_id 
= self
.extract_id(url
) 
 839         url 
= proto 
+ '://www.youtube.com/watch?v=%s&gl=US&hl=en&has_verified=1&bpctr=9999999999' % video_id
 
 840         video_webpage 
= self
._download
_webpage
(url
, video_id
) 
 842         # Attempt to extract SWF player URL 
 843         mobj 
= re
.search(r
'swfConfig.*?"(https?:\\/\\/.*?watch.*?-.*?\.swf)"', video_webpage
) 
 845             player_url 
= re
.sub(r
'\\(.)', r
'\1', mobj
.group(1)) 
 851         if re
.search(r
'player-age-gate-content">', video_webpage
) is not None: 
 853             # We simulate the access to the video from www.youtube.com/v/{video_id} 
 854             # this can be viewed without login into Youtube 
 855             url 
= proto 
+ '://www.youtube.com/embed/%s' % video_id
 
 856             embed_webpage 
= self
._download
_webpage
(url
, video_id
, 'Downloading embed webpage') 
 857             data 
= compat_urllib_parse
.urlencode({ 
 858                 'video_id': video_id
, 
 859                 'eurl': 'https://youtube.googleapis.com/v/' + video_id
, 
 860                 'sts': self
._search
_regex
( 
 861                     r
'"sts"\s*:\s*(\d+)', embed_webpage
, 'sts', default
=''), 
 863             video_info_url 
= proto 
+ '://www.youtube.com/get_video_info?' + data
 
 864             video_info_webpage 
= self
._download
_webpage
( 
 865                 video_info_url
, video_id
, 
 866                 note
='Refetching age-gated info webpage', 
 867                 errnote
='unable to download video info webpage') 
 868             video_info 
= compat_parse_qs(video_info_webpage
) 
 872                 # Try looking directly into the video webpage 
 873                 mobj 
= re
.search(r
';ytplayer\.config\s*=\s*({.*?});', video_webpage
) 
 875                     raise ValueError('Could not find ytplayer.config')  # caught below 
 876                 json_code 
= uppercase_escape(mobj
.group(1)) 
 877                 ytplayer_config 
= json
.loads(json_code
) 
 878                 args 
= ytplayer_config
['args'] 
 879                 # Convert to the same format returned by compat_parse_qs 
 880                 video_info 
= dict((k
, [v
]) for k
, v 
in args
.items()) 
 881                 if not args
.get('url_encoded_fmt_stream_map'): 
 882                     raise ValueError('No stream_map present')  # caught below 
 884                 # We fallback to the get_video_info pages (used by the embed page) 
 885                 self
.report_video_info_webpage_download(video_id
) 
 886                 for el_type 
in ['&el=embedded', '&el=detailpage', '&el=vevo', '']: 
 888                         '%s://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en' 
 889                         % (proto
, video_id
, el_type
)) 
 890                     video_info_webpage 
= self
._download
_webpage
( 
 892                         video_id
, note
=False, 
 893                         errnote
='unable to download video info webpage') 
 894                     video_info 
= compat_parse_qs(video_info_webpage
) 
 895                     if 'token' in video_info
: 
 897         if 'token' not in video_info
: 
 898             if 'reason' in video_info
: 
 899                 raise ExtractorError( 
 900                     'YouTube said: %s' % video_info
['reason'][0], 
 901                     expected
=True, video_id
=video_id
) 
 903                 raise ExtractorError( 
 904                     '"token" parameter not in video info for unknown reason', 
 907         if 'view_count' in video_info
: 
 908             view_count 
= int(video_info
['view_count'][0]) 
 912         # Check for "rental" videos 
 913         if 'ypc_video_rental_bar_text' in video_info 
and 'author' not in video_info
: 
 914             raise ExtractorError('"rental" videos not supported') 
 916         # Start extracting information 
 917         self
.report_information_extraction(video_id
) 
 920         if 'author' not in video_info
: 
 921             raise ExtractorError('Unable to extract uploader name') 
 922         video_uploader 
= compat_urllib_parse
.unquote_plus(video_info
['author'][0]) 
 925         video_uploader_id 
= None 
 926         mobj 
= re
.search(r
'<link itemprop="url" href="http://www.youtube.com/(?:user|channel)/([^"]+)">', video_webpage
) 
 928             video_uploader_id 
= mobj
.group(1) 
 930             self
._downloader
.report_warning('unable to extract uploader nickname') 
 933         if 'title' in video_info
: 
 934             video_title 
= video_info
['title'][0] 
 936             self
._downloader
.report_warning('Unable to extract video title') 
 940         # We try first to get a high quality image: 
 941         m_thumb 
= re
.search(r
'<span itemprop="thumbnail".*?href="(.*?)">', 
 942                             video_webpage
, re
.DOTALL
) 
 943         if m_thumb 
is not None: 
 944             video_thumbnail 
= m_thumb
.group(1) 
 945         elif 'thumbnail_url' not in video_info
: 
 946             self
._downloader
.report_warning('unable to extract video thumbnail') 
 947             video_thumbnail 
= None 
 948         else:   # don't panic if we can't find it 
 949             video_thumbnail 
= compat_urllib_parse
.unquote_plus(video_info
['thumbnail_url'][0]) 
 953         mobj 
= re
.search(r
'(?s)id="eow-date.*?>(.*?)</span>', video_webpage
) 
 956                 r
'(?s)id="watch-uploader-info".*?>.*?(?:Published|Uploaded|Streamed live) on (.*?)</strong>', 
 959             upload_date 
= ' '.join(re
.sub(r
'[/,-]', r
' ', mobj
.group(1)).split()) 
 960             upload_date 
= unified_strdate(upload_date
) 
 962         m_cat_container 
= self
._search
_regex
( 
 963             r
'(?s)<h4[^>]*>\s*Category\s*</h4>\s*<ul[^>]*>(.*?)</ul>', 
 964             video_webpage
, 'categories', default
=None) 
 966             category 
= self
._html
_search
_regex
( 
 967                 r
'(?s)<a[^<]+>(.*?)</a>', m_cat_container
, 'category', 
 969             video_categories 
= None if category 
is None else [category
] 
 971             video_categories 
= None 
 974         video_description 
= get_element_by_id("eow-description", video_webpage
) 
 975         if video_description
: 
 976             video_description 
= re
.sub(r
'''(?x) 
 978                     (?:[a-zA-Z-]+="[^"]+"\s+)*? 
 980                     (?:[a-zA-Z-]+="[^"]+"\s+)*? 
 981                     class="yt-uix-redirect-link"\s*> 
 984             ''', r
'\1', video_description
) 
 985             video_description 
= clean_html(video_description
) 
 987             fd_mobj 
= re
.search(r
'<meta name="description" content="([^"]+)"', video_webpage
) 
 989                 video_description 
= unescapeHTML(fd_mobj
.group(1)) 
 991                 video_description 
= '' 
 993         def _extract_count(count_name
): 
 994             count 
= self
._search
_regex
( 
 995                 r
'id="watch-%s"[^>]*>.*?([\d,]+)\s*</span>' % re
.escape(count_name
), 
 996                 video_webpage
, count_name
, default
=None) 
 997             if count 
is not None: 
 998                 return int(count
.replace(',', '')) 
1000         like_count 
= _extract_count('like') 
1001         dislike_count 
= _extract_count('dislike') 
1004         video_subtitles 
= self
.extract_subtitles(video_id
, video_webpage
) 
1005         automatic_captions 
= self
.extract_automatic_captions(video_id
, video_webpage
) 
1007         if 'length_seconds' not in video_info
: 
1008             self
._downloader
.report_warning('unable to extract video duration') 
1009             video_duration 
= None 
1011             video_duration 
= int(compat_urllib_parse
.unquote_plus(video_info
['length_seconds'][0])) 
1014         video_annotations 
= None 
1015         if self
._downloader
.params
.get('writeannotations', False): 
1016             video_annotations 
= self
._extract
_annotations
(video_id
) 
1018         def _map_to_format_list(urlmap
): 
1020             for itag
, video_real_url 
in urlmap
.items(): 
1023                     'url': video_real_url
, 
1024                     'player_url': player_url
, 
1026                 if itag 
in self
._formats
: 
1027                     dct
.update(self
._formats
[itag
]) 
1031         if 'conn' in video_info 
and video_info
['conn'][0].startswith('rtmp'): 
1032             self
.report_rtmp_download() 
1034                 'format_id': '_rtmp', 
1036                 'url': video_info
['conn'][0], 
1037                 'player_url': player_url
, 
1039         elif len(video_info
.get('url_encoded_fmt_stream_map', [''])[0]) >= 1 or len(video_info
.get('adaptive_fmts', [''])[0]) >= 1: 
1040             encoded_url_map 
= video_info
.get('url_encoded_fmt_stream_map', [''])[0] + ',' + video_info
.get('adaptive_fmts', [''])[0] 
1041             if 'rtmpe%3Dyes' in encoded_url_map
: 
1042                 raise ExtractorError('rtmpe downloads are not supported, see https://github.com/rg3/youtube-dl/issues/343 for more information.', expected
=True) 
1044             for url_data_str 
in encoded_url_map
.split(','): 
1045                 url_data 
= compat_parse_qs(url_data_str
) 
1046                 if 'itag' not in url_data 
or 'url' not in url_data
: 
1048                 format_id 
= url_data
['itag'][0] 
1049                 url 
= url_data
['url'][0] 
1051                 if 'sig' in url_data
: 
1052                     url 
+= '&signature=' + url_data
['sig'][0] 
1053                 elif 's' in url_data
: 
1054                     encrypted_sig 
= url_data
['s'][0] 
1055                     ASSETS_RE 
= r
'"assets":.+?"js":\s*("[^"]+")' 
1057                     jsplayer_url_json 
= self
._search
_regex
( 
1059                         embed_webpage 
if age_gate 
else video_webpage
, 
1060                         'JS player URL (1)', default
=None) 
1061                     if not jsplayer_url_json 
and not age_gate
: 
1062                         # We need the embed website after all 
1063                         if embed_webpage 
is None: 
1064                             embed_url 
= proto 
+ '://www.youtube.com/embed/%s' % video_id
 
1065                             embed_webpage 
= self
._download
_webpage
( 
1066                                 embed_url
, video_id
, 'Downloading embed webpage') 
1067                         jsplayer_url_json 
= self
._search
_regex
( 
1068                             ASSETS_RE
, embed_webpage
, 'JS player URL') 
1070                     player_url 
= json
.loads(jsplayer_url_json
) 
1071                     if player_url 
is None: 
1072                         player_url_json 
= self
._search
_regex
( 
1073                             r
'ytplayer\.config.*?"url"\s*:\s*("[^"]+")', 
1074                             video_webpage
, 'age gate player URL') 
1075                         player_url 
= json
.loads(player_url_json
) 
1077                     if self
._downloader
.params
.get('verbose'): 
1078                         if player_url 
is None: 
1079                             player_version 
= 'unknown' 
1080                             player_desc 
= 'unknown' 
1082                             if player_url
.endswith('swf'): 
1083                                 player_version 
= self
._search
_regex
( 
1084                                     r
'-(.+?)(?:/watch_as3)?\.swf$', player_url
, 
1085                                     'flash player', fatal
=False) 
1086                                 player_desc 
= 'flash player %s' % player_version
 
1088                                 player_version 
= self
._search
_regex
( 
1089                                     r
'html5player-([^/]+?)(?:/html5player)?\.js', 
1091                                     'html5 player', fatal
=False) 
1092                                 player_desc 
= 'html5 player %s' % player_version
 
1094                         parts_sizes 
= self
._signature
_cache
_id
(encrypted_sig
) 
1095                         self
.to_screen('{%s} signature length %s, %s' % 
1096                                        (format_id
, parts_sizes
, player_desc
)) 
1098                     signature 
= self
._decrypt
_signature
( 
1099                         encrypted_sig
, video_id
, player_url
, age_gate
) 
1100                     url 
+= '&signature=' + signature
 
1101                 if 'ratebypass' not in url
: 
1102                     url 
+= '&ratebypass=yes' 
1103                 url_map
[format_id
] = url
 
1104             formats 
= _map_to_format_list(url_map
) 
1105         elif video_info
.get('hlsvp'): 
1106             manifest_url 
= video_info
['hlsvp'][0] 
1107             url_map 
= self
._extract
_from
_m
3u8(manifest_url
, video_id
) 
1108             formats 
= _map_to_format_list(url_map
) 
1110             raise ExtractorError('no conn, hlsvp or url_encoded_fmt_stream_map information found in video info') 
1112         # Look for the DASH manifest 
1113         if self
._downloader
.params
.get('youtube_include_dash_manifest', True): 
1114             dash_mpd 
= video_info
.get('dashmpd') 
1116                 dash_manifest_url 
= dash_mpd
[0] 
1118                     dash_formats 
= self
._parse
_dash
_manifest
( 
1119                         video_id
, dash_manifest_url
, player_url
, age_gate
) 
1120                 except (ExtractorError
, KeyError) as e
: 
1121                     self
.report_warning( 
1122                         'Skipping DASH manifest: %r' % e
, video_id
) 
1124                     # Hide the formats we found through non-DASH 
1125                     dash_keys 
= set(df
['format_id'] for df 
in dash_formats
) 
1127                         if f
['format_id'] in dash_keys
: 
1128                             f
['format_id'] = 'nondash-%s' % f
['format_id'] 
1129                             f
['preference'] = f
.get('preference', 0) - 10000 
1130                     formats
.extend(dash_formats
) 
1132         # Check for malformed aspect ratio 
1133         stretched_m 
= re
.search( 
1134             r
'<meta\s+property="og:video:tag".*?content="yt:stretch=(?P<w>[0-9]+):(?P<h>[0-9]+)">', 
1137             ratio 
= float(stretched_m
.group('w')) / float(stretched_m
.group('h')) 
1139                 if f
.get('vcodec') != 'none': 
1140                     f
['stretched_ratio'] = ratio
 
1142         self
._sort
_formats
(formats
) 
1146             'uploader': video_uploader
, 
1147             'uploader_id': video_uploader_id
, 
1148             'upload_date': upload_date
, 
1149             'title': video_title
, 
1150             'thumbnail': video_thumbnail
, 
1151             'description': video_description
, 
1152             'categories': video_categories
, 
1153             'subtitles': video_subtitles
, 
1154             'automatic_captions': automatic_captions
, 
1155             'duration': video_duration
, 
1156             'age_limit': 18 if age_gate 
else 0, 
1157             'annotations': video_annotations
, 
1158             'webpage_url': proto 
+ '://www.youtube.com/watch?v=%s' % video_id
, 
1159             'view_count': view_count
, 
1160             'like_count': like_count
, 
1161             'dislike_count': dislike_count
, 
1162             'average_rating': float_or_none(video_info
.get('avg_rating', [None])[0]), 
1167 class YoutubePlaylistIE(YoutubeBaseInfoExtractor
): 
1168     IE_DESC 
= 'YouTube.com playlists' 
1169     _VALID_URL 
= r
"""(?x)(?: 
1174                            (?:course|view_play_list|my_playlists|artist|playlist|watch|embed/videoseries) 
1175                            \? (?:.*?&)*? (?:p|a|list)= 
1179                             (?:PL|LL|EC|UU|FL|RD|UL)?[0-9A-Za-z-_]{10,} 
1180                             # Top tracks, they can also include dots 
1185                         ((?:PL|LL|EC|UU|FL|RD|UL)[0-9A-Za-z-_]{10,}) 
1187     _TEMPLATE_URL 
= 'https://www.youtube.com/playlist?list=%s' 
1188     _VIDEO_RE 
= r
'href="\s*/watch\?v=(?P<id>[0-9A-Za-z_-]{11})&[^"]*?index=(?P<index>\d+)' 
1189     IE_NAME 
= 'youtube:playlist' 
1191         'url': 'https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re', 
1193             'title': 'ytdl test PL', 
1194             'id': 'PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re', 
1196         'playlist_count': 3, 
1198         'url': 'https://www.youtube.com/playlist?list=PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx', 
1200             'id': 'PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx', 
1201             'title': 'YDL_Empty_List', 
1203         'playlist_count': 0, 
1205         'note': 'Playlist with deleted videos (#651). As a bonus, the video #51 is also twice in this list.', 
1206         'url': 'https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC', 
1208             'title': '29C3: Not my department', 
1209             'id': 'PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC', 
1211         'playlist_count': 95, 
1213         'note': 'issue #673', 
1214         'url': 'PLBB231211A4F62143', 
1216             'title': '[OLD]Team Fortress 2 (Class-based LP)', 
1217             'id': 'PLBB231211A4F62143', 
1219         'playlist_mincount': 26, 
1221         'note': 'Large playlist', 
1222         'url': 'https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q', 
1224             'title': 'Uploads from Cauchemar', 
1225             'id': 'UUBABnxM4Ar9ten8Mdjj1j0Q', 
1227         'playlist_mincount': 799, 
1229         'url': 'PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl', 
1231             'title': 'YDL_safe_search', 
1232             'id': 'PLtPgu7CB4gbY9oDN3drwC3cMbJggS7dKl', 
1234         'playlist_count': 2, 
1237         'url': 'http://www.youtube.com/embed/videoseries?list=PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 
1238         'playlist_count': 4, 
1241             'id': 'PL6IaIsEjSbf96XFRuNccS_RuEXwNdsoEu', 
1244         'note': 'Embedded SWF player', 
1245         'url': 'http://www.youtube.com/p/YN5VISEtHet5D4NEvfTd0zcgFk84NqFZ?hl=en_US&fs=1&rel=0', 
1246         'playlist_count': 4, 
1249             'id': 'YN5VISEtHet5D4NEvfTd0zcgFk84NqFZ', 
1252         'note': 'Buggy playlist: the webpage has a "Load more" button but it doesn\'t have more videos', 
1253         'url': 'https://www.youtube.com/playlist?list=UUXw-G3eDE9trcvY2sBMM_aA', 
1255             'title': 'Uploads from Interstellar Movie', 
1256             'id': 'UUXw-G3eDE9trcvY2sBMM_aA', 
1258         'playlist_mincout': 21, 
1261     def _real_initialize(self
): 
1264     def _ids_to_results(self
, ids
): 
1266             self
.url_result(vid_id
, 'Youtube', video_id
=vid_id
) 
1269     def _extract_mix(self
, playlist_id
): 
1270         # The mixes are generated from a single video 
1271         # the id of the playlist is just 'RD' + video_id 
1272         url 
= 'https://youtube.com/watch?v=%s&list=%s' % (playlist_id
[-11:], playlist_id
) 
1273         webpage 
= self
._download
_webpage
( 
1274             url
, playlist_id
, 'Downloading Youtube mix') 
1275         search_title 
= lambda class_name
: get_element_by_attribute('class', class_name
, webpage
) 
1277             search_title('playlist-title') or 
1278             search_title('title long-title') or 
1279             search_title('title')) 
1280         title 
= clean_html(title_span
) 
1281         ids 
= orderedSet(re
.findall( 
1282             r
'''(?xs)data-video-username=".*?".*? 
1283                        href="/watch\?v=([0-9A-Za-z_-]{11})&[^"]*?list=%s''' % re
.escape(playlist_id
), 
1285         url_results 
= self
._ids
_to
_results
(ids
) 
1287         return self
.playlist_result(url_results
, playlist_id
, title
) 
1289     def _extract_playlist(self
, playlist_id
): 
1290         url 
= self
._TEMPLATE
_URL 
% playlist_id
 
1291         page 
= self
._download
_webpage
(url
, playlist_id
) 
1292         more_widget_html 
= content_html 
= page
 
1294         for match 
in re
.findall(r
'<div class="yt-alert-message">([^<]+)</div>', page
): 
1295             match 
= match
.strip() 
1296             # Check if the playlist exists or is private 
1297             if re
.match(r
'[^<]*(The|This) playlist (does not exist|is private)[^<]*', match
): 
1298                 raise ExtractorError( 
1299                     'The playlist doesn\'t exist or is private, use --username or ' 
1300                     '--netrc to access it.', 
1302             elif re
.match(r
'[^<]*Invalid parameters[^<]*', match
): 
1303                 raise ExtractorError( 
1304                     'Invalid parameters. Maybe URL is incorrect.', 
1306             elif re
.match(r
'[^<]*Choose your language[^<]*', match
): 
1309                 self
.report_warning('Youtube gives an alert message: ' + match
) 
1311         # Extract the video ids from the playlist pages 
1314         for page_num 
in itertools
.count(1): 
1315             matches 
= re
.finditer(self
._VIDEO
_RE
, content_html
) 
1316             # We remove the duplicates and the link with index 0 
1317             # (it's not the first video of the playlist) 
1318             new_ids 
= orderedSet(m
.group('id') for m 
in matches 
if m
.group('index') != '0') 
1321             mobj 
= re
.search(r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html
) 
1325             more 
= self
._download
_json
( 
1326                 'https://youtube.com/%s' % mobj
.group('more'), playlist_id
, 
1327                 'Downloading page #%s' % page_num
, 
1328                 transform_source
=uppercase_escape
) 
1329             content_html 
= more
['content_html'] 
1330             if not content_html
.strip(): 
1331                 # Some webpages show a "Load more" button but they don't 
1334             more_widget_html 
= more
['load_more_widget_html'] 
1336         playlist_title 
= self
._html
_search
_regex
( 
1337             r
'(?s)<h1 class="pl-header-title[^"]*">\s*(.*?)\s*</h1>', 
1340         url_results 
= self
._ids
_to
_results
(ids
) 
1341         return self
.playlist_result(url_results
, playlist_id
, playlist_title
) 
1343     def _real_extract(self
, url
): 
1344         # Extract playlist id 
1345         mobj 
= re
.match(self
._VALID
_URL
, url
) 
1347             raise ExtractorError('Invalid URL: %s' % url
) 
1348         playlist_id 
= mobj
.group(1) or mobj
.group(2) 
1350         # Check if it's a video-specific URL 
1351         query_dict 
= compat_urlparse
.parse_qs(compat_urlparse
.urlparse(url
).query
) 
1352         if 'v' in query_dict
: 
1353             video_id 
= query_dict
['v'][0] 
1354             if self
._downloader
.params
.get('noplaylist'): 
1355                 self
.to_screen('Downloading just video %s because of --no-playlist' % video_id
) 
1356                 return self
.url_result(video_id
, 'Youtube', video_id
=video_id
) 
1358                 self
.to_screen('Downloading playlist %s - add --no-playlist to just download video %s' % (playlist_id
, video_id
)) 
1360         if playlist_id
.startswith('RD') or playlist_id
.startswith('UL'): 
1361             # Mixes require a custom extraction process 
1362             return self
._extract
_mix
(playlist_id
) 
1364         return self
._extract
_playlist
(playlist_id
) 
1367 class YoutubeChannelIE(InfoExtractor
): 
1368     IE_DESC 
= 'YouTube.com channels' 
1369     _VALID_URL 
= r
'https?://(?:youtu\.be|(?:\w+\.)?youtube(?:-nocookie)?\.com)/channel/(?P<id>[0-9A-Za-z_-]+)' 
1370     _TEMPLATE_URL 
= 'https://www.youtube.com/channel/%s/videos' 
1371     IE_NAME 
= 'youtube:channel' 
1373         'note': 'paginated channel', 
1374         'url': 'https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w', 
1375         'playlist_mincount': 91, 
1377             'id': 'UCKfVa3S1e4PHvxWcwyMMg8w', 
1382     def extract_videos_from_page(page
): 
1385         for mobj 
in re
.finditer(r
'(?:title="(?P<title>[^"]+)"[^>]+)?href="/watch\?v=(?P<id>[0-9A-Za-z_-]+)&?', page
): 
1386             video_id 
= mobj
.group('id') 
1387             video_title 
= unescapeHTML(mobj
.group('title')) 
1389                 idx 
= ids_in_page
.index(video_id
) 
1390                 if video_title 
and not titles_in_page
[idx
]: 
1391                     titles_in_page
[idx
] = video_title
 
1393                 ids_in_page
.append(video_id
) 
1394                 titles_in_page
.append(video_title
) 
1395         return zip(ids_in_page
, titles_in_page
) 
1397     def _real_extract(self
, url
): 
1398         channel_id 
= self
._match
_id
(url
) 
1400         url 
= self
._TEMPLATE
_URL 
% channel_id
 
1401         channel_page 
= self
._download
_webpage
(url
, channel_id
, 'Downloading page #1') 
1402         autogenerated 
= re
.search(r
'''(?x) 
1404                     channel-header-autogenerated-label| 
1405                     yt-channel-title-autogenerated 
1406                 )[^"]*"''', channel_page
) is not None 
1409             # The videos are contained in a single page 
1410             # the ajax pages can't be used, they are empty 
1413                     video_id
, 'Youtube', video_id
=video_id
, 
1414                     video_title
=video_title
) 
1415                 for video_id
, video_title 
in self
.extract_videos_from_page(channel_page
)] 
1416             return self
.playlist_result(entries
, channel_id
) 
1419             more_widget_html 
= content_html 
= channel_page
 
1420             for pagenum 
in itertools
.count(1): 
1422                 for video_id
, video_title 
in self
.extract_videos_from_page(content_html
): 
1423                     yield self
.url_result( 
1424                         video_id
, 'Youtube', video_id
=video_id
, 
1425                         video_title
=video_title
) 
1428                     r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', 
1433                 more 
= self
._download
_json
( 
1434                     'https://youtube.com/%s' % mobj
.group('more'), channel_id
, 
1435                     'Downloading page #%s' % (pagenum 
+ 1), 
1436                     transform_source
=uppercase_escape
) 
1437                 content_html 
= more
['content_html'] 
1438                 more_widget_html 
= more
['load_more_widget_html'] 
1440         return self
.playlist_result(_entries(), channel_id
) 
1443 class YoutubeUserIE(YoutubeChannelIE
): 
1444     IE_DESC 
= 'YouTube.com user videos (URL or "ytuser" keyword)' 
1445     _VALID_URL 
= r
'(?:(?:(?:https?://)?(?:\w+\.)?youtube\.com/(?:user/)?(?!(?:attribution_link|watch|results)(?:$|[^a-z_A-Z0-9-])))|ytuser:)(?!feed/)(?P<id>[A-Za-z0-9_-]+)' 
1446     _TEMPLATE_URL 
= 'https://www.youtube.com/user/%s/videos' 
1447     IE_NAME 
= 'youtube:user' 
1450         'url': 'https://www.youtube.com/user/TheLinuxFoundation', 
1451         'playlist_mincount': 320, 
1453             'title': 'TheLinuxFoundation', 
1456         'url': 'ytuser:phihag', 
1457         'only_matching': True, 
1461     def suitable(cls
, url
): 
1462         # Don't return True if the url can be extracted with other youtube 
1463         # extractor, the regex would is too permissive and it would match. 
1464         other_ies 
= iter(klass 
for (name
, klass
) in globals().items() if name
.endswith('IE') and klass 
is not cls
) 
1465         if any(ie
.suitable(url
) for ie 
in other_ies
): 
1468             return super(YoutubeUserIE
, cls
).suitable(url
) 
1471 class YoutubeSearchIE(SearchInfoExtractor
, YoutubePlaylistIE
): 
1472     IE_DESC 
= 'YouTube.com searches' 
1473     # there doesn't appear to be a real limit, for example if you search for 
1474     # 'python' you get more than 8.000.000 results 
1475     _MAX_RESULTS 
= float('inf') 
1476     IE_NAME 
= 'youtube:search' 
1477     _SEARCH_KEY 
= 'ytsearch' 
1478     _EXTRA_QUERY_ARGS 
= {} 
1481     def _get_n_results(self
, query
, n
): 
1482         """Get a specified number of results for a query""" 
1487         for pagenum 
in itertools
.count(1): 
1489                 'search_query': query
, 
1493             url_query
.update(self
._EXTRA
_QUERY
_ARGS
) 
1494             result_url 
= 'https://www.youtube.com/results?' + compat_urllib_parse
.urlencode(url_query
) 
1495             data 
= self
._download
_json
( 
1496                 result_url
, video_id
='query "%s"' % query
, 
1497                 note
='Downloading page %s' % pagenum
, 
1498                 errnote
='Unable to download API page') 
1499             html_content 
= data
[1]['body']['content'] 
1501             if 'class="search-message' in html_content
: 
1502                 raise ExtractorError( 
1503                     '[youtube] No video results', expected
=True) 
1505             new_videos 
= self
._ids
_to
_results
(orderedSet(re
.findall( 
1506                 r
'href="/watch\?v=(.{11})', html_content
))) 
1507             videos 
+= new_videos
 
1508             if not new_videos 
or len(videos
) > limit
: 
1513         return self
.playlist_result(videos
, query
) 
1516 class YoutubeSearchDateIE(YoutubeSearchIE
): 
1517     IE_NAME 
= YoutubeSearchIE
.IE_NAME 
+ ':date' 
1518     _SEARCH_KEY 
= 'ytsearchdate' 
1519     IE_DESC 
= 'YouTube.com searches, newest videos first' 
1520     _EXTRA_QUERY_ARGS 
= {'search_sort': 'video_date_uploaded'} 
1523 class YoutubeSearchURLIE(InfoExtractor
): 
1524     IE_DESC 
= 'YouTube.com search URLs' 
1525     IE_NAME 
= 'youtube:search_url' 
1526     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/results\?(.*?&)?search_query=(?P<query>[^&]+)(?:[&]|$)' 
1528         'url': 'https://www.youtube.com/results?baz=bar&search_query=youtube-dl+test+video&filters=video&lclk=video', 
1529         'playlist_mincount': 5, 
1531             'title': 'youtube-dl test video', 
1535     def _real_extract(self
, url
): 
1536         mobj 
= re
.match(self
._VALID
_URL
, url
) 
1537         query 
= compat_urllib_parse
.unquote_plus(mobj
.group('query')) 
1539         webpage 
= self
._download
_webpage
(url
, query
) 
1540         result_code 
= self
._search
_regex
( 
1541             r
'(?s)<ol[^>]+class="item-section"(.*?)</ol>', webpage
, 'result HTML') 
1543         part_codes 
= re
.findall( 
1544             r
'(?s)<h3 class="yt-lockup-title">(.*?)</h3>', result_code
) 
1546         for part_code 
in part_codes
: 
1547             part_title 
= self
._html
_search
_regex
( 
1548                 [r
'(?s)title="([^"]+)"', r
'>([^<]+)</a>'], part_code
, 'item title', fatal
=False) 
1549             part_url_snippet 
= self
._html
_search
_regex
( 
1550                 r
'(?s)href="([^"]+)"', part_code
, 'item URL') 
1551             part_url 
= compat_urlparse
.urljoin( 
1552                 'https://www.youtube.com/', part_url_snippet
) 
1556                 'title': part_title
, 
1560             '_type': 'playlist', 
1566 class YoutubeShowIE(InfoExtractor
): 
1567     IE_DESC 
= 'YouTube.com (multi-season) shows' 
1568     _VALID_URL 
= r
'https?://www\.youtube\.com/show/(?P<id>[^?#]*)' 
1569     IE_NAME 
= 'youtube:show' 
1571         'url': 'http://www.youtube.com/show/airdisasters', 
1572         'playlist_mincount': 3, 
1574             'id': 'airdisasters', 
1575             'title': 'Air Disasters', 
1579     def _real_extract(self
, url
): 
1580         mobj 
= re
.match(self
._VALID
_URL
, url
) 
1581         playlist_id 
= mobj
.group('id') 
1582         webpage 
= self
._download
_webpage
( 
1583             url
, playlist_id
, 'Downloading show webpage') 
1584         # There's one playlist for each season of the show 
1585         m_seasons 
= list(re
.finditer(r
'href="(/playlist\?list=.*?)"', webpage
)) 
1586         self
.to_screen('%s: Found %s seasons' % (playlist_id
, len(m_seasons
))) 
1589                 'https://www.youtube.com' + season
.group(1), 'YoutubePlaylist') 
1590             for season 
in m_seasons
 
1592         title 
= self
._og
_search
_title
(webpage
, fatal
=False) 
1595             '_type': 'playlist', 
1602 class YoutubeFeedsInfoExtractor(YoutubeBaseInfoExtractor
): 
1604     Base class for extractors that fetch info from 
1605     http://www.youtube.com/feed_ajax 
1606     Subclasses must define the _FEED_NAME and _PLAYLIST_TITLE properties. 
1608     _LOGIN_REQUIRED 
= True 
1609     # use action_load_personal_feed instead of action_load_system_feed 
1610     _PERSONAL_FEED 
= False 
1613     def _FEED_TEMPLATE(self
): 
1614         action 
= 'action_load_system_feed' 
1615         if self
._PERSONAL
_FEED
: 
1616             action 
= 'action_load_personal_feed' 
1617         return 'https://www.youtube.com/feed_ajax?%s=1&feed_name=%s&paging=%%s' % (action
, self
._FEED
_NAME
) 
1621         return 'youtube:%s' % self
._FEED
_NAME
 
1623     def _real_initialize(self
): 
1626     def _real_extract(self
, url
): 
1629         for i 
in itertools
.count(1): 
1630             info 
= self
._download
_json
( 
1631                 self
._FEED
_TEMPLATE 
% paging
, 
1632                 '%s feed' % self
._FEED
_NAME
, 
1633                 'Downloading page %s' % i
, 
1634                 transform_source
=uppercase_escape
) 
1635             feed_html 
= info
.get('feed_html') or info
.get('content_html') 
1636             load_more_widget_html 
= info
.get('load_more_widget_html') or feed_html
 
1637             m_ids 
= re
.finditer(r
'"/watch\?v=(.*?)["&]', feed_html
) 
1638             ids 
= orderedSet(m
.group(1) for m 
in m_ids
) 
1639             feed_entries
.extend( 
1640                 self
.url_result(video_id
, 'Youtube', video_id
=video_id
) 
1641                 for video_id 
in ids
) 
1643                 r
'data-uix-load-more-href="/?[^"]+paging=(?P<paging>\d+)', 
1644                 load_more_widget_html
) 
1647             paging 
= mobj
.group('paging') 
1648         return self
.playlist_result(feed_entries
, playlist_title
=self
._PLAYLIST
_TITLE
) 
1651 class YoutubeRecommendedIE(YoutubeFeedsInfoExtractor
): 
1652     IE_NAME 
= 'youtube:recommended' 
1653     IE_DESC 
= 'YouTube.com recommended videos, ":ytrec" for short (requires authentication)' 
1654     _VALID_URL 
= r
'https?://www\.youtube\.com/feed/recommended|:ytrec(?:ommended)?' 
1655     _FEED_NAME 
= 'recommended' 
1656     _PLAYLIST_TITLE 
= 'Youtube Recommended videos' 
1659 class YoutubeWatchLaterIE(YoutubePlaylistIE
): 
1660     IE_NAME 
= 'youtube:watchlater' 
1661     IE_DESC 
= 'Youtube watch later list, ":ytwatchlater" for short (requires authentication)' 
1662     _VALID_URL 
= r
'https?://www\.youtube\.com/(?:feed/watch_later|playlist\?list=WL)|:ytwatchlater' 
1664     _TESTS 
= []  # override PlaylistIE tests 
1666     def _real_extract(self
, url
): 
1667         return self
._extract
_playlist
('WL') 
1670 class YoutubeHistoryIE(YoutubePlaylistIE
): 
1671     IE_NAME 
= 'youtube:history' 
1672     IE_DESC 
= 'Youtube watch history, ":ythistory" for short (requires authentication)' 
1673     _VALID_URL 
= 'https?://www\.youtube\.com/feed/history|:ythistory' 
1676     def _real_extract(self
, url
): 
1677         title 
= 'Youtube History' 
1678         page 
= self
._download
_webpage
('https://www.youtube.com/feed/history', title
) 
1680         # The extraction process is the same as for playlists, but the regex 
1681         # for the video ids doesn't contain an index 
1683         more_widget_html 
= content_html 
= page
 
1685         for page_num 
in itertools
.count(1): 
1686             matches 
= re
.findall(r
'href="\s*/watch\?v=([0-9A-Za-z_-]{11})', content_html
) 
1687             new_ids 
= orderedSet(matches
) 
1690             mobj 
= re
.search(r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html
) 
1694             more 
= self
._download
_json
( 
1695                 'https://youtube.com/%s' % mobj
.group('more'), title
, 
1696                 'Downloading page #%s' % page_num
, 
1697                 transform_source
=uppercase_escape
) 
1698             content_html 
= more
['content_html'] 
1699             more_widget_html 
= more
['load_more_widget_html'] 
1702             '_type': 'playlist', 
1704             'entries': self
._ids
_to
_results
(ids
), 
1708 class YoutubeFavouritesIE(YoutubeBaseInfoExtractor
): 
1709     IE_NAME 
= 'youtube:favorites' 
1710     IE_DESC 
= 'YouTube.com favourite videos, ":ytfav" for short (requires authentication)' 
1711     _VALID_URL 
= r
'https?://www\.youtube\.com/my_favorites|:ytfav(?:ou?rites)?' 
1712     _LOGIN_REQUIRED 
= True 
1714     def _real_extract(self
, url
): 
1715         webpage 
= self
._download
_webpage
('https://www.youtube.com/my_favorites', 'Youtube Favourites videos') 
1716         playlist_id 
= self
._search
_regex
(r
'list=(.+?)["&]', webpage
, 'favourites playlist id') 
1717         return self
.url_result(playlist_id
, 'YoutubePlaylist') 
1720 class YoutubeSubscriptionsIE(YoutubePlaylistIE
): 
1721     IE_NAME 
= 'youtube:subscriptions' 
1722     IE_DESC 
= 'YouTube.com subscriptions feed, "ytsubs" keyword (requires authentication)' 
1723     _VALID_URL 
= r
'https?://www\.youtube\.com/feed/subscriptions|:ytsubs(?:criptions)?' 
1726     def _real_extract(self
, url
): 
1727         title 
= 'Youtube Subscriptions' 
1728         page 
= self
._download
_webpage
('https://www.youtube.com/feed/subscriptions', title
) 
1730         # The extraction process is the same as for playlists, but the regex 
1731         # for the video ids doesn't contain an index 
1733         more_widget_html 
= content_html 
= page
 
1735         for page_num 
in itertools
.count(1): 
1736             matches 
= re
.findall(r
'href="\s*/watch\?v=([0-9A-Za-z_-]{11})', content_html
) 
1737             new_ids 
= orderedSet(matches
) 
1740             mobj 
= re
.search(r
'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html
) 
1744             more 
= self
._download
_json
( 
1745                 'https://youtube.com/%s' % mobj
.group('more'), title
, 
1746                 'Downloading page #%s' % page_num
, 
1747                 transform_source
=uppercase_escape
) 
1748             content_html 
= more
['content_html'] 
1749             more_widget_html 
= more
['load_more_widget_html'] 
1752             '_type': 'playlist', 
1754             'entries': self
._ids
_to
_results
(ids
), 
1758 class YoutubeTruncatedURLIE(InfoExtractor
): 
1759     IE_NAME 
= 'youtube:truncated_url' 
1760     IE_DESC 
= False  # Do not list 
1761     _VALID_URL 
= r
'''(?x) 
1763         (?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/ 
1766             annotation_id=annotation_[^&]+| 
1771             attribution_link\?a=[^&]+ 
1777         'url': 'http://www.youtube.com/watch?annotation_id=annotation_3951667041', 
1778         'only_matching': True, 
1780         'url': 'http://www.youtube.com/watch?', 
1781         'only_matching': True, 
1783         'url': 'https://www.youtube.com/watch?x-yt-cl=84503534', 
1784         'only_matching': True, 
1786         'url': 'https://www.youtube.com/watch?feature=foo', 
1787         'only_matching': True, 
1789         'url': 'https://www.youtube.com/watch?hl=en-GB', 
1790         'only_matching': True, 
1793     def _real_extract(self
, url
): 
1794         raise ExtractorError( 
1795             'Did you forget to quote the URL? Remember that & is a meta ' 
1796             'character in most shells, so you want to put the URL in quotes, ' 
1798             '"http://www.youtube.com/watch?feature=foo&v=BaW_jenozKc" ' 
1799             ' or simply  youtube-dl BaW_jenozKc  .', 
1803 class YoutubeTruncatedIDIE(InfoExtractor
): 
1804     IE_NAME 
= 'youtube:truncated_id' 
1805     IE_DESC 
= False  # Do not list 
1806     _VALID_URL 
= r
'https?://(?:www\.)?youtube\.com/watch\?v=(?P<id>[0-9A-Za-z_-]{1,10})$' 
1809         'url': 'https://www.youtube.com/watch?v=N_708QY7Ob', 
1810         'only_matching': True, 
1813     def _real_extract(self
, url
): 
1814         video_id 
= self
._match
_id
(url
) 
1815         raise ExtractorError( 
1816             'Incomplete YouTube ID %s. URL %s looks truncated.' % (video_id
, url
),