code:
from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    float_or_none,
    int_or_none,
    join_nonempty,
    parse_iso8601,
    qualities,
    try_get,
)

class SRGSSRIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    (?:
                        https?://tp\.srgssr\.ch/p(?:/[^/]+)+\?urn=urn|
                        srgssr
                    ):
                    (?P<bu>
                        srf|rts|rsi|rtr|swi
                    ):(?:[^:]+:)?
                    (?P<type>
                        video|audio
                    ):
                    (?P<id>
                        [0-9a-f\-]{36}|\d+
                    )
                    '''
    _GEO_BYPASS = False
    _GEO_COUNTRIES = ['CH']

    _ERRORS = {
        'AGERATING12': 'To protect children under the age of 12, this video is only available between 8 p.m. and 6 a.m.',
        'AGERATING18': 'To protect children under the age of 18, this video is only available between 11 p.m. and 5 a.m.',
        # 'ENDDATE': 'For legal reasons, this video was only available for a specified period of time.',
        'GEOBLOCK': 'For legal reasons, this video is only available in Switzerland.',
        'LEGAL': 'The video cannot be transmitted for legal reasons.',
        'STARTDATE': 'This video is not yet available. Please try again later.',
    }
    _DEFAULT_LANGUAGE_CODES = {
        'srf': 'de',
        'rts': 'fr',
        'rsi': 'it',
        'rtr': 'rm',
        'swi': 'en',
    }

    def _get_tokenized_src(self, url, video_id, format_id):
        token = self._download_json(
            'http://tp.srgssr.ch/akahd/token?acl=*',
            video_id, 'Downloading %s token' % format_id, fatal=False) or {}
        auth_params = try_get(token, lambda x: x['token']['authparams'])
        if auth_params:
            url += ('?' if '?' not in url else '&') + auth_params
        return url

    def _get_media_data(self, bu, media_type, media_id):
        query = {'onlyChapters': True} if media_type == 'video' else {}
        full_media_data = self._download_json(
            'https://il.srgssr.ch/integrationlayer/2.0/%s/mediaComposition/%s/%s.json'
            % (bu, media_type, media_id),
            media_id, query=query)['chapterList']
        try:
            media_data = next(
                x for x in full_media_data if x.get('id') == media_id)
        except StopIteration:
            raise ExtractorError('No media information found')

        block_reason = media_data.get('blockReason')
        if block_reason and block_reason in self._ERRORS:
            message = self._ERRORS[block_reason]
            if block_reason == 'GEOBLOCK':
                self.raise_geo_restricted(
                    msg=message, countries=self._GEO_COUNTRIES)
            raise ExtractorError(
                '%s said: %s' % (self.IE_NAME, message), expected=True)

        return media_data

    def _real_extract(self, url):
        bu, media_type, media_id = self._match_valid_url(url).groups()
        media_data = self._get_media_data(bu, media_type, media_id)
        title = media_data['title']

        formats = []
        subtitles = {}
        q = qualities(['SD', 'HD'])
        for source in (media_data.get('resourceList') or []):
            format_url = source.get('url')
            if not format_url:
                continue
            protocol = source.get('protocol')
            quality = source.get('quality')
            format_id = join_nonempty(protocol, source.get('encoding'), quality)

            if protocol in ('HDS', 'HLS'):
                if source.get('tokenType') == 'AKAMAI':
                    format_url = self._get_tokenized_src(
                        format_url, media_id, format_id)
                    fmts, subs = self._extract_akamai_formats_and_subtitles(
                        format_url, media_id)
                    formats.extend(fmts)
                    subtitles = self._merge_subtitles(subtitles, subs)
                elif protocol == 'HLS':
                    m3u8_fmts, m3u8_subs = self._extract_m3u8_formats_and_subtitles(
                        format_url, media_id, 'mp4', 'm3u8_native',
                        m3u8_id=format_id, fatal=False)
                    formats.extend(m3u8_fmts)
                    subtitles = self._merge_subtitles(subtitles, m3u8_subs)
            elif protocol in ('HTTP', 'HTTPS'):
                formats.append({
                    'format_id': format_id,
                    'url': format_url,
                    'quality': q(quality),
                })

        # This is needed because for audio medias the podcast url is usually
        # always included, even if is only an audio segment and not the
        # whole episode.
        if int_or_none(media_data.get('position')) == 0:
            for p in ('S', 'H'):
                podcast_url = media_data.get('podcast%sdUrl' % p)
                if not podcast_url:
                    continue
                quality = p + 'D'
                formats.append({
                    'format_id': 'PODCAST-' + quality,
                    'url': podcast_url,
                    'quality': q(quality),
                })
        self._sort_formats(formats)

        if media_type == 'video':
            for sub in (media_data.get('subtitleList') or []):
                sub_url = sub.get('url')
                if not sub_url:
                    continue
                lang = sub.get('locale') or self._DEFAULT_LANGUAGE_CODES[bu]
                subtitles.setdefault(lang, []).append({
                    'url': sub_url,
                })

        return {
            'id': media_id,
            'title': title,
            'description': media_data.get('description'),
            'timestamp': parse_iso8601(media_data.get('date')),
            'thumbnail': media_data.get('imageUrl'),
            'duration': float_or_none(media_data.get('duration'), 1000),
            'subtitles': subtitles,
            'formats': formats,
        }

class SRGSSRPlayIE(InfoExtractor):
    IE_DESC = 'srf.ch, rts.ch, rsi.ch, rtr.ch and swissinfo.ch play sites'
    _VALID_URL = r'''(?x)
                    https?://
                        (?:(?:www|play)\.)?
                        (?P<bu>srf|rts|rsi|rtr|swissinfo)\.ch/play/(?:tv|radio)/
                        (?:
                            [^/]+/(?P<type>video|audio)/[^?]+|
                            popup(?P<type_2>video|audio)player
                        )
                        \?.*?\b(?:id=|urn=urn:[^:]+:video:)(?P<id>[0-9a-f\-]{36}|\d+)
                    '''

    _TESTS = [{
        'url': 'http://www.srf.ch/play/tv/10vor10/video/snowden-beantragt-asyl-in-russland?id=28e1a57d-5b76-4399-8ab3-9097f071e6c5',
        'md5': '6db2226ba97f62ad42ce09783680046c',
        'info_dict': {
            'id': '28e1a57d-5b76-4399-8ab3-9097f071e6c5',
            'ext': 'mp4',
            'upload_date': '20130701',
            'title': 'Snowden beantragt Asyl in Russland',
            'timestamp': 1372708215,
            'duration': 113.827,
            'thumbnail': r're:^https?://.*1383719781\.png$',
        },
        'expected_warnings': ['Unable to download f4m manifest'],
    }, {
        'url': 'http://www.rtr.ch/play/radio/actualitad/audio/saira-tujetsch-tuttina-cuntinuar-cun-sedrun-muster-turissem?id=63cb0778-27f8-49af-9284-8c7a8c6d15fc',
        'info_dict': {
            'id': '63cb0778-27f8-49af-9284-8c7a8c6d15fc',
            'ext': 'mp3',
            'upload_date': '20151013',
            'title': 'Saira: Tujetsch - tuttina cuntinuar cun Sedrun Mustér Turissem',
            'timestamp': 1444709160,
            'duration': 336.816,
        },
        'params': {
            # rtmp download
            'skip_download': True,
        },
    }, {
        'url': 'http://www.rts.ch/play/tv/-/video/le-19h30?id=6348260',
        'md5': '67a2a9ae4e8e62a68d0e9820cc9782df',
        'info_dict': {
            'id': '6348260',
            'display_id': '6348260',
            'ext': 'mp4',
            'duration': 1796.76,
            'title': 'Le 19h30',
            'upload_date': '20141201',
            'timestamp': 1417458600,
            'thumbnail': r're:^https?://.*\.image',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }, {
        'url': 'http://play.swissinfo.ch/play/tv/business/video/why-people-were-against-tax-reforms?id=42960270',
        'info_dict': {
            'id': '42960270',
            'ext': 'mp4',
            'title': 'Why people were against tax reforms',
            'description': 'md5:7ac442c558e9630e947427469c4b824d',
            'duration': 94.0,
            'upload_date': '20170215',
            'timestamp': 1487173560,
            'thumbnail': r're:https?://www\.swissinfo\.ch/srgscalableimage/42961964',
            'subtitles': 'count:9',
        },
        'params': {
            'skip_download': True,
        }
    }, {
        'url': 'https://www.srf.ch/play/tv/popupvideoplayer?id=c4dba0ca-e75b-43b2-a34f-f708a4932e01',
        'only_matching': True,
    }, {
        'url': 'https://www.srf.ch/play/tv/10vor10/video/snowden-beantragt-asyl-in-russland?urn=urn:srf:video:28e1a57d-5b76-4399-8ab3-9097f071e6c5',
        'only_matching': True,
    }, {
        'url': 'https://www.rts.ch/play/tv/19h30/video/le-19h30?urn=urn:rts:video:6348260',
        'only_matching': True,
    }, {
        # audio segment, has podcastSdUrl of the full episode
        'url': 'https://www.srf.ch/play/radio/popupaudioplayer?id=50b20dc8-f05b-4972-bf03-e438ff2833eb',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        bu = mobj.group('bu')
        media_type = mobj.group('type') or mobj.group('type_2')
        media_id = mobj.group('id')
        return self.url_result('srgssr:%s:%s:%s' % (bu[:3], media_type, media_id), 'SRGSSR')


code:
from .ard import ARDMediathekBaseIE
from ..utils import (
    ExtractorError,
    get_element_by_attribute,
)

class SRMediathekIE(ARDMediathekBaseIE):
    IE_NAME = 'sr:mediathek'
    IE_DESC = 'Saarländischer Rundfunk'
    _VALID_URL = r'https?://sr-mediathek(?:\.sr-online)?\.de/index\.php\?.*?&id=(?P<id>[0-9]+)'

    _TESTS = [{
        'url': 'http://sr-mediathek.sr-online.de/index.php?seite=7&id=28455',
        'info_dict': {
            'id': '28455',
            'ext': 'mp4',
            'title': 'sportarena (26.10.2014)',
            'description': 'Ringen: KSV Köllerbach gegen Aachen-Walheim; Frauen-Fußball: 1. FC Saarbrücken gegen Sindelfingen; Motorsport: Rallye in Losheim; dazu: Interview mit Timo Bernhard; Turnen: TG Saar; Reitsport: Deutscher Voltigier-Pokal; Badminton: Interview mit Michael Fuchs ',
            'thumbnail': r're:^https?://.*\.jpg$',
        },
        'skip': 'no longer available',
    }, {
        'url': 'http://sr-mediathek.sr-online.de/index.php?seite=7&id=37682',
        'info_dict': {
            'id': '37682',
            'ext': 'mp4',
            'title': 'Love, Cakes and Rock\'n\'Roll',
            'description': 'md5:18bf9763631c7d326c22603681e1123d',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'http://sr-mediathek.de/index.php?seite=7&id=7480',
        'only_matching': True,
    }]

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

        if '>Der gewünschte Beitrag ist leider nicht mehr verfügbar.<' in webpage:
            raise ExtractorError('Video %s is no longer available' % video_id, expected=True)

        media_collection_url = self._search_regex(
            r'data-mediacollection-ardplayer="([^"]+)"', webpage, 'media collection url')
        info = self._extract_media_info(media_collection_url, webpage, video_id)
        info.update({
            'id': video_id,
            'title': get_element_by_attribute('class', 'ardplayer-title', webpage),
            'description': self._og_search_description(webpage),
            'thumbnail': self._og_search_thumbnail(webpage),
        })
        return info

repo_name: Lesmiscore/ytdl


code:
import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    orderedSet,
    unescapeHTML,
)

class StanfordOpenClassroomIE(InfoExtractor):
    IE_NAME = 'stanfordoc'
    IE_DESC = 'Stanford Open ClassRoom'
    _VALID_URL = r'https?://openclassroom\.stanford\.edu(?P<path>/?|(/MainFolder/(?:HomePage|CoursePage|VideoPage)\.php([?]course=(?P<course>[^&]+)(&video=(?P<video>[^&]+))?(&.*)?)?))$'
    _TEST = {
        'url': 'http://openclassroom.stanford.edu/MainFolder/VideoPage.php?course=PracticalUnix&video=intro-environment&speed=100',
        'md5': '544a9468546059d4e80d76265b0443b8',
        'info_dict': {
            'id': 'PracticalUnix_intro-environment',
            'ext': 'mp4',
            'title': 'Intro Environment',
        }
    }

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)

        if mobj.group('course') and mobj.group('video'):  # A specific video
            course = mobj.group('course')
            video = mobj.group('video')
            info = {
                'id': course + '_' + video,
                'uploader': None,
                'upload_date': None,
            }

            baseUrl = 'http://openclassroom.stanford.edu/MainFolder/courses/' + course + '/videos/'
            xmlUrl = baseUrl + video + '.xml'
            mdoc = self._download_xml(xmlUrl, info['id'])
            try:
                info['title'] = mdoc.findall('./title')[0].text
                info['url'] = baseUrl + mdoc.findall('./videoFile')[0].text
            except IndexError:
                raise ExtractorError('Invalid metadata XML file')
            return info
        elif mobj.group('course'):  # A course page
            course = mobj.group('course')
            info = {
                'id': course,
                '_type': 'playlist',
                'uploader': None,
                'upload_date': None,
            }

            coursepage = self._download_webpage(
                url, info['id'],
                note='Downloading course info page',
                errnote='Unable to download course info page')

            info['title'] = self._html_search_regex(
                r'
([^<]+)
', coursepage, 'title', default=info['id'])

            info['description'] = self._html_search_regex(
                r'(?s)<description>([^<]+)</description>',
                coursepage, 'description', fatal=False)

            links = orderedSet(re.findall(r'', coursepage))
            info['entries'] = [self.url_result(
                'http://openclassroom.stanford.edu/MainFolder/%s' % unescapeHTML(l)
            ) for l in links]
            return info
        else:  # Root page
            info = {
                'id': 'Stanford OpenClassroom',
                '_type': 'playlist',
                'uploader': None,
                'upload_date': None,
            }
            info['title'] = info['id']

            rootURL = 'http://openclassroom.stanford.edu/MainFolder/HomePage.php'
            rootpage = self._download_webpage(rootURL, info['id'],
                                              errnote='Unable to download course info page')

            links = orderedSet(re.findall(r'', rootpage))
            info['entries'] = [self.url_result(
                'http://openclassroom.stanford.edu/MainFolder/%s' % unescapeHTML(l)
            ) for l in links]
            return info


code:
from .common import InfoExtractor
from ..utils import int_or_none, urljoin

class StarTrekIE(InfoExtractor):
    _VALID_URL = r'(?P<base>https?://(?:intl|www)\.startrek\.com)/videos/(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'https://intl.startrek.com/videos/watch-welcoming-jess-bush-to-the-ready-room',
        'md5': '491df5035c9d4dc7f63c79caaf9c839e',
        'info_dict': {
            'id': 'watch-welcoming-jess-bush-to-the-ready-room',
            'ext': 'mp4',
            'title': 'WATCH: Welcoming Jess Bush to The Ready Room',
            'duration': 1888,
            'timestamp': 1655388000,
            'upload_date': '20220616',
            'description': 'md5:1ffee884e3920afbdd6dd04e926a1221',
            'thumbnail': r're:https://(?:intl|www)\.startrek\.com/sites/default/files/styles/video_1920x1080/public/images/2022-06/pp_14794_rr_thumb_107_yt_16x9\.jpg(?:\?.+)?',
            'subtitles': {'en-US': [{
                'url': r're:https://(?:intl|www)\.startrek\.com/sites/default/files/video/captions/2022-06/TRR_SNW_107_v4\.vtt',
            }, {
                'url': 'https://media.startrek.com/2022/06/16/2043801155561/1069981_hls/trr_snw_107_v4-c4bfc25d/stream_vtt.m3u8',
            }]},
        }
    }, {
        'url': 'https://www.startrek.com/videos/watch-ethan-peck-and-gia-sandhu-beam-down-to-the-ready-room',
        'md5': 'f5ad74fbb86e91e0882fc0a333178d1d',
        'info_dict': {
            'id': 'watch-ethan-peck-and-gia-sandhu-beam-down-to-the-ready-room',
            'ext': 'mp4',
            'title': 'WATCH: Ethan Peck and Gia Sandhu Beam Down to The Ready Room',
            'duration': 1986,
            'timestamp': 1654221600,
            'upload_date': '20220603',
            'description': 'md5:b3aa0edacfe119386567362dec8ed51b',
            'thumbnail': r're:https://www\.startrek\.com/sites/default/files/styles/video_1920x1080/public/images/2022-06/pp_14792_rr_thumb_105_yt_16x9_1.jpg(?:\?.+)?',
            'subtitles': {'en-US': [{
                'url': r're:https://(?:intl|www)\.startrek\.com/sites/default/files/video/captions/2022-06/TRR_SNW_105_v5\.vtt',
            }]},
        }
    }]

    def _real_extract(self, url):
        urlbase, video_id = self._match_valid_url(url).group('base', 'id')
        webpage = self._download_webpage(url, video_id)

        player = self._search_regex(
            r'(<\s*div\s+id\s*=\s*"cvp-player-[^<]+<\s*/div\s*>)', webpage, 'player')

        hls = self._html_search_regex(r'\bdata-hls\s*=\s*"([^"]+)"', player, 'HLS URL')
        formats, subtitles = self._extract_m3u8_formats_and_subtitles(hls, video_id, 'mp4')
        self._sort_formats(formats)

        captions = self._html_search_regex(
            r'\bdata-captions-url\s*=\s*"([^"]+)"', player, 'captions URL', fatal=False)
        if captions:
            subtitles.setdefault('en-US', [])[:0] = [{'url': urljoin(urlbase, captions)}]

        # NB: Most of the data in the json_ld is undesirable
        json_ld = self._search_json_ld(webpage, video_id, fatal=False)

        return {
            'id': video_id,
            'title': self._html_search_regex(
                r'\bdata-title\s*=\s*"([^"]+)"', player, 'title', json_ld.get('title')),
            'description': self._html_search_regex(
                r'(?s)<\s*div\s+class\s*=\s*"header-body"\s*>(.+?)<\s*/div\s*>',
                webpage, 'description', fatal=False),
            'duration': int_or_none(self._html_search_regex(
                r'\bdata-duration\s*=\s*"(\d+)"', player, 'duration', fatal=False)),
            'formats': formats,
            'subtitles': subtitles,
            'thumbnail': urljoin(urlbase, self._html_search_regex(
                r'\bdata-poster-url\s*=\s*"([^"]+)"', player, 'thumbnail', fatal=False)),
            'timestamp': json_ld.get('timestamp'),
        }


code:
from .common import InfoExtractor
from ..compat import (
    compat_str,
)
from ..utils import (
    clean_html,
    ExtractorError,
    traverse_obj,
    int_or_none,
)

class StarTVIE(InfoExtractor):
    _VALID_URL = r"""(?x)
        https?://(?:www\.)?startv\.com\.tr/
        (?:
            (?:dizi|program)/(?:[^/?#&]+)/(?:bolumler|fragmanlar|ekstralar)|
            video/arsiv/(?:dizi|program)/(?:[^/?#&]+)
        )/
        (?P<id>[^/?#&]+)
    """
    IE_NAME = 'startv'
    _TESTS = [
        {
            'url': 'https://www.startv.com.tr/dizi/cocuk/bolumler/3-bolum',
            'md5': '72381a32bcc2e2eb5841e8c8bf68f127',
            'info_dict': {
                'id': '904972',
                'display_id': '3-bolum',
                'ext': 'mp4',
                'title': '3. Bölüm',
                'description': 'md5:3a8049f05a75c2e8747116a673275de4',
                'thumbnail': r're:^https?://.*\.jpg(?:\?.*?)?$',
                'timestamp': 1569281400,
                'upload_date': '20190923'
            },
        },
        {
            'url': 'https://www.startv.com.tr/video/arsiv/dizi/avlu/44-bolum',
            'only_matching': True
        },
        {
            'url': 'https://www.startv.com.tr/dizi/cocuk/fragmanlar/5-bolum-fragmani',
            'only_matching': True
        },
        {
            'url': 'https://www.startv.com.tr/dizi/cocuk/ekstralar/5-bolumun-nefes-kesen-final-sahnesi',
            'only_matching': True
        },
        {
            'url': 'https://www.startv.com.tr/program/burcu-ile-haftasonu/bolumler/1-bolum',
            'only_matching': True
        },
        {
            'url': 'https://www.startv.com.tr/program/burcu-ile-haftasonu/fragmanlar/2-fragman',
            'only_matching': True
        },
        {
            'url': 'https://www.startv.com.tr/video/arsiv/program/buyukrisk/14-bolumde-hangi-unlu-ne-sordu-',
            'only_matching': True
        },
        {
            'url': 'https://www.startv.com.tr/video/arsiv/program/buyukrisk/buyuk-risk-334-bolum',
            'only_matching': True
        },
        {
            'url': 'https://www.startv.com.tr/video/arsiv/program/dada/dada-58-bolum',
            'only_matching': True
        }
    ]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        info_url = self._search_regex(
            r'(["\'])videoUrl\1\s*:\s*\1(?P<url>(?:(?!\1).)+)\1\s*',
            webpage, 'video info url', group='url')

        info = traverse_obj(self._download_json(info_url, display_id), 'data', expected_type=dict)
        if not info:
            raise ExtractorError('Failed to extract API data')

        video_id = compat_str(info.get('id'))
        title = info.get('title') or self._og_search_title(webpage)
        description = clean_html(info.get('description')) or self._og_search_description(webpage, default=None)
        thumbnail = self._proto_relative_url(
            self._og_search_thumbnail(webpage), scheme='http:')

        formats = self._extract_m3u8_formats(
            traverse_obj(info, ('flavors', 'hls')), video_id, entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'timestamp': int_or_none(info.get('release_date')),
            'formats': formats
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    extract_attributes,
    ExtractorError,
    get_element_by_class,
)

class SteamIE(InfoExtractor):
    _VALID_URL = r"""(?x)
        https?://(?:store\.steampowered|steamcommunity)\.com/
            (?:agecheck/)?
            (?P<urltype>video|app)/ #If the page is only for videos or for a game
            (?P<gameID>\d+)/?
            (?P<videoID>\d*)(?P<extra>\??) # For urltype == video we sometimes get the videoID
        |
        https?://(?:www\.)?steamcommunity\.com/sharedfiles/filedetails/\?id=(?P<fileID>[0-9]+)
    """
    _VIDEO_PAGE_TEMPLATE = 'http://store.steampowered.com/video/%s/'
    _AGECHECK_TEMPLATE = 'http://store.steampowered.com/agecheck/video/%s/?snr=1_agecheck_agecheck__age-gate&ageDay=1&ageMonth=January&ageYear=1970'
    _TESTS = [{
        'url': 'http://store.steampowered.com/video/105600/',
        'playlist': [
            {
                'md5': '695242613303ffa2a4c44c9374ddc067',
                'info_dict': {
                    'id': '256785003',
                    'ext': 'mp4',
                    'title': 'Terraria video 256785003',
                    'thumbnail': r're:^https://cdn\.[^\.]+\.steamstatic\.com',
                    'n_entries': 2,
                }
            },
            {
                'md5': '6a294ee0c4b1f47f5bb76a65e31e3592',
                'info_dict': {
                    'id': '2040428',
                    'ext': 'mp4',
                    'title': 'Terraria video 2040428',
                    'playlist_index': 2,
                    'thumbnail': r're:^https://cdn\.[^\.]+\.steamstatic\.com',
                    'n_entries': 2,
                }
            }
        ],
        'info_dict': {
            'id': '105600',
            'title': 'Terraria',
        },
        'params': {
            'playlistend': 2,
        }
    }, {
        'url': 'https://store.steampowered.com/app/271590/Grand_Theft_Auto_V/',
        'info_dict': {
            'id': '256757115',
            'title': 'Grand Theft Auto V video 256757115',
            'ext': 'mp4',
            'thumbnail': r're:^https://cdn\.[^\.]+\.steamstatic\.com',
            'n_entries': 20,
        },
    }]

    def _real_extract(self, url):
        m = self._match_valid_url(url)
        fileID = m.group('fileID')
        if fileID:
            video_url = url
            playlist_id = fileID
        else:
            gameID = m.group('gameID')
            playlist_id = gameID
            video_url = self._VIDEO_PAGE_TEMPLATE % playlist_id

        self._set_cookie('steampowered.com', 'wants_mature_content', '1')
        self._set_cookie('steampowered.com', 'birthtime', '944006401')
        self._set_cookie('steampowered.com', 'lastagecheckage', '1-0-2000')

        webpage = self._download_webpage(video_url, playlist_id)

        if re.search('<div[^>]+>Please enter your birth date to continue:</div>', webpage) is not None:
            video_url = self._AGECHECK_TEMPLATE % playlist_id
            self.report_age_confirmation()
            webpage = self._download_webpage(video_url, playlist_id)

        videos = re.findall(r'(<div[^>]+id=[\'"]highlight_movie_(\d+)[\'"][^>]+>)', webpage)
        entries = []
        playlist_title = get_element_by_class('apphub_AppName', webpage)
        for movie, movie_id in videos:
            if not movie:
                continue
            movie = extract_attributes(movie)
            if not movie_id:
                continue
            entry = {
                'id': movie_id,
                'title': f'{playlist_title} video {movie_id}',
            }
            formats = []
            if movie:
                entry['thumbnail'] = movie.get('data-poster')
                for quality in ('', '-hd'):
                    for ext in ('webm', 'mp4'):
                        video_url = movie.get('data-%s%s-source' % (ext, quality))
                        if video_url:
                            formats.append({
                                'format_id': ext + quality,
                                'url': video_url,
                            })
            self._sort_formats(formats)
            entry['formats'] = formats
            entries.append(entry)
        embedded_videos = re.findall(r'(<iframe[^>]+>)', webpage)
        for evideos in embedded_videos:
            evideos = extract_attributes(evideos).get('src')
            video_id = self._search_regex(r'youtube\.com/embed/([0-9A-Za-z_-]{11})', evideos, 'youtube_video_id', default=None)
            if video_id:
                entries.append({
                    '_type': 'url_transparent',
                    'id': video_id,
                    'url': video_id,
                    'ie_key': 'Youtube',
                })
        if not entries:
            raise ExtractorError('Could not find any videos')

        return self.playlist_result(entries, playlist_id, playlist_title)

class SteamCommunityBroadcastIE(InfoExtractor):
    _VALID_URL = r'https?://steamcommunity\.(?:com)/broadcast/watch/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://steamcommunity.com/broadcast/watch/76561199073851486',
        'info_dict': {
            'id': '76561199073851486',
            'title': r're:Steam Community :: pepperm!nt :: Broadcast 2022-06-26 \d{2}:\d{2}',
            'ext': 'mp4',
            'uploader_id': 1113585758,
            'uploader': 'pepperm!nt',
            'live_status': 'is_live',
        },
        'skip': 'Stream has ended',
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        json_data = self._download_json(
            'https://steamcommunity.com/broadcast/getbroadcastmpd/',
            video_id, query={'steamid': f'{video_id}'})

        formats, subs = self._extract_m3u8_formats_and_subtitles(json_data['hls_url'], video_id)

        ''' # We cannot download live dash atm
        mpd_formats, mpd_subs = self._extract_mpd_formats_and_subtitles(json_data['url'], video_id)
        formats.extend(mpd_formats)
        self._merge_subtitles(mpd_subs, target=subs)
        '''

        uploader_json = self._download_json(
            'https://steamcommunity.com/actions/ajaxresolveusers',
            video_id, query={'steamids': video_id})[0]

        self._sort_formats(formats)
        return {
            'id': video_id,
            'title': self._html_extract_title(webpage) or self._og_search_title(webpage),
            'formats': formats,
            'live_status': 'is_live',
            'view_count': json_data.get('num_view'),
            'uploader': uploader_json.get('persona_name'),
            'uploader_id': uploader_json.get('accountid'),
            'subtitles': subs,
        }


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    clean_html,
    clean_podcast_url,
    ExtractorError,
    int_or_none,
    str_or_none,
    try_get,
    url_or_none,
)

class StitcherBaseIE(InfoExtractor):
    _VALID_URL_BASE = r'https?://(?:www\.)?stitcher\.com/(?:podcast|show)/'

    def _call_api(self, path, video_id, query):
        resp = self._download_json(
            'https://api.prod.stitcher.com/' + path,
            video_id, query=query)
        error_massage = try_get(resp, lambda x: x['errors'][0]['message'])
        if error_massage:
            raise ExtractorError(error_massage, expected=True)
        return resp['data']

    def _extract_description(self, data):
        return clean_html(data.get('html_description') or data.get('description'))

    def _extract_audio_url(self, episode):
        return url_or_none(episode.get('audio_url') or episode.get('guid'))

    def _extract_show_info(self, show):
        return {
            'thumbnail': show.get('image_base_url'),
            'series': show.get('title'),
        }

    def _extract_episode(self, episode, audio_url, show_info):
        info = {
            'id': compat_str(episode['id']),
            'display_id': episode.get('slug'),
            'title': episode['title'].strip(),
            'description': self._extract_description(episode),
            'duration': int_or_none(episode.get('duration')),
            'url': clean_podcast_url(audio_url),
            'vcodec': 'none',
            'timestamp': int_or_none(episode.get('date_published')),
            'season_number': int_or_none(episode.get('season')),
            'season_id': str_or_none(episode.get('season_id')),
        }
        info.update(show_info)
        return info

class StitcherIE(StitcherBaseIE):
    _VALID_URL = StitcherBaseIE._VALID_URL_BASE + r'(?:[^/]+/)+e(?:pisode)?/(?:[^/#?&]+-)?(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://www.stitcher.com/podcast/the-talking-machines/e/40789481?autoplay=true',
        'md5': 'e9635098e0da10b21a0e2b85585530f6',
        'info_dict': {
            'id': '40789481',
            'ext': 'mp3',
            'title': 'Machine Learning Mastery and Cancer Clusters',
            'description': 'md5:547adb4081864be114ae3831b4c2b42f',
            'duration': 1604,
            'thumbnail': r're:^https?://.*\.jpg',
            'upload_date': '20151008',
            'timestamp': 1444285800,
            'series': 'Talking Machines',
        },
    }, {
        'url': 'http://www.stitcher.com/podcast/panoply/vulture-tv/e/the-rare-hourlong-comedy-plus-40846275?autoplay=true',
        'info_dict': {
            'id': '40846275',
            'display_id': 'the-rare-hourlong-comedy-plus',
            'ext': 'mp3',
            'title': "The CW's 'Crazy Ex-Girlfriend'",
            'description': 'md5:04f1e2f98eb3f5cbb094cea0f9e19b17',
            'duration': 2235,
            'thumbnail': r're:^https?://.*\.jpg',
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'Page Not Found',
    }, {
        # escaped title
        'url': 'http://www.stitcher.com/podcast/marketplace-on-stitcher/e/40910226?autoplay=true',
        'only_matching': True,
    }, {
        'url': 'http://www.stitcher.com/podcast/panoply/getting-in/e/episode-2a-how-many-extracurriculars-should-i-have-40876278?autoplay=true',
        'only_matching': True,
    }, {
        'url': 'https://www.stitcher.com/show/threedom/episode/circles-on-a-stick-200212584',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        audio_id = self._match_id(url)
        data = self._call_api(
            'shows/episodes', audio_id, {'episode_ids': audio_id})
        episode = data['episodes'][0]
        audio_url = self._extract_audio_url(episode)
        if not audio_url:
            self.raise_login_required()
        show = try_get(data, lambda x: x['shows'][0], dict) or {}
        return self._extract_episode(
            episode, audio_url, self._extract_show_info(show))

class StitcherShowIE(StitcherBaseIE):
    _VALID_URL = StitcherBaseIE._VALID_URL_BASE + r'(?P<id>[^/#?&]+)/?(?:[?#&]|$)'
    _TESTS = [{
        'url': 'http://www.stitcher.com/podcast/the-talking-machines',
        'info_dict': {
            'id': 'the-talking-machines',
            'title': 'Talking Machines',
            'description': 'md5:831f0995e40f26c10231af39cf1ebf0b',
        },
        'playlist_mincount': 106,
    }, {
        'url': 'https://www.stitcher.com/show/the-talking-machines',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        show_slug = self._match_id(url)
        data = self._call_api(
            'search/show/%s/allEpisodes' % show_slug, show_slug, {'count': 10000})
        show = try_get(data, lambda x: x['shows'][0], dict) or {}
        show_info = self._extract_show_info(show)

        entries = []
        for episode in (data.get('episodes') or []):
            audio_url = self._extract_audio_url(episode)
            if not audio_url:
                continue
            entries.append(self._extract_episode(episode, audio_url, show_info))

        return self.playlist_result(
            entries, show_slug, show.get('title'),
            self._extract_description(show))


code:
import functools

from .common import InfoExtractor
from ..utils import (
    format_field,
    int_or_none,
    OnDemandPagedList,
    smuggle_url,
)

class StoryFireBaseIE(InfoExtractor):
    _VALID_URL_BASE = r'https?://(?:www\.)?storyfire\.com/'

    def _call_api(self, path, video_id, resource, query=None):
        return self._download_json(
            'https://storyfire.com/app/%s/%s' % (path, video_id), video_id,
            'Downloading %s JSON metadata' % resource, query=query)

    def _parse_video(self, video):
        title = video['title']
        vimeo_id = self._search_regex(
            r'https?://player\.vimeo\.com/external/(\d+)',
            video['vimeoVideoURL'], 'vimeo id')

        uploader_id = video.get('hostID')

        return {
            '_type': 'url_transparent',
            'id': vimeo_id,
            'title': title,
            'description': video.get('description'),
            'url': smuggle_url(
                'https://player.vimeo.com/video/' + vimeo_id, {
                    'http_headers': {
                        'Referer': 'https://storyfire.com/',
                    }
                }),
            'thumbnail': video.get('storyImage'),
            'view_count': int_or_none(video.get('views')),
            'like_count': int_or_none(video.get('likesCount')),
            'comment_count': int_or_none(video.get('commentsCount')),
            'duration': int_or_none(video.get('videoDuration')),
            'timestamp': int_or_none(video.get('publishDate')),
            'uploader': video.get('username'),
            'uploader_id': uploader_id,
            'uploader_url': format_field(uploader_id, None, 'https://storyfire.com/user/%s/video'),
            'episode_number': int_or_none(video.get('episodeNumber') or video.get('episode_number')),
        }

class StoryFireIE(StoryFireBaseIE):
    _VALID_URL = StoryFireBaseIE._VALID_URL_BASE + r'video-details/(?P<id>[0-9a-f]{24})'
    _TEST = {
        'url': 'https://storyfire.com/video-details/5df1d132b6378700117f9181',
        'md5': 'caec54b9e4621186d6079c7ec100c1eb',
        'info_dict': {
            'id': '378954662',
            'ext': 'mp4',
            'title': 'Buzzfeed Teaches You About Memes',
            'uploader_id': 'ntZAJFECERSgqHSxzonV5K2E89s1',
            'timestamp': 1576129028,
            'description': 'md5:0b4e28021548e144bed69bb7539e62ea',
            'uploader': 'whang!',
            'upload_date': '20191212',
            'duration': 418,
            'view_count': int,
            'like_count': int,
            'comment_count': int,
        },
        'params': {
            'skip_download': True,
        },
        'expected_warnings': ['Unable to download JSON metadata']
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video = self._call_api(
            'generic/video-detail', video_id, 'video')['video']
        return self._parse_video(video)

class StoryFireUserIE(StoryFireBaseIE):
    _VALID_URL = StoryFireBaseIE._VALID_URL_BASE + r'user/(?P<id>[^/]+)/video'
    _TEST = {
        'url': 'https://storyfire.com/user/UQ986nFxmAWIgnkZQ0ftVhq4nOk2/video',
        'info_dict': {
            'id': 'UQ986nFxmAWIgnkZQ0ftVhq4nOk2',
        },
        'playlist_mincount': 151,
    }
    _PAGE_SIZE = 20

    def _fetch_page(self, user_id, page):
        videos = self._call_api(
            'publicVideos', user_id, 'page %d' % (page + 1), {
                'skip': page * self._PAGE_SIZE,
            })['videos']
        for video in videos:
            yield self._parse_video(video)

    def _real_extract(self, url):
        user_id = self._match_id(url)
        entries = OnDemandPagedList(functools.partial(
            self._fetch_page, user_id), self._PAGE_SIZE)
        return self.playlist_result(entries, user_id)

class StoryFireSeriesIE(StoryFireBaseIE):
    _VALID_URL = StoryFireBaseIE._VALID_URL_BASE + r'write/series/stories/(?P<id>[^/?&#]+)'
    _TESTS = [{
        'url': 'https://storyfire.com/write/series/stories/-Lq6MsuIHLODO6d2dDkr/',
        'info_dict': {
            'id': '-Lq6MsuIHLODO6d2dDkr',
        },
        'playlist_mincount': 13,
    }, {
        'url': 'https://storyfire.com/write/series/stories/the_mortal_one/',
        'info_dict': {
            'id': 'the_mortal_one',
        },
        'playlist_count': 0,
    }]

    def _extract_videos(self, stories):
        for story in stories.values():
            if story.get('hasVideo'):
                yield self._parse_video(story)

    def _real_extract(self, url):
        series_id = self._match_id(url)
        stories = self._call_api(
            'seriesStories', series_id, 'series stories')
        return self.playlist_result(self._extract_videos(stories), series_id)


code:
from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    float_or_none,
    int_or_none,
    try_get,
    parse_codecs,
)

class StreamableIE(InfoExtractor):
    _VALID_URL = r'https?://streamable\.com/(?:[es]/)?(?P<id>\w+)'
    _EMBED_REGEX = [r'<iframe[^>]+\bsrc=(?P<q1>[\'"])(?P<url>(?:https?:)?//streamable\.com/.+?)(?P=q1)']
    _TESTS = [
        {
            'url': 'https://streamable.com/dnd1',
            'md5': '3e3bc5ca088b48c2d436529b64397fef',
            'info_dict': {
                'id': 'dnd1',
                'ext': 'mp4',
                'title': 'Mikel Oiarzabal scores to make it 0-3 for La Real against Espanyol',
                'thumbnail': r're:https?://.*\.jpg$',
                'uploader': 'teabaker',
                'timestamp': 1454964157.35115,
                'upload_date': '20160208',
                'duration': 61.516,
                'view_count': int,
            }
        },
        # older video without bitrate, width/height, codecs, etc. info
        {
            'url': 'https://streamable.com/moo',
            'md5': '2cf6923639b87fba3279ad0df3a64e73',
            'info_dict': {
                'id': 'moo',
                'ext': 'mp4',
                'title': '"Please don\'t eat me!"',
                'thumbnail': r're:https?://.*\.jpg$',
                'timestamp': 1426115495,
                'upload_date': '20150311',
                'duration': 12,
                'view_count': int,
            }
        },
        {
            'url': 'https://streamable.com/e/dnd1',
            'only_matching': True,
        },
        {
            'url': 'https://streamable.com/s/okkqk/drxjds',
            'only_matching': True,
        }
    ]

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

        # Note: Using the ajax API, as the public Streamable API doesn't seem
        # to return video info like the title properly sometimes, and doesn't
        # include info like the video duration
        video = self._download_json(
            'https://ajax.streamable.com/videos/%s' % video_id, video_id)

        # Format IDs:
        # 0 The video is being uploaded
        # 1 The video is being processed
        # 2 The video has at least one file ready
        # 3 The video is unavailable due to an error
        status = video.get('status')
        if status != 2:
            raise ExtractorError(
                'This video is currently unavailable. It may still be uploading or processing.',
                expected=True)

        title = video.get('reddit_title') or video['title']

        formats = []
        for key, info in video['files'].items():
            if not info.get('url'):
                continue
            formats.append({
                'format_id': key,
                'url': self._proto_relative_url(info['url']),
                'width': int_or_none(info.get('width')),
                'height': int_or_none(info.get('height')),
                'filesize': int_or_none(info.get('size')),
                'fps': int_or_none(info.get('framerate')),
                'vbr': float_or_none(info.get('bitrate'), 1000),
                'vcodec': parse_codecs(try_get(info, lambda x: x['input_metadata']['video_codec_name'])).get('vcodec'),
                'acodec': parse_codecs(try_get(info, lambda x: x['input_metadata']['audio_codec_name'])).get('acodec'),
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': video.get('description'),
            'thumbnail': self._proto_relative_url(video.get('thumbnail_url')),
            'uploader': video.get('owner', {}).get('user_name'),
            'timestamp': float_or_none(video.get('date_added')),
            'duration': float_or_none(video.get('duration')),
            'view_count': int_or_none(video.get('plays')),
            'formats': formats
        }


code:
from .common import InfoExtractor

class StreamanityIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?streamanity\.com/video/(?P<id>[A-Za-z0-9]+)'
    _TESTS = [{
        'url': 'https://streamanity.com/video/9DFPTnuYi8f2',
        'md5': '6ab171e8d4a02ad5dcbff6bea44cf5a1',
        'info_dict': {
            'id': '9DFPTnuYi8f2',
            'ext': 'mp4',
            'title': 'Bitcoin vs The Lighting Network',
            'thumbnail': r're:https://res\.cloudinary\.com/.+\.png',
            'description': '',
            'uploader': 'Tom Bombadil (Freddy78)',
        }
    }, {
        'url': 'https://streamanity.com/video/JktOUjSlfzTD',
        'md5': '31f131e28abd3377c38be586a59532dc',
        'info_dict': {
            'id': 'JktOUjSlfzTD',
            'ext': 'mp4',
            'title': 'Share data when you see it',
            'thumbnail': r're:https://res\.cloudinary\.com/.+\.png',
            'description': 'Reposting as data should be public and stored on blockchain',
            'uploader': 'digitalcurrencydaily',
        }
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_info = self._download_json(
            f'https://app.streamanity.com/api/video/{video_id}', video_id)['data']['video']

        formats = self._extract_m3u8_formats(
            f'https://stream.mux.com/{video_info["play_id"]}.m3u8?token={video_info["token"]}',
            video_id, ext='mp4', m3u8_id='hls')
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video_info['title'],
            'description': video_info.get('description'),
            'uploader': video_info.get('author_name'),
            'is_live': False,
            'thumbnail': video_info.get('thumb'),
            'formats': formats,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    urlencode_postdata,
)

class StreamcloudIE(InfoExtractor):
    IE_NAME = 'streamcloud.eu'
    _VALID_URL = r'https?://streamcloud\.eu/(?P<id>[a-zA-Z0-9_-]+)(?:/(?P<fname>[^#?]*)\.html)?'

    _TESTS = [{
        'url': 'http://streamcloud.eu/skp9j99s4bpz/youtube-dl_test_video_____________-BaW_jenozKc.mp4.html',
        'md5': '6bea4c7fa5daaacc2a946b7146286686',
        'info_dict': {
            'id': 'skp9j99s4bpz',
            'ext': 'mp4',
            'title': 'youtube-dl test video  \'/\\ ä ↭',
        },
        'skip': 'Only available from the EU'
    }, {
        'url': 'http://streamcloud.eu/ua8cmfh1nbe6/NSHIP-148--KUC-NG--H264-.mp4.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        url = 'http://streamcloud.eu/%s' % video_id

        orig_webpage = self._download_webpage(url, video_id)

        if '>File Not Found<' in orig_webpage:
            raise ExtractorError(
                'Video %s does not exist' % video_id, expected=True)

        fields = re.findall(r'''(?x)<input\s+
            type="(?:hidden|submit)"\s+
            name="([^"]+)"\s+
            (?:id="[^"]+"\s+)?
            value="([^"]*)"
            ''', orig_webpage)

        self._sleep(6, video_id)

        webpage = self._download_webpage(
            url, video_id, data=urlencode_postdata(fields), headers={
                b'Content-Type': b'application/x-www-form-urlencoded',
            })

        try:
            title = self._html_search_regex(
                r'<h1[^>]*>([^<]+)<', webpage, 'title')
            video_url = self._search_regex(
                r'file:\s*"([^"]+)"', webpage, 'video URL')
        except ExtractorError:
            message = self._html_search_regex(
                r'(?s)<div[^>]+class=(["\']).*?msgboxinfo.*?\1[^>]*>(?P<message>.+?)</div>',
                webpage, 'message', default=None, group='message')
            if message:
                raise ExtractorError('%s said: %s' % (self.IE_NAME, message), expected=True)
            raise
        thumbnail = self._search_regex(
            r'image:\s*"([^"]+)"', webpage, 'thumbnail URL', fatal=False)

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


code:
import json

from .common import InfoExtractor
from ..utils import (
    float_or_none,
    int_or_none,
    parse_codecs,
    traverse_obj,
    urljoin,
)

class StreamCZIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?:stream|televizeseznam)\.cz/[^?#]+/(?P<display_id>[^?#]+)-(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.televizeseznam.cz/video/lajna/buh-57953890',
        'md5': '40c41ade1464a390a0b447e333df4239',
        'info_dict': {
            'id': '57953890',
            'ext': 'mp4',
            'title': 'Bůh',
            'display_id': 'buh',
            'description': 'md5:8f5f09b9b7bc67df910486cdd88f7165',
            'duration': 1369.6,
            'view_count': int,
        }
    }, {
        'url': 'https://www.stream.cz/kdo-to-mluvi/kdo-to-mluvi-velke-odhaleni-prinasi-novy-porad-uz-od-25-srpna-64087937',
        'md5': '41fd358000086a1ccdb068c77809b158',
        'info_dict': {
            'id': '64087937',
            'ext': 'mp4',
            'title': 'Kdo to mluví? Velké odhalení přináší nový pořad už od 25. srpna',
            'display_id': 'kdo-to-mluvi-velke-odhaleni-prinasi-novy-porad-uz-od-25-srpna',
            'description': 'md5:97a811000a6460266029d6c1c2ebcd59',
            'duration': 50.2,
            'view_count': int,
        }
    }, {
        'url': 'https://www.stream.cz/tajemno/znicehonic-jim-skrz-strechu-prolitnul-zahadny-predmet-badatele-vse-objasnili-64147267',
        'md5': '3ee4d0be040e8f4a543e67e509d55e3f',
        'info_dict': {
            'id': '64147267',
            'ext': 'mp4',
            'title': 'Zničehonic jim skrz střechu prolítnul záhadný předmět. Badatelé vše objasnili',
            'display_id': 'znicehonic-jim-skrz-strechu-prolitnul-zahadny-predmet-badatele-vse-objasnili',
            'description': 'md5:4b8ada6718d34bb011c4e04ca4bc19bf',
            'duration': 442.84,
            'view_count': int,
        }
    }]

    def _extract_formats(self, spl_url, video):
        for ext, pref, streams in (
                ('ts', -1, traverse_obj(video, ('http_stream', 'qualities')) or {}),
                ('mp4', 1, video.get('mp4') or {})):
            for format_id, stream in streams.items():
                if not stream.get('url'):
                    continue
                yield {
                    'format_id': f'{format_id}-{ext}',
                    'ext': ext,
                    'source_preference': pref,
                    'url': urljoin(spl_url, stream['url']),
                    'tbr': float_or_none(stream.get('bandwidth'), scale=1000),
                    'duration': float_or_none(stream.get('duration'), scale=1000),
                    'width': traverse_obj(stream, ('resolution', 0)),
                    'height': traverse_obj(stream, ('resolution', 1)) or int_or_none(format_id.replace('p', '')),
                    **parse_codecs(stream.get('codec')),
                }

    def _real_extract(self, url):
        display_id, video_id = self._match_valid_url(url).groups()

        data = self._download_json(
            'https://www.televizeseznam.cz/api/graphql', video_id, 'Downloading GraphQL result',
            data=json.dumps({
                'variables': {'urlName': video_id},
                'query': '''
                    query LoadEpisode($urlName : String){ episode(urlName: $urlName){ ...VideoDetailFragmentOnEpisode } }
                    fragment VideoDetailFragmentOnEpisode on Episode {
                        id
                        spl
                        urlName
                        name
                        perex
                        duration
                        views
                    }'''
            }).encode('utf-8'),
            headers={'Content-Type': 'application/json;charset=UTF-8'}
        )['data']['episode']

        spl_url = data['spl'] + 'spl2,3'
        metadata = self._download_json(spl_url, video_id, 'Downloading playlist')
        if 'Location' in metadata and 'data' not in metadata:
            spl_url = metadata['Location']
            metadata = self._download_json(spl_url, video_id, 'Downloading redirected playlist')
        video = metadata['data']

        subtitles = {}
        for subs in video.get('subtitles', {}).values():
            if not subs.get('language'):
                continue
            for ext, sub_url in subs.get('urls').items():
                subtitles.setdefault(subs['language'], []).append({
                    'ext': ext,
                    'url': urljoin(spl_url, sub_url)
                })

        formats = list(self._extract_formats(spl_url, video))
        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': data.get('name'),
            'description': data.get('perex'),
            'duration': float_or_none(data.get('duration')),
            'view_count': int_or_none(data.get('views')),
            'formats': formats,
            'subtitles': subtitles,
        }


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

class StreamFFIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?streamff\.com/v/(?P<id>[a-zA-Z0-9]+)'

    _TESTS = [{
        'url': 'https://streamff.com/v/55cc94',
        'md5': '8745a67bb5e5c570738efe7983826370',
        'info_dict': {
            'id': '55cc94',
            'ext': 'mp4',
            'title': '55cc94',
            'timestamp': 1634764643,
            'upload_date': '20211020',
            'view_count': int,
        }
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        json_data = self._download_json(f'https://streamff.com/api/videos/{video_id}', video_id)
        return {
            'id': video_id,
            'title': json_data.get('name') or video_id,
            'url': 'https://streamff.com/%s' % json_data['videoLink'],
            'view_count': int_or_none(json_data.get('views')),
            'timestamp': parse_iso8601(json_data.get('date')),
        }


code:
from .common import InfoExtractor
from ..utils import (
    int_or_none,
    parse_iso8601,
    str_or_none,
    strip_or_none,
    try_get,
    urljoin,
)

class StreetVoiceIE(InfoExtractor):
    _VALID_URL = r'https?://(?:.+?\.)?streetvoice\.com/[^/]+/songs/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://streetvoice.com/skippylu/songs/123688/',
        'md5': '0eb535970629a5195685355f3ed60bfd',
        'info_dict': {
            'id': '123688',
            'ext': 'mp3',
            'title': '流浪',
            'description': 'md5:8eb0bfcc9dcd8aa82bd6efca66e3fea6',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 270,
            'upload_date': '20100923',
            'uploader': 'Crispy脆樂團',
            'uploader_id': '627810',
            'uploader_url': 're:^https?://streetvoice.com/skippylu/',
            'timestamp': 1285261661,
            'view_count': int,
            'like_count': int,
            'comment_count': int,
            'repost_count': int,
            'track': '流浪',
            'track_id': '123688',
            'album': '2010',
        }
    }, {
        'url': 'http://tw.streetvoice.com/skippylu/songs/94440/',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        song_id = self._match_id(url)
        base_url = 'https://streetvoice.com/api/v4/song/%s/' % song_id
        song = self._download_json(base_url, song_id, query={
            'fields': 'album,comments_count,created_at,id,image,length,likes_count,name,nickname,plays_count,profile,share_count,synopsis,user,username',
        })
        title = song['name']

        formats = []
        for suffix, format_id in [('hls/file', 'hls'), ('file', 'http'), ('file/original', 'original')]:
            f_url = (self._download_json(
                base_url + suffix + '/', song_id,
                'Downloading %s format URL' % format_id,
                data=b'', fatal=False) or {}).get('file')
            if not f_url:
                continue
            f = {
                'ext': 'mp3',
                'format_id': format_id,
                'url': f_url,
                'vcodec': 'none',
            }
            if format_id == 'hls':
                f['protocol'] = 'm3u8_native'
            abr = self._search_regex(r'\.mp3\.(\d+)k', f_url, 'bitrate', default=None)
            if abr:
                abr = int(abr)
                f.update({
                    'abr': abr,
                    'tbr': abr,
                })
            formats.append(f)

        user = song.get('user') or {}
        username = user.get('username')
        get_count = lambda x: int_or_none(song.get(x + '_count'))

        return {
            'id': song_id,
            'formats': formats,
            'title': title,
            'description': strip_or_none(song.get('synopsis')),
            'thumbnail': song.get('image'),
            'duration': int_or_none(song.get('length')),
            'timestamp': parse_iso8601(song.get('created_at')),
            'uploader': try_get(user, lambda x: x['profile']['nickname']),
            'uploader_id': str_or_none(user.get('id')),
            'uploader_url': urljoin(url, '/%s/' % username) if username else None,
            'view_count': get_count('plays'),
            'like_count': get_count('likes'),
            'comment_count': get_count('comments'),
            'repost_count': get_count('share'),
            'track': title,
            'track_id': song_id,
            'album': try_get(song, lambda x: x['album']['name']),
        }


code:
from .common import InfoExtractor

class StretchInternetIE(InfoExtractor):
    _VALID_URL = r'https?://portal\.stretchinternet\.com/[^/]+/(?:portal|full)\.htm\?.*?\beventId=(?P<id>\d+)'
    _TEST = {
        'url': 'https://portal.stretchinternet.com/umary/portal.htm?eventId=573272&streamType=video',
        'info_dict': {
            'id': '573272',
            'ext': 'mp4',
            'title': 'UNIVERSITY OF MARY WRESTLING VS UPPER IOWA',
            # 'timestamp': 1575668361,
            # 'upload_date': '20191206',
            'uploader_id': '99997',
        }
    }

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

        media_url = self._download_json(
            'https://core.stretchlive.com/trinity/event/tcg/' + video_id,
            video_id)[0]['media'][0]['url']
        event = self._download_json(
            'https://neo-client.stretchinternet.com/portal-ws/getEvent.json',
            video_id, query={'eventID': video_id, 'token': 'asdf'})['event']

        return {
            'id': video_id,
            'title': event['title'],
            # TODO: parse US timezone abbreviations
            # 'timestamp': event.get('dateTimeString'),
            'url': 'https://' + media_url,
            'uploader_id': event.get('ownerID'),
        }


code:
from .common import InfoExtractor
from ..compat import (
    compat_str,
)
from ..utils import (
    ExtractorError,
    lowercase_escape,
    try_get,
)

class StripchatIE(InfoExtractor):
    _VALID_URL = r'https?://stripchat\.com/(?P<id>[^/?#]+)'
    _TESTS = [{
        'url': 'https://stripchat.com/feel_me',
        'info_dict': {
            'id': 'feel_me',
            'ext': 'mp4',
            'title': 're:^feel_me [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'description': str,
            'is_live': True,
            'age_limit': 18,
        },
        'skip': 'Room is offline',
    }, {
        'url': 'https://stripchat.com/Rakhijaan@xh',
        'only_matching': True
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id, headers=self.geo_verification_headers())

        data = self._parse_json(
            self._search_regex(
                r'<script\b[^>]*>\s*window\.__PRELOADED_STATE__\s*=(?P<value>.*?)<\/script>',
                webpage, 'data', default='{}', group='value'),
            video_id, transform_source=lowercase_escape, fatal=False)
        if not data:
            raise ExtractorError('Unable to find configuration for stream.')

        if try_get(data, lambda x: x['viewCam']['show'], dict):
            raise ExtractorError('Model is in private show', expected=True)
        elif not try_get(data, lambda x: x['viewCam']['model']['isLive'], bool):
            raise ExtractorError('Model is offline', expected=True)

        server = try_get(data, lambda x: x['viewCam']['viewServers']['flashphoner-hls'], compat_str)
        host = try_get(data, lambda x: x['config']['data']['hlsStreamHost'], compat_str)
        model_id = try_get(data, lambda x: x['viewCam']['model']['id'], int)

        formats = self._extract_m3u8_formats(
            'https://b-%s.%s/hls/%d/%d.m3u8' % (server, host, model_id, model_id),
            video_id, ext='mp4', m3u8_id='hls', fatal=False, live=True)
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video_id,
            'description': self._og_search_description(webpage),
            'is_live': True,
            'formats': formats,
            # Stripchat declares the RTA meta-tag, but in an non-standard format so _rta_search() can't be used
            'age_limit': 18,
        }


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    float_or_none,
    int_or_none,
    smuggle_url,
    str_or_none,
    try_get,
)

class STVPlayerIE(InfoExtractor):
    IE_NAME = 'stv:player'
    _VALID_URL = r'https?://player\.stv\.tv/(?P<type>episode|video)/(?P<id>[a-z0-9]{4})'
    _TESTS = [{
        # shortform
        'url': 'https://player.stv.tv/video/4gwd/emmerdale/60-seconds-on-set-with-laura-norton/',
        'md5': '5adf9439c31d554f8be0707c7abe7e0a',
        'info_dict': {
            'id': '5333973339001',
            'ext': 'mp4',
            'upload_date': '20170301',
            'title': '60 seconds on set with Laura Norton',
            'description': "How many questions can Laura - a.k.a Kerry Wyatt - answer in 60 seconds? Let\'s find out!",
            'timestamp': 1488388054,
            'uploader_id': '1486976045',
        },
        'skip': 'this resource is unavailable outside of the UK',
    }, {
        # episodes
        'url': 'https://player.stv.tv/episode/4125/jennifer-saunders-memory-lane',
        'only_matching': True,
    }]
    BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/1486976045/default_default/index.html?videoId=%s'
    _PTYPE_MAP = {
        'episode': 'episodes',
        'video': 'shortform',
    }

    def _real_extract(self, url):
        ptype, video_id = self._match_valid_url(url).groups()

        webpage = self._download_webpage(url, video_id, fatal=False) or ''
        props = self._search_nextjs_data(webpage, video_id, default='{}').get('props') or {}
        player_api_cache = try_get(
            props, lambda x: x['initialReduxState']['playerApiCache']) or {}

        api_path, resp = None, {}
        for k, v in player_api_cache.items():
            if k.startswith('/episodes/') or k.startswith('/shortform/'):
                api_path, resp = k, v
                break
        else:
            episode_id = str_or_none(try_get(
                props, lambda x: x['pageProps']['episodeId']))
            api_path = '/%s/%s' % (self._PTYPE_MAP[ptype], episode_id or video_id)

        result = resp.get('results')
        if not result:
            resp = self._download_json(
                'https://player.api.stv.tv/v1' + api_path, video_id)
            result = resp['results']

        video = result['video']
        video_id = compat_str(video['id'])

        subtitles = {}
        _subtitles = result.get('_subtitles') or {}
        for ext, sub_url in _subtitles.items():
            subtitles.setdefault('en', []).append({
                'ext': 'vtt' if ext == 'webvtt' else ext,
                'url': sub_url,
            })

        programme = result.get('programme') or {}

        return {
            '_type': 'url_transparent',
            'id': video_id,
            'url': smuggle_url(self.BRIGHTCOVE_URL_TEMPLATE % video_id, {'geo_countries': ['GB']}),
            'description': result.get('summary'),
            'duration': float_or_none(video.get('length'), 1000),
            'subtitles': subtitles,
            'view_count': int_or_none(result.get('views')),
            'series': programme.get('name') or programme.get('shortName'),
            'ie_key': 'BrightcoveNew',
        }


code:
import re
import urllib.parse

from .common import InfoExtractor
from ..utils import str_or_none, traverse_obj

class SubstackIE(InfoExtractor):
    _VALID_URL = r'https?://(?P<username>[\w-]+)\.substack\.com/p/(?P<id>[\w-]+)'
    _TESTS = [{
        'url': 'https://haleynahman.substack.com/p/i-made-a-vlog?s=r',
        'md5': 'f27e4fc6252001d48d479f45e65cdfd5',
        'info_dict': {
            'id': '47660949',
            'ext': 'mp4',
            'title': 'I MADE A VLOG',
            'description': 'md5:10c01ff93439a62e70ce963b2aa0b7f6',
            'thumbnail': 'md5:bec758a34d8ee9142d43bcebdf33af18',
            'uploader': 'Maybe Baby',
            'uploader_id': '33628',
        }
    }, {
        'url': 'https://haleynahman.substack.com/p/-dear-danny-i-found-my-boyfriends?s=r',
        'md5': '0a63eacec877a1171a62cfa69710fcea',
        'info_dict': {
            'id': '51045592',
            'ext': 'mpga',
            'title': "🎧 Dear Danny: I found my boyfriend's secret Twitter account",
            'description': 'md5:a57f2439319e56e0af92dd0c95d75797',
            'thumbnail': 'md5:daa40b6b79249417c14ff8103db29639',
            'uploader': 'Maybe Baby',
            'uploader_id': '33628',
        }
    }, {
        'url': 'https://andrewzimmern.substack.com/p/mussels-with-black-bean-sauce-recipe',
        'md5': 'fd3c07077b02444ff0130715b5f632bb',
        'info_dict': {
            'id': '47368578',
            'ext': 'mp4',
            'title': 'Mussels with Black Bean Sauce: Recipe of the Week #7',
            'description': 'md5:b96234a2906c7d854d5229818d889515',
            'thumbnail': 'md5:e30bfaa9da40e82aa62354263a9dd232',
            'uploader': "Andrew Zimmern's Spilled Milk ",
            'uploader_id': '577659',
        }
    }]

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        if not re.search(r'<script[^>]+src=["\']https://substackcdn.com/[^"\']+\.js', webpage):
            return

        mobj = re.search(r'{[^}]*["\']subdomain["\']\s*:\s*["\'](?P<subdomain>[^"]+)', webpage)
        if mobj:
            parsed = urllib.parse.urlparse(url)
            yield parsed._replace(netloc=f'{mobj.group("subdomain")}.substack.com').geturl()
            raise cls.StopExtraction()

    def _extract_video_formats(self, video_id, username):
        formats, subtitles = [], {}
        for video_format in ('hls', 'mp4'):
            video_url = f'https://{username}.substack.com/api/v1/video/upload/{video_id}/src?type={video_format}'

            if video_format == 'hls':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(video_url, video_id, 'mp4', fatal=False)
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)
            else:
                formats.append({
                    'url': video_url,
                    'ext': video_format,
                })

        return formats, subtitles

    def _real_extract(self, url):
        display_id, username = self._match_valid_url(url).group('id', 'username')
        webpage = self._download_webpage(url, display_id)

        webpage_info = self._search_json(r'<script[^>]*>\s*window\._preloads\s*=', webpage, 'preloads', display_id)

        post_type = webpage_info['post']['type']
        formats, subtitles = [], {}
        if post_type == 'podcast':
            formats, subtitles = [{'url': webpage_info['post']['podcast_url']}], {}
        elif post_type == 'video':
            formats, subtitles = self._extract_video_formats(webpage_info['post']['videoUpload']['id'], username)
        else:
            self.raise_no_formats(f'Page type "{post_type}" is not supported')

        self._sort_formats(formats)
        return {
            'id': str(webpage_info['post']['id']),
            'formats': formats,
            'subtitles': subtitles,
            'title': traverse_obj(webpage_info, ('post', 'title')),
            'description': traverse_obj(webpage_info, ('post', 'description')),
            'thumbnail': traverse_obj(webpage_info, ('post', 'cover_image')),
            'uploader': traverse_obj(webpage_info, ('pub', 'name')),
            'uploader_id': str_or_none(traverse_obj(webpage_info, ('post', 'publication_id'))),
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    parse_duration,
    int_or_none,
    qualities,
    determine_ext,
)

class SunPornoIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:www\.)?sunporno\.com/videos|embeds\.sunporno\.com/embed)/(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://www.sunporno.com/videos/807778/',
        'md5': '507887e29033502f29dba69affeebfc9',
        'info_dict': {
            'id': '807778',
            'ext': 'mp4',
            'title': 'md5:0a400058e8105d39e35c35e7c5184164',
            'description': 'md5:a31241990e1bd3a64e72ae99afb325fb',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 302,
            'age_limit': 18,
        }
    }, {
        'url': 'http://embeds.sunporno.com/embed/807778',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            'http://www.sunporno.com/videos/%s' % video_id, video_id)

        title = self._html_extract_title(webpage)
        description = self._html_search_meta(
            'description', webpage, 'description')
        thumbnail = self._html_search_regex(
            r'poster="([^"]+)"', webpage, 'thumbnail', fatal=False)

        duration = parse_duration(self._search_regex(
            (r'itemprop="duration"[^>]*>\s*(\d+:\d+)\s*<',
             r'>Duration:\s*<span[^>]+>\s*(\d+:\d+)\s*<'),
            webpage, 'duration', fatal=False))

        view_count = int_or_none(self._html_search_regex(
            r'class="views">(?:<noscript>)?\s*(\d+)\s*<',
            webpage, 'view count', fatal=False))
        comment_count = int_or_none(self._html_search_regex(
            r'(\d+)</b> Comments?',
            webpage, 'comment count', fatal=False, default=None))

        formats = []
        quality = qualities(['mp4', 'flv'])
        for video_url in re.findall(r'<(?:source|video) src="([^"]+)"', webpage):
            video_ext = determine_ext(video_url)
            formats.append({
                'url': video_url,
                'format_id': video_ext,
                'quality': quality(video_ext),
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'duration': duration,
            'view_count': view_count,
            'comment_count': comment_count,
            'formats': formats,
            'age_limit': 18,
        }


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

class SverigesRadioBaseIE(InfoExtractor):
    _BASE_URL = 'https://sverigesradio.se/sida/playerajax/'
    _QUALITIES = ['low', 'medium', 'high']
    _EXT_TO_CODEC_MAP = {
        'mp3': 'mp3',
        'm4a': 'aac',
    }
    _CODING_FORMAT_TO_ABR_MAP = {
        5: 128,
        11: 192,
        12: 32,
        13: 96,
    }

    def _real_extract(self, url):
        audio_id = self._match_id(url)
        query = {
            'id': audio_id,
            'type': self._AUDIO_TYPE,
        }

        item = self._download_json(
            self._BASE_URL + 'audiometadata', audio_id,
            'Downloading audio JSON metadata', query=query)['items'][0]
        title = item['subtitle']

        query['format'] = 'iis'
        urls = []
        formats = []
        for quality in self._QUALITIES:
            query['quality'] = quality
            audio_url_data = self._download_json(
                self._BASE_URL + 'getaudiourl', audio_id,
                'Downloading %s format JSON metadata' % quality,
                fatal=False, query=query) or {}
            audio_url = audio_url_data.get('audioUrl')
            if not audio_url or audio_url in urls:
                continue
            urls.append(audio_url)
            ext = determine_ext(audio_url)
            coding_format = audio_url_data.get('codingFormat')
            abr = int_or_none(self._search_regex(
                r'_a(\d+)\.m4a', audio_url, 'audio bitrate',
                default=None)) or self._CODING_FORMAT_TO_ABR_MAP.get(coding_format)
            formats.append({
                'abr': abr,
                'acodec': self._EXT_TO_CODEC_MAP.get(ext),
                'ext': ext,
                'format_id': str_or_none(coding_format),
                'vcodec': 'none',
                'url': audio_url,
            })
        self._sort_formats(formats)

        return {
            'id': audio_id,
            'title': title,
            'formats': formats,
            'series': item.get('title'),
            'duration': int_or_none(item.get('duration')),
            'thumbnail': item.get('displayimageurl'),
            'description': item.get('description'),
        }

class SverigesRadioPublicationIE(SverigesRadioBaseIE):
    IE_NAME = 'sverigesradio:publication'
    _VALID_URL = r'https?://(?:www\.)?sverigesradio\.se/sida/(?:artikel|gruppsida)\.aspx\?.*?\bartikel=(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://sverigesradio.se/sida/artikel.aspx?programid=83&artikel=7038546',
        'md5': '6a4917e1923fccb080e5a206a5afa542',
        'info_dict': {
            'id': '7038546',
            'ext': 'm4a',
            'duration': 132,
            'series': 'Nyheter (Ekot)',
            'title': 'Esa Teittinen: Sanningen har inte kommit fram',
            'description': 'md5:daf7ce66a8f0a53d5465a5984d3839df',
            'thumbnail': r're:^https?://.*\.jpg',
        },
    }, {
        'url': 'https://sverigesradio.se/sida/gruppsida.aspx?programid=3304&grupp=6247&artikel=7146887',
        'only_matching': True,
    }]
    _AUDIO_TYPE = 'publication'

class SverigesRadioEpisodeIE(SverigesRadioBaseIE):
    IE_NAME = 'sverigesradio:episode'
    _VALID_URL = r'https?://(?:www\.)?sverigesradio\.se/(?:sida/)?avsnitt/(?P<id>[0-9]+)'
    _TEST = {
        'url': 'https://sverigesradio.se/avsnitt/1140922?programid=1300',
        'md5': '20dc4d8db24228f846be390b0c59a07c',
        'info_dict': {
            'id': '1140922',
            'ext': 'mp3',
            'duration': 3307,
            'series': 'Konflikt',
            'title': 'Metoo och valen',
            'description': 'md5:fcb5c1f667f00badcc702b196f10a27e',
            'thumbnail': r're:^https?://.*\.jpg',
        }
    }
    _AUDIO_TYPE = 'episode'


code:
import re

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    determine_ext,
    dict_get,
    int_or_none,
    unified_timestamp,
    str_or_none,
    strip_or_none,
    try_get,
)

class SVTBaseIE(InfoExtractor):
    _GEO_COUNTRIES = ['SE']

    def _extract_video(self, video_info, video_id):
        is_live = dict_get(video_info, ('live', 'simulcast'), default=False)
        m3u8_protocol = 'm3u8' if is_live else 'm3u8_native'
        formats = []
        subtitles = {}
        for vr in video_info['videoReferences']:
            player_type = vr.get('playerType') or vr.get('format')
            vurl = vr['url']
            ext = determine_ext(vurl)
            if ext == 'm3u8':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(
                    vurl, video_id,
                    ext='mp4', entry_protocol=m3u8_protocol,
                    m3u8_id=player_type, fatal=False)
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)
            elif ext == 'f4m':
                formats.extend(self._extract_f4m_formats(
                    vurl + '?hdcore=3.3.0', video_id,
                    f4m_id=player_type, fatal=False))
            elif ext == 'mpd':
                fmts, subs = self._extract_mpd_formats_and_subtitles(
                    vurl, video_id, mpd_id=player_type, fatal=False)
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)
            else:
                formats.append({
                    'format_id': player_type,
                    'url': vurl,
                })
        rights = try_get(video_info, lambda x: x['rights'], dict) or {}
        if not formats and rights.get('geoBlockedSweden'):
            self.raise_geo_restricted(
                'This video is only available in Sweden',
                countries=self._GEO_COUNTRIES, metadata_available=True)
        self._sort_formats(formats)

        subtitle_references = dict_get(video_info, ('subtitles', 'subtitleReferences'))
        if isinstance(subtitle_references, list):
            for sr in subtitle_references:
                subtitle_url = sr.get('url')
                subtitle_lang = sr.get('language', 'sv')
                if subtitle_url:
                    sub = {
                        'url': subtitle_url,
                    }
                    if determine_ext(subtitle_url) == 'm3u8':
                        # XXX: no way of testing, is it ever hit?
                        sub['ext'] = 'vtt'
                    subtitles.setdefault(subtitle_lang, []).append(sub)

        title = video_info.get('title')

        series = video_info.get('programTitle')
        season_number = int_or_none(video_info.get('season'))
        episode = video_info.get('episodeTitle')
        episode_number = int_or_none(video_info.get('episodeNumber'))

        timestamp = unified_timestamp(rights.get('validFrom'))
        duration = int_or_none(dict_get(video_info, ('materialLength', 'contentDuration')))
        age_limit = None
        adult = dict_get(
            video_info, ('inappropriateForChildren', 'blockedForChildren'),
            skip_false_values=False)
        if adult is not None:
            age_limit = 18 if adult else 0

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'subtitles': subtitles,
            'duration': duration,
            'timestamp': timestamp,
            'age_limit': age_limit,
            'series': series,
            'season_number': season_number,
            'episode': episode,
            'episode_number': episode_number,
            'is_live': is_live,
        }

class SVTIE(SVTBaseIE):
    _VALID_URL = r'https?://(?:www\.)?svt\.se/wd\?(?:.*?&)?widgetId=(?P<widget_id>\d+)&.*?\barticleId=(?P<id>\d+)'
    _EMBED_REGEX = [r'(?:<iframe src|href)="(?P<url>%s[^"]*)"' % _VALID_URL]
    _TEST = {
        'url': 'http://www.svt.se/wd?widgetId=23991&sectionId=541&articleId=2900353&type=embed&contextSectionId=123&autostart=false',
        'md5': '33e9a5d8f646523ce0868ecfb0eed77d',
        'info_dict': {
            'id': '2900353',
            'ext': 'mp4',
            'title': 'Stjärnorna skojar till det - under SVT-intervjun',
            'duration': 27,
            'age_limit': 0,
        },
    }

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        widget_id = mobj.group('widget_id')
        article_id = mobj.group('id')

        info = self._download_json(
            'http://www.svt.se/wd?widgetId=%s&articleId=%s&format=json&type=embed&output=json' % (widget_id, article_id),
            article_id)

        info_dict = self._extract_video(info['video'], article_id)
        info_dict['title'] = info['context']['title']
        return info_dict

class SVTPlayBaseIE(SVTBaseIE):
    _SVTPLAY_RE = r'root\s*\[\s*(["\'])_*svtplay\1\s*\]\s*=\s*(?P<json>{.+?})\s*;\s*\n'

class SVTPlayIE(SVTPlayBaseIE):
    IE_DESC = 'SVT Play and Öppet arkiv'
    _VALID_URL = r'''(?x)
                    (?:
                        (?:
                            svt:|
                            https?://(?:www\.)?svt\.se/barnkanalen/barnplay/[^/]+/
                        )
                        (?P<svt_id>[^/?#&]+)|
                        https?://(?:www\.)?(?:svtplay|oppetarkiv)\.se/(?:video|klipp|kanaler)/(?P<id>[^/?#&]+)
                        (?:.*?(?:modalId|id)=(?P<modal_id>[\da-zA-Z-]+))?
                    )
                    '''
    _TESTS = [{
        'url': 'https://www.svtplay.se/video/30479064',
        'md5': '2382036fd6f8c994856c323fe51c426e',
        'info_dict': {
            'id': '8zVbDPA',
            'ext': 'mp4',
            'title': 'Designdrömmar i Stenungsund',
            'timestamp': 1615770000,
            'upload_date': '20210315',
            'duration': 3519,
            'thumbnail': r're:^https?://(?:.*[\.-]jpg|www.svtstatic.se/image/.*)$',
            'age_limit': 0,
            'subtitles': {
                'sv': [{
                    'ext': 'vtt',
                }]
            },
        },
        'params': {
            # skip for now due to download test asserts that segment is > 10000 bytes and svt uses
            # init segments that are smaller
            # AssertionError: Expected test_SVTPlay_jNwpV9P.mp4 to be at least 9.77KiB, but it's only 864.00B
            'skip_download': True,
        },
    }, {
        'url': 'https://www.svtplay.se/video/30479064/husdrommar/husdrommar-sasong-8-designdrommar-i-stenungsund?modalId=8zVbDPA',
        'only_matching': True,
    }, {
        'url': 'https://www.svtplay.se/video/30684086/rapport/rapport-24-apr-18-00-7?id=e72gVpa',
        'only_matching': True,
    }, {
        # geo restricted to Sweden
        'url': 'http://www.oppetarkiv.se/video/5219710/trollflojten',
        'only_matching': True,
    }, {
        'url': 'http://www.svtplay.se/klipp/9023742/stopptid-om-bjorn-borg',
        'only_matching': True,
    }, {
        'url': 'https://www.svtplay.se/kanaler/svt1',
        'only_matching': True,
    }, {
        'url': 'svt:1376446-003A',
        'only_matching': True,
    }, {
        'url': 'svt:14278044',
        'only_matching': True,
    }, {
        'url': 'https://www.svt.se/barnkanalen/barnplay/kar/eWv5MLX/',
        'only_matching': True,
    }, {
        'url': 'svt:eWv5MLX',
        'only_matching': True,
    }]

    def _extract_by_video_id(self, video_id, webpage=None):
        data = self._download_json(
            'https://api.svt.se/videoplayer-api/video/%s' % video_id,
            video_id, headers=self.geo_verification_headers())
        info_dict = self._extract_video(data, video_id)
        if not info_dict.get('title'):
            title = dict_get(info_dict, ('episode', 'series'))
            if not title and webpage:
                title = re.sub(
                    r'\s*\|\s*.+?$', '', self._og_search_title(webpage))
            if not title:
                title = video_id
            info_dict['title'] = title
        return info_dict

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        video_id = mobj.group('id')
        svt_id = mobj.group('svt_id') or mobj.group('modal_id')

        if svt_id:
            return self._extract_by_video_id(svt_id)

        webpage = self._download_webpage(url, video_id)

        data = self._parse_json(
            self._search_regex(
                self._SVTPLAY_RE, webpage, 'embedded data', default='{}',
                group='json'),
            video_id, fatal=False)

        thumbnail = self._og_search_thumbnail(webpage)

        if data:
            video_info = try_get(
                data, lambda x: x['context']['dispatcher']['stores']['VideoTitlePageStore']['data']['video'],
                dict)
            if video_info:
                info_dict = self._extract_video(video_info, video_id)
                info_dict.update({
                    'title': data['context']['dispatcher']['stores']['MetaStore']['title'],
                    'thumbnail': thumbnail,
                })
                return info_dict

            svt_id = try_get(
                data, lambda x: x['statistics']['dataLake']['content']['id'],
                compat_str)

        if not svt_id:
            svt_id = self._search_regex(
                (r'<video[^>]+data-video-id=["\']([\da-zA-Z-]+)',
                 r'<[^>]+\bdata-rt=["\']top-area-play-button["\'][^>]+\bhref=["\'][^"\']*video/%s/[^"\']*\b(?:modalId|id)=([\da-zA-Z-]+)' % re.escape(video_id),
                 r'["\']videoSvtId["\']\s*:\s*["\']([\da-zA-Z-]+)',
                 r'["\']videoSvtId\\?["\']\s*:\s*\\?["\']([\da-zA-Z-]+)',
                 r'"content"\s*:\s*{.*?"id"\s*:\s*"([\da-zA-Z-]+)"',
                 r'["\']svtId["\']\s*:\s*["\']([\da-zA-Z-]+)',
                 r'["\']svtId\\?["\']\s*:\s*\\?["\']([\da-zA-Z-]+)'),
                webpage, 'video id')

        info_dict = self._extract_by_video_id(svt_id, webpage)
        info_dict['thumbnail'] = thumbnail

        return info_dict

class SVTSeriesIE(SVTPlayBaseIE):
    _VALID_URL = r'https?://(?:www\.)?svtplay\.se/(?P<id>[^/?&#]+)(?:.+?\btab=(?P<season_slug>[^&#]+))?'
    _TESTS = [{
        'url': 'https://www.svtplay.se/rederiet',
        'info_dict': {
            'id': '14445680',
            'title': 'Rederiet',
            'description': 'md5:d9fdfff17f5d8f73468176ecd2836039',
        },
        'playlist_mincount': 318,
    }, {
        'url': 'https://www.svtplay.se/rederiet?tab=season-2-14445680',
        'info_dict': {
            'id': 'season-2-14445680',
            'title': 'Rederiet - Säsong 2',
            'description': 'md5:d9fdfff17f5d8f73468176ecd2836039',
        },
        'playlist_mincount': 12,
    }]

    @classmethod
    def suitable(cls, url):
        return False if SVTIE.suitable(url) or SVTPlayIE.suitable(url) else super(SVTSeriesIE, cls).suitable(url)

    def _real_extract(self, url):
        series_slug, season_id = self._match_valid_url(url).groups()

        series = self._download_json(
            'https://api.svt.se/contento/graphql', series_slug,
            'Downloading series page', query={
                'query': '''{
  listablesBySlug(slugs: ["%s"]) {
    associatedContent(include: [productionPeriod, season]) {
      items {
        item {
          ... on Episode {
            videoSvtId
          }
        }
      }
      id
      name
    }
    id
    longDescription
    name
    shortDescription
  }
}''' % series_slug,
            })['data']['listablesBySlug'][0]

        season_name = None

        entries = []
        for season in series['associatedContent']:
            if not isinstance(season, dict):
                continue
            if season_id:
                if season.get('id') != season_id:
                    continue
                season_name = season.get('name')
            items = season.get('items')
            if not isinstance(items, list):
                continue
            for item in items:
                video = item.get('item') or {}
                content_id = video.get('videoSvtId')
                if not content_id or not isinstance(content_id, compat_str):
                    continue
                entries.append(self.url_result(
                    'svt:' + content_id, SVTPlayIE.ie_key(), content_id))

        title = series.get('name')
        season_name = season_name or season_id

        if title and season_name:
            title = '%s - %s' % (title, season_name)
        elif season_id:
            title = season_id

        return self.playlist_result(
            entries, season_id or series.get('id'), title,
            dict_get(series, ('longDescription', 'shortDescription')))

class SVTPageIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?svt\.se/(?P<path>(?:[^/]+/)*(?P<id>[^/?&#]+))'
    _TESTS = [{
        'url': 'https://www.svt.se/sport/ishockey/bakom-masken-lehners-kamp-mot-mental-ohalsa',
        'info_dict': {
            'id': '25298267',
            'title': 'Bakom masken – Lehners kamp mot mental ohälsa',
        },
        'playlist_count': 4,
    }, {
        'url': 'https://www.svt.se/nyheter/utrikes/svenska-andrea-ar-en-mil-fran-branderna-i-kalifornien',
        'info_dict': {
            'id': '24243746',
            'title': 'Svenska Andrea redo att fly sitt hem i Kalifornien',
        },
        'playlist_count': 2,
    }, {
        # only programTitle
        'url': 'http://www.svt.se/sport/ishockey/jagr-tacklar-giroux-under-intervjun',
        'info_dict': {
            'id': '8439V2K',
            'ext': 'mp4',
            'title': 'Stjärnorna skojar till det - under SVT-intervjun',
            'duration': 27,
            'age_limit': 0,
        },
    }, {
        'url': 'https://www.svt.se/nyheter/lokalt/vast/svt-testar-tar-nagon-upp-skrapet-1',
        'only_matching': True,
    }, {
        'url': 'https://www.svt.se/vader/manadskronikor/maj2018',
        'only_matching': True,
    }]

    @classmethod
    def suitable(cls, url):
        return False if SVTIE.suitable(url) or SVTPlayIE.suitable(url) else super(SVTPageIE, cls).suitable(url)

    def _real_extract(self, url):
        path, display_id = self._match_valid_url(url).groups()

        article = self._download_json(
            'https://api.svt.se/nss-api/page/' + path, display_id,
            query={'q': 'articles'})['articles']['content'][0]

        entries = []

        def _process_content(content):
            if content.get('_type') in ('VIDEOCLIP', 'VIDEOEPISODE'):
                video_id = compat_str(content['image']['svtId'])
                entries.append(self.url_result(
                    'svt:' + video_id, SVTPlayIE.ie_key(), video_id))

        for media in article.get('media', []):
            _process_content(media)

        for obj in article.get('structuredBody', []):
            _process_content(obj.get('content') or {})

        return self.playlist_result(
            entries, str_or_none(article.get('id')),
            strip_or_none(article.get('title')))


code:
import re
from .common import InfoExtractor
from ..utils import determine_ext, number_of_digits

class SwipeVideoIE(InfoExtractor):
    # SwipeVideoo can only be embedded
    _VALID_URL = r'swipevideo:(?P<id>[0-9a-zA-Z]+)'
    _TESTS = [{
        'url': 'swipevideo:3027850cf6',
        'info_dict': {
            'id': '3027850cf6',
            'title': 'MUSICAL「Party」',
            'duration': 183.019,
            'fps': 29.996885569257834,
        },
    }]

    @classmethod
    def _extract_urls(cls, webpage):
        if 'https://swipevideo.site/libs/embedcdn.js' not in webpage:
            return []

        # the query selector used by the script is ".sv-embed"
        urls = []
        for i in re.finditer(r'<[a-zA-Z0-9_:-]+?\s+class=(["\']?)sv-embed\1[^>]+?>', webpage):
            mobj = re.search(r'data-cid=(["\']?)(?P<id>[0-9a-zA-Z]+)\1', i.group(0))
            if not mobj:
                continue
            urls.append('swipevideo:' + mobj.group('id'))

        return urls

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

        base_url = f'https://mercury-prod-cdn-cont.azureedge.net/{video_id}'
        info_data = self._download_json(f'{base_url}/info.json', video_id)

        title = info_data.get('title')
        pattern = info_data.get('pattern') or '\\${src}/%d.jpg'
        duration = info_data.get('duration')
        frames = info_data.get('frame')
        approx_framerate = frames / duration

        formats = []
        srcsets = info_data.get('srcset') or []
        secset_digits = number_of_digits(len(srcsets))
        for idx, srcset in enumerate(srcsets):
            resolved_pattern = re.sub(r'\\?\${src}', srcset, pattern)
            images = [dict(
                url=f'{base_url}/{resolved_pattern % (x + 1)}') for x in range(frames)]
            formats.append({
                'format_id': f'%0{secset_digits}d-%s' % (idx, srcset),
                'protocol': 'image_series',
                'vcodec': 'jpg',
                'acodec': 'none',
                # FileDownloader is resposible for converting to this format
                'ext': 'mp4',
                'url': images[0]['url'],
                'fragments': images,
                'duration': duration,
                'frame_count': frames,
                'fps': approx_framerate,
                'format_note': f'Angle {idx + 1}',
                'preference': -idx,
                'fragment_base_url': base_url,
            })

        audio_path = info_data.get('audio')
        if audio_path:
            formats.append({
                'format_id': 'audio',
                'url': f'{base_url}/{audio_path}',
                'vcodec': 'none',
                'acodec': None,
                'ext': determine_ext(audio_path),
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'duration': duration,
            'fps': approx_framerate,
            'formats': formats,
        }


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

class SWRMediathekIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?swrmediathek\.de/(?:content/)?player\.htm\?show=(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'

    _TESTS = [{
        'url': 'http://swrmediathek.de/player.htm?show=849790d0-dab8-11e3-a953-0026b975f2e6',
        'md5': '8c5f6f0172753368547ca8413a7768ac',
        'info_dict': {
            'id': '849790d0-dab8-11e3-a953-0026b975f2e6',
            'ext': 'mp4',
            'title': 'SWR odysso',
            'description': 'md5:2012e31baad36162e97ce9eb3f157b8a',
            'thumbnail': r're:^http:.*\.jpg$',
            'duration': 2602,
            'upload_date': '20140515',
            'uploader': 'SWR Fernsehen',
            'uploader_id': '990030',
        },
    }, {
        'url': 'http://swrmediathek.de/player.htm?show=0e1a8510-ddf2-11e3-9be3-0026b975f2e6',
        'md5': 'b10ab854f912eecc5a6b55cd6fc1f545',
        'info_dict': {
            'id': '0e1a8510-ddf2-11e3-9be3-0026b975f2e6',
            'ext': 'mp4',
            'title': 'Nachtcafé - Alltagsdroge Alkohol - zwischen Sektempfang und Komasaufen',
            'description': 'md5:e0a3adc17e47db2c23aab9ebc36dbee2',
            'thumbnail': r're:http://.*\.jpg',
            'duration': 5305,
            'upload_date': '20140516',
            'uploader': 'SWR Fernsehen',
            'uploader_id': '990030',
        },
        'skip': 'redirect to http://swrmediathek.de/index.htm?hinweis=swrlink',
    }, {
        'url': 'http://swrmediathek.de/player.htm?show=bba23e10-cb93-11e3-bf7f-0026b975f2e6',
        'md5': '4382e4ef2c9d7ce6852535fa867a0dd3',
        'info_dict': {
            'id': 'bba23e10-cb93-11e3-bf7f-0026b975f2e6',
            'ext': 'mp3',
            'title': 'Saša Stanišic: Vor dem Fest',
            'description': 'md5:5b792387dc3fbb171eb709060654e8c9',
            'thumbnail': r're:http://.*\.jpg',
            'duration': 3366,
            'upload_date': '20140520',
            'uploader': 'SWR 2',
            'uploader_id': '284670',
        },
        'skip': 'redirect to http://swrmediathek.de/index.htm?hinweis=swrlink',
    }]

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

        video = self._download_json(
            'http://swrmediathek.de/AjaxEntry?ekey=%s' % video_id,
            video_id, 'Downloading video JSON')

        attr = video['attr']
        title = attr['entry_title']
        media_type = attr.get('entry_etype')

        formats = []
        for entry in video.get('sub', []):
            if entry.get('name') != 'entry_media':
                continue

            entry_attr = entry.get('attr', {})
            f_url = entry_attr.get('val2')
            if not f_url:
                continue
            codec = entry_attr.get('val0')
            if codec == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    f_url, video_id, 'mp4', 'm3u8_native',
                    m3u8_id='hls', fatal=False))
            elif codec == 'f4m':
                formats.extend(self._extract_f4m_formats(
                    f_url + '?hdcore=3.7.0', video_id,
                    f4m_id='hds', fatal=False))
            else:
                formats.append({
                    'format_id': determine_protocol({'url': f_url}),
                    'url': f_url,
                    'quality': int_or_none(entry_attr.get('val1')),
                    'vcodec': codec if media_type == 'Video' else 'none',
                    'acodec': codec if media_type == 'Audio' else None,
                })
        self._sort_formats(formats)

        upload_date = None
        entry_pdatet = attr.get('entry_pdatet')
        if entry_pdatet:
            upload_date = entry_pdatet[:-4]

        return {
            'id': video_id,
            'title': title,
            'description': attr.get('entry_descl'),
            'thumbnail': attr.get('entry_image_16_9'),
            'duration': parse_duration(attr.get('entry_durat')),
            'upload_date': upload_date,
            'uploader': attr.get('channel_title'),
            'uploader_id': attr.get('channel_idkey'),
            'formats': formats,
        }