'danmaku': [{
'ext': 'xml',
'url': f'https://comment.bilibili.com/{cid}.xml',
- }]
+ }],
}
subtitle_info = traverse_obj(self._download_json(
for s in subs_list:
subtitles.setdefault(s['lan'], []).append({
'ext': 'srt',
- 'data': self.json2srt(self._download_json(s['subtitle_url'], video_id))
+ 'data': self.json2srt(self._download_json(s['subtitle_url'], video_id)),
})
return subtitles
yield {
**metainfo,
'id': f'{video_id}_{cid}',
- 'title': f'{metainfo.get("title")} - {list(edges.values())[0].get("title")}',
+ 'title': f'{metainfo.get("title")} - {next(iter(edges.values())).get("title")}',
'formats': self.extract_formats(play_info),
'description': f'{json.dumps(edges, ensure_ascii=False)}\n{metainfo.get("description", "")}',
'duration': float_or_none(play_info.get('timelength'), scale=1000),
'url': 'https://www.bilibili.com/video/BV1bK411W797',
'info_dict': {
'id': 'BV1bK411W797',
- 'title': '物语中的人物是如何吐槽自己的OP的'
+ 'title': '物语中的人物是如何吐槽自己的OP的',
},
'playlist_count': 18,
'playlist': [{
'view_count': int,
'description': 'md5:e3c401cf7bc363118d1783dd74068a68',
'duration': 90.314,
- }
- }]
+ },
+ }],
}, {
'note': 'Specific page of Anthology',
'url': 'https://www.bilibili.com/video/BV1bK411W797?p=1',
'view_count': int,
'description': 'md5:e3c401cf7bc363118d1783dd74068a68',
'duration': 90.314,
- }
+ },
}, {
'note': 'video has subtitles',
'url': 'https://www.bilibili.com/video/BV12N4y1M7rh',
'view_count': int,
'like_count': int,
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
- 'subtitles': 'count:2'
+ 'subtitles': 'count:2',
},
'params': {'listsubtitles': True},
}, {
is_interactive = traverse_obj(video_data, ('rights', 'is_stein_gate'))
if is_interactive:
return self.playlist_result(
- self._get_interactive_entries(video_id, cid, metainfo), **metainfo, **{
- 'duration': traverse_obj(initial_state, ('videoData', 'duration', {int_or_none})),
- '__post_extractor': self.extract_comments(aid),
- })
+ self._get_interactive_entries(video_id, cid, metainfo), **metainfo,
+ duration=traverse_obj(initial_state, ('videoData', 'duration', {int_or_none})),
+ __post_extractor=self.extract_comments(aid))
else:
return {
**metainfo,
'duration': 1425.256,
'timestamp': 1554566400,
'upload_date': '20190406',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
'skip': 'Geo-restricted',
}, {
'duration': 1922.129,
'timestamp': 1602853860,
'upload_date': '20201016',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
}]
'duration': 1525.777,
'timestamp': 1425074413,
'upload_date': '20150227',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
}],
}]
'title': '鬼灭之刃',
'description': 'md5:e2cc9848b6f69be6db79fc2a82d9661b',
},
- 'playlist_mincount': 26
+ 'playlist_mincount': 26,
}, {
'url': 'https://www.bilibili.com/bangumi/play/ss2251',
'info_dict': {
'duration': 1436.992,
'timestamp': 1343185080,
'upload_date': '20120725',
- 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$'
+ 'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
},
}],
}]
'upload_date': '20230924',
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
'view_count': int,
- }
+ },
}]
def _real_extract(self, url):
'upload_date': '20230924',
'thumbnail': r're:^https?://.*\.(jpg|jpeg|png)$',
'view_count': int,
- }
+ },
}],
'params': {'playlist_items': '1'},
}, {
for position in (
46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39,
12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63,
- 57, 62, 11, 36, 20, 34, 44, 52
+ 57, 62, 11, 36, 20, 34, 44, 52,
):
char_at_position = try_call(lambda: session_key[position])
if char_at_position:
'uploader_id': ('meta', 'mid', {str_or_none}),
'timestamp': ('meta', 'ptime', {int_or_none}),
'thumbnail': ('meta', 'cover', {url_or_none}),
- })
+ }),
}
def get_entries(page_data):
mid, sid = self._match_valid_url(url).group('mid', 'sid')
playlist_id = f'{mid}_{sid}'
playlist_meta = traverse_obj(self._download_json(
- f'https://api.bilibili.com/x/series/series?series_id={sid}', playlist_id, fatal=False
+ f'https://api.bilibili.com/x/series/series?series_id={sid}', playlist_id, fatal=False,
), {
'title': ('data', 'meta', 'name', {str}),
'description': ('data', 'meta', 'description', {str}),
'page_count': math.ceil(entry_count / page_size),
'page_size': page_size,
'uploader': self._get_uploader(mid, playlist_id),
- **playlist_meta
+ **playlist_meta,
}
def get_entries(page_data):
'upload_date': '20201109',
'modified_timestamp': int,
'modified_date': str,
- 'thumbnail': r"re:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg",
+ 'thumbnail': r're:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg',
'view_count': int,
'like_count': int,
},
'uploader_id': '84912',
'timestamp': 1604905176,
'upload_date': '20201109',
- 'thumbnail': r"re:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg",
+ 'thumbnail': r're:http://i\d\.hdslb\.com/bfs/archive/14b83c62aa8871b79083df1e9ab4fbc699ad16fe\.jpg',
},
'playlist_mincount': 22,
}, {
for page_num in itertools.count(1):
page_data = self._download_json(
'https://api.bilibili.com/x/v2/medialist/resource/list',
- list_id, query=query, note=f'getting playlist {query["biz_id"]} page {page_num}'
+ list_id, query=query, note=f'getting playlist {query["biz_id"]} page {page_num}',
)['data']
yield from self._get_entries(page_data, 'media_list', ending_key='bv_id')
query['oid'] = traverse_obj(page_data, ('media_list', -1, 'id'))
'tid': ('tid', {int_or_none}),
'sort_field': ('sortFiled', {int_or_none}),
'desc': ('desc', {bool_or_none}, {str_or_none}, {str.lower}),
- })
+ }),
}
metadata = {
'id': f'{query["type"]}_{query["biz_id"]}',
'url': 'https://www.bilibili.com/v/kichiku/mad',
'info_dict': {
'id': 'kichiku: mad',
- 'title': 'kichiku: mad'
+ 'title': 'kichiku: mad',
},
'playlist_mincount': 45,
'params': {
- 'playlistend': 45
- }
+ 'playlistend': 45,
+ },
}]
def _fetch_page(self, api_url, num_pages, query, page_num):
parsed_json = self._download_json(
api_url, query, query={'Search_key': query, 'pn': page_num},
- note='Extracting results from page %s of %s' % (page_num, num_pages))
+ note=f'Extracting results from page {page_num} of {num_pages}')
video_list = traverse_obj(parsed_json, ('data', 'archives'), expected_type=list)
if not video_list:
- raise ExtractorError('Failed to retrieve video list for page %d' % page_num)
+ raise ExtractorError(f'Failed to retrieve video list for page {page_num}')
for video in video_list:
yield self.url_result(
- 'https://www.bilibili.com/video/%s' % video['bvid'], 'BiliBili', video['bvid'])
+ 'https://www.bilibili.com/video/{}'.format(video['bvid']), 'BiliBili', video['bvid'])
def _entries(self, category, subcategory, query):
# map of categories : subcategories : RIDs
'manual_vocaloid': 126,
'guide': 22,
'theatre': 216,
- 'course': 127
+ 'course': 127,
},
}
def _real_extract(self, url):
category, subcategory = urllib.parse.urlparse(url).path.split('/')[2:4]
- query = '%s: %s' % (category, subcategory)
+ query = f'{category}: {subcategory}'
return self.playlist_result(self._entries(category, subcategory, query), query, query)
formats = [{
'url': play_data['cdns'][0],
'filesize': int_or_none(play_data.get('size')),
- 'vcodec': 'none'
+ 'vcodec': 'none',
}]
for a_format in formats:
subtitles = {
'origin': [{
'url': lyric,
- }]
+ }],
}
return {
def _real_extract(self, url):
video_id = self._match_id(url)
return self.url_result(
- 'http://www.bilibili.tv/video/av%s/' % video_id,
+ f'http://www.bilibili.tv/video/av{video_id}/',
ie=BiliBiliIE.ie_key(), video_id=video_id)
return json.get('data')
def json2srt(self, json):
- data = '\n\n'.join(
+ return '\n\n'.join(
f'{i + 1}\n{srt_subtitles_timecode(line["from"])} --> {srt_subtitles_timecode(line["to"])}\n{line["content"]}'
for i, line in enumerate(traverse_obj(json, (
'body', lambda _, l: l['content'] and l['from'] and l['to']))))
- return data
def _get_subtitles(self, *, ep_id=None, aid=None):
sub_json = self._call_api(
note='Downloading login key', errnote='Unable to download login key')['data']
public_key = Cryptodome.RSA.importKey(key_data['key'])
- password_hash = Cryptodome.PKCS1_v1_5.new(public_key).encrypt((key_data['hash'] + password).encode('utf-8'))
+ password_hash = Cryptodome.PKCS1_v1_5.new(public_key).encrypt((key_data['hash'] + password).encode())
login_post = self._download_json(
'https://passport.bilibili.tv/x/intl/passport-login/web/login/password?lang=en-US', None, data=urlencode_postdata({
'username': username,
'password': base64.b64encode(password_hash).decode('ascii'),
'keep_me': 'true',
's_locale': 'en_US',
- 'isTrusted': 'true'
+ 'isTrusted': 'true',
}), note='Logging in', errnote='Unable to log in')
if login_post.get('code'):
if login_post.get('message'):
'chapters': [{
'start_time': 0,
'end_time': 76.242,
- 'title': '<Untitled Chapter 1>'
+ 'title': '<Untitled Chapter 1>',
}, {
'start_time': 76.242,
'end_time': 161.161,
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': 1325.742,
'end_time': 1403.903,
- 'title': 'Outro'
+ 'title': 'Outro',
}],
- }
+ },
}, {
# Non-Bstation page
'url': 'https://www.bilibili.tv/en/play/1033760/11005006',
'chapters': [{
'start_time': 0,
'end_time': 88.0,
- 'title': '<Untitled Chapter 1>'
+ 'title': '<Untitled Chapter 1>',
}, {
'start_time': 88.0,
'end_time': 156.0,
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': 1173.0,
'end_time': 1259.535,
- 'title': 'Outro'
+ 'title': 'Outro',
}],
- }
+ },
}, {
# Subtitle with empty content
'url': 'https://www.bilibili.tv/en/play/1005144/10131790',
'thumbnail': r're:^https://pic\.bstarstatic\.com/ogv/.+\.png$',
'episode_number': 140,
},
- 'skip': 'According to the copyright owner\'s request, you may only watch the video after you log in.'
+ 'skip': 'According to the copyright owner\'s request, you may only watch the video after you log in.',
}, {
# episode comment extraction
'url': 'https://www.bilibili.tv/en/play/34580/340317',
'chapters': [{
'start_time': 0,
'end_time': 61.0,
- 'title': '<Untitled Chapter 1>'
+ 'title': '<Untitled Chapter 1>',
}, {
'start_time': 61.0,
'end_time': 134.0,
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': 1290.0,
'end_time': 1379.0,
- 'title': 'Outro'
+ 'title': 'Outro',
}],
},
'params': {
- 'getcomments': True
- }
+ 'getcomments': True,
+ },
}, {
# user generated content comment extraction
'url': 'https://www.bilibili.tv/en/video/2045730385',
'thumbnail': r're:https://pic\.bstarstatic\.(?:com|net)/ugc/f6c363659efd2eabe5683fbb906b1582\.jpg',
},
'params': {
- 'getcomments': True
- }
+ 'getcomments': True,
+ },
}, {
# episode id without intro and outro
'url': 'https://www.bilibili.tv/en/play/1048837/11246489',
# Non-Bstation layout, read through episode list
season_json = self._call_api(f'/web/v2/ogv/play/episodes?season_id={season_id}&platform=web', video_id)
video_data = traverse_obj(season_json, (
- 'sections', ..., 'episodes', lambda _, v: str(v['episode_id']) == video_id
+ 'sections', ..., 'episodes', lambda _, v: str(v['episode_id']) == video_id,
), expected_type=dict, get_all=False)
# XXX: webpage metadata may not accurate, it just used to not crash when video_data not found
'id': replies.get('rpid'),
'like_count': int_or_none(replies.get('like_count')),
'parent': replies.get('parent'),
- 'timestamp': unified_timestamp(replies.get('ctime_text'))
+ 'timestamp': unified_timestamp(replies.get('ctime_text')),
}
if not traverse_obj(comment_api_raw_data, ('data', 'cursor', 'is_end')):
chapters = [{
'start_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'opening_start_time')), 1000),
'end_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'opening_end_time')), 1000),
- 'title': 'Intro'
+ 'title': 'Intro',
}, {
'start_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'ending_start_time')), 1000),
'end_time': float_or_none(traverse_obj(intro_ending_json, ('skip', 'ending_end_time')), 1000),
- 'title': 'Outro'
+ 'title': 'Outro',
}]
return {
episode_id = str(episode['episode_id'])
yield self.url_result(smuggle_url(
BiliIntlIE._make_url(episode_id, series_id),
- self._parse_video_metadata(episode)
+ self._parse_video_metadata(episode),
), BiliIntlIE, episode_id)
def _real_extract(self, url):
'url': 'https://live.bilibili.com/196',
'info_dict': {
'id': '33989',
- 'description': "周六杂谈回,其他时候随机游戏。 | \n录播:@下播型泛式录播组。 | \n直播通知群(全员禁言):666906670,902092584,59971⑧481 (功能一样,别多加)",
+ 'description': '周六杂谈回,其他时候随机游戏。 | \n录播:@下播型泛式录播组。 | \n直播通知群(全员禁言):666906670,902092584,59971⑧481 (功能一样,别多加)',
'ext': 'flv',
- 'title': "太空狼人杀联动,不被爆杀就算赢",
- 'thumbnail': "https://i0.hdslb.com/bfs/live/new_room_cover/e607bc1529057ef4b332e1026e62cf46984c314d.jpg",
+ 'title': '太空狼人杀联动,不被爆杀就算赢',
+ 'thumbnail': 'https://i0.hdslb.com/bfs/live/new_room_cover/e607bc1529057ef4b332e1026e62cf46984c314d.jpg',
'timestamp': 1650802769,
},
- 'skip': 'not live'
+ 'skip': 'not live',
}, {
'url': 'https://live.bilibili.com/196?broadcast_type=0&is_room_feed=1?spm_id_from=333.999.space_home.strengthen_live_card.click',
- 'only_matching': True
+ 'only_matching': True,
}, {
'url': 'https://live.bilibili.com/blanc/196',
- 'only_matching': True
+ 'only_matching': True,
}]
_FORMATS = {
raise ExtractorError('Streamer is not live', expected=True)
formats = []
- for qn in self._FORMATS.keys():
+ for qn in self._FORMATS:
stream_data = self._call_api('xlive/web-room/v2/index/getRoomPlayInfo', room_id, {
'room_id': room_id,
'qn': qn,