[ie/showroom] Rework extractors (#17142)

Closes #2072, Closes #7297
Authored by: doe1080
This commit is contained in:
doe1080
2026-07-12 00:48:16 +00:00
committed by GitHub
parent 59d9ae606a
commit 6a188aed91
2 changed files with 117 additions and 60 deletions
+4 -1
View File
@@ -1718,7 +1718,10 @@ from .shahid import (
from .sharepoint import SharePointIE from .sharepoint import SharePointIE
from .shemaroome import ShemarooMeIE from .shemaroome import ShemarooMeIE
from .shiey import ShieyIE from .shiey import ShieyIE
from .showroomlive import ShowRoomLiveIE from .showroomlive import (
ShowRoomLiveIE,
ShowRoomVodIE,
)
from .sibnet import SibnetEmbedIE from .sibnet import SibnetEmbedIE
from .simplecast import ( from .simplecast import (
SimplecastEpisodeIE, SimplecastEpisodeIE,
+113 -59
View File
@@ -1,80 +1,134 @@
import datetime as dt
import json
from .common import InfoExtractor from .common import InfoExtractor
from ..utils import ( from ..utils import (
ExtractorError, UserNotLive,
clean_html,
extract_attributes,
int_or_none, int_or_none,
urljoin, str_or_none,
url_or_none,
)
from ..utils.traversal import (
find_element,
require,
traverse_obj,
) )
class ShowRoomLiveIE(InfoExtractor): class ShowRoomLiveIE(InfoExtractor):
_WORKING = False IE_NAME = 'showroom:live'
_VALID_URL = r'https?://(?:www\.)?showroom-live\.com/(?!onlive|timetable|event|campaign|news|ranking|room)(?P<id>[^/?#&]+)' IE_DESC = 'SHOWROOM'
_TEST = {
'url': 'https://www.showroom-live.com/48_Nana_Okada', _VALID_URL = r'https?://(?:www\.)?showroom-live\.com/r/(?P<id>[\w-]+)'
_TESTS = [{
'url': 'https://www.showroom-live.com/r/48_Yui_Oguri',
'only_matching': True, 'only_matching': True,
} }]
def _real_extract(self, url): def _real_extract(self, url):
broadcaster_id = self._match_id(url) broadcaster_id = self._match_id(url)
webpage = self._download_webpage(
url, broadcaster_id, headers={'Accept-Language': 'ja'})
nuxt_data = self._search_nuxt_json(webpage, broadcaster_id)['data']
webpage = self._download_webpage(url, broadcaster_id) cookies = self._get_cookies(url)
sr_id = traverse_obj(cookies, ('sr_id', 'value', {str}, filter))
if not sr_id:
self.raise_login_required()
room_id = self._search_regex( room_profile = traverse_obj(nuxt_data, (
(r'SrGlobal\.roomId\s*=\s*(\d+)', f'roomProfile-{broadcaster_id}-{sr_id}', {dict}))
r'(?:profile|room)\?room_id\=(\d+)'), webpage, 'room_id') start_timestamp = traverse_obj(room_profile, ('current_live_started_at', {int_or_none}))
is_live = traverse_obj(room_profile, ('is_onlive', {bool}))
room = self._download_json( if not is_live:
urljoin(url, f'/api/room/profile?room_id={room_id}'), if start_timestamp:
broadcaster_id) start_time = dt.datetime.fromtimestamp(
start_timestamp, dt.timezone.utc,
).astimezone().strftime('%Y-%m-%d %H:%M:%S %Z')
is_live = room.get('is_onlive') self.raise_no_formats(
if is_live is not True: f'Next livestream is scheduled to start at {start_time}', expected=True)
raise ExtractorError(f'{broadcaster_id} is offline', expected=True)
uploader = room.get('performer_name') or broadcaster_id return {
title = room.get('room_name') or room.get('main_name') or uploader 'id': broadcaster_id,
'live_status': 'is_upcoming',
'release_timestamp': start_timestamp,
}
raise UserNotLive(video_id=broadcaster_id)
room_id = traverse_obj(room_profile, ('room_id', {str_or_none}))
room_name = traverse_obj(room_profile, (
('room_name', 'main_name'), {clean_html}, filter, any))
streaming_url_list = self._download_json( streaming_url_list = self._download_json(
urljoin(url, f'/api/live/streaming_url?room_id={room_id}'), 'https://www.showroom-live.com/api/live/streaming_url',
broadcaster_id)['streaming_url_list'] broadcaster_id, query={'room_id': room_id})
m3u8_url = traverse_obj(streaming_url_list, (
formats = [] 'streaming_url_list', lambda _, v: v['type'] == 'hls_all',
for stream in streaming_url_list: 'url', {url_or_none}, any, {require('m3u8 URL')}))
stream_url = stream.get('url')
if not stream_url:
continue
stream_type = stream.get('type')
if stream_type == 'hls':
m3u8_formats = self._extract_m3u8_formats(
stream_url, broadcaster_id, ext='mp4', m3u8_id='hls',
live=True)
for f in m3u8_formats:
f['quality'] = int_or_none(stream.get('quality', 100))
formats.extend(m3u8_formats)
elif stream_type == 'rtmp':
stream_name = stream.get('stream_name')
if not stream_name:
continue
formats.append({
'url': stream_url,
'play_path': stream_name,
'page_url': url,
'player_url': 'https://www.showroom-live.com/assets/swf/v3/ShowRoomLive.swf',
'rtmp_live': True,
'ext': 'flv',
'format_id': 'rtmp',
'format_note': stream.get('label'),
'quality': int_or_none(stream.get('quality', 100)),
})
return { return {
'id': str(room.get('live_id') or broadcaster_id), 'title': room_name,
'title': title, 'channel': room_name,
'description': room.get('description'), 'channel_id': broadcaster_id,
'timestamp': int_or_none(room.get('current_live_started_at')), 'formats': self._extract_m3u8_formats(m3u8_url, broadcaster_id, 'mp4'),
'uploader': uploader, 'is_live': is_live,
'uploader_id': broadcaster_id, 'release_timestamp': start_timestamp,
'view_count': int_or_none(room.get('view_num')), **traverse_obj(room_profile, {
'formats': formats, 'id': ('live_id', {str_or_none}),
'is_live': True, 'channel_follower_count': ('follower_num', {int_or_none}),
'channel_is_verified': ('is_official', {bool}),
'description': ('description', {clean_html}, filter),
'genres': ('genre_name', {clean_html}, filter, all, filter),
'tags': ('live_tags', ..., 'name', {clean_html}, filter, all, filter),
'thumbnail': ('image_square', {url_or_none}),
'view_count': ('view_num', {int_or_none}),
}),
}
class ShowRoomVodIE(InfoExtractor):
IE_NAME = 'showroom:vod'
_VALID_URL = r'https?://(?:www\.)?showroom-live\.com/episode/watch\?(?:[^#]+&)?id=(?P<id>\w+)'
_TESTS = [{
'url': 'https://www.showroom-live.com/episode/watch?id=214',
'info_dict': {
'id': '214',
'ext': 'mp4',
'title': 'aaa',
},
}]
def _real_extract(self, url):
episode_id = self._match_id(url)
webpage = self._download_webpage(url, episode_id)
episode_data = traverse_obj(webpage, (
{find_element(id='episode-data', html=True)},
{extract_attributes}, 'data-episode', {json.loads}, {dict}))
streaming_url_list = self._download_json(
'https://www.showroom-live.com/api/episode/streaming_url',
episode_id, query={'episode_id': episode_id})
m3u8_url = traverse_obj(streaming_url_list, (
'streaming_url_list', 'hls_all',
'hls_all', {url_or_none}, {require('m3u8 URL')}))
return {
'id': episode_id,
'formats': self._extract_m3u8_formats(m3u8_url, episode_id, 'mp4'),
'thumbnail': traverse_obj(streaming_url_list, ('thumbnail_url', {url_or_none})),
**traverse_obj(episode_data, {
'title': ('title', {clean_html}, filter),
'description': ('description', {clean_html}, filter),
'duration': ('video_time', {int_or_none}),
'timestamp': ('display_started_at', {int_or_none}),
}),
**traverse_obj(episode_data, ('series', {
'series': ('name', {clean_html}, filter),
'series_id': ('id', {str_or_none}),
})),
} }