]>
Raphaël G. Git Repositories - youtubedl/blob - youtube_dl/extractor/brightcove.py
   2 from __future__ 
import unicode_literals
 
   6 import xml
.etree
.ElementTree
 
   8 from .common 
import InfoExtractor
 
  15     compat_urllib_request
, 
  24 class BrightcoveIE(InfoExtractor
): 
  25     _VALID_URL 
= r
'https?://.*brightcove\.com/(services|viewer).*\?(?P<query>.*)' 
  26     _FEDERATED_URL_TEMPLATE 
= 'http://c.brightcove.com/services/viewer/htmlFederated?%s' 
  30             # From http://www.8tv.cat/8aldia/videos/xavier-sala-i-martin-aquesta-tarda-a-8-al-dia/ 
  31             'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1654948606001&flashID=myExperience&%40videoPlayer=2371591881001', 
  32             'file': '2371591881001.mp4', 
  33             'md5': '5423e113865d26e40624dce2e4b45d95', 
  34             'note': 'Test Brightcove downloads and detection in GenericIE', 
  36                 'title': 'Xavier Sala i Martín: “Un banc que no presta és un banc zombi que no serveix per a res”', 
  38                 'description': 'md5:a950cc4285c43e44d763d036710cd9cd', 
  42             # From http://medianetwork.oracle.com/video/player/1785452137001 
  43             'url': 'http://c.brightcove.com/services/viewer/htmlFederated?playerID=1217746023001&flashID=myPlayer&%40videoPlayer=1785452137001', 
  44             'file': '1785452137001.flv', 
  46                 'title': 'JVMLS 2012: Arrays 2.0 - Opportunities and Challenges', 
  47                 'description': 'John Rose speaks at the JVM Language Summit, August 1, 2012.', 
  52             # From http://mashable.com/2013/10/26/thermoelectric-bracelet-lets-you-control-your-body-temperature/ 
  53             'url': 'http://c.brightcove.com/services/viewer/federated_f9?&playerID=1265504713001&publisherID=AQ%7E%7E%2CAAABBzUwv1E%7E%2CxP-xFHVUstiMFlNYfvF4G9yFnNaqCw_9&videoID=2750934548001', 
  55                 'id': '2750934548001', 
  57                 'title': 'This Bracelet Acts as a Personal Thermostat', 
  58                 'description': 'md5:547b78c64f4112766ccf4e151c20b6a0', 
  59                 'uploader': 'Mashable', 
  63             # test that the default referer works 
  64             # from http://national.ballet.ca/interact/video/Lost_in_Motion_II/ 
  65             'url': 'http://link.brightcove.com/services/player/bcpid756015033001?bckey=AQ~~,AAAApYJi_Ck~,GxhXCegT1Dp39ilhXuxMJxasUhVNZiil&bctid=2878862109001', 
  67                 'id': '2878862109001', 
  69                 'title': 'Lost in Motion II', 
  70                 'description': 'md5:363109c02998fee92ec02211bd8000df', 
  71                 'uploader': 'National Ballet of Canada', 
  77     def _build_brighcove_url(cls
, object_str
): 
  79         Build a Brightcove url from a xml string containing 
  80         <object class="BrightcoveExperience">{params}</object> 
  83         # Fix up some stupid HTML, see https://github.com/rg3/youtube-dl/issues/1553 
  84         object_str 
= re
.sub(r
'(<param name="[^"]+" value="[^"]+")>', 
  85                             lambda m
: m
.group(1) + '/>', object_str
) 
  86         # Fix up some stupid XML, see https://github.com/rg3/youtube-dl/issues/1608 
  87         object_str 
= object_str
.replace('<--', '<!--') 
  88         object_str 
= fix_xml_ampersands(object_str
) 
  90         object_doc 
= xml
.etree
.ElementTree
.fromstring(object_str
.encode('utf-8')) 
  92         fv_el 
= find_xpath_attr(object_doc
, './param', 'name', 'flashVars') 
  96                 for k
, v 
in compat_parse_qs(fv_el
.attrib
['value']).items()) 
 100         def find_param(name
): 
 101             if name 
in flashvars
: 
 102                 return flashvars
[name
] 
 103             node 
= find_xpath_attr(object_doc
, './param', 'name', name
) 
 105                 return node
.attrib
['value'] 
 110         playerID 
= find_param('playerID') 
 112             raise ExtractorError('Cannot find player ID') 
 113         params
['playerID'] = playerID
 
 115         playerKey 
= find_param('playerKey') 
 116         # Not all pages define this value 
 117         if playerKey 
is not None: 
 118             params
['playerKey'] = playerKey
 
 119         # The three fields hold the id of the video 
 120         videoPlayer 
= find_param('@videoPlayer') or find_param('videoId') or find_param('videoID') 
 121         if videoPlayer 
is not None: 
 122             params
['@videoPlayer'] = videoPlayer
 
 123         linkBase 
= find_param('linkBaseURL') 
 124         if linkBase 
is not None: 
 125             params
['linkBaseURL'] = linkBase
 
 126         data 
= compat_urllib_parse
.urlencode(params
) 
 127         return cls
._FEDERATED
_URL
_TEMPLATE 
% data
 
 130     def _extract_brightcove_url(cls
, webpage
): 
 131         """Try to extract the brightcove url from the webpage, returns None 
 134         urls 
= cls
._extract
_brightcove
_urls
(webpage
) 
 135         return urls
[0] if urls 
else None 
 138     def _extract_brightcove_urls(cls
, webpage
): 
 139         """Return a list of all Brightcove URLs from the webpage """ 
 141         url_m 
= re
.search(r
'<meta\s+property="og:video"\s+content="(http://c.brightcove.com/[^"]+)"', webpage
) 
 143             url 
= unescapeHTML(url_m
.group(1)) 
 144             # Some sites don't add it, we can't download with this url, for example: 
 145             # http://www.ktvu.com/videos/news/raw-video-caltrain-releases-video-of-man-almost/vCTZdY/ 
 146             if 'playerKey' in url
: 
 149         matches 
= re
.findall( 
 152                 [^>]+?class=[\'"][^>]*?BrightcoveExperience.*?[\'"] |
 
 153                 [^
>]*?
>\s
*<param\s
+name
="movie"\s
+value
="https?://[^/]*brightcove\.com/ 
 156         return [cls._build_brighcove_url(m) for m in matches] 
 158     def _real_extract(self, url): 
 159         url, smuggled_data = unsmuggle_url(url, {}) 
 161         # Change the 'videoId' and others field to '@videoPlayer' 
 162         url = re.sub(r'(?<=[?&])(videoI(d|D)|bctid)', '%40videoPlayer', url) 
 163         # Change bckey (used by bcove.me urls) to playerKey 
 164         url = re.sub(r'(?<=[?&])bckey', 'playerKey', url) 
 165         mobj = re.match(self._VALID_URL, url) 
 166         query_str = mobj.group('query') 
 167         query = compat_urlparse.parse_qs(query_str) 
 169         videoPlayer = query.get('@videoPlayer') 
 171             # We set the original url as the default 'Referer' header 
 172             referer = smuggled_data.get('Referer', url) 
 173             return self._get_video_info( 
 174                 videoPlayer[0], query_str, query, referer=referer) 
 176             player_key = query['playerKey'] 
 177             return self._get_playlist_info(player_key[0]) 
 179     def _get_video_info(self, video_id, query_str, query, referer=None): 
 180         request_url = self._FEDERATED_URL_TEMPLATE % query_str 
 181         req = compat_urllib_request.Request(request_url) 
 182         linkBase = query.get('linkBaseURL') 
 183         if linkBase is not None: 
 184             referer = linkBase[0] 
 185         if referer is not None: 
 186             req.add_header('Referer', referer) 
 187         webpage = self._download_webpage(req, video_id) 
 189         self.report_extraction(video_id) 
 190         info = self._search_regex(r'var experienceJSON = ({.*?});', webpage, 'json') 
 191         info = json.loads(info)['data'] 
 192         video_info = info['programmedContent']['videoPlayer']['mediaDTO'] 
 193         video_info['_youtubedl_adServerURL'] = info.get('adServerURL') 
 195         return self._extract_video_info(video_info) 
 197     def _get_playlist_info(self, player_key): 
 198         info_url = 'http://c.brightcove.com/services/json/experience/runtime/?command=get_programming_for_experience&playerKey=%s' % player_key 
 199         playlist_info = self._download_webpage( 
 200             info_url, player_key, 'Downloading playlist information') 
 202         json_data = json.loads(playlist_info) 
 203         if 'videoList' not in json_data: 
 204             raise ExtractorError('Empty playlist') 
 205         playlist_info = json_data['videoList'] 
 206         videos = [self._extract_video_info(video_info) for video_info in playlist_info['mediaCollectionDTO']['videoDTOs']] 
 208         return self.playlist_result(videos, playlist_id=playlist_info['id'], 
 209                                     playlist_title=playlist_info['mediaCollectionDTO']['displayName']) 
 211     def _extract_video_info(self, video_info): 
 213             'id': compat_str(video_info['id']), 
 214             'title': video_info['displayName'].strip(), 
 215             'description': video_info.get('shortDescription'), 
 216             'thumbnail': video_info.get('videoStillURL') or video_info.get('thumbnailURL'), 
 217             'uploader': video_info.get('publisherName'), 
 220         renditions = video_info.get('renditions') 
 222             renditions = sorted(renditions, key=lambda r: r['size']) 
 224                 'url': rend['defaultURL'], 
 225                 'height': rend.get('frameHeight'), 
 226                 'width': rend.get('frameWidth'), 
 227             } for rend in renditions] 
 228         elif video_info.get('FLVFullLengthURL') is not None: 
 230                 'url': video_info['FLVFullLengthURL'], 
 233         if self._downloader.params.get('include_ads', False): 
 234             adServerURL = video_info.get('_youtubedl_adServerURL') 
 243                         'title': info['title'], 
 244                         'entries': [ad_info, info], 
 249         if 'url' not in info and not info.get('formats'): 
 250             raise ExtractorError('Unable to extract video url for %s' % info['id'])