]>
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 M |
18 | _TESTS = [{ |
19 | 'url': 'https://live.eplus.jp/ex/player?ib=YEFxb3Vyc2Dombnjg7blkrLlrablnJLjgrnjgq%2Fjg7zjg6vjgqLjgqTjg4njg6vlkIzlpb3kvJpgTGllbGxhIQ%3D%3D', | |
20 | 'info_dict': { | |
21 | 'id': '354502-0001-002', | |
22 | 'title': 'LoveLive!Series Presents COUNTDOWN LoveLive! 2021→2022~LIVE with a smile!~【Streaming+(配信)】', | |
23 | 'live_status': 'was_live', | |
24 | 'release_date': '20211231', | |
25 | 'release_timestamp': 1640952000, | |
26 | 'description': str, | |
27 | }, | |
28 | 'params': { | |
29 | 'skip_download': True, | |
30 | 'ignore_no_formats_error': True, | |
31 | }, | |
32 | 'expected_warnings': [ | |
33 | 'Could not find the playlist URL. This event may not be accessible', | |
34 | 'No video formats found!', | |
35 | 'Requested format is not available', | |
36 | ], | |
d5d1517e M |
37 | }, { |
38 | 'url': 'https://live.eplus.jp/sample', | |
39 | 'info_dict': { | |
40 | 'id': 'stream1ng20210719-test-005', | |
41 | 'title': 'Online streaming test for DRM', | |
42 | 'live_status': 'was_live', | |
43 | 'release_date': '20210719', | |
44 | 'release_timestamp': 1626703200, | |
d5d1517e M |
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 | 'This video is DRM protected', | |
55 | ], | |
56 | }, { | |
57 | 'url': 'https://live.eplus.jp/2053935', | |
58 | 'info_dict': { | |
59 | 'id': '331320-0001-001', | |
60 | 'title': '丘みどり2020配信LIVE Vol.2 ~秋麗~ 【Streaming+(配信チケット)】', | |
61 | 'live_status': 'was_live', | |
62 | 'release_date': '20200920', | |
63 | 'release_timestamp': 1600596000, | |
64 | }, | |
65 | 'params': { | |
66 | 'skip_download': True, | |
67 | 'ignore_no_formats_error': True, | |
68 | }, | |
69 | 'expected_warnings': [ | |
70 | 'Could not find the playlist URL. This event may not be accessible', | |
71 | 'No video formats found!', | |
72 | 'Requested format is not available', | |
73 | ], | |
295fbb3a M |
74 | }] |
75 | ||
d5d1517e M |
76 | _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' |
77 | ||
78 | def _login(self, username, password, urlh): | |
79 | if not self._get_cookies('https://live.eplus.jp/').get('ci_session'): | |
80 | raise ExtractorError('Unable to get ci_session cookie') | |
81 | ||
82 | cltft_token = urlh.headers.get('X-CLTFT-Token') | |
83 | if not cltft_token: | |
84 | raise ExtractorError('Unable to get X-CLTFT-Token') | |
85 | self._set_cookie('live.eplus.jp', 'X-CLTFT-Token', cltft_token) | |
86 | ||
87 | login_json = self._download_json( | |
88 | 'https://live.eplus.jp/member/api/v1/FTAuth/idpw', None, | |
89 | note='Sending pre-login info', errnote='Unable to send pre-login info', headers={ | |
90 | 'Content-Type': 'application/json; charset=UTF-8', | |
91 | 'Referer': urlh.url, | |
92 | 'X-Cltft-Token': cltft_token, | |
93 | 'Accept': '*/*', | |
94 | }, data=json.dumps({ | |
95 | 'loginId': username, | |
96 | 'loginPassword': password, | |
97 | }).encode()) | |
98 | if not login_json.get('isSuccess'): | |
99 | raise ExtractorError('Login failed: Invalid id or password', expected=True) | |
100 | ||
101 | self._request_webpage( | |
102 | urlh.url, None, note='Logging in', errnote='Unable to log in', | |
103 | data=urlencode_postdata({ | |
104 | 'loginId': username, | |
105 | 'loginPassword': password, | |
106 | 'Token.Default': cltft_token, | |
107 | 'op': 'nextPage', | |
108 | }), headers={'Referer': urlh.url}) | |
109 | ||
295fbb3a M |
110 | def _real_extract(self, url): |
111 | video_id = self._match_id(url) | |
d5d1517e M |
112 | webpage, urlh = self._download_webpage_handle( |
113 | url, video_id, headers={'User-Agent': self._USER_AGENT}) | |
114 | if urlh.url.startswith('https://live.eplus.jp/member/auth'): | |
115 | username, password = self._get_login_info() | |
116 | if not username: | |
117 | self.raise_login_required() | |
118 | self._login(username, password, urlh) | |
119 | webpage = self._download_webpage( | |
120 | url, video_id, headers={'User-Agent': self._USER_AGENT}) | |
295fbb3a M |
121 | |
122 | data_json = self._search_json(r'<script>\s*var app\s*=', webpage, 'data json', video_id) | |
123 | ||
d5d1517e M |
124 | if data_json.get('drm_mode') == 'ON': |
125 | self.report_drm(video_id) | |
126 | ||
295fbb3a M |
127 | delivery_status = data_json.get('delivery_status') |
128 | archive_mode = data_json.get('archive_mode') | |
129 | release_timestamp = try_call(lambda: unified_timestamp(data_json['event_datetime']) - 32400) | |
130 | release_timestamp_str = data_json.get('event_datetime_text') # JST | |
131 | ||
132 | self.write_debug(f'delivery_status = {delivery_status}, archive_mode = {archive_mode}') | |
133 | ||
134 | if delivery_status == 'PREPARING': | |
135 | live_status = 'is_upcoming' | |
136 | elif delivery_status == 'STARTED': | |
137 | live_status = 'is_live' | |
138 | elif delivery_status == 'STOPPED': | |
139 | if archive_mode != 'ON': | |
140 | raise ExtractorError( | |
141 | 'This event has ended and there is no archive for this event', expected=True) | |
142 | live_status = 'post_live' | |
143 | elif delivery_status == 'WAIT_CONFIRM_ARCHIVED': | |
144 | live_status = 'post_live' | |
145 | elif delivery_status == 'CONFIRMED_ARCHIVE': | |
146 | live_status = 'was_live' | |
147 | else: | |
148 | self.report_warning(f'Unknown delivery_status {delivery_status}, treat it as a live') | |
149 | live_status = 'is_live' | |
150 | ||
151 | formats = [] | |
152 | ||
153 | m3u8_playlist_urls = self._search_json( | |
d5d1517e | 154 | r'var\s+listChannels\s*=', webpage, 'hls URLs', video_id, contains_pattern=r'\[.+\]', default=[]) |
295fbb3a M |
155 | if not m3u8_playlist_urls: |
156 | if live_status == 'is_upcoming': | |
157 | self.raise_no_formats( | |
158 | f'Could not find the playlist URL. This live event will begin at {release_timestamp_str} JST', expected=True) | |
159 | else: | |
160 | self.raise_no_formats( | |
161 | 'Could not find the playlist URL. This event may not be accessible', expected=True) | |
162 | elif live_status == 'is_upcoming': | |
163 | self.raise_no_formats(f'This live event will begin at {release_timestamp_str} JST', expected=True) | |
164 | elif live_status == 'post_live': | |
165 | self.raise_no_formats('This event has ended, and the archive will be available shortly', expected=True) | |
166 | else: | |
167 | for m3u8_playlist_url in m3u8_playlist_urls: | |
168 | formats.extend(self._extract_m3u8_formats(m3u8_playlist_url, video_id)) | |
169 | # FIXME: HTTP request headers need to be updated to continue download | |
170 | warning = 'Due to technical limitations, the download will be interrupted after one hour' | |
171 | if live_status == 'is_live': | |
172 | self.report_warning(warning) | |
173 | elif live_status == 'was_live': | |
174 | self.report_warning(f'{warning}. You can restart to continue the download') | |
175 | ||
176 | return { | |
177 | 'id': data_json['app_id'], | |
178 | 'title': data_json.get('app_name'), | |
179 | 'formats': formats, | |
180 | 'live_status': live_status, | |
181 | 'description': data_json.get('content'), | |
182 | 'release_timestamp': release_timestamp, | |
183 | } |