remove unnecessary assignment parenthesis
[youtube-dl] / youtube_dl / extractor / animeondemand.py
1 from __future__ import unicode_literals
2
3 import re
4
5 from .common import InfoExtractor
6 from ..compat import compat_str
7 from ..utils import (
8     determine_ext,
9     extract_attributes,
10     ExtractorError,
11     urlencode_postdata,
12     urljoin,
13 )
14
15
16 class AnimeOnDemandIE(InfoExtractor):
17     _VALID_URL = r'https?://(?:www\.)?anime-on-demand\.de/anime/(?P<id>\d+)'
18     _LOGIN_URL = 'https://www.anime-on-demand.de/users/sign_in'
19     _APPLY_HTML5_URL = 'https://www.anime-on-demand.de/html5apply'
20     _NETRC_MACHINE = 'animeondemand'
21     # German-speaking countries of Europe
22     _GEO_COUNTRIES = ['AT', 'CH', 'DE', 'LI', 'LU']
23     _TESTS = [{
24         # jap, OmU
25         'url': 'https://www.anime-on-demand.de/anime/161',
26         'info_dict': {
27             'id': '161',
28             'title': 'Grimgar, Ashes and Illusions (OmU)',
29             'description': 'md5:6681ce3c07c7189d255ac6ab23812d31',
30         },
31         'playlist_mincount': 4,
32     }, {
33         # Film wording is used instead of Episode, ger/jap, Dub/OmU
34         'url': 'https://www.anime-on-demand.de/anime/39',
35         'only_matching': True,
36     }, {
37         # Episodes without titles, jap, OmU
38         'url': 'https://www.anime-on-demand.de/anime/162',
39         'only_matching': True,
40     }, {
41         # ger/jap, Dub/OmU, account required
42         'url': 'https://www.anime-on-demand.de/anime/169',
43         'only_matching': True,
44     }, {
45         # Full length film, non-series, ger/jap, Dub/OmU, account required
46         'url': 'https://www.anime-on-demand.de/anime/185',
47         'only_matching': True,
48     }, {
49         # Flash videos
50         'url': 'https://www.anime-on-demand.de/anime/12',
51         'only_matching': True,
52     }]
53
54     def _login(self):
55         username, password = self._get_login_info()
56         if username is None:
57             return
58
59         login_page = self._download_webpage(
60             self._LOGIN_URL, None, 'Downloading login page')
61
62         if '>Our licensing terms allow the distribution of animes only to German-speaking countries of Europe' in login_page:
63             self.raise_geo_restricted(
64                 '%s is only available in German-speaking countries of Europe' % self.IE_NAME)
65
66         login_form = self._form_hidden_inputs('new_user', login_page)
67
68         login_form.update({
69             'user[login]': username,
70             'user[password]': password,
71         })
72
73         post_url = self._search_regex(
74             r'<form[^>]+action=(["\'])(?P<url>.+?)\1', login_page,
75             'post url', default=self._LOGIN_URL, group='url')
76
77         if not post_url.startswith('http'):
78             post_url = urljoin(self._LOGIN_URL, post_url)
79
80         response = self._download_webpage(
81             post_url, None, 'Logging in',
82             data=urlencode_postdata(login_form), headers={
83                 'Referer': self._LOGIN_URL,
84             })
85
86         if all(p not in response for p in ('>Logout<', 'href="/users/sign_out"')):
87             error = self._search_regex(
88                 r'<p[^>]+\bclass=(["\'])(?:(?!\1).)*\balert\b(?:(?!\1).)*\1[^>]*>(?P<error>.+?)</p>',
89                 response, 'error', default=None, group='error')
90             if error:
91                 raise ExtractorError('Unable to login: %s' % error, expected=True)
92             raise ExtractorError('Unable to log in')
93
94     def _real_initialize(self):
95         self._login()
96
97     def _real_extract(self, url):
98         anime_id = self._match_id(url)
99
100         webpage = self._download_webpage(url, anime_id)
101
102         if 'data-playlist=' not in webpage:
103             self._download_webpage(
104                 self._APPLY_HTML5_URL, anime_id,
105                 'Activating HTML5 beta', 'Unable to apply HTML5 beta')
106             webpage = self._download_webpage(url, anime_id)
107
108         csrf_token = self._html_search_meta(
109             'csrf-token', webpage, 'csrf token', fatal=True)
110
111         anime_title = self._html_search_regex(
112             r'(?s)<h1[^>]+itemprop="name"[^>]*>(.+?)</h1>',
113             webpage, 'anime name')
114         anime_description = self._html_search_regex(
115             r'(?s)<div[^>]+itemprop="description"[^>]*>(.+?)</div>',
116             webpage, 'anime description', default=None)
117
118         entries = []
119
120         def extract_info(html, video_id, num=None):
121             title, description = [None] * 2
122             formats = []
123
124             for input_ in re.findall(
125                     r'<input[^>]+class=["\'].*?streamstarter[^>]+>', html):
126                 attributes = extract_attributes(input_)
127                 title = attributes.get('data-dialog-header')
128                 playlist_urls = []
129                 for playlist_key in ('data-playlist', 'data-otherplaylist', 'data-stream'):
130                     playlist_url = attributes.get(playlist_key)
131                     if isinstance(playlist_url, compat_str) and re.match(
132                             r'/?[\da-zA-Z]+', playlist_url):
133                         playlist_urls.append(attributes[playlist_key])
134                 if not playlist_urls:
135                     continue
136
137                 lang = attributes.get('data-lang')
138                 lang_note = attributes.get('value')
139
140                 for playlist_url in playlist_urls:
141                     kind = self._search_regex(
142                         r'videomaterialurl/\d+/([^/]+)/',
143                         playlist_url, 'media kind', default=None)
144                     format_id_list = []
145                     if lang:
146                         format_id_list.append(lang)
147                     if kind:
148                         format_id_list.append(kind)
149                     if not format_id_list and num is not None:
150                         format_id_list.append(compat_str(num))
151                     format_id = '-'.join(format_id_list)
152                     format_note = ', '.join(filter(None, (kind, lang_note)))
153                     item_id_list = []
154                     if format_id:
155                         item_id_list.append(format_id)
156                     item_id_list.append('videomaterial')
157                     playlist = self._download_json(
158                         urljoin(url, playlist_url), video_id,
159                         'Downloading %s JSON' % ' '.join(item_id_list),
160                         headers={
161                             'X-Requested-With': 'XMLHttpRequest',
162                             'X-CSRF-Token': csrf_token,
163                             'Referer': url,
164                             'Accept': 'application/json, text/javascript, */*; q=0.01',
165                         }, fatal=False)
166                     if not playlist:
167                         continue
168                     stream_url = playlist.get('streamurl')
169                     if stream_url:
170                         rtmp = re.search(
171                             r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+/))(?P<playpath>mp[34]:.+)',
172                             stream_url)
173                         if rtmp:
174                             formats.append({
175                                 'url': rtmp.group('url'),
176                                 'app': rtmp.group('app'),
177                                 'play_path': rtmp.group('playpath'),
178                                 'page_url': url,
179                                 'player_url': 'https://www.anime-on-demand.de/assets/jwplayer.flash-55abfb34080700304d49125ce9ffb4a6.swf',
180                                 'rtmp_real_time': True,
181                                 'format_id': 'rtmp',
182                                 'ext': 'flv',
183                             })
184                             continue
185                     start_video = playlist.get('startvideo', 0)
186                     playlist = playlist.get('playlist')
187                     if not playlist or not isinstance(playlist, list):
188                         continue
189                     playlist = playlist[start_video]
190                     title = playlist.get('title')
191                     if not title:
192                         continue
193                     description = playlist.get('description')
194                     for source in playlist.get('sources', []):
195                         file_ = source.get('file')
196                         if not file_:
197                             continue
198                         ext = determine_ext(file_)
199                         format_id_list = [lang, kind]
200                         if ext == 'm3u8':
201                             format_id_list.append('hls')
202                         elif source.get('type') == 'video/dash' or ext == 'mpd':
203                             format_id_list.append('dash')
204                         format_id = '-'.join(filter(None, format_id_list))
205                         if ext == 'm3u8':
206                             file_formats = self._extract_m3u8_formats(
207                                 file_, video_id, 'mp4',
208                                 entry_protocol='m3u8_native', m3u8_id=format_id, fatal=False)
209                         elif source.get('type') == 'video/dash' or ext == 'mpd':
210                             continue
211                             file_formats = self._extract_mpd_formats(
212                                 file_, video_id, mpd_id=format_id, fatal=False)
213                         else:
214                             continue
215                         for f in file_formats:
216                             f.update({
217                                 'language': lang,
218                                 'format_note': format_note,
219                             })
220                         formats.extend(file_formats)
221
222             return {
223                 'title': title,
224                 'description': description,
225                 'formats': formats,
226             }
227
228         def extract_entries(html, video_id, common_info, num=None):
229             info = extract_info(html, video_id, num)
230
231             if info['formats']:
232                 self._sort_formats(info['formats'])
233                 f = common_info.copy()
234                 f.update(info)
235                 entries.append(f)
236
237             # Extract teaser/trailer only when full episode is not available
238             if not info['formats']:
239                 m = re.search(
240                     r'data-dialog-header=(["\'])(?P<title>.+?)\1[^>]+href=(["\'])(?P<href>.+?)\3[^>]*>(?P<kind>Teaser|Trailer)<',
241                     html)
242                 if m:
243                     f = common_info.copy()
244                     f.update({
245                         'id': '%s-%s' % (f['id'], m.group('kind').lower()),
246                         'title': m.group('title'),
247                         'url': urljoin(url, m.group('href')),
248                     })
249                     entries.append(f)
250
251         def extract_episodes(html):
252             for num, episode_html in enumerate(re.findall(
253                     r'(?s)<h3[^>]+class="episodebox-title".+?>Episodeninhalt<', html), 1):
254                 episodebox_title = self._search_regex(
255                     (r'class="episodebox-title"[^>]+title=(["\'])(?P<title>.+?)\1',
256                      r'class="episodebox-title"[^>]+>(?P<title>.+?)<'),
257                     episode_html, 'episodebox title', default=None, group='title')
258                 if not episodebox_title:
259                     continue
260
261                 episode_number = int(self._search_regex(
262                     r'(?:Episode|Film)\s*(\d+)',
263                     episodebox_title, 'episode number', default=num))
264                 episode_title = self._search_regex(
265                     r'(?:Episode|Film)\s*\d+\s*-\s*(.+)',
266                     episodebox_title, 'episode title', default=None)
267
268                 video_id = 'episode-%d' % episode_number
269
270                 common_info = {
271                     'id': video_id,
272                     'series': anime_title,
273                     'episode': episode_title,
274                     'episode_number': episode_number,
275                 }
276
277                 extract_entries(episode_html, video_id, common_info)
278
279         def extract_film(html, video_id):
280             common_info = {
281                 'id': anime_id,
282                 'title': anime_title,
283                 'description': anime_description,
284             }
285             extract_entries(html, video_id, common_info)
286
287         extract_episodes(webpage)
288
289         if not entries:
290             extract_film(webpage, anime_id)
291
292         return self.playlist_result(entries, anime_id, anime_title, anime_description)