[npo:live] Add extractor (Closes #4691)
[youtube-dl] / youtube_dl / extractor / npo.py
1 from __future__ import unicode_literals
2
3 import re
4
5 from .common import InfoExtractor
6 from ..utils import (
7     fix_xml_ampersands,
8     parse_duration,
9     qualities,
10     strip_jsonp,
11     unified_strdate,
12     url_basename,
13 )
14
15
16 class NPOBaseIE(InfoExtractor):
17     def _get_token(self, video_id):
18         token_page = self._download_webpage(
19             'http://ida.omroep.nl/npoplayer/i.js',
20             video_id, note='Downloading token')
21         return self._search_regex(
22             r'npoplayer\.token = "(.+?)"', token_page, 'token')
23
24
25 class NPOIE(NPOBaseIE):
26     IE_NAME = 'npo.nl'
27     _VALID_URL = r'https?://www\.npo\.nl/[^/]+/[^/]+/(?P<id>[^/?]+)'
28
29     _TESTS = [
30         {
31             'url': 'http://www.npo.nl/nieuwsuur/22-06-2014/VPWON_1220719',
32             'md5': '4b3f9c429157ec4775f2c9cb7b911016',
33             'info_dict': {
34                 'id': 'VPWON_1220719',
35                 'ext': 'm4v',
36                 'title': 'Nieuwsuur',
37                 'description': 'Dagelijks tussen tien en elf: nieuws, sport en achtergronden.',
38                 'upload_date': '20140622',
39             },
40         },
41         {
42             'url': 'http://www.npo.nl/de-mega-mike-mega-thomas-show/27-02-2009/VARA_101191800',
43             'md5': 'da50a5787dbfc1603c4ad80f31c5120b',
44             'info_dict': {
45                 'id': 'VARA_101191800',
46                 'ext': 'm4v',
47                 'title': 'De Mega Mike & Mega Thomas show',
48                 'description': 'md5:3b74c97fc9d6901d5a665aac0e5400f4',
49                 'upload_date': '20090227',
50                 'duration': 2400,
51             },
52         },
53         {
54             'url': 'http://www.npo.nl/tegenlicht/25-02-2013/VPWON_1169289',
55             'md5': 'f8065e4e5a7824068ed3c7e783178f2c',
56             'info_dict': {
57                 'id': 'VPWON_1169289',
58                 'ext': 'm4v',
59                 'title': 'Tegenlicht',
60                 'description': 'md5:d6476bceb17a8c103c76c3b708f05dd1',
61                 'upload_date': '20130225',
62                 'duration': 3000,
63             },
64         },
65         {
66             'url': 'http://www.npo.nl/de-nieuwe-mens-deel-1/21-07-2010/WO_VPRO_043706',
67             'info_dict': {
68                 'id': 'WO_VPRO_043706',
69                 'ext': 'wmv',
70                 'title': 'De nieuwe mens - Deel 1',
71                 'description': 'md5:518ae51ba1293ffb80d8d8ce90b74e4b',
72                 'duration': 4680,
73             },
74             'params': {
75                 # mplayer mms download
76                 'skip_download': True,
77             }
78         },
79         # non asf in streams
80         {
81             'url': 'http://www.npo.nl/hoe-gaat-europa-verder-na-parijs/10-01-2015/WO_NOS_762771',
82             'md5': 'b3da13de374cbe2d5332a7e910bef97f',
83             'info_dict': {
84                 'id': 'WO_NOS_762771',
85                 'ext': 'mp4',
86                 'title': 'Hoe gaat Europa verder na Parijs?',
87             },
88         },
89     ]
90
91     def _real_extract(self, url):
92         video_id = self._match_id(url)
93         return self._get_info(video_id)
94
95     def _get_info(self, video_id):
96         metadata = self._download_json(
97             'http://e.omroep.nl/metadata/aflevering/%s' % video_id,
98             video_id,
99             # We have to remove the javascript callback
100             transform_source=strip_jsonp,
101         )
102
103         token = self._get_token(video_id)
104
105         formats = []
106
107         pubopties = metadata.get('pubopties')
108         if pubopties:
109             quality = qualities(['adaptive', 'wmv_sb', 'h264_sb', 'wmv_bb', 'h264_bb', 'wvc1_std', 'h264_std'])
110             for format_id in pubopties:
111                 format_info = self._download_json(
112                     'http://ida.omroep.nl/odi/?prid=%s&puboptions=%s&adaptive=yes&token=%s'
113                     % (video_id, format_id, token),
114                     video_id, 'Downloading %s JSON' % format_id)
115                 if format_info.get('error_code', 0) or format_info.get('errorcode', 0):
116                     continue
117                 streams = format_info.get('streams')
118                 if streams:
119                     video_info = self._download_json(
120                         streams[0] + '&type=json',
121                         video_id, 'Downloading %s stream JSON' % format_id)
122                 else:
123                     video_info = format_info
124                 video_url = video_info.get('url')
125                 if not video_url:
126                     continue
127                 if format_id == 'adaptive':
128                     formats.extend(self._extract_m3u8_formats(video_url, video_id))
129                 else:
130                     formats.append({
131                         'url': video_url,
132                         'format_id': format_id,
133                         'quality': quality(format_id),
134                     })
135
136         streams = metadata.get('streams')
137         if streams:
138             for i, stream in enumerate(streams):
139                 stream_url = stream.get('url')
140                 if not stream_url:
141                     continue
142                 if '.asf' not in stream_url:
143                     formats.append({
144                         'url': stream_url,
145                         'quality': stream.get('kwaliteit'),
146                     })
147                     continue
148                 asx = self._download_xml(
149                     stream_url, video_id,
150                     'Downloading stream %d ASX playlist' % i,
151                     transform_source=fix_xml_ampersands)
152                 ref = asx.find('./ENTRY/Ref')
153                 if ref is None:
154                     continue
155                 video_url = ref.get('href')
156                 if not video_url:
157                     continue
158                 formats.append({
159                     'url': video_url,
160                     'ext': stream.get('formaat', 'asf'),
161                     'quality': stream.get('kwaliteit'),
162                 })
163
164         self._sort_formats(formats)
165
166         return {
167             'id': video_id,
168             'title': metadata['titel'],
169             'description': metadata['info'],
170             'thumbnail': metadata.get('images', [{'url': None}])[-1]['url'],
171             'upload_date': unified_strdate(metadata.get('gidsdatum')),
172             'duration': parse_duration(metadata.get('tijdsduur')),
173             'formats': formats,
174         }
175
176
177 class NPOLiveIE(NPOBaseIE):
178     IE_NAME = 'npo.nl:live'
179     _VALID_URL = r'https?://www\.npo\.nl/live/(?P<id>.+)'
180
181     _TEST = {
182         'url': 'http://www.npo.nl/live/npo-1',
183         'info_dict': {
184             'id': 'LI_NEDERLAND1_136692',
185             'display_id': 'npo-1',
186             'ext': 'mp4',
187             'title': 're:^Nederland 1 [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
188             'description': 'Livestream',
189             'is_live': True,
190         },
191         'params': {
192             'skip_download': True,
193         }
194     }
195
196     def _real_extract(self, url):
197         display_id = self._match_id(url)
198
199         webpage = self._download_webpage(url, display_id)
200
201         live_id = self._search_regex(
202             r'data-prid="([^"]+)"', webpage, 'live id')
203
204         metadata = self._download_json(
205             'http://e.omroep.nl/metadata/%s' % live_id,
206             display_id, transform_source=strip_jsonp)
207
208         token = self._get_token(display_id)
209
210         formats = []
211
212         streams = metadata.get('streams')
213         if streams:
214             for stream in streams:
215                 stream_type = stream.get('type').lower()
216                 if stream_type == 'ss':
217                     continue
218                 stream_info = self._download_json(
219                     'http://ida.omroep.nl/aapi/?stream=%s&token=%s&type=jsonp'
220                     % (stream.get('url'), token),
221                     display_id, 'Downloading %s JSON' % stream_type)
222                 if stream_info.get('error_code', 0) or stream_info.get('errorcode', 0):
223                     continue
224                 stream_url = self._download_json(
225                     stream_info['stream'], display_id,
226                     'Downloading %s URL' % stream_type,
227                     transform_source=strip_jsonp)
228                 if stream_type == 'hds':
229                     f4m_formats = self._extract_f4m_formats(stream_url, display_id)
230                     # f4m downloader downloads only piece of live stream
231                     for f4m_format in f4m_formats:
232                         f4m_format['preference'] = -1
233                     formats.extend(f4m_formats)
234                 elif stream_type == 'hls':
235                     formats.extend(self._extract_m3u8_formats(stream_url, display_id, 'mp4'))
236                 else:
237                     formats.append({
238                         'url': stream_url,
239                     })
240
241         self._sort_formats(formats)
242
243         return {
244             'id': live_id,
245             'display_id': display_id,
246             'title': self._live_title(metadata['titel']),
247             'description': metadata['info'],
248             'thumbnail': metadata.get('images', [{'url': None}])[-1]['url'],
249             'formats': formats,
250             'is_live': True,
251         }
252
253
254 class TegenlichtVproIE(NPOIE):
255     IE_NAME = 'tegenlicht.vpro.nl'
256     _VALID_URL = r'https?://tegenlicht\.vpro\.nl/afleveringen/.*?'
257
258     _TESTS = [
259         {
260             'url': 'http://tegenlicht.vpro.nl/afleveringen/2012-2013/de-toekomst-komt-uit-afrika.html',
261             'md5': 'f8065e4e5a7824068ed3c7e783178f2c',
262             'info_dict': {
263                 'id': 'VPWON_1169289',
264                 'ext': 'm4v',
265                 'title': 'Tegenlicht',
266                 'description': 'md5:d6476bceb17a8c103c76c3b708f05dd1',
267                 'upload_date': '20130225',
268             },
269         },
270     ]
271
272     def _real_extract(self, url):
273         name = url_basename(url)
274         webpage = self._download_webpage(url, name)
275         urn = self._html_search_meta('mediaurn', webpage)
276         info_page = self._download_json(
277             'http://rs.vpro.nl/v2/api/media/%s.json' % urn, name)
278         return self._get_info(info_page['mid'])