]>
Commit | Line | Data |
---|---|---|
ab4bdc91 MS |
1 | # coding: utf-8 |
2 | from __future__ import unicode_literals | |
f542a3d2 S |
3 | |
4 | import re | |
5 | ||
ab4bdc91 | 6 | from .common import InfoExtractor |
f542a3d2 | 7 | from ..compat import compat_urllib_request |
ab4bdc91 | 8 | from ..utils import ( |
f542a3d2 S |
9 | clean_html, |
10 | determine_ext, | |
ab4bdc91 MS |
11 | encode_dict, |
12 | sanitized_Request, | |
13 | ExtractorError, | |
14 | urlencode_postdata | |
15 | ) | |
ab4bdc91 | 16 | |
b4c299ba | 17 | |
ab4bdc91 | 18 | class FunimationIE(InfoExtractor): |
9c163950 | 19 | _VALID_URL = r'https?://(?:www\.)?funimation\.com/shows/[^/]+/videos/official/(?P<id>[^/?#&]+)' |
ab4bdc91 MS |
20 | |
21 | _TEST = { | |
22 | 'url': 'http://www.funimation.com/shows/air/videos/official/breeze', | |
23 | 'info_dict': { | |
d357bbd3 S |
24 | 'id': '658', |
25 | 'display_id': 'breeze', | |
ab4bdc91 | 26 | 'ext': 'mp4', |
d357bbd3 S |
27 | 'title': 'Air - 1 - Breeze', |
28 | 'description': 'md5:1769f43cd5fc130ace8fd87232207892', | |
29 | 'thumbnail': 're:https?://.*\.jpg', | |
30 | }, | |
ab4bdc91 MS |
31 | } |
32 | ||
f542a3d2 S |
33 | def _download_webpage(self, url_or_request, video_id, note='Downloading webpage'): |
34 | HEADERS = { | |
35 | 'User-Agent': 'Mozilla/5.0 (Windows NT 5.2; WOW64; rv:42.0) Gecko/20100101 Firefox/42.0', | |
36 | } | |
37 | if isinstance(url_or_request, compat_urllib_request.Request): | |
38 | for header, value in HEADERS.items(): | |
39 | url_or_request.add_header(header, value) | |
40 | else: | |
41 | url_or_request = sanitized_Request(url_or_request, headers=HEADERS) | |
42 | response = super(FunimationIE, self)._download_webpage(url_or_request, video_id, note) | |
43 | return response | |
44 | ||
ab4bdc91 MS |
45 | def _login(self): |
46 | (username, password) = self._get_login_info() | |
47 | if username is None: | |
48 | return | |
ab4bdc91 | 49 | data = urlencode_postdata(encode_dict({ |
ab4bdc91 MS |
50 | 'email_field': username, |
51 | 'password_field': password, | |
52 | })) | |
f542a3d2 | 53 | login_request = sanitized_Request('http://www.funimation.com/login', data, headers={ |
59a4ff48 S |
54 | 'Content-Type': 'application/x-www-form-urlencoded' |
55 | }) | |
f542a3d2 S |
56 | login = self._download_webpage( |
57 | login_request, None, 'Logging in as %s' % username) | |
ab4bdc91 | 58 | if re.search(r'<meta property="og:url" content="http://www.funimation.com/login"/>', login) is not None: |
b4c299ba | 59 | raise ExtractorError('Unable to login, wrong username or password.', expected=True) |
ab4bdc91 MS |
60 | |
61 | def _real_initialize(self): | |
62 | self._login() | |
63 | ||
64 | def _real_extract(self, url): | |
f542a3d2 S |
65 | display_id = self._match_id(url) |
66 | ||
67 | webpage = self._download_webpage(url, display_id) | |
68 | ||
69 | items = self._parse_json( | |
70 | self._search_regex( | |
71 | r'var\s+playersData\s*=\s*(\[.+?\]);\n', | |
72 | webpage, 'players data'), | |
73 | display_id)[0]['playlist'][0]['items'] | |
74 | ||
75 | item = next(item for item in items if item.get('itemAK') == display_id) | |
76 | ||
77 | ERRORS_MAP = { | |
78 | 'ERROR_MATURE_CONTENT_LOGGED_IN': 'matureContentLoggedIn', | |
79 | 'ERROR_MATURE_CONTENT_LOGGED_OUT': 'matureContentLoggedOut', | |
80 | 'ERROR_SUBSCRIPTION_LOGGED_OUT': 'subscriptionLoggedOut', | |
81 | 'ERROR_VIDEO_EXPIRED': 'videoExpired', | |
82 | 'ERROR_TERRITORY_UNAVAILABLE': 'territoryUnavailable', | |
83 | 'SVODBASIC_SUBSCRIPTION_IN_PLAYER': 'basicSubscription', | |
84 | 'SVODNON_SUBSCRIPTION_IN_PLAYER': 'nonSubscription', | |
85 | 'ERROR_PLAYER_NOT_RESPONDING': 'playerNotResponding', | |
86 | 'ERROR_UNABLE_TO_CONNECT_TO_CDN': 'unableToConnectToCDN', | |
87 | 'ERROR_STREAM_NOT_FOUND': 'streamNotFound', | |
88 | } | |
89 | ||
90 | error_messages = {} | |
91 | video_error_messages = self._search_regex( | |
92 | r'var\s+videoErrorMessages\s*=\s*({.+?});\n', | |
93 | webpage, 'error messages', default=None) | |
94 | if video_error_messages: | |
95 | error_messages_json = self._parse_json(video_error_messages, display_id, fatal=False) | |
96 | if error_messages_json: | |
97 | for _, error in error_messages_json.items(): | |
98 | type_ = error.get('type') | |
99 | description = error.get('description') | |
100 | content = error.get('content') | |
101 | if type_ == 'text' and description and content: | |
102 | error_message = ERRORS_MAP.get(description) | |
103 | if error_message: | |
104 | error_messages[error_message] = content | |
105 | ||
106 | errors = [] | |
107 | formats = [] | |
108 | for video in item['videoSet']: | |
109 | auth_token = video.get('authToken') | |
110 | if not auth_token: | |
111 | continue | |
112 | funimation_id = video.get('FUNImationID') or video.get('videoId') | |
113 | if not auth_token.startswith('?'): | |
114 | auth_token = '?%s' % auth_token | |
115 | for quality in ('sd', 'hd', 'hd1080'): | |
116 | format_url = video.get('%sUrl' % quality) | |
117 | if not format_url: | |
118 | continue | |
119 | if not format_url.startswith(('http', '//')): | |
120 | errors.append(format_url) | |
121 | continue | |
122 | if determine_ext(format_url) == 'm3u8': | |
123 | m3u8_formats = self._extract_m3u8_formats( | |
124 | format_url + auth_token, display_id, 'mp4', entry_protocol='m3u8_native', | |
125 | m3u8_id=funimation_id or 'hls', fatal=False) | |
126 | if m3u8_formats: | |
127 | formats.extend(m3u8_formats) | |
128 | else: | |
129 | formats.append({ | |
130 | 'url': format_url + auth_token, | |
131 | }) | |
132 | ||
133 | if not formats and errors: | |
134 | raise ExtractorError( | |
135 | '%s returned error: %s' | |
136 | % (self.IE_NAME, clean_html(error_messages.get(errors[0], errors[0]))), | |
137 | expected=True) | |
138 | ||
139 | title = item['title'] | |
140 | artist = item.get('artist') | |
141 | if artist: | |
142 | title = '%s - %s' % (artist, title) | |
143 | description = self._og_search_description(webpage) or item.get('description') | |
144 | thumbnail = self._og_search_thumbnail(webpage) or item.get('posterUrl') | |
145 | video_id = item.get('itemId') or display_id | |
ab4bdc91 MS |
146 | |
147 | return { | |
148 | 'id': video_id, | |
f542a3d2 S |
149 | 'display_id': display_id, |
150 | 'title': title, | |
151 | 'description': description, | |
152 | 'thumbnail': thumbnail, | |
ab4bdc91 | 153 | 'formats': formats, |
ab4bdc91 | 154 | } |