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

Code before:

        thumbnail = traverse_obj(entry, ('attributes', 'asset', 'url'))
        # these can be pretty large, e.g. https://api.mujrozhlas.cz/episodes/8ad147a0-d971-3fb2-9e6b-552a60f335fb
        # with a 2100x1400 jpeg

        timestamp = unified_timestamp(traverse_obj(entry, ('attributes', 'since')))
        modified_timestamp = unified_timestamp(traverse_obj(entry, ('attributes', 'updated')))

        formats = self._extract_audio(entry['attributes'], audio_id)

        return {
            'id': audio_id,
            'title': title,
            'formats': formats,
            'timestamp': timestamp,
            'thumbnail': thumbnail,
            'modified_timestamp': modified_timestamp,
            **traverse_obj(entry, {
                'description': ('attributes', 'description'),
                'episode_number': ('attributes', 'part'),
                'series': ('attributes', 'mirroredShow', 'title'),
                'chapter': ('attributes', 'mirroredSerial', 'title'),
                'artist': ('meta', 'ga', 'contentAuthor'),
                'channel_id': ('meta', 'ga', 'contentCreator'),
            })
        }

    def _extract_video(self, uuid):
        url = f'{self._API_ROOT}/episodes/{uuid}'
        entry = self._download_json(
            url, uuid, note='Getting episode info from API',
            errnote='Getting episode info failed'
        )['data']

        return self._extract_audio_entry(entry)

    def _get_episodes(self, base_url, uuid):
        # extract info
        base_json = self._download_json(base_url, uuid,
                                        note='Downloading playlist base json',
                                        errnote='Downloading playlist base json failed')
        id = uuid
        title = base_json['data']['attributes']['title']

        episodes = []
        episodes_url = base_json['data']['relationships']['episodes']['links']['related']
        while (True):
            episodes_json = self._download_json(episodes_url, id,
                                                note='Downloading episode list',
                                                errnote='Downloading episode list failed')
            episodes_data = episodes_json['data']

Review comment (security):
```suggestion
    def _extract_audio_entry(self, entry):
        audio_id = entry['meta']['ga']['contentId']

        return {
            'id': audio_id,
            'formats': self._extract_formats(entry['attributes'], audio_id),
            **traverse_obj(entry, {
                'title': ('attributes', 'title'),
                'description': ('attributes', 'description'),
                'episode_number': ('attributes', 'part'),
                'series': ('attributes', 'mirroredShow', 'title'),
                'chapter': ('attributes', 'mirroredSerial', 'title'),
                'artist': ('meta', 'ga', 'contentAuthor'),
                'channel_id': ('meta', 'ga', 'contentCreator'),
                'timestamp': ('attributes', 'since', {unified_timestamp}),
                'modified_timestamp': ('attributes', 'updated', {unified_timestamp}),
                'thumbnail': ('attributes', 'asset', 'url', {url_or_none}),
            })
        }
```

Code after:

    def _extract_audio_entry(self, entry):
        audio_id = entry['meta']['ga']['contentId']

        return {
            'id': audio_id,
            'formats': self._extract_formats(entry['attributes'], audio_id),
            **traverse_obj(entry, {
                'title': ('attributes', 'title'),
                'description': ('attributes', 'description'),
                'episode_number': ('attributes', 'part'),
                'series': ('attributes', 'mirroredShow', 'title'),
                'chapter': ('attributes', 'mirroredSerial', 'title'),
                'artist': ('meta', 'ga', 'contentAuthor'),
                'channel_id': ('meta', 'ga', 'contentCreator'),
                'timestamp': ('attributes', 'since', {unified_timestamp}),
                'modified_timestamp': ('attributes', 'updated', {unified_timestamp}),
                'thumbnail': ('attributes', 'asset', 'url', {url_or_none}),
            })
        }

    def _entries(self, api_url, playlist_id):
        for page in itertools.count(1):
            episodes = self._download_json(
                api_url, playlist_id, note=f'Downloading episodes page {page}',
                errnote=f'Failed to download episodes page {page}', fatal=False)
            for episode in traverse_obj(episodes, ('data', lambda _, v: v['meta']['ga']['contentId'])):
                yield self._extract_audio_entry(episode)
            api_url = traverse_obj(episodes, ('links', 'next', {url_or_none}))
            if not api_url:
                break

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        info = self._search_json(r'\bvar\s+dl\s*=', webpage, 'info json', display_id)

        entity = info['siteEntityBundle']

        if entity == 'episode':
            return self._extract_audio_entry(self._call_api(
                'episodes', info['contentId'], 'episode info API JSON'))

        elif entity in ('show', 'serial'):
            playlist_id = info['contentShow'].split(':')[0] if entity == 'show' else info['contentId']
            data = self._call_api(f'{entity}s', playlist_id, f'{entity} playlist JSON')
            api_url = data['relationships']['episodes']['links']['related']
            return self.playlist_result(
                self._entries(api_url, playlist_id), playlist_id,
                **traverse_obj(data, ('attributes', {
                    'title': 'title',