]>
Commit | Line | Data |
---|---|---|
ff5e16f2 THD |
1 | from .common import InfoExtractor |
2 | from ..utils import ( | |
3 | ExtractorError, | |
4 | dict_get, | |
5 | traverse_obj, | |
6 | try_get, | |
7 | ) | |
8 | ||
9 | ||
10 | class MirrativBaseIE(InfoExtractor): | |
11 | def assert_error(self, response): | |
12 | error_message = traverse_obj(response, ('status', 'error')) | |
13 | if error_message: | |
14 | raise ExtractorError('Mirrativ says: %s' % error_message, expected=True) | |
15 | ||
16 | ||
17 | class MirrativIE(MirrativBaseIE): | |
18 | IE_NAME = 'mirrativ' | |
19 | _VALID_URL = r'https?://(?:www\.)?mirrativ\.com/live/(?P<id>[^/?#&]+)' | |
ff5e16f2 THD |
20 | |
21 | TESTS = [{ | |
02fc6feb LNO |
22 | 'url': 'https://mirrativ.com/live/UQomuS7EMgHoxRHjEhNiHw', |
23 | 'info_dict': { | |
24 | 'id': 'UQomuS7EMgHoxRHjEhNiHw', | |
25 | 'title': 'ねむいぃ、。『参加型』🔰jcが初めてやるCOD✨初見さん大歓迎💗', | |
26 | 'is_live': True, | |
27 | 'description': 'md5:bfcd8f77f2fab24c3c672e5620f3f16e', | |
28 | 'thumbnail': r're:https?://.+', | |
29 | 'uploader': '# あ ち ゅ 。💡', | |
30 | 'uploader_id': '118572165', | |
31 | 'duration': None, | |
32 | 'view_count': 1241, | |
33 | 'release_timestamp': 1646229192, | |
34 | 'timestamp': 1646229167, | |
35 | 'was_live': False, | |
36 | }, | |
37 | 'skip': 'livestream', | |
38 | }, { | |
ff5e16f2 THD |
39 | 'url': 'https://mirrativ.com/live/POxyuG1KmW2982lqlDTuPw', |
40 | 'only_matching': True, | |
41 | }] | |
42 | ||
43 | def _real_extract(self, url): | |
44 | video_id = self._match_id(url) | |
45 | webpage = self._download_webpage('https://www.mirrativ.com/live/%s' % video_id, video_id) | |
02fc6feb | 46 | live_response = self._download_json(f'https://www.mirrativ.com/api/live/live?live_id={video_id}', video_id) |
ff5e16f2 THD |
47 | self.assert_error(live_response) |
48 | ||
49 | hls_url = dict_get(live_response, ('archive_url_hls', 'streaming_url_hls')) | |
50 | is_live = bool(live_response.get('is_live')) | |
ff5e16f2 THD |
51 | if not hls_url: |
52 | raise ExtractorError('Neither archive nor live is available.', expected=True) | |
53 | ||
54 | formats = self._extract_m3u8_formats( | |
55 | hls_url, video_id, | |
56 | ext='mp4', entry_protocol='m3u8_native', | |
57 | m3u8_id='hls', live=is_live) | |
ff5e16f2 | 58 | |
ff5e16f2 THD |
59 | return { |
60 | 'id': video_id, | |
02fc6feb LNO |
61 | 'title': self._og_search_title(webpage, default=None) or self._search_regex( |
62 | r'<title>\s*(.+?) - Mirrativ\s*</title>', webpage) or live_response.get('title'), | |
ff5e16f2 | 63 | 'is_live': is_live, |
02fc6feb | 64 | 'description': live_response.get('description'), |
ff5e16f2 | 65 | 'formats': formats, |
02fc6feb LNO |
66 | 'thumbnail': live_response.get('image_url'), |
67 | 'uploader': traverse_obj(live_response, ('owner', 'name')), | |
68 | 'uploader_id': traverse_obj(live_response, ('owner', 'user_id')), | |
69 | 'duration': try_get(live_response, lambda x: x['ended_at'] - x['started_at']) if not is_live else None, | |
70 | 'view_count': live_response.get('total_viewer_num'), | |
71 | 'release_timestamp': live_response.get('started_at'), | |
72 | 'timestamp': live_response.get('created_at'), | |
73 | 'was_live': bool(live_response.get('is_archive')), | |
ff5e16f2 THD |
74 | } |
75 | ||
76 | ||
77 | class MirrativUserIE(MirrativBaseIE): | |
78 | IE_NAME = 'mirrativ:user' | |
79 | _VALID_URL = r'https?://(?:www\.)?mirrativ\.com/user/(?P<id>\d+)' | |
ff5e16f2 THD |
80 | |
81 | _TESTS = [{ | |
82 | # Live archive is available up to 3 days | |
83 | # see: https://helpfeel.com/mirrativ/%E9%8C%B2%E7%94%BB-5e26d3ad7b59ef0017fb49ac (Japanese) | |
84 | 'url': 'https://www.mirrativ.com/user/110943130', | |
85 | 'note': 'multiple archives available', | |
86 | 'only_matching': True, | |
87 | }] | |
88 | ||
89 | def _entries(self, user_id): | |
90 | page = 1 | |
91 | while page is not None: | |
92 | api_response = self._download_json( | |
02fc6feb LNO |
93 | f'https://www.mirrativ.com/api/live/live_history?user_id={user_id}&page={page}', user_id, |
94 | note=f'Downloading page {page}') | |
ff5e16f2 THD |
95 | self.assert_error(api_response) |
96 | lives = api_response.get('lives') | |
97 | if not lives: | |
98 | break | |
99 | for live in lives: | |
100 | if not live.get('is_archive') and not live.get('is_live'): | |
101 | # neither archive nor live is available, so skip it | |
102 | # or the service will ban your IP address for a while | |
103 | continue | |
104 | live_id = live.get('live_id') | |
105 | url = 'https://www.mirrativ.com/live/%s' % live_id | |
106 | yield self.url_result(url, video_id=live_id, video_title=live.get('title')) | |
107 | page = api_response.get('next_page') | |
108 | ||
109 | def _real_extract(self, url): | |
110 | user_id = self._match_id(url) | |
111 | user_info = self._download_json( | |
02fc6feb | 112 | f'https://www.mirrativ.com/api/user/profile?user_id={user_id}', user_id, |
ff5e16f2 THD |
113 | note='Downloading user info', fatal=False) |
114 | self.assert_error(user_info) | |
115 | ||
02fc6feb LNO |
116 | return self.playlist_result( |
117 | self._entries(user_id), user_id, | |
118 | user_info.get('name'), user_info.get('description')) |