]>
Commit | Line | Data |
---|---|---|
b68a2613 | 1 | # coding: utf-8 |
3647136f S |
2 | from __future__ import unicode_literals |
3 | ||
4 | from .common import InfoExtractor | |
b68a2613 | 5 | from ..compat import ( |
cccedc1a | 6 | compat_HTTPError, |
b68a2613 S |
7 | compat_urllib_request, |
8 | compat_urllib_parse, | |
1f048735 | 9 | compat_urllib_parse_unquote, |
b68a2613 S |
10 | ) |
11 | from ..utils import ( | |
12 | determine_ext, | |
cccedc1a | 13 | ExtractorError, |
b68a2613 S |
14 | int_or_none, |
15 | parse_iso8601, | |
30a45388 | 16 | HEADRequest, |
b68a2613 | 17 | ) |
3647136f S |
18 | |
19 | ||
20 | class ViewsterIE(InfoExtractor): | |
92085e70 | 21 | _VALID_URL = r'https?://(?:www\.)?viewster\.com/(?:serie|movie)/(?P<id>\d+-\d+-\d+)' |
7be5a62e | 22 | _TESTS = [{ |
b68a2613 | 23 | # movie, Type=Movie |
7be5a62e | 24 | 'url': 'http://www.viewster.com/movie/1140-11855-000/the-listening-project/', |
cb4e4219 | 25 | 'md5': 'e642d1b27fcf3a4ffa79f194f5adde36', |
7be5a62e S |
26 | 'info_dict': { |
27 | 'id': '1140-11855-000', | |
cb4e4219 | 28 | 'ext': 'mp4', |
b68a2613 S |
29 | 'title': 'The listening Project', |
30 | 'description': 'md5:bac720244afd1a8ea279864e67baa071', | |
31 | 'timestamp': 1214870400, | |
32 | 'upload_date': '20080701', | |
33 | 'duration': 4680, | |
34 | }, | |
7be5a62e | 35 | }, { |
b68a2613 S |
36 | # series episode, Type=Episode |
37 | 'url': 'http://www.viewster.com/serie/1284-19427-001/the-world-and-a-wall/', | |
cb4e4219 | 38 | 'md5': '9243079a8531809efe1b089db102c069', |
7be5a62e | 39 | 'info_dict': { |
b68a2613 | 40 | 'id': '1284-19427-001', |
cb4e4219 | 41 | 'ext': 'mp4', |
b68a2613 S |
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, | |
7ce50a35 S |
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, | |
7be5a62e | 74 | }] |
3647136f S |
75 | |
76 | _ACCEPT_HEADER = 'application/json, text/javascript, */*; q=0.01' | |
77 | ||
b68a2613 S |
78 | def _download_json(self, url, video_id, note='Downloading JSON metadata', fatal=True): |
79 | request = compat_urllib_request.Request(url) | |
3647136f | 80 | request.add_header('Accept', self._ACCEPT_HEADER) |
b68a2613 S |
81 | request.add_header('Auth-token', self._AUTH_TOKEN) |
82 | return super(ViewsterIE, self)._download_json(request, video_id, note, fatal=fatal) | |
3647136f | 83 | |
b68a2613 S |
84 | def _real_extract(self, url): |
85 | video_id = self._match_id(url) | |
799207e8 | 86 | # Get 'api_token' cookie |
92085e70 | 87 | self._request_webpage(HEADRequest('http://www.viewster.com/'), video_id) |
88 | cookies = self._get_cookies('http://www.viewster.com/') | |
1f048735 | 89 | self._AUTH_TOKEN = compat_urllib_parse_unquote(cookies['api_token'].value) |
7be5a62e | 90 | |
b68a2613 S |
91 | info = self._download_json( |
92 | 'https://public-api.viewster.com/search/%s' % video_id, | |
93 | video_id, 'Downloading entry JSON') | |
7be5a62e | 94 | |
b68a2613 | 95 | entry_id = info.get('Id') or info['id'] |
7be5a62e | 96 | |
b68a2613 | 97 | # unfinished serie has no Type |
d0fed4ac | 98 | if info.get('Type') in ('Serie', None): |
cccedc1a S |
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 | |
b68a2613 S |
108 | entries = [ |
109 | self.url_result( | |
110 | 'http://www.viewster.com/movie/%s' % episode['OriginId'], 'Viewster') | |
111 | for episode in episodes] | |
c84683c8 | 112 | title = (info.get('Title') or info['Synopsis']['Title']).strip() |
b68a2613 S |
113 | description = info.get('Synopsis', {}).get('Detailed') |
114 | return self.playlist_result(entries, video_id, title, description) | |
7be5a62e | 115 | |
b68a2613 | 116 | formats = [] |
92085e70 | 117 | for media_type in ('application/f4m+xml', 'application/x-mpegURL', 'video/mp4'): |
b68a2613 S |
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': | |
dedd35c6 | 134 | m3u8_formats = self._extract_m3u8_formats( |
b68a2613 | 135 | video_url, video_id, 'mp4', m3u8_id='hls', |
dedd35c6 S |
136 | fatal=False) # m3u8 sometimes fail |
137 | if m3u8_formats: | |
138 | formats.extend(m3u8_formats) | |
b68a2613 | 139 | else: |
8e97596b S |
140 | format_id = media.get('Bitrate') |
141 | f = { | |
b68a2613 | 142 | 'url': video_url, |
8e97596b | 143 | 'format_id': 'mp4-%s' % format_id, |
92085e70 | 144 | 'height': int_or_none(media.get('Height')), |
145 | 'width': int_or_none(media.get('Width')), | |
8e97596b S |
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) | |
9612f233 S |
152 | |
153 | if not formats and not info.get('LanguageSets') and not info.get('VODSettings'): | |
154 | self.raise_geo_restricted() | |
155 | ||
b68a2613 | 156 | self._sort_formats(formats) |
7be5a62e | 157 | |
b68a2613 S |
158 | synopsis = info.get('Synopsis', {}) |
159 | # Prefer title outside synopsis since it's less messy | |
c84683c8 | 160 | title = (info.get('Title') or synopsis['Title']).strip() |
b68a2613 S |
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 | } |