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