ronantakizawa/codereview-bench huggingface dataset:
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/_extractors.py (Python)

Code before:
    RedGifsIE,
    RedGifsSearchIE,
    RedGifsUserIE,
)
from .redtube import RedTubeIE
from .regiotv import RegioTVIE
from .rentv import (
    RENTVIE,
    RENTVArticleIE,
)
from .restudy import RestudyIE
from .reuters import ReutersIE
from .reverbnation import ReverbNationIE
from .rice import RICEIE
from .rmcdecouverte import RMCDecouverteIE
from .rockstargames import RockstarGamesIE
from .rokfin import (
    RokfinIE,
    RokfinStackIE,
    RokfinChannelIE,
    RokfinSearchIE,
)
from .roosterteeth import RoosterTeethIE, RoosterTeethSeriesIE
from .rottentomatoes import (
    RottenTomatoesIE,
    RottenTomatoesPlaylistIE,
)
from .rozhlas import (
    RozhlasIE,
    RozhlasVltavaIE,
)
from .rte import RteIE, RteRadioIE
from .rtlnl import (
    RtlNlIE,
    RTLLuTeleVODIE,
    RTLLuArticleIE,
    RTLLuLiveIE,
    RTLLuRadioIE,
)
from .rtl2 import (
    RTL2IE,
    RTL2YouIE,
    RTL2YouSeriesIE,
)
from .rtnews import (
    RTNewsIE,
    RTDocumentryIE,
    RTDocumentryPlaylistIE,
    RuptlyIE,
)
from .rtp import RTPIE

Review comment (refactor):
I think it may be better do to this all in one extractor. See my following suggestions
```suggestion
```

Code after:
    RedGifsIE,
    RedGifsSearchIE,
    RedGifsUserIE,
)
from .redtube import RedTubeIE
from .regiotv import RegioTVIE
from .rentv import (
    RENTVIE,
    RENTVArticleIE,
)
from .restudy import RestudyIE
from .reuters import ReutersIE
from .reverbnation import ReverbNationIE
from .rice import RICEIE
from .rmcdecouverte import RMCDecouverteIE
from .rockstargames import RockstarGamesIE
from .rokfin import (
    RokfinIE,
    RokfinStackIE,
    RokfinChannelIE,
    RokfinSearchIE,
)
from .roosterteeth import RoosterTeethIE, RoosterTeethSeriesIE
from .rottentomatoes import RottenTomatoesIE
from .rozhlas import (
    RozhlasIE,
    RozhlasVltavaIE,
)
from .rte import RteIE, RteRadioIE
from .rtlnl import (
    RtlNlIE,
    RTLLuTeleVODIE,
    RTLLuArticleIE,
    RTLLuLiveIE,
    RTLLuRadioIE,
)
from .rtl2 import (
    RTL2IE,
    RTL2YouIE,
    RTL2YouSeriesIE,
)
from .rtnews import (
    RTNewsIE,
    RTDocumentryIE,
    RTDocumentryPlaylistIE,
    RuptlyIE,
)
from .rtp import RTPIE
from .rtrfm import RTRFMIE
from .rts import RTSIE
from .rtve import (
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/rottentomatoes.py (Python)

Code before:
from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    js_to_json,
    parse_duration,
    traverse_obj,
    urljoin,
)


class RottenTomatoesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/[^/]+/trailers/(?P<id>[\da-zA-Z]+)'

    _TESTS = [{
        'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
        'info_dict': {
            'id': '11028566',
            'ext': 'mp4',
            'title': 'Best Tom Hanks Movies',
            'description': 'We look at the filmography of America\'s Dad, as we count down our list of the best Tom Hanks movies.',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 1468.592
        },
    }, {
        'url': 'https://www.rottentomatoes.com/m/toy_story_3/trailers/VycaVoBKhGuk',
        'info_dict': {
            'id': 'VycaVoBKhGuk',
            'ext': 'mp4',
            'title': 'Toy Story 3: Trailer 2',
            'description': '',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 149.941
        },
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)

Review comment (refactor):
We can extract 3 values here:
 - `playlist` will be the base of the `playlist_id` variable
 - `tr` will be checking if this is a trailers playlist, and we can use it to append `-trailers` to the `playlist_id` if so
 - `id` is the optional `video_id`

```suggestion
    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/(?P<playlist>[^/]+)(?:/(?P<tr>trailers)(?:/(?P<id>\w+))?)?'
```

Code after:
from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    clean_html,
    float_or_none,
    get_element_by_class,
    join_nonempty,
    traverse_obj,
    url_or_none,
)


class RottenTomatoesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/(?P<playlist>[^/]+)(?:/(?P<tr>trailers)(?:/(?P<id>\w+))?)?'

    _TESTS = [{
        'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
        'info_dict': {
            'id': '11028566',
            'ext': 'mp4',
            'title': 'Toy Story 3',
            'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.'
        },
        'skip': 'No longer available',
    }, {
        'url': 'https://www.rottentomatoes.com/m/toy_story_3/trailers/VycaVoBKhGuk',
        'info_dict': {
            'id': 'VycaVoBKhGuk',
            'ext': 'mp4',
            'title': 'Toy Story 3: Trailer 2',
            'description': '',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 149.941
        },
    }, {
        'url': 'http://www.rottentomatoes.com/m/toy_story_3',
        'info_dict': {
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/_extractors.py (Python)

Code before:
    RedGifsIE,
    RedGifsSearchIE,
    RedGifsUserIE,
)
from .redtube import RedTubeIE
from .regiotv import RegioTVIE
from .rentv import (
    RENTVIE,
    RENTVArticleIE,
)
from .restudy import RestudyIE
from .reuters import ReutersIE
from .reverbnation import ReverbNationIE
from .rice import RICEIE
from .rmcdecouverte import RMCDecouverteIE
from .rockstargames import RockstarGamesIE
from .rokfin import (
    RokfinIE,
    RokfinStackIE,
    RokfinChannelIE,
    RokfinSearchIE,
)
from .roosterteeth import RoosterTeethIE, RoosterTeethSeriesIE
from .rottentomatoes import (
    RottenTomatoesIE,
)
from .rozhlas import (
    RozhlasIE,
    RozhlasVltavaIE,
)
from .rte import RteIE, RteRadioIE
from .rtlnl import (
    RtlNlIE,
    RTLLuTeleVODIE,
    RTLLuArticleIE,
    RTLLuLiveIE,
    RTLLuRadioIE,
)
from .rtl2 import (
    RTL2IE,
    RTL2YouIE,
    RTL2YouSeriesIE,
)
from .rtnews import (
    RTNewsIE,
    RTDocumentryIE,
    RTDocumentryPlaylistIE,
    RuptlyIE,
)
from .rtp import RTPIE
from .rtrfm import RTRFMIE

Review comment (suggestion):
```suggestion
from .rottentomatoes import RottenTomatoesIE
```

Code after:
    RedGifsIE,
    RedGifsSearchIE,
    RedGifsUserIE,
)
from .redtube import RedTubeIE
from .regiotv import RegioTVIE
from .rentv import (
    RENTVIE,
    RENTVArticleIE,
)
from .restudy import RestudyIE
from .reuters import ReutersIE
from .reverbnation import ReverbNationIE
from .rice import RICEIE
from .rmcdecouverte import RMCDecouverteIE
from .rockstargames import RockstarGamesIE
from .rokfin import (
    RokfinIE,
    RokfinStackIE,
    RokfinChannelIE,
    RokfinSearchIE,
)
from .roosterteeth import RoosterTeethIE, RoosterTeethSeriesIE
from .rottentomatoes import RottenTomatoesIE
from .rozhlas import (
    RozhlasIE,
    RozhlasVltavaIE,
)
from .rte import RteIE, RteRadioIE
from .rtlnl import (
    RtlNlIE,
    RTLLuTeleVODIE,
    RTLLuArticleIE,
    RTLLuLiveIE,
    RTLLuRadioIE,
)
from .rtl2 import (
    RTL2IE,
    RTL2YouIE,
    RTL2YouSeriesIE,
)
from .rtnews import (
    RTNewsIE,
    RTDocumentryIE,
    RTDocumentryPlaylistIE,
    RuptlyIE,
)
from .rtp import RTPIE
from .rtrfm import RTRFMIE
from .rts import RTSIE
from .rtve import (
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/rottentomatoes.py (Python)

Code before:
from .common import InfoExtractor
from ..utils import (
    clean_html,
    ExtractorError,
    float_or_none,
    get_element_by_class,
    join_nonempty,
    traverse_obj,
    url_or_none,
)


class RottenTomatoesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/(?P<playlist>[^/]+)(?:/(?P<tr>trailers)(?:/(?P<id>\w+))?)?'

    _TESTS = [{
        'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
        'info_dict': {
            'id': '11028566',
            'ext': 'mp4',
            'title': 'Toy Story 3',
            'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.'
        },
        'skip': 'No longer available',
    }, {
        'url': 'https://www.rottentomatoes.com/m/toy_story_3/trailers/VycaVoBKhGuk',
        'info_dict': {
            'id': 'VycaVoBKhGuk',
            'ext': 'mp4',

Review comment (refactor):
```suggestion
    ExtractorError,
    clean_html,
```

Code after:
from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    clean_html,
    float_or_none,
    get_element_by_class,
    join_nonempty,
    traverse_obj,
    url_or_none,
)


class RottenTomatoesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/(?P<playlist>[^/]+)(?:/(?P<tr>trailers)(?:/(?P<id>\w+))?)?'

    _TESTS = [{
        'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
        'info_dict': {
            'id': '11028566',
            'ext': 'mp4',
            'title': 'Toy Story 3',
            'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.'
        },
        'skip': 'No longer available',
    }, {
        'url': 'https://www.rottentomatoes.com/m/toy_story_3/trailers/VycaVoBKhGuk',
        'info_dict': {
            'id': 'VycaVoBKhGuk',
            'ext': 'mp4',
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/wevidi.py (Python)

Code before:
        formats = []
        for res in resolutions:
            format_id = str(-(res // -2) - 1)
            formats.append({
                'acodec': 'mp4a.40.2',
                'ext': 'mp4',
                'format_id': format_id,
                'resolution': resolution_map.get(res),
                'url': f'https://www.wevidi.net/videoplayback/{srcVID}/{srcUID}/{srcNAME}/{format_id}',
                'vcodec': 'avc1.42E01E',
            })

        return formats

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

        title = get_element_by_class('video_title', webpage)
        wvplayer_props = json.loads(js_to_json(self._search_regex(
            r'WVPlayer\(({.+?)autoplay', webpage,
            'wvplayer_props', flags=re.DOTALL) + '}'))

        return {
            'id': video_id,
            'title': title,
            'description': clean_html(get_element_by_class('descr_long', webpage)),
            'uploader': get_element_by_class('username', webpage),
            'formats': self._extract_formats(wvplayer_props),
            'thumbnail': self._og_search_thumbnail(webpage),
            'duration': float_or_none(wvplayer_props.get('duration')),
        }


Review comment (suggestion):
```suggestion
            'title': clean_html(get_element_by_class('video_title', webpage)),
```

Code after:
            3: 360,
            4: 480,
            5: 720,
            6: 1080
        }

        src_path = f'{wvplayer_props["srcVID"]}/{wvplayer_props["srcUID"]}/{wvplayer_props["srcNAME"]}'
        for res in traverse_obj(wvplayer_props, ('resolutions', ..., {int}, {lambda x: x or None})):
            format_id = str(-(res // -2) - 1)
            yield {
                'acodec': 'mp4a.40.2',
                'ext': 'mp4',
                'format_id': format_id,
                'height': resolution_map.get(res),
                'url': f'https://www.wevidi.net/videoplayback/{src_path}/{format_id}',
                'vcodec': 'avc1.42E01E',
            }

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

        wvplayer_props = self._search_json(
            r'WVPlayer\(', webpage, 'player', video_id,
            transform_source=lambda x: js_to_json(x.replace('||', '}')))

        return {
            'id': video_id,
            'title': clean_html(get_element_by_class('video_title', webpage)),
            'description': clean_html(get_element_by_class('descr_long', webpage)),
            'uploader': clean_html(get_element_by_class('username', webpage)),
            'formats': list(self._extract_formats(wvplayer_props)),
            'thumbnail': self._og_search_thumbnail(webpage),
            'duration': float_or_none(wvplayer_props.get('duration')),
        }
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/wevidi.py (Python)

Code before:
            format_id = str(-(res // -2) - 1)
            formats.append({
                'acodec': 'mp4a.40.2',
                'ext': 'mp4',
                'format_id': format_id,
                'resolution': resolution_map.get(res),
                'url': f'https://www.wevidi.net/videoplayback/{srcVID}/{srcUID}/{srcNAME}/{format_id}',
                'vcodec': 'avc1.42E01E',
            })

        return formats

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

        title = get_element_by_class('video_title', webpage)
        wvplayer_props = json.loads(js_to_json(self._search_regex(
            r'WVPlayer\(({.+?)autoplay', webpage,
            'wvplayer_props', flags=re.DOTALL) + '}'))

        return {
            'id': video_id,
            'title': title,
            'description': clean_html(get_element_by_class('descr_long', webpage)),
            'uploader': get_element_by_class('username', webpage),
            'formats': self._extract_formats(wvplayer_props),
            'thumbnail': self._og_search_thumbnail(webpage),
            'duration': float_or_none(wvplayer_props.get('duration')),
        }


Review comment (suggestion):
clean just in case
```suggestion
            'uploader': clean_html(get_element_by_class('username', webpage)),
```

Code after:
            5: 720,
            6: 1080
        }

        src_path = f'{wvplayer_props["srcVID"]}/{wvplayer_props["srcUID"]}/{wvplayer_props["srcNAME"]}'
        for res in traverse_obj(wvplayer_props, ('resolutions', ..., {int}, {lambda x: x or None})):
            format_id = str(-(res // -2) - 1)
            yield {
                'acodec': 'mp4a.40.2',
                'ext': 'mp4',
                'format_id': format_id,
                'height': resolution_map.get(res),
                'url': f'https://www.wevidi.net/videoplayback/{src_path}/{format_id}',
                'vcodec': 'avc1.42E01E',
            }

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

        wvplayer_props = self._search_json(
            r'WVPlayer\(', webpage, 'player', video_id,
            transform_source=lambda x: js_to_json(x.replace('||', '}')))

        return {
            'id': video_id,
            'title': clean_html(get_element_by_class('video_title', webpage)),
            'description': clean_html(get_element_by_class('descr_long', webpage)),
            'uploader': clean_html(get_element_by_class('username', webpage)),
            'formats': list(self._extract_formats(wvplayer_props)),
            'thumbnail': self._og_search_thumbnail(webpage),
            'duration': float_or_none(wvplayer_props.get('duration')),
        }
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/wevidi.py (Python)

Code before:
import json
import re
from .common import InfoExtractor
from ..utils import clean_html, float_or_none, get_element_by_class, js_to_json


class WeVidiIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?wevidi\.net/watch/(?P<id>[0-9A-Za-z_-]{11})'
    _TESTS = [{
        'url': 'https://wevidi.net/watch/2th7UO5F4KV',
        'md5': 'b913d1ff5bbad499e2c7ef4aa6d829d7',
        'info_dict': {
            'id': '2th7UO5F4KV',
            'ext': 'mp4',
            'title': 'YouTube Alternative: WeVidi - customizable channels & more',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:73a27d0a87d49fbcc5584566326ebeed',
            'uploader': 'eclecRC',
            'duration': 932.098,
        }
    }, {
        'url': 'https://wevidi.net/watch/ievRuuQHbPS',
        'md5': 'ce8a94989a959bff9003fa27ee572935',
        'info_dict': {
            'id': 'ievRuuQHbPS',
            'ext': 'mp4',
            'title': 'WeVidi Playlists',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:32cdfca272687390d9bd9b0c9c6153ee',
            'uploader': 'WeVidi',
            'duration': 36.1999,
        }
    }, {

Review comment (suggestion):
```suggestion
    _VALID_URL = r'https?://(?:www\.)?wevidi\.net/watch/(?P<id>[\w-]{11})'
```

Code after:
from .common import InfoExtractor
from ..utils import clean_html, float_or_none, get_element_by_class, js_to_json, traverse_obj


class WeVidiIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?wevidi\.net/watch/(?P<id>[\w-]{11})'
    _TESTS = [{
        'url': 'https://wevidi.net/watch/2th7UO5F4KV',
        'md5': 'b913d1ff5bbad499e2c7ef4aa6d829d7',
        'info_dict': {
            'id': '2th7UO5F4KV',
            'ext': 'mp4',
            'title': 'YouTube Alternative: WeVidi - customizable channels & more',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:73a27d0a87d49fbcc5584566326ebeed',
            'uploader': 'eclecRC',
            'duration': 932.098,
        }
    }, {
        'url': 'https://wevidi.net/watch/ievRuuQHbPS',
        'md5': 'ce8a94989a959bff9003fa27ee572935',
        'info_dict': {
            'id': 'ievRuuQHbPS',
            'ext': 'mp4',
            'title': 'WeVidi Playlists',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:32cdfca272687390d9bd9b0c9c6153ee',
            'uploader': 'WeVidi',
            'duration': 36.1999,
        }
    }, {
        'url': 'https://wevidi.net/watch/PcMzDWaQSWb',
        'md5': '55ee0d3434be5d9e5cc76b83f2bb57ec',
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/globalplayer.py (Python)

Code before:
from .common import InfoExtractor
from ..utils import (
    clean_html,
    parse_duration,
    str_or_none,
    traverse_obj,
    unified_strdate,
    unified_timestamp,
    urlhandle_detect_ext,
)


class GlobalPlayerBaseIE(InfoExtractor):

    def _get_pageProps(self, url, video_id):
        webpage = self._download_webpage(url, video_id)
        data = self._search_nextjs_data(webpage, video_id)
        return traverse_obj(data, ('props', 'pageProps'))

    def _request_ext(self, url, video_id):
        urlh = self._request_webpage(url, video_id, note='Determining source extension')
        return urlhandle_detect_ext(urlh)

    def _extract_audio(self, episode, series):
        return {
            'vcodec': 'none',
            **traverse_obj(series, {
                'series': 'title',
                'series_id': 'id',
                'thumbnail': 'imageUrl',
            }),
            **traverse_obj(episode, {
                'id': 'id',
                'description': ('description', {clean_html}),
                'duration': ('duration', {parse_duration}),
                'thumbnail': 'imageUrl',
                'url': 'streamUrl',
                'timestamp': (['pubDate', 'startDate'], {unified_timestamp}),
                # pubDate for podcasts, startDate for radio catchup - that's all we need to have both in one
                'title': 'title',

Review comment (suggestion):
snake case
```suggestion
    def _get_page_props(self, url, video_id):
```

Code after:
from .common import InfoExtractor
from ..utils import (
    clean_html,
    join_nonempty,
    parse_duration,
    str_or_none,
    traverse_obj,
    unified_strdate,
    unified_timestamp,
    urlhandle_detect_ext,
)


class GlobalPlayerBaseIE(InfoExtractor):
    def _get_page_props(self, url, video_id):
        webpage = self._download_webpage(url, video_id)
        return self._search_nextjs_data(webpage, video_id)['props']['pageProps']

    def _request_ext(self, url, video_id):
        return urlhandle_detect_ext(self._request_webpage(  # Server rejects HEAD requests
            url, video_id, note='Determining source extension'))

    def _extract_audio(self, episode, series):
        return {
            'vcodec': 'none',
            **traverse_obj(series, {
                'series': 'title',
                'series_id': 'id',
                'thumbnail': 'imageUrl',
                'uploader': 'itunesAuthor',  # podcasts only
            }),
            **traverse_obj(episode, {
                'id': 'id',
                'description': ('description', {clean_html}),
                'duration': ('duration', {parse_duration}),
                'thumbnail': 'imageUrl',
                'url': 'streamUrl',
                'timestamp': (('pubDate', 'startDate'), {unified_timestamp}),
                'title': 'title',
            }, get_all=False)
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/globalplayer.py (Python)

Code before:
from .common import InfoExtractor
from ..utils import (
    clean_html,
    parse_duration,
    str_or_none,
    traverse_obj,
    unified_strdate,
    unified_timestamp,
    urlhandle_detect_ext,
)


class GlobalPlayerBaseIE(InfoExtractor):

    def _get_pageProps(self, url, video_id):
        webpage = self._download_webpage(url, video_id)
        data = self._search_nextjs_data(webpage, video_id)
        return traverse_obj(data, ('props', 'pageProps'))

    def _request_ext(self, url, video_id):
        urlh = self._request_webpage(url, video_id, note='Determining source extension')
        return urlhandle_detect_ext(urlh)

    def _extract_audio(self, episode, series):
        return {
            'vcodec': 'none',
            **traverse_obj(series, {
                'series': 'title',
                'series_id': 'id',
                'thumbnail': 'imageUrl',
            }),
            **traverse_obj(episode, {
                'id': 'id',
                'description': ('description', {clean_html}),
                'duration': ('duration', {parse_duration}),
                'thumbnail': 'imageUrl',
                'url': 'streamUrl',
                'timestamp': (['pubDate', 'startDate'], {unified_timestamp}),
                # pubDate for podcasts, startDate for radio catchup - that's all we need to have both in one
                'title': 'title',
            }, get_all=False)
        }


Review comment (suggestion):
`traverse_obj` isn't needed here since returning `None` will be fatal in all subclasses anyways
```suggestion
        return self._search_nextjs_data(webpage, video_id)['props']['pageProps']
```

Code after:
from .common import InfoExtractor
from ..utils import (
    clean_html,
    join_nonempty,
    parse_duration,
    str_or_none,
    traverse_obj,
    unified_strdate,
    unified_timestamp,
    urlhandle_detect_ext,
)


class GlobalPlayerBaseIE(InfoExtractor):
    def _get_page_props(self, url, video_id):
        webpage = self._download_webpage(url, video_id)
        return self._search_nextjs_data(webpage, video_id)['props']['pageProps']

    def _request_ext(self, url, video_id):
        return urlhandle_detect_ext(self._request_webpage(  # Server rejects HEAD requests
            url, video_id, note='Determining source extension'))

    def _extract_audio(self, episode, series):
        return {
            'vcodec': 'none',
            **traverse_obj(series, {
                'series': 'title',
                'series_id': 'id',
                'thumbnail': 'imageUrl',
                'uploader': 'itunesAuthor',  # podcasts only
            }),
            **traverse_obj(episode, {
                'id': 'id',
                'description': ('description', {clean_html}),
                'duration': ('duration', {parse_duration}),
                'thumbnail': 'imageUrl',
                'url': 'streamUrl',
                'timestamp': (('pubDate', 'startDate'), {unified_timestamp}),
                'title': 'title',
            }, get_all=False)
        }