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

Code before:
        return {
            'id': urn,
            **traverse_obj(item, {
                'formats': ('audioList', lambda _, v: url_or_none(v['href']), {
                    'url': 'href',
                    'format_id': ('distributionType', {str}),
                    'abr': ('audioBitrate', {int_or_none}),
                    'acodec': ('audioCodec', {str}),
                    'vcodec': {value('none')},
                }),
            }),
            **traverse_obj(item, {
                'channel': ('programSet', 'publicationService', 'organizationName', {str}),
                'description': ('description', {str}),
                'duration': ('duration', {int_or_none}),
                'series': ('show', 'title', {str}),
                'episode_number': ('episodeNumber', {int_or_none}),
                'thumbnail': ('image', 'url1X1', {url_or_none}, {update_url(query=None)}),
                'timestamp': ('startDate', {parse_iso8601}),
                'title': ('title', {str}),
            }),
        }


class ARDAudiothekPlaylistIE(ARDAudiothekBaseIE):
    _VALID_URL = r'''https:?//(?:www\.)?ardaudiothek\.de/sendung/(?P<playlist>[a-zA-Z-]+)/(?P<id>urn:ard:show:[a-f0-9]{16})'''

    _TESTS = [
        {
            'url': 'https://www.ardaudiothek.de/sendung/mia-insomnia/urn:ard:show:c405aa26d9a4060a/',
            'info_dict': {
                'display_id': 'mia-insomnia',
                'title': 'Mia Insomnia',
                'id': 'urn:ard:show:c405aa26d9a4060a',
                'description': 'md5:d9ceb7a6b4d26a4db3316573bb564292',
            },
            'playlist_mincount': 37,
        },
    ]

    _QUERY_PLAYLIST = '''
    query($id: ID!) {
        show(id: $id) {
            title
            description
            items(filter: { isPublished: { equalTo: true } }) {
                nodes {
                    url
                }
            }
        }

Review comment (suggestion):
I changed this to `\w` for a reason. Otherwise, URLs such as https://www.ardaudiothek.de/sendung/100-berlin/urn:ard:show:4d248e0806ce37bc/ would not match. Maybe add that as a `only_matching` test.
```suggestion
    _VALID_URL = r'''https:?//(?:www\.)?ardaudiothek\.de/sendung/(?P<playlist>[\w-]+)/(?P<id>urn:ard:show:[a-f0-9]{16})'''
```

Code after:
                    'abr': ('audioBitrate', {int_or_none}),
                    'acodec': ('audioCodec', {str}),
                    'vcodec': {value('none')},
                }),
                'channel': ('programSet', 'publicationService', 'organizationName', {str}),
                'description': ('description', {str}),
                'duration': ('duration', {int_or_none}),
                'series': ('show', 'title', {str}),
                'episode_number': ('episodeNumber', {int_or_none}),
                'thumbnail': ('image', 'url1X1', {url_or_none}, {update_url(query=None)}),
                'timestamp': ('startDate', {parse_iso8601}),
                'title': ('title', {str}),
            }),
        }


class ARDAudiothekPlaylistIE(ARDAudiothekBaseIE):
    _VALID_URL = r'https:?//(?:www\.)?ardaudiothek\.de/sendung/(?P<playlist>[\w-]+)/(?P<id>urn:ard:show:[a-f0-9]{16})'

    _TESTS = [{
        'url': 'https://www.ardaudiothek.de/sendung/mia-insomnia/urn:ard:show:c405aa26d9a4060a/',
        'info_dict': {
            'display_id': 'mia-insomnia',
            'title': 'Mia Insomnia',
            'id': 'urn:ard:show:c405aa26d9a4060a',
            'description': 'md5:d9ceb7a6b4d26a4db3316573bb564292',
        },
        'playlist_mincount': 37,
    }, {
        'url': 'https://www.ardaudiothek.de/sendung/100-berlin/urn:ard:show:4d248e0806ce37bc/',
        'only_matching': True,
    }]

    _QUERY_PLAYLIST = '''
    query($id: ID!) {
        show(id: $id) {
            title
            description
            items(filter: { isPublished: { equalTo: true } }) {
                nodes {
                    url
                }
            }
        }
    }'''

    def _real_extract(self, url):
        urn, playlist = self._match_valid_url(url).group('id', 'playlist')
        playlist_info = self._graphql_query(urn, self._QUERY_PLAYLIST)['show']
        entries = []
        for url in traverse_obj(playlist_info, ('items', 'nodes', ..., 'url', {url_or_none})):