]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/stageplus.py
[cleanup] Fix infodict returned fields (#8906)
[yt-dlp.git] / yt_dlp / extractor / stageplus.py
CommitLineData
e5265dc6 1import json
2import uuid
3
4from .common import InfoExtractor
5from ..utils import (
6 float_or_none,
7 traverse_obj,
8 try_call,
9 unified_timestamp,
10 url_or_none,
11)
12
13
14class StagePlusVODConcertIE(InfoExtractor):
15 _NETRC_MACHINE = 'stageplus'
16 _VALID_URL = r'https?://(?:www\.)?stage-plus\.com/video/(?P<id>vod_concert_\w+)'
17 _TESTS = [{
18 'url': 'https://www.stage-plus.com/video/vod_concert_APNM8GRFDPHMASJKBSPJACG',
19 'playlist_count': 6,
20 'info_dict': {
21 'id': 'vod_concert_APNM8GRFDPHMASJKBSPJACG',
22 'title': 'Yuja Wang plays Rachmaninoff\'s Piano Concerto No. 2 – from Odeonsplatz',
23 'description': 'md5:50f78ec180518c9bdb876bac550996fc',
f4f9f6d0 24 'artists': ['Yuja Wang', 'Lorenzo Viotti'],
e5265dc6 25 'upload_date': '20230331',
26 'timestamp': 1680249600,
27 'release_date': '20210709',
28 'release_timestamp': 1625788800,
29 'thumbnails': 'count:3',
30 },
31 'playlist': [{
32 'info_dict': {
33 'id': 'performance_work_A1IN4PJFE9MM2RJ3CLBMUSJBBSOJAD9O',
34 'ext': 'mp4',
35 'title': 'Piano Concerto No. 2 in C Minor, Op. 18',
36 'description': 'md5:50f78ec180518c9bdb876bac550996fc',
37 'upload_date': '20230331',
38 'timestamp': 1680249600,
39 'release_date': '20210709',
40 'release_timestamp': 1625788800,
41 'duration': 2207,
42 'chapters': 'count:5',
f4f9f6d0 43 'artists': ['Yuja Wang'],
44 'composers': ['Sergei Rachmaninoff'],
e5265dc6 45 'album': 'Yuja Wang plays Rachmaninoff\'s Piano Concerto No. 2 – from Odeonsplatz',
f4f9f6d0 46 'album_artists': ['Yuja Wang', 'Lorenzo Viotti'],
e5265dc6 47 'track': 'Piano Concerto No. 2 in C Minor, Op. 18',
48 'track_number': 1,
49 'genre': 'Instrumental Concerto',
50 },
51 }],
52 'params': {'skip_download': 'm3u8'},
53 }]
54
55 # TODO: Prune this after livestream and/or album extractors are added
56 _GRAPHQL_QUERY = '''query videoDetailPage($videoId: ID!, $sliderItemsFirst: Int = 24) {
57 node(id: $videoId) {
58 __typename
59 ...LiveConcertFields
60 ... on LiveConcert {
61 artists {
62 edges {
63 role {
64 ...RoleFields
65 }
66 node {
67 id
68 name
69 sortName
70 }
71 }
72 }
73 isAtmos
74 maxResolution
75 groups {
76 id
77 name
78 typeDisplayName
79 }
80 shortDescription
81 performanceWorks {
82 ...livePerformanceWorkFields
83 }
84 totalDuration
85 sliders {
86 ...contentContainerFields
87 }
88 vodConcert {
89 __typename
90 id
91 }
92 }
93 ...VideoFields
94 ... on Video {
95 artists {
96 edges {
97 role {
98 ...RoleFields
99 }
100 node {
101 id
102 name
103 sortName
104 }
105 }
106 }
107 isAtmos
108 maxResolution
109 isLossless
110 description
111 productionDate
112 takedownDate
113 sliders {
114 ...contentContainerFields
115 }
116 }
117 ...VodConcertFields
118 ... on VodConcert {
119 artists {
120 edges {
121 role {
122 ...RoleFields
123 }
124 node {
125 id
126 name
127 sortName
128 }
129 }
130 }
131 isAtmos
132 maxResolution
133 groups {
134 id
135 name
136 typeDisplayName
137 }
138 performanceWorks {
139 ...PerformanceWorkFields
140 }
141 shortDescription
142 productionDate
143 takedownDate
144 sliders {
145 ...contentContainerFields
146 }
147 }
148 }
149}
150
151fragment LiveConcertFields on LiveConcert {
152 endTime
153 id
154 pictures {
155 ...PictureFields
156 }
157 reruns {
158 ...liveConcertRerunFields
159 }
160 publicationLevel
161 startTime
162 streamStartTime
163 subtitle
164 title
165 typeDisplayName
166 stream {
167 ...liveStreamFields
168 }
169 trailerStream {
170 ...streamFields
171 }
172 geoAccessCountries
173 geoAccessMode
174}
175
176fragment PictureFields on Picture {
177 id
178 url
179 type
180}
181
182fragment liveConcertRerunFields on LiveConcertRerun {
183 streamStartTime
184 endTime
185 startTime
186 stream {
187 ...rerunStreamFields
188 }
189}
190
191fragment rerunStreamFields on RerunStream {
192 publicationLevel
193 streamType
194 url
195}
196
197fragment liveStreamFields on LiveStream {
198 publicationLevel
199 streamType
200 url
201}
202
203fragment streamFields on Stream {
204 publicationLevel
205 streamType
206 url
207}
208
209fragment RoleFields on Role {
210 __typename
211 id
212 type
213 displayName
214}
215
216fragment livePerformanceWorkFields on LivePerformanceWork {
217 __typename
218 id
219 artists {
220 ...artistWithRoleFields
221 }
222 groups {
223 edges {
224 node {
225 id
226 name
227 typeDisplayName
228 }
229 }
230 }
231 work {
232 ...workFields
233 }
234}
235
236fragment artistWithRoleFields on ArtistWithRoleConnection {
237 edges {
238 role {
239 ...RoleFields
240 }
241 node {
242 id
243 name
244 sortName
245 }
246 }
247}
248
249fragment workFields on Work {
250 id
251 title
252 movements {
253 id
254 title
255 }
256 composers {
257 id
258 name
259 }
260 genre {
261 id
262 title
263 }
264}
265
266fragment contentContainerFields on CuratedContentContainer {
267 __typename
268 ...SliderFields
269 ...BannerFields
270}
271
272fragment SliderFields on Slider {
273 id
274 headline
275 items(first: $sliderItemsFirst) {
276 edges {
277 node {
278 id
279 __typename
280 ...AlbumFields
281 ...ArtistFields
282 ...EpochFields
283 ...GenreFields
284 ...GroupFields
285 ...LiveConcertFields
286 ...PartnerFields
287 ...PerformanceWorkFields
288 ...VideoFields
289 ...VodConcertFields
290 }
291 }
292 }
293}
294
295fragment AlbumFields on Album {
296 artistAndGroupDisplayInfo
297 id
298 pictures {
299 ...PictureFields
300 }
301 title
302}
303
304fragment ArtistFields on Artist {
305 id
306 name
307 roles {
308 ...RoleFields
309 }
310 pictures {
311 ...PictureFields
312 }
313}
314
315fragment EpochFields on Epoch {
316 id
317 endYear
318 pictures {
319 ...PictureFields
320 }
321 startYear
322 title
323}
324
325fragment GenreFields on Genre {
326 id
327 pictures {
328 ...PictureFields
329 }
330 title
331}
332
333fragment GroupFields on Group {
334 id
335 name
336 typeDisplayName
337 pictures {
338 ...PictureFields
339 }
340}
341
342fragment PartnerFields on Partner {
343 id
344 name
345 typeDisplayName
346 subtypeDisplayName
347 pictures {
348 ...PictureFields
349 }
350}
351
352fragment PerformanceWorkFields on PerformanceWork {
353 __typename
354 id
355 artists {
356 ...artistWithRoleFields
357 }
358 groups {
359 edges {
360 node {
361 id
362 name
363 typeDisplayName
364 }
365 }
366 }
367 work {
368 ...workFields
369 }
370 stream {
371 ...streamFields
372 }
373 vodConcert {
374 __typename
375 id
376 }
377 duration
378 cuePoints {
379 mark
380 title
381 }
382}
383
384fragment VideoFields on Video {
385 id
386 archiveReleaseDate
387 title
388 subtitle
389 pictures {
390 ...PictureFields
391 }
392 stream {
393 ...streamFields
394 }
395 trailerStream {
396 ...streamFields
397 }
398 duration
399 typeDisplayName
400 duration
401 geoAccessCountries
402 geoAccessMode
403 publicationLevel
404 takedownDate
405}
406
407fragment VodConcertFields on VodConcert {
408 id
409 archiveReleaseDate
410 pictures {
411 ...PictureFields
412 }
413 subtitle
414 title
415 typeDisplayName
416 totalDuration
417 geoAccessCountries
418 geoAccessMode
419 trailerStream {
420 ...streamFields
421 }
422 publicationLevel
423 takedownDate
424}
425
426fragment BannerFields on Banner {
427 description
428 link
429 pictures {
430 ...PictureFields
431 }
432 title
433}'''
434
435 _TOKEN = None
436
437 def _perform_login(self, username, password):
438 auth = self._download_json('https://audience.api.stageplus.io/oauth/token', None, headers={
439 'Content-Type': 'application/json',
440 'Origin': 'https://www.stage-plus.com',
441 }, data=json.dumps({
442 'grant_type': 'password',
443 'username': username,
444 'password': password,
445 'device_info': 'Chrome (Windows)',
446 'client_device_id': str(uuid.uuid4()),
447 }, separators=(',', ':')).encode(), note='Logging in')
448
449 if auth.get('access_token'):
450 self._TOKEN = auth['access_token']
451
452 def _real_initialize(self):
453 if self._TOKEN:
454 return
455
456 self._TOKEN = try_call(
457 lambda: self._get_cookies('https://www.stage-plus.com/')['dgplus_access_token'].value)
458 if not self._TOKEN:
459 self.raise_login_required()
460
461 def _real_extract(self, url):
462 concert_id = self._match_id(url)
463
464 data = self._download_json('https://audience.api.stageplus.io/graphql', concert_id, headers={
465 'authorization': f'Bearer {self._TOKEN}',
466 'content-type': 'application/json',
467 'Origin': 'https://www.stage-plus.com',
468 }, data=json.dumps({
469 'query': self._GRAPHQL_QUERY,
470 'variables': {'videoId': concert_id},
471 'operationName': 'videoDetailPage'
472 }, separators=(',', ':')).encode())['data']['node']
473
474 metadata = traverse_obj(data, {
475 'title': 'title',
476 'description': ('shortDescription', {str}),
f4f9f6d0 477 'artists': ('artists', 'edges', ..., 'node', 'name'),
e5265dc6 478 'timestamp': ('archiveReleaseDate', {unified_timestamp}),
479 'release_timestamp': ('productionDate', {unified_timestamp}),
480 })
481
482 thumbnails = traverse_obj(data, ('pictures', lambda _, v: url_or_none(v['url']), {
483 'id': 'name',
484 'url': 'url',
485 })) or None
486
e5265dc6 487 entries = []
488 for idx, video in enumerate(traverse_obj(data, (
489 'performanceWorks', lambda _, v: v['id'] and url_or_none(v['stream']['url']))), 1):
490 formats, subtitles = self._extract_m3u8_formats_and_subtitles(
56b3dc03 491 video['stream']['url'], video['id'], 'mp4', m3u8_id='hls', query={'token': self._TOKEN})
e5265dc6 492 entries.append({
493 'id': video['id'],
494 'formats': formats,
495 'subtitles': subtitles,
e5265dc6 496 'album': metadata.get('title'),
f4f9f6d0 497 'album_artists': metadata.get('artist'),
e5265dc6 498 'track_number': idx,
499 **metadata,
500 **traverse_obj(video, {
501 'title': ('work', 'title'),
502 'track': ('work', 'title'),
503 'duration': ('duration', {float_or_none}),
504 'chapters': (
505 'cuePoints', lambda _, v: float_or_none(v['mark']) is not None, {
506 'title': 'title',
507 'start_time': ('mark', {float_or_none}),
508 }),
f4f9f6d0 509 'artists': ('artists', 'edges', ..., 'node', 'name'),
510 'composers': ('work', 'composers', ..., 'name'),
e5265dc6 511 'genre': ('work', 'genre', 'title'),
512 }),
513 })
514
515 return self.playlist_result(entries, concert_id, thumbnails=thumbnails, **metadata)