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

Code before:
            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='stream info', headers=CrunchyrollBaseIE._AUTH_HEADERS)

        available_formats = {'': ('', '', stream_response['url'])}
        for hardsub_lang, stream in traverse_obj(stream_response, 'hardSubs', lambda _, v: v['url'], default={}).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'])]
        if '' in available_formats and 'all' not in requested_hardsubs:
            full_format_langs = set(requested_hardsubs)
            self.to_screen(
                'To get all 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 = []
        for format_id, hardsub_lang, stream_url in available_formats.values():
            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 {f"{format_id} " if hardsub_lang else ""}MPD manifest')
            else:
                adaptive_formats = [{
                    'format_id': format_id,
                    'url': stream_url,

Review comment (suggestion):
```suggestion
        for hardsub_lang, stream in traverse_obj(stream_response, ('hardSubs', {dict.items}, lambda _, v: v[1]['url'])):
```

Code after:
            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,
                    fatal=False, note=f'Downloading {f"{format_id} " if hardsub_lang else ""}MPD manifest')
                self._merge_subtitles(dash_subs, target=subtitles)
            else: