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

Code before:
    _TESTS = [{
        'url': 'https://sound.orf.at/podcast/oe3/fruehstueck-bei-mir/nicolas-stockhammer-15102023',
        'md5': '526a5700e03d271a1505386a8721ab9b',
        'info_dict': {
            'id': 'nicolas-stockhammer-15102023',
            'ext': 'mp3',
            'title': 'Nicolas Stockhammer (15.10.2023)',
            'duration': 3396.0,
            'series': 'Frühstück bei mir',
        },
        'skip': 'ORF podcasts are only available for a limited time'
    }]

    def _real_extract(self, url):
        station, show, show_id = self._match_valid_url(url).group('station', 'show', 'id')
        data = self._download_json(
            f'https://audioapi.orf.at/radiothek/api/2.0/podcast/{station}/{show}/{show_id}', show_id)

        return {
            'id': show_id,
            'url': data.get('payload').get('enclosures')[0]['url'],
            'ext': 'mp3',
            'title': data.get('payload').get('title'),
            'description': clean_html(data.get('payload').get('description')),
            'duration': data.get('payload').get('duration') / 1000,
            'series': data.get('payload').get('podcast').get('title'),
        }


class ORFIPTVIE(InfoExtractor):
    IE_NAME = 'orf:iptv'
    IE_DESC = 'iptv.ORF.at'
    _VALID_URL = r'https?://iptv\.orf\.at/(?:#/)?stories/(?P<id>\d+)'

    _TEST = {
        'url': 'http://iptv.orf.at/stories/2275236/',
        'md5': 'c8b22af4718a4b4af58342529453e3e5',
        'info_dict': {
            'id': '350612',
            'ext': 'flv',
            'title': 'Weitere Evakuierungen um Vulkan Calbuco',
            'description': 'md5:d689c959bdbcf04efeddedbf2299d633',
            'duration': 68.197,
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20150425',
        },
    }

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


Review comment (suggestion):
```suggestion
            'ext': 'mp3',
            **traverse_obj(data, ('payload', {
                'url': ('enclosures', 0, 'url'),
                'title': 'title',
                'description': ('description', {clean_html}),
                'duration': ('duration', {functools.partial(float_or_none, scale=1000)}),
                'series': ('podcast', 'title'),
            })),
```
Make sure to also add `from ..utils.traversal import traverse_obj`

Code after:
    _VALID_URL = rf'https?://sound\.orf\.at/podcast/(?P<station>{_STATION_RE})/(?P<show>[\w-]+)/(?P<id>[\w-]+)'
    _TESTS = [{
        'url': 'https://sound.orf.at/podcast/oe3/fruehstueck-bei-mir/nicolas-stockhammer-15102023',
        'md5': '526a5700e03d271a1505386a8721ab9b',
        'info_dict': {
            'id': 'nicolas-stockhammer-15102023',
            'ext': 'mp3',
            'title': 'Nicolas Stockhammer (15.10.2023)',
            'duration': 3396.0,
            'series': 'Frühstück bei mir',
        },
        'skip': 'ORF podcasts are only available for a limited time'
    }]

    def _real_extract(self, url):
        station, show, show_id = self._match_valid_url(url).group('station', 'show', 'id')
        data = self._download_json(
            f'https://audioapi.orf.at/radiothek/api/2.0/podcast/{station}/{show}/{show_id}', show_id)

        return {
            'id': show_id,
            'ext': 'mp3',
            'vcodec': 'none',
            **traverse_obj(data, ('payload', {
                'url': ('enclosures', 0, 'url'),
                'ext': ('enclosures', 0, 'type', {mimetype2ext}),
                'title': 'title',
                'description': ('description', {clean_html}),
                'duration': ('duration', {functools.partial(float_or_none, scale=1000)}),
                'series': ('podcast', 'title'),
            })),
        }


class ORFIPTVIE(InfoExtractor):
    IE_NAME = 'orf:iptv'
    IE_DESC = 'iptv.ORF.at'
    _VALID_URL = r'https?://iptv\.orf\.at/(?:#/)?stories/(?P<id>\d+)'

    _TEST = {
        'url': 'http://iptv.orf.at/stories/2275236/',
        'md5': 'c8b22af4718a4b4af58342529453e3e5',
        'info_dict': {
            'id': '350612',
            'ext': 'flv',
            'title': 'Weitere Evakuierungen um Vulkan Calbuco',
            'description': 'md5:d689c959bdbcf04efeddedbf2299d633',
            'duration': 68.197,
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20150425',
        },