]>
Commit | Line | Data |
---|---|---|
9139d2fa HTL |
1 | from .common import InfoExtractor |
2 | from ..utils import ( | |
3 | ExtractorError, | |
4 | int_or_none, | |
5 | parse_iso8601, | |
6 | traverse_obj, | |
7 | try_get, | |
8 | ) | |
9 | ||
10 | ||
11 | class WASDTVBaseIE(InfoExtractor): | |
12 | ||
13 | def _fetch(self, path, video_id, description, query={}): | |
14 | response = self._download_json( | |
15 | f'https://wasd.tv/api/{path}', video_id, query=query, | |
16 | note=f'Downloading {description} metadata', | |
17 | errnote=f'Unable to download {description} metadata') | |
18 | error = response.get('error') | |
19 | if error: | |
20 | raise ExtractorError(f'{self.IE_NAME} returned error: {error}', expected=True) | |
21 | return response.get('result') | |
22 | ||
23 | def _extract_thumbnails(self, thumbnails_dict): | |
24 | return [{ | |
25 | 'url': url, | |
26 | 'preference': index, | |
27 | } for index, url in enumerate( | |
28 | traverse_obj(thumbnails_dict, (('small', 'medium', 'large'),))) if url] | |
29 | ||
30 | def _real_extract(self, url): | |
31 | container = self._get_container(url) | |
32 | stream = traverse_obj(container, ('media_container_streams', 0)) | |
33 | media = try_get(stream, lambda x: x['stream_media'][0]) | |
34 | if not media: | |
35 | raise ExtractorError('Can not extract media data.', expected=True) | |
36 | media_meta = media.get('media_meta') | |
37 | media_url, is_live = self._get_media_url(media_meta) | |
38 | video_id = media.get('media_id') or container.get('media_container_id') | |
39 | formats, subtitles = self._extract_m3u8_formats_and_subtitles(media_url, video_id, 'mp4') | |
40 | self._sort_formats(formats) | |
41 | return { | |
42 | 'id': str(video_id), | |
43 | 'title': container.get('media_container_name') or self._og_search_title(self._download_webpage(url, video_id)), | |
44 | 'description': container.get('media_container_description'), | |
45 | 'thumbnails': self._extract_thumbnails(media_meta.get('media_preview_images')), | |
46 | 'timestamp': parse_iso8601(container.get('created_at')), | |
47 | 'view_count': int_or_none(stream.get('stream_current_viewers' if is_live else 'stream_total_viewers')), | |
48 | 'is_live': is_live, | |
49 | 'formats': formats, | |
50 | 'subtitles': subtitles, | |
51 | } | |
52 | ||
53 | def _get_container(self, url): | |
54 | raise NotImplementedError('Subclass for get media container') | |
55 | ||
56 | def _get_media_url(self, media_meta): | |
57 | raise NotImplementedError('Subclass for get media url') | |
58 | ||
59 | ||
60 | class WASDTVStreamIE(WASDTVBaseIE): | |
61 | IE_NAME = 'wasdtv:stream' | |
62 | _VALID_URL = r'https?://wasd\.tv/(?P<id>[^/#?]+)$' | |
63 | _TESTS = [{ | |
64 | 'url': 'https://wasd.tv/24_7', | |
65 | 'info_dict': { | |
66 | 'id': '559738', | |
67 | 'ext': 'mp4', | |
68 | 'title': 'Live 24/7 Music', | |
69 | 'description': '24/7 Music', | |
70 | 'timestamp': int, | |
71 | 'upload_date': r're:^\d{8}$', | |
72 | 'is_live': True, | |
73 | 'view_count': int, | |
74 | }, | |
75 | }] | |
76 | ||
77 | def _get_container(self, url): | |
78 | nickname = self._match_id(url) | |
79 | channel = self._fetch(f'channels/nicknames/{nickname}', video_id=nickname, description='channel') | |
80 | channel_id = channel.get('channel_id') | |
81 | containers = self._fetch( | |
82 | 'v2/media-containers', channel_id, 'running media containers', | |
83 | query={ | |
84 | 'channel_id': channel_id, | |
85 | 'media_container_type': 'SINGLE', | |
86 | 'media_container_status': 'RUNNING', | |
87 | }) | |
88 | if not containers: | |
89 | raise ExtractorError(f'{nickname} is offline', expected=True) | |
90 | return containers[0] | |
91 | ||
92 | def _get_media_url(self, media_meta): | |
93 | return media_meta['media_url'], True | |
94 | ||
95 | ||
96 | class WASDTVRecordIE(WASDTVBaseIE): | |
97 | IE_NAME = 'wasdtv:record' | |
22b22b7d | 98 | _VALID_URL = r'https?://wasd\.tv/[^/#?]+(?:/videos)?\?record=(?P<id>\d+)$' |
9139d2fa HTL |
99 | _TESTS = [{ |
100 | 'url': 'https://wasd.tv/spacemita/videos?record=907755', | |
101 | 'md5': 'c9899dd85be4cc997816ff9f9ca516ce', | |
102 | 'info_dict': { | |
103 | 'id': '906825', | |
104 | 'ext': 'mp4', | |
105 | 'title': 'Музыкальный', | |
106 | 'description': 'md5:f510388d929ff60ae61d4c3cab3137cc', | |
107 | 'timestamp': 1645812079, | |
108 | 'upload_date': '20220225', | |
109 | 'thumbnail': r're:^https?://.+\.jpg', | |
110 | 'is_live': False, | |
111 | 'view_count': int, | |
112 | }, | |
22b22b7d | 113 | }, { |
114 | 'url': 'https://wasd.tv/spacemita?record=907755', | |
115 | 'only_matching': True, | |
9139d2fa HTL |
116 | }] |
117 | ||
118 | def _get_container(self, url): | |
119 | container_id = self._match_id(url) | |
120 | return self._fetch( | |
121 | f'v2/media-containers/{container_id}', container_id, 'media container') | |
122 | ||
123 | def _get_media_url(self, media_meta): | |
124 | media_archive_url = media_meta.get('media_archive_url') | |
125 | if media_archive_url: | |
126 | return media_archive_url, False | |
127 | return media_meta['media_url'], True | |
128 | ||
129 | ||
130 | class WASDTVClipIE(WASDTVBaseIE): | |
131 | IE_NAME = 'wasdtv:clip' | |
132 | _VALID_URL = r'https?://wasd\.tv/[^/#?]+/clips\?clip=(?P<id>\d+)$' | |
133 | _TESTS = [{ | |
134 | 'url': 'https://wasd.tv/spacemita/clips?clip=26804', | |
135 | 'md5': '818885e720143d7a4e776ff66fcff148', | |
136 | 'info_dict': { | |
137 | 'id': '26804', | |
138 | 'ext': 'mp4', | |
139 | 'title': 'Пуш флексит на голове стримера', | |
140 | 'timestamp': 1646682908, | |
141 | 'upload_date': '20220307', | |
142 | 'thumbnail': r're:^https?://.+\.jpg', | |
143 | 'view_count': int, | |
144 | }, | |
145 | }] | |
146 | ||
147 | def _real_extract(self, url): | |
148 | clip_id = self._match_id(url) | |
149 | clip = self._fetch(f'v2/clips/{clip_id}', video_id=clip_id, description='clip') | |
150 | clip_data = clip.get('clip_data') | |
151 | formats, subtitles = self._extract_m3u8_formats_and_subtitles(clip_data.get('url'), video_id=clip_id, ext='mp4') | |
152 | self._sort_formats(formats) | |
153 | return { | |
154 | 'id': clip_id, | |
155 | 'title': clip.get('clip_title') or self._og_search_title(self._download_webpage(url, clip_id, fatal=False)), | |
156 | 'thumbnails': self._extract_thumbnails(clip_data.get('preview')), | |
157 | 'timestamp': parse_iso8601(clip.get('created_at')), | |
158 | 'view_count': int_or_none(clip.get('clip_views_count')), | |
159 | 'formats': formats, | |
160 | 'subtitles': subtitles, | |
161 | } |