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

Code before:
            }).encode(),
            headers={
                'content-type': 'application/json',
                'x-vrt-client-name': 'WEB',
                'x-vrt-client-version': '1.5.9',
                'x-vrt-zone': 'default',
            },
        )['data']['page']

        video_id = metadata['player']['modes'][0]['streamId']
        video_token = self._fetch_video_token()

        try:
            streaming_info = self._call_api(video_id, 'vrtnu-web@PROD', id_token=video_token)
        except ExtractorError as e:
            if not video_token and isinstance(e.cause, HTTPError) and e.cause.status == 404:
                self.raise_login_required()
            raise

        formats, subtitles = self._extract_formats_and_subtitles(streaming_info, video_id)

        code = traverse_obj(streaming_info, ('code', {str}))
        if not formats and code:
            if code in ('CONTENT_AVAILABLE_ONLY_FOR_BE_RESIDENTS', 'CONTENT_AVAILABLE_ONLY_IN_BE', 'CONTENT_UNAVAILABLE_VIA_PROXY'):
                self.raise_geo_restricted()
            elif code in ('CONTENT_AVAILABLE_ONLY_FOR_BE_RESIDENTS_AND_EXPATS', 'CONTENT_IS_AGE_RESTRICTED', 'CONTENT_REQUIRES_AUTHENTICATION'):
                self.raise_login_required()
            else:
                raise ExtractorError(f'Unable to extract formats: {code}')

        return {
            **self._json_ld(
                traverse_obj(metadata, ('ldjson', ..., {json.loads})),
                video_id,
                fatal=False,
            ),
            **traverse_obj(metadata, ('episode', {
                'age_limit': ('ageRaw', {parse_age_limit}),
                'description': ('description', {str}),
                'duration': ('durationRaw', {parse_duration}),
                'episode': ('title', {str}),
                'episode_id': ('id', {str_or_none}),
                'episode_number': ('episodeNumberRaw', {int_or_none}),
                'season': ('season', 'titleRaw', {str}),
                'season_id': ('id', {str_or_none}),
                'season_number': ('season', 'titleRaw', {int_or_none}),
                'series': ('program', 'title', {str}),
                'timestamp': ('onTimeRaw', {parse_iso8601}),
                'title': ('title', {str}),
            })),
            'channel': 'VRT',

Review comment (suggestion):
@bergoid's version also had `release_date`, `release_timestamp` & `upload_date`, so maybe we should add these as well?

  ```py
**traverse_obj(metadata, {
    'release_date': ('episode', 'onTimeRaw', {unified_strdate}),
    'release_timestamp': ('episode', 'onTimeRaw', {parse_iso8601}),
    'upload_date': ('episode', 'onTimeRaw', {unified_strdate}),
})
```

Code after:

        video_id = metadata['player']['modes'][0]['streamId']

        try:
            streaming_info = self._call_api(video_id, 'vrtnu-web@PROD', id_token=video_token)
        except ExtractorError as e:
            if not video_token and isinstance(e.cause, HTTPError) and e.cause.status == 404:
                self.raise_login_required()
            raise

        formats, subtitles = self._extract_formats_and_subtitles(streaming_info, video_id)

        code = traverse_obj(streaming_info, ('code', {str}))
        if not formats and code:
            if code in ('CONTENT_AVAILABLE_ONLY_FOR_BE_RESIDENTS', 'CONTENT_AVAILABLE_ONLY_IN_BE', 'CONTENT_UNAVAILABLE_VIA_PROXY'):
                self.raise_geo_restricted(countries=['BE'])
            elif code in ('CONTENT_AVAILABLE_ONLY_FOR_BE_RESIDENTS_AND_EXPATS', 'CONTENT_IS_AGE_RESTRICTED', 'CONTENT_REQUIRES_AUTHENTICATION'):
                self.raise_login_required()
            else:
                self.raise_no_formats(f'Unable to extract formats: {code}')

        return {
            'duration': float_or_none(streaming_info.get('duration'), 1000),
            'thumbnail': url_or_none(streaming_info.get('posterImageUrl')),
            **self._json_ld(traverse_obj(metadata, ('ldjson', ..., {json.loads})), video_id, fatal=False),
            **traverse_obj(metadata, ('episode', {
                'title': ('title', {str}),
                'description': ('description', {str}),
                'timestamp': ('onTimeRaw', {parse_iso8601}),
                'series': ('program', 'title', {str}),
                'season': ('season', 'titleRaw', {str}),
                'season_number': ('season', 'titleRaw', {int_or_none}),
                'season_id': ('id', {str_or_none}),
                'episode': ('title', {str}),
                'episode_number': ('episodeNumberRaw', {int_or_none}),
                'episode_id': ('id', {str_or_none}),
                'age_limit': ('ageRaw', {parse_age_limit}),
                'channel': ('brand', {str}),
                'duration': ('durationRaw', {parse_duration}),
            })),
            'id': video_id,
            'display_id': display_id,
            'formats': formats,
            'subtitles': subtitles,
            '_old_archive_ids': [make_archive_id('Canvas', video_id)],
        }


class KetnetIE(VRTBaseIE):
    _VALID_URL = r'https?://(?:www\.)?ketnet\.be/(?P<id>(?:[^/]+/)*[^/?#&]+)'
    _TESTS = [{