aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/yt_dlp_plugins/extractor
diff options
context:
space:
mode:
Diffstat (limited to 'yt_dlp_plugins/extractor')
-rw-r--r--yt_dlp_plugins/extractor/radiko.py55
1 files changed, 48 insertions, 7 deletions
diff --git a/yt_dlp_plugins/extractor/radiko.py b/yt_dlp_plugins/extractor/radiko.py
index c6cea37..9c70a1b 100644
--- a/yt_dlp_plugins/extractor/radiko.py
+++ b/yt_dlp_plugins/extractor/radiko.py
@@ -7,12 +7,14 @@ import pkgutil
from yt_dlp.extractor.common import InfoExtractor
from yt_dlp.utils import (
+ ExtractorError,
OnDemandPagedList,
clean_html,
int_or_none,
join_nonempty,
parse_qs,
traverse_obj,
+ urlencode_postdata,
url_or_none,
update_url_query,
)
@@ -84,6 +86,8 @@ class _RadikoBaseIE(InfoExtractor):
_DELIVERED_ONDEMAND = ('radiko.jp',)
_DOESNT_WORK_WITH_FFMPEG = ('tf-f-rpaa-radiko.smartstream.ne.jp', 'si-f-radiko.smartstream.ne.jp')
+ _has_tf30 = None
+
def _index_regions(self):
region_data = {}
@@ -178,16 +182,21 @@ class _RadikoBaseIE(InfoExtractor):
"X-Radiko-AuthToken": auth_token,
},
"user": auth2_headers["X-Radiko-User"],
+ "has_tf30": self._has_tf30,
}
if not region_mismatch:
self.cache.store("rajiko", station_region, auth_data)
return auth_data
- def _auth(self, station_region):
+ def _auth(self, station_region, need_tf30=False):
cachedata = self.cache.load("rajiko", station_region)
self.write_debug(cachedata)
if cachedata is not None:
+ if need_tf30 and not cachedata.get("has_tf30"):
+ self.write_debug("Cached token doesn't have timefree 30, getting a new one")
+ return self._negotiate_token(station_region)
+
auth_headers = cachedata.get("token")
response = self._download_webpage("https://radiko.jp/v2/api/auth_check", station_region, "Checking cached token",
headers=auth_headers, expected_status=401)
@@ -229,8 +238,16 @@ class _RadikoBaseIE(InfoExtractor):
self.to_screen(f"{station_id}: Using cached station metadata")
return cachedata.get("meta")
- def _get_station_formats(self, station, timefree, auth_data, start_at=None, end_at=None):
- device = self._configuration_arg('device', ['aSmartPhone7a'], casesense=True, ie_key="rajiko")[0] # aSmartPhone7a formats = always happy path
+ def _get_station_formats(self, station, timefree, auth_data, start_at=None, end_at=None, tf30_override=False):
+ config_device = traverse_obj(self._configuration_arg('device', casesense=True, ie_key="rajiko"), 0)
+
+ if not tf30_override:
+ device = config_device or "aSmartPhone7a" # this device only gives us the on-demand one for timefree
+ # that's good imo - we just get the one that works, and don't bother with probing the rest as well
+ else:
+ device = config_device or "pc_html5" # the on-demand one doesnt work with timefree30 stuff sadly
+ # so just use pc_html5 which has everything
+
url_data = self._download_xml(f"https://radiko.jp/v3/station/stream/{device}/{station}.xml",
station, note=f"Downloading {device} stream information")
@@ -368,6 +385,7 @@ class RadikoLiveIE(_RadikoBaseIE):
class RadikoTimeFreeIE(_RadikoBaseIE):
+ _NETRC_MACHINE = "rajiko"
_VALID_URL = r"https?://(?:www\.)?radiko\.jp/#!/ts/(?P<station>[A-Z0-9-_]+)/(?P<id>\d+)"
_TESTS = [{
"url": "https://radiko.jp/#!/ts/INT/20240809230000",
@@ -430,6 +448,29 @@ class RadikoTimeFreeIE(_RadikoBaseIE):
},
}]
+ def _perform_login(self, username, password):
+ try:
+ login_info = self._download_json('https://radiko.jp/ap/member/webapi/member/login', None, note='Logging in',
+ data=urlencode_postdata({'mail': username, 'pass': password}))
+ self._has_tf30 = '2' in login_info.get('privileges')
+ # areafree = 1, timefree30 = 2, double plan = both
+ self.write_debug({**login_info, "radiko_session": "PRIVATE", "member_ukey": "PRIVATE"})
+ except ExtractorError as error:
+ if isinstance(error.cause, HTTPError) and error.cause.status == 401:
+ raise ExtractorError('Invalid username and/or password', expected=True)
+ raise
+
+ def _check_tf30(self):
+ if self._has_tf30 is not None:
+ return self._has_tf30
+ if self._get_cookies('https://radiko.jp').get('radiko_session') is None:
+ return
+ account_info = self._download_json('https://radiko.jp/ap/member/webapi/v2/member/login/check',
+ None, note='Checking account status from cookies', expected_status=400)
+ self.write_debug({**account_info, "user_key": "PRIVATE"})
+ self._has_tf30 = account_info.get('timefreeplus') == '1'
+ return self._has_tf30
+
def _get_programme_meta(self, station_id, url_time):
day = url_time.broadcast_day_string()
meta = self._download_json(f"https://radiko.jp/v4/program/station/date/{day}/{station_id}.json", station_id,
@@ -493,11 +534,11 @@ class RadikoTimeFreeIE(_RadikoBaseIE):
end = times[1]
now = datetime.datetime.now(tz=rtime.JST)
expiry_free, expiry_tf30 = end.expiry()
- have_tf30 = False
if expiry_tf30 < now:
self.raise_no_formats("Programme is no longer available.", video_id=meta["id"], expected=True)
- elif not have_tf30 and expiry_free < now:
+ need_tf30 = expiry_free < now
+ if need_tf30 and not self._check_tf30():
self.raise_login_required("Programme is only available with a Timefree 30 subscription")
elif start > now:
self.raise_no_formats("Programme has not aired yet.", video_id=meta["id"], expected=True)
@@ -509,8 +550,8 @@ class RadikoTimeFreeIE(_RadikoBaseIE):
region = self._get_station_region(station)
station_meta = self._get_station_meta(region, station)
chapters = self._extract_chapters(station, start, end, video_id=meta["id"])
- auth_data = self._auth(region)
- formats = self._get_station_formats(station, True, auth_data, start_at=start, end_at=end)
+ auth_data = self._auth(region, need_tf30=need_tf30)
+ formats = self._get_station_formats(station, True, auth_data, start_at=start, end_at=end, tf30_override=need_tf30)
return {
**station_meta,