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

Code before:
            })

            time_to_skip = None if is_onair else cursor - ft

            domain = urllib.parse.urlparse(playlist_url).netloc
            subformats = self._extract_m3u8_formats(
                playlist_url, video_id, ext='m4a',
                live=True, fatal=False, m3u8_id=domain,
                note=f'Downloading m3u8 information from {domain}',
                headers={
                    'X-Radiko-AreaId': area_id,
                    'X-Radiko-AuthToken': auth_token,
                })
            for sf in subformats:
                if (is_onair ^ pcu.startswith(self._HOSTS_FOR_LIVE)) or (
                        not is_onair and pcu.startswith(self._HOSTS_FOR_TIME_FREE_FFMPEG_UNSUPPORTED)):
                    sf['preference'] = -100
                    sf['format_note'] = 'not preferred'
                if not is_onair and timefree_int == 1 and time_to_skip:
                    sf['downloader_options'] = {'ffmpeg_args': ['-ss', str(time_to_skip)]}
            formats.extend(subformats)

        return formats

    def _extract_performers(self, prog):
        return [x.strip() for x in try_call(lambda: re.split(self._PERFORMER_SPLIT_RE, prog.find('pfm').text))]


class RadikoIE(RadikoBaseIE):
    _VALID_URL = r'https?://(?:www\.)?radiko\.jp/#!/ts/(?P<station>[A-Z0-9-]+)/(?P<id>\d+)'

    _TESTS = [{
        # QRR (文化放送) station provides <desc>
        'url': 'https://radiko.jp/#!/ts/QRR/20210425101300',
        'only_matching': True,
    }, {
        # FMT (TOKYO FM) station does not provide <desc>
        'url': 'https://radiko.jp/#!/ts/FMT/20210810150000',
        'only_matching': True,
    }, {
        'url': 'https://radiko.jp/#!/ts/JOAK-FM/20210509090000',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        station, video_id = self._match_valid_url(url).groups()
        vid_int = unified_timestamp(video_id, False)
        prog, station_program, ft, radio_begin, radio_end = self._find_program(video_id, station, vid_int)

        auth_token, area_id = self._auth_client()


Review comment (bug):
This is still fatal (`try_call` returns `None` when it fails, which is not iterable), and the `artists` PR has not been merged yet, so we'll still need a string for now:
```suggestion
    def _extract_performers(self, prog):
        performers = traverse_obj(prog, (
            'pfm/text()', ..., {lambda x: re.split(r'[/／、　,，]', x)}, ..., {str.strip}))
        # TODO: change 'artist' fields to 'artists' and return traversal list instead of str
        return ', '.join(performers) or None
```

Code after:

            time_to_skip = None if is_onair else cursor - ft

            domain = urllib.parse.urlparse(playlist_url).netloc
            subformats = self._extract_m3u8_formats(
                playlist_url, video_id, ext='m4a',
                live=True, fatal=False, m3u8_id=domain,
                note=f'Downloading m3u8 information from {domain}',
                headers={
                    'X-Radiko-AreaId': area_id,
                    'X-Radiko-AuthToken': auth_token,
                })
            for sf in subformats:
                if (is_onair ^ pcu.startswith(self._HOSTS_FOR_LIVE)) or (
                        not is_onair and pcu.startswith(self._HOSTS_FOR_TIME_FREE_FFMPEG_UNSUPPORTED)):
                    sf['preference'] = -100
                    sf['format_note'] = 'not preferred'
                if not is_onair and timefree_int == 1 and time_to_skip:
                    sf['downloader_options'] = {'ffmpeg_args': ['-ss', str(time_to_skip)]}
            formats.extend(subformats)

        return formats

    def _extract_performers(self, prog):
        performers = traverse_obj(prog, (
            'pfm/text()', ..., {lambda x: re.split(r'[/／、　,，]', x)}, ..., {str.strip}))
        # TODO: change 'artist' fields to 'artists' and return traversal list instead of str
        return ', '.join(performers) or None


class RadikoIE(RadikoBaseIE):
    _VALID_URL = r'https?://(?:www\.)?radiko\.jp/#!/ts/(?P<station>[A-Z0-9-]+)/(?P<id>\d+)'

    _TESTS = [{
        # QRR (文化放送) station provides <desc>
        'url': 'https://radiko.jp/#!/ts/QRR/20210425101300',
        'only_matching': True,
    }, {
        # FMT (TOKYO FM) station does not provide <desc>
        'url': 'https://radiko.jp/#!/ts/FMT/20210810150000',
        'only_matching': True,
    }, {
        'url': 'https://radiko.jp/#!/ts/JOAK-FM/20210509090000',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        station, video_id = self._match_valid_url(url).groups()
        vid_int = unified_timestamp(video_id, False)
        prog, station_program, ft, radio_begin, radio_end = self._find_program(video_id, station, vid_int)