2 from __future__
import unicode_literals
7 from .common
import InfoExtractor
19 class MildomBaseIE(InfoExtractor
):
22 def _call_api(self
, url
, video_id
, query
=None, note
='Downloading JSON metadata', body
=None):
23 if not self
._GUEST
_ID
:
24 self
._GUEST
_ID
= f
'pc-gp-{random_uuidv4()}'
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 {},
30 '__guest_id': self
._GUEST
_ID
,
35 if content
['code'] != 0:
37 f
'Mildom says: {content["message"]} (code {content["code"]})',
39 return content
['body']
42 class MildomIE(MildomBaseIE
):
44 IE_DESC
= 'Record ongoing live by specific user in Mildom'
45 _VALID_URL
= r
'https?://(?:(?:www|m)\.)mildom\.com/(?P<id>\d+)'
47 def _real_extract(self
, url
):
48 video_id
= self
._match
_id
(url
)
49 webpage
= self
._download
_webpage
(f
'https://www.mildom.com/{video_id}', video_id
)
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}
)
54 result_video_id
= enterstudio
.get('log_id', video_id
)
56 servers
= self
._call
_api
(
57 'https://cloudac.mildom.com/nonolive/gappserv/live/liveserver', result_video_id
,
58 note
='Downloading live server list', query
={
60 'live_server_type': 'hls',
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')
70 formats
= self
._extract
_m
3u8_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',
78 fmt
.setdefault('http_headers', {})['Referer'] = 'https://www.mildom.com/'
80 self
._sort
_formats
(formats
)
83 'id': result_video_id
,
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),
86 'timestamp': float_or_none(enterstudio
.get('live_start_ms'), scale
=1000),
87 'uploader': self
._html
_search
_meta
('twitter:title', webpage
, default
=None) or traverse_obj(enterstudio
, 'loginname'),
88 'uploader_id': video_id
,
94 class MildomVodIE(MildomBaseIE
):
95 IE_NAME
= 'mildom:vod'
96 IE_DESC
= 'VOD in Mildom'
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]*)'
99 'url': 'https://www.mildom.com/playback/10882672/10882672-1597662269',
101 'id': '10882672-1597662269',
103 'title': '始めてのミルダム配信じゃぃ!',
104 'thumbnail': r
're:^https?://.*\.(png|jpg)$',
105 'upload_date': '20200817',
107 'description': 'ゲームをしたくて!',
108 'timestamp': 1597662269.0,
109 'uploader_id': '10882672',
110 'uploader': 'kson組長(けいそん)',
113 'url': 'https://www.mildom.com/playback/10882672/10882672-1597758589870-477',
115 'id': '10882672-1597758589870-477',
117 'title': '【kson】感染メイズ!麻酔銃で無双する',
118 'thumbnail': r
're:^https?://.*\.(png|jpg)$',
119 'timestamp': 1597759093.0,
120 'uploader': 'kson組長(けいそん)',
122 'uploader_id': '10882672',
123 'description': 'このステージ絶対乗り越えたい',
124 'upload_date': '20200818',
127 'url': 'https://www.mildom.com/playback/10882672/10882672-buha9td2lrn97fk2jme0',
129 'id': '10882672-buha9td2lrn97fk2jme0',
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,
138 'description': 'WTF',
142 def _real_extract(self
, url
):
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
)
146 autoplay
= self
._call
_api
(
147 'https://cloudac.mildom.com/nonolive/videocontent/playback/getPlaybackDetail', video_id
,
148 note
='Downloading playback metadata', query
={
153 'url': autoplay
['audio_url'],
154 'format_id': 'audio',
155 'protocol': 'm3u8_native',
160 for fmt
in autoplay
['video_link']:
162 'format_id': 'video-%s' % fmt
['name'],
164 'protocol': 'm3u8_native',
165 'width': fmt
['level'] * autoplay
['video_width'] // autoplay
['video_height'],
166 'height': fmt
['level'],
172 self
._sort
_formats
(formats
)
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),
180 'thumbnail': dict_get(autoplay
, ('upload_pic', 'video_pic')),
181 'uploader': traverse_obj(autoplay
, ('author_info', 'login_name')),
182 'uploader_id': user_id
,
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]+)'
192 'url': 'https://www.mildom.com/clip/10042245-63921673e7b147ebb0806d42b5ba5ce9',
194 'id': '10042245-63921673e7b147ebb0806d42b5ba5ce9',
196 'timestamp': 1619181890,
198 'thumbnail': r
're:https?://.+',
200 'uploader_id': '10042245',
203 'url': 'https://www.mildom.com/clip/10111524-ebf4036e5aa8411c99fb3a1ae0902864',
205 'id': '10111524-ebf4036e5aa8411c99fb3a1ae0902864',
207 'timestamp': 1621094003,
209 'thumbnail': r
're:https?://.+',
211 'uploader_id': '10111524',
214 'url': 'https://www.mildom.com/clip/10660174-2c539e6e277c4aaeb4b1fbe8d22cb902',
216 'id': '10660174-2c539e6e277c4aaeb4b1fbe8d22cb902',
218 'timestamp': 1614769431,
220 'thumbnail': r
're:https?://.+',
221 'uploader': 'ドルゴルスレンギーン=ダグワドルジ',
222 'uploader_id': '10660174',
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
)
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
={
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
,
246 'url': clip_detail
['url'],
247 'ext': determine_ext(clip_detail
.get('url'), 'mp4'),
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+)'
256 'url': 'https://www.mildom.com/profile/10093333',
259 'title': 'Uploads from ねこばたけ',
261 'playlist_mincount': 732,
263 'url': 'https://www.mildom.com/profile/10882672',
266 'title': 'Uploads from kson組長(けいそん)',
268 'playlist_mincount': 201,
271 def _fetch_page(self
, user_id
, page
):
273 reply
= self
._call
_api
(
274 'https://cloudac.mildom.com/nonolive/videocontent/profile/playbackList',
275 user_id
, note
=f
'Downloading page {page}', query
={
286 yield self
.url_result(f
'https://www.mildom.com/playback/{user_id}/{v_id}')
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
)
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']
296 return self
.playlist_result(
297 OnDemandPagedList(functools
.partial(self
._fetch
_page
, user_id
), 30),
298 user_id
, f
'Uploads from {profile["loginname"]}')