]> jfr.im git - yt-dlp.git/blame - yt_dlp/extractor/radlive.py
[extractor] Deprecate `_sort_formats`
[yt-dlp.git] / yt_dlp / extractor / radlive.py
CommitLineData
92790da2 1import json
2
e0ddbd02 3from ..utils import (
4 ExtractorError,
5 format_field,
6 traverse_obj,
7 try_get,
8 unified_timestamp
9)
92790da2 10from .common import InfoExtractor
11
12
13class RadLiveIE(InfoExtractor):
14 IE_NAME = 'radlive'
15 _VALID_URL = r'https?://(?:www\.)?rad\.live/content/(?P<content_type>feature|episode)/(?P<id>[a-f0-9-]+)'
16 _TESTS = [{
17 'url': 'https://rad.live/content/feature/dc5acfbc-761b-4bec-9564-df999905116a',
18 'md5': '6219d5d31d52de87d21c9cf5b7cb27ff',
19 'info_dict': {
20 'id': 'dc5acfbc-761b-4bec-9564-df999905116a',
21 'ext': 'mp4',
22 'title': 'Deathpact - Digital Mirage 2 [Full Set]',
23 'language': 'en',
24 'thumbnail': 'https://static.12core.net/cb65ae077a079c68380e38f387fbc438.png',
25 'description': '',
26 'release_timestamp': 1600185600.0,
27 'channel': 'Proximity',
28 'channel_id': '9ce6dd01-70a4-4d59-afb6-d01f807cd009',
29 'channel_url': 'https://rad.live/content/channel/9ce6dd01-70a4-4d59-afb6-d01f807cd009',
30 }
31 }, {
32 'url': 'https://rad.live/content/episode/bbcf66ec-0d02-4ca0-8dc0-4213eb2429bf',
33 'md5': '40b2175f347592125d93e9a344080125',
34 'info_dict': {
35 'id': 'bbcf66ec-0d02-4ca0-8dc0-4213eb2429bf',
36 'ext': 'mp4',
37 'title': 'E01: Bad Jokes 1',
38 'language': 'en',
39 'thumbnail': 'https://lsp.littlstar.com/channels/WHISTLE/BAD_JOKES/SEASON_1/BAD_JOKES_101/poster.jpg',
40 'description': 'Bad Jokes - Champions, Adam Pally, Super Troopers, Team Edge and 2Hype',
41 'release_timestamp': None,
42 'channel': None,
43 'channel_id': None,
44 'channel_url': None,
45 'episode': 'E01: Bad Jokes 1',
46 'episode_number': 1,
47 'episode_id': '336',
48 },
49 }]
50
51 def _real_extract(self, url):
52 content_type, video_id = self._match_valid_url(url).groups()
53
54 webpage = self._download_webpage(url, video_id)
55
56 content_info = json.loads(self._search_regex(
57 r'<script[^>]*type=([\'"])application/json\1[^>]*>(?P<json>{.+?})</script>',
58 webpage, 'video info', group='json'))['props']['pageProps']['initialContentData']
59 video_info = content_info[content_type]
60
61 if not video_info:
62 raise ExtractorError('Unable to extract video info, make sure the URL is valid')
63
64 formats = self._extract_m3u8_formats(video_info['assets']['videos'][0]['url'], video_id)
92790da2 65
66 data = video_info.get('structured_data', {})
67
68 release_date = unified_timestamp(traverse_obj(data, ('releasedEvent', 'startDate')))
69 channel = next(iter(content_info.get('channels', [])), {})
70 channel_id = channel.get('lrn', '').split(':')[-1] or None
71
72 result = {
73 'id': video_id,
74 'title': video_info['title'],
75 'formats': formats,
76 'language': traverse_obj(data, ('potentialAction', 'target', 'inLanguage')),
77 'thumbnail': traverse_obj(data, ('image', 'contentUrl')),
78 'description': data.get('description'),
79 'release_timestamp': release_date,
80 'channel': channel.get('name'),
81 'channel_id': channel_id,
a70635b8 82 'channel_url': format_field(channel_id, None, 'https://rad.live/content/channel/%s'),
92790da2 83
84 }
85 if content_type == 'episode':
86 result.update({
87 # TODO: Get season number when downloading single episode
88 'episode': video_info.get('title'),
89 'episode_number': video_info.get('number'),
90 'episode_id': video_info.get('id'),
91 })
92
93 return result
94
95
6368e2e6 96class RadLiveSeasonIE(RadLiveIE): # XXX: Do not subclass from concrete IE
92790da2 97 IE_NAME = 'radlive:season'
98 _VALID_URL = r'https?://(?:www\.)?rad\.live/content/season/(?P<id>[a-f0-9-]+)'
99 _TESTS = [{
100 'url': 'https://rad.live/content/season/08a290f7-c9ef-4e22-9105-c255995a2e75',
101 'md5': '40b2175f347592125d93e9a344080125',
102 'info_dict': {
103 'id': '08a290f7-c9ef-4e22-9105-c255995a2e75',
104 'title': 'Bad Jokes - Season 1',
105 },
106 'playlist_mincount': 5,
107 }]
108
109 @classmethod
110 def suitable(cls, url):
111 return False if RadLiveIE.suitable(url) else super(RadLiveSeasonIE, cls).suitable(url)
112
113 def _real_extract(self, url):
114 season_id = self._match_id(url)
115 webpage = self._download_webpage(url, season_id)
116
117 content_info = json.loads(self._search_regex(
118 r'<script[^>]*type=([\'"])application/json\1[^>]*>(?P<json>{.+?})</script>',
119 webpage, 'video info', group='json'))['props']['pageProps']['initialContentData']
120 video_info = content_info['season']
121
122 entries = [{
123 '_type': 'url_transparent',
124 'id': episode['structured_data']['url'].split('/')[-1],
125 'url': episode['structured_data']['url'],
126 'series': try_get(content_info, lambda x: x['series']['title']),
127 'season': video_info['title'],
128 'season_number': video_info.get('number'),
129 'season_id': video_info.get('id'),
130 'ie_key': RadLiveIE.ie_key(),
131 } for episode in video_info['episodes']]
132
133 return self.playlist_result(entries, season_id, video_info.get('title'))
134
135
6368e2e6 136class RadLiveChannelIE(RadLiveIE): # XXX: Do not subclass from concrete IE
92790da2 137 IE_NAME = 'radlive:channel'
138 _VALID_URL = r'https?://(?:www\.)?rad\.live/content/channel/(?P<id>[a-f0-9-]+)'
139 _TESTS = [{
140 'url': 'https://rad.live/content/channel/5c4d8df4-6fa0-413c-81e3-873479b49274',
141 'md5': '625156a08b7f2b0b849f234e664457ac',
142 'info_dict': {
143 'id': '5c4d8df4-6fa0-413c-81e3-873479b49274',
144 'title': 'Whistle Sports',
145 },
146 'playlist_mincount': 7,
147 }]
148
149 _QUERY = '''
150query WebChannelListing ($lrn: ID!) {
151 channel (id:$lrn) {
152 name
153 features {
154 structured_data
155 }
156 }
157}'''
158
159 @classmethod
160 def suitable(cls, url):
161 return False if RadLiveIE.suitable(url) else super(RadLiveChannelIE, cls).suitable(url)
162
163 def _real_extract(self, url):
164 channel_id = self._match_id(url)
165
166 graphql = self._download_json(
167 'https://content.mhq.12core.net/graphql', channel_id,
168 headers={'Content-Type': 'application/json'},
169 data=json.dumps({
170 'query': self._QUERY,
171 'variables': {'lrn': f'lrn:12core:media:content:channel:{channel_id}'}
172 }).encode('utf-8'))
173
174 data = traverse_obj(graphql, ('data', 'channel'))
175 if not data:
176 raise ExtractorError('Unable to extract video info, make sure the URL is valid')
177
178 entries = [{
179 '_type': 'url_transparent',
180 'url': feature['structured_data']['url'],
181 'ie_key': RadLiveIE.ie_key(),
182 } for feature in data['features']]
183
184 return self.playlist_result(entries, channel_id, data.get('name'))