]>
Commit | Line | Data |
---|---|---|
9787c5f4 | 1 | # coding: utf-8 |
2 | from __future__ import unicode_literals | |
3 | ||
41c2c254 RA |
4 | import json |
5 | import uuid | |
96c186e1 | 6 | |
e33a7253 | 7 | from .adobepass import AdobePassIE |
41c2c254 | 8 | from ..compat import compat_str |
e37b54b1 | 9 | from ..utils import ( |
bf6ec2fe S |
10 | int_or_none, |
11 | parse_age_limit, | |
12 | parse_duration, | |
13 | try_get, | |
14 | unified_timestamp, | |
684ae102 | 15 | update_url_query, |
e37b54b1 | 16 | ) |
9787c5f4 | 17 | |
18 | ||
e33a7253 | 19 | class FOXIE(AdobePassIE): |
96c186e1 | 20 | _VALID_URL = r'https?://(?:www\.)?(?:fox\.com|nationalgeographic\.com/tv)/watch/(?P<id>[\da-fA-F]+)' |
bf6ec2fe S |
21 | _TESTS = [{ |
22 | # clip | |
23 | 'url': 'https://www.fox.com/watch/4b765a60490325103ea69888fb2bd4e8/', | |
5e3a6fec | 24 | 'md5': 'ebd296fcc41dd4b19f8115d8461a3165', |
9787c5f4 | 25 | 'info_dict': { |
bf6ec2fe | 26 | 'id': '4b765a60490325103ea69888fb2bd4e8', |
9787c5f4 | 27 | 'ext': 'mp4', |
bf6ec2fe S |
28 | 'title': 'Aftermath: Bruce Wayne Develops Into The Dark Knight', |
29 | 'description': 'md5:549cd9c70d413adb32ce2a779b53b486', | |
30 | 'duration': 102, | |
31 | 'timestamp': 1504291893, | |
32 | 'upload_date': '20170901', | |
33 | 'creator': 'FOX', | |
34 | 'series': 'Gotham', | |
9787c5f4 | 35 | }, |
bf6ec2fe S |
36 | 'params': { |
37 | 'skip_download': True, | |
38 | }, | |
39 | }, { | |
40 | # episode, geo-restricted | |
41 | 'url': 'https://www.fox.com/watch/087036ca7f33c8eb79b08152b4dd75c1/', | |
42 | 'only_matching': True, | |
43 | }, { | |
44 | # episode, geo-restricted, tv provided required | |
45 | 'url': 'https://www.fox.com/watch/30056b295fb57f7452aeeb4920bc3024/', | |
46 | 'only_matching': True, | |
96c186e1 RA |
47 | }, { |
48 | 'url': 'https://www.nationalgeographic.com/tv/watch/f690e05ebbe23ab79747becd0cc223d1/', | |
49 | 'only_matching': True, | |
bf6ec2fe | 50 | }] |
41c2c254 | 51 | _access_token = None |
96c186e1 | 52 | |
41c2c254 RA |
53 | def _call_api(self, path, video_id, data=None): |
54 | headers = { | |
55 | 'X-Api-Key': '238bb0a0c2aba67922c48709ce0c06fd', | |
56 | } | |
57 | if self._access_token: | |
58 | headers['Authorization'] = 'Bearer ' + self._access_token | |
59 | return self._download_json( | |
60 | 'https://api2.fox.com/v2.0/' + path, | |
61 | video_id, data=data, headers=headers) | |
96c186e1 | 62 | |
41c2c254 RA |
63 | def _real_initialize(self): |
64 | self._access_token = self._call_api( | |
65 | 'login', None, json.dumps({ | |
66 | 'deviceId': compat_str(uuid.uuid4()), | |
67 | }).encode())['accessToken'] | |
9787c5f4 | 68 | |
69 | def _real_extract(self, url): | |
70 | video_id = self._match_id(url) | |
7aa0ee32 | 71 | |
41c2c254 | 72 | video = self._call_api('vodplayer/' + video_id, video_id) |
bf6ec2fe S |
73 | |
74 | title = video['name'] | |
41c2c254 | 75 | release_url = video['url'] |
bf6ec2fe S |
76 | |
77 | data = try_get( | |
78 | video, lambda x: x['trackingData']['properties'], dict) or {} | |
79 | ||
96c186e1 | 80 | rating = video.get('contentRating') |
bf6ec2fe | 81 | if data.get('authRequired'): |
684ae102 RA |
82 | resource = self._get_mvpd_resource( |
83 | 'fbc-fox', title, video.get('guid'), rating) | |
84 | release_url = update_url_query( | |
85 | release_url, { | |
86 | 'auth': self._extract_mvpd_auth( | |
87 | url, video_id, 'fbc-fox', resource) | |
88 | }) | |
96c186e1 RA |
89 | m3u8_url = self._download_json(release_url, video_id)['playURL'] |
90 | formats = self._extract_m3u8_formats( | |
91 | m3u8_url, video_id, 'mp4', | |
92 | entry_protocol='m3u8_native', m3u8_id='hls') | |
93 | self._sort_formats(formats) | |
94 | ||
95 | duration = int_or_none(video.get('durationInSeconds')) or int_or_none( | |
96 | video.get('duration')) or parse_duration(video.get('duration')) | |
97 | timestamp = unified_timestamp(video.get('datePublished')) | |
98 | creator = data.get('brand') or data.get('network') or video.get('network') | |
99 | series = video.get('seriesName') or data.get( | |
100 | 'seriesName') or data.get('show') | |
684ae102 RA |
101 | |
102 | subtitles = {} | |
103 | for doc_rel in video.get('documentReleases', []): | |
104 | rel_url = doc_rel.get('url') | |
105 | if not url or doc_rel.get('format') != 'SCC': | |
106 | continue | |
107 | subtitles['en'] = [{ | |
108 | 'url': rel_url, | |
109 | 'ext': 'scc', | |
110 | }] | |
111 | break | |
bf6ec2fe | 112 | |
96c186e1 | 113 | return { |
bf6ec2fe S |
114 | 'id': video_id, |
115 | 'title': title, | |
96c186e1 RA |
116 | 'formats': formats, |
117 | 'description': video.get('description'), | |
bf6ec2fe S |
118 | 'duration': duration, |
119 | 'timestamp': timestamp, | |
96c186e1 | 120 | 'age_limit': parse_age_limit(rating), |
bf6ec2fe S |
121 | 'creator': creator, |
122 | 'series': series, | |
96c186e1 RA |
123 | 'season_number': int_or_none(video.get('seasonNumber')), |
124 | 'episode': video.get('name'), | |
125 | 'episode_number': int_or_none(video.get('episodeNumber')), | |
126 | 'release_year': int_or_none(video.get('releaseYear')), | |
684ae102 | 127 | 'subtitles': subtitles, |
bf6ec2fe | 128 | } |