Compare commits
10 Commits
bd9918040d
...
0e08823ff3
Author | SHA1 | Date | |
---|---|---|---|
|
0e08823ff3 | ||
|
4d05f84325 | ||
|
e0094e63c3 | ||
|
fd8242e3ef | ||
|
ad01fa6cca | ||
|
2eac0fa379 | ||
|
d7b502a727 | ||
|
74e39ca0fd | ||
|
dc990a61cc | ||
|
9bbe366275 |
@ -577,9 +577,11 @@ class TestJSInterpreter(unittest.TestCase):
|
|||||||
def test_unary_operators(self):
|
def test_unary_operators(self):
|
||||||
jsi = JSInterpreter('function f(){return 2 - - - 2;}')
|
jsi = JSInterpreter('function f(){return 2 - - - 2;}')
|
||||||
self.assertEqual(jsi.call_function('f'), 0)
|
self.assertEqual(jsi.call_function('f'), 0)
|
||||||
# fails
|
jsi = JSInterpreter('function f(){return 2 + - + - - 2;}')
|
||||||
# jsi = JSInterpreter('function f(){return 2 + - + - - 2;}')
|
self.assertEqual(jsi.call_function('f'), 0)
|
||||||
# self.assertEqual(jsi.call_function('f'), 0)
|
# https://github.com/ytdl-org/youtube-dl/issues/32815
|
||||||
|
jsi = JSInterpreter('function f(){return 0 - 7 * - 6;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 42)
|
||||||
|
|
||||||
""" # fails so far
|
""" # fails so far
|
||||||
def test_packed(self):
|
def test_packed(self):
|
||||||
|
@ -158,6 +158,10 @@ _NSIG_TESTS = [
|
|||||||
'https://www.youtube.com/s/player/b7910ca8/player_ias.vflset/en_US/base.js',
|
'https://www.youtube.com/s/player/b7910ca8/player_ias.vflset/en_US/base.js',
|
||||||
'_hXMCwMt9qE310D', 'LoZMgkkofRMCZQ',
|
'_hXMCwMt9qE310D', 'LoZMgkkofRMCZQ',
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
'https://www.youtube.com/s/player/590f65a6/player_ias.vflset/en_US/base.js',
|
||||||
|
'1tm7-g_A9zsI8_Lay_', 'xI4Vem4Put_rOg',
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -2448,12 +2448,12 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
import BaseHTTPServer as compat_http_server
|
import BaseHTTPServer as compat_http_server
|
||||||
|
|
||||||
|
# urllib.parse
|
||||||
try:
|
try:
|
||||||
from urllib.parse import unquote_to_bytes as compat_urllib_parse_unquote_to_bytes
|
from urllib.parse import unquote_to_bytes as compat_urllib_parse_unquote_to_bytes
|
||||||
from urllib.parse import unquote as compat_urllib_parse_unquote
|
from urllib.parse import unquote as compat_urllib_parse_unquote
|
||||||
from urllib.parse import unquote_plus as compat_urllib_parse_unquote_plus
|
from urllib.parse import unquote_plus as compat_urllib_parse_unquote_plus
|
||||||
from urllib.parse import urlencode as compat_urllib_parse_urlencode
|
from urllib.parse import urlencode as compat_urllib_parse_urlencode
|
||||||
from urllib.parse import parse_qs as compat_parse_qs
|
|
||||||
except ImportError: # Python 2
|
except ImportError: # Python 2
|
||||||
_asciire = (compat_urllib_parse._asciire if hasattr(compat_urllib_parse, '_asciire')
|
_asciire = (compat_urllib_parse._asciire if hasattr(compat_urllib_parse, '_asciire')
|
||||||
else re.compile(r'([\x00-\x7f]+)'))
|
else re.compile(r'([\x00-\x7f]+)'))
|
||||||
@ -2543,60 +2543,80 @@ except ImportError: # Python 2
|
|||||||
|
|
||||||
return compat_urllib_parse._urlencode(encode_elem(query), doseq=doseq)
|
return compat_urllib_parse._urlencode(encode_elem(query), doseq=doseq)
|
||||||
|
|
||||||
# HACK: The following is the correct parse_qs implementation from cpython 3's stdlib.
|
|
||||||
# Python 2's version is apparently totally broken
|
|
||||||
def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
|
|
||||||
encoding='utf-8', errors='replace'):
|
|
||||||
qs, _coerce_result = qs, compat_str
|
|
||||||
pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
|
|
||||||
r = []
|
|
||||||
for name_value in pairs:
|
|
||||||
if not name_value and not strict_parsing:
|
|
||||||
continue
|
|
||||||
nv = name_value.split('=', 1)
|
|
||||||
if len(nv) != 2:
|
|
||||||
if strict_parsing:
|
|
||||||
raise ValueError('bad query field: %r' % (name_value,))
|
|
||||||
# Handle case of a control-name with no equal sign
|
|
||||||
if keep_blank_values:
|
|
||||||
nv.append('')
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
if len(nv[1]) or keep_blank_values:
|
|
||||||
name = nv[0].replace('+', ' ')
|
|
||||||
name = compat_urllib_parse_unquote(
|
|
||||||
name, encoding=encoding, errors=errors)
|
|
||||||
name = _coerce_result(name)
|
|
||||||
value = nv[1].replace('+', ' ')
|
|
||||||
value = compat_urllib_parse_unquote(
|
|
||||||
value, encoding=encoding, errors=errors)
|
|
||||||
value = _coerce_result(value)
|
|
||||||
r.append((name, value))
|
|
||||||
return r
|
|
||||||
|
|
||||||
def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False,
|
|
||||||
encoding='utf-8', errors='replace'):
|
|
||||||
parsed_result = {}
|
|
||||||
pairs = _parse_qsl(qs, keep_blank_values, strict_parsing,
|
|
||||||
encoding=encoding, errors=errors)
|
|
||||||
for name, value in pairs:
|
|
||||||
if name in parsed_result:
|
|
||||||
parsed_result[name].append(value)
|
|
||||||
else:
|
|
||||||
parsed_result[name] = [value]
|
|
||||||
return parsed_result
|
|
||||||
|
|
||||||
setattr(compat_urllib_parse, '_urlencode',
|
setattr(compat_urllib_parse, '_urlencode',
|
||||||
getattr(compat_urllib_parse, 'urlencode'))
|
getattr(compat_urllib_parse, 'urlencode'))
|
||||||
for name, fix in (
|
for name, fix in (
|
||||||
('unquote_to_bytes', compat_urllib_parse_unquote_to_bytes),
|
('unquote_to_bytes', compat_urllib_parse_unquote_to_bytes),
|
||||||
('parse_unquote', compat_urllib_parse_unquote),
|
('parse_unquote', compat_urllib_parse_unquote),
|
||||||
('unquote_plus', compat_urllib_parse_unquote_plus),
|
('unquote_plus', compat_urllib_parse_unquote_plus),
|
||||||
('urlencode', compat_urllib_parse_urlencode),
|
('urlencode', compat_urllib_parse_urlencode)):
|
||||||
('parse_qs', compat_parse_qs)):
|
|
||||||
setattr(compat_urllib_parse, name, fix)
|
setattr(compat_urllib_parse, name, fix)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
# arguments changed in 3.8 and 3.10
|
||||||
|
from urllib.parse import parse_qs as _parse_qs
|
||||||
|
_parse_qs('a=b', separator='&')
|
||||||
|
compat_parse_qs = _parse_qs
|
||||||
|
except (ImportError, TypeError): # Python 2, < 3.10
|
||||||
|
|
||||||
compat_urllib_parse_parse_qs = compat_parse_qs
|
# HACK: The following is the correct parse_qs implementation from cpython 3's stdlib.
|
||||||
|
# Python 2's version is apparently totally broken
|
||||||
|
# Also use this implementation for Py < 3.10
|
||||||
|
# * support only default separator '&', not r'[&;]', like 3.10+
|
||||||
|
# * support max_num_fields, like 3.8+
|
||||||
|
def _parse_qsl(qs, keep_blank_values=False, strict_parsing=False,
|
||||||
|
encoding='utf-8', errors='replace',
|
||||||
|
max_num_fields=None, separator='&'):
|
||||||
|
if not isinstance(separator, (compat_str, str)):
|
||||||
|
raise ValueError('Separator must be of type string or bytes')
|
||||||
|
# DoS protection, if anyone cares
|
||||||
|
if qs and max_num_fields is not None and qs.count(separator) >= max_num_fields:
|
||||||
|
raise ValueError('Too many fields')
|
||||||
|
_coerce_result = compat_str
|
||||||
|
r = []
|
||||||
|
for name_value in qs.split(separator):
|
||||||
|
if not name_value and not strict_parsing:
|
||||||
|
continue
|
||||||
|
nv = name_value.split('=', 1)
|
||||||
|
if len(nv) != 2:
|
||||||
|
if strict_parsing:
|
||||||
|
raise ValueError('bad query field: %r' % (name_value,))
|
||||||
|
# Handle case of a control-name with no equal sign
|
||||||
|
if keep_blank_values:
|
||||||
|
nv.append('')
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
if len(nv[1]) or keep_blank_values:
|
||||||
|
name = nv[0].replace('+', ' ')
|
||||||
|
name = compat_urllib_parse_unquote(
|
||||||
|
name, encoding=encoding, errors=errors)
|
||||||
|
name = _coerce_result(name)
|
||||||
|
value = nv[1].replace('+', ' ')
|
||||||
|
value = compat_urllib_parse_unquote(
|
||||||
|
value, encoding=encoding, errors=errors)
|
||||||
|
value = _coerce_result(value)
|
||||||
|
r.append((name, value))
|
||||||
|
return r
|
||||||
|
|
||||||
|
def compat_parse_qs(qs, keep_blank_values=False, strict_parsing=False,
|
||||||
|
encoding='utf-8', errors='replace',
|
||||||
|
max_num_fields=None, separator='&'):
|
||||||
|
parsed_result = {}
|
||||||
|
pairs = _parse_qsl(qs, keep_blank_values, strict_parsing,
|
||||||
|
encoding, errors, max_num_fields, separator)
|
||||||
|
for name, value in pairs:
|
||||||
|
if name in parsed_result:
|
||||||
|
parsed_result[name].append(value)
|
||||||
|
else:
|
||||||
|
parsed_result[name] = [value]
|
||||||
|
return parsed_result
|
||||||
|
|
||||||
|
for name, fix in (
|
||||||
|
('parse_qs', compat_parse_qs),
|
||||||
|
('parse_qsl', _parse_qsl)):
|
||||||
|
setattr(compat_urllib_parse, name, fix)
|
||||||
|
|
||||||
|
compat_urllib_parse_parse_qs = compat_parse_qs
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib.request import DataHandler as compat_urllib_request_DataHandler
|
from urllib.request import DataHandler as compat_urllib_request_DataHandler
|
||||||
|
@ -4,7 +4,12 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import extract_attributes
|
from ..utils import (
|
||||||
|
extract_attributes,
|
||||||
|
parse_qs,
|
||||||
|
remove_start,
|
||||||
|
smuggle_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BFIPlayerIE(InfoExtractor):
|
class BFIPlayerIE(InfoExtractor):
|
||||||
@ -12,26 +17,39 @@ class BFIPlayerIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://player\.bfi\.org\.uk/[^/]+/film/watch-(?P<id>[\w-]+)-online'
|
_VALID_URL = r'https?://player\.bfi\.org\.uk/[^/]+/film/watch-(?P<id>[\w-]+)-online'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'https://player.bfi.org.uk/free/film/watch-computer-doctor-1974-online',
|
'url': 'https://player.bfi.org.uk/free/film/watch-computer-doctor-1974-online',
|
||||||
'md5': 'e8783ebd8e061ec4bc6e9501ed547de8',
|
'md5': '15598bdd6a413ce9363970754f054d76',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'htNnhlZjE60C9VySkQEIBtU-cNV1Xx63',
|
'id': 'htNnhlZjE60C9VySkQEIBtU-cNV1Xx63',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Computer Doctor',
|
'title': 'Computer Doctor',
|
||||||
'description': 'md5:fb6c240d40c4dbe40428bdd62f78203b',
|
'description': 'md5:fb6c240d40c4dbe40428bdd62f78203b',
|
||||||
|
'timestamp': 1564424975,
|
||||||
|
'upload_date': '20190729',
|
||||||
|
'uploader_id': '6057949427001',
|
||||||
},
|
},
|
||||||
'skip': 'BFI Player films cannot be played outside of the UK',
|
# 'skip': 'BFI Player films cannot be played outside of the UK',
|
||||||
}
|
}
|
||||||
|
_BRIGHTCOVE_ACCOUNT_ID = '6057949427001'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
entries = []
|
|
||||||
for player_el in re.findall(r'(?s)<[^>]+class="player"[^>]*>', webpage):
|
film_only = 'play-film' in parse_qs(url, keep_blank_values=True)
|
||||||
player_attr = extract_attributes(player_el)
|
|
||||||
ooyala_id = player_attr.get('data-video-id')
|
def entries():
|
||||||
if not ooyala_id:
|
for player_el in re.finditer(r'(?s)<video-js\b[^>]+>', webpage):
|
||||||
continue
|
player_attr = extract_attributes(player_el.group(0))
|
||||||
entries.append(self.url_result(
|
bcv_id, account_id, player_id, embed = (
|
||||||
'ooyala:' + ooyala_id, 'Ooyala',
|
player_attr.get(x) for x in ('data-ref-id', 'data-acid', 'data-pid', 'data-embed'))
|
||||||
ooyala_id, player_attr.get('data-label')))
|
if not bcv_id:
|
||||||
return self.playlist_result(entries)
|
continue
|
||||||
|
if film_only and player_attr.get('data-video-type') != 'film':
|
||||||
|
continue
|
||||||
|
bc_url = 'brightcove:new:%s:%s:%s:video:ref:%s' % (
|
||||||
|
account_id or self._BRIGHTCOVE_ACCOUNT_ID, player_id or 'default', embed or 'default', bcv_id)
|
||||||
|
|
||||||
|
yield self.url_result(smuggle_url(
|
||||||
|
bc_url, {'referrer': url, 'force_videoid': remove_start(bcv_id, 'ref:')}), ie='BrightcoveNew', video_id=video_id)
|
||||||
|
|
||||||
|
return self.playlist_result(entries())
|
||||||
|
@ -340,7 +340,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
|
|
||||||
class BrightcoveNewIE(AdobePassIE):
|
class BrightcoveNewIE(AdobePassIE):
|
||||||
IE_NAME = 'brightcove:new'
|
IE_NAME = 'brightcove:new'
|
||||||
_VALID_URL = r'https?://players\.brightcove\.net/(?P<account_id>\d+)/(?P<player_id>[^/]+)_(?P<embed>[^/]+)/index\.html\?.*(?P<content_type>video|playlist)Id=(?P<video_id>\d+|ref:[^&]+)'
|
_VALID_URL = r'(?:brightcove:new|(?P<u>https?)):(?(u)//players\.brightcove\.net/)(?P<account_id>\d+)(?(u)/|:)(?P<player_id>[^/]+)(?(u)_|:)(?P<embed>[^/]+)(?(u)/index\.html\?.*|:)(?P<content_type>video|playlist)(?(u)Id=|:)(?P<video_id>\d+|ref:[^&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://players.brightcove.net/929656772001/e41d32dc-ec74-459e-a845-6c69f7b724ea_default/index.html?videoId=4463358922001',
|
'url': 'http://players.brightcove.net/929656772001/e41d32dc-ec74-459e-a845-6c69f7b724ea_default/index.html?videoId=4463358922001',
|
||||||
'md5': 'c8100925723840d4b0d243f7025703be',
|
'md5': 'c8100925723840d4b0d243f7025703be',
|
||||||
@ -593,7 +593,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
'ip_blocks': smuggled_data.get('geo_ip_blocks'),
|
'ip_blocks': smuggled_data.get('geo_ip_blocks'),
|
||||||
})
|
})
|
||||||
|
|
||||||
account_id, player_id, embed, content_type, video_id = re.match(self._VALID_URL, url).groups()
|
account_id, player_id, embed, content_type, video_id = re.match(self._VALID_URL, url).groups()[1:]
|
||||||
|
|
||||||
policy_key_id = '%s_%s' % (account_id, player_id)
|
policy_key_id = '%s_%s' % (account_id, player_id)
|
||||||
policy_key = self._downloader.cache.load('brightcove', policy_key_id)
|
policy_key = self._downloader.cache.load('brightcove', policy_key_id)
|
||||||
@ -678,4 +678,4 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
json_data.get('description'))
|
json_data.get('description'))
|
||||||
|
|
||||||
return self._parse_brightcove_metadata(
|
return self._parse_brightcove_metadata(
|
||||||
json_data, video_id, headers=headers)
|
json_data, smuggled_data.get('force_videoid') or video_id, headers=headers)
|
||||||
|
@ -3033,7 +3033,6 @@ class InfoExtractor(object):
|
|||||||
transform_source=transform_source, default=None)
|
transform_source=transform_source, default=None)
|
||||||
|
|
||||||
def _extract_jwplayer_data(self, webpage, video_id, *args, **kwargs):
|
def _extract_jwplayer_data(self, webpage, video_id, *args, **kwargs):
|
||||||
|
|
||||||
# allow passing `transform_source` through to _find_jwplayer_data()
|
# allow passing `transform_source` through to _find_jwplayer_data()
|
||||||
transform_source = kwargs.pop('transform_source', None)
|
transform_source = kwargs.pop('transform_source', None)
|
||||||
kwfind = compat_kwargs({'transform_source': transform_source}) if transform_source else {}
|
kwfind = compat_kwargs({'transform_source': transform_source}) if transform_source else {}
|
||||||
|
@ -8,7 +8,7 @@ from ..compat import compat_str
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
str_or_none,
|
str_or_none,
|
||||||
try_get,
|
traverse_obj,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -109,7 +109,7 @@ class PalcoMP3ArtistIE(PalcoMP3BaseIE):
|
|||||||
}
|
}
|
||||||
name'''
|
name'''
|
||||||
|
|
||||||
@ classmethod
|
@classmethod
|
||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
return False if re.match(PalcoMP3IE._VALID_URL, url) else super(PalcoMP3ArtistIE, cls).suitable(url)
|
return False if re.match(PalcoMP3IE._VALID_URL, url) else super(PalcoMP3ArtistIE, cls).suitable(url)
|
||||||
|
|
||||||
@ -118,7 +118,8 @@ class PalcoMP3ArtistIE(PalcoMP3BaseIE):
|
|||||||
artist = self._call_api(artist_slug, self._ARTIST_FIELDS_TMPL)['artist']
|
artist = self._call_api(artist_slug, self._ARTIST_FIELDS_TMPL)['artist']
|
||||||
|
|
||||||
def entries():
|
def entries():
|
||||||
for music in (try_get(artist, lambda x: x['musics']['nodes'], list) or []):
|
for music in traverse_obj(artist, (
|
||||||
|
'musics', 'nodes', lambda _, m: m['musicID'])):
|
||||||
yield self._parse_music(music)
|
yield self._parse_music(music)
|
||||||
|
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
@ -137,7 +138,7 @@ class PalcoMP3VideoIE(PalcoMP3BaseIE):
|
|||||||
'title': 'Maiara e Maraisa - Você Faz Falta Aqui - DVD Ao Vivo Em Campo Grande',
|
'title': 'Maiara e Maraisa - Você Faz Falta Aqui - DVD Ao Vivo Em Campo Grande',
|
||||||
'description': 'md5:7043342c09a224598e93546e98e49282',
|
'description': 'md5:7043342c09a224598e93546e98e49282',
|
||||||
'upload_date': '20161107',
|
'upload_date': '20161107',
|
||||||
'uploader_id': 'maiaramaraisaoficial',
|
'uploader_id': '@maiaramaraisaoficial',
|
||||||
'uploader': 'Maiara e Maraisa',
|
'uploader': 'Maiara e Maraisa',
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
@ -14,6 +14,7 @@ from .utils import (
|
|||||||
remove_quotes,
|
remove_quotes,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
variadic,
|
variadic,
|
||||||
|
write_string,
|
||||||
)
|
)
|
||||||
from .compat import (
|
from .compat import (
|
||||||
compat_basestring,
|
compat_basestring,
|
||||||
@ -53,15 +54,16 @@ def wraps_op(op):
|
|||||||
|
|
||||||
# NB In principle NaN cannot be checked by membership.
|
# NB In principle NaN cannot be checked by membership.
|
||||||
# Here all NaN values are actually this one, so _NaN is _NaN,
|
# Here all NaN values are actually this one, so _NaN is _NaN,
|
||||||
# although _NaN != _NaN.
|
# although _NaN != _NaN. Ditto Infinity.
|
||||||
|
|
||||||
_NaN = float('nan')
|
_NaN = float('nan')
|
||||||
|
_Infinity = float('inf')
|
||||||
|
|
||||||
|
|
||||||
def _js_bit_op(op):
|
def _js_bit_op(op):
|
||||||
|
|
||||||
def zeroise(x):
|
def zeroise(x):
|
||||||
return 0 if x in (None, JS_Undefined, _NaN) else x
|
return 0 if x in (None, JS_Undefined, _NaN, _Infinity) else x
|
||||||
|
|
||||||
@wraps_op(op)
|
@wraps_op(op)
|
||||||
def wrapped(a, b):
|
def wrapped(a, b):
|
||||||
@ -84,7 +86,7 @@ def _js_arith_op(op):
|
|||||||
def _js_div(a, b):
|
def _js_div(a, b):
|
||||||
if JS_Undefined in (a, b) or not (a or b):
|
if JS_Undefined in (a, b) or not (a or b):
|
||||||
return _NaN
|
return _NaN
|
||||||
return operator.truediv(a or 0, b) if b else float('inf')
|
return operator.truediv(a or 0, b) if b else _Infinity
|
||||||
|
|
||||||
|
|
||||||
def _js_mod(a, b):
|
def _js_mod(a, b):
|
||||||
@ -220,6 +222,42 @@ class LocalNameSpace(ChainMap):
|
|||||||
return 'LocalNameSpace%s' % (self.maps, )
|
return 'LocalNameSpace%s' % (self.maps, )
|
||||||
|
|
||||||
|
|
||||||
|
class Debugger(object):
|
||||||
|
ENABLED = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write(*args, **kwargs):
|
||||||
|
level = kwargs.get('level', 100)
|
||||||
|
|
||||||
|
def truncate_string(s, left, right=0):
|
||||||
|
if s is None or len(s) <= left + right:
|
||||||
|
return s
|
||||||
|
return '...'.join((s[:left - 3], s[-right:] if right else ''))
|
||||||
|
|
||||||
|
write_string('[debug] JS: {0}{1}\n'.format(
|
||||||
|
' ' * (100 - level),
|
||||||
|
' '.join(truncate_string(compat_str(x), 50, 50) for x in args)))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def wrap_interpreter(cls, f):
|
||||||
|
def interpret_statement(self, stmt, local_vars, allow_recursion, *args, **kwargs):
|
||||||
|
if cls.ENABLED and stmt.strip():
|
||||||
|
cls.write(stmt, level=allow_recursion)
|
||||||
|
try:
|
||||||
|
ret, should_ret = f(self, stmt, local_vars, allow_recursion, *args, **kwargs)
|
||||||
|
except Exception as e:
|
||||||
|
if cls.ENABLED:
|
||||||
|
if isinstance(e, ExtractorError):
|
||||||
|
e = e.orig_msg
|
||||||
|
cls.write('=> Raises:', e, '<-|', stmt, level=allow_recursion)
|
||||||
|
raise
|
||||||
|
if cls.ENABLED and stmt.strip():
|
||||||
|
if should_ret or not repr(ret) == stmt:
|
||||||
|
cls.write(['->', '=>'][should_ret], repr(ret), '<-|', stmt, level=allow_recursion)
|
||||||
|
return ret, should_ret
|
||||||
|
return interpret_statement
|
||||||
|
|
||||||
|
|
||||||
class JSInterpreter(object):
|
class JSInterpreter(object):
|
||||||
__named_object_counter = 0
|
__named_object_counter = 0
|
||||||
|
|
||||||
@ -307,8 +345,7 @@ class JSInterpreter(object):
|
|||||||
def __op_chars(cls):
|
def __op_chars(cls):
|
||||||
op_chars = set(';,[')
|
op_chars = set(';,[')
|
||||||
for op in cls._all_operators():
|
for op in cls._all_operators():
|
||||||
for c in op[0]:
|
op_chars.update(op[0])
|
||||||
op_chars.add(c)
|
|
||||||
return op_chars
|
return op_chars
|
||||||
|
|
||||||
def _named_object(self, namespace, obj):
|
def _named_object(self, namespace, obj):
|
||||||
@ -326,9 +363,8 @@ class JSInterpreter(object):
|
|||||||
# collections.Counter() is ~10% slower in both 2.7 and 3.9
|
# collections.Counter() is ~10% slower in both 2.7 and 3.9
|
||||||
counters = dict((k, 0) for k in _MATCHING_PARENS.values())
|
counters = dict((k, 0) for k in _MATCHING_PARENS.values())
|
||||||
start, splits, pos, delim_len = 0, 0, 0, len(delim) - 1
|
start, splits, pos, delim_len = 0, 0, 0, len(delim) - 1
|
||||||
in_quote, escaping, skipping = None, False, 0
|
in_quote, escaping, after_op, in_regex_char_group = None, False, True, False
|
||||||
after_op, in_regex_char_group = True, False
|
skipping = 0
|
||||||
|
|
||||||
for idx, char in enumerate(expr):
|
for idx, char in enumerate(expr):
|
||||||
paren_delta = 0
|
paren_delta = 0
|
||||||
if not in_quote:
|
if not in_quote:
|
||||||
@ -382,10 +418,12 @@ class JSInterpreter(object):
|
|||||||
return separated[0][1:].strip(), separated[1].strip()
|
return separated[0][1:].strip(), separated[1].strip()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _all_operators():
|
def _all_operators(_cached=[]):
|
||||||
return itertools.chain(
|
if not _cached:
|
||||||
# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
|
_cached.extend(itertools.chain(
|
||||||
_SC_OPERATORS, _LOG_OPERATORS, _COMP_OPERATORS, _OPERATORS)
|
# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
|
||||||
|
_SC_OPERATORS, _LOG_OPERATORS, _COMP_OPERATORS, _OPERATORS))
|
||||||
|
return _cached
|
||||||
|
|
||||||
def _operator(self, op, left_val, right_expr, expr, local_vars, allow_recursion):
|
def _operator(self, op, left_val, right_expr, expr, local_vars, allow_recursion):
|
||||||
if op in ('||', '&&'):
|
if op in ('||', '&&'):
|
||||||
@ -416,7 +454,7 @@ class JSInterpreter(object):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
if allow_undefined:
|
if allow_undefined:
|
||||||
return JS_Undefined
|
return JS_Undefined
|
||||||
raise self.Exception('Cannot get index {idx:.100}'.format(**locals()), expr=repr(obj), cause=e)
|
raise self.Exception('Cannot get index {idx!r:.100}'.format(**locals()), expr=repr(obj), cause=e)
|
||||||
|
|
||||||
def _dump(self, obj, namespace):
|
def _dump(self, obj, namespace):
|
||||||
try:
|
try:
|
||||||
@ -438,6 +476,7 @@ class JSInterpreter(object):
|
|||||||
_FINALLY_RE = re.compile(r'finally\s*\{')
|
_FINALLY_RE = re.compile(r'finally\s*\{')
|
||||||
_SWITCH_RE = re.compile(r'switch\s*\(')
|
_SWITCH_RE = re.compile(r'switch\s*\(')
|
||||||
|
|
||||||
|
@Debugger.wrap_interpreter
|
||||||
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
|
def interpret_statement(self, stmt, local_vars, allow_recursion=100):
|
||||||
if allow_recursion < 0:
|
if allow_recursion < 0:
|
||||||
raise self.Exception('Recursion limit reached')
|
raise self.Exception('Recursion limit reached')
|
||||||
@ -511,7 +550,6 @@ class JSInterpreter(object):
|
|||||||
expr = self._dump(inner, local_vars) + outer
|
expr = self._dump(inner, local_vars) + outer
|
||||||
|
|
||||||
if expr.startswith('('):
|
if expr.startswith('('):
|
||||||
|
|
||||||
m = re.match(r'\((?P<d>[a-z])%(?P<e>[a-z])\.length\+(?P=e)\.length\)%(?P=e)\.length', expr)
|
m = re.match(r'\((?P<d>[a-z])%(?P<e>[a-z])\.length\+(?P=e)\.length\)%(?P=e)\.length', expr)
|
||||||
if m:
|
if m:
|
||||||
# short-cut eval of frequently used `(d%e.length+e.length)%e.length`, worth ~6% on `pytest -k test_nsig`
|
# short-cut eval of frequently used `(d%e.length+e.length)%e.length`, worth ~6% on `pytest -k test_nsig`
|
||||||
@ -693,7 +731,7 @@ class JSInterpreter(object):
|
|||||||
(?P<op>{_OPERATOR_RE})?
|
(?P<op>{_OPERATOR_RE})?
|
||||||
=(?!=)(?P<expr>.*)$
|
=(?!=)(?P<expr>.*)$
|
||||||
)|(?P<return>
|
)|(?P<return>
|
||||||
(?!if|return|true|false|null|undefined)(?P<name>{_NAME_RE})$
|
(?!if|return|true|false|null|undefined|NaN|Infinity)(?P<name>{_NAME_RE})$
|
||||||
)|(?P<indexing>
|
)|(?P<indexing>
|
||||||
(?P<in>{_NAME_RE})\[(?P<idx>.+)\]$
|
(?P<in>{_NAME_RE})\[(?P<idx>.+)\]$
|
||||||
)|(?P<attribute>
|
)|(?P<attribute>
|
||||||
@ -727,11 +765,12 @@ class JSInterpreter(object):
|
|||||||
raise JS_Break()
|
raise JS_Break()
|
||||||
elif expr == 'continue':
|
elif expr == 'continue':
|
||||||
raise JS_Continue()
|
raise JS_Continue()
|
||||||
|
|
||||||
elif expr == 'undefined':
|
elif expr == 'undefined':
|
||||||
return JS_Undefined, should_return
|
return JS_Undefined, should_return
|
||||||
elif expr == 'NaN':
|
elif expr == 'NaN':
|
||||||
return _NaN, should_return
|
return _NaN, should_return
|
||||||
|
elif expr == 'Infinity':
|
||||||
|
return _Infinity, should_return
|
||||||
|
|
||||||
elif md.get('return'):
|
elif md.get('return'):
|
||||||
return local_vars[m.group('name')], should_return
|
return local_vars[m.group('name')], should_return
|
||||||
@ -760,18 +799,28 @@ class JSInterpreter(object):
|
|||||||
right_expr = separated.pop()
|
right_expr = separated.pop()
|
||||||
# handle operators that are both unary and binary, minimal BODMAS
|
# handle operators that are both unary and binary, minimal BODMAS
|
||||||
if op in ('+', '-'):
|
if op in ('+', '-'):
|
||||||
|
# simplify/adjust consecutive instances of these operators
|
||||||
undone = 0
|
undone = 0
|
||||||
while len(separated) > 1 and not separated[-1].strip():
|
while len(separated) > 1 and not separated[-1].strip():
|
||||||
undone += 1
|
undone += 1
|
||||||
separated.pop()
|
separated.pop()
|
||||||
if op == '-' and undone % 2 != 0:
|
if op == '-' and undone % 2 != 0:
|
||||||
right_expr = op + right_expr
|
right_expr = op + right_expr
|
||||||
|
elif op == '+':
|
||||||
|
while len(separated) > 1 and separated[-1].strip() in self.OP_CHARS:
|
||||||
|
right_expr = separated.pop() + right_expr
|
||||||
|
# hanging op at end of left => unary + (strip) or - (push right)
|
||||||
left_val = separated[-1]
|
left_val = separated[-1]
|
||||||
for dm_op in ('*', '%', '/', '**'):
|
for dm_op in ('*', '%', '/', '**'):
|
||||||
bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim))
|
bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim))
|
||||||
if len(bodmas) > 1 and not bodmas[-1].strip():
|
if len(bodmas) > 1 and not bodmas[-1].strip():
|
||||||
expr = op.join(separated) + op + right_expr
|
expr = op.join(separated) + op + right_expr
|
||||||
right_expr = None
|
if len(separated) > 1:
|
||||||
|
separated.pop()
|
||||||
|
right_expr = op.join((left_val, right_expr))
|
||||||
|
else:
|
||||||
|
separated = [op.join((left_val, right_expr))]
|
||||||
|
right_expr = None
|
||||||
break
|
break
|
||||||
if right_expr is None:
|
if right_expr is None:
|
||||||
continue
|
continue
|
||||||
@ -797,6 +846,8 @@ class JSInterpreter(object):
|
|||||||
|
|
||||||
def eval_method():
|
def eval_method():
|
||||||
if (variable, member) == ('console', 'debug'):
|
if (variable, member) == ('console', 'debug'):
|
||||||
|
if Debugger.ENABLED:
|
||||||
|
Debugger.write(self.interpret_expression('[{}]'.format(arg_str), local_vars, allow_recursion))
|
||||||
return
|
return
|
||||||
types = {
|
types = {
|
||||||
'String': compat_str,
|
'String': compat_str,
|
||||||
|
@ -2402,11 +2402,11 @@ class YoutubeDLError(Exception):
|
|||||||
class ExtractorError(YoutubeDLError):
|
class ExtractorError(YoutubeDLError):
|
||||||
"""Error during info extraction."""
|
"""Error during info extraction."""
|
||||||
|
|
||||||
def __init__(self, msg, tb=None, expected=False, cause=None, video_id=None):
|
def __init__(self, msg, tb=None, expected=False, cause=None, video_id=None, ie=None):
|
||||||
""" tb, if given, is the original traceback (so that it can be printed out).
|
""" tb, if given, is the original traceback (so that it can be printed out).
|
||||||
If expected is set, this is a normal error message and most likely not a bug in youtube-dl.
|
If expected is set, this is a normal error message and most likely not a bug in youtube-dl.
|
||||||
"""
|
"""
|
||||||
|
self.orig_msg = msg
|
||||||
if sys.exc_info()[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError):
|
if sys.exc_info()[0] in (compat_urllib_error.URLError, socket.timeout, UnavailableVideoError):
|
||||||
expected = True
|
expected = True
|
||||||
if video_id is not None:
|
if video_id is not None:
|
||||||
@ -2421,6 +2421,7 @@ class ExtractorError(YoutubeDLError):
|
|||||||
self.exc_info = sys.exc_info() # preserve original exception
|
self.exc_info = sys.exc_info() # preserve original exception
|
||||||
self.cause = cause
|
self.cause = cause
|
||||||
self.video_id = video_id
|
self.video_id = video_id
|
||||||
|
self.ie = ie
|
||||||
|
|
||||||
def format_traceback(self):
|
def format_traceback(self):
|
||||||
if self.traceback is None:
|
if self.traceback is None:
|
||||||
@ -6561,3 +6562,24 @@ def join_nonempty(*values, **kwargs):
|
|||||||
if from_dict is not None:
|
if from_dict is not None:
|
||||||
values = (traverse_obj(from_dict, variadic(v)) for v in values)
|
values = (traverse_obj(from_dict, variadic(v)) for v in values)
|
||||||
return delim.join(map(compat_str, filter(None, values)))
|
return delim.join(map(compat_str, filter(None, values)))
|
||||||
|
|
||||||
|
|
||||||
|
class classproperty(object):
|
||||||
|
"""property access for class methods with optional caching"""
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
func = args[0] if len(args) > 0 else kwargs.get('func')
|
||||||
|
if not func:
|
||||||
|
return functools.partial(cls, *args, **kwargs)
|
||||||
|
return super(classproperty, cls).__new__(cls)
|
||||||
|
|
||||||
|
def __init__(self, func, cache=False):
|
||||||
|
functools.update_wrapper(self, func)
|
||||||
|
self.func = func
|
||||||
|
self._cache = {} if cache else None
|
||||||
|
|
||||||
|
def __get__(self, _, cls):
|
||||||
|
if self._cache is None:
|
||||||
|
return self.func(cls)
|
||||||
|
elif cls not in self._cache:
|
||||||
|
self._cache[cls] = self.func(cls)
|
||||||
|
return self._cache[cls]
|
||||||
|
Loading…
Reference in New Issue
Block a user