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

Code before:
            'id': '572515',
            'ext': 'mp4',
            'title': 'Clase inédita',
            'duration': 335.817,
            'thumbnail': r're:https://img2\.rtve\.es/v/.*\.png',
            'series': 'El coro de la cárcel',
        },
        'params': {
            'skip_download': True,
        },
    }

    def _real_extract(self, url):
        page_id = self._match_id(url)
        webpage = self._download_webpage(url, page_id)

        play_url = self._html_search_meta('contentUrl', webpage)
        if play_url is None:
            raise ExtractorError('The webpage doesn\'t contain any video', expected=True)

        return self.url_result(play_url, ie=RTVEALaCartaIE.ie_key())


class RTVEProgramIE(RTVEBaseIE):
    IE_NAME = 'rtve.es:program'
    IE_DESC = 'RTVE.es program'
    _VALID_URL = r'https?://(?:www\.)?rtve\.es/play/videos/(?P<id>[\w-]+)/?(?:[?#]|$)'
    _TESTS = [{
        'url': 'https://www.rtve.es/play/videos/saber-vivir/',
        'info_dict': {
            'id': '111570',
            'title': 'Saber vivir - Programa de ciencia y futuro en RTVE Play',
        },
        'playlist_mincount': 400,
    }]

    def _entries(self, program_id):
        total_page_count = 1
        current_page = 1
        while current_page <= total_page_count:
            videos_data = self._download_json(
                f'https://www.rtve.es/api/programas/{program_id}/videos',
                program_id, note=f'Downloading page {current_page}',
                query={
                    'type': 39816,
                    'page': current_page,
                    'size': 60,
                })
            current_page += 1
            total_page_count = traverse_obj(videos_data, ('page', 'totalPages', {int})) or 1
            for video in traverse_obj(videos_data, ('page', 'items', lambda _, v: url_or_none(v['htmlUrl']))):

Review comment (suggestion):
```suggestion
    IE_DESC = 'RTVE.es programs'
```

Code after:
        'url': 'https://www.rtve.es/television/20091103/video-inedito-del-8o-programa/299020.shtml',
        'info_dict': {
            'id': '572515',
            'ext': 'mp4',
            'title': 'Clase inédita',
            'duration': 335.817,
            'thumbnail': r're:https://img2\.rtve\.es/v/.*\.png',
            'series': 'El coro de la cárcel',
        },
        'params': {
            'skip_download': True,
        },
    }

    def _real_extract(self, url):
        page_id = self._match_id(url)
        webpage = self._download_webpage(url, page_id)

        play_url = self._html_search_meta('contentUrl', webpage)
        if play_url is None:
            raise ExtractorError('The webpage doesn\'t contain any video', expected=True)

        return self.url_result(play_url, ie=RTVEALaCartaIE.ie_key())


class RTVEProgramIE(RTVEBaseIE):
    IE_NAME = 'rtve.es:program'
    IE_DESC = 'RTVE.es programs'
    _VALID_URL = r'https?://(?:www\.)?rtve\.es/play/videos/(?P<id>[\w-]+)/?(?:[?#]|$)'
    _TESTS = [{
        'url': 'https://www.rtve.es/play/videos/saber-vivir/',
        'info_dict': {
            'id': '111570',
            'title': 'Saber vivir - Programa de ciencia y futuro en RTVE Play',
        },
        'playlist_mincount': 400,
    }]
    _PAGE_SIZE = 60

    def _fetch_page(self, program_id, page_num):
        return self._download_json(
            f'https://www.rtve.es/api/programas/{program_id}/videos',
            program_id, note=f'Downloading page {page_num}',
            query={
                'type': 39816,
                'page': page_num,
                'size': 60,
            })

    def _entries(self, page_data):
        for video in traverse_obj(page_data, ('page', 'items', lambda _, v: url_or_none(v['htmlUrl']))):