Compare commits

...

9 Commits

Author SHA1 Message Date
doe1080
9efed0c505
Merge f5001ce3bd into f919729538 2024-11-19 02:44:39 +00:00
doe1080
f5001ce3bd fix 2024-11-19 11:42:26 +09:00
doe1080
9b1d30cccf
Merge branch 'yt-dlp:master' into onsen 2024-11-19 11:40:54 +09:00
doe1080
a97bf699af
Merge branch 'yt-dlp:master' into onsen 2024-11-17 23:24:33 +09:00
doe1080
8fd2c44be1 Update test, regex, sort 2024-11-17 22:25:57 +09:00
doe1080
f8a77440a5
Merge branch 'yt-dlp:master' into onsen 2024-11-17 22:12:26 +09:00
doe1080
7622770586
Merge branch 'yt-dlp:master' into onsen 2024-11-17 20:32:33 +09:00
doe1080
acdcecd02d
Merge branch 'yt-dlp:master' into onsen 2024-11-17 05:36:18 +09:00
doe1080
7ceb05cfcc [ie/Onsen] Add extractor 2024-09-10 18:11:54 +09:00
2 changed files with 139 additions and 0 deletions

View File

@ -1450,6 +1450,7 @@
OnetPlIE,
)
from .onionstudios import OnionStudiosIE
from .onsen import OnsenIE
from .opencast import (
OpencastIE,
OpencastPlaylistIE,

138
yt_dlp/extractor/onsen.py Normal file
View File

@ -0,0 +1,138 @@
import base64
import json
from .common import InfoExtractor
from ..utils import (
ExtractorError,
parse_qs,
str_or_none,
strip_or_none,
)
from ..utils.traversal import traverse_obj
class OnsenIE(InfoExtractor):
IE_NAME = 'onsen'
IE_DESC = 'インターネットラジオステーション<音泉>'
_BASE_URL = 'https://www.onsen.ag/'
_HEADERS = {'Referer': _BASE_URL}
_NETRC_MACHINE = 'onsen'
_VALID_URL = r'https?://(?:(?:share|www)\.)onsen\.ag/program/(?P<id>[\w-]+)'
_TESTS = [{
'url': 'https://share.onsen.ag/program/onsenking?p=90&c=MTA0NjI',
'info_dict': {
'id': '10462',
'ext': 'm4a',
'title': '第SP回',
'cast': ['下野紘', '佐藤元', '守屋亨香'],
'description': 'md5:083c1eddf198694cd3cc83f4d5c03863',
'display_id': 'MTA0NjI=',
'http_headers': {'Referer': 'https://www.onsen.ag/'},
'media_type': 'sound',
'section_start': 0,
'series': '音泉キング「下野紘」のラジオ きみはもちろん、<音泉>ファミリーだよね?',
'series_id': 'onsenking',
'tags': ['音泉キング', '音泉ジュニア'],
'thumbnail': r're:https://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+$',
'upload_date': '20220627',
'webpage_url': 'https://www.onsen.ag/program/onsenking?c=MTA0NjI=',
},
}, {
'url': 'https://share.onsen.ag/program/girls-band-cry-radio?p=370&c=MTgwMDE',
'info_dict': {
'id': '18001',
'ext': 'mp4',
'title': '第4回',
'cast': ['夕莉', '理名', '朱李', '凪都', '美怜'],
'description': 'md5:1d7f6a2f1f5a3e2a8ada4e9f652262dd',
'display_id': 'MTgwMDE=',
'http_headers': {'Referer': 'https://www.onsen.ag/'},
'media_type': 'movie',
'section_start': 0,
'series': 'TVアニメ『ガールズバンドクライ』WEBラジオ「ガールズバンドクライラジオにも全部ぶち込め。',
'series_id': 'girls-band-cry-radio',
'tags': ['ガールズバンドクライ', 'ガルクラ', 'ガルクラジオ'],
'thumbnail': r're:https://d3bzklg4lms4gh\.cloudfront\.net/program_info/image/default/production/.+$',
'upload_date': '20240425',
'webpage_url': 'https://www.onsen.ag/program/girls-band-cry-radio?c=MTgwMDE=',
},
'skip': 'Only available for premium supporters',
}, {
'url': 'https://www.onsen.ag/program/g-witch',
'info_dict': {
'id': 'g-witch',
'title': '機動戦士ガンダム 水星の魔女~アスティカシア高等専門学園 ラジオ委員会~',
},
'playlist_mincount': 7,
}]
def _perform_login(self, username, password):
signin = self._download_json(
f'{self._BASE_URL}web_api/signin', None, 'Logging in', headers={
'content-type': 'application/json; charset=UTF-8',
}, data=json.dumps({
'session': {
'email': username,
'password': password,
},
}).encode(), expected_status=401)
if signin.get('error'):
raise ExtractorError('Invalid username or password', expected=True)
def _get_info(self, program, program_id, metadata):
m3u8 = program['streaming_url']
rd = self._search_regex(rf'{program_id}(\d{{6}})', m3u8, 'release_date', default=None)
display_id = base64.b64encode(str(program['id']).encode()).decode()
return {
'display_id': display_id,
'formats': self._extract_m3u8_formats(m3u8, program_id, headers=self._HEADERS),
'http_headers': self._HEADERS,
'upload_date': f'20{rd}' if rd else None,
'webpage_url': f'{self._BASE_URL}program/{program_id}?c={display_id}',
**metadata,
**traverse_obj(program, {
'id': ('id', {str_or_none}),
'title': ('title', {strip_or_none}),
'media_type': ('media_type', {str}),
'thumbnail': ('poster_image_url', {lambda x: x.split('?')[0]}),
}),
'cast': metadata['cast'] + traverse_obj(program, ('guests', ..., 'name', {str})),
}
def _real_extract(self, url):
program_id = self._match_id(url)
qs = {k: v[0] for k, v in parse_qs(url).items() if v}
programs = self._download_json(
f'{self._BASE_URL}web_api/programs/{program_id}', program_id)
metadata = {
'cast': traverse_obj(programs, ('performers', ..., 'name', {str})),
'section_start': int(qs.get('t', 0)),
'series_id': program_id,
**traverse_obj(programs['program_info'], {
'description': ('description', {str}),
'series': ('title', {str}),
'tags': ('hashtag_list', {list}),
}),
}
if 'c' in qs:
p_id = base64.b64decode(qs['c'] + '=' * (-len(qs['c']) % 4)).decode()
program = traverse_obj(
programs, ('contents', lambda _, v: v['id'] == int(p_id)), get_all=False)
if not program:
raise ExtractorError('This program is no longer available', expected=True)
if not program['streaming_url']:
self.raise_login_required('This program is only available for premium supporters')
return self._get_info(program, program_id, metadata)
else:
entries = [
self._get_info(program, program_id, metadata)
for program in programs['contents'] if program['streaming_url']
]
return self.playlist_result(entries, program_id, metadata['series'])