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