]> jfr.im git - yt-dlp.git/blobdiff - yt_dlp/utils.py
[cleanup] Misc (#5044)
[yt-dlp.git] / yt_dlp / utils.py
index f93573692282bea8da548374644bf23cda7a0f55..d0be7f19ef7189ef4e93e353c268374d912f52d8 100644 (file)
@@ -232,7 +232,7 @@ def random_user_agent():
 ])
 
 PACKED_CODES_RE = r"}\('(.+)',(\d+),(\d+),'([^']+)'\.split\('\|'\)"
-JSON_LD_RE = r'(?is)<script[^>]+type=(["\']?)application/ld\+json\1[^>]*>\s*(?P<json_ld>{.+?})\s*</script>'
+JSON_LD_RE = r'(?is)<script[^>]+type=(["\']?)application/ld\+json\1[^>]*>\s*(?P<json_ld>{.+?}|\[.+?\])\s*</script>'
 
 NUMBER_RE = r'\d+(?:\.\d+)?'
 
@@ -3180,6 +3180,10 @@ def multipart_encode(data, boundary=None):
     return out, content_type
 
 
+def variadic(x, allowed_types=(str, bytes, dict)):
+    return x if isinstance(x, collections.abc.Iterable) and not isinstance(x, allowed_types) else (x,)
+
+
 def dict_get(d, key_or_keys, default=None, skip_false_values=True):
     for val in map(d.get, variadic(key_or_keys)):
         if val is not None and (val or not skip_false_values):
@@ -3546,7 +3550,7 @@ def get_compatible_ext(*, vcodecs, acodecs, vexts, aexts, preferences=None):
     COMPATIBLE_CODECS = {
         'mp4': {
             'av1', 'hevc', 'avc1', 'mp4a',  # fourcc (m3u8, mpd)
-            'h264', 'aacl',  # Set in ISM
+            'h264', 'aacl', 'ec-3',  # Set in ISM
         },
         'webm': {
             'av1', 'vp9', 'vp8', 'opus', 'vrbs',
@@ -3793,6 +3797,9 @@ def __init__(self, chapters, ranges):
         self.chapters, self.ranges = chapters, ranges
 
     def __call__(self, info_dict, ydl):
+        if not self.ranges and not self.chapters:
+            yield {}
+
         warning = ('There are no chapters matching the regex' if info_dict.get('chapters')
                    else 'Cannot match chapters since chapter information is unavailable')
         for regex in self.chapters or []:
@@ -5443,10 +5450,6 @@ def get_first(obj, keys, **kwargs):
     return traverse_obj(obj, (..., *variadic(keys)), **kwargs, get_all=False)
 
 
-def variadic(x, allowed_types=(str, bytes, dict)):
-    return x if isinstance(x, collections.abc.Iterable) and not isinstance(x, allowed_types) else (x,)
-
-
 def time_seconds(**kwargs):
     t = datetime.datetime.now(datetime.timezone(datetime.timedelta(**kwargs)))
     return t.timestamp()
@@ -5481,7 +5484,7 @@ def jwt_decode_hs256(jwt):
 WINDOWS_VT_MODE = False if compat_os_name == 'nt' else None
 
 
-@ functools.cache
+@functools.cache
 def supports_terminal_sequences(stream):
     if compat_os_name == 'nt':
         if not WINDOWS_VT_MODE:
@@ -5631,7 +5634,7 @@ def __str__(self):
             *(f'\n{c}'.replace('\n', '\n| ')[1:] for c in self.configs),
             delim='\n')
 
-    @ staticmethod
+    @staticmethod
     def read_file(filename, default=[]):
         try:
             optionf = open(filename, 'rb')
@@ -5652,7 +5655,7 @@ def read_file(filename, default=[]):
             optionf.close()
         return res
 
-    @ staticmethod
+    @staticmethod
     def hide_login_info(opts):
         PRIVATE_OPTS = {'-p', '--password', '-u', '--username', '--video-password', '--ap-password', '--ap-username'}
         eqre = re.compile('^(?P<key>' + ('|'.join(re.escape(po) for po in PRIVATE_OPTS)) + ')=.+$')
@@ -5676,7 +5679,7 @@ def append_config(self, *args, label=None):
         if config.init(*args):
             self.configs.append(config)
 
-    @ property
+    @property
     def all_args(self):
         for config in reversed(self.configs):
             yield from config.all_args
@@ -5723,7 +5726,7 @@ def __exit__(self, type, value, traceback):
 
     # taken from https://github.com/python/cpython/blob/3.9/Lib/asyncio/runners.py with modifications
     # for contributors: If there's any new library using asyncio needs to be run in non-async, move these function out of this class
-    @ staticmethod
+    @staticmethod
     def run_with_loop(main, loop):
         if not asyncio.iscoroutine(main):
             raise ValueError(f'a coroutine was expected, got {main!r}')
@@ -5735,7 +5738,7 @@ def run_with_loop(main, loop):
             if hasattr(loop, 'shutdown_default_executor'):
                 loop.run_until_complete(loop.shutdown_default_executor())
 
-    @ staticmethod
+    @staticmethod
     def _cancel_all_tasks(loop):
         to_cancel = asyncio.all_tasks(loop)
 
@@ -5769,7 +5772,7 @@ def cached_method(f):
     """Cache a method"""
     signature = inspect.signature(f)
 
-    @ functools.wraps(f)
+    @functools.wraps(f)
     def wrapper(self, *args, **kwargs):
         bound_args = signature.bind(self, *args, **kwargs)
         bound_args.apply_defaults()
@@ -5801,7 +5804,7 @@ class Namespace(types.SimpleNamespace):
     def __iter__(self):
         return iter(self.__dict__.values())
 
-    @ property
+    @property
     def items_(self):
         return self.__dict__.items()
 
@@ -5840,13 +5843,13 @@ def __init__(self, _retries, _error_callback, **kwargs):
     def _should_retry(self):
         return self._error is not NO_DEFAULT and self.attempt <= self.retries
 
-    @ property
+    @property
     def error(self):
         if self._error is NO_DEFAULT:
             return None
         return self._error
 
-    @ error.setter
+    @error.setter
     def error(self, value):
         self._error = value
 
@@ -5858,7 +5861,7 @@ def __iter__(self):
             if self.error:
                 self.error_callback(self.error, self.attempt, self.retries)
 
-    @ staticmethod
+    @staticmethod
     def report_retry(e, count, retries, *, sleep_func, info, warn, error=None, suffix=None):
         """Utility function for reporting retries"""
         if count > retries: