You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
7.7 KiB

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