mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-06-27 17:08:32 +00:00
Merge 8a1b4022df
into 06c1a8cdff
This commit is contained in:
commit
84bfc43ae7
@ -1972,6 +1972,7 @@
|
|||||||
SproutVideoIE,
|
SproutVideoIE,
|
||||||
VidsIoIE,
|
VidsIoIE,
|
||||||
)
|
)
|
||||||
|
from .spwn import SPWNIE
|
||||||
from .srgssr import (
|
from .srgssr import (
|
||||||
SRGSSRIE,
|
SRGSSRIE,
|
||||||
SRGSSRPlayIE,
|
SRGSSRPlayIE,
|
||||||
|
126
yt_dlp/extractor/spwn.py
Normal file
126
yt_dlp/extractor/spwn.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .wrestleuniverse import WrestleUniverseBaseIE
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
clean_html,
|
||||||
|
parse_iso8601,
|
||||||
|
parse_qs,
|
||||||
|
update_url,
|
||||||
|
url_or_none,
|
||||||
|
)
|
||||||
|
from ..utils.traversal import require, traverse_obj
|
||||||
|
|
||||||
|
|
||||||
|
class SPWNIE(WrestleUniverseBaseIE):
|
||||||
|
_BASE_URL = 'https://spwn.jp'
|
||||||
|
_LOGIN_HEADERS = {'Content-Type': 'application/json'}
|
||||||
|
_LOGIN_HINT = (
|
||||||
|
'Use --username refresh --password <refreshToken>, --username and --password, '
|
||||||
|
'--netrc-cmd, or --netrc (spwn) to provide account credentials')
|
||||||
|
_LOGIN_QUERY = {'key': 'AIzaSyC-RDWv-QnYNWsJ6geLPpFYArlo2uPWCpA'}
|
||||||
|
_NETRC_MACHINE = 'spwn'
|
||||||
|
_VALID_URL = r'https?://spwn\.jp/events/(?P<id>[^/?#]+)(/streaming)?'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://spwn.jp/events/evt_wG62Svb4HkCQkK3QwDqI/streaming?vid=ki6OfjHeVlDGVKtEJwe4',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'ki6OfjHeVlDGVKtEJwe4',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '本編',
|
||||||
|
'categories': 'count:1',
|
||||||
|
'description': 'md5:7b6ef6bc591dc3e8641acf224a2345fe',
|
||||||
|
'release_date': '20250606',
|
||||||
|
'release_timestamp': 1749200400,
|
||||||
|
'series': '劇場版「ポールプリンセス!!」【劇場版アニメ本編配信】',
|
||||||
|
'series_id': 'evt_wG62Svb4HkCQkK3QwDqI',
|
||||||
|
'tags': 'count:2',
|
||||||
|
'thumbnail': r're:https?://public-web\.spwn\.jp/events/.+',
|
||||||
|
'timestamp': 1749200400,
|
||||||
|
'upload_date': '20250606',
|
||||||
|
},
|
||||||
|
'skip': 'Paid only',
|
||||||
|
}, {
|
||||||
|
'url': 'https://spwn.jp/events/evt_wG62Svb4HkCQkK3QwDqI',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'evt_wG62Svb4HkCQkK3QwDqI',
|
||||||
|
'title': '劇場版「ポールプリンセス!!」【劇場版アニメ本編配信】',
|
||||||
|
},
|
||||||
|
'playlist_count': 2,
|
||||||
|
'skip': 'Paid only',
|
||||||
|
}]
|
||||||
|
|
||||||
|
@WrestleUniverseBaseIE._TOKEN.getter
|
||||||
|
def _TOKEN(self):
|
||||||
|
if not self._REAL_TOKEN or self._TOKEN_EXPIRY <= int(time.time()):
|
||||||
|
if not self._REFRESH_TOKEN:
|
||||||
|
self.raise_login_required(
|
||||||
|
f'No refreshToken provided. {self._LOGIN_HINT}', method=None)
|
||||||
|
self._refresh_token()
|
||||||
|
return self._REAL_TOKEN
|
||||||
|
|
||||||
|
def _perform_login(self, username, password):
|
||||||
|
if username.lower() == 'refresh':
|
||||||
|
self._REFRESH_TOKEN = password
|
||||||
|
return self._refresh_token()
|
||||||
|
return super()._perform_login(username, password)
|
||||||
|
|
||||||
|
def _call_api(self, event_id, note, url=None, path=None):
|
||||||
|
if not url:
|
||||||
|
url = f'https://firestore.googleapis.com/v1/projects/spwn-balus/databases/(default)/documents/{path}'
|
||||||
|
|
||||||
|
return traverse_obj(self._download_json(
|
||||||
|
url, event_id, f'Downloading {note}', fatal=False,
|
||||||
|
), ('fields', {dict}), default={})
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
event_id = self._match_id(url)
|
||||||
|
event = self._call_api(event_id, 'event info', path=f'events/{event_id}')
|
||||||
|
info = self._download_json(
|
||||||
|
'https://us-central1-spwn-balus.cloudfunctions.net/getStreamingKey/',
|
||||||
|
event_id, headers={
|
||||||
|
'Authorization': f'Bearer {self._TOKEN}',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Origin': self._BASE_URL,
|
||||||
|
}, data=json.dumps({'eid': event_id}).encode())
|
||||||
|
if traverse_obj(info, ('isError', {bool})):
|
||||||
|
msg = traverse_obj(info, ('msg', {str}, filter))
|
||||||
|
raise ExtractorError(
|
||||||
|
msg or 'API returned an error response', expected=bool(msg))
|
||||||
|
|
||||||
|
video_id = traverse_obj(parse_qs(url), ('vid', 0, {str}))
|
||||||
|
if not video_id:
|
||||||
|
return self.playlist_result([self.url_result(
|
||||||
|
f'{self._BASE_URL}/events/{event_id}/streaming?vid={vid}', SPWNIE,
|
||||||
|
) for vid in traverse_obj(info, ('videoIds', ..., {str}, filter))],
|
||||||
|
event_id, traverse_obj(event, ('title', 'stringValue', {str})))
|
||||||
|
|
||||||
|
video = self._call_api(event_id, 'video info', path=f'streaming/{event_id}/videos/{video_id}')
|
||||||
|
policy, signature, m3u8_url = traverse_obj(info, ('cookies', video_id, 'default', (
|
||||||
|
('cookie', 'CloudFront-Policy', {str}, {require('CloudFront policy')}),
|
||||||
|
('cookie', 'CloudFront-Signature', {str}, {require('CloudFront signature')}),
|
||||||
|
('url', {url_or_none}, {require('manifest URL')}),
|
||||||
|
)))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': traverse_obj(video, ('name', 'stringValue', {str})),
|
||||||
|
'formats': self._extract_m3u8_formats(
|
||||||
|
update_url(m3u8_url, netloc='vod.spwnlive.net'), video_id,
|
||||||
|
'mp4', query={'policy': policy, 'signature': signature}),
|
||||||
|
'series_id': event_id,
|
||||||
|
**traverse_obj(event, {
|
||||||
|
'cast': ('artistRefs', 'arrayValue', 'values', ..., 'referenceValue',
|
||||||
|
{lambda x: self._call_api(event_id, 'artist info', url=f'{self._API_BASE}/{x}')},
|
||||||
|
'name', 'stringValue', {str}, filter, all, filter),
|
||||||
|
'categories': ('categories', 'arrayValue', 'values', ..., 'stringValue', {str}, filter, all, filter),
|
||||||
|
'description': ('description', 'stringValue', {clean_html}, {clean_html}, filter),
|
||||||
|
'series': ('title', 'stringValue', {str}),
|
||||||
|
'tags': ('twitterHashTag', 'arrayValue', 'values', ..., 'stringValue', {str}, filter, all, filter),
|
||||||
|
'thumbnail': ('defaultImg', 'stringValue', {url_or_none}),
|
||||||
|
}),
|
||||||
|
**traverse_obj(event, ('parts', 'arrayValue', 'values', ..., 'mapValue', 'fields', {
|
||||||
|
'release_timestamp': ('openTime', 'timestampValue', {parse_iso8601}),
|
||||||
|
'timestamp': ('startTime', 'timestampValue', {parse_iso8601}),
|
||||||
|
}, any)),
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user