[ie/soundcloud] Add `formats` extractor-arg (#10004)

Authored by: bashonly
pull/7631/merge
bashonly 7 months ago committed by GitHub
parent eef1e9f44f
commit beaf832c7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1841,6 +1841,9 @@ The following extractors use this feature:
#### afreecatvlive #### afreecatvlive
* `cdn`: One or more CDN IDs to use with the API call for stream URLs, e.g. `gcp_cdn`, `gs_cdn_pc_app`, `gs_cdn_mobile_web`, `gs_cdn_pc_web` * `cdn`: One or more CDN IDs to use with the API call for stream URLs, e.g. `gcp_cdn`, `gs_cdn_pc_app`, `gs_cdn_mobile_web`, `gs_cdn_pc_web`
#### soundcloud
* `formats`: Formats to request from the API. Requested values should be in the format of `{protocol}_{extension}` (omitting the bitrate), e.g. `hls_opus,http_aac`. The `*` character functions as a wildcard, e.g. `*_mp3`, and can passed by itself to request all formats. Known protocols include `http`, `hls` and `hls-aes`; known extensions include `aac`, `opus` and `mp3`. Original `download` formats are always extracted. Default is `http_aac,hls_aac,http_opus,hls_opus,http_mp3,hls_mp3`
**Note**: These options may be changed/removed in the future without concern for backward compatibility **Note**: These options may be changed/removed in the future without concern for backward compatibility
<!-- MANPAGE: MOVE "INSTALLATION" SECTION HERE --> <!-- MANPAGE: MOVE "INSTALLATION" SECTION HERE -->

@ -1,3 +1,4 @@
import functools
import itertools import itertools
import json import json
import re import re
@ -12,6 +13,7 @@ from ..utils import (
error_to_compat_str, error_to_compat_str,
float_or_none, float_or_none,
int_or_none, int_or_none,
join_nonempty,
mimetype2ext, mimetype2ext,
parse_qs, parse_qs,
str_or_none, str_or_none,
@ -68,6 +70,16 @@ class SoundcloudBaseIE(InfoExtractor):
'original': 0, 'original': 0,
} }
_DEFAULT_FORMATS = ['http_aac', 'hls_aac', 'http_opus', 'hls_opus', 'http_mp3', 'hls_mp3']
@functools.cached_property
def _is_requested(self):
return re.compile(r'|'.join(set(
re.escape(pattern).replace(r'\*', r'.*') if pattern != 'default'
else '|'.join(map(re.escape, self._DEFAULT_FORMATS))
for pattern in self._configuration_arg('formats', ['default'], ie_key=SoundcloudIE)
))).fullmatch
def _store_client_id(self, client_id): def _store_client_id(self, client_id):
self.cache.store('soundcloud', 'client_id', client_id) self.cache.store('soundcloud', 'client_id', client_id)
@ -216,7 +228,7 @@ class SoundcloudBaseIE(InfoExtractor):
redirect_url = (self._download_json(download_url, track_id, fatal=False) or {}).get('redirectUri') redirect_url = (self._download_json(download_url, track_id, fatal=False) or {}).get('redirectUri')
if redirect_url: if redirect_url:
urlh = self._request_webpage( urlh = self._request_webpage(
HEADRequest(redirect_url), track_id, fatal=False) HEADRequest(redirect_url), track_id, 'Checking for original download format', fatal=False)
if urlh: if urlh:
format_url = urlh.url format_url = urlh.url
format_urls.add(format_url) format_urls.add(format_url)
@ -258,7 +270,7 @@ class SoundcloudBaseIE(InfoExtractor):
abr = f.get('abr') abr = f.get('abr')
if abr: if abr:
f['abr'] = int(abr) f['abr'] = int(abr)
if protocol == 'hls': if protocol in ('hls', 'hls-aes'):
protocol = 'm3u8' if ext == 'aac' else 'm3u8_native' protocol = 'm3u8' if ext == 'aac' else 'm3u8_native'
else: else:
protocol = 'http' protocol = 'http'
@ -274,11 +286,32 @@ class SoundcloudBaseIE(InfoExtractor):
if extract_flat: if extract_flat:
break break
format_url = t['url'] format_url = t['url']
stream = None
protocol = traverse_obj(t, ('format', 'protocol', {str}))
if protocol == 'progressive':
protocol = 'http'
if protocol != 'hls' and '/hls' in format_url:
protocol = 'hls'
if protocol == 'encrypted-hls' or '/encrypted-hls' in format_url:
protocol = 'hls-aes'
ext = None
if preset := traverse_obj(t, ('preset', {str_or_none})):
ext = preset.split('_')[0]
if ext not in KNOWN_EXTENSIONS:
ext = mimetype2ext(traverse_obj(t, ('format', 'mime_type', {str})))
identifier = join_nonempty(protocol, ext, delim='_')
if not self._is_requested(identifier):
self.write_debug(f'"{identifier}" is not a requested format, skipping')
continue
stream = None
for retry in self.RetryManager(fatal=False): for retry in self.RetryManager(fatal=False):
try: try:
stream = self._download_json(format_url, track_id, query=query, headers=self._HEADERS) stream = self._download_json(
format_url, track_id, f'Downloading {identifier} format info JSON',
query=query, headers=self._HEADERS)
except ExtractorError as e: except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 429: if isinstance(e.cause, HTTPError) and e.cause.status == 429:
self.report_warning( self.report_warning(
@ -289,27 +322,14 @@ class SoundcloudBaseIE(InfoExtractor):
else: else:
self.report_warning(e.msg) self.report_warning(e.msg)
if not isinstance(stream, dict): stream_url = traverse_obj(stream, ('url', {url_or_none}))
continue
stream_url = url_or_none(stream.get('url'))
if invalid_url(stream_url): if invalid_url(stream_url):
continue continue
format_urls.add(stream_url) format_urls.add(stream_url)
stream_format = t.get('format') or {}
protocol = stream_format.get('protocol')
if protocol != 'hls' and '/hls' in format_url:
protocol = 'hls'
ext = None
preset = str_or_none(t.get('preset'))
if preset:
ext = preset.split('_')[0]
if ext not in KNOWN_EXTENSIONS:
ext = mimetype2ext(stream_format.get('mime_type'))
add_format({ add_format({
'url': stream_url, 'url': stream_url,
'ext': ext, 'ext': ext,
}, 'http' if protocol == 'progressive' else protocol, }, protocol, t.get('snipped') or '/preview/' in format_url)
t.get('snipped') or '/preview/' in format_url)
for f in formats: for f in formats:
f['vcodec'] = 'none' f['vcodec'] = 'none'

Loading…
Cancel
Save