2 from __future__ 
import unicode_literals
 
   5 import xml
.etree
.ElementTree 
as etree
 
   8 from .common 
import InfoExtractor
 
  11     compat_etree_register_namespace
, 
  25 class ITVIE(InfoExtractor
): 
  26     _VALID_URL 
= r
'https?://(?:www\.)?itv\.com/hub/[^/]+/(?P<id>[0-9a-zA-Z]+)' 
  27     _GEO_COUNTRIES 
= ['GB'] 
  29         'url': 'http://www.itv.com/hub/mr-bean-animated-series/2a2936a0053', 
  33             'title': 'Home Movie', 
  37             'skip_download': True, 
  41     def _real_extract(self
, url
): 
  42         video_id 
= self
._match
_id
(url
) 
  43         webpage 
= self
._download
_webpage
(url
, video_id
) 
  44         params 
= extract_attributes(self
._search
_regex
( 
  45             r
'(?s)(<[^>]+id="video"[^>]*>)', webpage
, 'params')) 
  48             'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/', 
  49             'tem': 'http://tempuri.org/', 
  50             'itv': 'http://schemas.datacontract.org/2004/07/Itv.BB.Mercury.Common.Types', 
  51             'com': 'http://schemas.itv.com/2009/05/Common', 
  53         for ns
, full_ns 
in ns_map
.items(): 
  54             compat_etree_register_namespace(ns
, full_ns
) 
  57             return xpath_with_ns(name
, ns_map
) 
  59         def _add_sub_element(element
, name
): 
  60             return etree
.SubElement(element
, _add_ns(name
)) 
  63             params
.get('data-video-autoplay-id') or 
  65                 params
.get('data-video-episode-id') or 
  66                 video_id
.replace('a', '/'))) 
  68         req_env 
= etree
.Element(_add_ns('soapenv:Envelope')) 
  69         _add_sub_element(req_env
, 'soapenv:Header') 
  70         body 
= _add_sub_element(req_env
, 'soapenv:Body') 
  71         get_playlist 
= _add_sub_element(body
, ('tem:GetPlaylist')) 
  72         request 
= _add_sub_element(get_playlist
, 'tem:request') 
  73         _add_sub_element(request
, 'itv:ProductionId').text 
= production_id
 
  74         _add_sub_element(request
, 'itv:RequestGuid').text 
= compat_str(uuid
.uuid4()).upper() 
  75         vodcrid 
= _add_sub_element(request
, 'itv:Vodcrid') 
  76         _add_sub_element(vodcrid
, 'com:Id') 
  77         _add_sub_element(request
, 'itv:Partition') 
  78         user_info 
= _add_sub_element(get_playlist
, 'tem:userInfo') 
  79         _add_sub_element(user_info
, 'itv:Broadcaster').text 
= 'Itv' 
  80         _add_sub_element(user_info
, 'itv:DM') 
  81         _add_sub_element(user_info
, 'itv:RevenueScienceValue') 
  82         _add_sub_element(user_info
, 'itv:SessionId') 
  83         _add_sub_element(user_info
, 'itv:SsoToken') 
  84         _add_sub_element(user_info
, 'itv:UserToken') 
  85         site_info 
= _add_sub_element(get_playlist
, 'tem:siteInfo') 
  86         _add_sub_element(site_info
, 'itv:AdvertisingRestriction').text 
= 'None' 
  87         _add_sub_element(site_info
, 'itv:AdvertisingSite').text 
= 'ITV' 
  88         _add_sub_element(site_info
, 'itv:AdvertisingType').text 
= 'Any' 
  89         _add_sub_element(site_info
, 'itv:Area').text 
= 'ITVPLAYER.VIDEO' 
  90         _add_sub_element(site_info
, 'itv:Category') 
  91         _add_sub_element(site_info
, 'itv:Platform').text 
= 'DotCom' 
  92         _add_sub_element(site_info
, 'itv:Site').text 
= 'ItvCom' 
  93         device_info 
= _add_sub_element(get_playlist
, 'tem:deviceInfo') 
  94         _add_sub_element(device_info
, 'itv:ScreenSize').text 
= 'Big' 
  95         player_info 
= _add_sub_element(get_playlist
, 'tem:playerInfo') 
  96         _add_sub_element(player_info
, 'itv:Version').text 
= '2' 
  98         headers 
= self
.geo_verification_headers() 
 100             'Content-Type': 'text/xml; charset=utf-8', 
 101             'SOAPAction': 'http://tempuri.org/PlaylistService/GetPlaylist', 
 103         resp_env 
= self
._download
_xml
( 
 104             params
['data-playlist-url'], video_id
, 
 105             headers
=headers
, data
=etree
.tostring(req_env
)) 
 106         playlist 
= xpath_element(resp_env
, './/Playlist') 
 108             fault_code 
= xpath_text(resp_env
, './/faultcode') 
 109             fault_string 
= xpath_text(resp_env
, './/faultstring') 
 110             if fault_code 
== 'InvalidGeoRegion': 
 111                 self
.raise_geo_restricted( 
 112                     msg
=fault_string
, countries
=self
._GEO
_COUNTRIES
) 
 113             raise ExtractorError('%s said: %s' % (self
.IE_NAME
, fault_string
)) 
 114         title 
= xpath_text(playlist
, 'EpisodeTitle', fatal
=True) 
 115         video_element 
= xpath_element(playlist
, 'VideoEntries/Video', fatal
=True) 
 116         media_files 
= xpath_element(video_element
, 'MediaFiles', fatal
=True) 
 117         rtmp_url 
= media_files
.attrib
['base'] 
 120         for media_file 
in media_files
.findall('MediaFile'): 
 121             play_path 
= xpath_text(media_file
, 'URL') 
 124             tbr 
= int_or_none(media_file
.get('bitrate'), 1000) 
 126                 'format_id': 'rtmp' + ('-%d' % tbr 
if tbr 
else ''), 
 127                 'play_path': play_path
, 
 128                 # Providing this swfVfy allows to avoid truncated downloads 
 129                 'player_url': 'http://www.itv.com/mercury/Mercury_VideoPlayer.swf', 
 134             app 
= self
._search
_regex
( 
 135                 'rtmpe?://[^/]+/(.+)$', rtmp_url
, 'app', default
=None) 
 138                     'url': rtmp_url
.split('?', 1)[0], 
 145         ios_playlist_url 
= params
.get('data-video-playlist') 
 146         hmac 
= params
.get('data-video-hmac') 
 147         if ios_playlist_url 
and hmac
: 
 148             headers 
= self
.geo_verification_headers() 
 150                 'Accept': 'application/vnd.itv.vod.playlist.v2+json', 
 151                 'Content-Type': 'application/json', 
 152                 'hmac': hmac
.upper(), 
 154             ios_playlist 
= self
._download
_json
( 
 155                 ios_playlist_url
, video_id
, data
=json
.dumps({ 
 162                         'manufacturer': 'Apple', 
 174                     'variantAvailability': { 
 176                             'min': ['hls', 'aes'], 
 177                             'max': ['hls', 'aes'] 
 179                         'platformTag': 'mobile' 
 181                 }).encode(), headers
=headers
, fatal
=False) 
 183                 video_data 
= ios_playlist
.get('Playlist', {}).get('Video', {}) 
 184                 ios_base_url 
= video_data
.get('Base') 
 185                 for media_file 
in video_data
.get('MediaFiles', []): 
 186                     href 
= media_file
.get('Href') 
 190                         href 
= ios_base_url 
+ href
 
 191                     ext 
= determine_ext(href
) 
 193                         formats
.extend(self
._extract
_m
3u8_formats
( 
 194                             href
, video_id
, 'mp4', entry_protocol
='m3u8_native', 
 195                             m3u8_id
='hls', fatal
=False)) 
 200         self
._sort
_formats
(formats
) 
 203         for caption_url 
in video_element
.findall('ClosedCaptioningURIs/URL'): 
 204             if not caption_url
.text
: 
 206             ext 
= determine_ext(caption_url
.text
, 'ttml') 
 207             subtitles
.setdefault('en', []).append({ 
 208                 'url': caption_url
.text
, 
 209                 'ext': 'ttml' if ext 
== 'xml' else ext
, 
 212         info 
= self
._search
_json
_ld
(webpage
, video_id
, default
={}) 
 217             'subtitles': subtitles
, 
 218             'episode_title': title
, 
 219             'episode_number': int_or_none(xpath_text(playlist
, 'EpisodeNumber')), 
 220             'series': xpath_text(playlist
, 'ProgrammeTitle'), 
 221             'duartion': parse_duration(xpath_text(playlist
, 'Duration')),