Comparing sensitive data, confidential files or internal emails?

Most legal and privacy policies prohibit uploading sensitive data online. Diffchecker Desktop ensures your confidential information never leaves your computer. Work offline and compare documents securely.

Untitled diff

Created Diff never expires
7 removals
487 lines
11 additions
489 lines
# coding: utf-8
# coding: utf-8


from __future__ import unicode_literals
from __future__ import unicode_literals




import itertools
import itertools
import json
import json
import os.path
import os.path
import random
import random
import re
import re
import time
import time
import traceback
import traceback


from .common import InfoExtractor, SearchInfoExtractor
from .common import InfoExtractor, SearchInfoExtractor
from ..jsinterp import JSInterpreter
from ..jsinterp import JSInterpreter
from ..swfinterp import SWFInterpreter
from ..swfinterp import SWFInterpreter
from ..compat import (
from ..compat import (
compat_chr,
compat_chr,
compat_kwargs,
compat_parse_qs,
compat_parse_qs,
compat_urllib_parse_unquote,
compat_urllib_parse_unquote,
compat_urllib_parse_unquote_plus,
compat_urllib_parse_unquote_plus,
compat_urllib_parse_urlencode,
compat_urllib_parse_urlencode,
compat_urllib_parse_urlparse,
compat_urllib_parse_urlparse,
compat_urlparse,
compat_urlparse,
compat_str,
compat_str,
)
)
from ..utils import (
from ..utils import (
clean_html,
clean_html,
error_to_compat_str,
error_to_compat_str,
ExtractorError,
ExtractorError,
float_or_none,
float_or_none,
get_element_by_attribute,
get_element_by_attribute,
get_element_by_id,
get_element_by_id,
int_or_none,
int_or_none,
mimetype2ext,
mimetype2ext,
orderedSet,
orderedSet,
parse_codecs,
parse_codecs,
parse_duration,
parse_duration,
remove_quotes,
remove_quotes,
remove_start,
remove_start,
smuggle_url,
smuggle_url,
str_to_int,
str_to_int,
try_get,
try_get,
unescapeHTML,
unescapeHTML,
unified_strdate,
unified_strdate,
unsmuggle_url,
unsmuggle_url,
uppercase_escape,
uppercase_escape,
urlencode_postdata,
urlencode_postdata,
)
)




class YoutubeBaseInfoExtractor(InfoExtractor):
class YoutubeBaseInfoExtractor(InfoExtractor):
"""Provide base functions for Youtube extractors"""
"""Provide base functions for Youtube extractors"""
_LOGIN_URL = 'https://accounts.google.com/ServiceLogin'
_LOGIN_URL = 'https://accounts.google.com/ServiceLogin'
_TWOFACTOR_URL = 'https://accounts.google.com/signin/challenge'
_TWOFACTOR_URL = 'https://accounts.google.com/signin/challenge'


_LOOKUP_URL = 'https://accounts.google.com/_/signin/sl/lookup'
_LOOKUP_URL = 'https://accounts.google.com/_/signin/sl/lookup'
_CHALLENGE_URL = 'https://accounts.google.com/_/signin/sl/challenge'
_CHALLENGE_URL = 'https://accounts.google.com/_/signin/sl/challenge'
_TFA_URL = 'https://accounts.google.com/_/signin/challenge?hl=en&TL={0}'
_TFA_URL = 'https://accounts.google.com/_/signin/challenge?hl=en&TL={0}'


_NETRC_MACHINE = 'youtube'
_NETRC_MACHINE = 'youtube'
# If True it will raise an error if no login info is provided
# If True it will raise an error if no login info is provided
_LOGIN_REQUIRED = False
_LOGIN_REQUIRED = False


_PLAYLIST_ID_RE = r'(?:PL|LL|EC|UU|FL|RD|UL|TL)[0-9A-Za-z-_]{10,}'
_PLAYLIST_ID_RE = r'(?:PL|LL|EC|UU|FL|RD|UL|TL)[0-9A-Za-z-_]{10,}'


def _set_language(self):
def _set_language(self):
self._set_cookie(
self._set_cookie(
'.youtube.com', 'PREF', 'f1=50000000&hl=en',
'.youtube.com', 'PREF', 'f1=50000000&hl=en',
# YouTube sets the expire time to about two months
# YouTube sets the expire time to about two months
expire_time=time.time() + 2 * 30 * 24 * 3600)
expire_time=time.time() + 2 * 30 * 24 * 3600)


def _ids_to_results(self, ids):
def _ids_to_results(self, ids):
return [
return [
self.url_result(vid_id, 'Youtube', video_id=vid_id)
self.url_result(vid_id, 'Youtube', video_id=vid_id)
for vid_id in ids]
for vid_id in ids]


def _login(self):
def _login(self):
"""
"""
Attempt to log in to YouTube.
Attempt to log in to YouTube.
True is returned if successful or skipped.
True is returned if successful or skipped.
False is returned if login failed.
False is returned if login failed.


If _LOGIN_REQUIRED is set and no authentication was provided, an error is raised.
If _LOGIN_REQUIRED is set and no authentication was provided, an error is raised.
"""
"""
(username, password) = self._get_login_info()
(username, password) = self._get_login_info()
# No authentication to be performed
# No authentication to be performed
if username is None:
if username is None:
if self._LOGIN_REQUIRED:
if self._LOGIN_REQUIRED:
raise ExtractorError('No login info available, needed for using %s.' % self.IE_NAME, expected=True)
raise ExtractorError('No login info available, needed for using %s.' % self.IE_NAME, expected=True)
return True
return True


login_page = self._download_webpage(
login_page = self._download_webpage(
self._LOGIN_URL, None,
self._LOGIN_URL, None,
note='Downloading login page',
note='Downloading login page',
errnote='unable to fetch login page', fatal=False)
errnote='unable to fetch login page', fatal=False)
if login_page is False:
if login_page is False:
return
return


login_form = self._hidden_inputs(login_page)
login_form = self._hidden_inputs(login_page)


def req(url, f_req, note, errnote):
def req(url, f_req, note, errnote):
data = login_form.copy()
data = login_form.copy()
data.update({
data.update({
'pstMsg': 1,
'pstMsg': 1,
'checkConnection': 'youtube',
'checkConnection': 'youtube',
'checkedDomains': 'youtube',
'checkedDomains': 'youtube',
'hl': 'en',
'hl': 'en',
'deviceinfo': '[null,null,null,[],null,"US",null,null,[],"GlifWebSignIn",null,[null,null,[]]]',
'deviceinfo': '[null,null,null,[],null,"US",null,null,[],"GlifWebSignIn",null,[null,null,[]]]',
'f.req': json.dumps(f_req),
'f.req': json.dumps(f_req),
'flowName': 'GlifWebSignIn',
'flowName': 'GlifWebSignIn',
'flowEntry': 'ServiceLogin',
'flowEntry': 'ServiceLogin',
})
})
return self._download_json(
return self._download_json(
url, None, note=note, errnote=errnote,
url, None, note=note, errnote=errnote,
transform_source=lambda s: re.sub(r'^[^[]*', '', s),
transform_source=lambda s: re.sub(r'^[^[]*', '', s),
fatal=False,
fatal=False,
data=urlencode_postdata(data), headers={
data=urlencode_postdata(data), headers={
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
'Google-Accounts-XSRF': 1,
'Google-Accounts-XSRF': 1,
})
})


def warn(message):
def warn(message):
self._downloader.report_warning(message)
self._downloader.report_warning(message)


lookup_req = [
lookup_req = [
username,
username,
None, [], None, 'US', None, None, 2, False, True,
None, [], None, 'US', None, None, 2, False, True,
[
[
None, None,
None, None,
[2, 1, None, 1,
[2, 1, None, 1,
'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Fnext%3D%252F%26action_handle_signin%3Dtrue%26hl%3Den%26app%3Ddesktop%26feature%3Dsign_in_button&hl=en&service=youtube&uilel=3&requestPath=%2FServiceLogin&Page=PasswordSeparationSignIn',
'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Fnext%3D%252F%26action_handle_signin%3Dtrue%26hl%3Den%26app%3Ddesktop%26feature%3Dsign_in_button&hl=en&service=youtube&uilel=3&requestPath=%2FServiceLogin&Page=PasswordSeparationSignIn',
None, [], 4],
None, [], 4],
1, [None, None, []], None, None, None, True
1, [None, None, []], None, None, None, True
],
],
username,
username,
]
]


lookup_results = req(
lookup_results = req(
self._LOOKUP_URL, lookup_req,
self._LOOKUP_URL, lookup_req,
'Looking up account info', 'Unable to look up account info')
'Looking up account info', 'Unable to look up account info')


if lookup_results is False:
if lookup_results is False:
return False
return False


user_hash = try_get(lookup_results, lambda x: x[0][2], compat_str)
user_hash = try_get(lookup_results, lambda x: x[0][2], compat_str)
if not user_hash:
if not user_hash:
warn('Unable to extract user hash')
warn('Unable to extract user hash')
return False
return False


challenge_req = [
challenge_req = [
user_hash,
user_hash,
None, 1, None, [1, None, None, None, [password, None, True]],
None, 1, None, [1, None, None, None, [password, None, True]],
[
[
None, None, [2, 1, None, 1, 'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Fnext%3D%252F%26action_handle_signin%3Dtrue%26hl%3Den%26app%3Ddesktop%26feature%3Dsign_in_button&hl=en&service=youtube&uilel=3&requestPath=%2FServiceLogin&Page=PasswordSeparationSignIn', None, [], 4],
None, None, [2, 1, None, 1, 'https://accounts.google.com/ServiceLogin?passive=true&continue=https%3A%2F%2Fwww.youtube.com%2Fsignin%3Fnext%3D%252F%26action_handle_signin%3Dtrue%26hl%3Den%26app%3Ddesktop%26feature%3Dsign_in_button&hl=en&service=youtube&uilel=3&requestPath=%2FServiceLogin&Page=PasswordSeparationSignIn', None, [], 4],
1, [None, None, []], None, None, None, True
1, [None, None, []], None, None, None, True
]]
]]


challenge_results = req(
challenge_results = req(
self._CHALLENGE_URL, challenge_req,
self._CHALLENGE_URL, challenge_req,
'Logging in', 'Unable to log in')
'Logging in', 'Unable to log in')


if challenge_results is False:
if challenge_results is False:
return
return


login_res = try_get(challenge_results, lambda x: x[0][5], list)
login_res = try_get(challenge_results, lambda x: x[0][5], list)
if login_res:
if login_res:
login_msg = try_get(login_res, lambda x: x[5], compat_str)
login_msg = try_get(login_res, lambda x: x[5], compat_str)
warn(
warn(
'Unable to login: %s' % 'Invalid password'
'Unable to login: %s' % 'Invalid password'
if login_msg == 'INCORRECT_ANSWER_ENTERED' else login_msg)
if login_msg == 'INCORRECT_ANSWER_ENTERED' else login_msg)
return False
return False


res = try_get(challenge_results, lambda x: x[0][-1], list)
res = try_get(challenge_results, lambda x: x[0][-1], list)
if not res:
if not res:
warn('Unable to extract result entry')
warn('Unable to extract result entry')
return False
return False


tfa = try_get(res, lambda x: x[0][0], list)
tfa = try_get(res, lambda x: x[0][0], list)
if tfa:
if tfa:
tfa_str = try_get(tfa, lambda x: x[2], compat_str)
tfa_str = try_get(tfa, lambda x: x[2], compat_str)
if tfa_str == 'TWO_STEP_VERIFICATION':
if tfa_str == 'TWO_STEP_VERIFICATION':
# SEND_SUCCESS - TFA code has been successfully sent to phone
# SEND_SUCCESS - TFA code has been successfully sent to phone
# QUOTA_EXCEEDED - reached the limit of TFA codes
# QUOTA_EXCEEDED - reached the limit of TFA codes
status = try_get(tfa, lambda x: x[5], compat_str)
status = try_get(tfa, lambda x: x[5], compat_str)
if status == 'QUOTA_EXCEEDED':
if status == 'QUOTA_EXCEEDED':
warn('Exceeded the limit of TFA codes, try later')
warn('Exceeded the limit of TFA codes, try later')
return False
return False


tl = try_get(challenge_results, lambda x: x[1][2], compat_str)
tl = try_get(challenge_results, lambda x: x[1][2], compat_str)
if not tl:
if not tl:
warn('Unable to extract TL')
warn('Unable to extract TL')
return False
return False


tfa_code = self._get_tfa_info('2-step verification code')
tfa_code = self._get_tfa_info('2-step verification code')


if not tfa_code:
if not tfa_code:
warn(
warn(
'Two-factor authentication required. Provide it either interactively or with --twofactor <code>'
'Two-factor authentication required. Provide it either interactively or with --twofactor <code>'
'(Note that only TOTP (Google Authenticator App) codes work at this time.)')
'(Note that only TOTP (Google Authenticator App) codes work at this time.)')
return False
return False


tfa_code = remove_start(tfa_code, 'G-')
tfa_code = remove_start(tfa_code, 'G-')


tfa_req = [
tfa_req = [
user_hash, None, 2, None,
user_hash, None, 2, None,
[
[
9, None, None, None, None, None, None, None,
9, None, None, None, None, None, None, None,
[None, tfa_code, True, 2]
[None, tfa_code, True, 2]
]]
]]


tfa_results = req(
tfa_results = req(
self._TFA_URL.format(tl), tfa_req,
self._TFA_URL.format(tl), tfa_req,
'Submitting TFA code', 'Unable to submit TFA code')
'Submitting TFA code', 'Unable to submit TFA code')


if tfa_results is False:
if tfa_results is False:
return False
return False


tfa_res = try_get(tfa_results, lambda x: x[0][5], list)
tfa_res = try_get(tfa_results, lambda x: x[0][5], list)
if tfa_res:
if tfa_res:
tfa_msg = try_get(tfa_res, lambda x: x[5], compat_str)
tfa_msg = try_get(tfa_res, lambda x: x[5], compat_str)
warn(
warn(
'Unable to finish TFA: %s' % 'Invalid TFA code'
'Unable to finish TFA: %s' % 'Invalid TFA code'
if tfa_msg == 'INCORRECT_ANSWER_ENTERED' else tfa_msg)
if tfa_msg == 'INCORRECT_ANSWER_ENTERED' else tfa_msg)
return False
return False


check_cookie_url = try_get(
check_cookie_url = try_get(
tfa_results, lambda x: x[0][-1][2], compat_str)
tfa_results, lambda x: x[0][-1][2], compat_str)
else:
else:
check_cookie_url = try_get(res, lambda x: x[2], compat_str)
check_cookie_url = try_get(res, lambda x: x[2], compat_str)


if not check_cookie_url:
if not check_cookie_url:
warn('Unable to extract CheckCookie URL')
warn('Unable to extract CheckCookie URL')
return False
return False


check_cookie_results = self._download_webpage(
check_cookie_results = self._download_webpage(
check_cookie_url, None, 'Checking cookie', fatal=False)
check_cookie_url, None, 'Checking cookie', fatal=False)


if check_cookie_results is False:
if check_cookie_results is False:
return False
return False


if 'https://myaccount.google.com/' not in check_cookie_results:
if 'https://myaccount.google.com/' not in check_cookie_results:
warn('Unable to log in')
warn('Unable to log in')
return False
return False


return True
return True


def _download_webpage(self, *args, **kwargs):
kwargs.setdefault('query', {})['disable_polymer'] = 'true'
return super(YoutubeBaseInfoExtractor, self)._download_webpage(
*args, **compat_kwargs(kwargs))

def _real_initialize(self):
def _real_initialize(self):
if self._downloader is None:
if self._downloader is None:
return
return
self._set_language()
self._set_language()
if not self._login():
if not self._login():
return
return




class YoutubeEntryListBaseInfoExtractor(YoutubeBaseInfoExtractor):
class YoutubeEntryListBaseInfoExtractor(YoutubeBaseInfoExtractor):
# Extract entries from page with "Load more" button
# Extract entries from page with "Load more" button
def _entries(self, page, playlist_id):
def _entries(self, page, playlist_id):
more_widget_html = content_html = page
more_widget_html = content_html = page
for page_num in itertools.count(1):
for page_num in itertools.count(1):
for entry in self._process_page(content_html):
for entry in self._process_page(content_html):
yield entry
yield entry


mobj = re.search(r'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html)
mobj = re.search(r'data-uix-load-more-href="/?(?P<more>[^"]+)"', more_widget_html)
if not mobj:
if not mobj:
break
break


more = self._download_json(
more = self._download_json(
'https://youtube.com/%s' % mobj.group('more'), playlist_id,
'https://youtube.com/%s' % mobj.group('more'), playlist_id,
'Downloading page #%s' % page_num,
'Downloading page #%s' % page_num,
transform_source=uppercase_escape)
transform_source=uppercase_escape)
content_html = more['content_html']
content_html = more['content_html']
if not content_html.strip():
if not content_html.strip():
# Some webpages show a "Load more" button but they don't
# Some webpages show a "Load more" button but they don't
# have more videos
# have more videos
break
break
more_widget_html = more['load_more_widget_html']
more_widget_html = more['load_more_widget_html']




class YoutubePlaylistBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor):
class YoutubePlaylistBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor):
def _process_page(self, content):
def _process_page(self, content):
for video_id, video_title in self.extract_videos_from_page(content):
for video_id, video_title in self.extract_videos_from_page(content):
yield self.url_result(video_id, 'Youtube', video_id, video_title)
yield self.url_result(video_id, 'Youtube', video_id, video_title)


def extract_videos_from_page(self, page):
def extract_videos_from_page(self, page):
ids_in_page = []
ids_in_page = []
titles_in_page = []
titles_in_page = []
for mobj in re.finditer(self._VIDEO_RE, page):
for mobj in re.finditer(self._VIDEO_RE, page):
# The link with index 0 is not the first video of the playlist (not sure if still actual)
# The link with index 0 is not the first video of the playlist (not sure if still actual)
if 'index' in mobj.groupdict() and mobj.group('id') == '0':
if 'index' in mobj.groupdict() and mobj.group('id') == '0':
continue
continue
video_id = mobj.group('id')
video_id = mobj.group('id')
video_title = unescapeHTML(mobj.group('title'))
video_title = unescapeHTML(mobj.group('title'))
if video_title:
if video_title:
video_title = video_title.strip()
video_title = video_title.strip()
try:
try:
idx = ids_in_page.index(video_id)
idx = ids_in_page.index(video_id)
if video_title and not titles_in_page[idx]:
if video_title and not titles_in_page[idx]:
titles_in_page[idx] = video_title
titles_in_page[idx] = video_title
except ValueError:
except ValueError:
ids_in_page.append(video_id)
ids_in_page.append(video_id)
titles_in_page.append(video_title)
titles_in_page.append(video_title)
return zip(ids_in_page, titles_in_page)
return zip(ids_in_page, titles_in_page)




class YoutubePlaylistsBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor):
class YoutubePlaylistsBaseInfoExtractor(YoutubeEntryListBaseInfoExtractor):
def _process_page(self, content):
def _process_page(self, content):
for playlist_id in orderedSet(re.findall(
for playlist_id in orderedSet(re.findall(
r'<h3[^>]+class="[^"]*yt-lockup-title[^"]*"[^>]*><a[^>]+href="/?playlist\?list=([0-9A-Za-z-_]{10,})"',
r'<h3[^>]+class="[^"]*yt-lockup-title[^"]*"[^>]*><a[^>]+href="/?playlist\?list=([0-9A-Za-z-_]{10,})"',
content)):
content)):
yield self.url_result(
yield self.url_result(
'https://www.youtube.com/playlist?list=%s' % playlist_id, 'YoutubePlaylist')
'https://www.youtube.com/playlist?list=%s' % playlist_id, 'YoutubePlaylist')


def _real_extract(self, url):
def _real_extract(self, url):
playlist_id = self._match_id(url)
playlist_id = self._match_id(url)
webpage = self._download_webpage(url, playlist_id)
webpage = self._download_webpage(url, playlist_id)
title = self._og_search_title(webpage, fatal=False)
title = self._og_search_title(webpage, fatal=False)
return self.playlist_result(self._entries(webpage, playlist_id), playlist_id, title)
return self.playlist_result(self._entries(webpage, playlist_id), playlist_id, title)




class YoutubeIE(YoutubeBaseInfoExtractor):
class YoutubeIE(YoutubeBaseInfoExtractor):
IE_DESC = 'YouTube.com'
IE_DESC = 'YouTube.com'
_VALID_URL = r"""(?x)^
_VALID_URL = r"""(?x)^
(
(
(?:https?://|//) # http(s):// or protocol-independent URL
(?:https?://|//) # http(s):// or protocol-independent URL
(?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/|
(?:(?:(?:(?:\w+\.)?[yY][oO][uU][tT][uU][bB][eE](?:-nocookie)?\.com/|
(?:www\.)?deturl\.com/www\.youtube\.com/|
(?:www\.)?deturl\.com/www\.youtube\.com/|
(?:www\.)?pwnyoutube\.com/|
(?:www\.)?pwnyoutube\.com/|
(?:www\.)?hooktube\.com/|
(?:www\.)?yourepeat\.com/|
(?:www\.)?yourepeat\.com/|
tube\.majestyc\.net/|
tube\.majestyc\.net/|
youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains
youtube\.googleapis\.com/) # the various hostnames, with wildcard subdomains
(?:.*?\#/)? # handle anchor (#/) redirect urls
(?:.*?\#/)? # handle anchor (#/) redirect urls
(?: # the various things that can precede the ID:
(?: # the various things that can precede the ID:
(?:(?:v|embed|e)/(?!videoseries)) # v/ or embed/ or e/
(?:(?:v|embed|e)/(?!videoseries)) # v/ or embed/ or e/
|(?: # or the v= param in all its forms
|(?: # or the v= param in all its forms
(?:(?:watch|movie)(?:_popup)?(?:\.php)?/?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
(?:(?:watch|movie)(?:_popup)?(?:\.php)?/?)? # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
(?:\?|\#!?) # the params delimiter ? or # or #!
(?:\?|\#!?) # the params delimiter ? or # or #!
(?:.*?[&;])?? # any other preceding param (like /?s=tuff&v=xxxx or ?s=tuff&amp;v=V36LpHqtcDY)
(?:.*?[&;])?? # any other preceding param (like /?s=tuff&v=xxxx or ?s=tuff&amp;v=V36LpHqtcDY)
v=
v=
)
)
))
))
|(?:
|(?:
youtu\.be| # just youtu.be/xxxx
youtu\.be| # just youtu.be/xxxx
vid\.plus| # or vid.plus/xxxx
vid\.plus| # or vid.plus/xxxx
zwearz\.com/watch| # or zwearz.com/watch/xxxx
zwearz\.com/watch| # or zwearz.com/watch/xxxx
)/
)/
|(?:www\.)?cleanvideosearch\.com/media/action/yt/watch\?videoId=
|(?:www\.)?cleanvideosearch\.com/media/action/yt/watch\?videoId=
)
)
)? # all until now is optional -> you can pass the naked ID
)? # all until now is optional -> you can pass the naked ID
([0-9A-Za-z_-]{11}) # here is it! the YouTube video ID
([0-9A-Za-z_-]{11}) # here is it! the YouTube video ID
(?!.*?\blist=
(?!.*?\blist=
(?:
(?:
%(playlist_id)s| # combined list/video URLs are handled by the playlist IE
%(playlist_id)s| # combined list/video URLs are handled by the playlist IE
WL # WL are handled by the watch later IE
WL # WL are handled by the watch later IE
)
)
)
)
(?(1).+)? # if we found the ID, everything can follow
(?(1).+)? # if we found the ID, everything can follow
$""" % {'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE}
$""" % {'playlist_id': YoutubeBaseInfoExtractor._PLAYLIST_ID_RE}
_NEXT_URL_RE = r'[\?&]next_url=([^&]+)'
_NEXT_URL_RE = r'[\?&]next_url=([^&]+)'
_formats = {
_formats = {
'5': {'ext': 'flv', 'width': 400, 'height': 240, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'},
'5': {'ext': 'flv', 'width': 400, 'height': 240, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'},
'6': {'ext': 'flv', 'width': 450, 'height': 270, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'},
'6': {'ext': 'flv', 'width': 450, 'height': 270, 'acodec': 'mp3', 'abr': 64, 'vcodec': 'h263'},
'13': {'ext': '3gp', 'acodec': 'aac', 'vcodec': 'mp4v'},
'13': {'ext': '3gp', 'acodec': 'aac', 'vcodec': 'mp4v'},
'17': {'ext': '3gp', 'width': 176, 'height': 144, 'acodec': 'aac', 'abr': 24, 'vcodec': 'mp4v'},
'17': {'ext': '3gp', 'width': 176, 'height': 144, 'acodec': 'aac', 'abr': 24, 'vcodec': 'mp4v'},
'18': {'ext': 'mp4', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 96, 'vcodec': 'h264'},
'18': {'ext': 'mp4', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 96, 'vcodec': 'h264'},
'22': {'ext': 'mp4', 'width': 1280, 'height': 720, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'},
'22': {'ext': 'mp4', 'width': 1280, 'height': 720, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'},
'34': {'ext': 'flv', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},
'34': {'ext': 'flv', 'width': 640, 'height': 360, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},
'35': {'ext': 'flv', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},
'35': {'ext': 'flv', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},
# itag 36 videos are either 320x180 (BaW_jenozKc) or 320x240 (__2ABJjxzNo), abr varies as well
# itag 36 videos are either 320x180 (BaW_jenozKc) or 320x240 (__2ABJjxzNo), abr varies as well
'36': {'ext': '3gp', 'width': 320, 'acodec': 'aac', 'vcodec': 'mp4v'},
'36': {'ext': '3gp', 'width': 320, 'acodec': 'aac', 'vcodec': 'mp4v'},
'37': {'ext': 'mp4', 'width': 1920, 'height': 1080, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'},
'37': {'ext': 'mp4', 'width': 1920, 'height': 1080, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'},
'38': {'ext': 'mp4', 'width': 4096, 'height': 3072, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'},
'38': {'ext': 'mp4', 'width': 4096, 'height': 3072, 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264'},
'43': {'ext': 'webm', 'width': 640, 'height': 360, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'},
'43': {'ext': 'webm', 'width': 640, 'height': 360, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'},
'44': {'ext': 'webm', 'width': 854, 'height': 480, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'},
'44': {'ext': 'webm', 'width': 854, 'height': 480, 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8'},
'45': {'ext': 'webm', 'width': 1280, 'height': 720, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'},
'45': {'ext': 'webm', 'width': 1280, 'height': 720, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'},
'46': {'ext': 'webm', 'width': 1920, 'height': 1080, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'},
'46': {'ext': 'webm', 'width': 1920, 'height': 1080, 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8'},
'59': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},
'59': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},
'78': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},
'78': {'ext': 'mp4', 'width': 854, 'height': 480, 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264'},




# 3D videos
# 3D videos
'82': {'ext': 'mp4', 'height': 360, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20},
'82': {'ext': 'mp4', 'height': 360, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20},
'83': {'ext': 'mp4', 'height': 480, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20},
'83': {'ext': 'mp4', 'height': 480, 'format_note': '3D', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -20},
'84': {'ext': 'mp4', 'height': 720, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20},
'84': {'ext': 'mp4', 'height': 720, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20},
'85': {'ext': 'mp4', 'height': 1080, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20},
'85': {'ext': 'mp4', 'height': 1080, 'format_note': '3D', 'acodec': 'aac', 'abr': 192, 'vcodec': 'h264', 'preference': -20},
'100': {'ext': 'webm', 'height': 360, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8', 'preference': -20},
'100': {'ext': 'webm', 'height': 360, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 128, 'vcodec': 'vp8', 'preference': -20},
'101': {'ext': 'webm', 'height': 480, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20},
'101': {'ext': 'webm', 'height': 480, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20},
'102': {'ext': 'webm', 'height': 720, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20},
'102': {'ext': 'webm', 'height': 720, 'format_note': '3D', 'acodec': 'vorbis', 'abr': 192, 'vcodec': 'vp8', 'preference': -20},


# Apple HTTP Live Streaming
# Apple HTTP Live Streaming
'91': {'ext': 'mp4', 'height': 144, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10},
'91': {'ext': 'mp4', 'height': 144, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10},
'92': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10},
'92': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10},
'93': {'ext': 'mp4', 'height': 360, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10},
'93': {'ext': 'mp4', 'height': 360, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10},
'94': {'ext': 'mp4', 'height': 480, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10},
'94': {'ext': 'mp4', 'height': 480, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 128, 'vcodec': 'h264', 'preference': -10},
'95': {'ext': 'mp4', 'height': 720, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10},
'95': {'ext': 'mp4', 'height': 720, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10},
'96': {'ext': 'mp4', 'height': 1080, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10},
'96': {'ext': 'mp4', 'height': 1080, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 256, 'vcodec': 'h264', 'preference': -10},
'132': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10},
'132': {'ext': 'mp4', 'height': 240, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 48, 'vcodec': 'h264', 'preference': -10},
'151': {'ext': 'mp4', 'height': 72, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 24, 'vcodec': 'h264', 'preference': -10},
'151': {'ext': 'mp4', 'height': 72, 'format_note': 'HLS', 'acodec': 'aac', 'abr': 24, 'vcodec': 'h264', 'preference': -10},


# DASH mp4 video
# DASH mp4 video
'133': {'ext': 'mp4', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'h264'},
'133': {'ext': 'mp4', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'h264'},
'134': {'ext': 'mp4', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'h264'},
'134': {'ext': 'mp4', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'h264'},
'135': {'ext': 'mp4', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'h264'},
'135': {'ext': 'mp4', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'h264'},
'136': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264'},
'136': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264'},
'137': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264'},
'137': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264'},
'138': {'ext': 'mp4', 'format_note': 'DASH video', 'vcodec': 'h264'}, # Height can vary (https://github.com/rg3/youtube-dl/issues/4559)
'138': {'ext': 'mp4', 'format_note': 'DASH video', 'vcodec': 'h264'}, # Height can vary (https://github.com/rg3/youtube-dl/issues/4559)
'160': {'ext': 'mp4', 'height': 144, 'format_note': 'DASH video', 'vcodec': 'h264'},
'160': {'ext': 'mp4', 'height': 144, 'format_note': 'DASH video', 'vcodec': 'h264'},
'212': {'ext': 'mp4', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'h264'},
'212': {'ext': 'mp4', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'h264'},
'264': {'ext': 'mp4', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'h264'},
'264': {'ext': 'mp4', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'h264'},
'298': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60},
'298': {'ext': 'mp4', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60},
'299': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60},
'299': {'ext': 'mp4', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'h264', 'fps': 60},
'266': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'h264'},
'266': {'ext': 'mp4', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'h264'},


# Dash mp4 audio
# Dash mp4 audio
'139': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 48, 'container': 'm4a_dash'},
'139': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 48, 'container': 'm4a_dash'},
'140': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 128, 'container': 'm4a_dash'},
'140': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 128, 'container': 'm4a_dash'},
'141': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 256, 'container': 'm4a_dash'},
'141': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'abr': 256, 'container': 'm4a_dash'},
'256': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'container': 'm4a_dash'},
'256': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'container': 'm4a_dash'},
'258': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'container': 'm4a_dash'},
'258': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'aac', 'container': 'm4a_dash'},
'325': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'dtse', 'container': 'm4a_dash'},
'325': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'dtse', 'container': 'm4a_dash'},
'328': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'ec-3', 'container': 'm4a_dash'},
'328': {'ext': 'm4a', 'format_note': 'DASH audio', 'acodec': 'ec-3', 'container': 'm4a_dash'},


# Dash webm
# Dash webm
'167': {'ext': 'webm', 'height': 360, 'width': 640, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'167': {'ext': 'webm', 'height': 360, 'width': 640, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'168': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'168': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'169': {'ext': 'webm', 'height': 720, 'width': 1280, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'169': {'ext': 'webm', 'height': 720, 'width': 1280, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'170': {'ext': 'webm', 'height': 1080, 'width': 1920, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'170': {'ext': 'webm', 'height': 1080, 'width': 1920, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'218': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'218': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'219': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'219': {'ext': 'webm', 'height': 480, 'width': 854, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp8'},
'278': {'ext': 'webm', 'height': 144, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp9'},
'278': {'ext': 'webm', 'height': 144, 'format_note': 'DASH video', 'container': 'webm', 'vcodec': 'vp9'},
'242': {'ext': 'webm', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'242': {'ext': 'webm', 'height': 240, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'243': {'ext': 'webm', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'243': {'ext': 'webm', 'height': 360, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'244': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'244': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'245': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'245': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'246': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'246': {'ext': 'webm', 'height': 480, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'247': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'247': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'248': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'271': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'271': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9'},
# itag 272 videos are either 3840x2160 (e.g. RtoitU2A-3E) or 7680x4320 (sLprVF6d7Ug)
# itag 272 videos are either 3840x2160 (e.g. RtoitU2A-3E) or 7680x4320 (sLprVF6d7Ug)
'272': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'272': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'302': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},
'302': {'ext': 'webm', 'height': 720, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},
'303': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},
'303': {'ext': 'webm', 'height': 1080, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},
'308': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},
'308': {'ext': 'webm', 'height': 1440, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},
'313': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'313': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9'},
'315': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},
'315': {'ext': 'webm', 'height': 2160, 'format_note': 'DASH video', 'vcodec': 'vp9', 'fps': 60},


# Dash webm audio
# Dash webm audio
'171': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 128},
'171': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 128},
'172': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 256},
'172': {'ext': 'webm', 'acodec': 'vorbis', 'format_note': 'DASH audio', 'abr': 256},


# Dash webm audio with opus inside
# Dash webm audio with opus inside
'249': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 50},
'249': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 50},
'250': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 70},
'250': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 70},
'251': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 160},
'251': {'ext': 'webm', 'format_note': 'DASH audio', 'acodec': 'opus', 'abr': 160},


# RTMP (unnamed)
# RTMP (unnamed)
'_rtmp': {'protocol': 'rtmp'},
'_rtmp': {'protocol': 'rtmp'},
}
}
_SUBTITLE_FORMATS = ('ttml', 'vtt')
_SUBTITLE_FORMATS = ('ttml', 'vtt')


_GEO_BYPASS = False
_GEO_BYPASS = False


IE_NAME = 'youtube'
IE_NAME = 'youtube'
_TESTS = [
_TESTS = [
{
{
'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&t=1s&end=9',
'url': 'https://www.youtube.com/watch?v=BaW_jenozKc&t=1s&end=9',
'info_dict': {
'info_dict': {
'id': 'BaW_jenozKc',
'id': 'BaW_jenozKc',
'ext': 'mp4',
'ext': 'mp4',
'title': 'youtube-dl test video "\'/\\ä↭𝕐',
'title': 'youtube-dl test video "\'/\\ä↭𝕐',
'uploader': 'Philipp Hagemeister',
'uploader': 'Philipp Hagemeister',
'uploader_id': 'phihag',
'uploader_id': 'phihag',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/phihag',
'uploader_url': r're:https?://(?:www\.)?youtube\.com/user/phihag',
'upload_date': '20121002',
'upload_date': '20121002',
'license': 'Standard YouTube License',
'license': 'Standard YouTube License',
'description': 'test chars: "\'/\\ä↭𝕐\ntest URL: https://github.com/rg3/youtube-dl/issues/1892\n\nThis is a test video for youtube-dl.\n\nFor more information, contact phihag@phihag.de .',
'description': 'test chars: "\'/\\ä↭𝕐\ntest URL: https://github.com/rg3/youtube-dl/issues/1892\n\nThis is a test video for youtube-dl.\n\nFor more information, contact phihag@phihag.de .',
'categories': ['Science & Technology'],
'categories': ['Science & Technology'],
'tags': ['youtube-dl'],
'tags': ['youtube-dl'],
'duration': 10,
'duration': 10,
'like_count': int,
'dislike_count': int,
'start_time': 1,
'end_time': 9,
}
},
{
'url': 'https://www.youtube.com/watch?v=UxxajLWwzqY',
'note': 'Test generic use_cipher_signature video (#897)',