[wdr:elefant] Add extractor
[youtube-dl] / youtube_dl / extractor / wdr.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5
6 from .common import InfoExtractor
7 from ..utils import (
8     determine_ext,
9     ExtractorError,
10     js_to_json,
11     strip_jsonp,
12     unified_strdate,
13     update_url_query,
14     urlhandle_detect_ext,
15 )
16
17
18 class WDRBaseIE(InfoExtractor):
19     def _extract_jsonp_url(self, webpage, display_id):
20         # for wdr.de the data-extension is in a tag with the class "mediaLink"
21         # for wdr.de radio players, in a tag with the class "wdrrPlayerPlayBtn"
22         # for wdrmaus, in a tag with the class "videoButton" (previously a link
23         # to the page in a multiline "videoLink"-tag)
24         json_metadata = self._html_search_regex(
25             r'''(?sx)class=
26                     (?:
27                         (["\'])(?:mediaLink|wdrrPlayerPlayBtn|videoButton)\b.*?\1[^>]+|
28                         (["\'])videoLink\b.*?\2[\s]*>\n[^\n]*
29                     )data-extension=(["\'])(?P<data>(?:(?!\3).)+)\3
30             ''',
31             webpage, 'media link', default=None, group='data')
32
33         if not json_metadata:
34             return
35
36         media_link_obj = self._parse_json(json_metadata, display_id,
37                                           transform_source=js_to_json)
38         return media_link_obj['mediaObj']['url']
39
40     def _extract_wdr_video(self, jsonp_url, display_id):
41         metadata = self._download_json(
42             jsonp_url, display_id, transform_source=strip_jsonp)
43
44         metadata_tracker_data = metadata['trackerData']
45         metadata_media_resource = metadata['mediaResource']
46
47         formats = []
48
49         # check if the metadata contains a direct URL to a file
50         for kind, media_resource in metadata_media_resource.items():
51             if kind not in ('dflt', 'alt'):
52                 continue
53
54             for tag_name, medium_url in media_resource.items():
55                 if tag_name not in ('videoURL', 'audioURL'):
56                     continue
57
58                 ext = determine_ext(medium_url)
59                 if ext == 'm3u8':
60                     formats.extend(self._extract_m3u8_formats(
61                         medium_url, display_id, 'mp4', 'm3u8_native',
62                         m3u8_id='hls'))
63                 elif ext == 'f4m':
64                     manifest_url = update_url_query(
65                         medium_url, {'hdcore': '3.2.0', 'plugin': 'aasp-3.2.0.77.18'})
66                     formats.extend(self._extract_f4m_formats(
67                         manifest_url, display_id, f4m_id='hds', fatal=False))
68                 elif ext == 'smil':
69                     formats.extend(self._extract_smil_formats(
70                         medium_url, 'stream', fatal=False))
71                 else:
72                     a_format = {
73                         'url': medium_url
74                     }
75                     if ext == 'unknown_video':
76                         urlh = self._request_webpage(
77                             medium_url, display_id, note='Determining extension')
78                         ext = urlhandle_detect_ext(urlh)
79                         a_format['ext'] = ext
80                     formats.append(a_format)
81
82         self._sort_formats(formats)
83
84         subtitles = {}
85         caption_url = metadata_media_resource.get('captionURL')
86         if caption_url:
87             subtitles['de'] = [{
88                 'url': caption_url,
89                 'ext': 'ttml',
90             }]
91
92         title = metadata_tracker_data['trackerClipTitle']
93
94         return {
95             'id': metadata_tracker_data.get('trackerClipId', display_id),
96             'display_id': display_id,
97             'title': title,
98             'alt_title': metadata_tracker_data.get('trackerClipSubcategory'),
99             'formats': formats,
100             'subtitles': subtitles,
101             'upload_date': unified_strdate(metadata_tracker_data.get('trackerClipAirTime')),
102         }
103
104
105 class WDRIE(WDRBaseIE):
106     _CURRENT_MAUS_URL = r'https?://(?:www\.)wdrmaus.de/(?:[^/]+/){1,2}[^/?#]+\.php5'
107     _PAGE_REGEX = r'/(?:mediathek/)?[^/]+/(?P<type>[^/]+)/(?P<display_id>.+)\.html'
108     _VALID_URL = r'(?P<page_url>https?://(?:www\d\.)?wdr\d?\.de)' + _PAGE_REGEX + '|' + _CURRENT_MAUS_URL
109
110     _TESTS = [
111         {
112             'url': 'http://www1.wdr.de/mediathek/video/sendungen/doku-am-freitag/video-geheimnis-aachener-dom-100.html',
113             # HDS download, MD5 is unstable
114             'info_dict': {
115                 'id': 'mdb-1058683',
116                 'ext': 'flv',
117                 'display_id': 'doku-am-freitag/video-geheimnis-aachener-dom-100',
118                 'title': 'Geheimnis Aachener Dom',
119                 'alt_title': 'Doku am Freitag',
120                 'upload_date': '20160304',
121                 'description': 'md5:87be8ff14d8dfd7a7ee46f0299b52318',
122                 'is_live': False,
123                 'subtitles': {'de': [{
124                     'url': 'http://ondemand-ww.wdr.de/medp/fsk0/105/1058683/1058683_12220974.xml',
125                     'ext': 'ttml',
126                 }]},
127             },
128         },
129         {
130             'url': 'http://www1.wdr.de/mediathek/audio/wdr3/wdr3-gespraech-am-samstag/audio-schriftstellerin-juli-zeh-100.html',
131             'md5': 'f4c1f96d01cf285240f53ea4309663d8',
132             'info_dict': {
133                 'id': 'mdb-1072000',
134                 'ext': 'mp3',
135                 'display_id': 'wdr3-gespraech-am-samstag/audio-schriftstellerin-juli-zeh-100',
136                 'title': 'Schriftstellerin Juli Zeh',
137                 'alt_title': 'WDR 3 Gespräch am Samstag',
138                 'upload_date': '20160312',
139                 'description': 'md5:e127d320bc2b1f149be697ce044a3dd7',
140                 'is_live': False,
141                 'subtitles': {}
142             },
143         },
144         {
145             'url': 'http://www1.wdr.de/mediathek/video/live/index.html',
146             'info_dict': {
147                 'id': 'mdb-103364',
148                 'ext': 'mp4',
149                 'display_id': 'index',
150                 'title': r're:^WDR Fernsehen im Livestream [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
151                 'alt_title': 'WDR Fernsehen Live',
152                 'upload_date': None,
153                 'description': 'md5:ae2ff888510623bf8d4b115f95a9b7c9',
154                 'is_live': True,
155                 'subtitles': {}
156             },
157             'params': {
158                 'skip_download': True,  # m3u8 download
159             },
160         },
161         {
162             'url': 'http://www1.wdr.de/mediathek/video/sendungen/aktuelle-stunde/aktuelle-stunde-120.html',
163             'playlist_mincount': 8,
164             'info_dict': {
165                 'id': 'aktuelle-stunde/aktuelle-stunde-120',
166             },
167         },
168         {
169             'url': 'http://www.wdrmaus.de/aktuelle-sendung/index.php5',
170             'info_dict': {
171                 'id': 'mdb-1323501',
172                 'ext': 'mp4',
173                 'upload_date': 're:^[0-9]{8}$',
174                 'title': 're:^Die Sendung mit der Maus vom [0-9.]{10}$',
175                 'description': 'Die Seite mit der Maus -',
176             },
177             'skip': 'The id changes from week to week because of the new episode'
178         },
179         {
180             'url': 'http://www.wdrmaus.de/filme/sachgeschichten/achterbahn.php5',
181             'md5': '803138901f6368ee497b4d195bb164f2',
182             'info_dict': {
183                 'id': 'mdb-186083',
184                 'ext': 'mp4',
185                 'upload_date': '20130919',
186                 'title': 'Sachgeschichte - Achterbahn ',
187                 'description': 'Die Seite mit der Maus -',
188             },
189         },
190         {
191             'url': 'http://www1.wdr.de/radio/player/radioplayer116~_layout-popupVersion.html',
192             # Live stream, MD5 unstable
193             'info_dict': {
194                 'id': 'mdb-869971',
195                 'ext': 'flv',
196                 'title': 'COSMO Livestream',
197                 'description': 'md5:2309992a6716c347891c045be50992e4',
198                 'upload_date': '20160101',
199             },
200         }
201     ]
202
203     def _real_extract(self, url):
204         mobj = re.match(self._VALID_URL, url)
205         url_type = mobj.group('type')
206         page_url = mobj.group('page_url')
207         display_id = mobj.group('display_id')
208         webpage = self._download_webpage(url, display_id)
209
210         jsonp_url = self._extract_jsonp_url(webpage, display_id)
211         info_dict = self._extract_wdr_video(jsonp_url, display_id)
212
213         if not info_dict:
214             entries = [
215                 self.url_result(page_url + href[0], 'WDR')
216                 for href in re.findall(
217                     r'<a href="(%s)"[^>]+data-extension=' % self._PAGE_REGEX,
218                     webpage)
219             ]
220
221             if entries:  # Playlist page
222                 return self.playlist_result(entries, playlist_id=display_id)
223
224             raise ExtractorError('No downloadable streams found', expected=True)
225
226         is_live = url_type == 'live'
227
228         if is_live:
229             info_dict.update({
230                 'title': self._live_title(info_dict['title']),
231                 'upload_date': None,
232             })
233         elif 'upload_date' not in info_dict:
234             info_dict['upload_date'] = unified_strdate(self._html_search_meta('DC.Date', webpage, 'upload date'))
235
236         info_dict.update({
237             'description': self._html_search_meta('Description', webpage),
238             'is_live': is_live,
239         })
240
241         return info_dict
242
243
244 class WDRElefantIE(WDRBaseIE):
245     _VALID_URL = r'https?://(?:www\.)wdrmaus.de/elefantenseite/#(?P<display_id>.+)'
246     IE_NAME = 'wdr:elefant'
247
248     _TESTS = [
249         {
250             'url': 'http://www.wdrmaus.de/elefantenseite/#folge_ostern_2015',
251             'info_dict': {
252                 'title': 'Folge Oster-Spezial 2015',
253                 'id': 'mdb-1088195',
254                 'ext': 'mp4',
255                 'age_limit': None,
256                 'upload_date': '20150406'
257             },
258             'params': {
259                 'skip_download' : True,
260             },
261         },
262     ]
263
264     def _real_extract(self, url):
265         mobj = re.match(self._VALID_URL, url)
266         display_id = mobj.group('display_id')
267
268         # Table of Contents seems to always be at this address, so fetch it directly.
269         # The website fetches configurationJS.php5, which links to tableOfContentsJS.php5.
270         table_of_contents = self._download_json(
271             'https://www.wdrmaus.de/elefantenseite/data/tableOfContentsJS.php5', display_id)
272         if display_id not in table_of_contents:
273             raise ExtractorError(
274                 'No entry in site\'s table of contents for this URL. '
275                 'Is the fragment part of the URL (after the #) correct?',
276                 expected=True)
277         xml_metadata_path = table_of_contents[display_id]['xmlPath']
278         xml_metadata = self._download_xml(
279             'https://www.wdrmaus.de/elefantenseite/' + xml_metadata_path, display_id)
280         zmdb_url_element = xml_metadata.find('./movie/zmdb_url')
281         if zmdb_url_element is None:
282             raise ExtractorError(
283                 'The URL looks valid, but no video was found. Note that download only works '
284                 'on pages showing a single video, not on video selection pages.',
285                 expected=True)
286         info_dict = self._extract_wdr_video(zmdb_url_element.text, display_id)
287         return info_dict
288
289
290 class WDRMobileIE(InfoExtractor):
291     _VALID_URL = r'''(?x)
292         https?://mobile-ondemand\.wdr\.de/
293         .*?/fsk(?P<age_limit>[0-9]+)
294         /[0-9]+/[0-9]+/
295         (?P<id>[0-9]+)_(?P<title>[0-9]+)'''
296     IE_NAME = 'wdr:mobile'
297     _TEST = {
298         'url': 'http://mobile-ondemand.wdr.de/CMS2010/mdb/ondemand/weltweit/fsk0/42/421735/421735_4283021.mp4',
299         'info_dict': {
300             'title': '4283021',
301             'id': '421735',
302             'ext': 'mp4',
303             'age_limit': 0,
304         },
305         'skip': 'Problems with loading data.'
306     }
307
308     def _real_extract(self, url):
309         mobj = re.match(self._VALID_URL, url)
310         return {
311             'id': mobj.group('id'),
312             'title': mobj.group('title'),
313             'age_limit': int(mobj.group('age_limit')),
314             'url': url,
315             'http_headers': {
316                 'User-Agent': 'mobile',
317             },
318         }