mirror of https://github.com/yt-dlp/yt-dlp.git
Compare commits
4 Commits
03f0ac07c6
...
2e8ed344a7
Author | SHA1 | Date |
---|---|---|
bashonly | 2e8ed344a7 | |
Simon Sawicki | 64766459e3 | |
bashonly | d07eb72f6f | |
bashonly | 734d7bd4a3 |
|
@ -2059,7 +2059,22 @@ Line 1
|
|||
assert extract_basic_auth('http://user:pass@foo.bar') == ('http://foo.bar', 'Basic dXNlcjpwYXNz')
|
||||
|
||||
@unittest.skipUnless(compat_os_name == 'nt', 'Only relevant on Windows')
|
||||
def test_Popen_windows_escaping(self):
|
||||
def test_windows_escaping(self):
|
||||
tests = [
|
||||
'test"&',
|
||||
'%CMDCMDLINE:~-1%&',
|
||||
'a\nb',
|
||||
'"',
|
||||
'\\',
|
||||
'!',
|
||||
'^!',
|
||||
'a \\ b',
|
||||
'a \\" b',
|
||||
'a \\ b\\',
|
||||
# We replace \r with \n
|
||||
('a\r\ra', 'a\n\na'),
|
||||
]
|
||||
|
||||
def run_shell(args):
|
||||
stdout, stderr, error = Popen.run(
|
||||
args, text=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
@ -2067,15 +2082,18 @@ Line 1
|
|||
assert not error
|
||||
return stdout
|
||||
|
||||
# Test escaping
|
||||
assert run_shell(['echo', 'test"&']) == '"test""&"\n'
|
||||
assert run_shell(['echo', '%CMDCMDLINE:~-1%&']) == '"%CMDCMDLINE:~-1%&"\n'
|
||||
assert run_shell(['echo', 'a\nb']) == '"a"\n"b"\n'
|
||||
assert run_shell(['echo', '"']) == '""""\n'
|
||||
assert run_shell(['echo', '\\']) == '\\\n'
|
||||
# Test if delayed expansion is disabled
|
||||
assert run_shell(['echo', '^!']) == '"^!"\n'
|
||||
assert run_shell('echo "^!"') == '"^!"\n'
|
||||
for argument in tests:
|
||||
if isinstance(argument, str):
|
||||
expected = argument
|
||||
else:
|
||||
argument, expected = argument
|
||||
|
||||
args = [sys.executable, '-c', 'import sys; print(end=sys.argv[1])', argument, 'end']
|
||||
assert run_shell(args) == expected
|
||||
|
||||
escaped = shell_quote(argument, shell=True)
|
||||
args = f'{sys.executable} -c "import sys; print(end=sys.argv[1])" {escaped} end'
|
||||
assert run_shell(args) == expected
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -10,17 +10,19 @@ from ..networking.exceptions import HTTPError
|
|||
from ..utils import (
|
||||
ExtractorError,
|
||||
int_or_none,
|
||||
join_nonempty,
|
||||
parse_age_limit,
|
||||
parse_duration,
|
||||
traverse_obj,
|
||||
try_get,
|
||||
unified_timestamp,
|
||||
url_or_none,
|
||||
urljoin,
|
||||
)
|
||||
|
||||
|
||||
class FOXIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?fox(?:sports)?\.com/(?:watch|replay)/(?P<id>[\da-fA-F]+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?fox(?:sports)?\.com/(?:watch|replay)/(?!play-)(?P<id>[\w-]+)'
|
||||
_TESTS = [{
|
||||
# clip
|
||||
'url': 'https://www.fox.com/watch/4b765a60490325103ea69888fb2bd4e8/',
|
||||
|
@ -33,8 +35,9 @@ class FOXIE(InfoExtractor):
|
|||
'duration': 102,
|
||||
'timestamp': 1504291893,
|
||||
'upload_date': '20170901',
|
||||
'creator': 'FOX',
|
||||
'series': 'Gotham',
|
||||
'creators': ['FOX'],
|
||||
# actual series name 'Gotham' is no longer returned by the API
|
||||
'series': 'Aftermath: Bruce Wayne Develops Into The Dark Knight',
|
||||
'age_limit': 14,
|
||||
'episode': 'Aftermath: Bruce Wayne Develops Into The Dark Knight',
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
|
@ -42,6 +45,24 @@ class FOXIE(InfoExtractor):
|
|||
'params': {
|
||||
'skip_download': True,
|
||||
},
|
||||
}, {
|
||||
# XML endpoint
|
||||
'url': 'https://www.foxsports.com/watch/fmc-m2du80v5ewz11pbw',
|
||||
'md5': '5451a633a5ca87b582a4d025df6852e6',
|
||||
'info_dict': {
|
||||
'id': 'fmc-m2du80v5ewz11pbw',
|
||||
'ext': 'mp4',
|
||||
'title': 'WWE FRIDAY NIGHT SMACKDOWN',
|
||||
'description': 'From Fiserv Forum in Milwaukee, WI',
|
||||
'duration': 5367,
|
||||
'timestamp': 1698176671,
|
||||
'upload_date': '20231024',
|
||||
'creators': ['fox-digital'],
|
||||
'series': 'WWE FRIDAY NIGHT SMACKDOWN',
|
||||
'age_limit': 0,
|
||||
'episode': 'WWE FRIDAY NIGHT SMACKDOWN',
|
||||
'thumbnail': r're:^https?://.*\.jpg$',
|
||||
},
|
||||
}, {
|
||||
# episode, geo-restricted
|
||||
'url': 'https://www.fox.com/watch/087036ca7f33c8eb79b08152b4dd75c1/',
|
||||
|
@ -57,9 +78,13 @@ class FOXIE(InfoExtractor):
|
|||
}]
|
||||
_GEO_BYPASS = False
|
||||
_HOME_PAGE_URL = 'https://www.fox.com/'
|
||||
_API_KEY = '6E9S4bmcoNnZwVLOHywOv8PJEdu76cM9'
|
||||
_API_KEY = '6E9S4bmcoNnZwVLOHywOv8PJEdu76cM9' # sports: 'cf289e299efdfa39fb6316f259d1de93'
|
||||
_access_token = None
|
||||
_device_id = compat_str(uuid.uuid4())
|
||||
_XML_NS = {
|
||||
'vmap': 'http://www.iab.net/videosuite/vmap',
|
||||
'yospacenet': 'http://www.yospace.com/extension',
|
||||
}
|
||||
|
||||
def _call_api(self, path, video_id, data=None):
|
||||
headers = {
|
||||
|
@ -69,7 +94,7 @@ class FOXIE(InfoExtractor):
|
|||
headers['Authorization'] = 'Bearer ' + self._access_token
|
||||
try:
|
||||
return self._download_json(
|
||||
'https://api3.fox.com/v2.0/' + path,
|
||||
urljoin('https://api3.fox.com/v2.0/', path),
|
||||
video_id, data=data, headers=headers)
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
|
||||
|
@ -103,8 +128,8 @@ class FOXIE(InfoExtractor):
|
|||
'previewpassmvpd?device_id=%s&mvpd_id=TempPass_fbcfox_60min' % self._device_id,
|
||||
video_id)['accessToken']
|
||||
|
||||
video = self._call_api('watch', video_id, data=json.dumps({
|
||||
'capabilities': ['drm/widevine', 'fsdk/yo'],
|
||||
video = self._call_api('https://prod.api.video.fox/v2.0/watch', video_id, data=json.dumps({
|
||||
'capabilities': ['fsdk/yo/v3'],
|
||||
'deviceWidth': 1280,
|
||||
'deviceHeight': 720,
|
||||
'maxRes': '720p',
|
||||
|
@ -119,13 +144,16 @@ class FOXIE(InfoExtractor):
|
|||
'privacy': {'us': '1---'},
|
||||
'siteSection': '',
|
||||
'streamType': 'vod',
|
||||
'streamId': video_id}).encode('utf-8'))
|
||||
|
||||
title = video['name']
|
||||
release_url = video['url']
|
||||
'streamId': video_id,
|
||||
}).encode())
|
||||
|
||||
try:
|
||||
m3u8_url = self._download_json(release_url, video_id)['playURL']
|
||||
if playback_url := traverse_obj(video, ('playbackUrl', {url_or_none})):
|
||||
xml_data = self._download_xml(playback_url, video_id)
|
||||
stream = xml_data.find('vmap:Extensions/vmap:Extension/yospacenet:Stream', self._XML_NS)
|
||||
m3u8_url = join_nonempty('https://', stream.get('urlDomain'), stream.get('urlSuffix'), delim='')
|
||||
else:
|
||||
m3u8_url = self._download_json(video['url'], video_id)['playURL']
|
||||
except ExtractorError as e:
|
||||
if isinstance(e.cause, HTTPError) and e.cause.status == 403:
|
||||
error = self._parse_json(e.cause.response.read().decode(), video_id)
|
||||
|
@ -133,9 +161,11 @@ class FOXIE(InfoExtractor):
|
|||
self.raise_geo_restricted(countries=['US'])
|
||||
raise ExtractorError(error['description'], expected=True)
|
||||
raise
|
||||
formats = self._extract_m3u8_formats(
|
||||
m3u8_url, video_id, 'mp4',
|
||||
entry_protocol='m3u8_native', m3u8_id='hls')
|
||||
|
||||
if not m3u8_url or m3u8_url == 'https://':
|
||||
raise ExtractorError('Unable to extract m3u8 url')
|
||||
|
||||
formats = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', m3u8_id='hls')
|
||||
|
||||
data = try_get(
|
||||
video, lambda x: x['trackingData']['properties'], dict) or {}
|
||||
|
@ -160,7 +190,7 @@ class FOXIE(InfoExtractor):
|
|||
|
||||
return {
|
||||
'id': video_id,
|
||||
'title': title,
|
||||
'title': video.get('name'),
|
||||
'formats': formats,
|
||||
'description': video.get('description'),
|
||||
'duration': duration,
|
||||
|
|
|
@ -5,7 +5,7 @@ from ..utils import float_or_none, make_archive_id, smuggle_url
|
|||
|
||||
|
||||
class FoxSportsIE(InfoExtractor):
|
||||
_VALID_URL = r'https?://(?:www\.)?foxsports\.com/watch/(?P<id>[\w-]+)'
|
||||
_VALID_URL = r'https?://(?:www\.)?foxsports\.com/watch/(?P<id>play-[\w-]+)'
|
||||
_TESTS = [{
|
||||
'url': 'https://www.foxsports.com/watch/play-612168c6700004b',
|
||||
'info_dict': {
|
||||
|
|
|
@ -1638,16 +1638,14 @@ def get_filesystem_encoding():
|
|||
return encoding if encoding is not None else 'utf-8'
|
||||
|
||||
|
||||
_WINDOWS_QUOTE_TRANS = str.maketrans({'"': '\\"', '\\': '\\\\'})
|
||||
_WINDOWS_QUOTE_TRANS = str.maketrans({'"': R'\"'})
|
||||
_CMD_QUOTE_TRANS = str.maketrans({
|
||||
# Keep quotes balanced by replacing them with `""` instead of `\\"`
|
||||
'"': '""',
|
||||
# Requires a variable `=` containing `"^\n\n"` (set in `utils.Popen`)
|
||||
# These require an env-variable `=` containing `"^\n\n"` (set in `utils.Popen`)
|
||||
# `=` should be unique since variables containing `=` cannot be set using cmd
|
||||
'\n': '%=%',
|
||||
# While we are only required to escape backslashes immediately before quotes,
|
||||
# we instead escape all of 'em anyways to be consistent
|
||||
'\\': '\\\\',
|
||||
'\r': '%=%',
|
||||
# Use zero length variable replacement so `%` doesn't get expanded
|
||||
# `cd` is always set as long as extensions are enabled (`/E:ON` in `utils.Popen`)
|
||||
'%': '%%cd:~,%',
|
||||
|
@ -1656,19 +1654,14 @@ _CMD_QUOTE_TRANS = str.maketrans({
|
|||
|
||||
def shell_quote(args, *, shell=False):
|
||||
args = list(variadic(args))
|
||||
if any(isinstance(item, bytes) for item in args):
|
||||
deprecation_warning('Passing bytes to utils.shell_quote is deprecated')
|
||||
encoding = get_filesystem_encoding()
|
||||
for index, item in enumerate(args):
|
||||
if isinstance(item, bytes):
|
||||
args[index] = item.decode(encoding)
|
||||
|
||||
if compat_os_name != 'nt':
|
||||
return shlex.join(args)
|
||||
|
||||
trans = _CMD_QUOTE_TRANS if shell else _WINDOWS_QUOTE_TRANS
|
||||
return ' '.join(
|
||||
s if re.fullmatch(r'[\w#$*\-+./:?@\\]+', s, re.ASCII) else s.translate(trans).join('""')
|
||||
s if re.fullmatch(r'[\w#$*\-+./:?@\\]+', s, re.ASCII)
|
||||
else re.sub(r'(\\+)("|$)', r'\1\1\2', s).translate(trans).join('""')
|
||||
for s in args)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue