]>
Commit | Line | Data |
---|---|---|
fb6e3f43 | 1 | import functools |
ffcb8191 | 2 | import json |
f9934b96 | 3 | import uuid |
ffcb8191 THD |
4 | |
5 | from .common import InfoExtractor | |
6 | from ..utils import ( | |
fb6e3f43 LNO |
7 | determine_ext, |
8 | dict_get, | |
9 | ExtractorError, | |
63bac931 | 10 | float_or_none, |
fb6e3f43 | 11 | OnDemandPagedList, |
fb6e3f43 | 12 | traverse_obj, |
ffcb8191 THD |
13 | ) |
14 | ||
15 | ||
16 | class MildomBaseIE(InfoExtractor): | |
17 | _GUEST_ID = None | |
ffcb8191 | 18 | |
fb6e3f43 LNO |
19 | def _call_api(self, url, video_id, query=None, note='Downloading JSON metadata', body=None): |
20 | if not self._GUEST_ID: | |
f9934b96 | 21 | self._GUEST_ID = f'pc-gp-{str(uuid.uuid4())}' |
fb6e3f43 LNO |
22 | |
23 | content = self._download_json( | |
24 | url, video_id, note=note, data=json.dumps(body).encode() if body else None, | |
25 | headers={'Content-Type': 'application/json'} if body else {}, | |
26 | query={ | |
27 | '__guest_id': self._GUEST_ID, | |
28 | '__platform': 'web', | |
29 | **(query or {}), | |
30 | }) | |
31 | ||
32 | if content['code'] != 0: | |
33 | raise ExtractorError( | |
34 | f'Mildom says: {content["message"]} (code {content["code"]})', | |
35 | expected=True) | |
36 | return content['body'] | |
ffcb8191 THD |
37 | |
38 | ||
39 | class MildomIE(MildomBaseIE): | |
40 | IE_NAME = 'mildom' | |
41 | IE_DESC = 'Record ongoing live by specific user in Mildom' | |
42 | _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/(?P<id>\d+)' | |
43 | ||
44 | def _real_extract(self, url): | |
45 | video_id = self._match_id(url) | |
fb6e3f43 | 46 | webpage = self._download_webpage(f'https://www.mildom.com/{video_id}', video_id) |
ffcb8191 THD |
47 | |
48 | enterstudio = self._call_api( | |
49 | 'https://cloudac.mildom.com/nonolive/gappserv/live/enterstudio', video_id, | |
50 | note='Downloading live metadata', query={'user_id': video_id}) | |
5d39972e | 51 | result_video_id = enterstudio.get('log_id', video_id) |
ffcb8191 | 52 | |
ffcb8191 | 53 | servers = self._call_api( |
5d39972e | 54 | 'https://cloudac.mildom.com/nonolive/gappserv/live/liveserver', result_video_id, |
ffcb8191 THD |
55 | note='Downloading live server list', query={ |
56 | 'user_id': video_id, | |
57 | 'live_server_type': 'hls', | |
58 | }) | |
59 | ||
fb6e3f43 LNO |
60 | playback_token = self._call_api( |
61 | 'https://cloudac.mildom.com/nonolive/gappserv/live/token', result_video_id, | |
62 | note='Obtaining live playback token', body={'host_id': video_id, 'type': 'hls'}) | |
63 | playback_token = traverse_obj(playback_token, ('data', ..., 'token'), get_all=False) | |
64 | if not playback_token: | |
65 | raise ExtractorError('Failed to obtain live playback token') | |
66 | ||
67 | formats = self._extract_m3u8_formats( | |
68 | f'{servers["stream_server"]}/{video_id}_master.m3u8?{playback_token}', | |
69 | result_video_id, 'mp4', headers={ | |
70 | 'Referer': 'https://www.mildom.com/', | |
71 | 'Origin': 'https://www.mildom.com', | |
72 | }) | |
73 | ||
ffcb8191 | 74 | for fmt in formats: |
a471f21d | 75 | fmt.setdefault('http_headers', {})['Referer'] = 'https://www.mildom.com/' |
ffcb8191 THD |
76 | |
77 | self._sort_formats(formats) | |
78 | ||
79 | return { | |
5d39972e | 80 | 'id': result_video_id, |
fb6e3f43 LNO |
81 | 'title': self._html_search_meta('twitter:description', webpage, default=None) or traverse_obj(enterstudio, 'anchor_intro'), |
82 | 'description': traverse_obj(enterstudio, 'intro', 'live_intro', expected_type=str), | |
63bac931 | 83 | 'timestamp': float_or_none(enterstudio.get('live_start_ms'), scale=1000), |
fb6e3f43 | 84 | 'uploader': self._html_search_meta('twitter:title', webpage, default=None) or traverse_obj(enterstudio, 'loginname'), |
ffcb8191 THD |
85 | 'uploader_id': video_id, |
86 | 'formats': formats, | |
87 | 'is_live': True, | |
88 | } | |
89 | ||
90 | ||
91 | class MildomVodIE(MildomBaseIE): | |
92 | IE_NAME = 'mildom:vod' | |
fb6e3f43 | 93 | IE_DESC = 'VOD in Mildom' |
63bac931 | 94 | _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/playback/(?P<user_id>\d+)/(?P<id>(?P=user_id)-[a-zA-Z0-9]+-?[0-9]*)' |
95 | _TESTS = [{ | |
96 | 'url': 'https://www.mildom.com/playback/10882672/10882672-1597662269', | |
97 | 'info_dict': { | |
98 | 'id': '10882672-1597662269', | |
99 | 'ext': 'mp4', | |
100 | 'title': '始めてのミルダム配信じゃぃ!', | |
101 | 'thumbnail': r're:^https?://.*\.(png|jpg)$', | |
102 | 'upload_date': '20200817', | |
103 | 'duration': 4138.37, | |
104 | 'description': 'ゲームをしたくて!', | |
105 | 'timestamp': 1597662269.0, | |
106 | 'uploader_id': '10882672', | |
107 | 'uploader': 'kson組長(けいそん)', | |
108 | }, | |
109 | }, { | |
110 | 'url': 'https://www.mildom.com/playback/10882672/10882672-1597758589870-477', | |
111 | 'info_dict': { | |
112 | 'id': '10882672-1597758589870-477', | |
113 | 'ext': 'mp4', | |
114 | 'title': '【kson】感染メイズ!麻酔銃で無双する', | |
115 | 'thumbnail': r're:^https?://.*\.(png|jpg)$', | |
116 | 'timestamp': 1597759093.0, | |
117 | 'uploader': 'kson組長(けいそん)', | |
118 | 'duration': 4302.58, | |
119 | 'uploader_id': '10882672', | |
120 | 'description': 'このステージ絶対乗り越えたい', | |
121 | 'upload_date': '20200818', | |
122 | }, | |
123 | }, { | |
124 | 'url': 'https://www.mildom.com/playback/10882672/10882672-buha9td2lrn97fk2jme0', | |
125 | 'info_dict': { | |
126 | 'id': '10882672-buha9td2lrn97fk2jme0', | |
127 | 'ext': 'mp4', | |
128 | 'title': '【kson組長】CART RACER!!!', | |
129 | 'thumbnail': r're:^https?://.*\.(png|jpg)$', | |
130 | 'uploader_id': '10882672', | |
131 | 'uploader': 'kson組長(けいそん)', | |
132 | 'upload_date': '20201104', | |
133 | 'timestamp': 1604494797.0, | |
134 | 'duration': 4657.25, | |
135 | 'description': 'WTF', | |
136 | }, | |
137 | }] | |
ffcb8191 THD |
138 | |
139 | def _real_extract(self, url): | |
fb6e3f43 LNO |
140 | user_id, video_id = self._match_valid_url(url).group('user_id', 'id') |
141 | webpage = self._download_webpage(f'https://www.mildom.com/playback/{user_id}/{video_id}', video_id) | |
ffcb8191 THD |
142 | |
143 | autoplay = self._call_api( | |
144 | 'https://cloudac.mildom.com/nonolive/videocontent/playback/getPlaybackDetail', video_id, | |
145 | note='Downloading playback metadata', query={ | |
146 | 'v_id': video_id, | |
147 | })['playback'] | |
148 | ||
c1df120e | 149 | formats = [{ |
ffcb8191 THD |
150 | 'url': autoplay['audio_url'], |
151 | 'format_id': 'audio', | |
152 | 'protocol': 'm3u8_native', | |
153 | 'vcodec': 'none', | |
154 | 'acodec': 'aac', | |
c1df120e | 155 | 'ext': 'm4a' |
ffcb8191 | 156 | }] |
ffcb8191 | 157 | for fmt in autoplay['video_link']: |
c1df120e | 158 | formats.append({ |
ffcb8191 THD |
159 | 'format_id': 'video-%s' % fmt['name'], |
160 | 'url': fmt['url'], | |
161 | 'protocol': 'm3u8_native', | |
162 | 'width': fmt['level'] * autoplay['video_width'] // autoplay['video_height'], | |
163 | 'height': fmt['level'], | |
164 | 'vcodec': 'h264', | |
165 | 'acodec': 'aac', | |
c1df120e | 166 | 'ext': 'mp4' |
ffcb8191 THD |
167 | }) |
168 | ||
ffcb8191 THD |
169 | self._sort_formats(formats) |
170 | ||
171 | return { | |
172 | 'id': video_id, | |
fb6e3f43 LNO |
173 | 'title': self._html_search_meta(('og:description', 'description'), webpage, default=None) or autoplay.get('title'), |
174 | 'description': traverse_obj(autoplay, 'video_intro'), | |
175 | 'timestamp': float_or_none(autoplay.get('publish_time'), scale=1000), | |
176 | 'duration': float_or_none(autoplay.get('video_length'), scale=1000), | |
63bac931 | 177 | 'thumbnail': dict_get(autoplay, ('upload_pic', 'video_pic')), |
fb6e3f43 | 178 | 'uploader': traverse_obj(autoplay, ('author_info', 'login_name')), |
ffcb8191 THD |
179 | 'uploader_id': user_id, |
180 | 'formats': formats, | |
181 | } | |
182 | ||
183 | ||
fb6e3f43 LNO |
184 | class MildomClipIE(MildomBaseIE): |
185 | IE_NAME = 'mildom:clip' | |
186 | IE_DESC = 'Clip in Mildom' | |
187 | _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/clip/(?P<id>(?P<user_id>\d+)-[a-zA-Z0-9]+)' | |
188 | _TESTS = [{ | |
189 | 'url': 'https://www.mildom.com/clip/10042245-63921673e7b147ebb0806d42b5ba5ce9', | |
190 | 'info_dict': { | |
191 | 'id': '10042245-63921673e7b147ebb0806d42b5ba5ce9', | |
192 | 'title': '全然違ったよ', | |
193 | 'timestamp': 1619181890, | |
194 | 'duration': 59, | |
195 | 'thumbnail': r're:https?://.+', | |
196 | 'uploader': 'ざきんぽ', | |
197 | 'uploader_id': '10042245', | |
198 | }, | |
199 | }, { | |
200 | 'url': 'https://www.mildom.com/clip/10111524-ebf4036e5aa8411c99fb3a1ae0902864', | |
201 | 'info_dict': { | |
202 | 'id': '10111524-ebf4036e5aa8411c99fb3a1ae0902864', | |
203 | 'title': 'かっこいい', | |
204 | 'timestamp': 1621094003, | |
205 | 'duration': 59, | |
206 | 'thumbnail': r're:https?://.+', | |
207 | 'uploader': '(ルーキー', | |
208 | 'uploader_id': '10111524', | |
209 | }, | |
210 | }, { | |
211 | 'url': 'https://www.mildom.com/clip/10660174-2c539e6e277c4aaeb4b1fbe8d22cb902', | |
212 | 'info_dict': { | |
213 | 'id': '10660174-2c539e6e277c4aaeb4b1fbe8d22cb902', | |
214 | 'title': 'あ', | |
215 | 'timestamp': 1614769431, | |
216 | 'duration': 31, | |
217 | 'thumbnail': r're:https?://.+', | |
218 | 'uploader': 'ドルゴルスレンギーン=ダグワドルジ', | |
219 | 'uploader_id': '10660174', | |
220 | }, | |
221 | }] | |
222 | ||
223 | def _real_extract(self, url): | |
224 | user_id, video_id = self._match_valid_url(url).group('user_id', 'id') | |
225 | webpage = self._download_webpage(f'https://www.mildom.com/clip/{video_id}', video_id) | |
226 | ||
227 | clip_detail = self._call_api( | |
228 | 'https://cloudac-cf-jp.mildom.com/nonolive/videocontent/clip/detail', video_id, | |
229 | note='Downloading playback metadata', query={ | |
230 | 'clip_id': video_id, | |
231 | }) | |
232 | ||
233 | return { | |
234 | 'id': video_id, | |
235 | 'title': self._html_search_meta( | |
236 | ('og:description', 'description'), webpage, default=None) or clip_detail.get('title'), | |
237 | 'timestamp': float_or_none(clip_detail.get('create_time')), | |
238 | 'duration': float_or_none(clip_detail.get('length')), | |
239 | 'thumbnail': clip_detail.get('cover'), | |
240 | 'uploader': traverse_obj(clip_detail, ('user_info', 'loginname')), | |
241 | 'uploader_id': user_id, | |
242 | ||
243 | 'url': clip_detail['url'], | |
244 | 'ext': determine_ext(clip_detail.get('url'), 'mp4'), | |
245 | } | |
246 | ||
247 | ||
ffcb8191 THD |
248 | class MildomUserVodIE(MildomBaseIE): |
249 | IE_NAME = 'mildom:user:vod' | |
250 | IE_DESC = 'Download all VODs from specific user in Mildom' | |
251 | _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/profile/(?P<id>\d+)' | |
252 | _TESTS = [{ | |
253 | 'url': 'https://www.mildom.com/profile/10093333', | |
254 | 'info_dict': { | |
255 | 'id': '10093333', | |
256 | 'title': 'Uploads from ねこばたけ', | |
257 | }, | |
fb6e3f43 | 258 | 'playlist_mincount': 732, |
63bac931 | 259 | }, { |
260 | 'url': 'https://www.mildom.com/profile/10882672', | |
261 | 'info_dict': { | |
262 | 'id': '10882672', | |
263 | 'title': 'Uploads from kson組長(けいそん)', | |
264 | }, | |
fb6e3f43 | 265 | 'playlist_mincount': 201, |
ffcb8191 THD |
266 | }] |
267 | ||
fb6e3f43 LNO |
268 | def _fetch_page(self, user_id, page): |
269 | page += 1 | |
270 | reply = self._call_api( | |
271 | 'https://cloudac.mildom.com/nonolive/videocontent/profile/playbackList', | |
272 | user_id, note=f'Downloading page {page}', query={ | |
273 | 'user_id': user_id, | |
274 | 'page': page, | |
275 | 'limit': '30', | |
276 | }) | |
277 | if not reply: | |
278 | return | |
279 | for x in reply: | |
280 | v_id = x.get('v_id') | |
281 | if not v_id: | |
282 | continue | |
283 | yield self.url_result(f'https://www.mildom.com/playback/{user_id}/{v_id}') | |
3097d9e5 | 284 | |
285 | def _real_extract(self, url): | |
286 | user_id = self._match_id(url) | |
287 | self.to_screen('This will download all VODs belonging to user. To download ongoing live video, use "https://www.mildom.com/%s" instead' % user_id) | |
288 | ||
289 | profile = self._call_api( | |
290 | 'https://cloudac.mildom.com/nonolive/gappserv/user/profileV2', user_id, | |
291 | query={'user_id': user_id}, note='Downloading user profile')['user_info'] | |
292 | ||
293 | return self.playlist_result( | |
fb6e3f43 LNO |
294 | OnDemandPagedList(functools.partial(self._fetch_page, user_id), 30), |
295 | user_id, f'Uploads from {profile["loginname"]}') |