3 from .common
import InfoExtractor
12 class CuriosityStreamBaseIE(InfoExtractor
):
13 _NETRC_MACHINE
= 'curiositystream'
16 def _handle_errors(self
, result
):
17 error
= result
.get('error', {}).get('message')
19 if isinstance(error
, dict):
20 error
= ', '.join(error
.values())
22 '%s said: %s' % (self
.IE_NAME
, error
), expected
=True)
24 def _call_api(self
, path
, video_id
, query
=None):
26 if not self
._auth
_token
:
27 auth_cookie
= self
._get
_cookies
('https://curiositystream.com').get('auth_token')
29 self
.write_debug('Obtained auth_token cookie')
30 self
._auth
_token
= auth_cookie
.value
32 headers
['X-Auth-Token'] = self
._auth
_token
33 result
= self
._download
_json
(
34 self
._API
_BASE
_URL
+ path
, video_id
, headers
=headers
, query
=query
)
35 self
._handle
_errors
(result
)
38 def _perform_login(self
, username
, password
):
39 result
= self
._download
_json
(
40 'https://api.curiositystream.com/v1/login', None,
41 note
='Logging in', data
=urlencode_postdata({
45 self
._handle
_errors
(result
)
46 CuriosityStreamBaseIE
._auth
_token
= result
['message']['auth_token']
49 class CuriosityStreamIE(CuriosityStreamBaseIE
):
50 IE_NAME
= 'curiositystream'
51 _VALID_URL
= r
'https?://(?:app\.)?curiositystream\.com/video/(?P<id>\d+)'
53 'url': 'https://app.curiositystream.com/video/2',
57 'title': 'How Did You Develop The Internet?',
58 'description': 'Vint Cerf, Google\'s Chief Internet Evangelist, describes how he and Bob Kahn created the internet.',
59 'channel': 'Curiosity Stream',
60 'categories': ['Technology', 'Interview'],
61 'average_rating': 96.79,
66 'skip_download': True,
70 _API_BASE_URL
= 'https://api.curiositystream.com/v1/media/'
72 def _real_extract(self
, url
):
73 video_id
= self
._match
_id
(url
)
76 for encoding_format
in ('m3u8', 'mpd'):
77 media
= self
._call
_api
(video_id
, video_id
, query
={
78 'encodingsNew': 'true',
79 'encodingsFormat': encoding_format
,
81 for encoding
in media
.get('encodings', []):
82 playlist_url
= encoding
.get('master_playlist_url')
83 if encoding_format
== 'm3u8':
84 # use `m3u8` entry_protocol until EXT-X-MAP is properly supported by `m3u8_native` entry_protocol
85 formats
.extend(self
._extract
_m
3u8_formats
(
86 playlist_url
, video_id
, 'mp4',
87 m3u8_id
='hls', fatal
=False))
88 elif encoding_format
== 'mpd':
89 formats
.extend(self
._extract
_mpd
_formats
(
90 playlist_url
, video_id
, mpd_id
='dash', fatal
=False))
91 encoding_url
= encoding
.get('url')
92 file_url
= encoding
.get('file_url')
93 if not encoding_url
and not file_url
:
96 'width': int_or_none(encoding
.get('width')),
97 'height': int_or_none(encoding
.get('height')),
98 'vbr': int_or_none(encoding
.get('video_bitrate')),
99 'abr': int_or_none(encoding
.get('audio_bitrate')),
100 'filesize': int_or_none(encoding
.get('size_in_bytes')),
101 'vcodec': encoding
.get('video_codec'),
102 'acodec': encoding
.get('audio_codec'),
103 'container': encoding
.get('container_type'),
105 for f_url
in (encoding_url
, file_url
):
109 rtmp
= re
.search(r
'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+))/(?P<playpath>mp[34]:.+)$', f_url
)
112 'url': rtmp
.group('url'),
113 'play_path': rtmp
.group('playpath'),
114 'app': rtmp
.group('app'),
124 self
._sort
_formats
(formats
)
126 title
= media
['title']
129 for closed_caption
in media
.get('closed_captions', []):
130 sub_url
= closed_caption
.get('file')
133 lang
= closed_caption
.get('code') or closed_caption
.get('language') or 'en'
134 subtitles
.setdefault(lang
, []).append({
142 'description': media
.get('description'),
143 'thumbnail': media
.get('image_large') or media
.get('image_medium') or media
.get('image_small'),
144 'duration': int_or_none(media
.get('duration')),
145 'tags': media
.get('tags'),
146 'subtitles': subtitles
,
147 'channel': media
.get('producer'),
148 'categories': [media
.get('primary_category'), media
.get('type')],
149 'average_rating': media
.get('rating_percentage'),
150 'series_id': str(media
.get('collection_id') or '') or None,
154 class CuriosityStreamCollectionBaseIE(CuriosityStreamBaseIE
):
156 def _real_extract(self
, url
):
157 collection_id
= self
._match
_id
(url
)
158 collection
= self
._call
_api
(collection_id
, collection_id
)
160 for media
in collection
.get('media', []):
161 media_id
= compat_str(media
.get('id'))
162 media_type
, ie
= ('series', CuriosityStreamSeriesIE
) if media
.get('is_collection') else ('video', CuriosityStreamIE
)
163 entries
.append(self
.url_result(
164 'https://curiositystream.com/%s/%s' % (media_type
, media_id
),
165 ie
=ie
.ie_key(), video_id
=media_id
))
166 return self
.playlist_result(
167 entries
, collection_id
,
168 collection
.get('title'), collection
.get('description'))
171 class CuriosityStreamCollectionsIE(CuriosityStreamCollectionBaseIE
):
172 IE_NAME
= 'curiositystream:collections'
173 _VALID_URL
= r
'https?://(?:app\.)?curiositystream\.com/collections/(?P<id>\d+)'
174 _API_BASE_URL
= 'https://api.curiositystream.com/v2/collections/'
176 'url': 'https://curiositystream.com/collections/86',
179 'title': 'Staff Picks',
180 'description': 'Wondering where to start? Here are a few of our favorite series and films... from our couch to yours.',
182 'playlist_mincount': 7,
184 'url': 'https://curiositystream.com/collections/36',
185 'only_matching': True,
189 class CuriosityStreamSeriesIE(CuriosityStreamCollectionBaseIE
):
190 IE_NAME
= 'curiositystream:series'
191 _VALID_URL
= r
'https?://(?:app\.)?curiositystream\.com/(?:series|collection)/(?P<id>\d+)'
192 _API_BASE_URL
= 'https://api.curiositystream.com/v2/series/'
194 'url': 'https://curiositystream.com/series/2',
197 'title': 'Curious Minds: The Internet',
198 'description': 'How is the internet shaping our lives in the 21st Century?',
200 'playlist_mincount': 16,
202 'url': 'https://curiositystream.com/collection/2',
203 'only_matching': True,