1
0
mirror of https://github.com/yt-dlp/yt-dlp.git synced 2025-07-08 22:38:33 +00:00
This commit is contained in:
doe1080 2025-06-12 01:44:31 +09:00
parent 2c7e09dd26
commit 740f6ea2e5

View File

@ -4,11 +4,14 @@
import json import json
from .common import InfoExtractor from .common import InfoExtractor
from ..networking.exceptions import HTTPError
from ..utils import ( from ..utils import (
ExtractorError, ExtractorError,
clean_html,
int_or_none,
parse_qs, parse_qs,
str_or_none, str_or_none,
strip_or_none, update_url,
url_or_none, url_or_none,
) )
from ..utils.traversal import traverse_obj from ..utils.traversal import traverse_obj
@ -18,8 +21,8 @@ class OnsenIE(InfoExtractor):
IE_NAME = 'onsen' IE_NAME = 'onsen'
IE_DESC = 'インターネットラジオステーション<音泉>' IE_DESC = 'インターネットラジオステーション<音泉>'
_BASE_URL = 'https://www.onsen.ag/' _BASE_URL = 'https://www.onsen.ag'
_HEADERS = {'Referer': _BASE_URL} _HEADERS = {'Referer': f'{_BASE_URL}/'}
_NETRC_MACHINE = 'onsen' _NETRC_MACHINE = 'onsen'
_VALID_URL = r'https?://(?:(?:share|www)\.)onsen\.ag/program/(?P<id>[^/?#]+)' _VALID_URL = r'https?://(?:(?:share|www)\.)onsen\.ag/program/(?P<id>[^/?#]+)'
_TESTS = [{ _TESTS = [{
@ -28,16 +31,15 @@ class OnsenIE(InfoExtractor):
'id': '10462', 'id': '10462',
'ext': 'm4a', 'ext': 'm4a',
'title': '第SP回', 'title': '第SP回',
'cast': ['下野紘', '佐藤元', '守屋亨香'], 'cast': 'count:3',
'description': 'md5:083c1eddf198694cd3cc83f4d5c03863', 'description': 'md5:de62c80a41c4c8d84da53a1ee681ad18',
'display_id': 'MTA0NjI=', 'display_id': 'MTA0NjI=',
'http_headers': {'Referer': 'https://www.onsen.ag/'},
'media_type': 'sound', 'media_type': 'sound',
'section_start': 0, 'section_start': 0,
'series': '音泉キング「下野紘」のラジオ きみはもちろん、<音泉>ファミリーだよね?', 'series': '音泉キング「下野紘」のラジオ きみはもちろん、<音泉>ファミリーだよね?',
'series_id': 'onsenking', 'series_id': 'onsenking',
'tags': ['音泉キング', '音泉ジュニア'], 'tags': 'count:2',
'thumbnail': r're:https?://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+$', 'thumbnail': r're:https?://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+',
'upload_date': '20220627', 'upload_date': '20220627',
'webpage_url': 'https://www.onsen.ag/program/onsenking?c=MTA0NjI=', 'webpage_url': 'https://www.onsen.ag/program/onsenking?c=MTA0NjI=',
}, },
@ -47,32 +49,31 @@ class OnsenIE(InfoExtractor):
'id': '18001', 'id': '18001',
'ext': 'mp4', 'ext': 'mp4',
'title': '第4回', 'title': '第4回',
'cast': ['夕莉', '理名', '朱李', '凪都', '美怜'], 'cast': 'count:5',
'description': 'md5:1d7f6a2f1f5a3e2a8ada4e9f652262dd', 'description': 'md5:1d7f6a2f1f5a3e2a8ada4e9f652262dd',
'display_id': 'MTgwMDE=', 'display_id': 'MTgwMDE=',
'http_headers': {'Referer': 'https://www.onsen.ag/'},
'media_type': 'movie', 'media_type': 'movie',
'section_start': 0, 'section_start': 0,
'series': 'TVアニメ『ガールズバンドクライ』WEBラジオ「ガールズバンドクライラジオにも全部ぶち込め。', 'series': 'TVアニメ『ガールズバンドクライ』WEBラジオ「ガールズバンドクライラジオにも全部ぶち込め。',
'series_id': 'girls-band-cry-radio', 'series_id': 'girls-band-cry-radio',
'tags': ['ガールズバンドクライ', 'ガルクラ', 'ガルクラジオ'], 'tags': 'count:3',
'thumbnail': r're:https?://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+$', 'thumbnail': r're:https?://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+',
'upload_date': '20240425', 'upload_date': '20240425',
'webpage_url': 'https://www.onsen.ag/program/girls-band-cry-radio?c=MTgwMDE=', 'webpage_url': 'https://www.onsen.ag/program/girls-band-cry-radio?c=MTgwMDE=',
}, },
'skip': 'Only available for premium supporters', 'skip': 'Only available for premium supporters',
}, { }, {
'url': 'https://www.onsen.ag/program/g-witch', 'url': 'https://www.onsen.ag/program/uma',
'info_dict': { 'info_dict': {
'id': 'g-witch', 'id': 'uma',
'title': '機動戦士ガンダム 水星の魔女~アスティカシア高等専門学園 ラジオ委員会~', 'title': 'UMA YELL RADIO',
}, },
'playlist_mincount': 7, 'playlist_mincount': 35,
}] }]
def _perform_login(self, username, password): def _perform_login(self, username, password):
sign_in = self._download_json( sign_in = self._download_json(
f'{self._BASE_URL}web_api/signin', None, 'Logging in', headers={ f'{self._BASE_URL}/web_api/signin', None, 'Logging in', headers={
'Accept': 'application/json, text/plain, */*', 'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
}, data=json.dumps({ }, data=json.dumps({
@ -87,12 +88,17 @@ def _perform_login(self, username, password):
def _real_extract(self, url): def _real_extract(self, url):
program_id = self._match_id(url) program_id = self._match_id(url)
qs = {k: v[0] for k, v in parse_qs(url).items() if v} try:
programs = self._download_json( programs = self._download_json(
f'{self._BASE_URL}web_api/programs/{program_id}', program_id) f'{self._BASE_URL}/web_api/programs/{program_id}', program_id)
enc_id = lambda p: base64.b64encode(str(p['id']).encode()).decode() except ExtractorError as e:
if isinstance(e.cause, HTTPError) and e.cause.status == 404:
raise ExtractorError('Invalid URL', expected=True)
raise
if 'c' not in qs: enc_id = lambda p: base64.b64encode(str(p['id']).encode()).decode()
query = {k: v[0] for k, v in parse_qs(url).items() if v}
if 'c' not in query:
entries = [self.url_result( entries = [self.url_result(
f'{url}?c={enc_id(program)}', OnsenIE, f'{url}?c={enc_id(program)}', OnsenIE,
) for program in traverse_obj(programs, ('contents', lambda _, v: v['id']))] ) for program in traverse_obj(programs, ('contents', lambda _, v: v['id']))]
@ -100,16 +106,23 @@ def _real_extract(self, url):
return self.playlist_result( return self.playlist_result(
entries, program_id, programs['program_info']['title']) entries, program_id, programs['program_info']['title'])
raw_id = base64.b64decode(qs['c'] + '=' * (-len(qs['c']) % 4)).decode() raw_id = base64.b64decode(query['c'] + '=' * (-len(query['c']) & 3)).decode()
p_keys = ('contents', lambda _, v: v['id'] == int(raw_id)) p_keys = ('contents', lambda _, v: v['id'] == int(raw_id))
if not (program := traverse_obj(programs, (*p_keys, any))):
raise ExtractorError('This program is no longer available', expected=True) program = traverse_obj(programs, (*p_keys, any))
if not (m3u8_url := traverse_obj(program, ('streaming_url', {url_or_none}))): if not program:
self.raise_login_required('This program is only available for premium supporters') raise ExtractorError(
'This program is no longer available', expected=True)
m3u8_url = traverse_obj(program, ('streaming_url', {url_or_none}))
if not m3u8_url:
self.raise_login_required(
'This program is only available for premium supporters')
display_id = enc_id(program) display_id = enc_id(program)
upload_date = None upload_date = None
if date_str := self._search_regex(rf'{program_id}0?(\d{{6}})', m3u8_url, 'date string', default=None): if date_str := self._search_regex(
rf'{program_id}0?(\d{{6}})', m3u8_url, 'date string', default=None,
):
with contextlib.suppress(ValueError): with contextlib.suppress(ValueError):
upload_date = dt.datetime.strptime(f'20{date_str}', '%Y%m%d').strftime('%Y%m%d') upload_date = dt.datetime.strptime(f'20{date_str}', '%Y%m%d').strftime('%Y%m%d')
@ -117,22 +130,22 @@ def _real_extract(self, url):
'display_id': display_id, 'display_id': display_id,
'formats': self._extract_m3u8_formats(m3u8_url, display_id, headers=self._HEADERS), 'formats': self._extract_m3u8_formats(m3u8_url, display_id, headers=self._HEADERS),
'http_headers': self._HEADERS, 'http_headers': self._HEADERS,
'section_start': int(qs.get('t', 0)), 'section_start': int_or_none(query.get('t', 0)),
'upload_date': upload_date, 'upload_date': upload_date,
'webpage_url': f'{self._BASE_URL}program/{program_id}?c={display_id}', 'webpage_url': f'{self._BASE_URL}/program/{program_id}?c={display_id}',
**traverse_obj(program, { **traverse_obj(program, {
'id': ('id', {str_or_none}), 'id': ('id', {str_or_none}),
'title': ('title', {strip_or_none}), 'title': ('title', {clean_html}),
'media_type': ('media_type', {str}), 'media_type': ('media_type', {str}),
'thumbnail': ('poster_image_url', {url_or_none}, {lambda x: x.partition('?')[0]}), 'thumbnail': ('poster_image_url', {url_or_none}, {update_url(query=None)}),
}), }),
**traverse_obj(programs, { **traverse_obj(programs, {
'cast': (('performers', (*p_keys, 'guests')), ..., 'name', {str}), 'cast': (('performers', (*p_keys, 'guests')), ..., 'name', {str}, filter, all, filter),
'series_id': ('directory_name', {str_or_none}), 'series_id': ('directory_name', {str}),
}), }),
**traverse_obj(programs, ('program_info', { **traverse_obj(programs, ('program_info', {
'description': ('description', {str}), 'description': ('description', {clean_html}),
'series': ('title', {str}), 'series': ('title', {clean_html}),
'tags': ('hashtag_list', ..., {str}), 'tags': ('hashtag_list', ..., {str}, filter, all, filter),
})), })),
} }