Update to ytdl-commit-ed5c44e7
[compat] Replace deficient ChainMap class in Py3.3 and earlier
ed5c44e7b7
This commit is contained in:
parent
05deb747bb
commit
1ac7f46184
4 changed files with 92 additions and 44 deletions
|
@ -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/b0a60ce](https://github.com/ytdl-org/youtube-dl/commit/b0a60ce2032172aeaaf27fe3866ab72768f10cb2)**<!--([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/ed5c44e](https://github.com/ytdl-org/youtube-dl/commit/ed5c44e7b74ac77f87ca5ed6cb5e964a0c6a0678)**<!--([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
|
||||||
|
|
||||||
|
|
|
@ -71,6 +71,9 @@ class TestJSInterpreter(unittest.TestCase):
|
||||||
jsi = JSInterpreter('function f(){return 0 ?? 42;}')
|
jsi = JSInterpreter('function f(){return 0 ?? 42;}')
|
||||||
self.assertEqual(jsi.call_function('f'), 0)
|
self.assertEqual(jsi.call_function('f'), 0)
|
||||||
|
|
||||||
|
jsi = JSInterpreter('function f(){return "life, the universe and everything" < 42;}')
|
||||||
|
self.assertFalse(jsi.call_function('f'))
|
||||||
|
|
||||||
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])
|
||||||
|
@ -193,6 +196,30 @@ class TestJSInterpreter(unittest.TestCase):
|
||||||
''')
|
''')
|
||||||
self.assertEqual(jsi.call_function('x'), 10)
|
self.assertEqual(jsi.call_function('x'), 10)
|
||||||
|
|
||||||
|
def test_catch(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() { try{throw 10} catch(e){return 5} }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 5)
|
||||||
|
|
||||||
|
def test_finally(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() { try{throw 10} finally {return 42} }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 42)
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() { try{throw 10} catch(e){return 5} finally {return 42} }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 42)
|
||||||
|
|
||||||
|
def test_nested_try(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() {try {
|
||||||
|
try{throw 10} finally {throw 42}
|
||||||
|
} catch(e){return 5} }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 5)
|
||||||
|
|
||||||
def test_for_loop_continue(self):
|
def test_for_loop_continue(self):
|
||||||
jsi = JSInterpreter('''
|
jsi = JSInterpreter('''
|
||||||
function x() { a=0; for (i=0; i-10; i++) { continue; a++ } return a }
|
function x() { a=0; for (i=0; i-10; i++) { continue; a++ } return a }
|
||||||
|
@ -205,6 +232,14 @@ class TestJSInterpreter(unittest.TestCase):
|
||||||
''')
|
''')
|
||||||
self.assertEqual(jsi.call_function('x'), 0)
|
self.assertEqual(jsi.call_function('x'), 0)
|
||||||
|
|
||||||
|
def test_for_loop_try(self):
|
||||||
|
jsi = JSInterpreter('''
|
||||||
|
function x() {
|
||||||
|
for (i=0; i-10; i++) { try { if (i == 5) throw i} catch {return 10} finally {break} };
|
||||||
|
return 42 }
|
||||||
|
''')
|
||||||
|
self.assertEqual(jsi.call_function('x'), 42)
|
||||||
|
|
||||||
def test_literal_list(self):
|
def test_literal_list(self):
|
||||||
jsi = JSInterpreter('''
|
jsi = JSInterpreter('''
|
||||||
function x() { return [1, 2, "asdf", [5, 6, 7]][3] }
|
function x() { return [1, 2, "asdf", [5, 6, 7]][3] }
|
||||||
|
|
|
@ -2528,9 +2528,6 @@ class YoutubeDL:
|
||||||
'--live-from-start is passed, but there are no formats that can be downloaded from the start. '
|
'--live-from-start is passed, but there are no formats that can be downloaded from the start. '
|
||||||
'If you want to download from the current time, use --no-live-from-start'))
|
'If you want to download from the current time, use --no-live-from-start'))
|
||||||
|
|
||||||
if not formats:
|
|
||||||
self.raise_no_formats(info_dict)
|
|
||||||
|
|
||||||
def is_wellformed(f):
|
def is_wellformed(f):
|
||||||
url = f.get('url')
|
url = f.get('url')
|
||||||
if not url:
|
if not url:
|
||||||
|
@ -2543,7 +2540,10 @@ class YoutubeDL:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Filter out malformed formats for better extraction robustness
|
# Filter out malformed formats for better extraction robustness
|
||||||
formats = list(filter(is_wellformed, formats))
|
formats = list(filter(is_wellformed, formats or []))
|
||||||
|
|
||||||
|
if not formats:
|
||||||
|
self.raise_no_formats(info_dict)
|
||||||
|
|
||||||
formats_dict = {}
|
formats_dict = {}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,8 @@ def _js_comp_op(op):
|
||||||
def wrapped(a, b):
|
def wrapped(a, b):
|
||||||
if JS_Undefined in (a, b):
|
if JS_Undefined in (a, b):
|
||||||
return False
|
return False
|
||||||
|
if isinstance(a, str) or isinstance(b, str):
|
||||||
|
return op(str(a or 0), str(b or 0))
|
||||||
return op(a or 0, b or 0)
|
return op(a or 0, b or 0)
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
@ -268,7 +270,9 @@ class JSInterpreter:
|
||||||
yield expr[start:]
|
yield expr[start:]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _separate_at_paren(cls, expr, delim):
|
def _separate_at_paren(cls, expr, delim=None):
|
||||||
|
if delim is None:
|
||||||
|
delim = expr and _MATCHING_PARENS[expr[0]]
|
||||||
separated = list(cls._separate(expr, delim, 1))
|
separated = list(cls._separate(expr, delim, 1))
|
||||||
if len(separated) < 2:
|
if len(separated) < 2:
|
||||||
raise cls.Exception(f'No terminating paren {delim}', expr)
|
raise cls.Exception(f'No terminating paren {delim}', expr)
|
||||||
|
@ -347,7 +351,7 @@ class JSInterpreter:
|
||||||
if expr.startswith('new '):
|
if expr.startswith('new '):
|
||||||
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(
|
expr = unified_timestamp(
|
||||||
self.interpret_expression(left, local_vars, allow_recursion), False)
|
self.interpret_expression(left, local_vars, allow_recursion), False)
|
||||||
if not expr:
|
if not expr:
|
||||||
|
@ -361,8 +365,8 @@ class JSInterpreter:
|
||||||
return None, should_return
|
return None, should_return
|
||||||
|
|
||||||
if expr.startswith('{'):
|
if expr.startswith('{'):
|
||||||
inner, outer = self._separate_at_paren(expr, '}')
|
inner, outer = self._separate_at_paren(expr)
|
||||||
# Look for Map first
|
# try for object expression (Map)
|
||||||
sub_expressions = [list(self._separate(sub_expr.strip(), ':', 1)) for sub_expr in self._separate(inner)]
|
sub_expressions = [list(self._separate(sub_expr.strip(), ':', 1)) for sub_expr in self._separate(inner)]
|
||||||
if all(len(sub_expr) == 2 for sub_expr in sub_expressions):
|
if all(len(sub_expr) == 2 for sub_expr in sub_expressions):
|
||||||
def dict_item(key, val):
|
def dict_item(key, val):
|
||||||
|
@ -380,7 +384,7 @@ class JSInterpreter:
|
||||||
expr = self._dump(inner, local_vars) + outer
|
expr = self._dump(inner, local_vars) + outer
|
||||||
|
|
||||||
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)
|
||||||
if not outer or should_abort:
|
if not outer or should_abort:
|
||||||
return inner, should_abort or should_return
|
return inner, should_abort or should_return
|
||||||
|
@ -388,53 +392,62 @@ class JSInterpreter:
|
||||||
expr = self._dump(inner, local_vars) + outer
|
expr = self._dump(inner, local_vars) + outer
|
||||||
|
|
||||||
if expr.startswith('['):
|
if expr.startswith('['):
|
||||||
inner, outer = self._separate_at_paren(expr, ']')
|
inner, outer = self._separate_at_paren(expr)
|
||||||
name = self._named_object(local_vars, [
|
name = self._named_object(local_vars, [
|
||||||
self.interpret_expression(item, local_vars, allow_recursion)
|
self.interpret_expression(item, local_vars, allow_recursion)
|
||||||
for item in self._separate(inner)])
|
for item in self._separate(inner)])
|
||||||
expr = name + outer
|
expr = name + outer
|
||||||
|
|
||||||
m = re.match(rf'''(?x)
|
m = re.match(r'''(?x)
|
||||||
(?P<try>try|finally)\s*|
|
(?P<try>try)\s*\{|
|
||||||
(?P<catch>catch\s*(?P<err>\(\s*{_NAME_RE}\s*\)))|
|
|
||||||
(?P<switch>switch)\s*\(|
|
(?P<switch>switch)\s*\(|
|
||||||
(?P<for>for)\s*\(|''', expr)
|
(?P<for>for)\s*\(
|
||||||
if m and m.group('try'):
|
''', expr)
|
||||||
if expr[m.end()] == '{':
|
md = m.groupdict() if m else {}
|
||||||
try_expr, expr = self._separate_at_paren(expr[m.end():], '}')
|
if md.get('try'):
|
||||||
else:
|
try_expr, expr = self._separate_at_paren(expr[m.end() - 1:])
|
||||||
try_expr, expr = expr[m.end() - 1:], ''
|
err = None
|
||||||
try:
|
try:
|
||||||
ret, should_abort = self.interpret_statement(try_expr, local_vars, allow_recursion)
|
ret, should_abort = self.interpret_statement(try_expr, local_vars, allow_recursion)
|
||||||
if should_abort:
|
if should_abort:
|
||||||
return ret, True
|
return ret, True
|
||||||
except JS_Throw as e:
|
|
||||||
local_vars[self._EXC_NAME] = e.error
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# XXX: This works for now, but makes debugging future issues very hard
|
# XXX: This works for now, but makes debugging future issues very hard
|
||||||
local_vars[self._EXC_NAME] = e
|
err = e
|
||||||
ret, should_abort = self.interpret_statement(expr, local_vars, allow_recursion)
|
|
||||||
return ret, should_abort or should_return
|
|
||||||
|
|
||||||
elif m and m.group('catch'):
|
pending = (None, False)
|
||||||
catch_expr, expr = self._separate_at_paren(expr[m.end():], '}')
|
m = re.match(r'catch\s*(?P<err>\(\s*{_NAME_RE}\s*\))?\{{'.format(**globals()), expr)
|
||||||
if self._EXC_NAME in local_vars:
|
if m:
|
||||||
catch_vars = local_vars.new_child({m.group('err'): local_vars.pop(self._EXC_NAME)})
|
sub_expr, expr = self._separate_at_paren(expr[m.end() - 1:])
|
||||||
ret, should_abort = self.interpret_statement(catch_expr, catch_vars, allow_recursion)
|
if err:
|
||||||
|
catch_vars = {}
|
||||||
|
if m.group('err'):
|
||||||
|
catch_vars[m.group('err')] = err.error if isinstance(err, JS_Throw) else err
|
||||||
|
catch_vars = local_vars.new_child(catch_vars)
|
||||||
|
err, pending = None, self.interpret_statement(sub_expr, catch_vars, allow_recursion)
|
||||||
|
|
||||||
|
m = re.match(r'finally\s*\{', expr)
|
||||||
|
if m:
|
||||||
|
sub_expr, expr = self._separate_at_paren(expr[m.end() - 1:])
|
||||||
|
ret, should_abort = self.interpret_statement(sub_expr, local_vars, allow_recursion)
|
||||||
if should_abort:
|
if should_abort:
|
||||||
return ret, True
|
return ret, True
|
||||||
|
|
||||||
ret, should_abort = self.interpret_statement(expr, local_vars, allow_recursion)
|
ret, should_abort = pending
|
||||||
return ret, should_abort or should_return
|
if should_abort:
|
||||||
|
return ret, True
|
||||||
|
|
||||||
elif m and m.group('for'):
|
if err:
|
||||||
constructor, remaining = self._separate_at_paren(expr[m.end() - 1:], ')')
|
raise err
|
||||||
|
|
||||||
|
elif md.get('for'):
|
||||||
|
constructor, remaining = self._separate_at_paren(expr[m.end() - 1:])
|
||||||
if remaining.startswith('{'):
|
if remaining.startswith('{'):
|
||||||
body, expr = self._separate_at_paren(remaining, '}')
|
body, expr = self._separate_at_paren(remaining)
|
||||||
else:
|
else:
|
||||||
switch_m = re.match(r'switch\s*\(', remaining) # FIXME
|
switch_m = re.match(r'switch\s*\(', remaining) # FIXME
|
||||||
if switch_m:
|
if switch_m:
|
||||||
switch_val, remaining = self._separate_at_paren(remaining[switch_m.end() - 1:], ')')
|
switch_val, remaining = self._separate_at_paren(remaining[switch_m.end() - 1:])
|
||||||
body, expr = self._separate_at_paren(remaining, '}')
|
body, expr = self._separate_at_paren(remaining, '}')
|
||||||
body = 'switch(%s){%s}' % (switch_val, body)
|
body = 'switch(%s){%s}' % (switch_val, body)
|
||||||
else:
|
else:
|
||||||
|
@ -453,11 +466,9 @@ class JSInterpreter:
|
||||||
except JS_Continue:
|
except JS_Continue:
|
||||||
pass
|
pass
|
||||||
self.interpret_expression(increment, local_vars, allow_recursion)
|
self.interpret_expression(increment, local_vars, allow_recursion)
|
||||||
ret, should_abort = self.interpret_statement(expr, local_vars, allow_recursion)
|
|
||||||
return ret, should_abort or should_return
|
|
||||||
|
|
||||||
elif m and m.group('switch'):
|
elif md.get('switch'):
|
||||||
switch_val, remaining = self._separate_at_paren(expr[m.end() - 1:], ')')
|
switch_val, remaining = self._separate_at_paren(expr[m.end() - 1:])
|
||||||
switch_val = self.interpret_expression(switch_val, local_vars, allow_recursion)
|
switch_val = self.interpret_expression(switch_val, local_vars, allow_recursion)
|
||||||
body, expr = self._separate_at_paren(remaining, '}')
|
body, expr = self._separate_at_paren(remaining, '}')
|
||||||
items = body.replace('default:', 'case default:').split('case ')[1:]
|
items = body.replace('default:', 'case default:').split('case ')[1:]
|
||||||
|
@ -480,6 +491,8 @@ class JSInterpreter:
|
||||||
break
|
break
|
||||||
if matched:
|
if matched:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if md:
|
||||||
ret, should_abort = self.interpret_statement(expr, local_vars, allow_recursion)
|
ret, should_abort = self.interpret_statement(expr, local_vars, allow_recursion)
|
||||||
return ret, should_abort or should_return
|
return ret, should_abort or should_return
|
||||||
|
|
||||||
|
@ -584,7 +597,7 @@ class JSInterpreter:
|
||||||
member = self.interpret_expression(m.group('member2'), local_vars, allow_recursion)
|
member = self.interpret_expression(m.group('member2'), local_vars, allow_recursion)
|
||||||
arg_str = expr[m.end():]
|
arg_str = expr[m.end():]
|
||||||
if arg_str.startswith('('):
|
if arg_str.startswith('('):
|
||||||
arg_str, remaining = self._separate_at_paren(arg_str, ')')
|
arg_str, remaining = self._separate_at_paren(arg_str)
|
||||||
else:
|
else:
|
||||||
arg_str, remaining = None, arg_str
|
arg_str, remaining = None, arg_str
|
||||||
|
|
||||||
|
@ -769,7 +782,7 @@ class JSInterpreter:
|
||||||
\((?P<args>[^)]*)\)\s*
|
\((?P<args>[^)]*)\)\s*
|
||||||
(?P<code>{.+})''' % {'name': re.escape(funcname)},
|
(?P<code>{.+})''' % {'name': re.escape(funcname)},
|
||||||
self.code)
|
self.code)
|
||||||
code, _ = self._separate_at_paren(func_m.group('code'), '}')
|
code, _ = self._separate_at_paren(func_m.group('code'))
|
||||||
if func_m is None:
|
if func_m is None:
|
||||||
raise self.Exception(f'Could not find JS function "{funcname}"')
|
raise self.Exception(f'Could not find JS function "{funcname}"')
|
||||||
return [x.strip() for x in func_m.group('args').split(',')], code
|
return [x.strip() for x in func_m.group('args').split(',')], code
|
||||||
|
@ -784,7 +797,7 @@ class JSInterpreter:
|
||||||
if mobj is None:
|
if mobj is None:
|
||||||
break
|
break
|
||||||
start, body_start = mobj.span()
|
start, body_start = mobj.span()
|
||||||
body, remaining = self._separate_at_paren(code[body_start - 1:], '}')
|
body, remaining = self._separate_at_paren(code[body_start - 1:])
|
||||||
name = self._named_object(local_vars, self.extract_function_from_code(
|
name = self._named_object(local_vars, self.extract_function_from_code(
|
||||||
[x.strip() for x in mobj.group('args').split(',')],
|
[x.strip() for x in mobj.group('args').split(',')],
|
||||||
body, local_vars, *global_stack))
|
body, local_vars, *global_stack))
|
||||||
|
|
Loading…
Reference in a new issue