1 from .common
import InfoExtractor
8 from ..utils
.traversal
import traverse_obj
11 class DigitalConcertHallIE(InfoExtractor
):
12 IE_DESC
= 'DigitalConcertHall extractor'
13 _VALID_URL
= r
'https?://(?:www\.)?digitalconcerthall\.com/(?P<language>[a-z]+)/(?P<type>film|concert|work)/(?P<id>[0-9]+)-?(?P<part>[0-9]+)?'
14 _OAUTH_URL
= 'https://api.digitalconcerthall.com/v2/oauth2/token'
16 _NETRC_MACHINE
= 'digitalconcerthall'
18 'note': 'Playlist with only one video',
19 'url': 'https://www.digitalconcerthall.com/en/concert/53201',
23 'composer': 'Kurt Weill',
24 'title': '[Magic Night]',
25 'thumbnail': r
're:^https?://images.digitalconcerthall.com/cms/thumbnails.*\.jpg$',
26 'upload_date': '20210624',
27 'timestamp': 1624548600,
29 'album_artists': ['Members of the Berliner Philharmoniker', 'Simon Rössler'],
30 'composers': ['Kurt Weill'],
32 'params': {'skip_download': 'm3u8'}
,
34 'note': 'Concert with several works and an interview',
35 'url': 'https://www.digitalconcerthall.com/en/concert/53785',
38 'album_artists': ['Berliner Philharmoniker', 'Kirill Petrenko'],
39 'title': 'Kirill Petrenko conducts Mendelssohn and Shostakovich',
40 'thumbnail': r
're:^https?://images.digitalconcerthall.com/cms/thumbnails.*\.jpg$',
42 'params': {'skip_download': 'm3u8'}
,
45 'url': 'https://www.digitalconcerthall.com/en/film/388',
49 'title': 'The Berliner Philharmoniker and Frank Peter Zimmermann',
50 'description': 'md5:cfe25a7044fa4be13743e5089b5b5eb2',
51 'thumbnail': r
're:^https?://images.digitalconcerthall.com/cms/thumbnails.*\.jpg$',
52 'upload_date': '20220714',
53 'timestamp': 1657785600,
54 'album_artists': ['Frank Peter Zimmermann', 'Benedikt von Bernstorff', 'Jakob von Bernstorff'],
56 'params': {'skip_download': 'm3u8'}
,
58 'note': 'Concert with several works and an interview',
59 'url': 'https://www.digitalconcerthall.com/en/work/53785-1',
62 'album_artists': ['Berliner Philharmoniker', 'Kirill Petrenko'],
63 'title': 'Kirill Petrenko conducts Mendelssohn and Shostakovich',
64 'thumbnail': r
're:^https?://images.digitalconcerthall.com/cms/thumbnails.*\.jpg$',
66 'params': {'skip_download': 'm3u8'}
,
70 def _perform_login(self
, username
, password
):
71 token_response
= self
._download
_json
(
73 None, 'Obtaining token', errnote
='Unable to obtain token', data
=urlencode_postdata({
75 'grant_type': 'device',
76 'device_vendor': 'unknown',
77 'app_id': 'dch.webapp',
78 'app_version': '1.0.0',
79 'client_secret': '2ySLN+2Fwb',
81 'Content-Type': 'application/x-www-form-urlencoded',
83 self
._ACCESS
_TOKEN
= token_response
['access_token']
87 None, note
='Logging in', errnote
='Unable to login', data
=urlencode_postdata({
88 'grant_type': 'password',
92 'Content-Type': 'application/x-www-form-urlencoded',
93 'Referer': 'https://www.digitalconcerthall.com',
94 'Authorization': f
'Bearer {self._ACCESS_TOKEN}',
96 except ExtractorError
:
97 self
.raise_login_required(msg
='Login info incorrect')
99 def _real_initialize(self
):
100 if not self
._ACCESS
_TOKEN
:
101 self
.raise_login_required(method
='password')
103 def _entries(self
, items
, language
, type_
, **kwargs
):
105 video_id
= item
['id']
106 stream_info
= self
._download
_json
(
107 self
._proto
_relative
_url
(item
['_links']['streams']['href']), video_id
, headers
={
108 'Accept': 'application/json',
109 'Authorization': f
'Bearer {self._ACCESS_TOKEN}',
110 'Accept-Language': language
,
114 for m3u8_url
in traverse_obj(stream_info
, ('channel', ..., 'stream', ..., 'url', {url_or_none}
)):
115 formats
.extend(self
._extract
_m
3u8_formats
(m3u8_url
, video_id
, 'mp4', fatal
=False))
119 'title': item
.get('title'),
120 'composer': item
.get('name_composer'),
122 'duration': item
.get('duration_total'),
123 'timestamp': traverse_obj(item
, ('date', 'published')),
124 'description': item
.get('short_description') or stream_info
.get('short_description'),
127 'start_time': chapter
.get('time'),
128 'end_time': try_get(chapter
, lambda x
: x
['time'] + x
['duration']),
129 'title': chapter
.get('text'),
130 } for chapter
in item
['cuepoints']] if item
.get('cuepoints') and type_
== 'concert' else None,
133 def _real_extract(self
, url
):
134 language
, type_
, video_id
, part
= self
._match
_valid
_url
(url
).group('language', 'type', 'id', 'part')
138 api_type
= 'concert' if type_
== 'work' else type_
139 vid_info
= self
._download
_json
(
140 f
'https://api.digitalconcerthall.com/v2/{api_type}/{video_id}', video_id
, headers
={
141 'Accept': 'application/json',
142 'Accept-Language': language
,
144 album_artists
= traverse_obj(vid_info
, ('_links', 'artist', ..., 'name'))
145 videos
= [vid_info
] if type_
== 'film' else traverse_obj(vid_info
, ('_embedded', ..., ...))
148 videos
= [videos
[int(part
) - 1]]
150 thumbnail
= traverse_obj(vid_info
, (
151 'image', ..., {self._proto_relative_url}
, {url_or_none}
,
152 {lambda x: x.format(width=0, height=0)}
, any
)) # NB: 0x0 is the original size
157 'title': vid_info
.get('title'),
158 'entries': self
._entries
(
159 videos
, language
, type_
, thumbnail
=thumbnail
, album_artists
=album_artists
),
160 'thumbnail': thumbnail
,
161 'album_artists': album_artists
,