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

Code before:

        if video_info := self._call_api(region, video_id, 'data/secure', token, 'Trying auth token'):
            video_extra = self._call_api(region, video_id, token=token, note='Retrieving extra attributes')
        else:
            video_info = self._call_api(region, video_id, 'data', note='Trying fallback', fatal=True)
            video_extra = {}

        duration = int_or_none(video_info.get('duration'), 1000)
        title = video_info.get('name')
        upload_date = video_info.get('created')

        formats = traverse_obj(video_info, ('extStreams', ..., {
            'url': ('streamUrl', {url_or_none}),
            'container': ('contentType', {mimetype2ext}),
            'aspect_ratio': ('aspectRatio'),
        }))

        for cur_format in formats:
            cur_format['filesize'] = int_or_none(video_extra.get('storageSize'))

        subtitles = {}
        for current_subs in video_info.get('subtitles'):
            lang_code = current_subs.get('lang')
            subtitles.setdefault(lang_code, []).append({
                'name': str_or_none(current_subs.get('label')),
                'url': url_or_none(current_subs['url']),
            })

        for current_chat in video_info.get('chats'):
            subtitles.setdefault('live_chat', []).append({'url': url_or_none(current_chat['url'])})

        return {
            'duration': duration,
            'formats': formats,
            'id': video_id,
            'timestamp': parse_iso8601(upload_date),
            'subtitles': subtitles,
            'title': title,
        }


class BlackboardCollaborateLaunchIE(InfoExtractor):
    _VALID_URL = r'https?://[a-z]+\.bbcollab\.com/launch/(?P<token>[\w\.\-]+)'

    _TESTS = [
        {
            'url': 'https://au.bbcollab.com/launch/eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJiYkNvbGxhYkFwaSIsInN1YiI6ImJiQ29sbGFiQXBpIiwiZXhwIjoxNzQwNDE2NDgzLCJpYXQiOjE3NDA0MTYxODMsInJlc291cmNlQWNjZXNzVGlja2V0Ijp7InJlc291cmNlSWQiOiI3MzI4YzRjZTNmM2U0ZTcwYmY3MTY3N2RkZTgzMzk2NSIsImNvbnN1bWVySWQiOiJhM2Q3NGM0Y2QyZGU0MGJmODFkMjFlODNlMmEzNzM5MCIsInR5cGUiOiJSRUNPUkRJTkciLCJyZXN0cmljdGlvbiI6eyJ0eXBlIjoiVElNRSIsImV4cGlyYXRpb25Ib3VycyI6MCwiZXhwaXJhdGlvbk1pbnV0ZXMiOjUsIm1heFJlcXVlc3RzIjotMX0sImRpc3Bvc2l0aW9uIjoiTEFVTkNIIiwibGF1bmNoVHlwZSI6bnVsbCwibGF1bmNoQ29tcG9uZW50IjpudWxsLCJsYXVuY2hQYXJhbUtleSI6bnVsbH19.xuELw4EafEwUMoYcCHidGn4Tw9O1QCbYHzYGJUl0kKk',
            'only_matching': True,
        },
    ]


Review comment (refactor):
- metadata/subtitles extraction should always strive to be non-fatal
- avoid using `str_or_none` except when you actually want to stringify non-string data (e.g. `int`=>`str`)
```suggestion
        for subs in traverse_obj(video_info, ('subtitles', lambda _, v: url_or_none(v['url']))):
            subtitles.setdefault(subs.get('lang') or 'und', []).append({
                'name': traverse_obj(subs, ('label', {str})),
                'url': subs['url'],
```

Code after:
        if video_info:
            video_extra = self._call_api(region, video_id, token=token, note='Retrieving extra attributes')
        else:
            video_info = self._call_api(region, video_id, path='data', note='Trying fallback', fatal=True)
            video_extra = {}

        formats = traverse_obj(video_info, ('extStreams', lambda _, v: url_or_none(v['streamUrl']), {
            'url': 'streamUrl',
            'ext': ('contentType', {mimetype2ext}),
            'aspect_ratio': ('aspectRatio', {float_or_none}),
        }))

        if filesize := traverse_obj(video_extra, ('storageSize', {int_or_none})):
            for fmt in formats:
                fmt['filesize'] = filesize

        subtitles = {}
        for subs in traverse_obj(video_info, ('subtitles', lambda _, v: url_or_none(v['url']))):
            subtitles.setdefault(subs.get('lang') or 'und', []).append({
                'name': traverse_obj(subs, ('label', {str})),
                'url': subs['url'],
            })

        for live_chat_url in traverse_obj(video_info, ('chats', ..., 'url', {url_or_none})):
            subtitles.setdefault('live_chat', []).append({'url': live_chat_url})

        return {
            **traverse_obj(video_info, {
                'title': ('name', {str}),
                'timestamp': ('created', {parse_iso8601}),
                'duration': ('duration', {int_or_none(scale=1000)}),
            }),
            'formats': formats,
            'id': video_id,
            'subtitles': subtitles,
        }


class BlackboardCollaborateLaunchIE(InfoExtractor):
    _VALID_URL = r'https?://[a-z]+\.bbcollab\.com/launch/(?P<id>[^/?#]+)'

    _TESTS = [
        {
            'url': 'https://au.bbcollab.com/launch/eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJiYkNvbGxhYkFwaSIsInN1YiI6ImJiQ29sbGFiQXBpIiwiZXhwIjoxNzQwNDE2NDgzLCJpYXQiOjE3NDA0MTYxODMsInJlc291cmNlQWNjZXNzVGlja2V0Ijp7InJlc291cmNlSWQiOiI3MzI4YzRjZTNmM2U0ZTcwYmY3MTY3N2RkZTgzMzk2NSIsImNvbnN1bWVySWQiOiJhM2Q3NGM0Y2QyZGU0MGJmODFkMjFlODNlMmEzNzM5MCIsInR5cGUiOiJSRUNPUkRJTkciLCJyZXN0cmljdGlvbiI6eyJ0eXBlIjoiVElNRSIsImV4cGlyYXRpb25Ib3VycyI6MCwiZXhwaXJhdGlvbk1pbnV0ZXMiOjUsIm1heFJlcXVlc3RzIjotMX0sImRpc3Bvc2l0aW9uIjoiTEFVTkNIIiwibGF1bmNoVHlwZSI6bnVsbCwibGF1bmNoQ29tcG9uZW50IjpudWxsLCJsYXVuY2hQYXJhbUtleSI6bnVsbH19.xuELw4EafEwUMoYcCHidGn4Tw9O1QCbYHzYGJUl0kKk',
            'only_matching': True,
        },
        {
            'url': 'https://us.bbcollab.com/launch/eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJiYkNvbGxhYkFwaSIsInN1YiI6ImJiQ29sbGFiQXBpIiwiZXhwIjoxNjk0NDgxOTc3LCJpYXQiOjE2OTQ0ODE2NzcsInJlc291cmNlQWNjZXNzVGlja2V0Ijp7InJlc291cmNlSWQiOiI3YWU0MTFhNTU3NjU0OWFiOTZlYjVmMTM1YmY3MWU5MCIsImNvbnN1bWVySWQiOiJBRUU2MEI4MDI2QzM3ODU2RjMwMzNEN0ZEOTQzMTFFNSIsInR5cGUiOiJSRUNPUkRJTkciLCJyZXN0cmljdGlvbiI6eyJ0eXBlIjoiVElNRSIsImV4cGlyYXRpb25Ib3VycyI6MCwiZXhwaXJhdGlvbk1pbnV0ZXMiOjUsIm1heFJlcXVlc3RzIjotMX0sImRpc3Bvc2l0aW9uIjoiTEFVTkNIIiwibGF1bmNoVHlwZSI6bnVsbCwibGF1bmNoQ29tcG9uZW50IjpudWxsLCJsYXVuY2hQYXJhbUtleSI6bnVsbH19.yOhRZNaIjXYoMYMpcTzgjZJCnIFaYf2cAzbco8OAxlY',
            'only_matching': True,
        },
        {