3 from .common
import InfoExtractor
12 class EplusIbIE(InfoExtractor
):
13 _NETRC_MACHINE
= 'eplus'
15 IE_DESC
= 'e+ (イープラス)'
16 _VALID_URL
= [r
'https?://live\.eplus\.jp/ex/player\?ib=(?P<id>(?:\w|%2B|%2F){86}%3D%3D)',
17 r
'https?://live\.eplus\.jp/(?P<id>sample|\d+)']
19 'url': 'https://live.eplus.jp/ex/player?ib=41K6Wzbr3PlcMD%2FOKHFlC%2FcZCe2Eaw7FK%2BpJS1ooUHki8d0vGSy2mYqxillQBe1dSnOxU%2B8%2FzXKls4XPBSb3vw%3D%3D',
21 'id': '335699-0001-006',
22 'title': '少女☆歌劇 レヴュースタァライト -The LIVE 青嵐- BLUE GLITTER <定点映像配信>【Streaming+(配信)】',
23 'live_status': 'was_live',
24 'release_date': '20201221',
25 'release_timestamp': 1608544800,
28 'skip_download': True,
29 'ignore_no_formats_error': True,
31 'expected_warnings': [
32 'This event may not be accessible',
33 'No video formats found',
34 'Requested format is not available',
37 'url': 'https://live.eplus.jp/ex/player?ib=6QSsQdyRAwOFZrEHWlhRm7vocgV%2FO0YzBZ%2BaBEBg1XR%2FmbLn0R%2F048dUoAY038%2F%2F92MJ73BsoAtvUpbV6RLtDQ%3D%3D&show_id=2371511',
39 'id': '348021-0054-001',
40 'title': 'ラブライブ!スーパースター!! Liella! First LoveLive! Tour ~Starlines~【東京/DAY.1】',
41 'live_status': 'was_live',
42 'release_date': '20220115',
43 'release_timestamp': 1642233600,
47 'skip_download': True,
48 'ignore_no_formats_error': True,
50 'expected_warnings': [
51 'Could not find the playlist URL. This event may not be accessible',
52 'No video formats found!',
53 'Requested format is not available',
56 'url': 'https://live.eplus.jp/sample',
58 'id': 'stream1ng20210719-test-005',
59 'title': 'Online streaming test for DRM',
60 'live_status': 'was_live',
61 'release_date': '20210719',
62 'release_timestamp': 1626703200,
65 'skip_download': True,
66 'ignore_no_formats_error': True,
68 'expected_warnings': [
69 'Could not find the playlist URL. This event may not be accessible',
70 'No video formats found!',
71 'Requested format is not available',
72 'This video is DRM protected',
75 'url': 'https://live.eplus.jp/2053935',
77 'id': '331320-0001-001',
78 'title': '丘みどり2020配信LIVE Vol.2 ~秋麗~ 【Streaming+(配信チケット)】',
79 'live_status': 'was_live',
80 'release_date': '20200920',
81 'release_timestamp': 1600596000,
84 'skip_download': True,
85 'ignore_no_formats_error': True,
87 'expected_warnings': [
88 'Could not find the playlist URL. This event may not be accessible',
89 'No video formats found!',
90 'Requested format is not available',
94 _USER_AGENT
= 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0'
96 def _login(self
, username
, password
, urlh
):
97 if not self
._get
_cookies
('https://live.eplus.jp/').get('ci_session'):
98 raise ExtractorError('Unable to get ci_session cookie')
100 cltft_token
= urlh
.headers
.get('X-CLTFT-Token')
102 raise ExtractorError('Unable to get X-CLTFT-Token')
103 self
._set
_cookie
('live.eplus.jp', 'X-CLTFT-Token', cltft_token
)
105 login_json
= self
._download
_json
(
106 'https://live.eplus.jp/member/api/v1/FTAuth/idpw', None,
107 note
='Sending pre-login info', errnote
='Unable to send pre-login info', headers
={
108 'Content-Type': 'application/json; charset=UTF-8',
110 'X-Cltft-Token': cltft_token
,
114 'loginPassword': password
,
116 if not login_json
.get('isSuccess'):
117 raise ExtractorError('Login failed: Invalid id or password', expected
=True)
119 self
._request
_webpage
(
120 urlh
.url
, None, note
='Logging in', errnote
='Unable to log in',
121 data
=urlencode_postdata({
123 'loginPassword': password
,
124 'Token.Default': cltft_token
,
126 }), headers
={'Referer': urlh
.url
})
128 def _real_extract(self
, url
):
129 video_id
= self
._match
_id
(url
)
130 webpage
, urlh
= self
._download
_webpage
_handle
(
131 url
, video_id
, headers
={'User-Agent': self
._USER
_AGENT
})
132 if urlh
.url
.startswith('https://live.eplus.jp/member/auth'):
133 username
, password
= self
._get
_login
_info
()
135 self
.raise_login_required()
136 self
._login
(username
, password
, urlh
)
137 webpage
= self
._download
_webpage
(
138 url
, video_id
, headers
={'User-Agent': self
._USER
_AGENT
})
140 data_json
= self
._search
_json
(r
'<script>\s*var app\s*=', webpage
, 'data json', video_id
)
142 if data_json
.get('drm_mode') == 'ON':
143 self
.report_drm(video_id
)
145 if data_json
.get('is_pass_ticket') == 'YES':
146 raise ExtractorError(
147 'This URL is for a pass ticket instead of a player page', expected
=True)
149 delivery_status
= data_json
.get('delivery_status')
150 archive_mode
= data_json
.get('archive_mode')
151 release_timestamp
= try_call(lambda: unified_timestamp(data_json
['event_datetime']) - 32400)
152 release_timestamp_str
= data_json
.get('event_datetime_text') # JST
154 self
.write_debug(f
'delivery_status = {delivery_status}, archive_mode = {archive_mode}')
156 if delivery_status
== 'PREPARING':
157 live_status
= 'is_upcoming'
158 elif delivery_status
== 'STARTED':
159 live_status
= 'is_live'
160 elif delivery_status
== 'STOPPED':
161 if archive_mode
!= 'ON':
162 raise ExtractorError(
163 'This event has ended and there is no archive for this event', expected
=True)
164 live_status
= 'post_live'
165 elif delivery_status
== 'WAIT_CONFIRM_ARCHIVED':
166 live_status
= 'post_live'
167 elif delivery_status
== 'CONFIRMED_ARCHIVE':
168 live_status
= 'was_live'
170 self
.report_warning(f
'Unknown delivery_status {delivery_status}, treat it as a live')
171 live_status
= 'is_live'
175 m3u8_playlist_urls
= self
._search
_json
(
176 r
'var\s+listChannels\s*=', webpage
, 'hls URLs', video_id
, contains_pattern
=r
'\[.+\]', default
=[])
177 if not m3u8_playlist_urls
:
178 if live_status
== 'is_upcoming':
179 self
.raise_no_formats(
180 f
'Could not find the playlist URL. This live event will begin at {release_timestamp_str} JST', expected
=True)
182 self
.raise_no_formats(
183 'Could not find the playlist URL. This event may not be accessible', expected
=True)
184 elif live_status
== 'is_upcoming':
185 self
.raise_no_formats(f
'This live event will begin at {release_timestamp_str} JST', expected
=True)
186 elif live_status
== 'post_live':
187 self
.raise_no_formats('This event has ended, and the archive will be available shortly', expected
=True)
189 for m3u8_playlist_url
in m3u8_playlist_urls
:
190 formats
.extend(self
._extract
_m
3u8_formats
(m3u8_playlist_url
, video_id
))
191 # FIXME: HTTP request headers need to be updated to continue download
192 warning
= 'Due to technical limitations, the download will be interrupted after one hour'
193 if live_status
== 'is_live':
194 self
.report_warning(warning
)
195 elif live_status
== 'was_live':
196 self
.report_warning(f
'{warning}. You can restart to continue the download')
199 'id': data_json
['app_id'],
200 'title': data_json
.get('app_name'),
202 'live_status': live_status
,
203 'description': data_json
.get('content'),
204 'release_timestamp': release_timestamp
,