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

Code before:
        return result

    def _extract_stream(self, internal_id, is_music):
        if is_music:
            object_type = 'music/'
        else:
            object_type = ''

        return self._download_json(
            f'https://cr-play-service.prd.crunchyrollsvc.com/v1/{object_type}{internal_id}/console/switch/play',
            internal_id, note='stream info', headers=CrunchyrollBaseIE._AUTH_HEADERS)

    def _extract_formats(self, stream_response, display_id=None):
        available_formats = {'': ('', '', stream_response['url'])}
        for hardsub_lang, stream in traverse_obj(stream_response, ('hardSubs', {dict.items}, ...)):
            available_formats[hardsub_lang] = (f'hardsub-{hardsub_lang}', hardsub_lang, stream['url'])

        requested_hardsubs = [('' if val == 'none' else val) for val in (self._configuration_arg('hardsub') or ['none'])]

        audio_locale = stream_response.get('audioLocale')
        hardsub_preference = qualities(requested_hardsubs[::-1])
        formats = []
        for format_id, hardsub_lang, stream_url in available_formats.values():
            adaptive_formats = self._extract_mpd_formats(
                stream_url, display_id, mpd_id=format_id, headers=CrunchyrollBaseIE._AUTH_HEADERS,
                fatal=False, note=f'Downloading {f"hardsub {hardsub_lang} " if hardsub_lang else ""}MPD manifest')
            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)

        return formats

    def _extract_subtitles(self, data):
        subtitles = {}

        for locale, subtitle in traverse_obj(data, ((None, 'meta'), 'subtitles', {dict.items}, ...)):
            subtitles[locale] = [traverse_obj(subtitle, {'url': 'url', 'ext': 'format'})]

        return 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 = {

Review comment (security):
This should employ the same lazy evaluation that hls had before. see [`_m3u8_meta_format` in `ie/common.py:`](<https://github.com/yt-dlp/yt-dlp/blob/master/yt_dlp/extractor/common.py#L1985-L1995>) for how to implement the dash version using `http_dash_segments`. For this the above removed block has to be restored.

I like showing the hardsub language, but I would like to also have the `format_id`. How about something like this for that:
```suggestion
            if hardsub_lang.lower() in full_format_langs:
                adaptive_formats = self._extract_mpd_formats(
                    stream_url, display_id, mpd_id=format_id, headers=CrunchyrollBaseIE._AUTH_HEADERS,
                    fatal=False, note=f'Downloading {format_id} {f" ({hardsub_lang})" if hardsub_lang else ""}MPD manifest')
            else:
                adaptive_formats = [{
                    ...
                }]
```

Code after:
        # if no skip events are available, a 403 xml error is returned
        skip_events = self._download_json(
            f'https://static.crunchyroll.com/skip-events/production/{internal_id}.json',
            internal_id, note='Downloading chapter info', fatal=False, errnote=False)
        if not skip_events:
            return None

        chapters = []
        for event in ('recap', 'intro', 'credits', 'preview'):
            start = traverse_obj(skip_events, (event, 'start', {float_or_none}))
            end = traverse_obj(skip_events, (event, 'end', {float_or_none}))
            # some chapters have no start and/or ending time, they will just be ignored
            if start is None or end is None:
                continue
            chapters.append({'title': event.capitalize(), 'start_time': start, 'end_time': end})

        return chapters

    def _extract_stream(self, identifier, display_id=None):
        if not display_id:
            display_id = identifier

        self._update_auth()
        stream_response = self._download_json(
            f'https://cr-play-service.prd.crunchyrollsvc.com/v1/{identifier}/console/switch/play',
            display_id, note='Downloading stream info', headers=CrunchyrollBaseIE._AUTH_HEADERS)

        available_formats = {'': ('', '', stream_response['url'])}
        for hardsub_lang, stream in traverse_obj(stream_response, ('hardSubs', {dict.items}, lambda _, v: v[1]['url'])):
            available_formats[hardsub_lang] = (f'hardsub-{hardsub_lang}', hardsub_lang, stream['url'])

        requested_hardsubs = [('' if val == 'none' else val) for val in (self._configuration_arg('hardsub') or ['none'])]
        hardsub_langs = [lang for lang in available_formats if lang]
        if hardsub_langs and 'all' not in requested_hardsubs:
            full_format_langs = set(requested_hardsubs)
            self.to_screen(f'Available hardsub languages: {", ".join(hardsub_langs)}')
            self.to_screen(
                'To extract formats of a hardsub language, use '
                '"--extractor-args crunchyrollbeta:hardsub=<language_code or all>". '
                'See https://github.com/yt-dlp/yt-dlp#crunchyrollbeta-crunchyroll for more info',
                only_once=True)
        else:
            full_format_langs = set(map(str.lower, available_formats))

        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,