Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/crunchyroll.py (Python)

Code before:
        hardsub_preference = qualities(requested_hardsubs[::-1])
        formats, subtitles = [], {}
        for format_id, hardsub_lang, stream_url in available_formats.values():
            if hardsub_lang.lower() in full_format_langs:
                adaptive_formats, subtitles = self._extract_mpd_formats_and_subtitles(
                    stream_url, display_id, mpd_id=format_id, headers=CrunchyrollBaseIE._AUTH_HEADERS,
                    fatal=False, note=f'Downloading {f"{format_id} " if hardsub_lang else ""}MPD manifest')
            else:
                adaptive_formats = [{
                    'format_id': format_id,
                    'url': stream_url,
                    'ext': None,
                    'protocol': 'http_dash_segments',
                    'preference': -100,
                    'quality': None,
                    'resolution': 'multiple',
                    'format_note': 'Quality selection URL'
                }]
            for f in adaptive_formats:
                if f.get('acodec') != 'none':
                    f['language'] = audio_locale
                f['quality'] = hardsub_preference(hardsub_lang.lower())
            formats.extend(adaptive_formats)

        for locale, caption in traverse_obj(stream_response, (('subtitles', 'captions'), {dict.items}, ...)):
            subtitles.setdefault(locale, []).append(traverse_obj(subtitle, {'url': 'url', 'ext': 'format'}))

        return formats, subtitles


class CrunchyrollCmsBaseIE(CrunchyrollBaseIE):
    _API_ENDPOINT = 'cms'
    _CMS_EXPIRY = None

    def _call_cms_api_signed(self, path, internal_id, lang, note='api'):
        if not CrunchyrollCmsBaseIE._CMS_EXPIRY or CrunchyrollCmsBaseIE._CMS_EXPIRY <= time_seconds():
            response = self._call_base_api('index/v2', None, lang, 'Retrieving signed policy')['cms_web']
            CrunchyrollCmsBaseIE._CMS_QUERY = {
                'Policy': response['policy'],
                'Signature': response['signature'],
                'Key-Pair-Id': response['key_pair_id'],
            }
            CrunchyrollCmsBaseIE._CMS_BUCKET = response['bucket']
            CrunchyrollCmsBaseIE._CMS_EXPIRY = parse_iso8601(response['expires']) - 10

        if not path.startswith('/cms/v2'):
            path = f'/cms/v2{CrunchyrollCmsBaseIE._CMS_BUCKET}/{path}'

        return self._call_base_api(
            path, internal_id, lang, f'Downloading {note} JSON (signed cms)', query=CrunchyrollCmsBaseIE._CMS_QUERY)


Review comment (suggestion):
```suggestion
        for locale, subtitle in traverse_obj(stream_response, (('subtitles', 'captions'), {dict.items}, ...)):
            subtitles.setdefault(locale, []).append(traverse_obj(subtitle, {'url': 'url', 'ext': 'format'}))
```

Code after:

        audio_locale = traverse_obj(stream_response, ('audioLocale', {str}))
        hardsub_preference = qualities(requested_hardsubs[::-1])
        formats, subtitles = [], {}
        for format_id, hardsub_lang, stream_url in available_formats.values():
            if hardsub_lang.lower() in full_format_langs:
                adaptive_formats, dash_subs = self._extract_mpd_formats_and_subtitles(
                    stream_url, display_id, mpd_id=format_id, headers=CrunchyrollBaseIE._AUTH_HEADERS,
                    fatal=False, note=f'Downloading {f"{format_id} " if hardsub_lang else ""}MPD manifest')
                self._merge_subtitles(dash_subs, target=subtitles)
            else:
                continue  # XXX: Update this if/when meta mpd formats are working
            for f in adaptive_formats:
                if f.get('acodec') != 'none':
                    f['language'] = audio_locale
                f['quality'] = hardsub_preference(hardsub_lang.lower())
            formats.extend(adaptive_formats)

        for locale, subtitle in traverse_obj(stream_response, (('subtitles', 'captions'), {dict.items}, ...)):
            subtitles.setdefault(locale, []).append(traverse_obj(subtitle, {'url': 'url', 'ext': 'format'}))

        return formats, subtitles


class CrunchyrollCmsBaseIE(CrunchyrollBaseIE):
    _API_ENDPOINT = 'cms'
    _CMS_EXPIRY = None

    def _call_cms_api_signed(self, path, internal_id, lang, note='api'):
        if not CrunchyrollCmsBaseIE._CMS_EXPIRY or CrunchyrollCmsBaseIE._CMS_EXPIRY <= time_seconds():
            response = self._call_base_api('index/v2', None, lang, 'Retrieving signed policy')['cms_web']
            CrunchyrollCmsBaseIE._CMS_QUERY = {
                'Policy': response['policy'],
                'Signature': response['signature'],
                'Key-Pair-Id': response['key_pair_id'],
            }
            CrunchyrollCmsBaseIE._CMS_BUCKET = response['bucket']
            CrunchyrollCmsBaseIE._CMS_EXPIRY = parse_iso8601(response['expires']) - 10

        if not path.startswith('/cms/v2'):
            path = f'/cms/v2{CrunchyrollCmsBaseIE._CMS_BUCKET}/{path}'

        return self._call_base_api(
            path, internal_id, lang, f'Downloading {note} JSON (signed cms)', query=CrunchyrollCmsBaseIE._CMS_QUERY)


class CrunchyrollBetaIE(CrunchyrollCmsBaseIE):
    IE_NAME = 'crunchyroll'
    _VALID_URL = r'''(?x)
        https?://(?:beta\.|www\.)?crunchyroll\.com/
        (?:(?P<lang>\w{2}(?:-\w{2})?)/)?