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

Code before:
import json
import urllib

from .common import InfoExtractor
from ..utils import clean_html, remove_end, unescapeHTML, unified_timestamp, url_or_none
from ..utils.traversal import traverse_obj


class TvwIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?tvw\.org/video/(?P<id>[^/?#]+)'

    _TESTS = [{
        'url': 'https://tvw.org/video/billy-frank-jr-statue-maquette-unveiling-ceremony-2024011211/',
        'md5': '9ceb94fe2bb7fd726f74f16356825703',
        'info_dict': {
            'id': '2024011211',
            'ext': 'mp4',
            'title': 'Billy Frank Jr. Statue Maquette Unveiling Ceremony',
            'thumbnail': r're:^https?://.*\.(?:jpe?g|png)$',
            'description': 'md5:58a8150017d985b4f377e11ee8f6f36e',
            'timestamp': 1704902400,
            'upload_date': '20240110',
            'location': 'Legislative Building',
            'display_id': 'billy-frank-jr-statue-maquette-unveiling-ceremony-2024011211',
            'categories': ['General Interest'],
        },
    }, {
        'url': 'https://tvw.org/video/ebeys-landing-state-park-2024081007/',
        'md5': '71e87dae3deafd65d75ff3137b9a32fc',
        'info_dict': {
            'id': '2024081007',

Review comment (refactor):
For my changes from below to work
```suggestion
from ..utils import (
    clean_html,
    extract_attributes,
    parse_qs,
    remove_end,
    unified_timestamp,
    url_or_none,
)
from ..utils.traversal import find_element, require, traverse_obj
```

Code after:
import json

from .common import InfoExtractor
from ..utils import (
    clean_html,
    extract_attributes,
    parse_qs,
    remove_end,
    require,
    unified_timestamp,
    url_or_none,
)
from ..utils.traversal import find_element, traverse_obj


class TvwIE(InfoExtractor):
    IE_NAME = 'tvw'
    _VALID_URL = r'https?://(?:www\.)?tvw\.org/video/(?P<id>[^/?#]+)'
    _TESTS = [{
        'url': 'https://tvw.org/video/billy-frank-jr-statue-maquette-unveiling-ceremony-2024011211/',
        'md5': '9ceb94fe2bb7fd726f74f16356825703',
        'info_dict': {
            'id': '2024011211',
            'ext': 'mp4',
            'title': 'Billy Frank Jr. Statue Maquette Unveiling Ceremony',
            'thumbnail': r're:^https?://.*\.(?:jpe?g|png)$',
            'description': 'md5:58a8150017d985b4f377e11ee8f6f36e',
            'timestamp': 1704902400,
            'upload_date': '20240110',
            'location': 'Legislative Building',
            'display_id': 'billy-frank-jr-statue-maquette-unveiling-ceremony-2024011211',
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/_extractors.py (Python)

Code before:
    TVCIE,
    TVCArticleIE,
)
from .tver import TVerIE
from .tvigle import TvigleIE
from .tviplayer import TVIPlayerIE
from .tvland import TVLandIE
from .tvn24 import TVN24IE
from .tvnoe import TVNoeIE
from .tvopengr import (
    TVOpenGrEmbedIE,
    TVOpenGrWatchIE,
)
from .tvp import (
    TVPIE,
    TVPEmbedIE,
    TVPStreamIE,
    TVPVODSeriesIE,
    TVPVODVideoIE,
)
from .tvplay import (
    TVPlayHomeIE,
    TVPlayIE,
)
from .tvplayer import TVPlayerIE
from .tvw import TvwIE, TvwTvChannelsIE
from .tweakers import TweakersIE
from .twentymin import TwentyMinutenIE
from .twentythreevideo import TwentyThreeVideoIE
from .twitcasting import (
    TwitCastingIE,
    TwitCastingLiveIE,
    TwitCastingUserIE,
)
from .twitch import (
    TwitchClipsIE,
    TwitchCollectionIE,
    TwitchStreamIE,
    TwitchVideosClipsIE,
    TwitchVideosCollectionsIE,
    TwitchVideosIE,
    TwitchVodIE,
)
from .twitter import (
    TwitterAmplifyIE,
    TwitterBroadcastIE,
    TwitterCardIE,
    TwitterIE,
    TwitterShortenerIE,
    TwitterSpacesIE,
)

Review comment (suggestion):
```suggestion
from .tvw import (
    TvwIE,
    TvwTvChannelsIE,
)
```

Code after:
from .tv4 import TV4IE
from .tv5mondeplus import TV5MondePlusIE
from .tv5unis import (
    TV5UnisIE,
    TV5UnisVideoIE,
)
from .tv24ua import TV24UAVideoIE
from .tva import TVAIE
from .tvanouvelles import (
    TVANouvellesArticleIE,
    TVANouvellesIE,
)
from .tvc import (
    TVCIE,
    TVCArticleIE,
)
from .tver import TVerIE
from .tvigle import TvigleIE
from .tviplayer import TVIPlayerIE
from .tvland import TVLandIE
from .tvn24 import TVN24IE
from .tvnoe import TVNoeIE
from .tvopengr import (
    TVOpenGrEmbedIE,
    TVOpenGrWatchIE,
)
from .tvp import (
    TVPIE,
    TVPEmbedIE,
    TVPStreamIE,
    TVPVODSeriesIE,
    TVPVODVideoIE,
)
from .tvplay import (
    TVPlayHomeIE,
    TVPlayIE,
)
from .tvplayer import TVPlayerIE
from .tvw import (
    TvwIE,
    TvwTvChannelsIE,
)
from .tweakers import TweakersIE
from .twentymin import TwentyMinutenIE
from .twentythreevideo import TwentyThreeVideoIE
from .twitcasting import (
    TwitCastingIE,
    TwitCastingLiveIE,
    TwitCastingUserIE,
)
from .twitch import (
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/ivoox.py (Python)

Code before:
import datetime
import json

from .common import InfoExtractor


class IvooxIE(InfoExtractor):
    _VALID_URL = (
        r'https?://(?:www\.)?ivoox\.com/.*_rf_(?P<id>[0-9]+)_1\.html',
        r'https?://go\.ivoox\.com/rf/(?P<id>[0-9]+)',
    )
    _TESTS = [
        {
            'url': 'https://www.ivoox.com/dex-08x30-rostros-del-mal-los-asesinos-en-audios-mp3_rf_143594959_1.html',
            'md5': 'f3cc6b8db8995e0c95604deb6a8f0f2f',
            'info_dict': {
                # For videos, only the 'id' and 'ext' fields are required to RUN the test:
                'id': '143594959',
                'ext': 'mp3',
                'timestamp': 1742727600,
                'author': 'Santiago Camacho',
                'channel': 'DIAS EXTRAÑOS con Santiago Camacho',
                'title': 'DEx 08x30 Rostros del mal: Los asesinos en serie que aterrorizaron España',
            },
        },
        {
            'url': 'https://go.ivoox.com/rf/143594959',
            'md5': 'f3cc6b8db8995e0c95604deb6a8f0f2f',
            'info_dict': {

Review comment (refactor):
```suggestion
from .common import InfoExtractor
from ..utils import int_or_none, parse_iso8601, url_or_none, urljoin
from ..utils.traversal import traverse_obj
```

Code after:
from .common import InfoExtractor
from ..utils import int_or_none, parse_iso8601, url_or_none, urljoin
from ..utils.traversal import traverse_obj


class IvooxIE(InfoExtractor):
    _VALID_URL = (
        r'https?://(?:www\.)?ivoox\.com/(?:\w{2}/)?[^/?#]+_rf_(?P<id>[0-9]+)_1\.html',
        r'https?://go\.ivoox\.com/rf/(?P<id>[0-9]+)',
    )
    _TESTS = [{
        'url': 'https://www.ivoox.com/dex-08x30-rostros-del-mal-los-asesinos-en-audios-mp3_rf_143594959_1.html',
        'md5': '993f712de5b7d552459fc66aa3726885',
        'info_dict': {
            'id': '143594959',
            'ext': 'mp3',
            'timestamp': 1742731200,
            'channel': 'DIAS EXTRAÑOS con Santiago Camacho',
            'title': 'DEx 08x30 Rostros del mal: Los asesinos en serie que aterrorizaron España',
            'description': 'md5:eae8b4b9740d0216d3871390b056bb08',
            'uploader': 'Santiago Camacho',
            'thumbnail': 'https://static-1.ivoox.com/audios/c/d/5/2/cd52f46783fe735000c33a803dce2554_XXL.jpg',
            'upload_date': '20250323',
            'episode': 'DEx 08x30 Rostros del mal: Los asesinos en serie que aterrorizaron España',
            'duration': 11837,
            'tags': ['españa', 'asesinos en serie', 'arropiero', 'historia criminal', 'mataviejas'],
        },
    }, {
        'url': 'https://go.ivoox.com/rf/143594959',
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/parti.py (Python)

Code before:
import datetime

from yt_dlp.utils._utils import UserNotLive

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    traverse_obj,
)


class PartiBaseIE(InfoExtractor):
    _RECORDING_BASE_URL = 'https://watch.parti.com'
    _GET_LIVESTREAM_API = 'https://api-backend.parti.com/parti_v2/profile/get_livestream_channel_info'
    _PLAYBACK_VERSION = '1.17.0'

    def _get_formats(self, stream_url, creator, is_live):
        return self._extract_m3u8_formats(stream_url, creator, 'mp4', live=is_live)

    def _build_recording_url(self, path):
        return self._RECORDING_BASE_URL + '/' + path


class PartiVideoIE(PartiBaseIE):
    IE_NAME = 'parti:video'
    IE_DESC = 'Download a video from parti.com'
    _VALID_URL = r'https://parti\.com/video/(?P<id>\d+)'
    _TESTS = [
        {
            'url': 'https://parti.com/video/66284',
            'info_dict': {
                'id': '66284',
                'ext': 'mp4',
                'title': str,

Review comment (refactor):
Not sure why you went with different styles of imports here
```suggestion
from .common import InfoExtractor
from ..utils import UserNotLive, int_or_none, parse_iso8601, url_or_none, urljoin
from ..utils.traversal import require, traverse_obj
```

Code after:
from .common import InfoExtractor
from ..utils import UserNotLive, int_or_none, parse_iso8601, url_or_none, urljoin
from ..utils.traversal import traverse_obj


class PartiBaseIE(InfoExtractor):
    def _call_api(self, path, video_id, note=None):
        return self._download_json(
            f'https://api-backend.parti.com/parti_v2/profile/{path}', video_id, note)


class PartiVideoIE(PartiBaseIE):
    IE_NAME = 'parti:video'
    _VALID_URL = r'https?://(?:www\.)?parti\.com/video/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://parti.com/video/66284',
        'info_dict': {
            'id': '66284',
            'ext': 'mp4',
            'title': 'NOW LIVE ',
            'upload_date': '20250327',
            'categories': ['Gaming'],
            'thumbnail': 'https://assets.parti.com/351424_eb9e5250-2821-484a-9c5f-ca99aa666c87.png',
            'channel': 'ItZTMGG',
            'timestamp': 1743044379,
        },
        'params': {'skip_download': 'm3u8'},
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        data = self._call_api(f'get_livestream_channel_info/recent/{video_id}', video_id)

        return {
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/francaisfacile.py (Python)

Code before:
from .common import InfoExtractor


class FrancaisFacileIE(InfoExtractor):
    _VALID_URL = r'https?:\/\/francaisfacile\.rfi\.fr\/fr\/(actualit%C3%A9|podcasts\/[^\/]+)\/(?P<id>\d+)-.*'
    IE_NAME = 'francaisfacile'
    _TESTS = [
        {
            'url': 'https://francaisfacile.rfi.fr/fr/podcasts/un-mot-une-histoire/20250317-le-mot-de-david-foenkinos-peut-%C3%AAtre',
            'md5': 'db83c2cc2589b4c24571c6b6cf14f5f1',
            'info_dict': {
                'id': '20250317',
                'title': 'Le mot de David Foenkinos: «peut-être» - Un mot, une histoire',
                'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/17/4ca6cbbe-0315-11f0-a85b-005056a97652.mp3',
                'ext': 'mp3',
            },
        },
        {
            'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250307-argentine-le-sac-d-un-alpiniste-retrouv%C3%A9-40-ans-apr%C3%A8s-sa-mort',
            'md5': 'b8c3a63652d4ae8e8092dda5700c1cd9',
            'info_dict': {
                'id': '20250307',
                'title': 'Argentine: le sac d\'un alpiniste retrouvé 40 ans après sa mort',
                'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/07/8edf4082-fb46-11ef-8a37-005056bf762b.mp3',
                'ext': 'mp3',
            },
        },
        {
            'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250305-r%C3%A9concilier-les-jeunes-avec-la-lecture-gr%C3%A2ce-aux-r%C3%A9seaux-sociaux',
            'md5': '4f33674cb205744345cc835991100afa',

Review comment (suggestion):
```suggestion
    _VALID_URL = r'https?://francaisfacile\.rfi\.fr/fr/(actualit%C3%A9|podcasts/[^/#?]+)/(?P<id>\d+)-'
```

Code after:
import urllib.parse

from .common import InfoExtractor
from ..networking.exceptions import HTTPError
from ..utils import (
    ExtractorError,
    float_or_none,
    url_or_none,
)
from ..utils.traversal import traverse_obj


class FrancaisFacileIE(InfoExtractor):
    _VALID_URL = r'https?://francaisfacile\.rfi\.fr/[a-z]{2}/(?:actualit%C3%A9|podcasts/[^/#?]+)/(?P<id>[^/#?]+)'
    _TESTS = [{
        'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250305-r%C3%A9concilier-les-jeunes-avec-la-lecture-gr%C3%A2ce-aux-r%C3%A9seaux-sociaux',
        'md5': '4f33674cb205744345cc835991100afa',
        'info_dict': {
            'id': 'WBMZ58952-FLE-FR-20250305',
            'display_id': '20250305-réconcilier-les-jeunes-avec-la-lecture-grâce-aux-réseaux-sociaux',
            'title': 'Réconcilier les jeunes avec la lecture grâce aux réseaux sociaux',
            'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/05/6b6af52a-f9ba-11ef-a1f8-005056a97652.mp3',
            'ext': 'mp3',
            'description': 'md5:b903c63d8585bd59e8cc4d5f80c4272d',
            'duration': 103.15,
            'timestamp': 1741177984,
            'upload_date': '20250305',
        },
    }, {
        'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250307-argentine-le-sac-d-un-alpiniste-retrouv%C3%A9-40-ans-apr%C3%A8s-sa-mort',
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/francaisfacile.py (Python)

Code before:
from .common import InfoExtractor


class FrancaisFacileIE(InfoExtractor):
    _VALID_URL = r'https?:\/\/francaisfacile\.rfi\.fr\/fr\/(actualit%C3%A9|podcasts\/[^\/]+)\/(?P<id>\d+)-.*'
    IE_NAME = 'francaisfacile'
    _TESTS = [
        {
            'url': 'https://francaisfacile.rfi.fr/fr/podcasts/un-mot-une-histoire/20250317-le-mot-de-david-foenkinos-peut-%C3%AAtre',
            'md5': 'db83c2cc2589b4c24571c6b6cf14f5f1',
            'info_dict': {
                'id': '20250317',
                'title': 'Le mot de David Foenkinos: «peut-être» - Un mot, une histoire',
                'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/17/4ca6cbbe-0315-11f0-a85b-005056a97652.mp3',
                'ext': 'mp3',
            },
        },
        {
            'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250307-argentine-le-sac-d-un-alpiniste-retrouv%C3%A9-40-ans-apr%C3%A8s-sa-mort',
            'md5': 'b8c3a63652d4ae8e8092dda5700c1cd9',
            'info_dict': {
                'id': '20250307',
                'title': 'Argentine: le sac d\'un alpiniste retrouvé 40 ans après sa mort',
                'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/07/8edf4082-fb46-11ef-8a37-005056bf762b.mp3',
                'ext': 'mp3',
            },
        },
        {

Review comment (refactor):
```suggestion
from .common import InfoExtractor
from ..utils import float_or_none, url_or_none
from ..utils.traversal import traverse_obj
```

Code after:
import urllib.parse

from .common import InfoExtractor
from ..networking.exceptions import HTTPError
from ..utils import (
    ExtractorError,
    float_or_none,
    url_or_none,
)
from ..utils.traversal import traverse_obj


class FrancaisFacileIE(InfoExtractor):
    _VALID_URL = r'https?://francaisfacile\.rfi\.fr/[a-z]{2}/(?:actualit%C3%A9|podcasts/[^/#?]+)/(?P<id>[^/#?]+)'
    _TESTS = [{
        'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250305-r%C3%A9concilier-les-jeunes-avec-la-lecture-gr%C3%A2ce-aux-r%C3%A9seaux-sociaux',
        'md5': '4f33674cb205744345cc835991100afa',
        'info_dict': {
            'id': 'WBMZ58952-FLE-FR-20250305',
            'display_id': '20250305-réconcilier-les-jeunes-avec-la-lecture-grâce-aux-réseaux-sociaux',
            'title': 'Réconcilier les jeunes avec la lecture grâce aux réseaux sociaux',
            'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/05/6b6af52a-f9ba-11ef-a1f8-005056a97652.mp3',
            'ext': 'mp3',
            'description': 'md5:b903c63d8585bd59e8cc4d5f80c4272d',
            'duration': 103.15,
            'timestamp': 1741177984,
            'upload_date': '20250305',
        },
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/francaisfacile.py (Python)

Code before:
from .common import InfoExtractor
from ..utils import float_or_none, url_or_none
from ..utils.traversal import traverse_obj


class FrancaisFacileIE(InfoExtractor):
    _VALID_URL = r'https?://francaisfacile\.rfi\.fr/fr/(actualit%C3%A9|podcasts/[^/#?]+)/(?P<id>\d+)-'
    IE_NAME = 'francaisfacile'
    _TESTS = [{
        'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250305-r%C3%A9concilier-les-jeunes-avec-la-lecture-gr%C3%A2ce-aux-r%C3%A9seaux-sociaux',
        'md5': '4f33674cb205744345cc835991100afa',
        'info_dict': {
            'id': 'WBMZ58952-FLE-FR-20250305',
            'title': 'Réconcilier les jeunes avec la lecture grâce aux réseaux sociaux',
            'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/05/6b6af52a-f9ba-11ef-a1f8-005056a97652.mp3',
            'ext': 'mp3',
            'description': 'md5:b903c63d8585bd59e8cc4d5f80c4272d',
            'duration': 103.15,
            'timestamp': 1741177984,
            'upload_date': '20250305',
        },
    }, {
        'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250307-argentine-le-sac-d-un-alpiniste-retrouv%C3%A9-40-ans-apr%C3%A8s-sa-mort',
        'md5': 'b8c3a63652d4ae8e8092dda5700c1cd9',
        'info_dict': {
            'id': 'WBMZ59102-FLE-FR-20250307',
            'title': 'Argentine: le sac d\'un alpiniste retrouvé 40 ans après sa mort',

Review comment (refactor):
```suggestion
from ..utils import (
    ExtractorError,
    float_or_none,
    url_or_none,
)
```

Code after:
import urllib.parse

from .common import InfoExtractor
from ..networking.exceptions import HTTPError
from ..utils import (
    ExtractorError,
    float_or_none,
    url_or_none,
)
from ..utils.traversal import traverse_obj


class FrancaisFacileIE(InfoExtractor):
    _VALID_URL = r'https?://francaisfacile\.rfi\.fr/[a-z]{2}/(?:actualit%C3%A9|podcasts/[^/#?]+)/(?P<id>[^/#?]+)'
    _TESTS = [{
        'url': 'https://francaisfacile.rfi.fr/fr/actualit%C3%A9/20250305-r%C3%A9concilier-les-jeunes-avec-la-lecture-gr%C3%A2ce-aux-r%C3%A9seaux-sociaux',
        'md5': '4f33674cb205744345cc835991100afa',
        'info_dict': {
            'id': 'WBMZ58952-FLE-FR-20250305',
            'display_id': '20250305-réconcilier-les-jeunes-avec-la-lecture-grâce-aux-réseaux-sociaux',
            'title': 'Réconcilier les jeunes avec la lecture grâce aux réseaux sociaux',
            'url': 'https://aod-fle.akamaized.net/fle/sounds/fr/2025/03/05/6b6af52a-f9ba-11ef-a1f8-005056a97652.mp3',
            'ext': 'mp3',
            'description': 'md5:b903c63d8585bd59e8cc4d5f80c4272d',
            'duration': 103.15,
            'timestamp': 1741177984,
            'upload_date': '20250305',
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/francaisfacile.py (Python)

Code before:
            'timestamp': 1742210897,
            'upload_date': '20250317',
        },
    }]

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

        try:  # yt-dlp's default user-agents are too old and blocked by the site
            webpage = self._download_webpage(url, display_id, headers={
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/136.0',
            })
        except ExtractorError as e:
            if not isinstance(e.cause, HTTPError) or e.cause.status != 403:
                raise
            # Retry with impersonation if hardcoded UA is insufficient
            webpage = self._download_webpage(url, display_id, impersonate=True)

        data = self._search_json(
            r'<script[^>]+\bdata-media-id=[^>]+\btype="application/json"[^>]*>',
            webpage, 'audio data', display_id)

        return {
            'id': data['mediaId'],
            'display_id': display_id,
            'title': self._html_extract_title(webpage),
            **self._search_json_ld(webpage, display_id, fatal=False),
            **traverse_obj(data, {
                'title': ('title', {str}),
                'url': ('sources', ..., 'url', {url_or_none}, any),
                'duration': ('sources', ..., 'duration', {float_or_none}, any),
            }),
        }


Review comment (refactor):
for audio-only http formats we need to do this:
```suggestion
            'display_id': display_id,
            'vcodec': 'none',
            'title': self._html_extract_title(webpage),
```

Code after:
            'description': 'md5:3fe35fae035803df696bfa7af2496e49',
            'duration': 198.96,
            'timestamp': 1742210897,
            'upload_date': '20250317',
        },
    }]

    def _real_extract(self, url):
        display_id = urllib.parse.unquote(self._match_id(url))

        try:  # yt-dlp's default user-agents are too old and blocked by the site
            webpage = self._download_webpage(url, display_id, headers={
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; rv:136.0) Gecko/20100101 Firefox/136.0',
            })
        except ExtractorError as e:
            if not isinstance(e.cause, HTTPError) or e.cause.status != 403:
                raise
            # Retry with impersonation if hardcoded UA is insufficient
            webpage = self._download_webpage(url, display_id, impersonate=True)

        data = self._search_json(
            r'<script[^>]+\bdata-media-id=[^>]+\btype="application/json"[^>]*>',
            webpage, 'audio data', display_id)

        return {
            'id': data['mediaId'],
            'display_id': display_id,
            'vcodec': 'none',
            'title': self._html_extract_title(webpage),
            **self._search_json_ld(webpage, display_id, fatal=False),
            **traverse_obj(data, {
                'title': ('title', {str}),
                'url': ('sources', ..., 'url', {url_or_none}, any),
                'duration': ('sources', ..., 'duration', {float_or_none}, any),
            }),
        }