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

Code before:
            'was_live': True,
            'live_status': 'was_live',
            'timestamp': 1635874720,
            'release_timestamp': 1635874720,
            'release_date': '20211102',
            'upload_date': '20211102',
            'dislike_count': int,
            'like_count': int,
            'tags': ['FreeThinkingMedia^'],
        }
    }]

    def _real_extract(self, url):
        video_id, video_type = self._match_valid_url(url).group('id', 'type')
        metadata = self._download_json_using_access_token(f'{_API_BASE_URL}{video_id}', video_id)

        scheduled = unified_timestamp(metadata.get('scheduledAt'))
        live_status = ('was_live' if metadata.get('stoppedAt')
                       else 'is_upcoming' if scheduled
                       else 'is_live' if video_type == 'stream'
                       else 'not_live')

        video_url = traverse_obj(metadata, 'url', ('content', 'contentUrl'), expected_type=url_or_none)
        timeline_url = traverse_obj(metadata, 'timelineUrl', ('content', 'timelineUrl'), expected_type=url_or_none)
        if (not video_url or video_url == 'fake.m3u8') and timeline_url:
            video_url = re.sub('https://image.v.rokfin.com/([^/]+)/storyboard.vtt$', 'https://stream.v.rokfin.com/\\1.m3u8', timeline_url)

        formats, subtitles = [{'url': video_url}] if video_url else [], {}
        if determine_ext(video_url) == 'm3u8':
            formats, subtitles = self._extract_m3u8_formats_and_subtitles(
                video_url, video_id, fatal=False, live=live_status == 'is_live')

        if not formats:
            if traverse_obj(metadata, 'premiumPlan', 'premium'):
                self.raise_login_required('This video is only available to premium users', True, method='cookies')
            elif scheduled:
                self.raise_no_formats(
                    f'Stream is offline; scheduled for {datetime.fromtimestamp(scheduled).strftime("%Y-%m-%d %H:%M:%S")}',
                    video_id=video_id, expected=True)
        self._sort_formats(formats)

        uploader = traverse_obj(metadata, ('createdBy', 'username'), ('creator', 'username'))
        timestamp = (scheduled or float_or_none(metadata.get('postedAtMilli'), 1000)
                     or unified_timestamp(metadata.get('creationDateTime')))
        return {
            'id': video_id,
            'formats': formats,
            'subtitles': subtitles,
            'title': str_or_none(traverse_obj(metadata, 'title', ('content', 'contentTitle'))),
            'duration': float_or_none(traverse_obj(metadata, ('content', 'duration'))),
            'thumbnail': url_or_none(traverse_obj(metadata, 'thumbnail', ('content', 'thumbnailUrl1'))),

Review comment (bug):
If they change the url format, instead of failing, this would just put `video_url = timeline_url` which we don't want. Maybe instead of `re.sub`, `_search_regex` the slug out first?

Like this, for e.g.
```suggestion
        if video_url in (None, 'fake.m3u8'):
            video_url = format_field(self._search_regex(
                r'https?://[^/]+/([^/]+)/storyboard.vtt',
                traverse_obj(metadata, 'timelineUrl', ('content', 'timelineUrl'), expected_type=url_or_none),
                video_id, default=None), None, 'https://stream.v.rokfin.com/%s.m3u8')
```

Code after:
            'is_live': False,
            'was_live': True,
            'live_status': 'was_live',
            'timestamp': 1635874720,
            'release_timestamp': 1635874720,
            'release_date': '20211102',
            'upload_date': '20211102',
            'dislike_count': int,
            'like_count': int,
            'tags': ['FreeThinkingMedia^'],
            'duration': None,
        }
    }, {
        'url': 'https://rokfin.com/post/126703/Brave-New-World--Aldous-Huxley-DEEPDIVE--Chpts-13--Quite-Frankly--Jay-Dyer',
        'info_dict': {
            'id': 'post/126703',
            'ext': 'mp4',
            'title': 'Brave New World - Aldous Huxley DEEPDIVE!  (Chpts 1-3) - Quite Frankly & Jay Dyer',
            'thumbnail': r're:https://img\.production\.rokfin\.com/.+',
            'channel': 'Jay Dyer',
            'channel_id': 186881,
            'channel_url': 'https://rokfin.com/jaydyer',
            'availability': 'premium_only',
            'live_status': 'not_live',
            'dislike_count': int,
            'like_count': int,
            'timestamp': 1678213357,
            'upload_date': '20230307',
            'tags': ['FreeThinkingMedia^', 'OpenMind^'],
            'description': 'md5:cb04e32e68326c9b2b251b297bacff35',
            'duration': 3100,
        }
    }, {
        'url': 'https://rokfin.com/stream/31332/The-Grayzone-live-on-Nordstream-blame-game',
        'info_dict': {
            'id': 'stream/31332',
            'ext': 'mp4',
            'title': 'The Grayzone live on Nordstream blame game',
            'thumbnail': r're:https://image\.v\.rokfin\.com/.+',
            'channel': 'Max Blumenthal',
            'channel_id': 248902,
            'channel_url': 'https://rokfin.com/MaxBlumenthal',
            'availability': 'premium_only',
            'live_status': 'was_live',
            'dislike_count': int,
            'like_count': int,
            'timestamp': 1678475166,
            'release_timestamp': 1678475166.0,
            'release_date': '20230310',
            'upload_date': '20230310',
            'tags': ['FreeThinkingMedia^'],