adultswim.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import json
  2. from .turner import TurnerBaseIE
  3. from ..utils import (
  4. determine_ext,
  5. float_or_none,
  6. int_or_none,
  7. mimetype2ext,
  8. parse_age_limit,
  9. parse_iso8601,
  10. strip_or_none,
  11. try_get,
  12. )
  13. class AdultSwimIE(TurnerBaseIE):
  14. _VALID_URL = r'https?://(?:www\.)?adultswim\.com/videos/(?P<show_path>[^/?#]+)(?:/(?P<episode_path>[^/?#]+))?'
  15. _TESTS = [{
  16. 'url': 'http://adultswim.com/videos/rick-and-morty/pilot',
  17. 'info_dict': {
  18. 'id': 'rQxZvXQ4ROaSOqq-or2Mow',
  19. 'ext': 'mp4',
  20. 'title': 'Rick and Morty - Pilot',
  21. 'description': 'Rick moves in with his daughter\'s family and establishes himself as a bad influence on his grandson, Morty.',
  22. 'timestamp': 1543294800,
  23. 'upload_date': '20181127',
  24. },
  25. 'params': {
  26. # m3u8 download
  27. 'skip_download': True,
  28. },
  29. 'expected_warnings': ['Unable to download f4m manifest'],
  30. }, {
  31. 'url': 'http://www.adultswim.com/videos/tim-and-eric-awesome-show-great-job/dr-steve-brule-for-your-wine/',
  32. 'info_dict': {
  33. 'id': 'sY3cMUR_TbuE4YmdjzbIcQ',
  34. 'ext': 'mp4',
  35. 'title': 'Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine',
  36. 'description': 'Dr. Brule reports live from Wine Country with a special report on wines. \nWatch Tim and Eric Awesome Show Great Job! episode #20, "Embarrassed" on Adult Swim.',
  37. 'upload_date': '20080124',
  38. 'timestamp': 1201150800,
  39. },
  40. 'params': {
  41. # m3u8 download
  42. 'skip_download': True,
  43. },
  44. 'skip': '404 Not Found',
  45. }, {
  46. 'url': 'http://www.adultswim.com/videos/decker/inside-decker-a-new-hero/',
  47. 'info_dict': {
  48. 'id': 'I0LQFQkaSUaFp8PnAWHhoQ',
  49. 'ext': 'mp4',
  50. 'title': 'Decker - Inside Decker: A New Hero',
  51. 'description': 'The guys recap the conclusion of the season. They announce a new hero, take a peek into the Victorville Film Archive and welcome back the talented James Dean.',
  52. 'timestamp': 1469480460,
  53. 'upload_date': '20160725',
  54. },
  55. 'params': {
  56. # m3u8 download
  57. 'skip_download': True,
  58. },
  59. 'expected_warnings': ['Unable to download f4m manifest'],
  60. }, {
  61. 'url': 'http://www.adultswim.com/videos/attack-on-titan',
  62. 'info_dict': {
  63. 'id': 'attack-on-titan',
  64. 'title': 'Attack on Titan',
  65. 'description': 'md5:41caa9416906d90711e31dc00cb7db7e',
  66. },
  67. 'playlist_mincount': 12,
  68. }, {
  69. 'url': 'http://www.adultswim.com/videos/streams/williams-stream',
  70. 'info_dict': {
  71. 'id': 'd8DEBj7QRfetLsRgFnGEyg',
  72. 'ext': 'mp4',
  73. 'title': r're:^Williams Stream \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
  74. 'description': 'original programming',
  75. },
  76. 'params': {
  77. # m3u8 download
  78. 'skip_download': True,
  79. },
  80. 'skip': '404 Not Found',
  81. }]
  82. def _real_extract(self, url):
  83. show_path, episode_path = self._match_valid_url(url).groups()
  84. display_id = episode_path or show_path
  85. query = '''query {
  86. getShowBySlug(slug:"%s") {
  87. %%s
  88. }
  89. }''' % show_path
  90. if episode_path:
  91. query = query % '''title
  92. getVideoBySlug(slug:"%s") {
  93. _id
  94. auth
  95. description
  96. duration
  97. episodeNumber
  98. launchDate
  99. mediaID
  100. seasonNumber
  101. poster
  102. title
  103. tvRating
  104. }''' % episode_path
  105. ['getVideoBySlug']
  106. else:
  107. query = query % '''metaDescription
  108. title
  109. videos(first:1000,sort:["episode_number"]) {
  110. edges {
  111. node {
  112. _id
  113. slug
  114. }
  115. }
  116. }'''
  117. show_data = self._download_json(
  118. 'https://www.adultswim.com/api/search', display_id,
  119. data=json.dumps({'query': query}).encode(),
  120. headers={'Content-Type': 'application/json'})['data']['getShowBySlug']
  121. if episode_path:
  122. video_data = show_data['getVideoBySlug']
  123. video_id = video_data['_id']
  124. episode_title = title = video_data['title']
  125. series = show_data.get('title')
  126. if series:
  127. title = '%s - %s' % (series, title)
  128. info = {
  129. 'id': video_id,
  130. 'title': title,
  131. 'description': strip_or_none(video_data.get('description')),
  132. 'duration': float_or_none(video_data.get('duration')),
  133. 'formats': [],
  134. 'subtitles': {},
  135. 'age_limit': parse_age_limit(video_data.get('tvRating')),
  136. 'thumbnail': video_data.get('poster'),
  137. 'timestamp': parse_iso8601(video_data.get('launchDate')),
  138. 'series': series,
  139. 'season_number': int_or_none(video_data.get('seasonNumber')),
  140. 'episode': episode_title,
  141. 'episode_number': int_or_none(video_data.get('episodeNumber')),
  142. }
  143. auth = video_data.get('auth')
  144. media_id = video_data.get('mediaID')
  145. if media_id:
  146. info.update(self._extract_ngtv_info(media_id, {
  147. # CDN_TOKEN_APP_ID from:
  148. # https://d2gg02c3xr550i.cloudfront.net/assets/asvp.e9c8bef24322d060ef87.bundle.js
  149. 'appId': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhcHBJZCI6ImFzLXR2ZS1kZXNrdG9wLXB0enQ2bSIsInByb2R1Y3QiOiJ0dmUiLCJuZXR3b3JrIjoiYXMiLCJwbGF0Zm9ybSI6ImRlc2t0b3AiLCJpYXQiOjE1MzI3MDIyNzl9.BzSCk-WYOZ2GMCIaeVb8zWnzhlgnXuJTCu0jGp_VaZE',
  150. }, {
  151. 'url': url,
  152. 'site_name': 'AdultSwim',
  153. 'auth_required': auth,
  154. }))
  155. if not auth:
  156. extract_data = self._download_json(
  157. 'https://www.adultswim.com/api/shows/v1/videos/' + video_id,
  158. video_id, query={'fields': 'stream'}, fatal=False) or {}
  159. assets = try_get(extract_data, lambda x: x['data']['video']['stream']['assets'], list) or []
  160. for asset in assets:
  161. asset_url = asset.get('url')
  162. if not asset_url:
  163. continue
  164. ext = determine_ext(asset_url, mimetype2ext(asset.get('mime_type')))
  165. if ext == 'm3u8':
  166. info['formats'].extend(self._extract_m3u8_formats(
  167. asset_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
  168. elif ext == 'f4m':
  169. continue
  170. # info['formats'].extend(self._extract_f4m_formats(
  171. # asset_url, video_id, f4m_id='hds', fatal=False))
  172. elif ext in ('scc', 'ttml', 'vtt'):
  173. info['subtitles'].setdefault('en', []).append({
  174. 'url': asset_url,
  175. })
  176. return info
  177. else:
  178. entries = []
  179. for edge in show_data.get('videos', {}).get('edges', []):
  180. video = edge.get('node') or {}
  181. slug = video.get('slug')
  182. if not slug:
  183. continue
  184. entries.append(self.url_result(
  185. 'http://adultswim.com/videos/%s/%s' % (show_path, slug),
  186. 'AdultSwim', video.get('_id')))
  187. return self.playlist_result(
  188. entries, show_path, show_data.get('title'),
  189. strip_or_none(show_data.get('metaDescription')))