Merge pull request #9466 from TRox1972/patch-1
[youtube-dl] / youtube_dl / extractor / nrk.py
1 # encoding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5
6 from .common import InfoExtractor
7 from ..compat import compat_urllib_parse_unquote
8 from ..utils import (
9     ExtractorError,
10     int_or_none,
11     parse_age_limit,
12     parse_duration,
13 )
14
15
16 class NRKBaseIE(InfoExtractor):
17     def _extract_formats(self, manifest_url, video_id, fatal=True):
18         formats = []
19         formats.extend(self._extract_f4m_formats(
20             manifest_url + '?hdcore=3.5.0&plugin=aasp-3.5.0.151.81',
21             video_id, f4m_id='hds', fatal=fatal))
22         formats.extend(self._extract_m3u8_formats(manifest_url.replace(
23             'akamaihd.net/z/', 'akamaihd.net/i/').replace('/manifest.f4m', '/master.m3u8'),
24             video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=fatal))
25         return formats
26
27     def _real_extract(self, url):
28         video_id = self._match_id(url)
29
30         data = self._download_json(
31             'http://%s/mediaelement/%s' % (self._API_HOST, video_id),
32             video_id, 'Downloading mediaelement JSON')
33
34         title = data.get('fullTitle') or data.get('mainTitle') or data['title']
35         video_id = data.get('id') or video_id
36
37         entries = []
38
39         media_assets = data.get('mediaAssets')
40         if media_assets and isinstance(media_assets, list):
41             def video_id_and_title(idx):
42                 return ((video_id, title) if len(media_assets) == 1
43                         else ('%s-%d' % (video_id, idx), '%s (Part %d)' % (title, idx)))
44             for num, asset in enumerate(media_assets, 1):
45                 asset_url = asset.get('url')
46                 if not asset_url:
47                     continue
48                 formats = self._extract_formats(asset_url, video_id, fatal=False)
49                 if not formats:
50                     continue
51                 self._sort_formats(formats)
52                 entry_id, entry_title = video_id_and_title(num)
53                 duration = parse_duration(asset.get('duration'))
54                 subtitles = {}
55                 for subtitle in ('webVtt', 'timedText'):
56                     subtitle_url = asset.get('%sSubtitlesUrl' % subtitle)
57                     if subtitle_url:
58                         subtitles.setdefault('no', []).append({'url': subtitle_url})
59                 entries.append({
60                     'id': asset.get('carrierId') or entry_id,
61                     'title': entry_title,
62                     'duration': duration,
63                     'subtitles': subtitles,
64                     'formats': formats,
65                 })
66
67         if not entries:
68             media_url = data.get('mediaUrl')
69             if media_url:
70                 formats = self._extract_formats(media_url, video_id)
71                 self._sort_formats(formats)
72                 duration = parse_duration(data.get('duration'))
73                 entries = [{
74                     'id': video_id,
75                     'title': title,
76                     'duration': duration,
77                     'formats': formats,
78                 }]
79
80         if not entries:
81             if data.get('usageRights', {}).get('isGeoBlocked'):
82                 raise ExtractorError(
83                     'NRK har ikke rettigheter til å vise dette programmet utenfor Norge',
84                     expected=True)
85
86         conviva = data.get('convivaStatistics') or {}
87         series = conviva.get('seriesName') or data.get('seriesTitle')
88         episode = conviva.get('episodeName') or data.get('episodeNumberOrDate')
89
90         thumbnails = None
91         images = data.get('images')
92         if images and isinstance(images, dict):
93             web_images = images.get('webImages')
94             if isinstance(web_images, list):
95                 thumbnails = [{
96                     'url': image['imageUrl'],
97                     'width': int_or_none(image.get('width')),
98                     'height': int_or_none(image.get('height')),
99                 } for image in web_images if image.get('imageUrl')]
100
101         description = data.get('description')
102
103         common_info = {
104             'description': description,
105             'series': series,
106             'episode': episode,
107             'age_limit': parse_age_limit(data.get('legalAge')),
108             'thumbnails': thumbnails,
109         }
110
111         vcodec = 'none' if data.get('mediaType') == 'Audio' else None
112
113         # TODO: extract chapters when https://github.com/rg3/youtube-dl/pull/9409 is merged
114
115         for entry in entries:
116             entry.update(common_info)
117             for f in entry['formats']:
118                 f['vcodec'] = vcodec
119
120         return self.playlist_result(entries, video_id, title, description)
121
122
123 class NRKIE(NRKBaseIE):
124     _VALID_URL = r'(?:nrk:|https?://(?:www\.)?nrk\.no/video/PS\*)(?P<id>\d+)'
125     _API_HOST = 'v8.psapi.nrk.no'
126     _TESTS = [{
127         # video
128         'url': 'http://www.nrk.no/video/PS*150533',
129         'md5': '2f7f6eeb2aacdd99885f355428715cfa',
130         'info_dict': {
131             'id': '150533',
132             'ext': 'mp4',
133             'title': 'Dompap og andre fugler i Piip-Show',
134             'description': 'md5:d9261ba34c43b61c812cb6b0269a5c8f',
135             'duration': 263,
136         }
137     }, {
138         # audio
139         'url': 'http://www.nrk.no/video/PS*154915',
140         # MD5 is unstable
141         'info_dict': {
142             'id': '154915',
143             'ext': 'flv',
144             'title': 'Slik høres internett ut når du er blind',
145             'description': 'md5:a621f5cc1bd75c8d5104cb048c6b8568',
146             'duration': 20,
147         }
148     }]
149
150
151 class NRKTVIE(NRKBaseIE):
152     IE_DESC = 'NRK TV and NRK Radio'
153     _VALID_URL = r'https?://(?:tv|radio)\.nrk(?:super)?\.no/(?:serie/[^/]+|program)/(?P<id>[a-zA-Z]{4}\d{8})(?:/\d{2}-\d{2}-\d{4})?(?:#del=(?P<part_id>\d+))?'
154     _API_HOST = 'psapi-we.nrk.no'
155
156     _TESTS = [{
157         'url': 'https://tv.nrk.no/serie/20-spoersmaal-tv/MUHH48000314/23-05-2014',
158         'md5': '4e9ca6629f09e588ed240fb11619922a',
159         'info_dict': {
160             'id': 'MUHH48000314AA',
161             'ext': 'mp4',
162             'title': '20 spørsmål 23.05.2014',
163             'description': 'md5:bdea103bc35494c143c6a9acdd84887a',
164             'duration': 1741.52,
165         },
166     }, {
167         'url': 'https://tv.nrk.no/program/mdfp15000514',
168         'md5': '43d0be26663d380603a9cf0c24366531',
169         'info_dict': {
170             'id': 'MDFP15000514CA',
171             'ext': 'mp4',
172             'title': 'Grunnlovsjubiléet - Stor ståhei for ingenting 24.05.2014',
173             'description': 'md5:89290c5ccde1b3a24bb8050ab67fe1db',
174             'duration': 4605.08,
175         },
176     }, {
177         # single playlist video
178         'url': 'https://tv.nrk.no/serie/tour-de-ski/MSPO40010515/06-01-2015#del=2',
179         'md5': 'adbd1dbd813edaf532b0a253780719c2',
180         'info_dict': {
181             'id': 'MSPO40010515-part2',
182             'ext': 'flv',
183             'title': 'Tour de Ski: Sprint fri teknikk, kvinner og menn 06.01.2015 (del 2:2)',
184             'description': 'md5:238b67b97a4ac7d7b4bf0edf8cc57d26',
185         },
186         'skip': 'Only works from Norway',
187     }, {
188         'url': 'https://tv.nrk.no/serie/tour-de-ski/MSPO40010515/06-01-2015',
189         'playlist': [{
190             'md5': '9480285eff92d64f06e02a5367970a7a',
191             'info_dict': {
192                 'id': 'MSPO40010515-part1',
193                 'ext': 'flv',
194                 'title': 'Tour de Ski: Sprint fri teknikk, kvinner og menn 06.01.2015 (del 1:2)',
195                 'description': 'md5:238b67b97a4ac7d7b4bf0edf8cc57d26',
196             },
197         }, {
198             'md5': 'adbd1dbd813edaf532b0a253780719c2',
199             'info_dict': {
200                 'id': 'MSPO40010515-part2',
201                 'ext': 'flv',
202                 'title': 'Tour de Ski: Sprint fri teknikk, kvinner og menn 06.01.2015 (del 2:2)',
203                 'description': 'md5:238b67b97a4ac7d7b4bf0edf8cc57d26',
204             },
205         }],
206         'info_dict': {
207             'id': 'MSPO40010515',
208             'title': 'Tour de Ski: Sprint fri teknikk, kvinner og menn',
209             'description': 'md5:238b67b97a4ac7d7b4bf0edf8cc57d26',
210             'duration': 6947.52,
211         },
212         'skip': 'Only works from Norway',
213     }, {
214         'url': 'https://radio.nrk.no/serie/dagsnytt/NPUB21019315/12-07-2015#',
215         'only_matching': True,
216     }]
217
218
219 class NRKPlaylistIE(InfoExtractor):
220     _VALID_URL = r'https?://(?:www\.)?nrk\.no/(?!video|skole)(?:[^/]+/)+(?P<id>[^/]+)'
221
222     _TESTS = [{
223         'url': 'http://www.nrk.no/troms/gjenopplev-den-historiske-solformorkelsen-1.12270763',
224         'info_dict': {
225             'id': 'gjenopplev-den-historiske-solformorkelsen-1.12270763',
226             'title': 'Gjenopplev den historiske solformørkelsen',
227             'description': 'md5:c2df8ea3bac5654a26fc2834a542feed',
228         },
229         'playlist_count': 2,
230     }, {
231         'url': 'http://www.nrk.no/kultur/bok/rivertonprisen-til-karin-fossum-1.12266449',
232         'info_dict': {
233             'id': 'rivertonprisen-til-karin-fossum-1.12266449',
234             'title': 'Rivertonprisen til Karin Fossum',
235             'description': 'Første kvinne på 15 år til å vinne krimlitteraturprisen.',
236         },
237         'playlist_count': 5,
238     }]
239
240     def _real_extract(self, url):
241         playlist_id = self._match_id(url)
242
243         webpage = self._download_webpage(url, playlist_id)
244
245         entries = [
246             self.url_result('nrk:%s' % video_id, 'NRK')
247             for video_id in re.findall(
248                 r'class="[^"]*\brich\b[^"]*"[^>]+data-video-id="([^"]+)"',
249                 webpage)
250         ]
251
252         playlist_title = self._og_search_title(webpage)
253         playlist_description = self._og_search_description(webpage)
254
255         return self.playlist_result(
256             entries, playlist_id, playlist_title, playlist_description)
257
258
259 class NRKSkoleIE(InfoExtractor):
260     IE_DESC = 'NRK Skole'
261     _VALID_URL = r'https?://(?:www\.)?nrk\.no/skole/klippdetalj?.*\btopic=(?P<id>[^/?#&]+)'
262
263     _TESTS = [{
264         'url': 'http://nrk.no/skole/klippdetalj?topic=nrk:klipp/616532',
265         'md5': '04cd85877cc1913bce73c5d28a47e00f',
266         'info_dict': {
267             'id': '6021',
268             'ext': 'flv',
269             'title': 'Genetikk og eneggede tvillinger',
270             'description': 'md5:3aca25dcf38ec30f0363428d2b265f8d',
271             'duration': 399,
272         },
273     }, {
274         'url': 'http://www.nrk.no/skole/klippdetalj?topic=nrk%3Aklipp%2F616532#embed',
275         'only_matching': True,
276     }, {
277         'url': 'http://www.nrk.no/skole/klippdetalj?topic=urn:x-mediadb:21379',
278         'only_matching': True,
279     }]
280
281     def _real_extract(self, url):
282         video_id = compat_urllib_parse_unquote(self._match_id(url))
283
284         webpage = self._download_webpage(url, video_id)
285
286         nrk_id = self._search_regex(r'data-nrk-id=["\'](\d+)', webpage, 'nrk id')
287         return self.url_result('nrk:%s' % nrk_id)