]> asedeno.scripts.mit.edu Git - youtube-dl.git/blob - youtube_dl/extractor/cspan.py
[cspan] Extract info from jwplayer data (closes #3672, closes #3734, closes #10638...
[youtube-dl.git] / youtube_dl / extractor / cspan.py
1 from __future__ import unicode_literals
2
3 import re
4
5 from .common import InfoExtractor
6 from ..utils import (
7     determine_ext,
8     ExtractorError,
9     extract_attributes,
10     find_xpath_attr,
11     get_element_by_class,
12     int_or_none,
13     js_to_json,
14     merge_dicts,
15     smuggle_url,
16     unescapeHTML,
17 )
18 from .senateisvp import SenateISVPIE
19 from .ustream import UstreamIE
20
21
22 class CSpanIE(InfoExtractor):
23     _VALID_URL = r'https?://(?:www\.)?c-span\.org/video/\?(?P<id>[0-9a-f]+)'
24     IE_DESC = 'C-SPAN'
25     _TESTS = [{
26         'url': 'http://www.c-span.org/video/?313572-1/HolderonV',
27         'md5': '94b29a4f131ff03d23471dd6f60b6a1d',
28         'info_dict': {
29             'id': '315139',
30             'title': 'Attorney General Eric Holder on Voting Rights Act Decision',
31         },
32         'playlist_mincount': 2,
33         'skip': 'Regularly fails on travis, for unknown reasons',
34     }, {
35         'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
36         # md5 is unstable
37         'info_dict': {
38             'id': 'c4486943',
39             'ext': 'mp4',
40             'title': 'CSPAN - International Health Care Models',
41             'description': 'md5:7a985a2d595dba00af3d9c9f0783c967',
42         }
43     }, {
44         'url': 'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall',
45         'info_dict': {
46             'id': '342759',
47             'title': 'General Motors Ignition Switch Recall',
48         },
49         'playlist_mincount': 6,
50     }, {
51         # Video from senate.gov
52         'url': 'http://www.c-span.org/video/?104517-1/immigration-reforms-needed-protect-skilled-american-workers',
53         'info_dict': {
54             'id': 'judiciary031715',
55             'ext': 'mp4',
56             'title': 'Immigration Reforms Needed to Protect Skilled American Workers',
57         },
58         'params': {
59             'skip_download': True,  # m3u8 downloads
60         }
61     }, {
62         # Ustream embedded video
63         'url': 'https://www.c-span.org/video/?114917-1/armed-services',
64         'info_dict': {
65             'id': '58428542',
66             'ext': 'flv',
67             'title': 'USHR07 Armed Services Committee',
68             'description': 'hsas00-2118-20150204-1000et-07\n\n\nUSHR07 Armed Services Committee',
69             'timestamp': 1423060374,
70             'upload_date': '20150204',
71             'uploader': 'HouseCommittee',
72             'uploader_id': '12987475',
73         },
74     }, {
75         # Audio Only
76         'url': 'https://www.c-span.org/video/?437336-1/judiciary-antitrust-competition-policy-consumer-rights',
77         'only_matching': True,
78     }]
79     BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'
80
81     def _real_extract(self, url):
82         video_id = self._match_id(url)
83         video_type = None
84         webpage = self._download_webpage(url, video_id)
85
86         ustream_url = UstreamIE._extract_url(webpage)
87         if ustream_url:
88             return self.url_result(ustream_url, UstreamIE.ie_key())
89
90         if '&vod' not in url:
91             bc = self._search_regex(
92                 r"(<[^>]+id='brightcove-player-embed'[^>]+>)",
93                 webpage, 'brightcove embed', default=None)
94             if bc:
95                 bc_attr = extract_attributes(bc)
96                 bc_url = self.BRIGHTCOVE_URL_TEMPLATE % (
97                     bc_attr.get('data-bcaccountid', '3162030207001'),
98                     bc_attr.get('data-noprebcplayerid', 'SyGGpuJy3g'),
99                     bc_attr.get('data-newbcplayerid', 'default'),
100                     bc_attr['data-bcid'])
101                 return self.url_result(smuggle_url(bc_url, {'source_url': url}))
102
103         def add_referer(formats):
104             for f in formats:
105                 f.setdefault('http_headers', {})['Referer'] = url
106
107         # As of 01.12.2020 this path looks to cover all cases making the rest
108         # of the code unnecessary
109         jwsetup = self._parse_json(
110             self._search_regex(
111                 r'(?s)jwsetup\s*=\s*({.+?})\s*;', webpage, 'jwsetup',
112                 default='{}'),
113             video_id, transform_source=js_to_json, fatal=False)
114         if jwsetup:
115             info = self._parse_jwplayer_data(
116                 jwsetup, video_id, require_title=False, m3u8_id='hls',
117                 base_url=url)
118             add_referer(info['formats'])
119             ld_info = self._search_json_ld(webpage, video_id, default={})
120             return merge_dicts(info, ld_info)
121
122         # Obsolete
123         # We first look for clipid, because clipprog always appears before
124         patterns = [r'id=\'clip(%s)\'\s*value=\'([0-9]+)\'' % t for t in ('id', 'prog')]
125         results = list(filter(None, (re.search(p, webpage) for p in patterns)))
126         if results:
127             matches = results[0]
128             video_type, video_id = matches.groups()
129             video_type = 'clip' if video_type == 'id' else 'program'
130         else:
131             m = re.search(r'data-(?P<type>clip|prog)id=["\'](?P<id>\d+)', webpage)
132             if m:
133                 video_id = m.group('id')
134                 video_type = 'program' if m.group('type') == 'prog' else 'clip'
135             else:
136                 senate_isvp_url = SenateISVPIE._search_iframe_url(webpage)
137                 if senate_isvp_url:
138                     title = self._og_search_title(webpage)
139                     surl = smuggle_url(senate_isvp_url, {'force_title': title})
140                     return self.url_result(surl, 'SenateISVP', video_id, title)
141                 video_id = self._search_regex(
142                     r'jwsetup\.clipprog\s*=\s*(\d+);',
143                     webpage, 'jwsetup program id', default=None)
144                 if video_id:
145                     video_type = 'program'
146         if video_type is None or video_id is None:
147             error_message = get_element_by_class('VLplayer-error-message', webpage)
148             if error_message:
149                 raise ExtractorError(error_message)
150             raise ExtractorError('unable to find video id and type')
151
152         def get_text_attr(d, attr):
153             return d.get(attr, {}).get('#text')
154
155         data = self._download_json(
156             'http://www.c-span.org/assets/player/ajax-player.php?os=android&html5=%s&id=%s' % (video_type, video_id),
157             video_id)['video']
158         if data['@status'] != 'Success':
159             raise ExtractorError('%s said: %s' % (self.IE_NAME, get_text_attr(data, 'error')), expected=True)
160
161         doc = self._download_xml(
162             'http://www.c-span.org/common/services/flashXml.php?%sid=%s' % (video_type, video_id),
163             video_id)
164
165         description = self._html_search_meta('description', webpage)
166
167         title = find_xpath_attr(doc, './/string', 'name', 'title').text
168         thumbnail = find_xpath_attr(doc, './/string', 'name', 'poster').text
169
170         files = data['files']
171         capfile = get_text_attr(data, 'capfile')
172
173         entries = []
174         for partnum, f in enumerate(files):
175             formats = []
176             for quality in f.get('qualities', []):
177                 formats.append({
178                     'format_id': '%s-%sp' % (get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
179                     'url': unescapeHTML(get_text_attr(quality, 'file')),
180                     'height': int_or_none(get_text_attr(quality, 'height')),
181                     'tbr': int_or_none(get_text_attr(quality, 'bitrate')),
182                 })
183             if not formats:
184                 path = unescapeHTML(get_text_attr(f, 'path'))
185                 if not path:
186                     continue
187                 formats = self._extract_m3u8_formats(
188                     path, video_id, 'mp4', entry_protocol='m3u8_native',
189                     m3u8_id='hls') if determine_ext(path) == 'm3u8' else [{'url': path, }]
190             add_referer(formats)
191             self._sort_formats(formats)
192             entries.append({
193                 'id': '%s_%d' % (video_id, partnum + 1),
194                 'title': (
195                     title if len(files) == 1 else
196                     '%s part %d' % (title, partnum + 1)),
197                 'formats': formats,
198                 'description': description,
199                 'thumbnail': thumbnail,
200                 'duration': int_or_none(get_text_attr(f, 'length')),
201                 'subtitles': {
202                     'en': [{
203                         'url': capfile,
204                         'ext': determine_ext(capfile, 'dfxp')
205                     }],
206                 } if capfile else None,
207             })
208
209         if len(entries) == 1:
210             entry = dict(entries[0])
211             entry['id'] = 'c' + video_id if video_type == 'clip' else video_id
212             return entry
213         else:
214             return {
215                 '_type': 'playlist',
216                 'entries': entries,
217                 'title': title,
218                 'id': 'c' + video_id if video_type == 'clip' else video_id,
219             }