[BiliIntl] Support user-generated videos (#3203)

Authored by: wlritchi
This commit is contained in:
Luc Ritchie 2022-03-27 23:21:42 -04:00 committed by GitHub
parent cb96c5be70
commit f5f15c9993
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -15,6 +15,7 @@
) )
from ..utils import ( from ..utils import (
ExtractorError, ExtractorError,
filter_dict,
int_or_none, int_or_none,
float_or_none, float_or_none,
mimetype2ext, mimetype2ext,
@ -755,15 +756,21 @@ def json2srt(self, json):
for i, line in enumerate(json['body']) if line.get('content')) for i, line in enumerate(json['body']) if line.get('content'))
return data return data
def _get_subtitles(self, ep_id): def _get_subtitles(self, *, ep_id=None, aid=None):
sub_json = self._call_api(f'/web/v2/subtitle?episode_id={ep_id}&platform=web', ep_id) sub_json = self._call_api(
'/web/v2/subtitle', ep_id or aid, note='Downloading subtitles list',
errnote='Unable to download subtitles list', query=filter_dict({
'platform': 'web',
'episode_id': ep_id,
'aid': aid,
}))
subtitles = {} subtitles = {}
for sub in sub_json.get('subtitles') or []: for sub in sub_json.get('subtitles') or []:
sub_url = sub.get('url') sub_url = sub.get('url')
if not sub_url: if not sub_url:
continue continue
sub_data = self._download_json( sub_data = self._download_json(
sub_url, ep_id, errnote='Unable to download subtitles', fatal=False, sub_url, ep_id or aid, errnote='Unable to download subtitles', fatal=False,
note='Downloading subtitles%s' % f' for {sub["lang"]}' if sub.get('lang') else '') note='Downloading subtitles%s' % f' for {sub["lang"]}' if sub.get('lang') else '')
if not sub_data: if not sub_data:
continue continue
@ -773,9 +780,14 @@ def _get_subtitles(self, ep_id):
}) })
return subtitles return subtitles
def _get_formats(self, ep_id): def _get_formats(self, *, ep_id=None, aid=None):
video_json = self._call_api(f'/web/playurl?ep_id={ep_id}&platform=web', ep_id, video_json = self._call_api(
note='Downloading video formats', errnote='Unable to download video formats') '/web/playurl', ep_id or aid, note='Downloading video formats',
errnote='Unable to download video formats', query=filter_dict({
'platform': 'web',
'ep_id': ep_id,
'aid': aid,
}))
video_json = video_json['playurl'] video_json = video_json['playurl']
formats = [] formats = []
for vid in video_json.get('video') or []: for vid in video_json.get('video') or []:
@ -809,15 +821,15 @@ def _get_formats(self, ep_id):
self._sort_formats(formats) self._sort_formats(formats)
return formats return formats
def _extract_ep_info(self, episode_data, ep_id): def _extract_video_info(self, video_data, *, ep_id=None, aid=None):
return { return {
'id': ep_id, 'id': ep_id or aid,
'title': episode_data.get('title_display') or episode_data['title'], 'title': video_data.get('title_display') or video_data.get('title'),
'thumbnail': episode_data.get('cover'), 'thumbnail': video_data.get('cover'),
'episode_number': int_or_none(self._search_regex( 'episode_number': int_or_none(self._search_regex(
r'^E(\d+)(?:$| - )', episode_data.get('title_display'), 'episode number', default=None)), r'^E(\d+)(?:$| - )', video_data.get('title_display') or '', 'episode number', default=None)),
'formats': self._get_formats(ep_id), 'formats': self._get_formats(ep_id=ep_id, aid=aid),
'subtitles': self._get_subtitles(ep_id), 'subtitles': self._get_subtitles(ep_id=ep_id, aid=aid),
'extractor_key': BiliIntlIE.ie_key(), 'extractor_key': BiliIntlIE.ie_key(),
} }
@ -854,7 +866,7 @@ def _perform_login(self, username, password):
class BiliIntlIE(BiliIntlBaseIE): class BiliIntlIE(BiliIntlBaseIE):
_VALID_URL = r'https?://(?:www\.)?bili(?:bili\.tv|intl\.com)/(?:[a-z]{2}/)?play/(?P<season_id>\d+)/(?P<id>\d+)' _VALID_URL = r'https?://(?:www\.)?bili(?:bili\.tv|intl\.com)/(?:[a-z]{2}/)?(play/(?P<season_id>\d+)/(?P<ep_id>\d+)|video/(?P<aid>\d+))'
_TESTS = [{ _TESTS = [{
# Bstation page # Bstation page
'url': 'https://www.bilibili.tv/en/play/34613/341736', 'url': 'https://www.bilibili.tv/en/play/34613/341736',
@ -889,24 +901,35 @@ class BiliIntlIE(BiliIntlBaseIE):
}, { }, {
'url': 'https://www.biliintl.com/en/play/34613/341736', 'url': 'https://www.biliintl.com/en/play/34613/341736',
'only_matching': True, 'only_matching': True,
}, {
# User-generated content (as opposed to a series licensed from a studio)
'url': 'https://bilibili.tv/en/video/2019955076',
'only_matching': True,
}, {
# No language in URL
'url': 'https://www.bilibili.tv/video/2019955076',
'only_matching': True,
}] }]
def _real_extract(self, url): def _real_extract(self, url):
season_id, video_id = self._match_valid_url(url).groups() season_id, ep_id, aid = self._match_valid_url(url).group('season_id', 'ep_id', 'aid')
video_id = ep_id or aid
webpage = self._download_webpage(url, video_id) webpage = self._download_webpage(url, video_id)
# Bstation layout # Bstation layout
initial_data = self._parse_json(self._search_regex( initial_data = self._parse_json(self._search_regex(
r'window\.__INITIAL_DATA__\s*=\s*({.+?});', webpage, r'window\.__INITIAL_(?:DATA|STATE)__\s*=\s*({.+?});', webpage,
'preload state', default='{}'), video_id, fatal=False) or {} 'preload state', default='{}'), video_id, fatal=False) or {}
episode_data = traverse_obj(initial_data, ('OgvVideo', 'epDetail'), expected_type=dict) video_data = (
traverse_obj(initial_data, ('OgvVideo', 'epDetail'), expected_type=dict)
or traverse_obj(initial_data, ('UgcVideo', 'videoData'), expected_type=dict) or {})
if not episode_data: if season_id and not video_data:
# Non-Bstation layout, read through episode list # 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) season_json = self._call_api(f'/web/v2/ogv/play/episodes?season_id={season_id}&platform=web', video_id)
episode_data = next( video_data = next(
episode for episode in traverse_obj(season_json, ('sections', ..., 'episodes', ...), expected_type=dict) episode for episode in traverse_obj(season_json, ('sections', ..., 'episodes', ...), expected_type=dict)
if str(episode.get('episode_id')) == video_id) if str(episode.get('episode_id')) == ep_id)
return self._extract_ep_info(episode_data, video_id) return self._extract_video_info(video_data, ep_id=ep_id, aid=aid)
class BiliIntlSeriesIE(BiliIntlBaseIE): class BiliIntlSeriesIE(BiliIntlBaseIE):
@ -934,7 +957,7 @@ def _entries(self, series_id):
series_json = self._call_api(f'/web/v2/ogv/play/episodes?season_id={series_id}&platform=web', series_id) series_json = self._call_api(f'/web/v2/ogv/play/episodes?season_id={series_id}&platform=web', series_id)
for episode in traverse_obj(series_json, ('sections', ..., 'episodes', ...), expected_type=dict, default=[]): for episode in traverse_obj(series_json, ('sections', ..., 'episodes', ...), expected_type=dict, default=[]):
episode_id = str(episode.get('episode_id')) episode_id = str(episode.get('episode_id'))
yield self._extract_ep_info(episode, episode_id) yield self._extract_video_info(episode, ep_id=episode_id)
def _real_extract(self, url): def _real_extract(self, url):
series_id = self._match_id(url) series_id = self._match_id(url)