1# coding: utf-8
2from __future__ import unicode_literals
3
4import re
5
6from .common import InfoExtractor
7from ..utils import (
8    int_or_none,
9    urlencode_postdata,
10    compat_str,
11    ExtractorError,
12)
13
14
15class CuriosityStreamBaseIE(InfoExtractor):
16    _NETRC_MACHINE = 'curiositystream'
17    _auth_token = None
18
19    def _handle_errors(self, result):
20        error = result.get('error', {}).get('message')
21        if error:
22            if isinstance(error, dict):
23                error = ', '.join(error.values())
24            raise ExtractorError(
25                '%s said: %s' % (self.IE_NAME, error), expected=True)
26
27    def _call_api(self, path, video_id, query=None):
28        headers = {}
29        if self._auth_token:
30            headers['X-Auth-Token'] = self._auth_token
31        result = self._download_json(
32            self._API_BASE_URL + path, video_id, headers=headers, query=query)
33        self._handle_errors(result)
34        return result['data']
35
36    def _real_initialize(self):
37        email, password = self._get_login_info()
38        if email is None:
39            return
40        result = self._download_json(
41            'https://api.curiositystream.com/v1/login', None,
42            note='Logging in', data=urlencode_postdata({
43                'email': email,
44                'password': password,
45            }))
46        self._handle_errors(result)
47        CuriosityStreamBaseIE._auth_token = result['message']['auth_token']
48
49
50class CuriosityStreamIE(CuriosityStreamBaseIE):
51    IE_NAME = 'curiositystream'
52    _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/video/(?P<id>\d+)'
53    _TESTS = [{
54        'url': 'https://app.curiositystream.com/video/2',
55        'info_dict': {
56            'id': '2',
57            'ext': 'mp4',
58            'title': 'How Did You Develop The Internet?',
59            'description': 'Vint Cerf, Google\'s Chief Internet Evangelist, describes how he and Bob Kahn created the internet.',
60            'channel': 'Curiosity Stream',
61            'categories': ['Technology', 'Interview'],
62            'average_rating': 96.79,
63            'series_id': '2',
64        },
65        'params': {
66            # m3u8 download
67            'skip_download': True,
68        },
69    }]
70
71    _API_BASE_URL = 'https://api.curiositystream.com/v1/media/'
72
73    def _real_extract(self, url):
74        video_id = self._match_id(url)
75
76        formats = []
77        for encoding_format in ('m3u8', 'mpd'):
78            media = self._call_api(video_id, video_id, query={
79                'encodingsNew': 'true',
80                'encodingsFormat': encoding_format,
81            })
82            for encoding in media.get('encodings', []):
83                playlist_url = encoding.get('master_playlist_url')
84                if encoding_format == 'm3u8':
85                    # use `m3u8` entry_protocol until EXT-X-MAP is properly supported by `m3u8_native` entry_protocol
86                    formats.extend(self._extract_m3u8_formats(
87                        playlist_url, video_id, 'mp4',
88                        m3u8_id='hls', fatal=False))
89                elif encoding_format == 'mpd':
90                    formats.extend(self._extract_mpd_formats(
91                        playlist_url, video_id, mpd_id='dash', fatal=False))
92                encoding_url = encoding.get('url')
93                file_url = encoding.get('file_url')
94                if not encoding_url and not file_url:
95                    continue
96                f = {
97                    'width': int_or_none(encoding.get('width')),
98                    'height': int_or_none(encoding.get('height')),
99                    'vbr': int_or_none(encoding.get('video_bitrate')),
100                    'abr': int_or_none(encoding.get('audio_bitrate')),
101                    'filesize': int_or_none(encoding.get('size_in_bytes')),
102                    'vcodec': encoding.get('video_codec'),
103                    'acodec': encoding.get('audio_codec'),
104                    'container': encoding.get('container_type'),
105                }
106                for f_url in (encoding_url, file_url):
107                    if not f_url:
108                        continue
109                    fmt = f.copy()
110                    rtmp = re.search(r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+))/(?P<playpath>mp[34]:.+)$', f_url)
111                    if rtmp:
112                        fmt.update({
113                            'url': rtmp.group('url'),
114                            'play_path': rtmp.group('playpath'),
115                            'app': rtmp.group('app'),
116                            'ext': 'flv',
117                            'format_id': 'rtmp',
118                        })
119                    else:
120                        fmt.update({
121                            'url': f_url,
122                            'format_id': 'http',
123                        })
124                    formats.append(fmt)
125        self._sort_formats(formats)
126
127        title = media['title']
128
129        subtitles = {}
130        for closed_caption in media.get('closed_captions', []):
131            sub_url = closed_caption.get('file')
132            if not sub_url:
133                continue
134            lang = closed_caption.get('code') or closed_caption.get('language') or 'en'
135            subtitles.setdefault(lang, []).append({
136                'url': sub_url,
137            })
138
139        return {
140            'id': video_id,
141            'formats': formats,
142            'title': title,
143            'description': media.get('description'),
144            'thumbnail': media.get('image_large') or media.get('image_medium') or media.get('image_small'),
145            'duration': int_or_none(media.get('duration')),
146            'tags': media.get('tags'),
147            'subtitles': subtitles,
148            'channel': media.get('producer'),
149            'categories': [media.get('primary_category'), media.get('type')],
150            'average_rating': media.get('rating_percentage'),
151            'series_id': str(media.get('collection_id') or '') or None,
152        }
153
154
155class CuriosityStreamCollectionBaseIE(CuriosityStreamBaseIE):
156
157    def _real_extract(self, url):
158        collection_id = self._match_id(url)
159        collection = self._call_api(collection_id, collection_id)
160        entries = []
161        for media in collection.get('media', []):
162            media_id = compat_str(media.get('id'))
163            media_type, ie = ('series', CuriosityStreamSeriesIE) if media.get('is_collection') else ('video', CuriosityStreamIE)
164            entries.append(self.url_result(
165                'https://curiositystream.com/%s/%s' % (media_type, media_id),
166                ie=ie.ie_key(), video_id=media_id))
167        return self.playlist_result(
168            entries, collection_id,
169            collection.get('title'), collection.get('description'))
170
171
172class CuriosityStreamCollectionsIE(CuriosityStreamCollectionBaseIE):
173    IE_NAME = 'curiositystream:collections'
174    _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/collections/(?P<id>\d+)'
175    _API_BASE_URL = 'https://api.curiositystream.com/v2/collections/'
176    _TESTS = [{
177        'url': 'https://curiositystream.com/collections/86',
178        'info_dict': {
179            'id': '86',
180            'title': 'Staff Picks',
181            'description': 'Wondering where to start? Here are a few of our favorite series and films... from our couch to yours.',
182        },
183        'playlist_mincount': 7,
184    }, {
185        'url': 'https://curiositystream.com/collections/36',
186        'only_matching': True,
187    }]
188
189
190class CuriosityStreamSeriesIE(CuriosityStreamCollectionBaseIE):
191    IE_NAME = 'curiositystream:series'
192    _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/(?:series|collection)/(?P<id>\d+)'
193    _API_BASE_URL = 'https://api.curiositystream.com/v2/series/'
194    _TESTS = [{
195        'url': 'https://curiositystream.com/series/2',
196        'info_dict': {
197            'id': '2',
198            'title': 'Curious Minds: The Internet',
199            'description': 'How is the internet shaping our lives in the 21st Century?',
200        },
201        'playlist_mincount': 16,
202    }, {
203        'url': 'https://curiositystream.com/collection/2',
204        'only_matching': True,
205    }]
206