|
|
@ -2,6 +2,7 @@ import base64
|
|
|
|
import uuid
|
|
|
|
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
from .common import InfoExtractor
|
|
|
|
from .common import InfoExtractor
|
|
|
|
|
|
|
|
from ..networking import Request
|
|
|
|
from ..networking.exceptions import HTTPError
|
|
|
|
from ..networking.exceptions import HTTPError
|
|
|
|
from ..utils import (
|
|
|
|
from ..utils import (
|
|
|
|
ExtractorError,
|
|
|
|
ExtractorError,
|
|
|
@ -24,6 +25,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|
|
|
_BASE_URL = 'https://www.crunchyroll.com'
|
|
|
|
_BASE_URL = 'https://www.crunchyroll.com'
|
|
|
|
_API_BASE = 'https://api.crunchyroll.com'
|
|
|
|
_API_BASE = 'https://api.crunchyroll.com'
|
|
|
|
_NETRC_MACHINE = 'crunchyroll'
|
|
|
|
_NETRC_MACHINE = 'crunchyroll'
|
|
|
|
|
|
|
|
_SWITCH_USER_AGENT = 'Crunchyroll/1.8.0 Nintendo Switch/12.3.12.0 UE4/4.27'
|
|
|
|
_REFRESH_TOKEN = None
|
|
|
|
_REFRESH_TOKEN = None
|
|
|
|
_AUTH_HEADERS = None
|
|
|
|
_AUTH_HEADERS = None
|
|
|
|
_AUTH_EXPIRY = None
|
|
|
|
_AUTH_EXPIRY = None
|
|
|
@ -179,10 +181,19 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|
|
|
display_id = identifier
|
|
|
|
display_id = identifier
|
|
|
|
|
|
|
|
|
|
|
|
self._update_auth()
|
|
|
|
self._update_auth()
|
|
|
|
stream_response = self._download_json(
|
|
|
|
headers = {**CrunchyrollBaseIE._AUTH_HEADERS, 'User-Agent': self._SWITCH_USER_AGENT}
|
|
|
|
f'https://cr-play-service.prd.crunchyrollsvc.com/v1/{identifier}/console/switch/play',
|
|
|
|
try:
|
|
|
|
display_id, note='Downloading stream info', errnote='Failed to download stream info',
|
|
|
|
stream_response = self._download_json(
|
|
|
|
headers=CrunchyrollBaseIE._AUTH_HEADERS)
|
|
|
|
f'https://cr-play-service.prd.crunchyrollsvc.com/v1/{identifier}/console/switch/play',
|
|
|
|
|
|
|
|
display_id, note='Downloading stream info', errnote='Failed to download stream info', headers=headers)
|
|
|
|
|
|
|
|
except ExtractorError as error:
|
|
|
|
|
|
|
|
if self.get_param('ignore_no_formats_error'):
|
|
|
|
|
|
|
|
self.report_warning(error.orig_msg)
|
|
|
|
|
|
|
|
return [], {}
|
|
|
|
|
|
|
|
elif isinstance(error.cause, HTTPError) and error.cause.status == 420:
|
|
|
|
|
|
|
|
raise ExtractorError(
|
|
|
|
|
|
|
|
'You have reached the rate-limit for active streams; try again later', expected=True)
|
|
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
available_formats = {'': ('', '', stream_response['url'])}
|
|
|
|
available_formats = {'': ('', '', stream_response['url'])}
|
|
|
|
for hardsub_lang, stream in traverse_obj(stream_response, ('hardSubs', {dict.items}, lambda _, v: v[1]['url'])):
|
|
|
|
for hardsub_lang, stream in traverse_obj(stream_response, ('hardSubs', {dict.items}, lambda _, v: v[1]['url'])):
|
|
|
@ -211,7 +222,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|
|
|
fatal=False, note=f'Downloading {f"{format_id} " if hardsub_lang else ""}MPD manifest')
|
|
|
|
fatal=False, note=f'Downloading {f"{format_id} " if hardsub_lang else ""}MPD manifest')
|
|
|
|
self._merge_subtitles(dash_subs, target=subtitles)
|
|
|
|
self._merge_subtitles(dash_subs, target=subtitles)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
continue # XXX: Update this if/when meta mpd formats are working
|
|
|
|
continue # XXX: Update this if meta mpd formats work; will be tricky with token invalidation
|
|
|
|
for f in adaptive_formats:
|
|
|
|
for f in adaptive_formats:
|
|
|
|
if f.get('acodec') != 'none':
|
|
|
|
if f.get('acodec') != 'none':
|
|
|
|
f['language'] = audio_locale
|
|
|
|
f['language'] = audio_locale
|
|
|
@ -221,6 +232,15 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|
|
|
for locale, subtitle in traverse_obj(stream_response, (('subtitles', 'captions'), {dict.items}, ...)):
|
|
|
|
for locale, subtitle in traverse_obj(stream_response, (('subtitles', 'captions'), {dict.items}, ...)):
|
|
|
|
subtitles.setdefault(locale, []).append(traverse_obj(subtitle, {'url': 'url', 'ext': 'format'}))
|
|
|
|
subtitles.setdefault(locale, []).append(traverse_obj(subtitle, {'url': 'url', 'ext': 'format'}))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Invalidate stream token to avoid rate-limit
|
|
|
|
|
|
|
|
error_msg = 'Unable to invalidate stream token; you may experience rate-limiting'
|
|
|
|
|
|
|
|
if stream_token := stream_response.get('token'):
|
|
|
|
|
|
|
|
self._request_webpage(Request(
|
|
|
|
|
|
|
|
f'https://cr-play-service.prd.crunchyrollsvc.com/v1/token/{identifier}/{stream_token}/inactive',
|
|
|
|
|
|
|
|
headers=headers, method='PATCH'), display_id, 'Invalidating stream token', error_msg, fatal=False)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self.report_warning(error_msg)
|
|
|
|
|
|
|
|
|
|
|
|
return formats, subtitles
|
|
|
|
return formats, subtitles
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|