mirror of
https://github.com/yt-dlp/yt-dlp.git
synced 2025-06-27 08:58:30 +00:00
[ie/spwn] Add Extractor
This commit is contained in:
parent
73bf102116
commit
8a1b4022df
@ -1971,6 +1971,7 @@
|
||||
SproutVideoIE,
|
||||
VidsIoIE,
|
||||
)
|
||||
from .spwn import SPWNIE
|
||||
from .srgssr import (
|
||||
SRGSSRIE,
|
||||
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