sovietscloset.py 8.0 KB

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