]>
Raphaël G. Git Repositories - youtubedl/blob - youtube_dl/extractor/bilibili.py
1e3f25515d3596f7b196a16ab867b7b5e8c891ab
   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', 
  59     _APP_KEY 
= '84956560bc028eb7' 
  60     _BILIBILI_KEY 
= '94aba54af9065f71de72f5508f1cd42e' 
  62     def _report_error(self
, result
): 
  63         if 'message' in result
: 
  64             raise ExtractorError('%s said: %s' % (self
.IE_NAME
, result
['message']), expected
=True) 
  65         elif 'code' in result
: 
  66             raise ExtractorError('%s returns error %d' % (self
.IE_NAME
, result
['code']), expected
=True) 
  68             raise ExtractorError('Can\'t extract Bangumi episode ID') 
  70     def _real_extract(self
, url
): 
  71         url
, smuggled_data 
= unsmuggle_url(url
, {}) 
  73         mobj 
= re
.match(self
._VALID
_URL
, url
) 
  74         video_id 
= mobj
.group('id') 
  75         anime_id 
= mobj
.group('anime_id') 
  76         webpage 
= self
._download
_webpage
(url
, video_id
) 
  78         if 'anime/' not in url
: 
  79             cid 
= compat_parse_qs(self
._search
_regex
( 
  80                 [r
'EmbedPlayer\([^)]+,\s*"([^"]+)"\)', 
  81                  r
'<iframe[^>]+src="https://secure\.bilibili\.com/secure,([^"]+)"'], 
  82                 webpage
, 'player parameters'))['cid'][0] 
  84             if 'no_bangumi_tip' not in smuggled_data
: 
  85                 self
.to_screen('Downloading episode %s. To download all videos in anime %s, re-run youtube-dl with %s' % ( 
  86                     video_id
, anime_id
, compat_urlparse
.urljoin(url
, '//bangumi.bilibili.com/anime/%s' % anime_id
))) 
  88                 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 
  90             headers
.update(self
.geo_verification_headers()) 
  92             js 
= self
._download
_json
( 
  93                 'http://bangumi.bilibili.com/web_api/get_source', video_id
, 
  94                 data
=urlencode_postdata({'episode_id': video_id
}), 
  96             if 'result' not in js
: 
  97                 self
._report
_error
(js
) 
  98             cid 
= js
['result']['cid'] 
 100         payload 
= 'appkey=%s&cid=%s&otype=json&quality=2&type=mp4' % (self
._APP
_KEY
, cid
) 
 101         sign 
= hashlib
.md5((payload 
+ self
._BILIBILI
_KEY
).encode('utf-8')).hexdigest() 
 103         video_info 
= self
._download
_json
( 
 104             'http://interface.bilibili.com/playurl?%s&sign=%s' % (payload
, sign
), 
 105             video_id
, note
='Downloading video info page', 
 106             headers
=self
.geo_verification_headers()) 
 108         if 'durl' not in video_info
: 
 109             self
._report
_error
(video_info
) 
 113         for idx
, durl 
in enumerate(video_info
['durl']): 
 116                 'filesize': int_or_none(durl
['size']), 
 118             for backup_url 
in durl
.get('backup_url', []): 
 121                     # backup URLs have lower priorities 
 122                     'preference': -2 if 'hd.mp4' in backup_url 
else -3, 
 125             for a_format 
in formats
: 
 126                 a_format
.setdefault('http_headers', {}).update({ 
 130             self
._sort
_formats
(formats
) 
 133                 'id': '%s_part%s' % (video_id
, idx
), 
 134                 'duration': float_or_none(durl
.get('length'), 1000), 
 138         title 
= self
._html
_search
_regex
('<h1[^>]+title="([^"]+)">', webpage
, 'title') 
 139         description 
= self
._html
_search
_meta
('description', webpage
) 
 140         timestamp 
= unified_timestamp(self
._html
_search
_regex
( 
 141             r
'<time[^>]+datetime="([^"]+)"', webpage
, 'upload time', default
=None)) 
 142         thumbnail 
= self
._html
_search
_meta
(['og:image', 'thumbnailUrl'], webpage
) 
 144         # TODO 'view_count' requires deobfuscating Javascript 
 148             'description': description
, 
 149             'timestamp': timestamp
, 
 150             'thumbnail': thumbnail
, 
 151             'duration': float_or_none(video_info
.get('timelength'), scale
=1000), 
 154         uploader_mobj 
= re
.search( 
 155             r
'<a[^>]+href="(?:https?:)?//space\.bilibili\.com/(?P<id>\d+)"[^>]+title="(?P<name>[^"]+)"', 
 159                 'uploader': uploader_mobj
.group('name'), 
 160                 'uploader_id': uploader_mobj
.group('id'), 
 163         for entry 
in entries
: 
 166         if len(entries
) == 1: 
 169             for idx
, entry 
in enumerate(entries
): 
 170                 entry
['id'] = '%s_part%d' % (video_id
, (idx 
+ 1)) 
 173                 '_type': 'multi_video', 
 176                 'description': description
, 
 181 class BiliBiliBangumiIE(InfoExtractor
): 
 182     _VALID_URL 
= r
'https?://bangumi\.bilibili\.com/anime/(?P<id>\d+)' 
 184     IE_NAME 
= 'bangumi.bilibili.com' 
 185     IE_DESC 
= 'BiliBili番剧' 
 188         'url': 'http://bangumi.bilibili.com/anime/1869', 
 192             'description': 'md5:6a9622b911565794c11f25f81d6a97d2', 
 194         'playlist_count': 26, 
 196         'url': 'http://bangumi.bilibili.com/anime/1869', 
 200             'description': 'md5:6a9622b911565794c11f25f81d6a97d2', 
 203             'md5': '91da8621454dd58316851c27c68b0c13', 
 208                 'description': '故事发生在日本的江户时代。风是一个小酒馆的打工女。一日,酒馆里来了一群恶霸,虽然他们的举动令风十分不满,但是毕竟风只是一届女流,无法对他们采取什么行动,只能在心里嘟哝。这时,酒家里又进来了个“不良份子...', 
 209                 'timestamp': 1414538739, 
 210                 'upload_date': '20141028', 
 211                 'episode': '疾风怒涛 Tempestuous Temperaments', 
 216             'playlist_items': '1', 
 221     def suitable(cls
, url
): 
 222         return False if BiliBiliIE
.suitable(url
) else super(BiliBiliBangumiIE
, cls
).suitable(url
) 
 224     def _real_extract(self
, url
): 
 225         bangumi_id 
= self
._match
_id
(url
) 
 227         # Sometimes this API returns a JSONP response 
 228         season_info 
= self
._download
_json
( 
 229             'http://bangumi.bilibili.com/jsonp/seasoninfo/%s.ver' % bangumi_id
, 
 230             bangumi_id
, transform_source
=strip_jsonp
)['result'] 
 233             '_type': 'url_transparent', 
 234             'url': smuggle_url(episode
['webplay_url'], {'no_bangumi_tip': 1}), 
 235             'ie_key': BiliBiliIE
.ie_key(), 
 236             'timestamp': parse_iso8601(episode
.get('update_time'), delimiter
=' '), 
 237             'episode': episode
.get('index_title'), 
 238             'episode_number': int_or_none(episode
.get('index')), 
 239         } for episode 
in season_info
['episodes']] 
 241         entries 
= sorted(entries
, key
=lambda entry
: entry
.get('episode_number')) 
 243         return self
.playlist_result( 
 245             season_info
.get('bangumi_title'), season_info
.get('evaluate'))