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