3 from ..utils
import ExtractorError
, traverse_obj
, try_get
, unified_timestamp
4 from .common
import InfoExtractor
7 class RadLiveIE(InfoExtractor
):
9 _VALID_URL
= r
'https?://(?:www\.)?rad\.live/content/(?P<content_type>feature|episode)/(?P<id>[a-f0-9-]+)'
11 'url': 'https://rad.live/content/feature/dc5acfbc-761b-4bec-9564-df999905116a',
12 'md5': '6219d5d31d52de87d21c9cf5b7cb27ff',
14 'id': 'dc5acfbc-761b-4bec-9564-df999905116a',
16 'title': 'Deathpact - Digital Mirage 2 [Full Set]',
18 'thumbnail': 'https://static.12core.net/cb65ae077a079c68380e38f387fbc438.png',
20 'release_timestamp': 1600185600.0,
21 'channel': 'Proximity',
22 'channel_id': '9ce6dd01-70a4-4d59-afb6-d01f807cd009',
23 'channel_url': 'https://rad.live/content/channel/9ce6dd01-70a4-4d59-afb6-d01f807cd009',
26 'url': 'https://rad.live/content/episode/bbcf66ec-0d02-4ca0-8dc0-4213eb2429bf',
27 'md5': '40b2175f347592125d93e9a344080125',
29 'id': 'bbcf66ec-0d02-4ca0-8dc0-4213eb2429bf',
31 'title': 'E01: Bad Jokes 1',
33 'thumbnail': 'https://lsp.littlstar.com/channels/WHISTLE/BAD_JOKES/SEASON_1/BAD_JOKES_101/poster.jpg',
34 'description': 'Bad Jokes - Champions, Adam Pally, Super Troopers, Team Edge and 2Hype',
35 'release_timestamp': None,
39 'episode': 'E01: Bad Jokes 1',
45 def _real_extract(self
, url
):
46 content_type
, video_id
= self
._match
_valid
_url
(url
).groups()
48 webpage
= self
._download
_webpage
(url
, video_id
)
50 content_info
= json
.loads(self
._search
_regex
(
51 r
'<script[^>]*type=([\'"])application/json\1[^>]*>(?P<json>{.+?})</script>',
52 webpage, 'video info', group='json'))['props']['pageProps']['initialContentData']
53 video_info = content_info[content_type]
56 raise ExtractorError('Unable to extract video info, make sure the URL is valid')
58 formats = self._extract_m3u8_formats(video_info['assets']['videos'][0]['url'], video_id)
59 self._sort_formats(formats)
61 data = video_info.get('structured_data', {})
63 release_date = unified_timestamp(traverse_obj(data, ('releasedEvent', 'startDate')))
64 channel = next(iter(content_info.get('channels', [])), {})
65 channel_id = channel.get('lrn', '').split(':')[-1] or None
69 'title': video_info['title'],
71 'language': traverse_obj(data, ('potentialAction', 'target', 'inLanguage')),
72 'thumbnail': traverse_obj(data, ('image', 'contentUrl')),
73 'description': data.get('description'),
74 'release_timestamp': release_date,
75 'channel': channel.get('name'),
76 'channel_id': channel_id,
77 'channel_url': f'https://rad.live/content/channel/{channel_id}' if channel_id else None,
80 if content_type == 'episode':
82 # TODO: Get season number when downloading single episode
83 'episode': video_info.get('title'),
84 'episode_number': video_info.get('number'),
85 'episode_id': video_info.get('id'),
91 class RadLiveSeasonIE(RadLiveIE):
92 IE_NAME = 'radlive:season'
93 _VALID_URL = r'https?://(?:www\.)?rad\.live/content/season/(?P<id>[a-f0-9-]+)'
95 'url': 'https://rad.live/content/season/08a290f7-c9ef-4e22-9105-c255995a2e75',
96 'md5': '40b2175f347592125d93e9a344080125',
98 'id': '08a290f7-c9ef-4e22-9105-c255995a2e75',
99 'title': 'Bad Jokes - Season 1',
101 'playlist_mincount': 5,
105 def suitable(cls, url):
106 return False if RadLiveIE.suitable(url) else super(RadLiveSeasonIE, cls).suitable(url)
108 def _real_extract(self, url):
109 season_id = self._match_id(url)
110 webpage = self._download_webpage(url, season_id)
112 content_info = json.loads(self._search_regex(
113 r'<script[^>]*type=([\'"])application
/json\
1[^
>]*>(?P
<json
>{.+?}
)</script
>',
114 webpage, 'video info
', group='json
'))['props
']['pageProps
']['initialContentData
']
115 video_info = content_info['season
']
118 '_type
': 'url_transparent
',
119 'id': episode['structured_data
']['url
'].split('/')[-1],
120 'url
': episode['structured_data
']['url
'],
121 'series
': try_get(content_info, lambda x: x['series
']['title
']),
122 'season
': video_info['title
'],
123 'season_number
': video_info.get('number
'),
124 'season_id
': video_info.get('id'),
125 'ie_key
': RadLiveIE.ie_key(),
126 } for episode in video_info['episodes
']]
128 return self.playlist_result(entries, season_id, video_info.get('title
'))
131 class RadLiveChannelIE(RadLiveIE):
132 IE_NAME = 'radlive
:channel
'
133 _VALID_URL = r'https?
://(?
:www\
.)?rad\
.live
/content
/channel
/(?P
<id>[a
-f0
-9-]+)'
135 'url
': 'https
://rad
.live
/content
/channel
/5c4d8df4
-6fa0
-413c
-81e3
-873479b49274
',
136 'md5
': '625156a08b7f2b0b849f234e664457ac
',
138 'id': '5c4d8df4
-6fa0
-413c
-81e3
-873479b49274
',
139 'title
': 'Whistle Sports
',
141 'playlist_mincount
': 7,
145 query WebChannelListing ($lrn: ID!) {
155 def suitable(cls, url):
156 return False if RadLiveIE.suitable(url) else super(RadLiveChannelIE, cls).suitable(url)
158 def _real_extract(self, url):
159 channel_id = self._match_id(url)
161 graphql = self._download_json(
162 'https
://content
.mhq
.12core
.net
/graphql
', channel_id,
163 headers={'Content-Type': 'application/json'},
165 'query
': self._QUERY,
166 'variables
': {'lrn': f'lrn:12core:media:content:channel:{channel_id}'}
169 data
= traverse_obj(graphql
, ('data', 'channel'))
171 raise ExtractorError('Unable to extract video info, make sure the URL is valid')
174 '_type': 'url_transparent',
175 'url': feature
['structured_data']['url'],
176 'ie_key': RadLiveIE
.ie_key(),
177 } for feature
in data
['features']]
179 return self
.playlist_result(entries
, channel_id
, data
.get('name'))