]>
Raphaël G. Git Repositories - youtubedl/blob - youtube_dl/extractor/bilibili.py
   2 from __future__ 
import unicode_literals
 
   7 from .common 
import InfoExtractor
 
  25 class BiliBiliIE(InfoExtractor
): 
  26     _VALID_URL 
= r
'https?://(?:www\.|bangumi\.|)bilibili\.(?:tv|com)/(?:video/av|anime/(?P<anime_id>\d+)/play#)(?P<id>\d+)' 
  29         'url': 'http://www.bilibili.tv/video/av1074402/', 
  30         'md5': '9fa226fe2b8a9a4d5a69b4c6a183417e', 
  35             'description': 'md5:ce18c2a2d2193f0df2917d270f2e5923', 
  37             'timestamp': 1398012660, 
  38             'upload_date': '20140420', 
  39             'thumbnail': r
're:^https?://.+\.jpg', 
  41             'uploader_id': '156160', 
  44         # Tested in BiliBiliBangumiIE 
  45         'url': 'http://bangumi.bilibili.com/anime/1869/play#40062', 
  46         'only_matching': True, 
  48         'url': 'http://bangumi.bilibili.com/anime/5802/play#100643', 
  49         'md5': '3f721ad1e75030cc06faf73587cfec57', 
  53             'title': 'CHAOS;CHILD', 
  54             'description': '如果你是神明,并且能够让妄想成为现实。那你会进行怎么样的妄想?是淫靡的世界?独裁社会?毁灭性的制裁?还是……2015年,涩谷。从6年前发生的大灾害“涩谷地震”之后复兴了的这个街区里新设立的私立高中...', 
  56         'skip': 'Geo-restricted to China', 
  58         # Title with double quotes 
  59         'url': 'http://www.bilibili.com/video/av8903802/', 
  63             'title': '阿滴英文|英文歌分享#6 "Closer', 
  64             'description': '滴妹今天唱Closer給你聽! 有史以来,被推最多次也是最久的歌曲,其实歌词跟我原本想像差蛮多的,不过还是好听! 微博@阿滴英文', 
  66             'uploader_id': '65880958', 
  67             'timestamp': 1488382620, 
  68             'upload_date': '20170301', 
  71             'skip_download': True,  # Test metadata only 
  75     _APP_KEY 
= '84956560bc028eb7' 
  76     _BILIBILI_KEY 
= '94aba54af9065f71de72f5508f1cd42e' 
  78     def _report_error(self
, result
): 
  79         if 'message' in result
: 
  80             raise ExtractorError('%s said: %s' % (self
.IE_NAME
, result
['message']), expected
=True) 
  81         elif 'code' in result
: 
  82             raise ExtractorError('%s returns error %d' % (self
.IE_NAME
, result
['code']), expected
=True) 
  84             raise ExtractorError('Can\'t extract Bangumi episode ID') 
  86     def _real_extract(self
, url
): 
  87         url
, smuggled_data 
= unsmuggle_url(url
, {}) 
  89         mobj 
= re
.match(self
._VALID
_URL
, url
) 
  90         video_id 
= mobj
.group('id') 
  91         anime_id 
= mobj
.group('anime_id') 
  92         webpage 
= self
._download
_webpage
(url
, video_id
) 
  94         if 'anime/' not in url
: 
  95             cid 
= compat_parse_qs(self
._search
_regex
( 
  96                 [r
'EmbedPlayer\([^)]+,\s*"([^"]+)"\)', 
  97                  r
'<iframe[^>]+src="https://secure\.bilibili\.com/secure,([^"]+)"'], 
  98                 webpage
, 'player parameters'))['cid'][0] 
 100             if 'no_bangumi_tip' not in smuggled_data
: 
 101                 self
.to_screen('Downloading episode %s. To download all videos in anime %s, re-run youtube-dl with %s' % ( 
 102                     video_id
, anime_id
, compat_urlparse
.urljoin(url
, '//bangumi.bilibili.com/anime/%s' % anime_id
))) 
 104                 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 
 107             headers
.update(self
.geo_verification_headers()) 
 109             js 
= self
._download
_json
( 
 110                 'http://bangumi.bilibili.com/web_api/get_source', video_id
, 
 111                 data
=urlencode_postdata({'episode_id': video_id
}), 
 113             if 'result' not in js
: 
 114                 self
._report
_error
(js
) 
 115             cid 
= js
['result']['cid'] 
 117         payload 
= 'appkey=%s&cid=%s&otype=json&quality=2&type=mp4' % (self
._APP
_KEY
, cid
) 
 118         sign 
= hashlib
.md5((payload 
+ self
._BILIBILI
_KEY
).encode('utf-8')).hexdigest() 
 123         headers
.update(self
.geo_verification_headers()) 
 125         video_info 
= self
._download
_json
( 
 126             'http://interface.bilibili.com/playurl?%s&sign=%s' % (payload
, sign
), 
 127             video_id
, note
='Downloading video info page', 
 130         if 'durl' not in video_info
: 
 131             self
._report
_error
(video_info
) 
 135         for idx
, durl 
in enumerate(video_info
['durl']): 
 138                 'filesize': int_or_none(durl
['size']), 
 140             for backup_url 
in durl
.get('backup_url', []): 
 143                     # backup URLs have lower priorities 
 144                     'preference': -2 if 'hd.mp4' in backup_url 
else -3, 
 147             for a_format 
in formats
: 
 148                 a_format
.setdefault('http_headers', {}).update({ 
 152             self
._sort
_formats
(formats
) 
 155                 'id': '%s_part%s' % (video_id
, idx
), 
 156                 'duration': float_or_none(durl
.get('length'), 1000), 
 160         title 
= self
._html
_search
_regex
('<h1[^>]*>([^<]+)</h1>', webpage
, 'title') 
 161         description 
= self
._html
_search
_meta
('description', webpage
) 
 162         timestamp 
= unified_timestamp(self
._html
_search
_regex
( 
 163             r
'<time[^>]+datetime="([^"]+)"', webpage
, 'upload time', default
=None)) 
 164         thumbnail 
= self
._html
_search
_meta
(['og:image', 'thumbnailUrl'], webpage
) 
 166         # TODO 'view_count' requires deobfuscating Javascript 
 170             'description': description
, 
 171             'timestamp': timestamp
, 
 172             'thumbnail': thumbnail
, 
 173             'duration': float_or_none(video_info
.get('timelength'), scale
=1000), 
 176         uploader_mobj 
= re
.search( 
 177             r
'<a[^>]+href="(?:https?:)?//space\.bilibili\.com/(?P<id>\d+)"[^>]+title="(?P<name>[^"]+)"', 
 181                 'uploader': uploader_mobj
.group('name'), 
 182                 'uploader_id': uploader_mobj
.group('id'), 
 185         for entry 
in entries
: 
 188         if len(entries
) == 1: 
 191             for idx
, entry 
in enumerate(entries
): 
 192                 entry
['id'] = '%s_part%d' % (video_id
, (idx 
+ 1)) 
 195                 '_type': 'multi_video', 
 198                 'description': description
, 
 203 class BiliBiliBangumiIE(InfoExtractor
): 
 204     _VALID_URL 
= r
'https?://bangumi\.bilibili\.com/anime/(?P<id>\d+)' 
 206     IE_NAME 
= 'bangumi.bilibili.com' 
 207     IE_DESC 
= 'BiliBili番剧' 
 210         'url': 'http://bangumi.bilibili.com/anime/1869', 
 214             'description': 'md5:6a9622b911565794c11f25f81d6a97d2', 
 216         'playlist_count': 26, 
 218         'url': 'http://bangumi.bilibili.com/anime/1869', 
 222             'description': 'md5:6a9622b911565794c11f25f81d6a97d2', 
 225             'md5': '91da8621454dd58316851c27c68b0c13', 
 230                 'description': '故事发生在日本的江户时代。风是一个小酒馆的打工女。一日,酒馆里来了一群恶霸,虽然他们的举动令风十分不满,但是毕竟风只是一届女流,无法对他们采取什么行动,只能在心里嘟哝。这时,酒家里又进来了个“不良份子...', 
 231                 'timestamp': 1414538739, 
 232                 'upload_date': '20141028', 
 233                 'episode': '疾风怒涛 Tempestuous Temperaments', 
 238             'playlist_items': '1', 
 243     def suitable(cls
, url
): 
 244         return False if BiliBiliIE
.suitable(url
) else super(BiliBiliBangumiIE
, cls
).suitable(url
) 
 246     def _real_extract(self
, url
): 
 247         bangumi_id 
= self
._match
_id
(url
) 
 249         # Sometimes this API returns a JSONP response 
 250         season_info 
= self
._download
_json
( 
 251             'http://bangumi.bilibili.com/jsonp/seasoninfo/%s.ver' % bangumi_id
, 
 252             bangumi_id
, transform_source
=strip_jsonp
)['result'] 
 255             '_type': 'url_transparent', 
 256             'url': smuggle_url(episode
['webplay_url'], {'no_bangumi_tip': 1}), 
 257             'ie_key': BiliBiliIE
.ie_key(), 
 258             'timestamp': parse_iso8601(episode
.get('update_time'), delimiter
=' '), 
 259             'episode': episode
.get('index_title'), 
 260             'episode_number': int_or_none(episode
.get('index')), 
 261         } for episode 
in season_info
['episodes']] 
 263         entries 
= sorted(entries
, key
=lambda entry
: entry
.get('episode_number')) 
 265         return self
.playlist_result( 
 267             season_info
.get('bangumi_title'), season_info
.get('evaluate'))