diff --git a/test/test_jsinterp.py b/test/test_jsinterp.py index 863e52458..778607fb2 100644 --- a/test/test_jsinterp.py +++ b/test/test_jsinterp.py @@ -129,6 +129,11 @@ def test_precedence(self): self.assertEqual(jsi.call_function('x'), [20, 20, 30, 40, 50]) def test_builtins(self): + jsi = JSInterpreter(''' + function x() { return NaN } + ''') + self.assertTrue(math.isnan(jsi.call_function('x'))) + jsi = JSInterpreter(''' function x() { return new Date('Wednesday 31 December 1969 18:01:26 MDT') - 0; } ''') diff --git a/yt_dlp/cache.py b/yt_dlp/cache.py index 602cb9edb..4f9fb78d3 100644 --- a/yt_dlp/cache.py +++ b/yt_dlp/cache.py @@ -51,15 +51,15 @@ def store(self, section, key, data, dtype='json'): tb = traceback.format_exc() self._ydl.report_warning(f'Writing cache to {fn!r} failed: {tb}') - def _validate(self, data, after): + def _validate(self, data, min_ver): version = traverse_obj(data, 'yt-dlp_version') if not version: # Backward compatibility data, version = {'data': data}, '2022.08.19' - if not after or version_tuple(version) > version_tuple(after): + if not min_ver or version_tuple(version) >= version_tuple(min_ver): return data['data'] - self._ydl.write_debug(f'Discarding old cache from version {version} (need {after})') + self._ydl.write_debug(f'Discarding old cache from version {version} (needs {min_ver})') - def load(self, section, key, dtype='json', default=None, *, after=None): + def load(self, section, key, dtype='json', default=None, *, min_ver=None): assert dtype in ('json',) if not self.enabled: @@ -70,7 +70,7 @@ def load(self, section, key, dtype='json', default=None, *, after=None): try: with open(cache_fn, encoding='utf-8') as cachef: self._ydl.write_debug(f'Loading {section}.{key} from cache') - return self._validate(json.load(cachef), after) + return self._validate(json.load(cachef), min_ver) except (ValueError, KeyError): try: file_size = os.path.getsize(cache_fn) diff --git a/yt_dlp/extractor/openload.py b/yt_dlp/extractor/openload.py index 4bba7bdd0..d2756a006 100644 --- a/yt_dlp/extractor/openload.py +++ b/yt_dlp/extractor/openload.py @@ -52,6 +52,8 @@ class PhantomJSwrapper: This class is experimental. """ + INSTALL_HINT = 'Please download it from https://phantomjs.org/download.html' + _BASE_JS = R''' phantom.onError = function(msg, trace) {{ var msgStack = ['PHANTOM ERROR: ' + msg]; @@ -110,8 +112,7 @@ def __init__(self, extractor, required_version=None, timeout=10000): self.exe = check_executable('phantomjs', ['-v']) if not self.exe: - raise ExtractorError( - 'PhantomJS not found, Please download it from https://phantomjs.org/download.html', expected=True) + raise ExtractorError(f'PhantomJS not found, {self.INSTALL_HINT}', expected=True) self.extractor = extractor @@ -237,6 +238,6 @@ def execute(self, jscode, video_id=None, *, note='Executing JS'): except Exception as e: raise ExtractorError(f'{note} failed: Unable to run PhantomJS binary', cause=e) if returncode: - raise ExtractorError(f'{note} failed:\n{stderr.strip()}') + raise ExtractorError(f'{note} failed with returncode {returncode}:\n{stderr.strip()}') return stdout diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py index b30dadf9f..0498f980d 100644 --- a/yt_dlp/extractor/youtube.py +++ b/yt_dlp/extractor/youtube.py @@ -2670,7 +2670,7 @@ def _extract_n_function_name(self, jscode): def _extract_n_function_code(self, video_id, player_url): player_id = self._extract_player_info(player_url) - func_code = self.cache.load('youtube-nsig', player_id, after='2022.08.19.1') + func_code = self.cache.load('youtube-nsig', player_id, min_ver='2022.08.19.2') jscode = func_code or self._load_player(video_id, player_url) jsi = JSInterpreter(jscode) @@ -3282,7 +3282,8 @@ def _extract_formats_and_subtitles(self, streaming_data, video_id, player_url, i except ExtractorError as e: phantomjs_hint = '' if isinstance(e, JSInterpreter.Exception): - phantomjs_hint = f' Install {self._downloader._format_err("PhantomJS", self._downloader.Styles.EMPHASIS)} to workaround the issue\n' + phantomjs_hint = (f' Install {self._downloader._format_err("PhantomJS", self._downloader.Styles.EMPHASIS)} ' + f'to workaround the issue. {PhantomJSwrapper.INSTALL_HINT}\n') self.report_warning( f'nsig extraction failed: You may experience throttling for some formats\n{phantomjs_hint}' f' n = {query["n"][0]} ; player = {player_url}', video_id=video_id, only_once=True) diff --git a/yt_dlp/jsinterp.py b/yt_dlp/jsinterp.py index cadb013a3..99bdca927 100644 --- a/yt_dlp/jsinterp.py +++ b/yt_dlp/jsinterp.py @@ -172,7 +172,14 @@ 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) - ret, should_ret = f(self, stmt, local_vars, allow_recursion, *args, **kwargs) + 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(): cls.write(['->', '=>'][should_ret], repr(ret), '<-|', stmt, level=allow_recursion) return ret, should_ret @@ -226,7 +233,7 @@ def _regex_flags(cls, expr): @staticmethod def _separate(expr, delim=',', max_split=None): - OP_CHARS = '+-*/%&|^=<>!,;{}()[]:' + OP_CHARS = '+-*/%&|^=<>!,;{}:' if not expr: return counters = {k: 0 for k in _MATCHING_PARENS.values()} @@ -504,7 +511,7 @@ def dict_item(key, val): (?P{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})? =(?!=)(?P.*)$ )|(?P - (?!if|return|true|false|null|undefined)(?P{_NAME_RE})$ + (?!if|return|true|false|null|undefined|NaN)(?P{_NAME_RE})$ )|(?P (?P{_NAME_RE})\[(?P.+)\]$ )|(?P @@ -539,6 +546,8 @@ def dict_item(key, val): raise JS_Continue() elif expr == 'undefined': return JS_Undefined, should_return + elif expr == 'NaN': + return float('NaN'), should_return elif m and m.group('return'): return local_vars.get(m.group('name'), JS_Undefined), should_return @@ -784,7 +793,7 @@ def resf(args, kwargs={}, allow_recursion=100): global_stack[0].update(itertools.zip_longest(argnames, args, fillvalue=None)) global_stack[0].update(kwargs) 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: return ret return resf