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

Code before:
        file = headline['file_list'][0]
        # this will break if there's an episode with multiple files, but i don't think that's ever actually the case

        info = {}

        url = file['file_name']
        info['id'] = programme_id + '_' + headline['headline_id']
        info['formats'] = self._extract_m3u8_formats(url, info['id'])
        info['title'] = file.get('file_title')
        info['description'] = file.get('file_title_sub')
        # info['webpage_url'] = file.get('share_url')
        info['thumbnail'] = headline.get('headline_image')
        info['container'] = 'm4a_dash'  # force fixup so seeking works
        info['was_live'] = True  # it's radio catch-up

        aa_vinfo4 = file.get('aa_vinfo4')
        # have to use whatever this is to get an actual air date
        # there is an onair_date var, but it's natural language that doesnt have half of what we need anyway
        info['release_timestamp'] = self._decode_time(aa_vinfo4.split('_')[0])
        # open_time is when it was put on vod
        info['timestamp'] = self._decode_time(file.get('open_time'))

        return info

    def _real_extract(self, url):
        video_id, site_id, corner_id, headline_id = self._match_valid_url(url).groups()

        programme_id = f'{site_id}_{corner_id}'
        json_url = f'https://www.nhk.or.jp/radioondemand/json/{site_id}/bangumi_{programme_id}.json'
        meta = self._download_json(json_url, programme_id)['main']

        series = meta.get('program_name')
        channel = meta.get('media_name')
        thumbnail = meta.get('thumbnail_c') or meta.get('thumbnail_p')

        detail_list = meta.get('detail_list')

        result = None

        # if it's 0123_45_6789 - a specific episode (as opposed to 0123_45 - the whole programme)
        if headline_id is not None:
            # we are downloading a single episode
            headline = (next((i for i in detail_list if i.get('headline_id') == headline_id), None))
            result = self._extract_episode_info(headline, programme_id)
            result['series'] = series
            result['channel'] = channel
            result['thumbnail'] = result.get('thumbnail') or thumbnail
        else:
            # we are downloading a whole playlist/show
            series_description = meta.get('site_detail')
            # series_url = meta.get('share_url')

Review comment (suggestion):
see `_VALID_URL` comment about `id`/`video_id` and `programme_id`
```suggestion
        site_id, corner_id, headline_id = self._match_valid_url(url).group('site', 'corner', 'headline')
```

Code after:
    def _extract_episode_info(self, headline, programme_id, series_meta):
        episode_id = f'{programme_id}_{headline["headline_id"]}'
        episode = traverse_obj(headline, ('file_list', 0, {dict}))

        return {
            **series_meta,
            'id': episode_id,
            'formats': self._extract_m3u8_formats(episode.get('file_name'), episode_id, fatal=False),
            'container': 'm4a_dash',  # force fixup, AAC-only HLS
            'was_live': True,
            'series': series_meta.get('title'),
            'thumbnail': url_or_none(headline.get('headline_image')) or series_meta.get('thumbnail'),
            **traverse_obj(episode, {
                'title': 'file_title',
                'description': 'file_title_sub',
                'timestamp': ('open_time', {unified_timestamp}),
                'release_timestamp': ('aa_vinfo4', {lambda x: x.split('_')[0]}, {unified_timestamp}),
            }),
        }

    def _real_extract(self, url):
        site_id, corner_id, headline_id = self._match_valid_url(url).group('site', 'corner', 'headline')
        programme_id = f'{site_id}_{corner_id}'

        if site_id == 'F261':
            json_url = 'https://www.nhk.or.jp/s-media/news/news-site/list/v1/all.json'
        else:
            json_url = f'https://www.nhk.or.jp/radioondemand/json/{site_id}/bangumi_{programme_id}.json'

        meta = self._download_json(json_url, programme_id)['main']

        series_meta = traverse_obj(meta, {
            'title': 'program_name',
            'channel': 'media_name',
            'thumbnail': (('thumbnail_c', 'thumbnail_p'), {url_or_none}),
        }, get_all=False)

        if headline_id:
            return self._extract_episode_info(
                traverse_obj(meta, (
                    'detail_list', lambda _, v: v['headline_id'] == headline_id), get_all=False),
                programme_id, series_meta)

        def entries():
            for headline in traverse_obj(meta, ('detail_list', ..., {dict})):
                yield self._extract_episode_info(headline, programme_id, series_meta)

        return self.playlist_result(
            entries(), programme_id, playlist_description=meta.get('site_detail'), **series_meta)