]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/sovietscloset.py
[misc] Add `hatch`, `ruff`, `pre-commit` and improve dev docs (#7409)
[yt-dlp.git] / yt_dlp / extractor / sovietscloset.py
CommitLineData
421ddcb8 1from .common import InfoExtractor
e897bd82 2from ..utils import try_get, unified_timestamp
421ddcb8
C
3
4
5class SovietsClosetBaseIE(InfoExtractor):
6 MEDIADELIVERY_REFERER = {'Referer': 'https://iframe.mediadelivery.net/'}
7
8 def parse_nuxt_jsonp(self, nuxt_jsonp_url, video_id, name):
9 nuxt_jsonp = self._download_webpage(nuxt_jsonp_url, video_id, note=f'Downloading {name} __NUXT_JSONP__')
66f4c04e 10 return self._search_nuxt_data(nuxt_jsonp, video_id, '__NUXT_JSONP__')
421ddcb8
C
11
12 def video_meta(self, video_id, game_name, category_name, episode_number, stream_date):
13 title = game_name
14 if category_name and category_name != 'Misc':
15 title += f' - {category_name}'
16 if episode_number:
17 title += f' #{episode_number}'
18
19 timestamp = unified_timestamp(stream_date)
20
21 return {
22 'id': video_id,
23 'title': title,
24 'http_headers': self.MEDIADELIVERY_REFERER,
25 'uploader': 'SovietWomble',
26 'creator': 'SovietWomble',
27 'release_timestamp': timestamp,
28 'timestamp': timestamp,
29 'uploader_id': 'SovietWomble',
30 'uploader_url': 'https://www.twitch.tv/SovietWomble',
31 'was_live': True,
32 'availability': 'public',
33 'series': game_name,
34 'season': category_name,
35 'episode_number': episode_number,
36 }
37
38
39class SovietsClosetIE(SovietsClosetBaseIE):
40 _VALID_URL = r'https?://(?:www\.)?sovietscloset\.com/video/(?P<id>[0-9]+)/?'
41 _TESTS = [
42 {
43 'url': 'https://sovietscloset.com/video/1337',
2f1a299c 44 'md5': 'bd012b04b261725510ca5383074cdd55',
421ddcb8
C
45 'info_dict': {
46 'id': '1337',
47 'ext': 'mp4',
48 'title': 'The Witcher #13',
49 'thumbnail': r're:^https?://.*\.b-cdn\.net/2f0cfbf4-3588-43a9-a7d6-7c9ea3755e67/thumbnail\.jpg$',
50 'uploader': 'SovietWomble',
51 'creator': 'SovietWomble',
52 'release_timestamp': 1492091580,
53 'release_date': '20170413',
54 'timestamp': 1492091580,
55 'upload_date': '20170413',
56 'uploader_id': 'SovietWomble',
57 'uploader_url': 'https://www.twitch.tv/SovietWomble',
3cf4b91d 58 'duration': 7007,
421ddcb8
C
59 'was_live': True,
60 'availability': 'public',
61 'series': 'The Witcher',
62 'season': 'Misc',
63 'episode_number': 13,
08d30158 64 'episode': 'Episode 13',
421ddcb8
C
65 },
66 },
67 {
68 'url': 'https://sovietscloset.com/video/1105',
2f1a299c 69 'md5': '89fa928f183893cb65a0b7be846d8a90',
421ddcb8
C
70 'info_dict': {
71 'id': '1105',
72 'ext': 'mp4',
2f1a299c 73 'title': 'Arma 3 - Zeus Games #5',
421ddcb8
C
74 'uploader': 'SovietWomble',
75 'thumbnail': r're:^https?://.*\.b-cdn\.net/c0e5e76f-3a93-40b4-bf01-12343c2eec5d/thumbnail\.jpg$',
421ddcb8
C
76 'creator': 'SovietWomble',
77 'release_timestamp': 1461157200,
78 'release_date': '20160420',
79 'timestamp': 1461157200,
80 'upload_date': '20160420',
81 'uploader_id': 'SovietWomble',
82 'uploader_url': 'https://www.twitch.tv/SovietWomble',
3cf4b91d 83 'duration': 8804,
421ddcb8
C
84 'was_live': True,
85 'availability': 'public',
86 'series': 'Arma 3',
87 'season': 'Zeus Games',
2f1a299c
C
88 'episode_number': 5,
89 'episode': 'Episode 5',
421ddcb8
C
90 },
91 },
92 ]
93
94 def _extract_bunnycdn_iframe(self, video_id, bunnycdn_id):
95 iframe = self._download_webpage(
96 f'https://iframe.mediadelivery.net/embed/5105/{bunnycdn_id}',
97 video_id, note='Downloading BunnyCDN iframe', headers=self.MEDIADELIVERY_REFERER)
98
99 m3u8_url = self._search_regex(r'(https?://.*?\.m3u8)', iframe, 'm3u8 url')
100 thumbnail_url = self._search_regex(r'(https?://.*?thumbnail\.jpg)', iframe, 'thumbnail url')
101
102 m3u8_formats = self._extract_m3u8_formats(m3u8_url, video_id, headers=self.MEDIADELIVERY_REFERER)
421ddcb8 103
3cf4b91d
C
104 if not m3u8_formats:
105 duration = None
106 else:
107 duration = self._extract_m3u8_vod_duration(
108 m3u8_formats[0]['url'], video_id, headers=self.MEDIADELIVERY_REFERER)
109
421ddcb8
C
110 return {
111 'formats': m3u8_formats,
112 'thumbnail': thumbnail_url,
3cf4b91d 113 'duration': duration,
421ddcb8
C
114 }
115
116 def _real_extract(self, url):
117 video_id = self._match_id(url)
118 webpage = self._download_webpage(url, video_id)
119
2f1a299c 120 static_assets_base = self._search_regex(r'(/_nuxt/static/\d+)', webpage, 'staticAssetsBase')
421ddcb8
C
121 static_assets_base = f'https://sovietscloset.com{static_assets_base}'
122
123 stream = self.parse_nuxt_jsonp(f'{static_assets_base}/video/{video_id}/payload.js', video_id, 'video')['stream']
124
125 return {
126 **self.video_meta(
127 video_id=video_id, game_name=stream['game']['name'],
128 category_name=try_get(stream, lambda x: x['subcategory']['name'], str),
129 episode_number=stream.get('number'), stream_date=stream.get('date')),
130 **self._extract_bunnycdn_iframe(video_id, stream['bunnyId']),
131 }
132
133
134class SovietsClosetPlaylistIE(SovietsClosetBaseIE):
135 _VALID_URL = r'https?://(?:www\.)?sovietscloset\.com/(?!video)(?P<id>[^#?]+)'
136 _TESTS = [
137
138 {
139 'url': 'https://sovietscloset.com/The-Witcher',
140 'info_dict': {
141 'id': 'The-Witcher',
142 'title': 'The Witcher',
143 },
144 'playlist_mincount': 31,
145 },
146 {
147 'url': 'https://sovietscloset.com/Arma-3/Zeus-Games',
148 'info_dict': {
149 'id': 'Arma-3/Zeus-Games',
150 'title': 'Arma 3 - Zeus Games',
151 },
152 'playlist_mincount': 3,
153 },
154 {
155 'url': 'https://sovietscloset.com/arma-3/zeus-games/',
156 'info_dict': {
157 'id': 'arma-3/zeus-games',
158 'title': 'Arma 3 - Zeus Games',
159 },
160 'playlist_mincount': 3,
161 },
f6d8776d
C
162 {
163 'url': 'https://sovietscloset.com/Total-War-Warhammer',
164 'info_dict': {
165 'id': 'Total-War-Warhammer',
166 'title': 'Total War: Warhammer - Greenskins',
167 },
168 'playlist_mincount': 33,
169 },
421ddcb8
C
170 ]
171
172 def _real_extract(self, url):
173 playlist_id = self._match_id(url)
174 if playlist_id.endswith('/'):
175 playlist_id = playlist_id[:-1]
176
177 webpage = self._download_webpage(url, playlist_id)
178
2f1a299c 179 static_assets_base = self._search_regex(r'(/_nuxt/static/\d+)', webpage, 'staticAssetsBase')
421ddcb8
C
180 static_assets_base = f'https://sovietscloset.com{static_assets_base}'
181
182 sovietscloset = self.parse_nuxt_jsonp(f'{static_assets_base}/payload.js', playlist_id, 'global')['games']
183
184 if '/' in playlist_id:
185 game_slug, category_slug = playlist_id.lower().split('/')
186 else:
187 game_slug = playlist_id.lower()
188 category_slug = 'misc'
189
190 game = next(game for game in sovietscloset if game['slug'].lower() == game_slug)
f6d8776d
C
191 category = next((cat for cat in game['subcategories'] if cat.get('slug', '').lower() == category_slug),
192 game['subcategories'][0])
193 category_slug = category.get('slug', '').lower() or category_slug
421ddcb8
C
194 playlist_title = game.get('name') or game_slug
195 if category_slug != 'misc':
196 playlist_title += f' - {category.get("name") or category_slug}'
197 entries = [{
198 **self.url_result(f'https://sovietscloset.com/video/{stream["id"]}', ie=SovietsClosetIE.ie_key()),
199 **self.video_meta(
200 video_id=stream['id'], game_name=game['name'], category_name=category.get('name'),
201 episode_number=i + 1, stream_date=stream.get('date')),
202 } for i, stream in enumerate(category['streams'])]
203
204 return self.playlist_result(entries, playlist_id, playlist_title)