]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/mildom.py
[cleanup] Mark some compat variables for removal (#2173)
[yt-dlp.git] / yt_dlp / extractor / mildom.py
CommitLineData
ffcb8191
THD
1# coding: utf-8
2from __future__ import unicode_literals
3
fb6e3f43 4import functools
ffcb8191 5import json
f9934b96 6import uuid
ffcb8191
THD
7
8from .common import InfoExtractor
9from ..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
19class 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
42class 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
94class 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
187class 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
251class 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"]}')