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

Code before:

        self._sort_formats(formats)

        result_obj = {'formats': formats}

        result_obj.update({'id': video_id})

        title = video.get('title')
        if title is not None:
            result_obj.update({'title': title})

        series = video.get('seriestitle')
        if series is not None:
            result_obj.update({'series': series})

        season_id = video.get('series')
        if season_id is not None:
            result_obj.update({'season_id': season_id})

        creator = video.get('creators', {}).get('creator')
        if creator is not None:
            result_obj.update({'creator': creator})

        timestamp = parse_iso8601(video.get('start'))
        if timestamp is not None:
            result_obj.update({'timestamp': timestamp})

        attachments = video.get('attachments', {}).get('attachment', [])
        if len(attachments) > 0:
            thumbnail = attachments[0].get('url')
            result_obj.update({'thumbnail': thumbnail})

        return result_obj


class OpencastIE(OpencastBaseIE):
    _VALID_URL = r'''(?x)
                    https?://(?P<host>%s)/paella/ui/watch.html\?.*?
                    id=(?P<id>%s)
                    ''' % (
        OpencastBaseIE._INSTANCES_RE,
        OpencastBaseIE._UUID_RE,
    )

    _API_BASE = 'https://%s/search/episode.json?id=%s'

    _TESTS = [
        {
            'url': 'https://oc-video1.ruhr-uni-bochum.de/paella/ui/watch.html?id=ed063cd5-72c8-46b5-a60a-569243edcea8',
            'md5': '554c8e99a90f7be7e874619fcf2a3bc9',
            'info_dict': {

Review comment (suggestion):
```suggestion
        return {
            'id': video_id,
            'formats': formats,
            'title': video.get('title'),
            'series': video.get('seriestitle'),
            'season_id': video.get('series'),
            'creator': traverse_obj(video, ('creators', 'creator')),
            'timestamp': parse_iso8601(video.get('start')),
        }
```

`None` value for mostly any key (exceptions are `id` and either `url` or `formats`) are allowed here

Code after:
                    https?://(?P<host>%s)/paella/ui/watch.html\?.*?
                    id=(?P<id>%s)
                    ''' % (OpencastBaseIE._INSTANCES_RE, OpencastBaseIE._UUID_RE)

    _API_BASE = 'https://%s/search/episode.json?id=%s'

    _TESTS = [
        {
            'url': 'https://oc-video1.ruhr-uni-bochum.de/paella/ui/watch.html?id=ed063cd5-72c8-46b5-a60a-569243edcea8',
            'md5': '554c8e99a90f7be7e874619fcf2a3bc9',
            'info_dict': {
                'id': 'ed063cd5-72c8-46b5-a60a-569243edcea8',
                'ext': 'mp4',
                'title': '11 - Kryptographie - 24.11.2015',
                'thumbnail': r're:^https?://.*\.jpg$',
                'timestamp': 1606208400,
                'upload_date': '20201124',
            },
        }
    ]

    def _real_extract(self, url):
        host, video_id = self._match_valid_url(url).group('host', 'id')
        return self._parse_mediapackage(
            self._call_api(host, video_id)['search-results']['result']['mediapackage'])


class OpencastPlaylistIE(OpencastBaseIE):
    _VALID_URL = r'''(?x)
                            https?://(?P<host>%s)/engage/ui/index.html\?.*?
                            epFrom=(?P<id>%s)
                    ''' % (OpencastBaseIE._INSTANCES_RE, OpencastBaseIE._UUID_RE)

    _API_BASE = 'https://%s/search/episode.json?sid=%s'

    _TESTS = [
        {
            'url': 'https://oc-video1.ruhr-uni-bochum.de/engage/ui/index.html?epFrom=cf68a4a1-36b1-4a53-a6ba-61af5705a0d0',
            'info_dict': {
                'id': 'cf68a4a1-36b1-4a53-a6ba-61af5705a0d0',
                'title': 'Kryptographie - WiSe 15/16',
            },
            'playlist_mincount': 28,
        },
        {
            'url': 'https://oc-video.ruhr-uni-bochum.de/engage/ui/index.html?e=1&p=1&epFrom=b1a54262-3684-403f-9731-8e77c3766f9a',
            'info_dict': {
                'id': 'b1a54262-3684-403f-9731-8e77c3766f9a',
                'title': 'inSTUDIES-Social movements and prefigurative politics in a global perspective',
            },
            'playlist_mincount': 6,