2 from __future__ 
import unicode_literals
 
   7 from .adobepass 
import AdobePassIE
 
  11     compat_urllib_parse_unquote
, 
  23 class FOXIE(AdobePassIE
): 
  24     _VALID_URL 
= r
'https?://(?:www\.)?fox\.com/watch/(?P<id>[\da-fA-F]+)' 
  27         'url': 'https://www.fox.com/watch/4b765a60490325103ea69888fb2bd4e8/', 
  28         'md5': 'ebd296fcc41dd4b19f8115d8461a3165', 
  30             'id': '4b765a60490325103ea69888fb2bd4e8', 
  32             'title': 'Aftermath: Bruce Wayne Develops Into The Dark Knight', 
  33             'description': 'md5:549cd9c70d413adb32ce2a779b53b486', 
  35             'timestamp': 1504291893, 
  36             'upload_date': '20170901', 
  42             'skip_download': True, 
  45         # episode, geo-restricted 
  46         'url': 'https://www.fox.com/watch/087036ca7f33c8eb79b08152b4dd75c1/', 
  47         'only_matching': True, 
  49         # episode, geo-restricted, tv provided required 
  50         'url': 'https://www.fox.com/watch/30056b295fb57f7452aeeb4920bc3024/', 
  51         'only_matching': True, 
  54     _HOME_PAGE_URL 
= 'https://www.fox.com/' 
  55     _API_KEY 
= 'abdcbed02c124d393b39e818a4312055' 
  58     def _call_api(self
, path
, video_id
, data
=None): 
  60             'X-Api-Key': self
._API
_KEY
, 
  62         if self
._access
_token
: 
  63             headers
['Authorization'] = 'Bearer ' + self
._access
_token
 
  65             return self
._download
_json
( 
  66                 'https://api2.fox.com/v2.0/' + path
, 
  67                 video_id
, data
=data
, headers
=headers
) 
  68         except ExtractorError 
as e
: 
  69             if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code 
== 403: 
  70                 entitlement_issues 
= self
._parse
_json
( 
  71                     e
.cause
.read().decode(), video_id
)['entitlementIssues'] 
  72                 for e 
in entitlement_issues
: 
  73                     if e
.get('errorCode') == 1005: 
  75                             'This video is only available via cable service provider ' 
  76                             'subscription. You may want to use --cookies.', expected
=True) 
  77                 messages 
= ', '.join([e
['message'] for e 
in entitlement_issues
]) 
  78                 raise ExtractorError(messages
, expected
=True) 
  81     def _real_initialize(self
): 
  82         if not self
._access
_token
: 
  83             mvpd_auth 
= self
._get
_cookies
(self
._HOME
_PAGE
_URL
).get('mvpd-auth') 
  85                 self
._access
_token 
= (self
._parse
_json
(compat_urllib_parse_unquote( 
  86                     mvpd_auth
.value
), None, fatal
=False) or {}).get('accessToken') 
  87             if not self
._access
_token
: 
  88                 self
._access
_token 
= self
._call
_api
( 
  89                     'login', None, json
.dumps({ 
  90                         'deviceId': compat_str(uuid
.uuid4()), 
  91                     }).encode())['accessToken'] 
  93     def _real_extract(self
, url
): 
  94         video_id 
= self
._match
_id
(url
) 
  96         video 
= self
._call
_api
('vodplayer/' + video_id
, video_id
) 
  99         release_url 
= video
['url'] 
 101             m3u8_url 
= self
._download
_json
(release_url
, video_id
)['playURL'] 
 102         except ExtractorError 
as e
: 
 103             if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code 
== 403: 
 104                 error 
= self
._parse
_json
(e
.cause
.read().decode(), video_id
) 
 105                 if error
.get('exception') == 'GeoLocationBlocked': 
 106                     self
.raise_geo_restricted(countries
=['US']) 
 107                 raise ExtractorError(error
['description'], expected
=True) 
 109         formats 
= self
._extract
_m
3u8_formats
( 
 110             m3u8_url
, video_id
, 'mp4', 
 111             entry_protocol
='m3u8_native', m3u8_id
='hls') 
 112         self
._sort
_formats
(formats
) 
 115             video
, lambda x
: x
['trackingData']['properties'], dict) or {} 
 117         duration 
= int_or_none(video
.get('durationInSeconds')) or int_or_none( 
 118             video
.get('duration')) or parse_duration(video
.get('duration')) 
 119         timestamp 
= unified_timestamp(video
.get('datePublished')) 
 120         creator 
= data
.get('brand') or data
.get('network') or video
.get('network') 
 121         series 
= video
.get('seriesName') or data
.get( 
 122             'seriesName') or data
.get('show') 
 125         for doc_rel 
in video
.get('documentReleases', []): 
 126             rel_url 
= doc_rel
.get('url') 
 127             if not url 
or doc_rel
.get('format') != 'SCC': 
 139             'description': video
.get('description'), 
 140             'duration': duration
, 
 141             'timestamp': timestamp
, 
 142             'age_limit': parse_age_limit(video
.get('contentRating')), 
 145             'season_number': int_or_none(video
.get('seasonNumber')), 
 146             'episode': video
.get('name'), 
 147             'episode_number': int_or_none(video
.get('episodeNumber')), 
 148             'release_year': int_or_none(video
.get('releaseYear')), 
 149             'subtitles': subtitles
,