code:
import json
import re
import time
import urllib.parse
import uuid

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    float_or_none,
    int_or_none,
    strip_or_none,
    traverse_obj,
    try_call,
    unified_timestamp,
)

class RedBeeBaseIE(InfoExtractor):
    _DEVICE_ID = str(uuid.uuid4())

    @property
    def _API_URL(self):
        """
        Ref: https://apidocs.emp.ebsd.ericsson.net
        Subclasses must set _REDBEE_CUSTOMER, _REDBEE_BUSINESS_UNIT
        """
        return f'https://exposure.api.redbee.live/v2/customer/{self._REDBEE_CUSTOMER}/businessunit/{self._REDBEE_BUSINESS_UNIT}'

    def _get_bearer_token(self, asset_id, jwt=None):
        request = {
            'deviceId': self._DEVICE_ID,
            'device': {
                'deviceId': self._DEVICE_ID,
                'name': 'Mozilla Firefox 102',
                'type': 'WEB',
            },
        }
        if jwt:
            request['jwt'] = jwt

        return self._download_json(
            f'{self._API_URL}/auth/{"gigyaLogin" if jwt else "anonymous"}',
            asset_id, data=json.dumps(request).encode('utf-8'), headers={
                'Content-Type': 'application/json;charset=utf-8'
            })['sessionToken']

    def _get_formats_and_subtitles(self, asset_id, **kwargs):
        bearer_token = self._get_bearer_token(asset_id, **kwargs)
        api_response = self._download_json(
            f'{self._API_URL}/entitlement/{asset_id}/play',
            asset_id, headers={
                'Authorization': f'Bearer {bearer_token}',
                'Accept': 'application/json, text/plain, */*'
            })

        formats, subtitles = [], {}
        for format in api_response['formats']:
            if not format.get('mediaLocator'):
                continue

            fmts, subs = [], {}
            if format.get('format') == 'DASH':
                fmts, subs = self._extract_mpd_formats_and_subtitles(
                    format['mediaLocator'], asset_id, fatal=False)
            elif format.get('format') == 'SMOOTHSTREAMING':
                fmts, subs = self._extract_ism_formats_and_subtitles(
                    format['mediaLocator'], asset_id, fatal=False)
            elif format.get('format') == 'HLS':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(
                    format['mediaLocator'], asset_id, fatal=False)

            if format.get('drm'):
                for f in fmts:
                    f['has_drm'] = True

            formats.extend(fmts)
            self._merge_subtitles(subs, target=subtitles)

        return formats, subtitles

class ParliamentLiveUKIE(RedBeeBaseIE):
    IE_NAME = 'parliamentlive.tv'
    IE_DESC = 'UK parliament videos'
    _VALID_URL = r'(?i)https?://(?:www\.)?parliamentlive\.tv/Event/Index/(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'

    _REDBEE_CUSTOMER = 'UKParliament'
    _REDBEE_BUSINESS_UNIT = 'ParliamentLive'

    _TESTS = [{
        'url': 'http://parliamentlive.tv/Event/Index/c1e9d44d-fd6c-4263-b50f-97ed26cc998b',
        'info_dict': {
            'id': 'c1e9d44d-fd6c-4263-b50f-97ed26cc998b',
            'ext': 'mp4',
            'title': 'Home Affairs Committee',
            'timestamp': 1395153872,
            'upload_date': '20140318',
            'thumbnail': r're:https?://[^?#]+c1e9d44d-fd6c-4263-b50f-97ed26cc998b[^/]*/thumbnail',
        },
    }, {
        'url': 'http://parliamentlive.tv/event/index/3f24936f-130f-40bf-9a5d-b3d6479da6a4',
        'only_matching': True,
    }, {
        'url': 'https://parliamentlive.tv/Event/Index/27cf25e4-e77b-42a3-93c5-c815cd6d7377',
        'info_dict': {
            'id': '27cf25e4-e77b-42a3-93c5-c815cd6d7377',
            'ext': 'mp4',
            'title': 'House of Commons',
            'timestamp': 1658392447,
            'upload_date': '20220721',
            'thumbnail': r're:https?://[^?#]+27cf25e4-e77b-42a3-93c5-c815cd6d7377[^/]*/thumbnail',
        },
    }]

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

        formats, subtitles = self._get_formats_and_subtitles(video_id)
        self._sort_formats(formats)

        video_info = self._download_json(
            f'https://www.parliamentlive.tv/Event/GetShareVideo/{video_id}', video_id, fatal=False)

        self._sort_formats(formats, ['res', 'proto'])

        return {
            'id': video_id,
            'formats': formats,
            'subtitles': subtitles,
            'title': traverse_obj(video_info, ('event', 'title')),
            'thumbnail': traverse_obj(video_info, 'thumbnailUrl'),
            'timestamp': traverse_obj(
                video_info, ('event', 'publishedStartTime'), expected_type=unified_timestamp),
        }

class RTBFIE(RedBeeBaseIE):
    _VALID_URL = r'''(?x)
        https?://(?:www\.)?rtbf\.be/
        (?:
            video/[^?]+\?.*\bid=|
            ouftivi/(?:[^/]+/)*[^?]+\?.*\bvideoId=|
            auvio/[^/]+\?.*\b(?P<live>l)?id=
        )(?P<id>\d+)'''
    _NETRC_MACHINE = 'rtbf'

    _REDBEE_CUSTOMER = 'RTBF'
    _REDBEE_BUSINESS_UNIT = 'Auvio'

    _TESTS = [{
        'url': 'https://www.rtbf.be/video/detail_les-diables-au-coeur-episode-2?id=1921274',
        'md5': '8c876a1cceeb6cf31b476461ade72384',
        'info_dict': {
            'id': '1921274',
            'ext': 'mp4',
            'title': 'Les Diables au coeur (épisode 2)',
            'description': '(du 25/04/2014)',
            'duration': 3099.54,
            'upload_date': '20140425',
            'timestamp': 1398456300,
        },
        'skip': 'No longer available',
    }, {
        # geo restricted
        'url': 'http://www.rtbf.be/ouftivi/heros/detail_scooby-doo-mysteres-associes?id=1097&videoId=2057442',
        'only_matching': True,
    }, {
        'url': 'http://www.rtbf.be/ouftivi/niouzz?videoId=2055858',
        'only_matching': True,
    }, {
        'url': 'http://www.rtbf.be/auvio/detail_jeudi-en-prime-siegfried-bracke?id=2102996',
        'only_matching': True,
    }, {
        # Live
        'url': 'https://www.rtbf.be/auvio/direct_pure-fm?lid=134775',
        'only_matching': True,
    }, {
        # Audio
        'url': 'https://www.rtbf.be/auvio/detail_cinq-heures-cinema?id=2360811',
        'only_matching': True,
    }, {
        # With Subtitle
        'url': 'https://www.rtbf.be/auvio/detail_les-carnets-du-bourlingueur?id=2361588',
        'only_matching': True,
    }, {
        'url': 'https://www.rtbf.be/auvio/detail_investigation?id=2921926',
        'md5': 'd5d11bb62169fef38d7ce7ac531e034f',
        'info_dict': {
            'id': '2921926',
            'ext': 'mp4',
            'title': 'Le handicap un confinement perpétuel - Maladie de Lyme',
            'description': 'md5:dcbd5dcf6015488c9069b057c15ccc52',
            'duration': 5258.8,
            'upload_date': '20220727',
            'timestamp': 1658934000,
            'series': '#Investigation',
            'thumbnail': r're:^https?://[^?&]+\.jpg$',
        },
    }, {
        'url': 'https://www.rtbf.be/auvio/detail_la-belgique-criminelle?id=2920492',
        'md5': '054f9f143bc79c89647c35e5a7d35fa8',
        'info_dict': {
            'id': '2920492',
            'ext': 'mp4',
            'title': '04 - Le crime de la rue Royale',
            'description': 'md5:0c3da1efab286df83f2ab3f8f96bd7a6',
            'duration': 1574.6,
            'upload_date': '20220723',
            'timestamp': 1658596887,
            'series': 'La Belgique criminelle - TV',
            'thumbnail': r're:^https?://[^?&]+\.jpg$',
        },
    }]

    _IMAGE_HOST = 'http://ds1.ds.static.rtbf.be'
    _PROVIDERS = {
        'YOUTUBE': 'Youtube',
        'DAILYMOTION': 'Dailymotion',
        'VIMEO': 'Vimeo',
    }
    _QUALITIES = [
        ('mobile', 'SD'),
        ('web', 'MD'),
        ('high', 'HD'),
    ]
    _LOGIN_URL = 'https://login.rtbf.be/accounts.login'
    _GIGYA_API_KEY = '3_kWKuPgcdAybqnqxq_MvHVk0-6PN8Zk8pIIkJM_yXOu-qLPDDsGOtIDFfpGivtbeO'
    _LOGIN_COOKIE_ID = f'glt_{_GIGYA_API_KEY}'

    def _perform_login(self, username, password):
        if self._get_cookies(self._LOGIN_URL).get(self._LOGIN_COOKIE_ID):
            return

        self._set_cookie('.rtbf.be', 'gmid', 'gmid.ver4', secure=True, expire_time=time.time() + 3600)

        login_response = self._download_json(
            self._LOGIN_URL, None, data=urllib.parse.urlencode({
                'loginID': username,
                'password': password,
                'APIKey': self._GIGYA_API_KEY,
                'targetEnv': 'jssdk',
                'sessionExpiration': '-2',
            }).encode('utf-8'), headers={
                'Content-Type': 'application/x-www-form-urlencoded',
            })

        if login_response['statusCode'] != 200:
            raise ExtractorError('Login failed. Server message: %s' % login_response['errorMessage'], expected=True)

        self._set_cookie('.rtbf.be', self._LOGIN_COOKIE_ID, login_response['sessionInfo']['login_token'],
                         secure=True, expire_time=time.time() + 3600)

    def _get_formats_and_subtitles(self, url, media_id):
        login_token = self._get_cookies(url).get(self._LOGIN_COOKIE_ID)
        if not login_token:
            self.raise_login_required()

        session_jwt = try_call(lambda: self._get_cookies(url)['rtbf_jwt'].value) or self._download_json(
            'https://login.rtbf.be/accounts.getJWT', media_id, query={
                'login_token': login_token.value,
                'APIKey': self._GIGYA_API_KEY,
                'sdk': 'js_latest',
                'authMode': 'cookie',
                'pageURL': url,
                'sdkBuild': '13273',
                'format': 'json',
            })['id_token']

        return super()._get_formats_and_subtitles(media_id, jwt=session_jwt)

    def _real_extract(self, url):
        live, media_id = self._match_valid_url(url).groups()
        embed_page = self._download_webpage(
            'https://www.rtbf.be/auvio/embed/' + ('direct' if live else 'media'),
            media_id, query={'id': media_id})

        media_data = self._html_search_regex(r'data-media="([^"]+)"', embed_page, 'media data', fatal=False)
        if not media_data:
            if re.search(r'<div[^>]+id="js-error-expired"[^>]+class="(?![^"]*hidden)', embed_page):
                raise ExtractorError('Livestream has ended.', expected=True)
            if re.search(r'<div[^>]+id="js-sso-connect"[^>]+class="(?![^"]*hidden)', embed_page):
                self.raise_login_required()

            raise ExtractorError('Could not find media data')

        data = self._parse_json(media_data, media_id)

        error = data.get('error')
        if error:
            raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)

        provider = data.get('provider')
        if provider in self._PROVIDERS:
            return self.url_result(data['url'], self._PROVIDERS[provider])

        title = traverse_obj(data, 'subtitle', 'title')
        is_live = data.get('isLive')
        height_re = r'-(\d+)p\.'
        formats, subtitles = [], {}

        # The old api still returns m3u8 and mpd manifest for livestreams, but these are 'fake'
        # since all they contain is a 20s video that is completely unrelated.
        # https://github.com/yt-dlp/yt-dlp/issues/4656#issuecomment-1214461092
        m3u8_url = None if data.get('isLive') else traverse_obj(data, 'urlHlsAes128', 'urlHls')
        if m3u8_url:
            fmts, subs = self._extract_m3u8_formats_and_subtitles(
                m3u8_url, media_id, 'mp4', m3u8_id='hls', fatal=False)
            formats.extend(fmts)
            self._merge_subtitles(subs, target=subtitles)

        fix_url = lambda x: x.replace('//rtbf-vod.', '//rtbf.') if '/geo/drm/' in x else x
        http_url = data.get('url')
        if formats and http_url and re.search(height_re, http_url):
            http_url = fix_url(http_url)
            for m3u8_f in formats[:]:
                height = m3u8_f.get('height')
                if not height:
                    continue
                f = m3u8_f.copy()
                del f['protocol']
                f.update({
                    'format_id': m3u8_f['format_id'].replace('hls-', 'http-'),
                    'url': re.sub(height_re, '-%dp.' % height, http_url),
                })
                formats.append(f)
        else:
            sources = data.get('sources') or {}
            for key, format_id in self._QUALITIES:
                format_url = sources.get(key)
                if not format_url:
                    continue
                height = int_or_none(self._search_regex(
                    height_re, format_url, 'height', default=None))
                formats.append({
                    'format_id': format_id,
                    'url': fix_url(format_url),
                    'height': height,
                })

        mpd_url = None if data.get('isLive') else data.get('urlDash')
        if mpd_url and (self.get_param('allow_unplayable_formats') or not data.get('drm')):
            fmts, subs = self._extract_mpd_formats_and_subtitles(
                mpd_url, media_id, mpd_id='dash', fatal=False)
            formats.extend(fmts)
            self._merge_subtitles(subs, target=subtitles)

        audio_url = data.get('urlAudio')
        if audio_url:
            formats.append({
                'format_id': 'audio',
                'url': audio_url,
                'vcodec': 'none',
            })

        for track in (data.get('tracks') or {}).values():
            sub_url = track.get('url')
            if not sub_url:
                continue
            subtitles.setdefault(track.get('lang') or 'fr', []).append({
                'url': sub_url,
            })

        if not formats:
            fmts, subs = self._get_formats_and_subtitles(url, f'live_{media_id}' if is_live else media_id)
            formats.extend(fmts)
            self._merge_subtitles(subs, target=subtitles)

        self._sort_formats(formats, ['res', 'proto'])
        return {
            'id': media_id,
            'formats': formats,
            'title': title,
            'description': strip_or_none(data.get('description')),
            'thumbnail': data.get('thumbnail'),
            'duration': float_or_none(data.get('realDuration')),
            'timestamp': int_or_none(data.get('liveFrom')),
            'series': data.get('programLabel'),
            'subtitles': subtitles,
            'is_live': is_live,
        }


code:
from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    float_or_none,
    ExtractorError,
)

class RedBullTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?redbull(?:\.tv|\.com(?:/[^/]+)?(?:/tv)?)(?:/events/[^/]+)?/(?:videos?|live|(?:film|episode)s)/(?P<id>AP-\w+)'
    _TESTS = [{
        # film
        'url': 'https://www.redbull.tv/video/AP-1Q6XCDTAN1W11',
        'md5': 'fb0445b98aa4394e504b413d98031d1f',
        'info_dict': {
            'id': 'AP-1Q6XCDTAN1W11',
            'ext': 'mp4',
            'title': 'ABC of... WRC - ABC of... S1E6',
            'description': 'md5:5c7ed8f4015c8492ecf64b6ab31e7d31',
            'duration': 1582.04,
        },
    }, {
        # episode
        'url': 'https://www.redbull.tv/video/AP-1PMHKJFCW1W11',
        'info_dict': {
            'id': 'AP-1PMHKJFCW1W11',
            'ext': 'mp4',
            'title': 'Grime - Hashtags S2E4',
            'description': 'md5:5546aa612958c08a98faaad4abce484d',
            'duration': 904,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.redbull.com/int-en/tv/video/AP-1UWHCAR9S1W11/rob-meets-sam-gaze?playlist=playlists::3f81040a-2f31-4832-8e2e-545b1d39d173',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/us-en/videos/AP-1YM9QCYE52111',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/us-en/events/AP-1XV2K61Q51W11/live/AP-1XUJ86FDH1W11',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/films/AP-1ZSMAW8FH2111',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/episodes/AP-1TQWK7XE11W11',
        'only_matching': True,
    }]

    def extract_info(self, video_id):
        session = self._download_json(
            'https://api.redbull.tv/v3/session', video_id,
            note='Downloading access token', query={
                'category': 'personal_computer',
                'os_family': 'http',
            })
        if session.get('code') == 'error':
            raise ExtractorError('%s said: %s' % (
                self.IE_NAME, session['message']))
        token = session['token']

        try:
            video = self._download_json(
                'https://api.redbull.tv/v3/products/' + video_id,
                video_id, note='Downloading video information',
                headers={'Authorization': token}
            )
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 404:
                error_message = self._parse_json(
                    e.cause.read().decode(), video_id)['error']
                raise ExtractorError('%s said: %s' % (
                    self.IE_NAME, error_message), expected=True)
            raise

        title = video['title'].strip()

        formats, subtitles = self._extract_m3u8_formats_and_subtitles(
            'https://dms.redbull.tv/v3/%s/%s/playlist.m3u8' % (video_id, token),
            video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls')
        self._sort_formats(formats)

        for resource in video.get('resources', []):
            if resource.startswith('closed_caption_'):
                splitted_resource = resource.split('_')
                if splitted_resource[2]:
                    subtitles.setdefault('en', []).append({
                        'url': 'https://resources.redbull.tv/%s/%s' % (video_id, resource),
                        'ext': splitted_resource[2],
                    })

        subheading = video.get('subheading')
        if subheading:
            title += ' - %s' % subheading

        return {
            'id': video_id,
            'title': title,
            'description': video.get('long_description') or video.get(
                'short_description'),
            'duration': float_or_none(video.get('duration'), scale=1000),
            'formats': formats,
            'subtitles': subtitles,
        }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        return self.extract_info(video_id)

class RedBullEmbedIE(RedBullTVIE):
    _VALID_URL = r'https?://(?:www\.)?redbull\.com/embed/(?P<id>rrn:content:[^:]+:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}:[a-z]{2}-[A-Z]{2,3})'
    _TESTS = [{
        # HLS manifest accessible only using assetId
        'url': 'https://www.redbull.com/embed/rrn:content:episode-videos:f3021f4f-3ed4-51ac-915a-11987126e405:en-INT',
        'only_matching': True,
    }]
    _VIDEO_ESSENSE_TMPL = '''... on %s {
      videoEssence {
        attributes
      }
    }'''

    def _real_extract(self, url):
        rrn_id = self._match_id(url)
        asset_id = self._download_json(
            'https://edge-graphql.crepo-production.redbullaws.com/v1/graphql',
            rrn_id, headers={
                'Accept': 'application/json',
                'API-KEY': 'e90a1ff11335423998b100c929ecc866',
            }, query={
                'query': '''{
  resource(id: "%s", enforceGeoBlocking: false) {
    %s
    %s
  }
}''' % (rrn_id, self._VIDEO_ESSENSE_TMPL % 'LiveVideo', self._VIDEO_ESSENSE_TMPL % 'VideoResource'),
            })['data']['resource']['videoEssence']['attributes']['assetId']
        return self.extract_info(asset_id)

class RedBullTVRrnContentIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?redbull\.com/(?P<region>[a-z]{2,3})-(?P<lang>[a-z]{2})/tv/(?:video|live|film)/(?P<id>rrn:content:[^:]+:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
    _TESTS = [{
        'url': 'https://www.redbull.com/int-en/tv/video/rrn:content:live-videos:e3e6feb4-e95f-50b7-962a-c70f8fd13c73/mens-dh-finals-fort-william',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/tv/video/rrn:content:videos:a36a0f36-ff1b-5db8-a69d-ee11a14bf48b/tn-ts-style?playlist=rrn:content:event-profiles:83f05926-5de8-5389-b5e4-9bb312d715e8:extras',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/tv/film/rrn:content:films:d1f4d00e-4c04-5d19-b510-a805ffa2ab83/follow-me',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        region, lang, rrn_id = self._match_valid_url(url).groups()
        rrn_id += ':%s-%s' % (lang, region.upper())
        return self.url_result(
            'https://www.redbull.com/embed/' + rrn_id,
            RedBullEmbedIE.ie_key(), rrn_id)

class RedBullIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?redbull\.com/(?P<region>[a-z]{2,3})-(?P<lang>[a-z]{2})/(?P<type>(?:episode|film|(?:(?:recap|trailer)-)?video)s|live)/(?!AP-|rrn:content:)(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.redbull.com/int-en/episodes/grime-hashtags-s02-e04',
        'md5': 'db8271a7200d40053a1809ed0dd574ff',
        'info_dict': {
            'id': 'AA-1MT8DQWA91W14',
            'ext': 'mp4',
            'title': 'Grime - Hashtags S2E4',
            'description': 'md5:5546aa612958c08a98faaad4abce484d',
        },
    }, {
        'url': 'https://www.redbull.com/int-en/films/kilimanjaro-mountain-of-greatness',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/recap-videos/uci-mountain-bike-world-cup-2017-mens-xco-finals-from-vallnord',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/trailer-videos/kings-of-content',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/videos/tnts-style-red-bull-dance-your-style-s1-e12',
        'only_matching': True,
    }, {
        'url': 'https://www.redbull.com/int-en/live/mens-dh-finals-fort-william',
        'only_matching': True,
    }, {
        # only available on the int-en website so a fallback is need for the API
        # https://www.redbull.com/v3/api/graphql/v1/v3/query/en-GB>en-INT?filter[uriSlug]=fia-wrc-saturday-recap-estonia&rb3Schema=v1:hero
        'url': 'https://www.redbull.com/gb-en/live/fia-wrc-saturday-recap-estonia',
        'only_matching': True,
    }]
    _INT_FALLBACK_LIST = ['de', 'en', 'es', 'fr']
    _LAT_FALLBACK_MAP = ['ar', 'bo', 'car', 'cl', 'co', 'mx', 'pe']

    def _real_extract(self, url):
        region, lang, filter_type, display_id = self._match_valid_url(url).groups()
        if filter_type == 'episodes':
            filter_type = 'episode-videos'
        elif filter_type == 'live':
            filter_type = 'live-videos'

        regions = [region.upper()]
        if region != 'int':
            if region in self._LAT_FALLBACK_MAP:
                regions.append('LAT')
            if lang in self._INT_FALLBACK_LIST:
                regions.append('INT')
        locale = '>'.join(['%s-%s' % (lang, reg) for reg in regions])

        rrn_id = self._download_json(
            'https://www.redbull.com/v3/api/graphql/v1/v3/query/' + locale,
            display_id, query={
                'filter[type]': filter_type,
                'filter[uriSlug]': display_id,
                'rb3Schema': 'v1:hero',
            })['data']['id']

        return self.url_result(
            'https://www.redbull.com/embed/' + rrn_id,
            RedBullEmbedIE.ie_key(), rrn_id)

repo_name: Lesmiscore/ytdl


code:
import random
from urllib.parse import urlparse

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    int_or_none,
    float_or_none,
    try_get,
    unescapeHTML,
    url_or_none,
    traverse_obj
)

class RedditIE(InfoExtractor):
    _VALID_URL = r'https?://(?P<subdomain>[^/]+\.)?reddit(?:media)?\.com/r/(?P<slug>[^/]+/comments/(?P<id>[^/?#&]+))'
    _TESTS = [{
        'url': 'https://www.reddit.com/r/videos/comments/6rrwyj/that_small_heart_attack/',
        'info_dict': {
            'id': 'zv89llsvexdz',
            'ext': 'mp4',
            'display_id': '6rrwyj',
            'title': 'That small heart attack.',
            'thumbnail': r're:^https?://.*\.(?:jpg|png)',
            'thumbnails': 'count:4',
            'timestamp': 1501941939,
            'upload_date': '20170805',
            'uploader': 'Antw87',
            'duration': 12,
            'like_count': int,
            'dislike_count': int,
            'comment_count': int,
            'age_limit': 0,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.reddit.com/r/videos/comments/6rrwyj',
        'only_matching': True,
    }, {
        # imgur
        'url': 'https://www.reddit.com/r/MadeMeSmile/comments/6t7wi5/wait_for_it/',
        'only_matching': True,
    }, {
        # imgur @ old reddit
        'url': 'https://old.reddit.com/r/MadeMeSmile/comments/6t7wi5/wait_for_it/',
        'only_matching': True,
    }, {
        # streamable
        'url': 'https://www.reddit.com/r/videos/comments/6t7sg9/comedians_hilarious_joke_about_the_guam_flag/',
        'only_matching': True,
    }, {
        # youtube
        'url': 'https://www.reddit.com/r/videos/comments/6t75wq/southern_man_tries_to_speak_without_an_accent/',
        'only_matching': True,
    }, {
        # reddit video @ nm reddit
        'url': 'https://nm.reddit.com/r/Cricket/comments/8idvby/lousy_cameraman_finds_himself_in_cairns_line_of/',
        'only_matching': True,
    }, {
        'url': 'https://www.redditmedia.com/r/serbia/comments/pu9wbx/ako_vu%C4%8Di%C4%87_izgubi_izbore_ja_%C4%87u_da_crknem/',
        'only_matching': True,
    }]

    @staticmethod
    def _gen_session_id():
        id_length = 16
        rand_max = 1 << (id_length * 4)
        return '%0.*x' % (id_length, random.randrange(rand_max))

    def _real_extract(self, url):
        subdomain, slug, video_id = self._match_valid_url(url).group('subdomain', 'slug', 'id')

        self._set_cookie('.reddit.com', 'reddit_session', self._gen_session_id())
        self._set_cookie('.reddit.com', '_options', '%7B%22pref_quarantine_optin%22%3A%20true%7D')
        data = self._download_json(f'https://{subdomain}reddit.com/r/{slug}/.json', video_id, fatal=False)
        if not data:
            # Fall back to old.reddit.com in case the requested subdomain fails
            data = self._download_json(f'https://old.reddit.com/r/{slug}/.json', video_id)
        data = data[0]['data']['children'][0]['data']
        video_url = data['url']

        # Avoid recursing into the same reddit URL
        if 'reddit.com/' in video_url and '/%s/' % video_id in video_url:
            raise ExtractorError('No media found', expected=True)

        over_18 = data.get('over_18')
        if over_18 is True:
            age_limit = 18
        elif over_18 is False:
            age_limit = 0
        else:
            age_limit = None

        thumbnails = []

        def add_thumbnail(src):
            if not isinstance(src, dict):
                return
            thumbnail_url = url_or_none(src.get('url'))
            if not thumbnail_url:
                return
            thumbnails.append({
                'url': unescapeHTML(thumbnail_url),
                'width': int_or_none(src.get('width')),
                'height': int_or_none(src.get('height')),
            })

        for image in try_get(data, lambda x: x['preview']['images']) or []:
            if not isinstance(image, dict):
                continue
            add_thumbnail(image.get('source'))
            resolutions = image.get('resolutions')
            if isinstance(resolutions, list):
                for resolution in resolutions:
                    add_thumbnail(resolution)

        info = {
            'title': data.get('title'),
            'thumbnails': thumbnails,
            'timestamp': float_or_none(data.get('created_utc')),
            'uploader': data.get('author'),
            'like_count': int_or_none(data.get('ups')),
            'dislike_count': int_or_none(data.get('downs')),
            'comment_count': int_or_none(data.get('num_comments')),
            'age_limit': age_limit,
        }

        # Check if media is hosted on reddit:
        reddit_video = traverse_obj(data, (('media', 'secure_media'), 'reddit_video'), get_all=False)
        if reddit_video:
            playlist_urls = [
                try_get(reddit_video, lambda x: unescapeHTML(x[y]))
                for y in ('dash_url', 'hls_url')
            ]

            # Update video_id
            display_id = video_id
            video_id = self._search_regex(
                r'https?://v\.redd\.it/(?P<id>[^/?#&]+)', reddit_video['fallback_url'],
                'video_id', default=display_id)

            dash_playlist_url = playlist_urls[0] or f'https://v.redd.it/{video_id}/DASHPlaylist.mpd'
            hls_playlist_url = playlist_urls[1] or f'https://v.redd.it/{video_id}/HLSPlaylist.m3u8'

            formats = self._extract_m3u8_formats(
                hls_playlist_url, display_id, 'mp4',
                entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)
            formats.extend(self._extract_mpd_formats(
                dash_playlist_url, display_id, mpd_id='dash', fatal=False))
            self._sort_formats(formats)

            return {
                **info,
                'id': video_id,
                'display_id': display_id,
                'formats': formats,
                'duration': int_or_none(reddit_video.get('duration')),
            }

        parsed_url = urlparse(video_url)
        if parsed_url.netloc == 'v.redd.it':
            self.raise_no_formats('This video is processing', expected=True, video_id=video_id)
            return {
                **info,
                'id': parsed_url.path.split('/')[1],
                'display_id': video_id,
            }

        # Not hosted on reddit, must continue extraction
        return {
            **info,
            'display_id': video_id,
            '_type': 'url_transparent',
            'url': video_url,
        }


code:
import functools

from .common import InfoExtractor
from ..compat import compat_parse_qs
from ..utils import (
    ExtractorError,
    int_or_none,
    qualities,
    try_get,
    OnDemandPagedList,
)

class RedGifsBaseInfoExtractor(InfoExtractor):
    _FORMATS = {
        'gif': 250,
        'sd': 480,
        'hd': None,
    }

    def _parse_gif_data(self, gif_data):
        video_id = gif_data.get('id')
        quality = qualities(tuple(self._FORMATS.keys()))

        orig_height = int_or_none(gif_data.get('height'))
        aspect_ratio = try_get(gif_data, lambda x: orig_height / x['width'])

        formats = []
        for format_id, height in self._FORMATS.items():
            video_url = gif_data['urls'].get(format_id)
            if not video_url:
                continue
            height = min(orig_height, height or orig_height)
            formats.append({
                'url': video_url,
                'format_id': format_id,
                'width': height * aspect_ratio if aspect_ratio else None,
                'height': height,
                'quality': quality(format_id),
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'webpage_url': f'https://redgifs.com/watch/{video_id}',
            'ie_key': RedGifsIE.ie_key(),
            'extractor': 'RedGifs',
            'title': ' '.join(gif_data.get('tags') or []) or 'RedGifs',
            'timestamp': int_or_none(gif_data.get('createDate')),
            'uploader': gif_data.get('userName'),
            'duration': int_or_none(gif_data.get('duration')),
            'view_count': int_or_none(gif_data.get('views')),
            'like_count': int_or_none(gif_data.get('likes')),
            'categories': gif_data.get('tags') or [],
            'tags': gif_data.get('tags'),
            'age_limit': 18,
            'formats': formats,
        }

    def _call_api(self, ep, video_id, *args, **kwargs):
        data = self._download_json(
            f'https://api.redgifs.com/v2/{ep}', video_id, *args, **kwargs)
        if 'error' in data:
            raise ExtractorError(f'RedGifs said: {data["error"]}', expected=True, video_id=video_id)
        return data

    def _fetch_page(self, ep, video_id, query, page):
        query['page'] = page + 1
        data = self._call_api(
            ep, video_id, query=query, note=f'Downloading JSON metadata page {page + 1}')

        for entry in data['gifs']:
            yield self._parse_gif_data(entry)

    def _prepare_api_query(self, query, fields):
        api_query = [
            (field_name, query.get(field_name, (default,))[0])
            for field_name, default in fields.items()]

        return {key: val for key, val in api_query if val is not None}

    def _paged_entries(self, ep, item_id, query, fields):
        page = int_or_none(query.get('page', (None,))[0])
        page_fetcher = functools.partial(
            self._fetch_page, ep, item_id, self._prepare_api_query(query, fields))
        return page_fetcher(page) if page else OnDemandPagedList(page_fetcher, self._PAGE_SIZE)

class RedGifsIE(RedGifsBaseInfoExtractor):
    _VALID_URL = r'https?://(?:(?:www\.)?redgifs\.com/watch/|thumbs2\.redgifs\.com/)(?P<id>[^-/?#\.]+)'
    _TESTS = [{
        'url': 'https://www.redgifs.com/watch/squeakyhelplesswisent',
        'info_dict': {
            'id': 'squeakyhelplesswisent',
            'ext': 'mp4',
            'title': 'Hotwife Legs Thick',
            'timestamp': 1636287915,
            'upload_date': '20211107',
            'uploader': 'ignored52',
            'duration': 16,
            'view_count': int,
            'like_count': int,
            'categories': list,
            'age_limit': 18,
        }
    }, {
        'url': 'https://thumbs2.redgifs.com/SqueakyHelplessWisent-mobile.mp4#t=0',
        'info_dict': {
            'id': 'squeakyhelplesswisent',
            'ext': 'mp4',
            'title': 'Hotwife Legs Thick',
            'timestamp': 1636287915,
            'upload_date': '20211107',
            'uploader': 'ignored52',
            'duration': 16,
            'view_count': int,
            'like_count': int,
            'categories': list,
            'age_limit': 18,
        }
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url).lower()
        video_info = self._call_api(
            f'gifs/{video_id}', video_id, note='Downloading video info')
        return self._parse_gif_data(video_info['gif'])

class RedGifsSearchIE(RedGifsBaseInfoExtractor):
    IE_DESC = 'Redgifs search'
    _VALID_URL = r'https?://(?:www\.)?redgifs\.com/browse\?(?P<query>[^#]+)'
    _PAGE_SIZE = 80
    _TESTS = [
        {
            'url': 'https://www.redgifs.com/browse?tags=Lesbian',
            'info_dict': {
                'id': 'tags=Lesbian',
                'title': 'Lesbian',
                'description': 'RedGifs search for Lesbian, ordered by trending'
            },
            'playlist_mincount': 100,
        },
        {
            'url': 'https://www.redgifs.com/browse?type=g&order=latest&tags=Lesbian',
            'info_dict': {
                'id': 'type=g&order=latest&tags=Lesbian',
                'title': 'Lesbian',
                'description': 'RedGifs search for Lesbian, ordered by latest'
            },
            'playlist_mincount': 100,
        },
        {
            'url': 'https://www.redgifs.com/browse?type=g&order=latest&tags=Lesbian&page=2',
            'info_dict': {
                'id': 'type=g&order=latest&tags=Lesbian&page=2',
                'title': 'Lesbian',
                'description': 'RedGifs search for Lesbian, ordered by latest'
            },
            'playlist_count': 80,
        }
    ]

    def _real_extract(self, url):
        query_str = self._match_valid_url(url).group('query')
        query = compat_parse_qs(query_str)
        if not query.get('tags'):
            raise ExtractorError('Invalid query tags', expected=True)

        tags = query.get('tags')[0]
        order = query.get('order', ('trending',))[0]

        query['search_text'] = [tags]
        entries = self._paged_entries('gifs/search', query_str, query, {
            'search_text': None,
            'order': 'trending',
            'type': None,
        })

        return self.playlist_result(
            entries, query_str, tags, f'RedGifs search for {tags}, ordered by {order}')

class RedGifsUserIE(RedGifsBaseInfoExtractor):
    IE_DESC = 'Redgifs user'
    _VALID_URL = r'https?://(?:www\.)?redgifs\.com/users/(?P<username>[^/?#]+)(?:\?(?P<query>[^#]+))?'
    _PAGE_SIZE = 30
    _TESTS = [
        {
            'url': 'https://www.redgifs.com/users/lamsinka89',
            'info_dict': {
                'id': 'lamsinka89',
                'title': 'lamsinka89',
                'description': 'RedGifs user lamsinka89, ordered by recent'
            },
            'playlist_mincount': 100,
        },
        {
            'url': 'https://www.redgifs.com/users/lamsinka89?page=3',
            'info_dict': {
                'id': 'lamsinka89?page=3',
                'title': 'lamsinka89',
                'description': 'RedGifs user lamsinka89, ordered by recent'
            },
            'playlist_count': 30,
        },
        {
            'url': 'https://www.redgifs.com/users/lamsinka89?order=best&type=g',
            'info_dict': {
                'id': 'lamsinka89?order=best&type=g',
                'title': 'lamsinka89',
                'description': 'RedGifs user lamsinka89, ordered by best'
            },
            'playlist_mincount': 100,
        }
    ]

    def _real_extract(self, url):
        username, query_str = self._match_valid_url(url).group('username', 'query')
        playlist_id = f'{username}?{query_str}' if query_str else username

        query = compat_parse_qs(query_str)
        order = query.get('order', ('recent',))[0]

        entries = self._paged_entries(f'users/{username}/search', playlist_id, query, {
            'order': 'recent',
            'type': None,
        })

        return self.playlist_result(
            entries, playlist_id, username, f'RedGifs user {username}, ordered by {order}')


code:
from .common import InfoExtractor
from ..utils import (
    determine_ext,
    ExtractorError,
    int_or_none,
    merge_dicts,
    str_to_int,
    unified_strdate,
    url_or_none,
)

class RedTubeIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:\w+\.)?redtube\.com/|embed\.redtube\.com/\?.*?\bid=)(?P<id>[0-9]+)'
    _EMBED_REGEX = [r']+?src=["\'](?P<url>(?:https?:)?//embed\.redtube\.com/\?.*?\bid=\d+)']
    _TESTS = [{
        'url': 'https://www.redtube.com/38864951',
        'md5': '4fba70cbca3aefd25767ab4b523c9878',
        'info_dict': {
            'id': '38864951',
            'ext': 'mp4',
            'title': 'Public Sex on the Balcony in Freezing Paris! Amateur Couple LeoLulu',
            'description': 'Watch video Public Sex on the Balcony in Freezing Paris! Amateur Couple LeoLulu on Redtube, home of free Blowjob porn videos and Blonde sex movies online. Video length: (10:46) - Uploaded by leolulu - Verified User - Starring Pornstar: Leolulu',
            'upload_date': '20210111',
            'timestamp': 1610343109,
            'duration': 646,
            'view_count': int,
            'age_limit': 18,
            'thumbnail': r're:https://\wi-ph\.rdtcdn\.com/videos/.+/.+\.jpg',
        },
    }, {
        'url': 'http://embed.redtube.com/?bgcolor=000000&id=1443286',
        'only_matching': True,
    }, {
        'url': 'http://it.redtube.com/66418',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(
            'http://www.redtube.com/%s' % video_id, video_id)

        ERRORS = (
            (('video-deleted-info', '>This video has been removed'), 'has been removed'),
            (('private_video_text', '>This video is private', '>Send a friend request to its owner to be able to view it'), 'is private'),
        )

        for patterns, message in ERRORS:
            if any(p in webpage for p in patterns):
                raise ExtractorError(
                    'Video %s %s' % (video_id, message), expected=True)

        info = self._search_json_ld(webpage, video_id, default={})

        if not info.get('title'):
            info['title'] = self._html_search_regex(
                (r'<h(\d)[^>]+class="(?:video_title_text|videoTitle|video_title)[^"]*">(?P<title>(?:(?!\1).)+)</h\1>',
                 r'(?:videoTitle|title)\s*:\s*(["\'])(?P<title>(?:(?!\1).)+)\1',),
                webpage, 'title', group='title',
                default=None) or self._og_search_title(webpage)

        formats = []
        sources = self._parse_json(
            self._search_regex(
                r'sources\s*:\s*({.+?})', webpage, 'source', default='{}'),
            video_id, fatal=False)
        if sources and isinstance(sources, dict):
            for format_id, format_url in sources.items():
                if format_url:
                    formats.append({
                        'url': format_url,
                        'format_id': format_id,
                        'height': int_or_none(format_id),
                    })
        medias = self._parse_json(
            self._search_regex(
                r'mediaDefinition["\']?\s*:\s*(\[.+?}\s*\])', webpage,
                'media definitions', default='{}'),
            video_id, fatal=False)
        for media in medias if isinstance(medias, list) else []:
            format_url = url_or_none(media.get('videoUrl'))
            if not format_url:
                continue
            format_id = media.get('format')
            quality = media.get('quality')
            if format_id == 'hls' or (format_id == 'mp4' and not quality):
                more_media = self._download_json(format_url, video_id, fatal=False)
            else:
                more_media = [media]
            for media in more_media if isinstance(more_media, list) else []:
                format_url = url_or_none(media.get('videoUrl'))
                if not format_url:
                    continue
                format_id = media.get('format')
                if format_id == 'hls' or determine_ext(format_url) == 'm3u8':
                    formats.extend(self._extract_m3u8_formats(
                        format_url, video_id, 'mp4',
                        entry_protocol='m3u8_native', m3u8_id=format_id or 'hls',
                        fatal=False))
                    continue
                format_id = media.get('quality')
                formats.append({
                    'url': format_url,
                    'ext': 'mp4',
                    'format_id': format_id,
                    'height': int_or_none(format_id),
                })
        if not formats:
            video_url = self._html_search_regex(
                r'<source src="(.+?)" type="video/mp4">', webpage, 'video URL')
            formats.append({'url': video_url, 'ext': 'mp4'})
        self._sort_formats(formats)

        thumbnail = self._og_search_thumbnail(webpage)
        upload_date = unified_strdate(self._search_regex(
            r']+>(?:ADDED|Published on) ([^<]+)<',
            webpage, 'upload date', default=None))
        duration = int_or_none(self._og_search_property(
            'video:duration', webpage, default=None) or self._search_regex(
                r'videoDuration\s*:\s*(\d+)', webpage, 'duration', default=None))
        view_count = str_to_int(self._search_regex(
            (r'
]*>Views
\s*
]*>\s*([\d,.]+)',
             r']*>VIEWS\s*	\s*\s*([\d,.]+)',
             r']+\bclass=["\']video_view_count[^>]*>\s*([\d,.]+)'),
            webpage, 'view count', default=None))

        # No self-labeling, but they describe themselves as
        # "Home of Videos Porno"
        age_limit = 18

        return merge_dicts(info, {
            'id': video_id,
            'ext': 'mp4',
            'thumbnail': thumbnail,
            'upload_date': upload_date,
            'duration': duration,
            'view_count': view_count,
            'age_limit': age_limit,
            'formats': formats,
        })


code:
from .common import InfoExtractor

from ..utils import (
    sanitized_Request,
    xpath_text,
    xpath_with_ns,
)

class RegioTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?regio-tv\.de/video/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://www.regio-tv.de/video/395808.html',
        'info_dict': {
            'id': '395808',
            'ext': 'mp4',
            'title': 'Wir in Ludwigsburg',
            'description': 'Mit unseren zuckersüßen Adventskindern, außerdem besuchen wir die Abendsterne!',
        }
    }, {
        'url': 'http://www.regio-tv.de/video/395808',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        key = self._search_regex(
            r'key\s*:\s*(["\'])(?P<key>.+?)\1', webpage, 'key', group='key')
        title = self._og_search_title(webpage)

        SOAP_TEMPLATE = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><{0} xmlns="http://v.telvi.de/"><key xsi:type="xsd:string">{1}</key></{0}></soap:Body></soap:Envelope>'

        request = sanitized_Request(
            'http://v.telvi.de/',
            SOAP_TEMPLATE.format('GetHTML5VideoData', key).encode('utf-8'))
        video_data = self._download_xml(request, video_id, 'Downloading video XML')

        NS_MAP = {
            'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
            'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
        }

        video_url = xpath_text(
            video_data, xpath_with_ns('.//video', NS_MAP), 'video url', fatal=True)
        thumbnail = xpath_text(
            video_data, xpath_with_ns('.//image', NS_MAP), 'thumbnail')
        description = self._og_search_description(
            webpage) or self._html_search_meta('description', webpage)

        return {
            'id': video_id,
            'url': video_url,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
        }


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    determine_ext,
    int_or_none,
    url_or_none,
)

class RENTVIE(InfoExtractor):
    _VALID_URL = r'(?:rentv:|https?://(?:www\.)?ren\.tv/(?:player|video/epizod)/)(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://ren.tv/video/epizod/118577',
        'md5': 'd91851bf9af73c0ad9b2cdf76c127fbb',
        'info_dict': {
            'id': '118577',
            'ext': 'mp4',
            'title': 'Документальный спецпроект: "Промывка мозгов. Технологии XXI века"',
            'timestamp': 1472230800,
            'upload_date': '20160826',
        }
    }, {
        'url': 'http://ren.tv/player/118577',
        'only_matching': True,
    }, {
        'url': 'rentv:118577',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage('http://ren.tv/player/' + video_id, video_id)
        config = self._parse_json(self._search_regex(
            r'config\s*=\s*({.+})\s*;', webpage, 'config'), video_id)
        title = config['title']
        formats = []
        for video in config['src']:
            src = url_or_none(video.get('src'))
            if not src:
                continue
            ext = determine_ext(src)
            if ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    src, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            else:
                formats.append({
                    'url': src,
                })
        self._sort_formats(formats)
        return {
            'id': video_id,
            'title': title,
            'description': config.get('description'),
            'thumbnail': config.get('image'),
            'duration': int_or_none(config.get('duration')),
            'timestamp': int_or_none(config.get('date')),
            'formats': formats,
        }

class RENTVArticleIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?ren\.tv/novosti/\d{4}-\d{2}-\d{2}/(?P<id>[^/?#]+)'
    _TESTS = [{
        'url': 'http://ren.tv/novosti/2016-10-26/video-mikroavtobus-popavshiy-v-dtp-s-gruzovikami-v-podmoskove-prevratilsya-v',
        'md5': 'ebd63c4680b167693745ab91343df1d6',
        'info_dict': {
            'id': '136472',
            'ext': 'mp4',
            'title': 'Видео: микроавтобус, попавший в ДТП с грузовиками в Подмосковье, превратился в груду металла',
            'description': 'Жертвами столкновения двух фур и микроавтобуса, по последним данным, стали семь человек.',
        }
    }, {
        # TODO: invalid m3u8
        'url': 'http://ren.tv/novosti/2015-09-25/sluchaynyy-prohozhiy-poymal-avtougonshchika-v-murmanske-video',
        'info_dict': {
            'id': 'playlist',
            'ext': 'mp4',
            'title': 'Случайный прохожий поймал автоугонщика в Мурманске. ВИДЕО | РЕН ТВ',
            'uploader': 'ren.tv',
        },
        'params': {
            # m3u8 downloads
            'skip_download': True,
        },
        'skip': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        drupal_settings = self._parse_json(self._search_regex(
            r'jQuery\.extend\(Drupal\.settings\s*,\s*({.+?})\);',
            webpage, 'drupal settings'), display_id)

        entries = []
        for config_profile in drupal_settings.get('ren_jwplayer', {}).values():
            media_id = config_profile.get('mediaid')
            if not media_id:
                continue
            media_id = compat_str(media_id)
            entries.append(self.url_result('rentv:' + media_id, 'RENTV', media_id))
        return self.playlist_result(entries, display_id)


code:
from .common import InfoExtractor

class RestudyIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:www|portal)\.)?restudy\.dk/video/[^/]+/id/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.restudy.dk/video/play/id/1637',
        'info_dict': {
            'id': '1637',
            'ext': 'flv',
            'title': 'Leiden-frosteffekt',
            'description': 'Denne video er et eksperiment med flydende kvælstof.',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        }
    }, {
        'url': 'https://portal.restudy.dk/video/leiden-frosteffekt/id/1637',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        title = self._og_search_title(webpage).strip()
        description = self._og_search_description(webpage).strip()

        formats = self._extract_smil_formats(
            'https://cdn.portal.restudy.dk/dynamic/themes/front/awsmedia/SmilDirectory/video_%s.xml' % video_id,
            video_id)
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'formats': formats,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    js_to_json,
    int_or_none,
    unescapeHTML,
)

class ReutersIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?reuters\.com/.*?\?.*?videoId=(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://www.reuters.com/video/2016/05/20/san-francisco-police-chief-resigns?videoId=368575562',
        'md5': '8015113643a0b12838f160b0b81cc2ee',
        'info_dict': {
            'id': '368575562',
            'ext': 'mp4',
            'title': 'San Francisco police chief resigns',
        }
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(
            'http://www.reuters.com/assets/iframe/yovideo?videoId=%s' % video_id, video_id)
        video_data = js_to_json(self._search_regex(
            r'(?s)Reuters\.yovideo\.drawPlayer\(({.*?})\);',
            webpage, 'video data'))

        def get_json_value(key, fatal=False):
            return self._search_regex(r'"%s"\s*:\s*"([^"]+)"' % key, video_data, key, fatal=fatal)

        title = unescapeHTML(get_json_value('title', fatal=True))
        mmid, fid = re.search(r',/(\d+)\?f=(\d+)', get_json_value('flv', fatal=True)).groups()

        mas_data = self._download_json(
            'http://mas-e.cds1.yospace.com/mas/%s/%s?trans=json' % (mmid, fid),
            video_id, transform_source=js_to_json)
        formats = []
        for f in mas_data:
            f_url = f.get('url')
            if not f_url:
                continue
            method = f.get('method')
            if method == 'hls':
                formats.extend(self._extract_m3u8_formats(
                    f_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
            else:
                container = f.get('container')
                ext = '3gp' if method == 'mobile' else container
                formats.append({
                    'format_id': ext,
                    'url': f_url,
                    'ext': ext,
                    'container': container if method != 'mobile' else None,
                })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'thumbnail': get_json_value('thumb'),
            'duration': int_or_none(get_json_value('seconds')),
            'formats': formats,
        }


code:
from .common import InfoExtractor
from ..utils import (
    qualities,
    str_or_none,
)

class ReverbNationIE(InfoExtractor):
    _VALID_URL = r'^https?://(?:www\.)?reverbnation\.com/.*?/song/(?P<id>\d+).*?$'
    _TESTS = [{
        'url': 'http://www.reverbnation.com/alkilados/song/16965047-mona-lisa',
        'md5': 'c0aaf339bcee189495fdf5a8c8ba8645',
        'info_dict': {
            'id': '16965047',
            'ext': 'mp3',
            'title': 'MONA LISA',
            'uploader': 'ALKILADOS',
            'uploader_id': '216429',
            'thumbnail': r're:^https?://.*\.jpg',
        },
    }]

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

        api_res = self._download_json(
            'https://api.reverbnation.com/song/%s' % song_id,
            song_id,
            note='Downloading information of song %s' % song_id
        )

        THUMBNAILS = ('thumbnail', 'image')
        quality = qualities(THUMBNAILS)
        thumbnails = []
        for thumb_key in THUMBNAILS:
            if api_res.get(thumb_key):
                thumbnails.append({
                    'url': api_res[thumb_key],
                    'preference': quality(thumb_key)
                })

        return {
            'id': song_id,
            'title': api_res['name'],
            'url': api_res['url'],
            'uploader': api_res.get('artist', {}).get('name'),
            'uploader_id': str_or_none(api_res.get('artist', {}).get('id')),
            'thumbnails': thumbnails,
            'ext': 'mp3',
            'vcodec': 'none',
        }


code:
import re

from .common import InfoExtractor
from ..compat import compat_parse_qs
from ..utils import (
    xpath_text,
    xpath_element,
    int_or_none,
    parse_iso8601,
    ExtractorError,
)

class RICEIE(InfoExtractor):
    _VALID_URL = r'https?://mediahub\.rice\.edu/app/[Pp]ortal/video\.aspx\?(?P<query>.+)'
    _TEST = {
        'url': 'https://mediahub.rice.edu/app/Portal/video.aspx?PortalID=25ffd62c-3d01-4b29-8c70-7c94270efb3e&DestinationID=66bc9434-03bd-4725-b47e-c659d8d809db&ContentID=YEWIvbhb40aqdjMD1ALSqw',
        'md5': '9b83b4a2eead4912dc3b7fac7c449b6a',
        'info_dict': {
            'id': 'YEWIvbhb40aqdjMD1ALSqw',
            'ext': 'mp4',
            'title': 'Active Learning in Archeology',
            'upload_date': '20140616',
            'timestamp': 1402926346,
        }
    }
    _NS = 'http://schemas.datacontract.org/2004/07/ensembleVideo.Data.Service.Contracts.Models.Player.Config'

    def _real_extract(self, url):
        qs = compat_parse_qs(self._match_valid_url(url).group('query'))
        if not qs.get('PortalID') or not qs.get('DestinationID') or not qs.get('ContentID'):
            raise ExtractorError('Invalid URL', expected=True)

        portal_id = qs['PortalID'][0]
        playlist_id = qs['DestinationID'][0]
        content_id = qs['ContentID'][0]

        content_data = self._download_xml('https://mediahub.rice.edu/api/portal/GetContentTitle', content_id, query={
            'portalId': portal_id,
            'playlistId': playlist_id,
            'contentId': content_id
        })
        metadata = xpath_element(content_data, './/metaData', fatal=True)
        title = xpath_text(metadata, 'primaryTitle', fatal=True)
        encodings = xpath_element(content_data, './/encodings', fatal=True)
        player_data = self._download_xml('https://mediahub.rice.edu/api/player/GetPlayerConfig', content_id, query={
            'temporaryLinkId': xpath_text(encodings, 'temporaryLinkId', fatal=True),
            'contentId': content_id,
        })

        common_fmt = {}
        dimensions = xpath_text(encodings, 'dimensions')
        if dimensions:
            wh = dimensions.split('x')
            if len(wh) == 2:
                common_fmt.update({
                    'width': int_or_none(wh[0]),
                    'height': int_or_none(wh[1]),
                })

        formats = []
        rtsp_path = xpath_text(player_data, self._xpath_ns('RtspPath', self._NS))
        if rtsp_path:
            fmt = {
                'url': rtsp_path,
                'format_id': 'rtsp',
            }
            fmt.update(common_fmt)
            formats.append(fmt)
        for source in player_data.findall(self._xpath_ns('.//Source', self._NS)):
            video_url = xpath_text(source, self._xpath_ns('File', self._NS))
            if not video_url:
                continue
            if '.m3u8' in video_url:
                formats.extend(self._extract_m3u8_formats(video_url, content_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
            else:
                fmt = {
                    'url': video_url,
                    'format_id': video_url.split(':')[0],
                }
                fmt.update(common_fmt)
                rtmp = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>.+))/(?P<playpath>mp4:.+)$', video_url)
                if rtmp:
                    fmt.update({
                        'url': rtmp.group('url'),
                        'play_path': rtmp.group('playpath'),
                        'app': rtmp.group('app'),
                        'ext': 'flv',
                    })
                formats.append(fmt)
        self._sort_formats(formats)

        thumbnails = []
        for content_asset in content_data.findall('.//contentAssets'):
            asset_type = xpath_text(content_asset, 'type')
            if asset_type == 'image':
                image_url = xpath_text(content_asset, 'httpPath')
                if not image_url:
                    continue
                thumbnails.append({
                    'id': xpath_text(content_asset, 'ID'),
                    'url': image_url,
                })

        return {
            'id': content_id,
            'title': title,
            'description': xpath_text(metadata, 'abstract'),
            'duration': int_or_none(xpath_text(metadata, 'duration')),
            'timestamp': parse_iso8601(xpath_text(metadata, 'dateUpdated')),
            'thumbnails': thumbnails,
            'formats': formats,
        }


code:
from .common import InfoExtractor
from .brightcove import BrightcoveLegacyIE
from ..compat import (
    compat_parse_qs,
    compat_urlparse,
)
from ..utils import smuggle_url

class RMCDecouverteIE(InfoExtractor):
    _VALID_URL = r'https?://rmcdecouverte\.bfmtv\.com/(?:[^?#]*_(?P<id>\d+)|mediaplayer-direct)/?(?:[#?]|$)'

    _TESTS = [{
        'url': 'https://rmcdecouverte.bfmtv.com/vestiges-de-guerre_22240/les-bunkers-secrets-domaha-beach_25303/',
        'info_dict': {
            'id': '6250879771001',
            'ext': 'mp4',
            'title': 'LES BUNKERS SECRETS D´OMAHA BEACH',
            'uploader_id': '1969646226001',
            'description': 'md5:aed573ca24abde62a148e0eba909657d',
            'timestamp': 1619622984,
            'upload_date': '20210428',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://rmcdecouverte.bfmtv.com/wheeler-dealers-occasions-a-saisir/program_2566/',
        'info_dict': {
            'id': '5983675500001',
            'ext': 'mp4',
            'title': 'CORVETTE',
            'description': 'md5:c1e8295521e45ffebf635d6a7658f506',
            'uploader_id': '1969646226001',
            'upload_date': '20181226',
            'timestamp': 1545861635,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'only available for a week',
    }, {
        'url': 'https://rmcdecouverte.bfmtv.com/avions-furtifs-la-technologie-de-lextreme_10598',
        'only_matching': True,
    }, {
        # The website accepts any URL as long as it has _\d+ at the end
        'url': 'https://rmcdecouverte.bfmtv.com/any/thing/can/go/here/_10598',
        'only_matching': True,
    }, {
        # live, geo restricted, bypassable
        'url': 'https://rmcdecouverte.bfmtv.com/mediaplayer-direct/',
        'only_matching': True,
    }]
    BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/1969646226001/default_default/index.html?videoId=%s'

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        display_id = mobj.group('id') or 'direct'
        webpage = self._download_webpage(url, display_id)
        brightcove_legacy_url = BrightcoveLegacyIE._extract_brightcove_url(webpage)
        if brightcove_legacy_url:
            brightcove_id = compat_parse_qs(compat_urlparse.urlparse(
                brightcove_legacy_url).query)['@videoPlayer'][0]
        else:
            brightcove_id = self._search_regex(
                r'data-video-id=["\'](\d+)', webpage, 'brightcove id')
        return self.url_result(
            smuggle_url(
                self.BRIGHTCOVE_URL_TEMPLATE % brightcove_id,
                {'geo_countries': ['FR']}),
            'BrightcoveNew', brightcove_id)


code:
from .common import InfoExtractor
from ..utils import (
    int_or_none,
    parse_iso8601,
)

class RockstarGamesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?rockstargames\.com/videos(?:/video/|#?/?\?.*\bvideo=)(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.rockstargames.com/videos/video/11544/',
        'md5': '03b5caa6e357a4bd50e3143fc03e5733',
        'info_dict': {
            'id': '11544',
            'ext': 'mp4',
            'title': 'Further Adventures in Finance and Felony Trailer',
            'description': 'md5:6d31f55f30cb101b5476c4a379e324a3',
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1464876000,
            'upload_date': '20160602',
        }
    }, {
        'url': 'http://www.rockstargames.com/videos#/?video=48',
        'only_matching': True,
    }]

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

        video = self._download_json(
            'https://www.rockstargames.com/videoplayer/videos/get-video.json',
            video_id, query={
                'id': video_id,
                'locale': 'en_us',
            })['video']

        title = video['title']

        formats = []
        for video in video['files_processed']['video/mp4']:
            if not video.get('src'):
                continue
            resolution = video.get('resolution')
            height = int_or_none(self._search_regex(
                r'^(\d+)[pP]$', resolution or '', 'height', default=None))
            formats.append({
                'url': self._proto_relative_url(video['src']),
                'format_id': resolution,
                'height': height,
            })

        if not formats:
            youtube_id = video.get('youtube_id')
            if youtube_id:
                return self.url_result(youtube_id, 'Youtube')

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': video.get('description'),
            'thumbnail': self._proto_relative_url(video.get('screencap')),
            'timestamp': parse_iso8601(video.get('created')),
            'formats': formats,
        }


code:
import itertools
import json
import re
import urllib.parse
from datetime import datetime

from .common import InfoExtractor, SearchInfoExtractor
from ..utils import (
    ExtractorError,
    determine_ext,
    float_or_none,
    format_field,
    int_or_none,
    str_or_none,
    traverse_obj,
    try_get,
    unescapeHTML,
    unified_timestamp,
    url_or_none,
    urlencode_postdata,
)

_API_BASE_URL = 'https://prod-api-v2.production.rokfin.com/api/v2/public/'

class RokfinIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?rokfin\.com/(?P<id>(?P<type>post|stream)/\d+)'
    _NETRC_MACHINE = 'rokfin'
    _AUTH_BASE = 'https://secure.rokfin.com/auth/realms/rokfin-web/protocol/openid-connect'
    _access_mgmt_tokens = {}  # OAuth 2.0: RFC 6749, Sec. 1.4-5
    _TESTS = [{
        'url': 'https://www.rokfin.com/post/57548/Mitt-Romneys-Crazy-Solution-To-Climate-Change',
        'info_dict': {
            'id': 'post/57548',
            'ext': 'mp4',
            'title': 'Mitt Romney\'s Crazy Solution To Climate Change',
            'thumbnail': r're:https://img\.production\.rokfin\.com/.+',
            'upload_date': '20211023',
            'timestamp': 1634998029,
            'channel': 'Jimmy Dore',
            'channel_id': 65429,
            'channel_url': 'https://rokfin.com/TheJimmyDoreShow',
            'duration': 213.0,
            'availability': 'public',
            'live_status': 'not_live',
            'dislike_count': int,
            'like_count': int,
        }
    }, {
        'url': 'https://rokfin.com/post/223/Julian-Assange-Arrested-Streaming-In-Real-Time',
        'info_dict': {
            'id': 'post/223',
            'ext': 'mp4',
            'title': 'Julian Assange Arrested: Streaming In Real Time',
            'thumbnail': r're:https://img\.production\.rokfin\.com/.+',
            'upload_date': '20190412',
            'timestamp': 1555052644,
            'channel': 'Ron Placone',
            'channel_id': 10,
            'channel_url': 'https://rokfin.com/RonPlacone',
            'availability': 'public',
            'live_status': 'not_live',
            'dislike_count': int,
            'like_count': int,
            'tags': ['FreeThinkingMedia^', 'RealProgressives^'],
        }
    }, {
        'url': 'https://www.rokfin.com/stream/10543/Its-A-Crazy-Mess-Regional-Director-Blows-Whistle-On-Pfizers-Vaccine-Trial-Data',
        'info_dict': {
            'id': 'stream/10543',
            'ext': 'mp4',
            'title': '"It\'s A Crazy Mess" Regional Director Blows Whistle On Pfizer\'s Vaccine Trial Data',
            'thumbnail': r're:https://img\.production\.rokfin\.com/.+',
            'description': 'md5:324ce2d3e3b62e659506409e458b9d8e',
            'channel': 'Ryan Cristián',
            'channel_id': 53856,
            'channel_url': 'https://rokfin.com/TLAVagabond',
            'availability': 'public',
            '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^'],
        }
    }]

    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)
        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'))),
            'description': str_or_none(traverse_obj(metadata, 'description', ('content', 'contentDescription'))),
            'like_count': int_or_none(metadata.get('likeCount')),
            'dislike_count': int_or_none(metadata.get('dislikeCount')),
            'channel': str_or_none(traverse_obj(metadata, ('createdBy', 'name'), ('creator', 'name'))),
            'channel_id': traverse_obj(metadata, ('createdBy', 'id'), ('creator', 'id')),
            'channel_url': url_or_none(f'https://rokfin.com/{uploader}') if uploader else None,
            'timestamp': timestamp,
            'release_timestamp': timestamp if live_status != 'not_live' else None,
            'tags': traverse_obj(metadata, ('tags', ..., 'title'), expected_type=str_or_none),
            'live_status': live_status,
            'availability': self._availability(
                needs_premium=bool(traverse_obj(metadata, 'premiumPlan', 'premium')),
                is_private=False, needs_subscription=False, needs_auth=False, is_unlisted=False),
            # 'comment_count': metadata.get('numComments'), # Data provided by website is wrong
            '__post_extractor': self.extract_comments(video_id) if video_type == 'post' else None,
        }

    def _get_comments(self, video_id):
        pages_total = None
        for page_n in itertools.count():
            raw_comments = self._download_json(
                f'{_API_BASE_URL}comment?postId={video_id[5:]}&page={page_n}&size=50',
                video_id, note=f'Downloading viewer comments page {page_n + 1}{format_field(pages_total, None, " of %s")}',
                fatal=False) or {}

            for comment in raw_comments.get('content') or []:
                yield {
                    'text': str_or_none(comment.get('comment')),
                    'author': str_or_none(comment.get('name')),
                    'id': comment.get('commentId'),
                    'author_id': comment.get('userId'),
                    'parent': 'root',
                    'like_count': int_or_none(comment.get('numLikes')),
                    'dislike_count': int_or_none(comment.get('numDislikes')),
                    'timestamp': unified_timestamp(comment.get('postedAt'))
                }

            pages_total = int_or_none(raw_comments.get('totalPages')) or None
            is_last = raw_comments.get('last')
            if not raw_comments.get('content') or is_last or (page_n > pages_total if pages_total else is_last is not False):
                return

    def _perform_login(self, username, password):
        # https://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth (Sec. 3.1)
        login_page = self._download_webpage(
            f'{self._AUTH_BASE}/auth?client_id=web&redirect_uri=https%3A%2F%2Frokfin.com%2Ffeed&response_mode=fragment&response_type=code&scope=openid',
            None, note='loading login page', errnote='error loading login page')
        authentication_point_url = unescapeHTML(self._search_regex(
            r'<form\s+[^>]+action\s*=\s*"(https://secure\.rokfin\.com/auth/realms/rokfin-web/login-actions/authenticate\?[^"]+)"',
            login_page, name='Authentication URL'))

        resp_body = self._download_webpage(
            authentication_point_url, None, note='logging in', fatal=False, expected_status=404,
            data=urlencode_postdata({'username': username, 'password': password, 'rememberMe': 'off', 'credentialId': ''}))
        if not self._authentication_active():
            if re.search(r'(?i)(invalid\s+username\s+or\s+password)', resp_body or ''):
                raise ExtractorError('invalid username/password', expected=True)
            raise ExtractorError('Login failed')

        urlh = self._request_webpage(
            f'{self._AUTH_BASE}/auth', None,
            note='granting user authorization', errnote='user authorization rejected by Rokfin',
            query={
                'client_id': 'web',
                'prompt': 'none',
                'redirect_uri': 'https://rokfin.com/silent-check-sso.html',
                'response_mode': 'fragment',
                'response_type': 'code',
                'scope': 'openid',
            })
        self._access_mgmt_tokens = self._download_json(
            f'{self._AUTH_BASE}/token', None,
            note='getting access credentials', errnote='error getting access credentials',
            data=urlencode_postdata({
                'code': urllib.parse.parse_qs(urllib.parse.urldefrag(urlh.geturl()).fragment).get('code')[0],
                'client_id': 'web',
                'grant_type': 'authorization_code',
                'redirect_uri': 'https://rokfin.com/silent-check-sso.html'
            }))

    def _authentication_active(self):
        return not (
            {'KEYCLOAK_IDENTITY', 'KEYCLOAK_IDENTITY_LEGACY', 'KEYCLOAK_SESSION', 'KEYCLOAK_SESSION_LEGACY'}
            - set(self._get_cookies(self._AUTH_BASE)))

    def _get_auth_token(self):
        return try_get(self._access_mgmt_tokens, lambda x: ' '.join([x['token_type'], x['access_token']]))

    def _download_json_using_access_token(self, url_or_request, video_id, headers={}, query={}):
        assert 'authorization' not in headers
        headers = headers.copy()
        auth_token = self._get_auth_token()
        refresh_token = self._access_mgmt_tokens.get('refresh_token')
        if auth_token:
            headers['authorization'] = auth_token

        json_string, urlh = self._download_webpage_handle(
            url_or_request, video_id, headers=headers, query=query, expected_status=401)
        if not auth_token or urlh.code != 401 or refresh_token is None:
            return self._parse_json(json_string, video_id)

        self._access_mgmt_tokens = self._download_json(
            f'{self._AUTH_BASE}/token', video_id,
            note='User authorization expired or canceled by Rokfin. Re-authorizing ...', errnote='Failed to re-authorize',
            data=urlencode_postdata({
                'grant_type': 'refresh_token',
                'refresh_token': refresh_token,
                'client_id': 'web'
            }))
        headers['authorization'] = self._get_auth_token()
        if headers['authorization'] is None:
            raise ExtractorError('User authorization lost', expected=True)

        return self._download_json(url_or_request, video_id, headers=headers, query=query)

class RokfinPlaylistBaseIE(InfoExtractor):
    _TYPES = {
        'video': 'post',
        'audio': 'post',
        'stream': 'stream',
        'dead_stream': 'stream',
        'stack': 'stack',
    }

    def _get_video_data(self, metadata):
        for content in metadata.get('content') or []:
            media_type = self._TYPES.get(content.get('mediaType'))
            video_id = content.get('id') if media_type == 'post' else content.get('mediaId')
            if not media_type or not video_id:
                continue

            yield self.url_result(f'https://rokfin.com/{media_type}/{video_id}', video_id=f'{media_type}/{video_id}',
                                  video_title=str_or_none(traverse_obj(content, ('content', 'contentTitle'))))

class RokfinStackIE(RokfinPlaylistBaseIE):
    IE_NAME = 'rokfin:stack'
    IE_DESC = 'Rokfin Stacks'
    _VALID_URL = r'https?://(?:www\.)?rokfin\.com/stack/(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'https://www.rokfin.com/stack/271/Tulsi-Gabbard-Portsmouth-Townhall-FULL--Feb-9-2020',
        'playlist_count': 8,
        'info_dict': {
            'id': '271',
        },
    }]

    def _real_extract(self, url):
        list_id = self._match_id(url)
        return self.playlist_result(self._get_video_data(
            self._download_json(f'{_API_BASE_URL}stack/{list_id}', list_id)), list_id)

class RokfinChannelIE(RokfinPlaylistBaseIE):
    IE_NAME = 'rokfin:channel'
    IE_DESC = 'Rokfin Channels'
    _VALID_URL = r'https?://(?:www\.)?rokfin\.com/(?!((feed/?)|(discover/?)|(channels/?))$)(?P<id>[^/]+)/?$'
    _TESTS = [{
        'url': 'https://rokfin.com/TheConvoCouch',
        'playlist_mincount': 100,
        'info_dict': {
            'id': '12071-new',
            'title': 'TheConvoCouch - New',
            'description': 'md5:bb622b1bca100209b91cd685f7847f06',
        },
    }]

    _TABS = {
        'new': 'posts',
        'top': 'top',
        'videos': 'video',
        'podcasts': 'audio',
        'streams': 'stream',
        'stacks': 'stack',
    }

    def _real_initialize(self):
        self._validate_extractor_args()

    def _validate_extractor_args(self):
        requested_tabs = self._configuration_arg('tab', None)
        if requested_tabs is not None and (len(requested_tabs) > 1 or requested_tabs[0] not in self._TABS):
            raise ExtractorError(f'Invalid extractor-arg "tab". Must be one of {", ".join(self._TABS)}', expected=True)

    def _entries(self, channel_id, channel_name, tab):
        pages_total = None
        for page_n in itertools.count(0):
            if tab in ('posts', 'top'):
                data_url = f'{_API_BASE_URL}user/{channel_name}/{tab}?page={page_n}&size=50'
            else:
                data_url = f'{_API_BASE_URL}post/search/{tab}?page={page_n}&size=50&creator={channel_id}'
            metadata = self._download_json(
                data_url, channel_name,
                note=f'Downloading video metadata page {page_n + 1}{format_field(pages_total, None, " of %s")}')

            yield from self._get_video_data(metadata)
            pages_total = int_or_none(metadata.get('totalPages')) or None
            is_last = metadata.get('last')
            if is_last or (page_n > pages_total if pages_total else is_last is not False):
                return

    def _real_extract(self, url):
        channel_name = self._match_id(url)
        channel_info = self._download_json(f'{_API_BASE_URL}user/{channel_name}', channel_name)
        channel_id = channel_info['id']
        tab = self._configuration_arg('tab', default=['new'])[0]

        return self.playlist_result(
            self._entries(channel_id, channel_name, self._TABS[tab]),
            f'{channel_id}-{tab}', f'{channel_name} - {tab.title()}', str_or_none(channel_info.get('description')))

class RokfinSearchIE(SearchInfoExtractor):
    IE_NAME = 'rokfin:search'
    IE_DESC = 'Rokfin Search'
    _SEARCH_KEY = 'rkfnsearch'
    _TYPES = {
        'video': (('id', 'raw'), 'post'),
        'audio': (('id', 'raw'), 'post'),
        'stream': (('content_id', 'raw'), 'stream'),
        'dead_stream': (('content_id', 'raw'), 'stream'),
        'stack': (('content_id', 'raw'), 'stack'),
    }
    _TESTS = [{
        'url': 'rkfnsearch5:"zelenko"',
        'playlist_count': 5,
        'info_dict': {
            'id': '"zelenko"',
            'title': '"zelenko"',
        }
    }]
    _db_url = None
    _db_access_key = None

    def _real_initialize(self):
        self._db_url, self._db_access_key = self.cache.load(self.ie_key(), 'auth', default=(None, None))
        if not self._db_url:
            self._get_db_access_credentials()

    def _search_results(self, query):
        total_pages = None
        for page_number in itertools.count(1):
            search_results = self._run_search_query(
                query, data={'query': query, 'page': {'size': 100, 'current': page_number}},
                note=f'Downloading page {page_number}{format_field(total_pages, None, " of ~%s")}')
            total_pages = traverse_obj(search_results, ('meta', 'page', 'total_pages'), expected_type=int_or_none)

            for result in search_results.get('results') or []:
                video_id_key, video_type = self._TYPES.get(traverse_obj(result, ('content_type', 'raw')), (None, None))
                video_id = traverse_obj(result, video_id_key, expected_type=int_or_none)
                if video_id and video_type:
                    yield self.url_result(url=f'https://rokfin.com/{video_type}/{video_id}')
            if not search_results.get('results'):
                return

    def _run_search_query(self, video_id, data, **kwargs):
        data = json.dumps(data).encode()
        for attempt in range(2):
            search_results = self._download_json(
                self._db_url, video_id, data=data, fatal=(attempt == 1),
                headers={'authorization': self._db_access_key}, **kwargs)
            if search_results:
                return search_results
            self.write_debug('Updating access credentials')
            self._get_db_access_credentials(video_id)

    def _get_db_access_credentials(self, video_id=None):
        auth_data = {'SEARCH_KEY': None, 'ENDPOINT_BASE': None}
        notfound_err_page = self._download_webpage(
            'https://rokfin.com/discover', video_id, expected_status=404, note='Downloading home page')
        for js_file_path in re.findall(r'<script\b[^>]*\ssrc\s*=\s*"(/static/js/[^">]+)"', notfound_err_page):
            js_content = self._download_webpage(
                f'https://rokfin.com{js_file_path}', video_id, note='Downloading JavaScript file', fatal=False)
            auth_data.update(re.findall(
                rf'REACT_APP_({"|".join(auth_data.keys())})\s*:\s*"([^"]+)"', js_content or ''))
            if not all(auth_data.values()):
                continue

            self._db_url = url_or_none(f'{auth_data["ENDPOINT_BASE"]}/api/as/v1/engines/rokfin-search/search.json')
            self._db_access_key = f'Bearer {auth_data["SEARCH_KEY"]}'
            self.cache.store(self.ie_key(), 'auth', (self._db_url, self._db_access_key))
            return
        raise ExtractorError('Unable to extract access credentials')


code:
from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    ExtractorError,
    int_or_none,
    join_nonempty,
    LazyList,
    parse_qs,
    str_or_none,
    traverse_obj,
    url_or_none,
    urlencode_postdata,
    urljoin,
    update_url_query,
)

class RoosterTeethBaseIE(InfoExtractor):
    _NETRC_MACHINE = 'roosterteeth'
    _API_BASE = 'https://svod-be.roosterteeth.com'
    _API_BASE_URL = f'{_API_BASE}/api/v1'

    def _perform_login(self, username, password):
        if self._get_cookies(self._API_BASE_URL).get('rt_access_token'):
            return

        try:
            self._download_json(
                'https://auth.roosterteeth.com/oauth/token',
                None, 'Logging in', data=urlencode_postdata({
                    'client_id': '4338d2b4bdc8db1239360f28e72f0d9ddb1fd01e7a38fbb07b4b1f4ba4564cc5',
                    'grant_type': 'password',
                    'username': username,
                    'password': password,
                }))
        except ExtractorError as e:
            msg = 'Unable to login'
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                resp = self._parse_json(e.cause.read().decode(), None, fatal=False)
                if resp:
                    error = resp.get('extra_info') or resp.get('error_description') or resp.get('error')
                    if error:
                        msg += ': ' + error
            self.report_warning(msg)

    def _extract_video_info(self, data):
        thumbnails = []
        for image in traverse_obj(data, ('included', 'images')):
            if image.get('type') not in ('episode_image', 'bonus_feature_image'):
                continue
            thumbnails.extend([{
                'id': name,
                'url': url,
            } for name, url in (image.get('attributes') or {}).items() if url_or_none(url)])

        attributes = data.get('attributes') or {}
        title = traverse_obj(attributes, 'title', 'display_title')
        sub_only = attributes.get('is_sponsors_only')

        return {
            'id': str(data.get('id')),
            'display_id': attributes.get('slug'),
            'title': title,
            'description': traverse_obj(attributes, 'description', 'caption'),
            'series': attributes.get('show_title'),
            'season_number': int_or_none(attributes.get('season_number')),
            'season_id': attributes.get('season_id'),
            'episode': title,
            'episode_number': int_or_none(attributes.get('number')),
            'episode_id': str_or_none(data.get('uuid')),
            'channel_id': attributes.get('channel_id'),
            'duration': int_or_none(attributes.get('length')),
            'thumbnails': thumbnails,
            'availability': self._availability(
                needs_premium=sub_only, needs_subscription=sub_only, needs_auth=sub_only,
                is_private=False, is_unlisted=False),
            'tags': attributes.get('genres')
        }

class RoosterTeethIE(RoosterTeethBaseIE):
    _VALID_URL = r'https?://(?:.+?\.)?roosterteeth\.com/(?:episode|watch)/(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'http://roosterteeth.com/episode/million-dollars-but-season-2-million-dollars-but-the-game-announcement',
        'info_dict': {
            'id': '9156',
            'display_id': 'million-dollars-but-season-2-million-dollars-but-the-game-announcement',
            'ext': 'mp4',
            'title': 'Million Dollars, But... The Game Announcement',
            'description': 'md5:168a54b40e228e79f4ddb141e89fe4f5',
            'thumbnail': r're:^https?://.*\.png$',
            'series': 'Million Dollars, But...',
            'episode': 'Million Dollars, But... The Game Announcement',
        },
        'params': {'skip_download': True},
    }, {
        'url': 'https://roosterteeth.com/watch/rwby-bonus-25',
        'info_dict': {
            'id': '40432',
            'display_id': 'rwby-bonus-25',
            'title': 'Grimm',
            'description': 'md5:f30ff570741213418a8d2c19868b93ab',
            'episode': 'Grimm',
            'channel_id': '92f780eb-ebfe-4bf5-a3b5-c6ad5460a5f1',
            'thumbnail': r're:^https?://.*\.(png|jpe?g)$',
            'ext': 'mp4',
        },
        'params': {'skip_download': True},
    }, {
        'url': 'http://achievementhunter.roosterteeth.com/episode/off-topic-the-achievement-hunter-podcast-2016-i-didn-t-think-it-would-pass-31',
        'only_matching': True,
    }, {
        'url': 'http://funhaus.roosterteeth.com/episode/funhaus-shorts-2016-austin-sucks-funhaus-shorts',
        'only_matching': True,
    }, {
        'url': 'http://screwattack.roosterteeth.com/episode/death-battle-season-3-mewtwo-vs-shadow',
        'only_matching': True,
    }, {
        'url': 'http://theknow.roosterteeth.com/episode/the-know-game-news-season-1-boring-steam-sales-are-better',
        'only_matching': True,
    }, {
        # only available for FIRST members
        'url': 'http://roosterteeth.com/episode/rt-docs-the-world-s-greatest-head-massage-the-world-s-greatest-head-massage-an-asmr-journey-part-one',
        'only_matching': True,
    }, {
        'url': 'https://roosterteeth.com/watch/million-dollars-but-season-2-million-dollars-but-the-game-announcement',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        api_episode_url = f'{self._API_BASE_URL}/watch/{display_id}'

        try:
            video_data = self._download_json(
                api_episode_url + '/videos', display_id,
                'Downloading video JSON metadata')['data'][0]
            m3u8_url = video_data['attributes']['url']
            # XXX: additional URL at video_data['links']['download']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                if self._parse_json(e.cause.read().decode(), display_id).get('access') is False:
                    self.raise_login_required(
                        '%s is only available for FIRST members' % display_id)
            raise

        formats, subtitles = self._extract_m3u8_formats_and_subtitles(
            m3u8_url, display_id, 'mp4', 'm3u8_native', m3u8_id='hls')
        self._sort_formats(formats)

        episode = self._download_json(
            api_episode_url, display_id,
            'Downloading episode JSON metadata')['data'][0]

        return {
            'display_id': display_id,
            'formats': formats,
            'subtitles': subtitles,
            **self._extract_video_info(episode)
        }

class RoosterTeethSeriesIE(RoosterTeethBaseIE):
    _VALID_URL = r'https?://(?:.+?\.)?roosterteeth\.com/series/(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://roosterteeth.com/series/rwby?season=7',
        'playlist_count': 13,
        'info_dict': {
            'id': 'rwby-7',
            'title': 'RWBY - Season 7',
        }
    }, {
        'url': 'https://roosterteeth.com/series/role-initiative',
        'playlist_mincount': 16,
        'info_dict': {
            'id': 'role-initiative',
            'title': 'Role Initiative',
        }
    }, {
        'url': 'https://roosterteeth.com/series/let-s-play-minecraft?season=9',
        'playlist_mincount': 50,
        'info_dict': {
            'id': 'let-s-play-minecraft-9',
            'title': 'Let\'s Play Minecraft - Season 9',
        }
    }]

    def _entries(self, series_id, season_number):
        display_id = join_nonempty(series_id, season_number)
        # TODO: extract bonus material
        for data in self._download_json(
                f'{self._API_BASE_URL}/shows/{series_id}/seasons?order=asc&order_by', display_id)['data']:
            idx = traverse_obj(data, ('attributes', 'number'))
            if season_number and idx != season_number:
                continue
            season_url = update_url_query(urljoin(self._API_BASE, data['links']['episodes']), {'per_page': 1000})
            season = self._download_json(season_url, display_id, f'Downloading season {idx} JSON metadata')['data']
            for episode in season:
                yield self.url_result(
                    f'https://www.roosterteeth.com{episode["canonical_links"]["self"]}',
                    RoosterTeethIE.ie_key(),
                    **self._extract_video_info(episode))

    def _real_extract(self, url):
        series_id = self._match_id(url)
        season_number = traverse_obj(parse_qs(url), ('season', 0), expected_type=int_or_none)

        entries = LazyList(self._entries(series_id, season_number))
        return self.playlist_result(
            entries,
            join_nonempty(series_id, season_number),
            join_nonempty(entries[0].get('series'), season_number, delim=' - Season '))


code:
from .common import InfoExtractor
from .internetvideoarchive import InternetVideoArchiveIE

class RottenTomatoesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?rottentomatoes\.com/m/[^/]+/trailers/(?P<id>\d+)'

    _TEST = {
        'url': 'http://www.rottentomatoes.com/m/toy_story_3/trailers/11028566/',
        'info_dict': {
            'id': '11028566',
            'ext': 'mp4',
            'title': 'Toy Story 3',
            'description': 'From the creators of the beloved TOY STORY films, comes a story that will reunite the gang in a whole new way.',
            'thumbnail': r're:^https?://.*\.jpg$',
        },
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        iva_id = self._search_regex(r'publishedid=(\d+)', webpage, 'internet video archive id')

        return {
            '_type': 'url_transparent',
            'url': 'http://video.internetvideoarchive.net/player/6/configuration.ashx?domain=www.videodetective.com&customerid=69249&playerid=641&publishedid=' + iva_id,
            'ie_key': InternetVideoArchiveIE.ie_key(),
            'id': video_id,
            'title': self._og_search_title(webpage),
        }


code:
from .common import InfoExtractor
from ..utils import (
    int_or_none,
    remove_start,
)

class RozhlasIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?prehravac\.rozhlas\.cz/audio/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://prehravac.rozhlas.cz/audio/3421320',
        'md5': '504c902dbc9e9a1fd50326eccf02a7e2',
        'info_dict': {
            'id': '3421320',
            'ext': 'mp3',
            'title': 'Echo Pavla Klusáka (30.06.2015 21:00)',
            'description': 'Osmdesátiny Terryho Rileyho jsou skvělou příležitostí proletět se elektronickými i akustickými díly zakladatatele minimalismu, který je aktivní už přes padesát let'
        }
    }, {
        'url': 'http://prehravac.rozhlas.cz/audio/3421320/embed',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            'http://prehravac.rozhlas.cz/audio/%s' % audio_id, audio_id)

        title = self._html_search_regex(
            r'
(.+?)
\s*
]*>.*?
\s*
]+id=["\']player-track',
            webpage, 'title', default=None) or remove_start(
            self._og_search_title(webpage), 'Radio Wave - ')
        description = self._html_search_regex(
            r'
]+title=(["\'])(?P<url>(?:(?!\1).)+)\1[^>]*>.*?
\s*
]+id=["\']player-track',
            webpage, 'description', fatal=False, group='url')
        duration = int_or_none(self._search_regex(
            r'data-duration=["\'](\d+)', webpage, 'duration', default=None))

        return {
            'id': audio_id,
            'url': 'http://media.rozhlas.cz/_audio/%s.mp3' % audio_id,
            'title': title,
            'description': description,
            'duration': duration,
            'vcodec': 'none',
        }