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

Code before:
            webpage = self._download_webpage(url, station_id)

            api_response = self._parse_json(self._search_json(
                r'<script[^>]+type\s*=\s*"application/json"[^>]+/webradios/[^>]+>',
                webpage, 'substation_data', station_id)['body'], station_id)
        else:
            api_response = self._download_json(
                f'https://www.radiofrance.fr/api/v2.1/stations/{station_id}/live', station_id)

        formats, subtitles = [], {}
        for media_source in traverse_obj(api_response, ('now', 'media', 'sources'), ('media', 'sources')):
            if media_source.get('format') == 'hls':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(media_source['url'], station_id, fatal=False)
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)
            else:
                formats.append({
                    'url': media_source['url'],
                    'abr': media_source.get('bitrate'),
                })

        return {
            'id': join_nonempty(station_id, substation_id, delim='-'),
            'title': traverse_obj(api_response, ('visual', 'legend')) or join_nonempty(
                traverse_obj(api_response, ('now', 'firstLine', 'title')),
                traverse_obj(api_response, ('now', 'secondLine', 'title')), delim=" - "),
            'formats': formats,
            'subtitles': subtitles,
            'live': True,
        }


class RadioFrancePlaylistBase(RadioFranceBase):
    """Subclasses must set _METADATA_KEY"""

    def _generate_playlist_entries(self, content_id, content_response):
        raise NotImplementedError('This method must be implemented by subclasses')

    def _real_extract(self, url):
        display_id = self._match_id(url)

        metadata = self._download_json(
            'https://www.radiofrance.fr/api/v2.1/path', display_id,
            query={'value': urllib.parse.urlparse(url).path})['content']

        content_id = metadata['id']

        return self.playlist_result(
            self._generate_playlist_entries(content_id, metadata[self._METADATA_KEY]), content_id,
            display_id=display_id, **{**traverse_obj(metadata, {
                'title': 'title',

Review comment (suggestion):
```suggestion
            'title': traverse_obj(api_response, ('visual', 'legend')) or join_nonempty(
                ('now', 'firstLine', 'title'), ('now', 'secondLine', 'title'),
                from_dict=api_response, delim=' - '),
```

Code after:
        'url': 'https://www.radiofrance.fr/mouv',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        station_id, substation_id = self._match_valid_url(url).group('id', 'substation_id')

        if substation_id:
            webpage = self._download_webpage(url, station_id)
            api_response = self._extract_data_from_webpage(webpage, station_id, 'webRadioData')
        else:
            api_response = self._download_json(
                f'https://www.radiofrance.fr/{station_id}/api/live', station_id)

        formats, subtitles = [], {}
        for media_source in traverse_obj(api_response, (('now', None), 'media', 'sources', lambda _, v: v['url'])):
            if media_source.get('format') == 'hls':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(media_source['url'], station_id, fatal=False)
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)
            else:
                formats.append({
                    'url': media_source['url'],
                    'abr': media_source.get('bitrate'),
                })

        return {
            'id': join_nonempty(station_id, substation_id),
            'title': traverse_obj(api_response, ('visual', 'legend')) or join_nonempty(
                ('now', 'firstLine', 'title'), ('now', 'secondLine', 'title'), from_dict=api_response, delim=' - '),
            'formats': formats,
            'subtitles': subtitles,
            'is_live': True,
        }


class RadioFrancePlaylistBase(RadioFranceBaseIE):
    """Subclasses must set _METADATA_KEY"""

    def _call_api(self, content_id, cursor, page_num):
        raise NotImplementedError('This method must be implemented by subclasses')

    def _generate_playlist_entries(self, content_id, content_response):
        for page_num in itertools.count(2):
            for entry in content_response['items']:
                yield self.url_result(
                    f'https://www.radiofrance.fr/{entry["path"]}', url_transparent=True, **traverse_obj(entry, {
                        'title': 'title',
                        'description': 'standFirst',
                        'timestamp': ('publishedDate', {int_or_none}),
                        'thumbnail': ('visual', 'src'),