]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/mildom.py
Allow extractors to specify section_start/end for clips
[yt-dlp.git] / yt_dlp / extractor / mildom.py
CommitLineData
fb6e3f43 1import functools
ffcb8191 2import json
f9934b96 3import uuid
ffcb8191
THD
4
5from .common import InfoExtractor
6from ..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
16class 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
39class 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
91class 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
184class 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
248class 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"]}')