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

Code before:
            'description': 'md5:5579d1a0355cc618558ba23d27067a62',
            'uploader': 'TBS',
            'episode': 'Wicked Lord Shingan... Reborn',
            'average_rating': 4.9,
            'season': 'Love, Chunibyo & Other Delusions - Heart Throb -',
            'thumbnail': 'https://img1.ak.crunchyroll.com/i/spire3-tmb/2ba0384e225a5370d5f0ee9496d91ea51389046521_main.jpg',
            'title': 'Love, Chunibyo & Other Delusions - Heart Throb - Episode 1 – Wicked Lord Shingan... Reborn',
            'season_number': 2,
            'upload_date': '20140108',
        },
        'params': {'skip_download': 'm3u8'},
        'expected_warnings': ['Unable to download XML']
    }, {
        'url': 'https://beta.crunchyroll.com/watch/GY2P1Q98Y/',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        lang, internal_id, display_id = self._match_valid_url(url).group('lang', 'id', 'display_id')

        if not self._get_cookies(url).get('etp_rt'):
            initial_state, app_config = self._get_beta_embedded_json(self._download_webpage(url, display_id), display_id)
            episode_data = initial_state['content']['byId'][internal_id]
            video_id = episode_data['external_id'].split('.')[1]
            series_id = re.sub(r'-{2,}', '-', episode_data['episode_metadata']['series_slug_title'])
            self.to_screen(f'{display_id}: Not logged in. Switching extractors. New url: https://www.crunchyroll.com/{lang}{series_id}/{display_id}-{video_id}')
            return self.url_result(f'https://www.crunchyroll.com/{lang}{series_id}/{display_id}-{video_id}',
                                   CrunchyrollIE.ie_key(), video_id)

        api_domain, bucket, params = self._get_params(lang)

        episode_response = self._download_json(
            f'{api_domain}/cms/v2{bucket}/episodes/{internal_id}', display_id,
            note='Retrieving episode metadata',
            query=params)
        if episode_response.get('is_premium_only') and not episode_response.get('playback'):
            raise ExtractorError('This video is for premium members only.', expected=True)
        stream_response = self._download_json(
            episode_response['playback'], display_id,
            note='Retrieving stream info')

        thumbnails = []
        for thumbnails_data in traverse_obj(episode_response, ('images', 'thumbnail')):
            for thumbnail_data in thumbnails_data:
                thumbnails.append({
                    'url': thumbnail_data.get('source'),
                    'width': thumbnail_data.get('width'),
                    'height': thumbnail_data.get('height'),
                })
        subtitles = {}
        for lang, subtitle_data in stream_response.get('subtitles').items():

Review comment (suggestion):
```suggestion
            self.to_screen(f'{display_id}: Not logged in. Redirecting to https://www.crunchyroll.com/{lang}{series_id}/{display_id}-{video_id}')
```

Code after:
            'id': '648781',
            'ext': 'mp4',
            'episode_number': 1,
            'timestamp': 1389173400,
            'series': 'Love, Chunibyo & Other Delusions - Heart Throb -',
            'description': 'md5:5579d1a0355cc618558ba23d27067a62',
            'uploader': 'TBS',
            'episode': 'Wicked Lord Shingan... Reborn',
            'average_rating': 4.9,
            'season': 'Love, Chunibyo & Other Delusions - Heart Throb -',
            'thumbnail': 'https://img1.ak.crunchyroll.com/i/spire3-tmb/2ba0384e225a5370d5f0ee9496d91ea51389046521_main.jpg',
            'title': 'Love, Chunibyo & Other Delusions - Heart Throb - Episode 1 – Wicked Lord Shingan... Reborn',
            'season_number': 2,
            'upload_date': '20140108',
        },
        'params': {'skip_download': 'm3u8'},
        'expected_warnings': ['Unable to download XML']
    }, {
        'url': 'https://beta.crunchyroll.com/watch/GY2P1Q98Y/',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        lang, internal_id, display_id = self._match_valid_url(url).group('lang', 'id', 'display_id')

        if not self._get_cookies(url).get('etp_rt'):
            return self._redirect_from_beta(url, lang, internal_id, display_id, True, CrunchyrollIE.ie_key())

        api_domain, bucket, params = self._get_params(lang)

        episode_response = self._download_json(
            f'{api_domain}/cms/v2{bucket}/episodes/{internal_id}', display_id,
            note='Retrieving episode metadata',
            query=params)
        if episode_response.get('is_premium_only') and not episode_response.get('playback'):
            raise ExtractorError('This video is for premium members only.', expected=True)
        stream_response = self._download_json(
            episode_response['playback'], display_id,
            note='Retrieving stream info')

        thumbnails = []
        for thumbnails_data in traverse_obj(episode_response, ('images', 'thumbnail')):
            for thumbnail_data in thumbnails_data:
                thumbnails.append({
                    'url': thumbnail_data.get('source'),
                    'width': thumbnail_data.get('width'),
                    'height': thumbnail_data.get('height'),
                })
        subtitles = {}
        for lang, subtitle_data in stream_response.get('subtitles').items():
            subtitles[lang] = [{