mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2024-11-29 10:41:23 +01:00
Compare commits
No commits in common. "90e62715b6acf5b78bf32050bad8062b305dcf57" and "261b2be36e917675a282f669de2cbeb52d87e579" have entirely different histories.
90e62715b6
...
261b2be36e
|
@ -53,18 +53,6 @@ class TestInfoExtractor(unittest.TestCase):
|
||||||
def test_ie_key(self):
|
def test_ie_key(self):
|
||||||
self.assertEqual(get_info_extractor(YoutubeIE.ie_key()), YoutubeIE)
|
self.assertEqual(get_info_extractor(YoutubeIE.ie_key()), YoutubeIE)
|
||||||
|
|
||||||
def test_get_netrc_login_info(self):
|
|
||||||
for params in [
|
|
||||||
{'usenetrc': True, 'netrc_location': './test/testdata/netrc/netrc'},
|
|
||||||
{'netrc_cmd': f'{sys.executable} ./test/testdata/netrc/print_netrc.py'},
|
|
||||||
]:
|
|
||||||
ie = DummyIE(FakeYDL(params))
|
|
||||||
self.assertEqual(ie._get_netrc_login_info(netrc_machine='normal_use'), ('user', 'pass'))
|
|
||||||
self.assertEqual(ie._get_netrc_login_info(netrc_machine='empty_user'), ('', 'pass'))
|
|
||||||
self.assertEqual(ie._get_netrc_login_info(netrc_machine='empty_pass'), ('user', ''))
|
|
||||||
self.assertEqual(ie._get_netrc_login_info(netrc_machine='both_empty'), ('', ''))
|
|
||||||
self.assertEqual(ie._get_netrc_login_info(netrc_machine='nonexistent'), (None, None))
|
|
||||||
|
|
||||||
def test_html_search_regex(self):
|
def test_html_search_regex(self):
|
||||||
html = '<p id="foo">Watch this <a href="http://www.youtube.com/watch?v=BaW_jenozKc">video</a></p>'
|
html = '<p id="foo">Watch this <a href="http://www.youtube.com/watch?v=BaW_jenozKc">video</a></p>'
|
||||||
search = lambda re, *args: self.ie._html_search_regex(re, html, *args)
|
search = lambda re, *args: self.ie._html_search_regex(re, html, *args)
|
||||||
|
|
|
@ -12,10 +12,9 @@ from yt_dlp.utils import (
|
||||||
str_or_none,
|
str_or_none,
|
||||||
)
|
)
|
||||||
from yt_dlp.utils.traversal import (
|
from yt_dlp.utils.traversal import (
|
||||||
|
traverse_obj,
|
||||||
require,
|
require,
|
||||||
subs_list_to_dict,
|
subs_list_to_dict,
|
||||||
traverse_obj,
|
|
||||||
trim_str,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_TEST_DATA = {
|
_TEST_DATA = {
|
||||||
|
@ -496,20 +495,6 @@ class TestTraversalHelpers:
|
||||||
{'url': 'https://example.com/subs/en2', 'ext': 'ext'},
|
{'url': 'https://example.com/subs/en2', 'ext': 'ext'},
|
||||||
]}, '`quality` key should sort subtitle list accordingly'
|
]}, '`quality` key should sort subtitle list accordingly'
|
||||||
|
|
||||||
def test_trim_str(self):
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
trim_str('positional')
|
|
||||||
|
|
||||||
assert callable(trim_str(start='a'))
|
|
||||||
assert trim_str(start='ab')('abc') == 'c'
|
|
||||||
assert trim_str(end='bc')('abc') == 'a'
|
|
||||||
assert trim_str(start='a', end='c')('abc') == 'b'
|
|
||||||
assert trim_str(start='ab', end='c')('abc') == ''
|
|
||||||
assert trim_str(start='a', end='bc')('abc') == ''
|
|
||||||
assert trim_str(start='ab', end='bc')('abc') == ''
|
|
||||||
assert trim_str(start='abc', end='abc')('abc') == ''
|
|
||||||
assert trim_str(start='', end='')('abc') == 'abc'
|
|
||||||
|
|
||||||
|
|
||||||
class TestDictGet:
|
class TestDictGet:
|
||||||
def test_dict_get(self):
|
def test_dict_get(self):
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
import unittest.mock
|
|
||||||
import warnings
|
import warnings
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
|
|
||||||
|
@ -72,7 +71,6 @@ from yt_dlp.utils import (
|
||||||
intlist_to_bytes,
|
intlist_to_bytes,
|
||||||
iri_to_uri,
|
iri_to_uri,
|
||||||
is_html,
|
is_html,
|
||||||
join_nonempty,
|
|
||||||
js_to_json,
|
js_to_json,
|
||||||
limit_length,
|
limit_length,
|
||||||
locked_file,
|
locked_file,
|
||||||
|
@ -345,13 +343,11 @@ class TestUtil(unittest.TestCase):
|
||||||
self.assertEqual(remove_start(None, 'A - '), None)
|
self.assertEqual(remove_start(None, 'A - '), None)
|
||||||
self.assertEqual(remove_start('A - B', 'A - '), 'B')
|
self.assertEqual(remove_start('A - B', 'A - '), 'B')
|
||||||
self.assertEqual(remove_start('B - A', 'A - '), 'B - A')
|
self.assertEqual(remove_start('B - A', 'A - '), 'B - A')
|
||||||
self.assertEqual(remove_start('non-empty', ''), 'non-empty')
|
|
||||||
|
|
||||||
def test_remove_end(self):
|
def test_remove_end(self):
|
||||||
self.assertEqual(remove_end(None, ' - B'), None)
|
self.assertEqual(remove_end(None, ' - B'), None)
|
||||||
self.assertEqual(remove_end('A - B', ' - B'), 'A')
|
self.assertEqual(remove_end('A - B', ' - B'), 'A')
|
||||||
self.assertEqual(remove_end('B - A', ' - B'), 'B - A')
|
self.assertEqual(remove_end('B - A', ' - B'), 'B - A')
|
||||||
self.assertEqual(remove_end('non-empty', ''), 'non-empty')
|
|
||||||
|
|
||||||
def test_remove_quotes(self):
|
def test_remove_quotes(self):
|
||||||
self.assertEqual(remove_quotes(None), None)
|
self.assertEqual(remove_quotes(None), None)
|
||||||
|
@ -2152,16 +2148,6 @@ Line 1
|
||||||
assert run_shell(args) == expected
|
assert run_shell(args) == expected
|
||||||
assert run_shell(shell_quote(args, shell=True)) == expected
|
assert run_shell(shell_quote(args, shell=True)) == expected
|
||||||
|
|
||||||
def test_partial_application(self):
|
|
||||||
assert callable(int_or_none(scale=10)), 'missing positional parameter should apply partially'
|
|
||||||
assert int_or_none(10, scale=0.1) == 100, 'positionally passed argument should call function'
|
|
||||||
assert int_or_none(v=10) == 10, 'keyword passed positional should call function'
|
|
||||||
assert int_or_none(scale=0.1)(10) == 100, 'call after partial applicatino should call the function'
|
|
||||||
|
|
||||||
assert callable(join_nonempty(delim=', ')), 'varargs positional should apply partially'
|
|
||||||
assert callable(join_nonempty()), 'varargs positional should apply partially'
|
|
||||||
assert join_nonempty(None, delim=', ') == '', 'passed varargs should call the function'
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
4
test/testdata/netrc/netrc
vendored
4
test/testdata/netrc/netrc
vendored
|
@ -1,4 +0,0 @@
|
||||||
machine normal_use login user password pass
|
|
||||||
machine empty_user login "" password pass
|
|
||||||
machine empty_pass login user password ""
|
|
||||||
machine both_empty login "" password ""
|
|
2
test/testdata/netrc/print_netrc.py
vendored
2
test/testdata/netrc/print_netrc.py
vendored
|
@ -1,2 +0,0 @@
|
||||||
with open('./test/testdata/netrc/netrc', encoding='utf-8') as fp:
|
|
||||||
print(fp.read())
|
|
|
@ -1409,13 +1409,6 @@ class InfoExtractor:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
self.write_debug(f'Using netrc for {netrc_machine} authentication')
|
self.write_debug(f'Using netrc for {netrc_machine} authentication')
|
||||||
|
|
||||||
# compat: <=py3.10: netrc cannot parse tokens as empty strings, will return `""` instead
|
|
||||||
# Ref: https://github.com/yt-dlp/yt-dlp/issues/11413
|
|
||||||
# https://github.com/python/cpython/commit/15409c720be0503131713e3d3abc1acd0da07378
|
|
||||||
if sys.version_info < (3, 11):
|
|
||||||
return tuple(x if x != '""' else '' for x in info[::2])
|
|
||||||
|
|
||||||
return info[0], info[2]
|
return info[0], info[2]
|
||||||
|
|
||||||
def _get_login_info(self, username_option='username', password_option='password', netrc_machine=None):
|
def _get_login_info(self, username_option='username', password_option='password', netrc_machine=None):
|
||||||
|
|
|
@ -150,6 +150,14 @@ class TwitterBaseIE(InfoExtractor):
|
||||||
def is_logged_in(self):
|
def is_logged_in(self):
|
||||||
return bool(self._get_cookies(self._API_BASE).get('auth_token'))
|
return bool(self._get_cookies(self._API_BASE).get('auth_token'))
|
||||||
|
|
||||||
|
# XXX: Temporary workaround until twitter.com => x.com migration is completed
|
||||||
|
def _real_initialize(self):
|
||||||
|
if self.is_logged_in or not self._get_cookies('https://twitter.com/').get('auth_token'):
|
||||||
|
return
|
||||||
|
# User has not yet been migrated to x.com and has passed twitter.com cookies
|
||||||
|
TwitterBaseIE._API_BASE = 'https://api.twitter.com/1.1/'
|
||||||
|
TwitterBaseIE._GRAPHQL_API_BASE = 'https://twitter.com/i/api/graphql/'
|
||||||
|
|
||||||
@functools.cached_property
|
@functools.cached_property
|
||||||
def _selected_api(self):
|
def _selected_api(self):
|
||||||
return self._configuration_arg('api', ['graphql'], ie_key='Twitter')[0]
|
return self._configuration_arg('api', ['graphql'], ie_key='Twitter')[0]
|
||||||
|
|
|
@ -644,14 +644,13 @@ class YoutubeBaseInfoExtractor(InfoExtractor):
|
||||||
YoutubeBaseInfoExtractor._OAUTH_ACCESS_TOKEN_CACHE[self._OAUTH_PROFILE] = {}
|
YoutubeBaseInfoExtractor._OAUTH_ACCESS_TOKEN_CACHE[self._OAUTH_PROFILE] = {}
|
||||||
|
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
msg = f'{self._OAUTH_DISPLAY_ID}: Using password input as refresh token'
|
refresh_token = refresh_token.strip('\'') or None
|
||||||
if self.get_param('cachedir') is not False:
|
|
||||||
msg += ' and caching token to disk; you should supply an empty password next time'
|
|
||||||
self.to_screen(msg)
|
|
||||||
self.cache.store(self._NETRC_MACHINE, self._oauth_cache_key, refresh_token)
|
|
||||||
else:
|
|
||||||
refresh_token = self.cache.load(self._NETRC_MACHINE, self._oauth_cache_key)
|
|
||||||
|
|
||||||
|
# Allow refresh token passed to initialize cache
|
||||||
|
if refresh_token:
|
||||||
|
self.cache.store(self._NETRC_MACHINE, self._oauth_cache_key, refresh_token)
|
||||||
|
|
||||||
|
refresh_token = refresh_token or self.cache.load(self._NETRC_MACHINE, self._oauth_cache_key)
|
||||||
if refresh_token:
|
if refresh_token:
|
||||||
YoutubeBaseInfoExtractor._OAUTH_ACCESS_TOKEN_CACHE[self._OAUTH_PROFILE]['refresh_token'] = refresh_token
|
YoutubeBaseInfoExtractor._OAUTH_ACCESS_TOKEN_CACHE[self._OAUTH_PROFILE]['refresh_token'] = refresh_token
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -212,23 +212,6 @@ def write_json_file(obj, fn):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def partial_application(func):
|
|
||||||
sig = inspect.signature(func)
|
|
||||||
required_args = [
|
|
||||||
param.name for param in sig.parameters.values()
|
|
||||||
if param.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.VAR_POSITIONAL)
|
|
||||||
if param.default is inspect.Parameter.empty
|
|
||||||
]
|
|
||||||
|
|
||||||
@functools.wraps(func)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
if set(required_args[len(args):]).difference(kwargs):
|
|
||||||
return functools.partial(func, *args, **kwargs)
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
|
|
||||||
return wrapped
|
|
||||||
|
|
||||||
|
|
||||||
def find_xpath_attr(node, xpath, key, val=None):
|
def find_xpath_attr(node, xpath, key, val=None):
|
||||||
""" Find the xpath xpath[@key=val] """
|
""" Find the xpath xpath[@key=val] """
|
||||||
assert re.match(r'^[a-zA-Z_-]+$', key)
|
assert re.match(r'^[a-zA-Z_-]+$', key)
|
||||||
|
@ -1209,7 +1192,6 @@ def extract_timezone(date_str, default=None):
|
||||||
return timezone, date_str
|
return timezone, date_str
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def parse_iso8601(date_str, delimiter='T', timezone=None):
|
def parse_iso8601(date_str, delimiter='T', timezone=None):
|
||||||
""" Return a UNIX timestamp from the given date """
|
""" Return a UNIX timestamp from the given date """
|
||||||
|
|
||||||
|
@ -1287,7 +1269,6 @@ def unified_timestamp(date_str, day_first=True):
|
||||||
return calendar.timegm(timetuple) + pm_delta * 3600 - timezone.total_seconds()
|
return calendar.timegm(timetuple) + pm_delta * 3600 - timezone.total_seconds()
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def determine_ext(url, default_ext='unknown_video'):
|
def determine_ext(url, default_ext='unknown_video'):
|
||||||
if url is None or '.' not in url:
|
if url is None or '.' not in url:
|
||||||
return default_ext
|
return default_ext
|
||||||
|
@ -1963,7 +1944,7 @@ def remove_start(s, start):
|
||||||
|
|
||||||
|
|
||||||
def remove_end(s, end):
|
def remove_end(s, end):
|
||||||
return s[:-len(end)] if s is not None and end and s.endswith(end) else s
|
return s[:-len(end)] if s is not None and s.endswith(end) else s
|
||||||
|
|
||||||
|
|
||||||
def remove_quotes(s):
|
def remove_quotes(s):
|
||||||
|
@ -1992,7 +1973,6 @@ def base_url(url):
|
||||||
return re.match(r'https?://[^?#]+/', url).group()
|
return re.match(r'https?://[^?#]+/', url).group()
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def urljoin(base, path):
|
def urljoin(base, path):
|
||||||
if isinstance(path, bytes):
|
if isinstance(path, bytes):
|
||||||
path = path.decode()
|
path = path.decode()
|
||||||
|
@ -2008,6 +1988,21 @@ def urljoin(base, path):
|
||||||
return urllib.parse.urljoin(base, path)
|
return urllib.parse.urljoin(base, path)
|
||||||
|
|
||||||
|
|
||||||
|
def partial_application(func):
|
||||||
|
sig = inspect.signature(func)
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
sig.bind(*args, **kwargs)
|
||||||
|
except TypeError:
|
||||||
|
return functools.partial(func, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
@partial_application
|
||||||
def int_or_none(v, scale=1, default=None, get_attr=None, invscale=1, base=None):
|
def int_or_none(v, scale=1, default=None, get_attr=None, invscale=1, base=None):
|
||||||
if get_attr and v is not None:
|
if get_attr and v is not None:
|
||||||
|
@ -2588,7 +2583,6 @@ def urlencode_postdata(*args, **kargs):
|
||||||
return urllib.parse.urlencode(*args, **kargs).encode('ascii')
|
return urllib.parse.urlencode(*args, **kargs).encode('ascii')
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def update_url(url, *, query_update=None, **kwargs):
|
def update_url(url, *, query_update=None, **kwargs):
|
||||||
"""Replace URL components specified by kwargs
|
"""Replace URL components specified by kwargs
|
||||||
@param url str or parse url tuple
|
@param url str or parse url tuple
|
||||||
|
@ -2609,7 +2603,6 @@ def update_url(url, *, query_update=None, **kwargs):
|
||||||
return urllib.parse.urlunparse(url._replace(**kwargs))
|
return urllib.parse.urlunparse(url._replace(**kwargs))
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def update_url_query(url, query):
|
def update_url_query(url, query):
|
||||||
return update_url(url, query_update=query)
|
return update_url(url, query_update=query)
|
||||||
|
|
||||||
|
@ -2931,7 +2924,6 @@ def error_to_str(err):
|
||||||
return f'{type(err).__name__}: {err}'
|
return f'{type(err).__name__}: {err}'
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def mimetype2ext(mt, default=NO_DEFAULT):
|
def mimetype2ext(mt, default=NO_DEFAULT):
|
||||||
if not isinstance(mt, str):
|
if not isinstance(mt, str):
|
||||||
if default is not NO_DEFAULT:
|
if default is not NO_DEFAULT:
|
||||||
|
@ -4672,7 +4664,6 @@ def to_high_limit_path(path):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def format_field(obj, field=None, template='%s', ignore=NO_DEFAULT, default='', func=IDENTITY):
|
def format_field(obj, field=None, template='%s', ignore=NO_DEFAULT, default='', func=IDENTITY):
|
||||||
val = traversal.traverse_obj(obj, *variadic(field))
|
val = traversal.traverse_obj(obj, *variadic(field))
|
||||||
if not val if ignore is NO_DEFAULT else val in variadic(ignore):
|
if not val if ignore is NO_DEFAULT else val in variadic(ignore):
|
||||||
|
@ -4837,7 +4828,6 @@ def number_of_digits(number):
|
||||||
return len('%d' % number)
|
return len('%d' % number)
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def join_nonempty(*values, delim='-', from_dict=None):
|
def join_nonempty(*values, delim='-', from_dict=None):
|
||||||
if from_dict is not None:
|
if from_dict is not None:
|
||||||
values = (traversal.traverse_obj(from_dict, variadic(v)) for v in values)
|
values = (traversal.traverse_obj(from_dict, variadic(v)) for v in values)
|
||||||
|
@ -5288,7 +5278,6 @@ class RetryManager:
|
||||||
time.sleep(delay)
|
time.sleep(delay)
|
||||||
|
|
||||||
|
|
||||||
@partial_application
|
|
||||||
def make_archive_id(ie, video_id):
|
def make_archive_id(ie, video_id):
|
||||||
ie_key = ie if isinstance(ie, str) else ie.ie_key()
|
ie_key = ie if isinstance(ie, str) else ie.ie_key()
|
||||||
return f'{ie_key.lower()} {video_id}'
|
return f'{ie_key.lower()} {video_id}'
|
||||||
|
|
|
@ -435,20 +435,6 @@ def find_elements(*, tag=None, cls=None, attr=None, value=None, html=False):
|
||||||
return functools.partial(func, cls)
|
return functools.partial(func, cls)
|
||||||
|
|
||||||
|
|
||||||
def trim_str(*, start=None, end=None):
|
|
||||||
def trim(s):
|
|
||||||
if s is None:
|
|
||||||
return None
|
|
||||||
start_idx = 0
|
|
||||||
if start and s.startswith(start):
|
|
||||||
start_idx = len(start)
|
|
||||||
if end and s.endswith(end):
|
|
||||||
return s[start_idx:-len(end)]
|
|
||||||
return s[start_idx:]
|
|
||||||
|
|
||||||
return trim
|
|
||||||
|
|
||||||
|
|
||||||
def get_first(obj, *paths, **kwargs):
|
def get_first(obj, *paths, **kwargs):
|
||||||
return traverse_obj(obj, *((..., *variadic(keys)) for keys in paths), **kwargs, get_all=False)
|
return traverse_obj(obj, *((..., *variadic(keys)) for keys in paths), **kwargs, get_all=False)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user