]>
Commit | Line | Data |
---|---|---|
d5d1517e M |
1 | import json |
2 | ||
295fbb3a M |
3 | from .common import InfoExtractor |
4 | from ..utils import ( | |
5 | ExtractorError, | |
6 | try_call, | |
7 | unified_timestamp, | |
d5d1517e | 8 | urlencode_postdata, |
295fbb3a M |
9 | ) |
10 | ||
11 | ||
12 | class EplusIbIE(InfoExtractor): | |
d5d1517e M |
13 | _NETRC_MACHINE = 'eplus' |
14 | IE_NAME = '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+)'] | |
295fbb3a | 18 | _TESTS = [{ |
351dc0bc | 19 | 'url': 'https://live.eplus.jp/ex/player?ib=41K6Wzbr3PlcMD%2FOKHFlC%2FcZCe2Eaw7FK%2BpJS1ooUHki8d0vGSy2mYqxillQBe1dSnOxU%2B8%2FzXKls4XPBSb3vw%3D%3D', |
295fbb3a | 20 | 'info_dict': { |
351dc0bc M |
21 | 'id': '335699-0001-006', |
22 | 'title': '少女☆歌劇 レヴュースタァライト -The LIVE 青嵐- BLUE GLITTER <定点映像配信>【Streaming+(配信)】', | |
295fbb3a | 23 | 'live_status': 'was_live', |
351dc0bc M |
24 | 'release_date': '20201221', |
25 | 'release_timestamp': 1608544800, | |
26 | }, | |
27 | 'params': { | |
28 | 'skip_download': True, | |
29 | 'ignore_no_formats_error': True, | |
30 | }, | |
31 | 'expected_warnings': [ | |
32 | 'This event may not be accessible', | |
33 | 'No video formats found', | |
34 | 'Requested format is not available', | |
35 | ], | |
36 | }, { | |
37 | 'url': 'https://live.eplus.jp/ex/player?ib=6QSsQdyRAwOFZrEHWlhRm7vocgV%2FO0YzBZ%2BaBEBg1XR%2FmbLn0R%2F048dUoAY038%2F%2F92MJ73BsoAtvUpbV6RLtDQ%3D%3D&show_id=2371511', | |
38 | 'info_dict': { | |
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, | |
295fbb3a M |
44 | 'description': str, |
45 | }, | |
46 | 'params': { | |
47 | 'skip_download': True, | |
48 | 'ignore_no_formats_error': True, | |
49 | }, | |
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', | |
54 | ], | |
d5d1517e M |
55 | }, { |
56 | 'url': 'https://live.eplus.jp/sample', | |
57 | 'info_dict': { | |
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, | |
d5d1517e M |
63 | }, |
64 | 'params': { | |
65 | 'skip_download': True, | |
66 | 'ignore_no_formats_error': True, | |
67 | }, | |
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', | |
73 | ], | |
74 | }, { | |
75 | 'url': 'https://live.eplus.jp/2053935', | |
76 | 'info_dict': { | |
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, | |
82 | }, | |
83 | 'params': { | |
84 | 'skip_download': True, | |
85 | 'ignore_no_formats_error': True, | |
86 | }, | |
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', | |
91 | ], | |
295fbb3a M |
92 | }] |
93 | ||
d5d1517e M |
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' |
95 | ||
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') | |
99 | ||
100 | cltft_token = urlh.headers.get('X-CLTFT-Token') | |
101 | if not cltft_token: | |
102 | raise ExtractorError('Unable to get X-CLTFT-Token') | |
103 | self._set_cookie('live.eplus.jp', 'X-CLTFT-Token', cltft_token) | |
104 | ||
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', | |
109 | 'Referer': urlh.url, | |
110 | 'X-Cltft-Token': cltft_token, | |
111 | 'Accept': '*/*', | |
112 | }, data=json.dumps({ | |
113 | 'loginId': username, | |
114 | 'loginPassword': password, | |
115 | }).encode()) | |
116 | if not login_json.get('isSuccess'): | |
117 | raise ExtractorError('Login failed: Invalid id or password', expected=True) | |
118 | ||
119 | self._request_webpage( | |
120 | urlh.url, None, note='Logging in', errnote='Unable to log in', | |
121 | data=urlencode_postdata({ | |
122 | 'loginId': username, | |
123 | 'loginPassword': password, | |
124 | 'Token.Default': cltft_token, | |
125 | 'op': 'nextPage', | |
126 | }), headers={'Referer': urlh.url}) | |
127 | ||
295fbb3a M |
128 | def _real_extract(self, url): |
129 | video_id = self._match_id(url) | |
d5d1517e M |
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() | |
134 | if not username: | |
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}) | |
295fbb3a M |
139 | |
140 | data_json = self._search_json(r'<script>\s*var app\s*=', webpage, 'data json', video_id) | |
141 | ||
d5d1517e M |
142 | if data_json.get('drm_mode') == 'ON': |
143 | self.report_drm(video_id) | |
144 | ||
351dc0bc M |
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) | |
148 | ||
295fbb3a M |
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 | |
153 | ||
154 | self.write_debug(f'delivery_status = {delivery_status}, archive_mode = {archive_mode}') | |
155 | ||
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' | |
169 | else: | |
170 | self.report_warning(f'Unknown delivery_status {delivery_status}, treat it as a live') | |
171 | live_status = 'is_live' | |
172 | ||
173 | formats = [] | |
174 | ||
175 | m3u8_playlist_urls = self._search_json( | |
d5d1517e | 176 | r'var\s+listChannels\s*=', webpage, 'hls URLs', video_id, contains_pattern=r'\[.+\]', default=[]) |
295fbb3a M |
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) | |
181 | else: | |
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) | |
188 | else: | |
189 | for m3u8_playlist_url in m3u8_playlist_urls: | |
190 | formats.extend(self._extract_m3u8_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') | |
197 | ||
198 | return { | |
199 | 'id': data_json['app_id'], | |
200 | 'title': data_json.get('app_name'), | |
201 | 'formats': formats, | |
202 | 'live_status': live_status, | |
203 | 'description': data_json.get('content'), | |
204 | 'release_timestamp': release_timestamp, | |
205 | } |