mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-12-22 18:17:17 -05:00
[jsinterp] Bring or-par with youtube-dl
Partially cherry-picked from: d231b56717
Authored by pukkandan, dirkf
This commit is contained in:
parent
d711839760
commit
49b4ceaedf
@ -71,7 +71,7 @@ yt-dlp is a [youtube-dl](https://github.com/ytdl-org/youtube-dl) fork based on t
|
|||||||
|
|
||||||
# NEW FEATURES
|
# NEW FEATURES
|
||||||
|
|
||||||
* Merged with **youtube-dl v2021.12.17+ [commit/adb5294](https://github.com/ytdl-org/youtube-dl/commit/adb5294177265ba35b45746dbb600965076ed150)**<!--([exceptions](https://github.com/yt-dlp/yt-dlp/issues/21))--> and **youtube-dlc v2020.11.11-3+ [commit/f9401f2](https://github.com/blackjack4494/yt-dlc/commit/f9401f2a91987068139c5f757b12fc711d4c0cee)**: You get all the features and patches of [youtube-dlc](https://github.com/blackjack4494/yt-dlc) in addition to the latest [youtube-dl](https://github.com/ytdl-org/youtube-dl)
|
* Merged with **youtube-dl v2021.12.17+ [commit/d231b56](https://github.com/ytdl-org/youtube-dl/commit/d231b56717c73ee597d2e077d11b69ed48a1b02d)**<!--([exceptions](https://github.com/yt-dlp/yt-dlp/issues/21))--> and **youtube-dlc v2020.11.11-3+ [commit/f9401f2](https://github.com/blackjack4494/yt-dlc/commit/f9401f2a91987068139c5f757b12fc711d4c0cee)**: You get all the features and patches of [youtube-dlc](https://github.com/blackjack4494/yt-dlc) in addition to the latest [youtube-dl](https://github.com/ytdl-org/youtube-dl)
|
||||||
|
|
||||||
* **[SponsorBlock Integration](#sponsorblock-options)**: You can mark/remove sponsor sections in youtube videos by utilizing the [SponsorBlock](https://sponsor.ajay.app) API
|
* **[SponsorBlock Integration](#sponsorblock-options)**: You can mark/remove sponsor sections in youtube videos by utilizing the [SponsorBlock](https://sponsor.ajay.app) API
|
||||||
|
|
||||||
|
@ -48,6 +48,9 @@ class TestJSInterpreter(unittest.TestCase):
|
|||||||
jsi = JSInterpreter('function f(){return 1 << 5;}')
|
jsi = JSInterpreter('function f(){return 1 << 5;}')
|
||||||
self.assertEqual(jsi.call_function('f'), 32)
|
self.assertEqual(jsi.call_function('f'), 32)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){return 2 ** 5}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 32)
|
||||||
|
|
||||||
jsi = JSInterpreter('function f(){return 19 & 21;}')
|
jsi = JSInterpreter('function f(){return 19 & 21;}')
|
||||||
self.assertEqual(jsi.call_function('f'), 17)
|
self.assertEqual(jsi.call_function('f'), 17)
|
||||||
|
|
||||||
@ -57,6 +60,12 @@ class TestJSInterpreter(unittest.TestCase):
|
|||||||
jsi = JSInterpreter('function f(){return []? 2+3: 4;}')
|
jsi = JSInterpreter('function f(){return []? 2+3: 4;}')
|
||||||
self.assertEqual(jsi.call_function('f'), 5)
|
self.assertEqual(jsi.call_function('f'), 5)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){return 1 == 2}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), False)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){return 0 && 1 || 2;}')
|
||||||
|
self.assertEqual(jsi.call_function('f'), 2)
|
||||||
|
|
||||||
def test_array_access(self):
|
def test_array_access(self):
|
||||||
jsi = JSInterpreter('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2.0] = 7; return x;}')
|
jsi = JSInterpreter('function f(){var x = [1,2,3]; x[0] = 4; x[0] = 5; x[2.0] = 7; return x;}')
|
||||||
self.assertEqual(jsi.call_function('f'), [5, 2, 7])
|
self.assertEqual(jsi.call_function('f'), [5, 2, 7])
|
||||||
@ -114,6 +123,16 @@ class TestJSInterpreter(unittest.TestCase):
|
|||||||
}''')
|
}''')
|
||||||
self.assertEqual(jsi.call_function('x'), [20, 20, 30, 40, 50])
|
self.assertEqual(jsi.call_function('x'), [20, 20, 30, 40, 50])
|
||||||
|
|
||||||
|
def test_builtins(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() { return new Date('Wednesday 31 December 1969 18:01:26 MDT') - 0; }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 86000)
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x(dt) { return new Date(dt) - 0; }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x', 'Wednesday 31 December 1969 18:01:26 MDT'), 86000)
|
||||||
|
|
||||||
def test_call(self):
|
def test_call(self):
|
||||||
jsi = JSInterpreter('''
|
jsi = JSInterpreter('''
|
||||||
function x() { return 2; }
|
function x() { return 2; }
|
||||||
@ -188,6 +207,17 @@ class TestJSInterpreter(unittest.TestCase):
|
|||||||
''')
|
''')
|
||||||
self.assertEqual(jsi.call_function('x'), 7)
|
self.assertEqual(jsi.call_function('x'), 7)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() { a=5; return (a -= 1, a+=3, a); }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 7)
|
||||||
|
|
||||||
|
def test_void(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() { return void 42; }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), None)
|
||||||
|
|
||||||
def test_return_function(self):
|
def test_return_function(self):
|
||||||
jsi = JSInterpreter('''
|
jsi = JSInterpreter('''
|
||||||
function x() { return [1, function(){return 1}][1] }
|
function x() { return [1, function(){return 1}][1] }
|
||||||
|
@ -109,6 +109,7 @@ _NSIG_TESTS = [
|
|||||||
class TestPlayerInfo(unittest.TestCase):
|
class TestPlayerInfo(unittest.TestCase):
|
||||||
def test_youtube_extract_player_info(self):
|
def test_youtube_extract_player_info(self):
|
||||||
PLAYER_URLS = (
|
PLAYER_URLS = (
|
||||||
|
('https://www.youtube.com/s/player/4c3f79c5/player_ias.vflset/en_US/base.js', '4c3f79c5'),
|
||||||
('https://www.youtube.com/s/player/64dddad9/player_ias.vflset/en_US/base.js', '64dddad9'),
|
('https://www.youtube.com/s/player/64dddad9/player_ias.vflset/en_US/base.js', '64dddad9'),
|
||||||
('https://www.youtube.com/s/player/64dddad9/player_ias.vflset/fr_FR/base.js', '64dddad9'),
|
('https://www.youtube.com/s/player/64dddad9/player_ias.vflset/fr_FR/base.js', '64dddad9'),
|
||||||
('https://www.youtube.com/s/player/64dddad9/player-plasma-ias-phone-en_US.vflset/base.js', '64dddad9'),
|
('https://www.youtube.com/s/player/64dddad9/player-plasma-ias-phone-en_US.vflset/base.js', '64dddad9'),
|
||||||
|
@ -17,6 +17,8 @@ from .utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
_NAME_RE = r'[a-zA-Z_$][\w$]*'
|
_NAME_RE = r'[a-zA-Z_$][\w$]*'
|
||||||
|
|
||||||
|
# Ref: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
|
||||||
_OPERATORS = { # None => Defined in JSInterpreter._operator
|
_OPERATORS = { # None => Defined in JSInterpreter._operator
|
||||||
'?': None,
|
'?': None,
|
||||||
|
|
||||||
@ -26,23 +28,31 @@ _OPERATORS = { # None => Defined in JSInterpreter._operator
|
|||||||
'|': operator.or_,
|
'|': operator.or_,
|
||||||
'^': operator.xor,
|
'^': operator.xor,
|
||||||
|
|
||||||
# FIXME: This should actually be below comparision
|
'===': operator.is_,
|
||||||
'>>': operator.rshift,
|
'!==': operator.is_not,
|
||||||
'<<': operator.lshift,
|
'==': operator.eq,
|
||||||
|
'!=': operator.ne,
|
||||||
|
|
||||||
'<=': operator.le,
|
'<=': operator.le,
|
||||||
'>=': operator.ge,
|
'>=': operator.ge,
|
||||||
'<': operator.lt,
|
'<': operator.lt,
|
||||||
'>': operator.gt,
|
'>': operator.gt,
|
||||||
|
|
||||||
|
'>>': operator.rshift,
|
||||||
|
'<<': operator.lshift,
|
||||||
|
|
||||||
'+': operator.add,
|
'+': operator.add,
|
||||||
'-': operator.sub,
|
'-': operator.sub,
|
||||||
|
|
||||||
'*': operator.mul,
|
'*': operator.mul,
|
||||||
'/': operator.truediv,
|
'/': operator.truediv,
|
||||||
'%': operator.mod,
|
'%': operator.mod,
|
||||||
|
|
||||||
|
'**': operator.pow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_COMP_OPERATORS = {'===', '!==', '==', '!=', '<=', '>=', '<', '>'}
|
||||||
|
|
||||||
_MATCHING_PARENS = dict(zip('({[', ')}]'))
|
_MATCHING_PARENS = dict(zip('({[', ')}]'))
|
||||||
_QUOTES = '\'"'
|
_QUOTES = '\'"'
|
||||||
|
|
||||||
@ -81,7 +91,7 @@ class LocalNameSpace(collections.ChainMap):
|
|||||||
|
|
||||||
class Debugger:
|
class Debugger:
|
||||||
import sys
|
import sys
|
||||||
ENABLED = 'pytest' in sys.modules
|
ENABLED = False and 'pytest' in sys.modules
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write(*args, level=100):
|
def write(*args, level=100):
|
||||||
@ -200,7 +210,7 @@ class JSInterpreter:
|
|||||||
if should_return:
|
if should_return:
|
||||||
return ret, should_return
|
return ret, should_return
|
||||||
|
|
||||||
m = re.match(r'(?P<var>var\s)|return(?:\s+|$)', stmt)
|
m = re.match(r'(?P<var>(?:var|const|let)\s)|return(?:\s+|$)', stmt)
|
||||||
if m:
|
if m:
|
||||||
expr = stmt[len(m.group(0)):].strip()
|
expr = stmt[len(m.group(0)):].strip()
|
||||||
should_return = not m.group('var')
|
should_return = not m.group('var')
|
||||||
@ -218,13 +228,18 @@ class JSInterpreter:
|
|||||||
obj = expr[4:]
|
obj = expr[4:]
|
||||||
if obj.startswith('Date('):
|
if obj.startswith('Date('):
|
||||||
left, right = self._separate_at_paren(obj[4:], ')')
|
left, right = self._separate_at_paren(obj[4:], ')')
|
||||||
expr = unified_timestamp(left[1:-1], False)
|
expr = unified_timestamp(
|
||||||
|
self.interpret_expression(left, local_vars, allow_recursion), False)
|
||||||
if not expr:
|
if not expr:
|
||||||
raise self.Exception(f'Failed to parse date {left!r}', expr)
|
raise self.Exception(f'Failed to parse date {left!r}', expr)
|
||||||
expr = self._dump(int(expr * 1000), local_vars) + right
|
expr = self._dump(int(expr * 1000), local_vars) + right
|
||||||
else:
|
else:
|
||||||
raise self.Exception(f'Unsupported object {obj}', expr)
|
raise self.Exception(f'Unsupported object {obj}', expr)
|
||||||
|
|
||||||
|
if expr.startswith('void '):
|
||||||
|
left = self.interpret_expression(expr[5:], local_vars, allow_recursion)
|
||||||
|
return None, should_return
|
||||||
|
|
||||||
if expr.startswith('{'):
|
if expr.startswith('{'):
|
||||||
inner, outer = self._separate_at_paren(expr, '}')
|
inner, outer = self._separate_at_paren(expr, '}')
|
||||||
inner, should_abort = self.interpret_statement(inner, local_vars, allow_recursion)
|
inner, should_abort = self.interpret_statement(inner, local_vars, allow_recursion)
|
||||||
@ -307,7 +322,8 @@ class JSInterpreter:
|
|||||||
if default:
|
if default:
|
||||||
matched = matched or case == 'default'
|
matched = matched or case == 'default'
|
||||||
elif not matched:
|
elif not matched:
|
||||||
matched = case != 'default' and switch_val == self.interpret_expression(case, local_vars, allow_recursion)
|
matched = (case != 'default'
|
||||||
|
and switch_val == self.interpret_expression(case, local_vars, allow_recursion))
|
||||||
if not matched:
|
if not matched:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@ -347,7 +363,7 @@ class JSInterpreter:
|
|||||||
m = re.match(fr'''(?x)
|
m = re.match(fr'''(?x)
|
||||||
(?P<assign>
|
(?P<assign>
|
||||||
(?P<out>{_NAME_RE})(?:\[(?P<index>[^\]]+?)\])?\s*
|
(?P<out>{_NAME_RE})(?:\[(?P<index>[^\]]+?)\])?\s*
|
||||||
(?P<op>{"|".join(map(re.escape, _OPERATORS))})?
|
(?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})?
|
||||||
=(?P<expr>.*)$
|
=(?P<expr>.*)$
|
||||||
)|(?P<return>
|
)|(?P<return>
|
||||||
(?!if|return|true|false|null|undefined)(?P<name>{_NAME_RE})$
|
(?!if|return|true|false|null|undefined)(?P<name>{_NAME_RE})$
|
||||||
@ -397,12 +413,14 @@ class JSInterpreter:
|
|||||||
|
|
||||||
for op in _OPERATORS:
|
for op in _OPERATORS:
|
||||||
separated = list(self._separate(expr, op))
|
separated = list(self._separate(expr, op))
|
||||||
if len(separated) < 2:
|
|
||||||
continue
|
|
||||||
right_expr = separated.pop()
|
right_expr = separated.pop()
|
||||||
while op == '-' and len(separated) > 1 and not separated[-1].strip():
|
while op in '<>*-' and len(separated) > 1 and not separated[-1].strip():
|
||||||
right_expr = f'-{right_expr}'
|
|
||||||
separated.pop()
|
separated.pop()
|
||||||
|
right_expr = f'{op}{right_expr}'
|
||||||
|
if op != '-':
|
||||||
|
right_expr = f'{separated.pop()}{op}{right_expr}'
|
||||||
|
if not separated:
|
||||||
|
continue
|
||||||
left_val = self.interpret_expression(op.join(separated), local_vars, allow_recursion)
|
left_val = self.interpret_expression(op.join(separated), local_vars, allow_recursion)
|
||||||
return self._operator(op, 0 if left_val is None else left_val,
|
return self._operator(op, 0 if left_val is None else left_val,
|
||||||
right_expr, expr, local_vars, allow_recursion), should_return
|
right_expr, expr, local_vars, allow_recursion), should_return
|
||||||
@ -564,8 +582,8 @@ class JSInterpreter:
|
|||||||
# Currently, it only supports function definitions
|
# Currently, it only supports function definitions
|
||||||
fields_m = re.finditer(
|
fields_m = re.finditer(
|
||||||
r'''(?x)
|
r'''(?x)
|
||||||
(?P<key>%s)\s*:\s*function\s*\((?P<args>[a-z,]+)\){(?P<code>[^}]+)}
|
(?P<key>%s)\s*:\s*function\s*\((?P<args>(?:%s|,)*)\){(?P<code>[^}]+)}
|
||||||
''' % _FUNC_NAME_RE,
|
''' % (_FUNC_NAME_RE, _NAME_RE),
|
||||||
fields)
|
fields)
|
||||||
for f in fields_m:
|
for f in fields_m:
|
||||||
argnames = f.group('args').split(',')
|
argnames = f.group('args').split(',')
|
||||||
@ -580,7 +598,7 @@ class JSInterpreter:
|
|||||||
(?:
|
(?:
|
||||||
function\s+%(name)s|
|
function\s+%(name)s|
|
||||||
[{;,]\s*%(name)s\s*=\s*function|
|
[{;,]\s*%(name)s\s*=\s*function|
|
||||||
var\s+%(name)s\s*=\s*function
|
(?:var|const|let)\s+%(name)s\s*=\s*function
|
||||||
)\s*
|
)\s*
|
||||||
\((?P<args>[^)]*)\)\s*
|
\((?P<args>[^)]*)\)\s*
|
||||||
(?P<code>{.+})''' % {'name': re.escape(funcname)},
|
(?P<code>{.+})''' % {'name': re.escape(funcname)},
|
||||||
@ -615,10 +633,8 @@ class JSInterpreter:
|
|||||||
argnames = tuple(argnames)
|
argnames = tuple(argnames)
|
||||||
|
|
||||||
def resf(args, kwargs={}, allow_recursion=100):
|
def resf(args, kwargs={}, allow_recursion=100):
|
||||||
global_stack[0].update({
|
global_stack[0].update(itertools.zip_longest(argnames, args, fillvalue=None))
|
||||||
**dict(itertools.zip_longest(argnames, args, fillvalue=None)),
|
global_stack[0].update(kwargs)
|
||||||
**kwargs
|
|
||||||
})
|
|
||||||
var_stack = LocalNameSpace(*global_stack)
|
var_stack = LocalNameSpace(*global_stack)
|
||||||
ret, should_abort = self.interpret_statement(code.replace('\n', ''), var_stack, allow_recursion - 1)
|
ret, should_abort = self.interpret_statement(code.replace('\n', ''), var_stack, allow_recursion - 1)
|
||||||
if should_abort:
|
if should_abort:
|
||||||
|
Loading…
Reference in New Issue
Block a user