1 from .common
import InfoExtractor
11 from ..compat
import compat_HTTPError
14 class AnimeLabBaseIE(InfoExtractor
):
15 _LOGIN_URL
= 'https://www.animelab.com/login'
16 _NETRC_MACHINE
= 'animelab'
19 def _is_logged_in(self
, login_page
=None):
20 if not self
._LOGGED
_IN
:
22 login_page
= self
._download
_webpage
(self
._LOGIN
_URL
, None, 'Downloading login page')
23 AnimeLabBaseIE
._LOGGED
_IN
= 'Sign In' not in login_page
24 return self
._LOGGED
_IN
26 def _perform_login(self
, username
, password
):
27 if self
._is
_logged
_in
():
36 response
= self
._download
_webpage
(
37 self
._LOGIN
_URL
, None, 'Logging in', 'Wrong login info',
38 data
=urlencode_postdata(login_form
),
39 headers
={'Content-Type': 'application/x-www-form-urlencoded'}
)
40 except ExtractorError
as e
:
41 if isinstance(e
.cause
, compat_HTTPError
) and e
.cause
.code
== 400:
42 raise ExtractorError('Unable to log in (wrong credentials?)', expected
=True)
45 if not self
._is
_logged
_in
(response
):
46 raise ExtractorError('Unable to login (cannot verify if logged in)')
48 def _real_initialize(self
):
49 if not self
._is
_logged
_in
():
50 self
.raise_login_required('Login is required to access any AnimeLab content')
53 class AnimeLabIE(AnimeLabBaseIE
):
54 _VALID_URL
= r
'https?://(?:www\.)?animelab\.com/player/(?P<id>[^/]+)'
56 # the following tests require authentication, but a free account will suffice
57 # just set 'usenetrc' to true in test/local_parameters.json if you use a .netrc file
58 # or you can set 'username' and 'password' there
59 # the tests also select a specific format so that the same video is downloaded
60 # regardless of whether the user is premium or not (needs testing on a premium account)
62 'url': 'https://www.animelab.com/player/fullmetal-alchemist-brotherhood-episode-42',
63 'md5': '05bde4b91a5d1ff46ef5b94df05b0f7f',
67 'display_id': 'fullmetal-alchemist-brotherhood-episode-42',
68 'title': 'Fullmetal Alchemist: Brotherhood - Episode 42 - Signs of a Counteroffensive',
69 'description': 'md5:103eb61dd0a56d3dfc5dbf748e5e83f4',
70 'series': 'Fullmetal Alchemist: Brotherhood',
71 'episode': 'Signs of a Counteroffensive',
79 'format': '[format_id=21711_yeshardsubbed_ja-JP][height=480]',
81 'skip': 'All AnimeLab content requires authentication',
84 def _real_extract(self
, url
):
85 display_id
= self
._match
_id
(url
)
87 # unfortunately we can get different URLs for the same formats
88 # e.g. if we are using a "free" account so no dubs available
89 # (so _remove_duplicate_formats is not effective)
90 # so we use a dictionary as a workaround
92 for language_option_url
in ('https://www.animelab.com/player/%s/subtitles',
93 'https://www.animelab.com/player/%s/dubbed'):
94 actual_url
= language_option_url
% display_id
95 webpage
= self
._download
_webpage
(actual_url
, display_id
, 'Downloading URL ' + actual_url
)
97 video_collection
= self
._parse
_json
(self
._search
_regex
(r
'new\s+?AnimeLabApp\.VideoCollection\s*?\((.*?)\);', webpage
, 'AnimeLab VideoCollection'), display_id
)
98 position
= int_or_none(self
._search
_regex
(r
'playlistPosition\s*?=\s*?(\d+)', webpage
, 'Playlist Position'))
100 raw_data
= video_collection
[position
]['videoEntry']
102 video_id
= str_or_none(raw_data
['id'])
104 # create a title from many sources (while grabbing other info)
105 # TODO use more fallback sources to get some of these
106 series
= raw_data
.get('showTitle')
107 video_type
= raw_data
.get('videoEntryType', {}).get('name')
108 episode_number
= raw_data
.get('episodeNumber')
109 episode_name
= raw_data
.get('name')
111 title_parts
= (series
, video_type
, episode_number
, episode_name
)
112 if None not in title_parts
:
113 title
= '%s - %s %s - %s' % title_parts
117 description
= raw_data
.get('synopsis') or self
._og
_search
_description
(webpage
, default
=None)
119 duration
= int_or_none(raw_data
.get('duration'))
121 thumbnail_data
= raw_data
.get('images', [])
123 for thumbnail
in thumbnail_data
:
124 for instance
in thumbnail
['imageInstances']:
125 image_data
= instance
.get('imageInfo', {})
127 'id': str_or_none(image_data
.get('id')),
128 'url': image_data
.get('fullPath'),
129 'width': image_data
.get('width'),
130 'height': image_data
.get('height'),
133 season_data
= raw_data
.get('season', {}) or {}
134 season
= str_or_none(season_data
.get('name'))
135 season_number
= int_or_none(season_data
.get('seasonNumber'))
136 season_id
= str_or_none(season_data
.get('id'))
138 for video_data
in raw_data
['videoList']:
139 current_video_list
= {}
140 current_video_list
['language'] = video_data
.get('language', {}).get('languageCode')
142 is_hardsubbed
= video_data
.get('hardSubbed')
144 for video_instance
in video_data
['videoInstances']:
145 httpurl
= video_instance
.get('httpUrl')
146 url
= httpurl
if httpurl
else video_instance
.get('rtmpUrl')
148 # this video format is unavailable to the user (not premium etc.)
151 current_format
= current_video_list
.copy()
155 format_id_parts
.append(str_or_none(video_instance
.get('id')))
157 if is_hardsubbed
is not None:
159 format_id_parts
.append('yeshardsubbed')
161 format_id_parts
.append('nothardsubbed')
163 format_id_parts
.append(current_format
['language'])
165 format_id
= '_'.join([x
for x
in format_id_parts
if x
is not None])
167 ext
= determine_ext(url
)
169 for format_
in self
._extract
_m
3u8_formats
(
170 url
, video_id
, m3u8_id
=format_id
, fatal
=False):
171 formats
[format_
['format_id']] = format_
174 for format_
in self
._extract
_mpd
_formats
(
175 url
, video_id
, mpd_id
=format_id
, fatal
=False):
176 formats
[format_
['format_id']] = format_
179 current_format
['url'] = url
180 quality_data
= video_instance
.get('videoQuality')
182 quality
= quality_data
.get('name') or quality_data
.get('description')
188 height
= int_or_none(self
._search
_regex
(r
'(\d+)p?$', quality
, 'Video format height', default
=None))
191 self
.report_warning('Could not get height of video')
193 current_format
['height'] = height
194 current_format
['format_id'] = format_id
196 formats
[current_format
['format_id']] = current_format
198 formats
= list(formats
.values())
199 self
._sort
_formats
(formats
)
203 'display_id': display_id
,
205 'description': description
,
207 'episode': episode_name
,
208 'episode_number': int_or_none(episode_number
),
209 'thumbnails': thumbnails
,
210 'duration': duration
,
213 'season_number': season_number
,
214 'season_id': season_id
,
218 class AnimeLabShowsIE(AnimeLabBaseIE
):
219 _VALID_URL
= r
'https?://(?:www\.)?animelab\.com/shows/(?P<id>[^/]+)'
222 'url': 'https://www.animelab.com/shows/attack-on-titan',
225 'title': 'Attack on Titan',
226 'description': 'md5:989d95a2677e9309368d5cf39ba91469',
228 'playlist_count': 59,
229 'skip': 'All AnimeLab content requires authentication',
232 def _real_extract(self
, url
):
233 _BASE_URL
= 'http://www.animelab.com'
234 _SHOWS_API_URL
= '/api/videoentries/show/videos/'
235 display_id
= self
._match
_id
(url
)
237 webpage
= self
._download
_webpage
(url
, display_id
, 'Downloading requested URL')
239 show_data_str
= self
._search
_regex
(r
'({"id":.*}),\svideoEntry', webpage
, 'AnimeLab show data')
240 show_data
= self
._parse
_json
(show_data_str
, display_id
)
242 show_id
= str_or_none(show_data
.get('id'))
243 title
= show_data
.get('name')
244 description
= show_data
.get('shortSynopsis') or show_data
.get('longSynopsis')
247 for season
in show_data
['seasons']:
248 season_id
= season
['id']
249 get_data
= urlencode_postdata({
250 'seasonId': season_id
,
253 # despite using urlencode_postdata, we are sending a GET request
254 target_url
= _BASE_URL
+ _SHOWS_API_URL
+ show_id
+ "?" + get_data
.decode('utf-8')
255 response
= self
._download
_webpage
(
257 None, 'Season id %s' % season_id
)
259 season_data
= self
._parse
_json
(response
, display_id
)
261 for video_data
in season_data
['list']:
262 entries
.append(self
.url_result(
263 _BASE_URL
+ '/player/' + video_data
['slug'], 'AnimeLab',
264 str_or_none(video_data
.get('id')), video_data
.get('name')
271 'description': description
,
275 # TODO implement myqueue