+ if 'anime/' not in url:
+ cid = self._search_regex(
+ r'\bcid(?:["\']:|=)(\d+)', webpage, 'cid',
+ default=None
+ ) or compat_parse_qs(self._search_regex(
+ [r'EmbedPlayer\([^)]+,\s*"([^"]+)"\)',
+ r'EmbedPlayer\([^)]+,\s*\\"([^"]+)\\"\)',
+ r'<iframe[^>]+src="https://secure\.bilibili\.com/secure,([^"]+)"'],
+ webpage, 'player parameters'))['cid'][0]
+ else:
+ if 'no_bangumi_tip' not in smuggled_data:
+ self.to_screen('Downloading episode %s. To download all videos in anime %s, re-run youtube-dl with %s' % (
+ video_id, anime_id, compat_urlparse.urljoin(url, '//bangumi.bilibili.com/anime/%s' % anime_id)))
+ headers = {
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
+ 'Referer': url
+ }
+ headers.update(self.geo_verification_headers())
+
+ js = self._download_json(
+ 'http://bangumi.bilibili.com/web_api/get_source', video_id,
+ data=urlencode_postdata({'episode_id': video_id}),
+ headers=headers)
+ if 'result' not in js:
+ self._report_error(js)
+ cid = js['result']['cid']
+
+ headers = {
+ 'Referer': url
+ }
+ headers.update(self.geo_verification_headers())
+
+ entries = []
+
+ RENDITIONS = ('qn=80&quality=80&type=', 'quality=2&type=mp4')
+ for num, rendition in enumerate(RENDITIONS, start=1):
+ payload = 'appkey=%s&cid=%s&otype=json&%s' % (self._APP_KEY, cid, rendition)
+ sign = hashlib.md5((payload + self._BILIBILI_KEY).encode('utf-8')).hexdigest()
+
+ video_info = self._download_json(
+ 'http://interface.bilibili.com/v2/playurl?%s&sign=%s' % (payload, sign),
+ video_id, note='Downloading video info page',
+ headers=headers, fatal=num == len(RENDITIONS))
+
+ if not video_info:
+ continue
+
+ if 'durl' not in video_info:
+ if num < len(RENDITIONS):
+ continue
+ self._report_error(video_info)
+
+ for idx, durl in enumerate(video_info['durl']):
+ formats = [{
+ 'url': durl['url'],
+ 'filesize': int_or_none(durl['size']),
+ }]
+ for backup_url in durl.get('backup_url', []):
+ formats.append({
+ 'url': backup_url,
+ # backup URLs have lower priorities
+ 'preference': -2 if 'hd.mp4' in backup_url else -3,
+ })
+
+ for a_format in formats:
+ a_format.setdefault('http_headers', {}).update({
+ 'Referer': url,
+ })
+
+ self._sort_formats(formats)
+
+ entries.append({
+ 'id': '%s_part%s' % (video_id, idx),
+ 'duration': float_or_none(durl.get('length'), 1000),
+ 'formats': formats,
+ })
+ break
+
+ title = self._html_search_regex(
+ ('<h1[^>]+\btitle=(["\'])(?P<title>(?:(?!\1).)+)\1',
+ '(?s)<h1[^>]*>(?P<title>.+?)</h1>'), webpage, 'title',
+ group='title')
+ description = self._html_search_meta('description', webpage)
+ timestamp = unified_timestamp(self._html_search_regex(
+ r'<time[^>]+datetime="([^"]+)"', webpage, 'upload time',
+ default=None) or self._html_search_meta(
+ 'uploadDate', webpage, 'timestamp', default=None))
+ thumbnail = self._html_search_meta(['og:image', 'thumbnailUrl'], webpage)
+
+ # TODO 'view_count' requires deobfuscating Javascript
+ info = {
+ 'id': video_id,
+ 'title': title,
+ 'description': description,
+ 'timestamp': timestamp,
+ 'thumbnail': thumbnail,
+ 'duration': float_or_none(video_info.get('timelength'), scale=1000),
+ }
+
+ uploader_mobj = re.search(
+ r'<a[^>]+href="(?:https?:)?//space\.bilibili\.com/(?P<id>\d+)"[^>]*>(?P<name>[^<]+)',
+ webpage)
+ if uploader_mobj:
+ info.update({
+ 'uploader': uploader_mobj.group('name'),
+ 'uploader_id': uploader_mobj.group('id'),
+ })
+ if not info.get('uploader'):
+ info['uploader'] = self._html_search_meta(
+ 'author', webpage, 'uploader', default=None)
+
+ for entry in entries:
+ entry.update(info)
+
+ if len(entries) == 1:
+ return entries[0]