]>
jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/wrestleuniverse.py
6 from .common
import InfoExtractor
7 from ..dependencies
import Cryptodome
18 class WrestleUniverseBaseIE(InfoExtractor
):
19 _VALID_URL_TMPL
= r
'https?://(?:www\.)?wrestle-universe\.com/(?:(?P<lang>\w{2})/)?%s/(?P<id>\w+)'
24 def _get_token_cookie(self
):
25 if not self
._TOKEN
or not self
._TOKEN
_EXPIRY
:
26 self
._TOKEN
= try_call(lambda: self
._get
_cookies
('https://www.wrestle-universe.com/')['token'].value
)
28 self
.raise_login_required()
29 expiry
= traverse_obj(jwt_decode_hs256(self
._TOKEN
), ('exp', {int_or_none}
))
31 raise ExtractorError('There was a problem with the token cookie')
32 self
._TOKEN
_EXPIRY
= expiry
34 if self
._TOKEN
_EXPIRY
<= int(time
.time()):
36 'Expired token. Refresh your cookies in browser and try again', expected
=True)
40 def _call_api(self
, video_id
, param
='', msg
='API', auth
=True, data
=None, query
={}, fatal
=True):
41 headers
= {'CA-CID': ''}
43 headers
['Content-Type'] = 'application/json;charset=utf-8'
44 data
= json
.dumps(data
, separators
=(',', ':')).encode()
46 headers
['Authorization'] = f
'Bearer {self._get_token_cookie()}'
47 return self
._download
_json
(
48 f
'https://api.wrestle-universe.com/v1/{self._API_PATH}/{video_id}{param}', video_id
,
49 note
=f
'Downloading {msg} JSON', errnote
=f
'Failed to download {msg} JSON',
50 data
=data
, headers
=headers
, query
=query
, fatal
=fatal
)
52 def _call_encrypted_api(self
, video_id
, param
='', msg
='API', data
={}, query={}
, fatal
=True):
54 raise ExtractorError('pycryptodomex not found. Please install', expected
=True)
55 private_key
= Cryptodome
.PublicKey
.RSA
.generate(2048)
56 cipher
= Cryptodome
.Cipher
.PKCS1_OAEP
.new(private_key
, hashAlgo
=Cryptodome
.Hash
.SHA1
)
62 return cipher
.decrypt(base64
.b64decode(data
)).decode()
63 except (ValueError, binascii
.Error
) as e
:
64 raise ExtractorError(f
'Could not decrypt data: {e}')
66 token
= base64
.b64encode(private_key
.public_key().export_key('DER')).decode()
67 api_json
= self
._call
_api
(video_id
, param
, msg
, data
={
68 # 'deviceId' (random uuid4 generated at login) is not required yet
71 }, query
=query
, fatal
=fatal
)
72 return api_json
, decrypt
74 def _download_metadata(self
, url
, video_id
, lang
, props_key
):
75 metadata
= self
._call
_api
(video_id
, msg
='metadata', query
={'al': lang or 'ja'}
, auth
=False, fatal
=False)
77 webpage
= self
._download
_webpage
(url
, video_id
)
78 nextjs_data
= self
._search
_nextjs
_data
(webpage
, video_id
)
79 metadata
= traverse_obj(nextjs_data
, ('props', 'pageProps', props_key
, {dict}
)) or {}
82 def _get_formats(self
, data
, path
, video_id
=None):
83 hls_url
= traverse_obj(data
, path
, get_all
=False)
84 if not hls_url
and not data
.get('canWatch'):
85 self
.raise_no_formats(
86 'This account does not have access to the requested content', expected
=True)
88 self
.raise_no_formats('No supported formats found')
89 return self
._extract
_m
3u8_formats
(hls_url
, video_id
, 'mp4', m3u8_id
='hls', live
=True)
92 class WrestleUniverseVODIE(WrestleUniverseBaseIE
):
93 _VALID_URL
= WrestleUniverseBaseIE
._VALID
_URL
_TMPL
% 'videos'
95 'url': 'https://www.wrestle-universe.com/en/videos/dp8mpjmcKfxzUhEHM2uFws',
97 'id': 'dp8mpjmcKfxzUhEHM2uFws',
99 'title': 'The 3rd “Futari wa Princess” Max Heart Tournament',
100 'description': 'md5:318d5061e944797fbbb81d5c7dd00bf5',
101 'location': '埼玉・春日部ふれあいキューブ',
104 'timestamp': 1674979200,
105 'upload_date': '20230129',
106 'thumbnail': 'https://image.asset.wrestle-universe.com/8FjD67P8rZc446RBQs5RBN/8FjD67P8rZc446RBQs5RBN',
107 'chapters': 'count:7',
111 'skip_download': 'm3u8',
115 _API_PATH
= 'videoEpisodes'
117 def _real_extract(self
, url
):
118 lang
, video_id
= self
._match
_valid
_url
(url
).group('lang', 'id')
119 metadata
= self
._download
_metadata
(url
, video_id
, lang
, 'videoEpisodeFallbackData')
120 video_data
= self
._call
_api
(video_id
, ':watch', 'watch', data
={
121 # 'deviceId' is required if ignoreDeviceRestriction is False
122 'ignoreDeviceRestriction': True,
127 'formats': self
._get
_formats
(video_data
, (
128 (('protocolHls', 'url'), ('chromecastUrls', ...)), {url_or_none}
), video_id
),
129 **traverse_obj(metadata
, {
130 'title': ('displayName', {str}
),
131 'description': ('description', {str}
),
132 'channel': ('labels', 'group', {str}
),
133 'location': ('labels', 'venue', {str}
),
134 'timestamp': ('watchStartTime', {int_or_none}
),
135 'thumbnail': ('keyVisualUrl', {url_or_none}
),
136 'cast': ('casts', ..., 'displayName', {str}
),
137 'duration': ('duration', {int}
),
138 'chapters': ('videoChapters', lambda _
, v
: isinstance(v
.get('start'), int), {
139 'title': ('displayName', {str}
),
140 'start_time': ('start', {int}
),
141 'end_time': ('end', {int}
),
147 class WrestleUniversePPVIE(WrestleUniverseBaseIE
):
148 _VALID_URL
= WrestleUniverseBaseIE
._VALID
_URL
_TMPL
% 'lives'
150 'note': 'HLS AES-128 key obtained via API',
151 'url': 'https://www.wrestle-universe.com/en/lives/buH9ibbfhdJAY4GKZcEuJX',
153 'id': 'buH9ibbfhdJAY4GKZcEuJX',
155 'title': '【PPV】Beyond the origins, into the future',
156 'description': 'md5:9a872db68cd09be4a1e35a3ee8b0bdfc',
158 'location': '東京・Twin Box AKIHABARA',
160 'timestamp': 1675076400,
161 'upload_date': '20230130',
162 'thumbnail': 'https://image.asset.wrestle-universe.com/rJs2m7cBaLXrwCcxMdQGRM/rJs2m7cBaLXrwCcxMdQGRM',
163 'thumbnails': 'count:3',
165 'key': '5633184acd6e43f1f1ac71c6447a4186',
166 'iv': '5bac71beb33197d5600337ce86de7862',
170 'skip_download': 'm3u8',
173 'note': 'unencrypted HLS',
174 'url': 'https://www.wrestle-universe.com/en/lives/wUG8hP5iApC63jbtQzhVVx',
176 'id': 'wUG8hP5iApC63jbtQzhVVx',
178 'title': 'GRAND PRINCESS \'22',
179 'description': 'md5:e4f43d0d4262de3952ff34831bc99858',
181 'location': '東京・両国国技館',
183 'timestamp': 1647665400,
184 'upload_date': '20220319',
185 'thumbnail': 'https://image.asset.wrestle-universe.com/i8jxSTCHPfdAKD4zN41Psx/i8jxSTCHPfdAKD4zN41Psx',
186 'thumbnails': 'count:3',
189 'skip_download': 'm3u8',
195 def _real_extract(self
, url
):
196 lang
, video_id
= self
._match
_valid
_url
(url
).group('lang', 'id')
197 metadata
= self
._download
_metadata
(url
, video_id
, lang
, 'eventFallbackData')
199 info
= traverse_obj(metadata
, {
200 'title': ('displayName', {str}
),
201 'description': ('description', {str}
),
202 'channel': ('labels', 'group', {str}
),
203 'location': ('labels', 'venue', {str}
),
204 'timestamp': ('startTime', {int_or_none}
),
205 'thumbnails': (('keyVisualUrl', 'alterKeyVisualUrl', 'heroKeyVisualUrl'), {'url': {url_or_none}
}),
208 ended_time
= traverse_obj(metadata
, ('endedTime', {int_or_none}
))
209 if info
.get('timestamp') and ended_time
:
210 info
['duration'] = ended_time
- info
['timestamp']
212 video_data
, decrypt
= self
._call
_encrypted
_api
(
213 video_id
, ':watchArchive', 'watch archive', data
={'method': 1}
)
214 formats
= self
._get
_formats
(video_data
, (
215 ('hls', None), ('urls', 'chromecastUrls'), ..., {url_or_none}
), video_id
)
217 # bitrates are exaggerated in PPV playlists, so avoid wrong/huge filesize_approx values
219 f
['tbr'] = int(f
['tbr'] / 2.5)
221 hls_aes_key
= traverse_obj(video_data
, ('hls', 'key', {decrypt}
))
222 if not hls_aes_key
and traverse_obj(video_data
, ('hls', 'encryptType', {int}
), default
=0) > 0:
223 self
.report_warning('HLS AES-128 key was not found in API response')
230 'iv': traverse_obj(video_data
, ('hls', 'iv', {decrypt}
)),