bilibili.py 23.8 KB
Newer Older
M
Mort Yao 已提交
1 2
#!/usr/bin/env python

M
Mort Yao 已提交
3
from ..common import *
M
Mort Yao 已提交
4
from ..extractor import VideoExtractor
5

6 7
import hashlib

M
MaxwellGoblin 已提交
8
class Bilibili(VideoExtractor):
M
Mort Yao 已提交
9
    name = "Bilibili"
10

M
Mort Yao 已提交
11
    # Bilibili media encoding options, in descending quality order.
M
MaxwellGoblin 已提交
12
    stream_types = [
M
Mort Yao 已提交
13
        {'id': 'flv_p60', 'quality': 116, 'audio_quality': 30280,
14
         'container': 'FLV', 'video_resolution': '1080p', 'desc': '高清 1080P60'},
M
Mort Yao 已提交
15
        # 'id': 'hdflv2', 'quality': 112?
M
Mort Yao 已提交
16
        {'id': 'flv', 'quality': 80, 'audio_quality': 30280,
17
         'container': 'FLV', 'video_resolution': '1080p', 'desc': '高清 1080P'},
M
Mort Yao 已提交
18
        {'id': 'flv720_p60', 'quality': 74, 'audio_quality': 30280,
19
         'container': 'FLV', 'video_resolution': '720p', 'desc': '高清 720P60'},
M
Mort Yao 已提交
20
        {'id': 'flv720', 'quality': 64, 'audio_quality': 30280,
21
         'container': 'FLV', 'video_resolution': '720p', 'desc': '高清 720P'},
22 23
        {'id': 'hdmp4', 'quality': 48, 'audio_quality': 30280,
         'container': 'MP4', 'video_resolution': '720p', 'desc': '高清 720P (MP4)'},
M
Mort Yao 已提交
24
        {'id': 'flv480', 'quality': 32, 'audio_quality': 30280,
25
         'container': 'FLV', 'video_resolution': '480p', 'desc': '清晰 480P'},
M
Mort Yao 已提交
26
        {'id': 'flv360', 'quality': 16, 'audio_quality': 30216,
27
         'container': 'FLV', 'video_resolution': '360p', 'desc': '流畅 360P'},
28
        # 'quality': 15?
M
MaxwellGoblin 已提交
29 30
    ]

M
Mort Yao 已提交
31 32 33 34 35 36 37 38 39 40 41
    @staticmethod
    def height_to_quality(height):
        if height <= 360:
            return 16
        elif height <= 480:
            return 32
        elif height <= 720:
            return 64
        else:
            return 80

M
MaxwellGoblin 已提交
42
    @staticmethod
M
Mort Yao 已提交
43 44 45 46 47 48 49 50 51
    def bilibili_headers(referer=None, cookie=None):
        # a reasonable UA
        ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36'
        headers = {'User-Agent': ua}
        if referer is not None:
            headers.update({'Referer': referer})
        if cookie is not None:
            headers.update({'Cookie': cookie})
        return headers
M
MaxwellGoblin 已提交
52

M
Mort Yao 已提交
53 54
    @staticmethod
    def bilibili_api(avid, cid, qn=0):
55
        return 'https://api.bilibili.com/x/player/playurl?avid=%s&cid=%s&qn=%s&type=&otype=json&fnver=0&fnval=16' % (avid, cid, qn)
M
Mort Yao 已提交
56

M
Mort Yao 已提交
57
    @staticmethod
58 59
    def bilibili_bangumi_api(avid, cid, ep_id, qn=0):
        return 'https://api.bilibili.com/pgc/player/web/playurl?avid=%s&cid=%s&qn=%s&type=&otype=json&ep_id=%s&fnver=0&fnval=16' % (avid, cid, qn, ep_id)
M
Mort Yao 已提交
60

61 62 63 64 65 66
    @staticmethod
    def bilibili_interface_api(cid, qn=0):
        entropy = 'rbMCKn@KuamXWlPMoJGsKcbiJKUfkPF_8dABscJntvqhRSETg'
        appkey, sec = ''.join([chr(ord(i) + 2) for i in entropy[::-1]]).split(':')
        params = 'appkey=%s&cid=%s&otype=json&qn=%s&quality=%s&type=' % (appkey, cid, qn, qn)
        chksum = hashlib.md5(bytes(params + sec, 'utf8')).hexdigest()
M
Mort Yao 已提交
67 68
        return 'https://interface.bilibili.com/v2/playurl?%s&sign=%s' % (params, chksum)

M
Mort Yao 已提交
69 70 71 72 73 74 75 76 77 78 79 80
    @staticmethod
    def bilibili_live_api(cid):
        return 'https://api.live.bilibili.com/room/v1/Room/playUrl?cid=%s&quality=0&platform=web' % cid

    @staticmethod
    def bilibili_live_room_info_api(room_id):
        return 'https://api.live.bilibili.com/room/v1/Room/get_info?room_id=%s' % room_id

    @staticmethod
    def bilibili_live_room_init_api(room_id):
        return 'https://api.live.bilibili.com/room/v1/Room/room_init?id=%s' % room_id

M
Mort Yao 已提交
81
    @staticmethod
M
Mort Yao 已提交
82 83
    def bilibili_space_channel_api(mid, cid, ps=100):
        return 'https://api.bilibili.com/x/space/channel/video?mid=%s&cid=%s&pn=1&ps=%s&order=0&jsonp=jsonp' % (mid, cid, ps)
M
Mort Yao 已提交
84 85 86

    @staticmethod
    def bilibili_space_favlist_api(vmid, fid, ps=100):
M
Mort Yao 已提交
87 88
        return 'https://api.bilibili.com/x/space/fav/arc?vmid=%s&fid=%s&pn=1&ps=%s&order=0&jsonp=jsonp' % (vmid, fid, ps)

M
Mort Yao 已提交
89
    @staticmethod
M
Mort Yao 已提交
90 91 92 93 94 95
    def bilibili_space_video_api(mid, ps=100):
        return 'https://space.bilibili.com/ajax/member/getSubmitVideos?mid=%s&page=1&pagesize=%s&order=0&jsonp=jsonp' % (mid, ps)

    @staticmethod
    def bilibili_vc_api(video_id):
        return 'https://api.vc.bilibili.com/clip/v1/video/detail?video_id=%s' % video_id
M
Mort Yao 已提交
96

M
MaxwellGoblin 已提交
97
    def prepare(self, **kwargs):
M
Mort Yao 已提交
98
        self.stream_qualities = {s['quality']: s for s in self.stream_types}
99

M
Mort Yao 已提交
100 101 102 103
        try:
            html_content = get_content(self.url, headers=self.bilibili_headers())
        except:
            html_content = ''  # live always returns 400 (why?)
M
Mort Yao 已提交
104 105
        #self.title = match1(html_content,
        #                    r'<h1 title="([^"]+)"')
A
ayanamist 已提交
106

M
Mort Yao 已提交
107 108 109 110 111 112 113
        # redirect: watchlater
        if re.match(r'https?://(www\.)?bilibili\.com/watchlater/#/av(\d+)', self.url):
            avid = match1(self.url, r'/av(\d+)')
            p = int(match1(self.url, r'/p(\d+)') or '1')
            self.url = 'https://www.bilibili.com/video/av%s?p=%s' % (avid, p)
            html_content = get_content(self.url, headers=self.bilibili_headers())

M
Mort Yao 已提交
114
        # redirect: bangumi/play/ss -> bangumi/play/ep
115 116 117
        # redirect: bangumi.bilibili.com/anime -> bangumi/play/ep
        elif re.match(r'https?://(www\.)?bilibili\.com/bangumi/play/ss(\d+)', self.url) or \
             re.match(r'https?://bangumi\.bilibili\.com/anime/(\d+)/play', self.url):
M
Mort Yao 已提交
118 119 120 121 122 123
            initial_state_text = match1(html_content, r'__INITIAL_STATE__=(.*?);\(function\(\)')  # FIXME
            initial_state = json.loads(initial_state_text)
            ep_id = initial_state['epList'][0]['id']
            self.url = 'https://www.bilibili.com/bangumi/play/ep%s' % ep_id
            html_content = get_content(self.url, headers=self.bilibili_headers())

124
        # sort it out
M
Mort Yao 已提交
125
        if re.match(r'https?://(www\.)?bilibili\.com/bangumi/play/ep(\d+)', self.url):
M
Mort Yao 已提交
126 127 128
            sort = 'bangumi'
        elif match1(html_content, r'<meta property="og:url" content="(https://www.bilibili.com/bangumi/play/[^"]+)"'):
            sort = 'bangumi'
M
Mort Yao 已提交
129 130 131
        elif re.match(r'https?://live\.bilibili\.com/', self.url):
            sort = 'live'
        elif re.match(r'https?://vc\.bilibili\.com/video/(\d+)', self.url):
M
Mort Yao 已提交
132
            sort = 'vc'
M
Mort Yao 已提交
133
        elif re.match(r'https?://(www\.)?bilibili\.com/video/av(\d+)', self.url):
M
Mort Yao 已提交
134
            sort = 'video'
135
        else:
M
Mort Yao 已提交
136 137
            self.download_playlist_by_url(self.url, **kwargs)
            return
M
Mort Yao 已提交
138 139 140

        # regular av video
        if sort == 'video':
141 142 143
            initial_state_text = match1(html_content, r'__INITIAL_STATE__=(.*?);\(function\(\)')  # FIXME
            initial_state = json.loads(initial_state_text)

M
Mort Yao 已提交
144
            playinfo_text = match1(html_content, r'__playinfo__=(.*?)</script><script>')  # FIXME
145
            playinfo = json.loads(playinfo_text) if playinfo_text else None
146

147 148 149
            html_content_ = get_content(self.url, headers=self.bilibili_headers(cookie='CURRENT_FNVAL=16'))
            playinfo_text_ = match1(html_content_, r'__playinfo__=(.*?)</script><script>')  # FIXME
            playinfo_ = json.loads(playinfo_text_) if playinfo_text_ else None
M
Mort Yao 已提交
150 151 152 153 154 155

            # warn if it is a multi-part video
            pn = initial_state['videoData']['videos']
            if pn > 1 and not kwargs.get('playlist'):
                log.w('This is a multipart video. (use --playlist to download all parts.)')

M
Mort Yao 已提交
156 157 158
            # set video title
            self.title = initial_state['videoData']['title']
            # refine title for a specific part, if it is a multi-part video
159 160
            p = int(match1(self.url, r'[\?&]p=(\d+)') or match1(self.url, r'/index_(\d+)') or
                    '1')  # use URL to decide p-number, not initial_state['p']
M
Mort Yao 已提交
161 162 163 164
            if pn > 1:
                part = initial_state['videoData']['pages'][p - 1]['part']
                self.title = '%s (P%s. %s)' % (self.title, p, part)

165 166 167
            # construct playinfos
            avid = initial_state['aid']
            cid = initial_state['videoData']['pages'][p - 1]['cid']  # use p-number, not initial_state['videoData']['cid']
168
            current_quality, best_quality = None, None
169
            if playinfo is not None:
170 171 172
                current_quality = playinfo['data']['quality'] or None  # 0 indicates an error, fallback to None
                if 'accept_quality' in playinfo['data'] and playinfo['data']['accept_quality'] != []:
                    best_quality = playinfo['data']['accept_quality'][0]
173 174 175 176 177
            playinfos = []
            if playinfo is not None:
                playinfos.append(playinfo)
            if playinfo_ is not None:
                playinfos.append(playinfo_)
178
            # get alternative formats from API
179 180
            for qn in [80, 64, 32, 16]:
                # automatic format for durl: qn=0
181 182
                # for dash, qn does not matter
                if current_quality is None or qn < current_quality:
183 184 185 186 187 188 189
                    api_url = self.bilibili_api(avid, cid, qn=qn)
                    api_content = get_content(api_url, headers=self.bilibili_headers())
                    api_playinfo = json.loads(api_content)
                    if api_playinfo['code'] == 0:  # success
                        playinfos.append(api_playinfo)
                    else:
                        message = api_playinfo['data']['message']
M
Mort Yao 已提交
190
                if best_quality is None or qn <= best_quality:
191 192 193 194 195
                    api_url = self.bilibili_interface_api(cid, qn=qn)
                    api_content = get_content(api_url, headers=self.bilibili_headers())
                    api_playinfo_data = json.loads(api_content)
                    if api_playinfo_data.get('quality'):
                        playinfos.append({'code': 0, 'message': '0', 'ttl': 1, 'data': api_playinfo_data})
196 197
            if not playinfos:
                log.w(message)
198 199 200
                # use bilibili error video instead
                url = 'https://static.hdslb.com/error.mp4'
                _, container, size = url_info(url)
201
                self.streams['flv480'] = {'container': container, 'size': size, 'src': [url]}
202 203
                return

204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
            for playinfo in playinfos:
                quality = playinfo['data']['quality']
                format_id = self.stream_qualities[quality]['id']
                container = self.stream_qualities[quality]['container'].lower()
                desc = self.stream_qualities[quality]['desc']

                if 'durl' in playinfo['data']:
                    src, size = [], 0
                    for durl in playinfo['data']['durl']:
                        src.append(durl['url'])
                        size += durl['size']
                    self.streams[format_id] = {'container': container, 'quality': desc, 'size': size, 'src': src}

                # DASH formats
                if 'dash' in playinfo['data']:
                    for video in playinfo['data']['dash']['video']:
                        # prefer the latter codecs!
                        s = self.stream_qualities[video['id']]
                        format_id = 'dash-' + s['id']  # prefix
                        container = 'mp4'  # enforce MP4 container
                        desc = s['desc']
                        audio_quality = s['audio_quality']
                        baseurl = video['baseUrl']
                        size = url_size(baseurl, headers=self.bilibili_headers(referer=self.url))

                        # find matching audio track
                        audio_baseurl = playinfo['data']['dash']['audio'][0]['baseUrl']
                        for audio in playinfo['data']['dash']['audio']:
                            if int(audio['id']) == audio_quality:
                                audio_baseurl = audio['baseUrl']
                                break
                        size += url_size(audio_baseurl, headers=self.bilibili_headers(referer=self.url))

                        self.dash_streams[format_id] = {'container': container, 'quality': desc,
                                                        'src': [[baseurl], [audio_baseurl]], 'size': size}
239

M
Mort Yao 已提交
240
        # bangumi
M
Mort Yao 已提交
241
        elif sort == 'bangumi':
M
Mort Yao 已提交
242 243
            initial_state_text = match1(html_content, r'__INITIAL_STATE__=(.*?);\(function\(\)')  # FIXME
            initial_state = json.loads(initial_state_text)
M
Mort Yao 已提交
244

245 246 247 248 249
            # warn if this bangumi has more than 1 video
            epn = len(initial_state['epList'])
            if epn > 1 and not kwargs.get('playlist'):
                log.w('This bangumi currently has %s videos. (use --playlist to download all videos.)' % epn)

250 251 252 253
            # set video title
            self.title = initial_state['h1Title']

            # construct playinfos
M
Mort Yao 已提交
254 255 256
            ep_id = initial_state['epInfo']['id']
            avid = initial_state['epInfo']['aid']
            cid = initial_state['epInfo']['cid']
257
            playinfos = []
M
Mort Yao 已提交
258 259
            api_url = self.bilibili_bangumi_api(avid, cid, ep_id)
            api_content = get_content(api_url, headers=self.bilibili_headers())
260 261 262 263 264
            api_playinfo = json.loads(api_content)
            if api_playinfo['code'] == 0:  # success
                playinfos.append(api_playinfo)
            else:
                log.e(api_playinfo['message'])
265
                return
266 267 268 269 270 271 272 273 274 275 276
            current_quality = api_playinfo['result']['quality']
            # get alternative formats from API
            for qn in [80, 64, 32, 16]:
                # automatic format for durl: qn=0
                # for dash, qn does not matter
                if qn != current_quality:
                    api_url = self.bilibili_bangumi_api(avid, cid, ep_id, qn=qn)
                    api_content = get_content(api_url, headers=self.bilibili_headers())
                    api_playinfo = json.loads(api_content)
                    if api_playinfo['code'] == 0:  # success
                        playinfos.append(api_playinfo)
277

278 279 280 281 282 283
            for playinfo in playinfos:
                if 'durl' in playinfo['result']:
                    quality = playinfo['result']['quality']
                    format_id = self.stream_qualities[quality]['id']
                    container = self.stream_qualities[quality]['container'].lower()
                    desc = self.stream_qualities[quality]['desc']
M
Mort Yao 已提交
284

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
                    src, size = [], 0
                    for durl in playinfo['result']['durl']:
                        src.append(durl['url'])
                        size += durl['size']
                    self.streams[format_id] = {'container': container, 'quality': desc, 'size': size, 'src': src}

                # DASH formats
                if 'dash' in playinfo['result']:
                    for video in playinfo['result']['dash']['video']:
                        # playinfo['result']['quality'] does not reflect the correct quality of DASH stream
                        quality = self.height_to_quality(video['height'])  # convert height to quality code
                        s = self.stream_qualities[quality]
                        format_id = 'dash-' + s['id']  # prefix
                        container = 'mp4'  # enforce MP4 container
                        desc = s['desc']
                        audio_quality = s['audio_quality']
                        baseurl = video['baseUrl']
                        size = url_size(baseurl, headers=self.bilibili_headers(referer=self.url))

                        # find matching audio track
                        audio_baseurl = playinfo['result']['dash']['audio'][0]['baseUrl']
                        for audio in playinfo['result']['dash']['audio']:
                            if int(audio['id']) == audio_quality:
                                audio_baseurl = audio['baseUrl']
                                break
                        size += url_size(audio_baseurl, headers=self.bilibili_headers(referer=self.url))

                        self.dash_streams[format_id] = {'container': container, 'quality': desc,
                                                        'src': [[baseurl], [audio_baseurl]], 'size': size}
M
Mort Yao 已提交
314

M
Mort Yao 已提交
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
        # vc video
        elif sort == 'vc':
            video_id = match1(self.url, r'https?://vc\.?bilibili\.com/video/(\d+)')
            api_url = self.bilibili_vc_api(video_id)
            api_content = get_content(api_url, headers=self.bilibili_headers())
            api_playinfo = json.loads(api_content)

            # set video title
            self.title = '%s (%s)' % (api_playinfo['data']['user']['name'], api_playinfo['data']['item']['id'])

            height = api_playinfo['data']['item']['height']
            quality = self.height_to_quality(height)  # convert height to quality code
            s = self.stream_qualities[quality]
            format_id = s['id']
            container = 'mp4'  # enforce MP4 container
            desc = s['desc']

            playurl = api_playinfo['data']['item']['video_playurl']
            size = int(api_playinfo['data']['item']['video_size'])

            self.streams[format_id] = {'container': container, 'quality': desc, 'size': size, 'src': [playurl]}

M
Mort Yao 已提交
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
        # live
        elif sort == 'live':
            m = re.match(r'https?://live\.bilibili\.com/(\w+)', self.url)
            short_id = m.group(1)
            api_url = self.bilibili_live_room_init_api(short_id)
            api_content = get_content(api_url, headers=self.bilibili_headers())
            room_init_info = json.loads(api_content)

            room_id = room_init_info['data']['room_id']
            api_url = self.bilibili_live_room_info_api(room_id)
            api_content = get_content(api_url, headers=self.bilibili_headers())
            room_info = json.loads(api_content)

            # set video title
            self.title = room_info['data']['title'] + '.' + str(int(time.time()))

            api_url = self.bilibili_live_api(room_id)
            api_content = get_content(api_url, headers=self.bilibili_headers())
            video_info = json.loads(api_content)

            durls = video_info['data']['durl']
            playurl = durls[0]['url']
            container = 'flv'  # enforce FLV container
            self.streams['flv'] = {'container': container, 'quality': 'unknown',
                                   'size': 0, 'src': [playurl]}

M
Mort Yao 已提交
363

364
        else:
M
Mort Yao 已提交
365 366
            # NOT IMPLEMENTED
            pass
367

M
Mort Yao 已提交
368 369 370 371
    def extract(self, **kwargs):
        # set UA and referer for downloading
        headers = self.bilibili_headers(referer=self.url)
        self.ua, self.referer = headers['User-Agent'], headers['Referer']
372

M
Mort Yao 已提交
373 374 375
        if not self.streams_sorted:
            # no stream is available
            return
376

M
Mort Yao 已提交
377 378 379 380 381 382 383
        if 'stream_id' in kwargs and kwargs['stream_id']:
            # extract the stream
            stream_id = kwargs['stream_id']
            if stream_id not in self.streams and stream_id not in self.dash_streams:
                log.e('[Error] Invalid video format.')
                log.e('Run \'-i\' command with no specific video format to view all available formats.')
                exit(2)
A
astronaut 已提交
384
        else:
M
Mort Yao 已提交
385 386
            # extract stream with the best quality
            stream_id = self.streams_sorted[0]['id']
M
MaxwellGoblin 已提交
387

M
Mort Yao 已提交
388 389 390 391 392 393
    def download_playlist_by_url(self, url, **kwargs):
        self.url = url
        kwargs['playlist'] = True

        html_content = get_content(self.url, headers=self.bilibili_headers())

394
        # sort it out
M
Mort Yao 已提交
395
        if re.match(r'https?://(www\.)?bilibili\.com/bangumi/play/ep(\d+)', self.url):
396 397 398
            sort = 'bangumi'
        elif match1(html_content, r'<meta property="og:url" content="(https://www.bilibili.com/bangumi/play/[^"]+)"'):
            sort = 'bangumi'
M
Mort Yao 已提交
399 400 401
        elif re.match(r'https?://(www\.)?bilibili\.com/bangumi/media/md(\d+)', self.url) or \
            re.match(r'https?://bangumi\.bilibili\.com/anime/(\d+)', self.url):
            sort = 'bangumi_md'
M
Mort Yao 已提交
402
        elif re.match(r'https?://(www\.)?bilibili\.com/video/av(\d+)', self.url):
403
            sort = 'video'
M
Mort Yao 已提交
404 405
        elif re.match(r'https?://space\.?bilibili\.com/(\d+)/channel/detail\?.*cid=(\d+)', self.url):
            sort = 'space_channel'
M
Mort Yao 已提交
406 407 408 409
        elif re.match(r'https?://space\.?bilibili\.com/(\d+)/favlist\?.*fid=(\d+)', self.url):
            sort = 'space_favlist'
        elif re.match(r'https?://space\.?bilibili\.com/(\d+)/video', self.url):
            sort = 'space_video'
M
Mort Yao 已提交
410 411 412
        else:
            log.e('[Error] Unsupported URL pattern.')
            exit(1)
413 414 415

        # regular av video
        if sort == 'video':
M
Mort Yao 已提交
416 417 418 419 420 421 422 423
            initial_state_text = match1(html_content, r'__INITIAL_STATE__=(.*?);\(function\(\)')  # FIXME
            initial_state = json.loads(initial_state_text)
            aid = initial_state['videoData']['aid']
            pn = initial_state['videoData']['videos']
            for pi in range(1, pn + 1):
                purl = 'https://www.bilibili.com/video/av%s?p=%s' % (aid, pi)
                self.__class__().download_by_url(purl, **kwargs)

424 425 426
        elif sort == 'bangumi':
            initial_state_text = match1(html_content, r'__INITIAL_STATE__=(.*?);\(function\(\)')  # FIXME
            initial_state = json.loads(initial_state_text)
M
Mort Yao 已提交
427
            epn, i = len(initial_state['epList']), 0
428
            for ep in initial_state['epList']:
M
Mort Yao 已提交
429
                i += 1; log.w('Extracting %s of %s videos ...' % (i, epn))
430 431 432
                ep_id = ep['id']
                epurl = 'https://www.bilibili.com/bangumi/play/ep%s/' % ep_id
                self.__class__().download_by_url(epurl, **kwargs)
M
Mort Yao 已提交
433 434 435 436 437 438 439 440 441 442 443 444
                sys.stdout.flush()

        elif sort == 'bangumi_md':
            initial_state_text = match1(html_content, r'__INITIAL_STATE__=(.*?);\(function\(\)')  # FIXME
            initial_state = json.loads(initial_state_text)
            epn, i = len(initial_state['mediaInfo']['episodes']), 0
            for ep in initial_state['mediaInfo']['episodes']:
                i += 1; log.w('Extracting %s of %s videos ...' % (i, epn))
                ep_id = ep['ep_id']
                epurl = 'https://www.bilibili.com/bangumi/play/ep%s/' % ep_id
                self.__class__().download_by_url(epurl, **kwargs)
                sys.stdout.flush()
445

M
Mort Yao 已提交
446 447 448 449 450 451 452 453
        elif sort == 'space_channel':
            m = re.match(r'https?://space\.?bilibili\.com/(\d+)/channel/detail\?.*cid=(\d+)', self.url)
            mid, cid = m.group(1), m.group(2)
            api_url = self.bilibili_space_channel_api(mid, cid)
            api_content = get_content(api_url, headers=self.bilibili_headers(referer=self.url))
            channel_info = json.loads(api_content)
            epn, i = len(channel_info['data']['list']['archives']), 0
            for video in channel_info['data']['list']['archives']:
M
Mort Yao 已提交
454 455 456 457 458
                i += 1; log.w('Extracting %s of %s videos ...' % (i, epn))
                url = 'https://www.bilibili.com/video/av%s' % video['aid']
                self.__class__().download_playlist_by_url(url, **kwargs)
                sys.stdout.flush()

M
Mort Yao 已提交
459 460 461
        elif sort == 'space_favlist':
            m = re.match(r'https?://space\.?bilibili\.com/(\d+)/favlist\?.*fid=(\d+)', self.url)
            vmid, fid = m.group(1), m.group(2)
M
Mort Yao 已提交
462
            api_url = self.bilibili_space_favlist_api(vmid, fid)
M
Mort Yao 已提交
463 464 465 466 467 468 469 470 471
            api_content = get_content(api_url, headers=self.bilibili_headers(referer=self.url))
            favlist_info = json.loads(api_content)
            epn, i = len(favlist_info['data']['archives']), 0
            for video in favlist_info['data']['archives']:
                i += 1; log.w('Extracting %s of %s videos ...' % (i, epn))
                url = 'https://www.bilibili.com/video/av%s' % video['aid']
                self.__class__().download_playlist_by_url(url, **kwargs)
                sys.stdout.flush()

M
Mort Yao 已提交
472 473 474 475 476 477 478 479
        elif sort == 'space_video':
            m = re.match(r'https?://space\.?bilibili\.com/(\d+)/video', self.url)
            mid = m.group(1)
            api_url = self.bilibili_space_video_api(mid)
            api_content = get_content(api_url, headers=self.bilibili_headers())
            videos_info = json.loads(api_content)
            epn, i = len(videos_info['data']['vlist']), 0
            for video in videos_info['data']['vlist']:
M
Mort Yao 已提交
480 481 482 483 484
                i += 1; log.w('Extracting %s of %s videos ...' % (i, epn))
                url = 'https://www.bilibili.com/video/av%s' % video['aid']
                self.__class__().download_playlist_by_url(url, **kwargs)
                sys.stdout.flush()

M
MaxwellGoblin 已提交
485 486 487

site = Bilibili()
download = site.download_by_url
M
Mort Yao 已提交
488
download_playlist = site.download_playlist_by_url
M
MaxwellGoblin 已提交
489 490

bilibili_download = download