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