[abc:iview] Add new extractor(closes #6148)
[youtube-dl] / youtube_dl / extractor / abc.py
1 from __future__ import unicode_literals
2
3 import re
4
5 from .common import InfoExtractor
6 from ..utils import (
7     ExtractorError,
8     js_to_json,
9     int_or_none,
10     update_url_query,
11     parse_iso8601,
12 )
13
14
15 class ABCIE(InfoExtractor):
16     IE_NAME = 'abc.net.au'
17     _VALID_URL = r'https?://www\.abc\.net\.au/news/(?:[^/]+/){1,2}(?P<id>\d+)'
18
19     _TESTS = [{
20         'url': 'http://www.abc.net.au/news/2014-11-05/australia-to-staff-ebola-treatment-centre-in-sierra-leone/5868334',
21         'md5': 'cb3dd03b18455a661071ee1e28344d9f',
22         'info_dict': {
23             'id': '5868334',
24             'ext': 'mp4',
25             'title': 'Australia to help staff Ebola treatment centre in Sierra Leone',
26             'description': 'md5:809ad29c67a05f54eb41f2a105693a67',
27         },
28         'skip': 'this video has expired',
29     }, {
30         'url': 'http://www.abc.net.au/news/2015-08-17/warren-entsch-introduces-same-sex-marriage-bill/6702326',
31         'md5': 'db2a5369238b51f9811ad815b69dc086',
32         'info_dict': {
33             'id': 'NvqvPeNZsHU',
34             'ext': 'mp4',
35             'upload_date': '20150816',
36             'uploader': 'ABC News (Australia)',
37             'description': 'Government backbencher Warren Entsch introduces a cross-party sponsored bill to legalise same-sex marriage, saying the bill is designed to promote "an inclusive Australia, not a divided one.". Read more here: http://ab.co/1Mwc6ef',
38             'uploader_id': 'NewsOnABC',
39             'title': 'Marriage Equality: Warren Entsch introduces same sex marriage bill',
40         },
41         'add_ie': ['Youtube'],
42         'skip': 'Not accessible from Travis CI server',
43     }, {
44         'url': 'http://www.abc.net.au/news/2015-10-23/nab-lifts-interest-rates-following-westpac-and-cba/6880080',
45         'md5': 'b96eee7c9edf4fc5a358a0252881cc1f',
46         'info_dict': {
47             'id': '6880080',
48             'ext': 'mp3',
49             'title': 'NAB lifts interest rates, following Westpac and CBA',
50             'description': 'md5:f13d8edc81e462fce4a0437c7dc04728',
51         },
52     }, {
53         'url': 'http://www.abc.net.au/news/2015-10-19/6866214',
54         'only_matching': True,
55     }]
56
57     def _real_extract(self, url):
58         video_id = self._match_id(url)
59         webpage = self._download_webpage(url, video_id)
60
61         mobj = re.search(
62             r'inline(?P<type>Video|Audio|YouTube)Data\.push\((?P<json_data>[^)]+)\);',
63             webpage)
64         if mobj is None:
65             expired = self._html_search_regex(r'(?s)class="expired-(?:video|audio)".+?<span>(.+?)</span>', webpage, 'expired', None)
66             if expired:
67                 raise ExtractorError('%s said: %s' % (self.IE_NAME, expired), expected=True)
68             raise ExtractorError('Unable to extract video urls')
69
70         urls_info = self._parse_json(
71             mobj.group('json_data'), video_id, transform_source=js_to_json)
72
73         if not isinstance(urls_info, list):
74             urls_info = [urls_info]
75
76         if mobj.group('type') == 'YouTube':
77             return self.playlist_result([
78                 self.url_result(url_info['url']) for url_info in urls_info])
79
80         formats = [{
81             'url': url_info['url'],
82             'vcodec': url_info.get('codec') if mobj.group('type') == 'Video' else 'none',
83             'width': int_or_none(url_info.get('width')),
84             'height': int_or_none(url_info.get('height')),
85             'tbr': int_or_none(url_info.get('bitrate')),
86             'filesize': int_or_none(url_info.get('filesize')),
87         } for url_info in urls_info]
88
89         self._sort_formats(formats)
90
91         return {
92             'id': video_id,
93             'title': self._og_search_title(webpage),
94             'formats': formats,
95             'description': self._og_search_description(webpage),
96             'thumbnail': self._og_search_thumbnail(webpage),
97         }
98
99
100 class ABCIViewIE(InfoExtractor):
101     IE_NAME = 'abc.net.au:iview'
102     _VALID_URL = r'https?://iview\.abc\.net\.au/programs/[^/]+/(?P<id>[^/?#]+)'
103
104     _TESTS = [{
105         'url': 'http://iview.abc.net.au/programs/gardening-australia/FA1505V024S00',
106         'md5': '979d10b2939101f0d27a06b79edad536',
107         'info_dict': {
108             'id': 'FA1505V024S00',
109             'ext': 'mp4',
110             'title': 'Series 27 Ep 24',
111             'description': 'md5:b28baeae7504d1148e1d2f0e3ed3c15d',
112             'upload_date': '20160820',
113             'uploader_id': 'abc1',
114             'timestamp': 1471719600,
115         },
116     }]
117
118     def _real_extract(self, url):
119         video_id = self._match_id(url)
120         webpage = self._download_webpage(url, video_id)
121         video_params = self._parse_json(self._search_regex(
122             r'videoParams\s*=\s*({.+?});', webpage, 'video params'), video_id)
123         title = video_params['title']
124         stream = next(s for s in video_params['playlist'] if s.get('type') == 'program')
125
126         formats = []
127         f4m_url = stream.get('hds-unmetered') or stream['hds-metered']
128         formats.extend(self._extract_f4m_formats(
129             update_url_query(f4m_url, {'hdcore': '3.7.0'}),
130             video_id, f4m_id='hds', fatal=False))
131         formats.extend(self._extract_m3u8_formats(f4m_url.replace(
132             'akamaihd.net/z/', 'akamaihd.net/i/').replace('/manifest.f4m', '/master.m3u8'),
133             video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
134         self._sort_formats(formats)
135
136         subtitles = {}
137         src_vtt = stream.get('captions', {}).get('src-vtt')
138         if src_vtt:
139             subtitles['en'] = [{
140                 'url': src_vtt,
141                 'ext': 'vtt',
142             }]
143
144         return {
145             'id': video_id,
146             'title': title,
147             'description': self._html_search_meta(['og:description', 'twitter:description'], webpage),
148             'thumbnail': self._html_search_meta(['og:image', 'twitter:image:src'], webpage),
149             'duration': int_or_none(video_params.get('eventDuration')),
150             'timestamp': parse_iso8601(video_params.get('pubDate'), ' '),
151             'series': video_params.get('seriesTitle'),
152             'series_id': video_params.get('seriesHouseNumber') or video_id[:7],
153             'episode_number': int_or_none(self._html_search_meta('episodeNumber', webpage)),
154             'episode': self._html_search_meta('episode_title', webpage),
155             'uploader_id': video_params.get('channel'),
156             'formats': formats,
157             'subtitles': subtitles,
158         }