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

Code before:
    }, {
        # radio 2, area specified
        # (the area doesnt actually matter, r2 is national)
        'url': 'https://www.nhk.or.jp/radio/player/?ch=r2',
        'params': {'extractor_args': {'nhkradirulive': {'area': ['fukuoka']}}},
        'info_dict': {
            'id': 'r2-fukuoka',
            'title': 're:^ＮＨＫネットラジオ第2 福岡.+$',
            'ext': 'm4a',
            'thumbnail': 'https://www.nhk.or.jp/common/img/media/r2-200x200.png',
            'live_status': 'is_live',
        },
    }, {
        # fm, area specified
        'url': 'https://www.nhk.or.jp/radio/player/?ch=fm',
        'params': {'extractor_args': {'nhkradirulive': {'area': ['sapporo']}}},
        'info_dict': {
            'id': 'fm-sapporo',
            'title': 're:^ＮＨＫネットラジオＦＭ 札幌.+$',
            'ext': 'm4a',
            'thumbnail': 'https://www.nhk.or.jp/common/img/media/fm-200x200.png',
            'live_status': 'is_live',
        }
    }]

    _NOA_STATION_IDS = {'r1': 'n1', 'r2': 'n2', 'fm': 'n3'}  # lookup table is easier than working it out

    def _real_extract(self, url):
        station = self._match_id(url)
        area = self._configuration_arg('area', ['tokyo'])[0]

        config = self._download_xml(
            'https://www.nhk.or.jp/radio/config/config_web.xml', station, 'Downloading area information')
        data = config.find(f'.//data//area[.="{area}"]/..')

        if not data:
            raise ExtractorError('Invalid area. Valid areas are: %s' % ', '.join(
                [i.text for i in config.findall('.//data//area')]), expected=True)

        noa_info = self._download_json(
            f'https:{config.find(".//url_program_noa").text}'.format(area=data.find('areakey').text),
            station, note=f'Downloading {area} station metadata')
        present_info = traverse_obj(noa_info, ('nowonair_list', self._NOA_STATION_IDS.get(station), 'present'))

        return {
            'title': ' '.join(traverse_obj(present_info, (('service', 'area',), 'name', {str}))),
            'id': join_nonempty(station, area),
            'thumbnails': traverse_obj(present_info, ('service', 'images', ..., {
                'url': 'url',
                'width': ('width', {int_or_none}),
                'height': ('height', {int_or_none}),

Review comment (suggestion):
agreed re: comment, but we can cleanup
```suggestion
    _NOA_STATION_IDS = {'r1': 'n1', 'r2': 'n2', 'fm': 'n3'}
```

Code after:
    }, {
        # radio 2, area specified
        # (the area doesnt actually matter, r2 is national)
        'url': 'https://www.nhk.or.jp/radio/player/?ch=r2',
        'params': {'extractor_args': {'nhkradirulive': {'area': ['fukuoka']}}},
        'info_dict': {
            'id': 'r2-fukuoka',
            'title': 're:^ＮＨＫネットラジオ第2 福岡.+$',
            'ext': 'm4a',
            'thumbnail': 'https://www.nhk.or.jp/common/img/media/r2-200x200.png',
            'live_status': 'is_live',
        },
    }, {
        # fm, area specified
        'url': 'https://www.nhk.or.jp/radio/player/?ch=fm',
        'params': {'extractor_args': {'nhkradirulive': {'area': ['sapporo']}}},
        'info_dict': {
            'id': 'fm-sapporo',
            'title': 're:^ＮＨＫネットラジオＦＭ 札幌.+$',
            'ext': 'm4a',
            'thumbnail': 'https://www.nhk.or.jp/common/img/media/fm-200x200.png',
            'live_status': 'is_live',
        }
    }]

    _NOA_STATION_IDS = {'r1': 'n1', 'r2': 'n2', 'fm': 'n3'}

    def _real_extract(self, url):
        station = self._match_id(url)
        area = self._configuration_arg('area', ['tokyo'])[0]

        config = self._download_xml(
            'https://www.nhk.or.jp/radio/config/config_web.xml', station, 'Downloading area information')
        data = config.find(f'.//data//area[.="{area}"]/..')

        if not data:
            raise ExtractorError('Invalid area. Valid areas are: %s' % ', '.join(
                [i.text for i in config.findall('.//data//area')]), expected=True)

        noa_info = self._download_json(
            f'https:{config.find(".//url_program_noa").text}'.format(area=data.find('areakey').text),
            station, note=f'Downloading {area} station metadata')
        present_info = traverse_obj(noa_info, ('nowonair_list', self._NOA_STATION_IDS.get(station), 'present'))

        return {
            'title': ' '.join(traverse_obj(present_info, (('service', 'area',), 'name', {str}))),
            'id': join_nonempty(station, area),
            'thumbnails': traverse_obj(present_info, ('service', 'images', ..., {
                'url': 'url',
                'width': ('width', {int_or_none}),
                'height': ('height', {int_or_none}),