sovietscloset.py 8.1 KB

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