diff options
Diffstat (limited to 'yt_dlp/extractor/mildom.py')
-rw-r--r-- | yt_dlp/extractor/mildom.py | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/yt_dlp/extractor/mildom.py b/yt_dlp/extractor/mildom.py new file mode 100644 index 0000000..f64d575 --- /dev/null +++ b/yt_dlp/extractor/mildom.py @@ -0,0 +1,291 @@ +import functools +import json +import uuid + +from .common import InfoExtractor +from ..utils import ( + determine_ext, + dict_get, + ExtractorError, + float_or_none, + OnDemandPagedList, + traverse_obj, +) + + +class MildomBaseIE(InfoExtractor): + _GUEST_ID = None + + def _call_api(self, url, video_id, query=None, note='Downloading JSON metadata', body=None): + if not self._GUEST_ID: + self._GUEST_ID = f'pc-gp-{str(uuid.uuid4())}' + + content = self._download_json( + url, video_id, note=note, data=json.dumps(body).encode() if body else None, + headers={'Content-Type': 'application/json'} if body else {}, + query={ + '__guest_id': self._GUEST_ID, + '__platform': 'web', + **(query or {}), + }) + + if content['code'] != 0: + raise ExtractorError( + f'Mildom says: {content["message"]} (code {content["code"]})', + expected=True) + return content['body'] + + +class MildomIE(MildomBaseIE): + IE_NAME = 'mildom' + IE_DESC = 'Record ongoing live by specific user in Mildom' + _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/(?P<id>\d+)' + + def _real_extract(self, url): + video_id = self._match_id(url) + webpage = self._download_webpage(f'https://www.mildom.com/{video_id}', video_id) + + enterstudio = self._call_api( + 'https://cloudac.mildom.com/nonolive/gappserv/live/enterstudio', video_id, + note='Downloading live metadata', query={'user_id': video_id}) + result_video_id = enterstudio.get('log_id', video_id) + + servers = self._call_api( + 'https://cloudac.mildom.com/nonolive/gappserv/live/liveserver', result_video_id, + note='Downloading live server list', query={ + 'user_id': video_id, + 'live_server_type': 'hls', + }) + + playback_token = self._call_api( + 'https://cloudac.mildom.com/nonolive/gappserv/live/token', result_video_id, + note='Obtaining live playback token', body={'host_id': video_id, 'type': 'hls'}) + playback_token = traverse_obj(playback_token, ('data', ..., 'token'), get_all=False) + if not playback_token: + raise ExtractorError('Failed to obtain live playback token') + + formats = self._extract_m3u8_formats( + f'{servers["stream_server"]}/{video_id}_master.m3u8?{playback_token}', + result_video_id, 'mp4', headers={ + 'Referer': 'https://www.mildom.com/', + 'Origin': 'https://www.mildom.com', + }) + + for fmt in formats: + fmt.setdefault('http_headers', {})['Referer'] = 'https://www.mildom.com/' + + return { + 'id': result_video_id, + 'title': self._html_search_meta('twitter:description', webpage, default=None) or traverse_obj(enterstudio, 'anchor_intro'), + 'description': traverse_obj(enterstudio, 'intro', 'live_intro', expected_type=str), + 'timestamp': float_or_none(enterstudio.get('live_start_ms'), scale=1000), + 'uploader': self._html_search_meta('twitter:title', webpage, default=None) or traverse_obj(enterstudio, 'loginname'), + 'uploader_id': video_id, + 'formats': formats, + 'is_live': True, + } + + +class MildomVodIE(MildomBaseIE): + IE_NAME = 'mildom:vod' + IE_DESC = 'VOD in Mildom' + _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/playback/(?P<user_id>\d+)/(?P<id>(?P=user_id)-[a-zA-Z0-9]+-?[0-9]*)' + _TESTS = [{ + 'url': 'https://www.mildom.com/playback/10882672/10882672-1597662269', + 'info_dict': { + 'id': '10882672-1597662269', + 'ext': 'mp4', + 'title': '始めてのミルダム配信じゃぃ!', + 'thumbnail': r're:^https?://.*\.(png|jpg)$', + 'upload_date': '20200817', + 'duration': 4138.37, + 'description': 'ゲームをしたくて!', + 'timestamp': 1597662269.0, + 'uploader_id': '10882672', + 'uploader': 'kson組長(けいそん)', + }, + }, { + 'url': 'https://www.mildom.com/playback/10882672/10882672-1597758589870-477', + 'info_dict': { + 'id': '10882672-1597758589870-477', + 'ext': 'mp4', + 'title': '【kson】感染メイズ!麻酔銃で無双する', + 'thumbnail': r're:^https?://.*\.(png|jpg)$', + 'timestamp': 1597759093.0, + 'uploader': 'kson組長(けいそん)', + 'duration': 4302.58, + 'uploader_id': '10882672', + 'description': 'このステージ絶対乗り越えたい', + 'upload_date': '20200818', + }, + }, { + 'url': 'https://www.mildom.com/playback/10882672/10882672-buha9td2lrn97fk2jme0', + 'info_dict': { + 'id': '10882672-buha9td2lrn97fk2jme0', + 'ext': 'mp4', + 'title': '【kson組長】CART RACER!!!', + 'thumbnail': r're:^https?://.*\.(png|jpg)$', + 'uploader_id': '10882672', + 'uploader': 'kson組長(けいそん)', + 'upload_date': '20201104', + 'timestamp': 1604494797.0, + 'duration': 4657.25, + 'description': 'WTF', + }, + }] + + def _real_extract(self, url): + user_id, video_id = self._match_valid_url(url).group('user_id', 'id') + webpage = self._download_webpage(f'https://www.mildom.com/playback/{user_id}/{video_id}', video_id) + + autoplay = self._call_api( + 'https://cloudac.mildom.com/nonolive/videocontent/playback/getPlaybackDetail', video_id, + note='Downloading playback metadata', query={ + 'v_id': video_id, + })['playback'] + + formats = [{ + 'url': autoplay['audio_url'], + 'format_id': 'audio', + 'protocol': 'm3u8_native', + 'vcodec': 'none', + 'acodec': 'aac', + 'ext': 'm4a' + }] + for fmt in autoplay['video_link']: + formats.append({ + 'format_id': 'video-%s' % fmt['name'], + 'url': fmt['url'], + 'protocol': 'm3u8_native', + 'width': fmt['level'] * autoplay['video_width'] // autoplay['video_height'], + 'height': fmt['level'], + 'vcodec': 'h264', + 'acodec': 'aac', + 'ext': 'mp4' + }) + + return { + 'id': video_id, + 'title': self._html_search_meta(('og:description', 'description'), webpage, default=None) or autoplay.get('title'), + 'description': traverse_obj(autoplay, 'video_intro'), + 'timestamp': float_or_none(autoplay.get('publish_time'), scale=1000), + 'duration': float_or_none(autoplay.get('video_length'), scale=1000), + 'thumbnail': dict_get(autoplay, ('upload_pic', 'video_pic')), + 'uploader': traverse_obj(autoplay, ('author_info', 'login_name')), + 'uploader_id': user_id, + 'formats': formats, + } + + +class MildomClipIE(MildomBaseIE): + IE_NAME = 'mildom:clip' + IE_DESC = 'Clip in Mildom' + _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/clip/(?P<id>(?P<user_id>\d+)-[a-zA-Z0-9]+)' + _TESTS = [{ + 'url': 'https://www.mildom.com/clip/10042245-63921673e7b147ebb0806d42b5ba5ce9', + 'info_dict': { + 'id': '10042245-63921673e7b147ebb0806d42b5ba5ce9', + 'title': '全然違ったよ', + 'timestamp': 1619181890, + 'duration': 59, + 'thumbnail': r're:https?://.+', + 'uploader': 'ざきんぽ', + 'uploader_id': '10042245', + }, + }, { + 'url': 'https://www.mildom.com/clip/10111524-ebf4036e5aa8411c99fb3a1ae0902864', + 'info_dict': { + 'id': '10111524-ebf4036e5aa8411c99fb3a1ae0902864', + 'title': 'かっこいい', + 'timestamp': 1621094003, + 'duration': 59, + 'thumbnail': r're:https?://.+', + 'uploader': '(ルーキー', + 'uploader_id': '10111524', + }, + }, { + 'url': 'https://www.mildom.com/clip/10660174-2c539e6e277c4aaeb4b1fbe8d22cb902', + 'info_dict': { + 'id': '10660174-2c539e6e277c4aaeb4b1fbe8d22cb902', + 'title': 'あ', + 'timestamp': 1614769431, + 'duration': 31, + 'thumbnail': r're:https?://.+', + 'uploader': 'ドルゴルスレンギーン=ダグワドルジ', + 'uploader_id': '10660174', + }, + }] + + def _real_extract(self, url): + user_id, video_id = self._match_valid_url(url).group('user_id', 'id') + webpage = self._download_webpage(f'https://www.mildom.com/clip/{video_id}', video_id) + + clip_detail = self._call_api( + 'https://cloudac-cf-jp.mildom.com/nonolive/videocontent/clip/detail', video_id, + note='Downloading playback metadata', query={ + 'clip_id': video_id, + }) + + return { + 'id': video_id, + 'title': self._html_search_meta( + ('og:description', 'description'), webpage, default=None) or clip_detail.get('title'), + 'timestamp': float_or_none(clip_detail.get('create_time')), + 'duration': float_or_none(clip_detail.get('length')), + 'thumbnail': clip_detail.get('cover'), + 'uploader': traverse_obj(clip_detail, ('user_info', 'loginname')), + 'uploader_id': user_id, + + 'url': clip_detail['url'], + 'ext': determine_ext(clip_detail.get('url'), 'mp4'), + } + + +class MildomUserVodIE(MildomBaseIE): + IE_NAME = 'mildom:user:vod' + IE_DESC = 'Download all VODs from specific user in Mildom' + _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/profile/(?P<id>\d+)' + _TESTS = [{ + 'url': 'https://www.mildom.com/profile/10093333', + 'info_dict': { + 'id': '10093333', + 'title': 'Uploads from ねこばたけ', + }, + 'playlist_mincount': 732, + }, { + 'url': 'https://www.mildom.com/profile/10882672', + 'info_dict': { + 'id': '10882672', + 'title': 'Uploads from kson組長(けいそん)', + }, + 'playlist_mincount': 201, + }] + + def _fetch_page(self, user_id, page): + page += 1 + reply = self._call_api( + 'https://cloudac.mildom.com/nonolive/videocontent/profile/playbackList', + user_id, note=f'Downloading page {page}', query={ + 'user_id': user_id, + 'page': page, + 'limit': '30', + }) + if not reply: + return + for x in reply: + v_id = x.get('v_id') + if not v_id: + continue + yield self.url_result(f'https://www.mildom.com/playback/{user_id}/{v_id}') + + def _real_extract(self, url): + user_id = self._match_id(url) + 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) + + profile = self._call_api( + 'https://cloudac.mildom.com/nonolive/gappserv/user/profileV2', user_id, + query={'user_id': user_id}, note='Downloading user profile')['user_info'] + + return self.playlist_result( + OnDemandPagedList(functools.partial(self._fetch_page, user_id), 30), + user_id, f'Uploads from {profile["loginname"]}') |