]>
Commit | Line | Data |
---|---|---|
1 | # coding: utf-8 | |
2 | from __future__ import unicode_literals | |
3 | ||
4 | from .common import InfoExtractor | |
5 | from ..compat import ( | |
6 | compat_HTTPError, | |
7 | compat_urllib_request, | |
8 | compat_urllib_parse, | |
9 | compat_urllib_parse_unquote, | |
10 | ) | |
11 | from ..utils import ( | |
12 | determine_ext, | |
13 | ExtractorError, | |
14 | int_or_none, | |
15 | parse_iso8601, | |
16 | HEADRequest, | |
17 | ) | |
18 | ||
19 | ||
20 | class ViewsterIE(InfoExtractor): | |
21 | _VALID_URL = r'https?://(?:www\.)?viewster\.com/(?:serie|movie)/(?P<id>\d+-\d+-\d+)' | |
22 | _TESTS = [{ | |
23 | # movie, Type=Movie | |
24 | 'url': 'http://www.viewster.com/movie/1140-11855-000/the-listening-project/', | |
25 | 'md5': 'e642d1b27fcf3a4ffa79f194f5adde36', | |
26 | 'info_dict': { | |
27 | 'id': '1140-11855-000', | |
28 | 'ext': 'mp4', | |
29 | 'title': 'The listening Project', | |
30 | 'description': 'md5:bac720244afd1a8ea279864e67baa071', | |
31 | 'timestamp': 1214870400, | |
32 | 'upload_date': '20080701', | |
33 | 'duration': 4680, | |
34 | }, | |
35 | }, { | |
36 | # series episode, Type=Episode | |
37 | 'url': 'http://www.viewster.com/serie/1284-19427-001/the-world-and-a-wall/', | |
38 | 'md5': '9243079a8531809efe1b089db102c069', | |
39 | 'info_dict': { | |
40 | 'id': '1284-19427-001', | |
41 | 'ext': 'mp4', | |
42 | 'title': 'The World and a Wall', | |
43 | 'description': 'md5:24814cf74d3453fdf5bfef9716d073e3', | |
44 | 'timestamp': 1428192000, | |
45 | 'upload_date': '20150405', | |
46 | 'duration': 1500, | |
47 | }, | |
48 | }, { | |
49 | # serie, Type=Serie | |
50 | 'url': 'http://www.viewster.com/serie/1303-19426-000/', | |
51 | 'info_dict': { | |
52 | 'id': '1303-19426-000', | |
53 | 'title': 'Is It Wrong to Try to Pick up Girls in a Dungeon?', | |
54 | 'description': 'md5:eeda9bef25b0d524b3a29a97804c2f11', | |
55 | }, | |
56 | 'playlist_count': 13, | |
57 | }, { | |
58 | # unfinished serie, no Type | |
59 | 'url': 'http://www.viewster.com/serie/1284-19427-000/baby-steps-season-2/', | |
60 | 'info_dict': { | |
61 | 'id': '1284-19427-000', | |
62 | 'title': 'Baby Steps—Season 2', | |
63 | 'description': 'md5:e7097a8fc97151e25f085c9eb7a1cdb1', | |
64 | }, | |
65 | 'playlist_mincount': 16, | |
66 | }, { | |
67 | # geo restricted series | |
68 | 'url': 'https://www.viewster.com/serie/1280-18794-002/', | |
69 | 'only_matching': True, | |
70 | }, { | |
71 | # geo restricted video | |
72 | 'url': 'https://www.viewster.com/serie/1280-18794-002/what-is-extraterritoriality-lawo/', | |
73 | 'only_matching': True, | |
74 | }] | |
75 | ||
76 | _ACCEPT_HEADER = 'application/json, text/javascript, */*; q=0.01' | |
77 | ||
78 | def _download_json(self, url, video_id, note='Downloading JSON metadata', fatal=True): | |
79 | request = compat_urllib_request.Request(url) | |
80 | request.add_header('Accept', self._ACCEPT_HEADER) | |
81 | request.add_header('Auth-token', self._AUTH_TOKEN) | |
82 | return super(ViewsterIE, self)._download_json(request, video_id, note, fatal=fatal) | |
83 | ||
84 | def _real_extract(self, url): | |
85 | video_id = self._match_id(url) | |
86 | # Get 'api_token' cookie | |
87 | self._request_webpage(HEADRequest('http://www.viewster.com/'), video_id) | |
88 | cookies = self._get_cookies('http://www.viewster.com/') | |
89 | self._AUTH_TOKEN = compat_urllib_parse_unquote(cookies['api_token'].value) | |
90 | ||
91 | info = self._download_json( | |
92 | 'https://public-api.viewster.com/search/%s' % video_id, | |
93 | video_id, 'Downloading entry JSON') | |
94 | ||
95 | entry_id = info.get('Id') or info['id'] | |
96 | ||
97 | # unfinished serie has no Type | |
98 | if info.get('Type') in ('Serie', None): | |
99 | try: | |
100 | episodes = self._download_json( | |
101 | 'https://public-api.viewster.com/series/%s/episodes' % entry_id, | |
102 | video_id, 'Downloading series JSON') | |
103 | except ExtractorError as e: | |
104 | if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404: | |
105 | self.raise_geo_restricted() | |
106 | else: | |
107 | raise | |
108 | entries = [ | |
109 | self.url_result( | |
110 | 'http://www.viewster.com/movie/%s' % episode['OriginId'], 'Viewster') | |
111 | for episode in episodes] | |
112 | title = (info.get('Title') or info['Synopsis']['Title']).strip() | |
113 | description = info.get('Synopsis', {}).get('Detailed') | |
114 | return self.playlist_result(entries, video_id, title, description) | |
115 | ||
116 | formats = [] | |
117 | for media_type in ('application/f4m+xml', 'application/x-mpegURL', 'video/mp4'): | |
118 | media = self._download_json( | |
119 | 'https://public-api.viewster.com/movies/%s/video?mediaType=%s' | |
120 | % (entry_id, compat_urllib_parse.quote(media_type)), | |
121 | video_id, 'Downloading %s JSON' % media_type, fatal=False) | |
122 | if not media: | |
123 | continue | |
124 | video_url = media.get('Uri') | |
125 | if not video_url: | |
126 | continue | |
127 | ext = determine_ext(video_url) | |
128 | if ext == 'f4m': | |
129 | video_url += '&' if '?' in video_url else '?' | |
130 | video_url += 'hdcore=3.2.0&plugin=flowplayer-3.2.0.1' | |
131 | formats.extend(self._extract_f4m_formats( | |
132 | video_url, video_id, f4m_id='hds')) | |
133 | elif ext == 'm3u8': | |
134 | m3u8_formats = self._extract_m3u8_formats( | |
135 | video_url, video_id, 'mp4', m3u8_id='hls', | |
136 | fatal=False) # m3u8 sometimes fail | |
137 | if m3u8_formats: | |
138 | formats.extend(m3u8_formats) | |
139 | else: | |
140 | format_id = media.get('Bitrate') | |
141 | f = { | |
142 | 'url': video_url, | |
143 | 'format_id': 'mp4-%s' % format_id, | |
144 | 'height': int_or_none(media.get('Height')), | |
145 | 'width': int_or_none(media.get('Width')), | |
146 | 'preference': 1, | |
147 | } | |
148 | if format_id and not f['height']: | |
149 | f['height'] = int_or_none(self._search_regex( | |
150 | r'^(\d+)[pP]$', format_id, 'height', default=None)) | |
151 | formats.append(f) | |
152 | ||
153 | if not formats and not info.get('LanguageSets') and not info.get('VODSettings'): | |
154 | self.raise_geo_restricted() | |
155 | ||
156 | self._sort_formats(formats) | |
157 | ||
158 | synopsis = info.get('Synopsis', {}) | |
159 | # Prefer title outside synopsis since it's less messy | |
160 | title = (info.get('Title') or synopsis['Title']).strip() | |
161 | description = synopsis.get('Detailed') or info.get('Synopsis', {}).get('Short') | |
162 | duration = int_or_none(info.get('Duration')) | |
163 | timestamp = parse_iso8601(info.get('ReleaseDate')) | |
164 | ||
165 | return { | |
166 | 'id': video_id, | |
167 | 'title': title, | |
168 | 'description': description, | |
169 | 'timestamp': timestamp, | |
170 | 'duration': duration, | |
171 | 'formats': formats, | |
172 | } |