code:
import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    int_or_none,
    str_to_int
)

class RUTVIE(InfoExtractor):
    IE_DESC = 'RUTV.RU'
    _VALID_URL = r'''(?x)
                    https?://
                        (?:test)?player\.(?:rutv\.ru|vgtrk\.com)/
                        (?P<path>
                            flash\d+v/container\.swf\?id=|
                            iframe/(?P<type>swf|video|live)/id/|
                            index/iframe/cast_id/
                        )
                        (?P<id>\d+)
                    '''
    _EMBED_URLS = [
        r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:test)?player\.(?:rutv\.ru|vgtrk\.com)/(?:iframe/(?:swf|video|live)/id|index/iframe/cast_id)/.+?)\1',
        r'<meta[^>]+?property=(["\'])og:video\1[^>]+?content=(["\'])(?P<url>https?://(?:test)?player\.(?:rutv\.ru|vgtrk\.com)/flash\d+v/container\.swf\?id=.+?\2)',
    ]

    _TESTS = [
        {
            'url': 'http://player.rutv.ru/flash2v/container.swf?id=774471&sid=kultura&fbv=true&isPlay=true&ssl=false&i=560&acc_video_id=episode_id/972347/video_id/978186/brand_id/31724',
            'info_dict': {
                'id': '774471',
                'ext': 'mp4',
                'title': 'Монологи на все времена',
                'description': 'md5:18d8b5e6a41fb1faa53819471852d5d5',
                'duration': 2906,
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            },
        },
        {
            'url': 'https://player.vgtrk.com/flash2v/container.swf?id=774016&sid=russiatv&fbv=true&isPlay=true&ssl=false&i=560&acc_video_id=episode_id/972098/video_id/977760/brand_id/57638',
            'info_dict': {
                'id': '774016',
                'ext': 'mp4',
                'title': 'Чужой в семье Сталина',
                'description': '',
                'duration': 2539,
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            },
        },
        {
            'url': 'http://player.rutv.ru/iframe/swf/id/766888/sid/hitech/?acc_video_id=4000',
            'info_dict': {
                'id': '766888',
                'ext': 'mp4',
                'title': 'Вести.net: интернет-гиганты начали перетягивание программных "одеял"',
                'description': 'md5:65ddd47f9830c4f42ed6475f8730c995',
                'duration': 279,
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            },
        },
        {
            'url': 'http://player.rutv.ru/iframe/video/id/771852/start_zoom/true/showZoomBtn/false/sid/russiatv/?acc_video_id=episode_id/970443/video_id/975648/brand_id/5169',
            'info_dict': {
                'id': '771852',
                'ext': 'mp4',
                'title': 'Прямой эфир. Жертвы загадочной болезни: смерть от старости в 17 лет',
                'description': 'md5:b81c8c55247a4bd996b43ce17395b2d8',
                'duration': 3096,
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            },
        },
        {
            'url': 'http://player.rutv.ru/iframe/live/id/51499/showZoomBtn/false/isPlay/true/sid/sochi2014',
            'info_dict': {
                'id': '51499',
                'ext': 'flv',
                'title': 'Сочи-2014. Биатлон. Индивидуальная гонка. Мужчины ',
                'description': 'md5:9e0ed5c9d2fa1efbfdfed90c9a6d179c',
            },
            'skip': 'Translation has finished',
        },
        {
            'url': 'http://player.rutv.ru/iframe/live/id/21/showZoomBtn/false/isPlay/true/',
            'info_dict': {
                'id': '21',
                'ext': 'mp4',
                'title': 're:^Россия 24. Прямой эфир [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
                'is_live': True,
            },
            'params': {
                # m3u8 download
                'skip_download': True,
            },
        },
        {
            'url': 'https://testplayer.vgtrk.com/iframe/live/id/19201/showZoomBtn/false/isPlay/true/',
            'only_matching': True,
        },
    ]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        video_id = mobj.group('id')
        video_path = mobj.group('path')

        if re.match(r'flash\d+v', video_path):
            video_type = 'video'
        elif video_path.startswith('iframe'):
            video_type = mobj.group('type')
            if video_type == 'swf':
                video_type = 'video'
        elif video_path.startswith('index/iframe/cast_id'):
            video_type = 'live'

        is_live = video_type == 'live'

        json_data = self._download_json(
            'http://player.vgtrk.com/iframe/data%s/id/%s' % ('live' if is_live else 'video', video_id),
            video_id, 'Downloading JSON')

        if json_data['errors']:
            raise ExtractorError('%s said: %s' % (self.IE_NAME, json_data['errors']), expected=True)

        playlist = json_data['data']['playlist']
        medialist = playlist['medialist']
        media = medialist[0]

        if media['errors']:
            raise ExtractorError('%s said: %s' % (self.IE_NAME, media['errors']), expected=True)

        view_count = playlist.get('count_views')
        priority_transport = playlist['priority_transport']

        thumbnail = media['picture']
        width = int_or_none(media['width'])
        height = int_or_none(media['height'])
        description = media['anons']
        title = media['title']
        duration = int_or_none(media.get('duration'))

        formats = []

        for transport, links in media['sources'].items():
            for quality, url in links.items():
                preference = -1 if priority_transport == transport else -2
                if transport == 'rtmp':
                    mobj = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>.+))/(?P<playpath>.+)$', url)
                    if not mobj:
                        continue
                    fmt = {
                        'url': mobj.group('url'),
                        'play_path': mobj.group('playpath'),
                        'app': mobj.group('app'),
                        'page_url': 'http://player.rutv.ru',
                        'player_url': 'http://player.rutv.ru/flash3v/osmf.swf?i=22',
                        'rtmp_live': True,
                        'ext': 'flv',
                        'vbr': str_to_int(quality),
                    }
                elif transport == 'm3u8':
                    formats.extend(self._extract_m3u8_formats(
                        url, video_id, 'mp4', quality=preference, m3u8_id='hls'))
                    continue
                else:
                    fmt = {
                        'url': url
                    }
                fmt.update({
                    'width': int_or_none(quality, default=height, invscale=width, scale=height),
                    'height': int_or_none(quality, default=height),
                    'format_id': '%s-%s' % (transport, quality),
                    'source_preference': preference,
                })
                formats.append(fmt)

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'view_count': view_count,
            'duration': duration,
            'formats': formats,
            'is_live': is_live,
        }


code:
import json
import re

from .common import InfoExtractor
from ..compat import compat_urllib_parse_urlparse
from ..utils import (
    determine_ext,
    ExtractorError,
    find_xpath_attr,
    int_or_none,
    traverse_obj,
    try_call,
    unified_strdate,
    url_or_none,
    xpath_attr,
    xpath_text,
)

class RuutuIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            (?:www\.)?(?:ruutu|supla)\.fi/(?:video|supla|audio)/|
                            static\.nelonenmedia\.fi/player/misc/embed_player\.html\?.*?\bnid=
                        )
                        (?P<id>\d+)
                    '''
    _TESTS = [
        {
            'url': 'http://www.ruutu.fi/video/2058907',
            'md5': 'ab2093f39be1ca8581963451b3c0234f',
            'info_dict': {
                'id': '2058907',
                'ext': 'mp4',
                'title': 'Oletko aina halunnut tietää mitä tapahtuu vain hetki ennen lähetystä? - Nyt se selvisi!',
                'description': 'md5:cfc6ccf0e57a814360df464a91ff67d6',
                'thumbnail': r're:^https?://.*\.jpg$',
                'duration': 114,
                'age_limit': 0,
                'upload_date': '20150508',
            },
        },
        {
            'url': 'http://www.ruutu.fi/video/2057306',
            'md5': '065a10ae4d5b8cfd9d0c3d332465e3d9',
            'info_dict': {
                'id': '2057306',
                'ext': 'mp4',
                'title': 'Superpesis: katso koko kausi Ruudussa',
                'description': 'md5:bfb7336df2a12dc21d18fa696c9f8f23',
                'thumbnail': r're:^https?://.*\.jpg$',
                'duration': 40,
                'age_limit': 0,
                'upload_date': '20150507',
                'series': 'Superpesis',
                'categories': ['Urheilu'],
            },
        },
        {
            'url': 'http://www.supla.fi/supla/2231370',
            'md5': 'df14e782d49a2c0df03d3be2a54ef949',
            'info_dict': {
                'id': '2231370',
                'ext': 'mp4',
                'title': 'Osa 1: Mikael Jungner',
                'description': 'md5:7d90f358c47542e3072ff65d7b1bcffe',
                'thumbnail': r're:^https?://.*\.jpg$',
                'age_limit': 0,
                'upload_date': '20151012',
                'series': 'Läpivalaisu',
            },
        },
        # Episode where <SourceFile> is "NOT-USED", but has other
        # downloadable sources available.
        {
            'url': 'http://www.ruutu.fi/video/3193728',
            'only_matching': True,
        },
        {
            # audio podcast
            'url': 'https://www.supla.fi/supla/3382410',
            'md5': 'b9d7155fed37b2ebf6021d74c4b8e908',
            'info_dict': {
                'id': '3382410',
                'ext': 'mp3',
                'title': 'Mikä ihmeen poltergeist?',
                'description': 'md5:bbb6963df17dfd0ecd9eb9a61bf14b52',
                'thumbnail': r're:^https?://.*\.jpg$',
                'age_limit': 0,
                'upload_date': '20190320',
                'series': 'Mysteeritarinat',
                'duration': 1324,
            },
            'expected_warnings': [
                'HTTP Error 502: Bad Gateway',
                'Failed to download m3u8 information',
            ],
        },
        {
            'url': 'http://www.supla.fi/audio/2231370',
            'only_matching': True,
        },
        {
            'url': 'https://static.nelonenmedia.fi/player/misc/embed_player.html?nid=3618790',
            'only_matching': True,
        },
        {
            # episode
            'url': 'https://www.ruutu.fi/video/3401964',
            'info_dict': {
                'id': '3401964',
                'ext': 'mp4',
                'title': 'Temptation Island Suomi - Kausi 5 - Jakso 17',
                'description': 'md5:87cf01d5e1e88adf0c8a2937d2bd42ba',
                'thumbnail': r're:^https?://.*\.jpg$',
                'duration': 2582,
                'age_limit': 12,
                'upload_date': '20190508',
                'series': 'Temptation Island Suomi',
                'season_number': 5,
                'episode_number': 17,
                'categories': ['Reality ja tositapahtumat', 'Kotimaiset suosikit', 'Romantiikka ja parisuhde'],
            },
            'params': {
                'skip_download': True,
            },
        },
        {
            # premium
            'url': 'https://www.ruutu.fi/video/3618715',
            'only_matching': True,
        },
    ]
    _API_BASE = 'https://gatling.nelonenmedia.fi'

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        # nelonen.fi
        settings = try_call(
            lambda: json.loads(re.search(
                r'jQuery\.extend\(Drupal\.settings, ({.+?})\);', webpage).group(1), strict=False))
        if settings:
            video_id = traverse_obj(settings, (
                'mediaCrossbowSettings', 'file', 'field_crossbow_video_id', 'und', 0, 'value'))
            if video_id:
                return [f'http://www.ruutu.fi/video/{video_id}']
        # hs.fi and is.fi
        settings = try_call(
            lambda: json.loads(re.search(
                '(?s)<script[^>]+id=[\'"]__NEXT_DATA__[\'"][^>]*>([^<]+)</script>',
                webpage).group(1), strict=False))
        if settings:
            video_ids = set(traverse_obj(settings, (
                'props', 'pageProps', 'page', 'assetData', 'splitBody', ..., 'video', 'sourceId')) or [])
            if video_ids:
                return [f'http://www.ruutu.fi/video/{v}' for v in video_ids]
            video_id = traverse_obj(settings, (
                'props', 'pageProps', 'page', 'assetData', 'mainVideo', 'sourceId'))
            if video_id:
                return [f'http://www.ruutu.fi/video/{video_id}']

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

        video_xml = self._download_xml(
            '%s/media-xml-cache' % self._API_BASE, video_id,
            query={'id': video_id})

        formats = []
        processed_urls = []

        def extract_formats(node):
            for child in node:
                if child.tag.endswith('Files'):
                    extract_formats(child)
                elif child.tag.endswith('File'):
                    video_url = child.text
                    if (not video_url or video_url in processed_urls
                            or any(p in video_url for p in ('NOT_USED', 'NOT-USED'))):
                        continue
                    processed_urls.append(video_url)
                    ext = determine_ext(video_url)
                    auth_video_url = url_or_none(self._download_webpage(
                        '%s/auth/access/v2' % self._API_BASE, video_id,
                        note='Downloading authenticated %s stream URL' % ext,
                        fatal=False, query={'stream': video_url}))
                    if auth_video_url:
                        processed_urls.append(auth_video_url)
                        video_url = auth_video_url
                    if ext == 'm3u8':
                        formats.extend(self._extract_m3u8_formats(
                            video_url, video_id, 'mp4',
                            entry_protocol='m3u8_native', m3u8_id='hls',
                            fatal=False))
                    elif ext == 'f4m':
                        formats.extend(self._extract_f4m_formats(
                            video_url, video_id, f4m_id='hds', fatal=False))
                    elif ext == 'mpd':
                        # video-only and audio-only streams are of different
                        # duration resulting in out of sync issue
                        continue
                        formats.extend(self._extract_mpd_formats(
                            video_url, video_id, mpd_id='dash', fatal=False))
                    elif ext == 'mp3' or child.tag == 'AudioMediaFile':
                        formats.append({
                            'format_id': 'audio',
                            'url': video_url,
                            'vcodec': 'none',
                        })
                    else:
                        proto = compat_urllib_parse_urlparse(video_url).scheme
                        if not child.tag.startswith('HTTP') and proto != 'rtmp':
                            continue
                        preference = -1 if proto == 'rtmp' else 1
                        label = child.get('label')
                        tbr = int_or_none(child.get('bitrate'))
                        format_id = '%s-%s' % (proto, label if label else tbr) if label or tbr else proto
                        if not self._is_valid_url(video_url, video_id, format_id):
                            continue
                        width, height = [int_or_none(x) for x in child.get('resolution', 'x').split('x')[:2]]
                        formats.append({
                            'format_id': format_id,
                            'url': video_url,
                            'width': width,
                            'height': height,
                            'tbr': tbr,
                            'preference': preference,
                        })

        extract_formats(video_xml.find('./Clip'))

        def pv(name):
            value = try_call(lambda: find_xpath_attr(
                video_xml, './Clip/PassthroughVariables/variable', 'name', name).get('value'))
            if value != 'NA':
                return value or None

        if not formats:
            if (not self.get_param('allow_unplayable_formats')
                    and xpath_text(video_xml, './Clip/DRM', default=None)):
                self.report_drm(video_id)
            ns_st_cds = pv('ns_st_cds')
            if ns_st_cds != 'free':
                raise ExtractorError('This video is %s.' % ns_st_cds, expected=True)

        self._sort_formats(formats)

        themes = pv('themes')

        return {
            'id': video_id,
            'title': xpath_attr(video_xml, './/Behavior/Program', 'program_name', 'title', fatal=True),
            'description': xpath_attr(video_xml, './/Behavior/Program', 'description', 'description'),
            'thumbnail': xpath_attr(video_xml, './/Behavior/Startpicture', 'href', 'thumbnail'),
            'duration': int_or_none(xpath_text(video_xml, './/Runtime', 'duration')) or int_or_none(pv('runtime')),
            'age_limit': int_or_none(xpath_text(video_xml, './/AgeLimit', 'age limit')),
            'upload_date': unified_strdate(pv('date_start')),
            'series': pv('series_name'),
            'season_number': int_or_none(pv('season_number')),
            'episode_number': int_or_none(pv('episode_number')),
            'categories': themes.split(',') if themes else None,
            'formats': formats,
        }

repo_name: Lesmiscore/ytdl


code:
from .common import InfoExtractor
from ..utils import (
    determine_ext,
    parse_duration,
    traverse_obj,
    unified_timestamp,
)

class RuvIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?ruv\.is/(?:sarpurinn/[^/]+|node)/(?P<id>[^/]+(?:/\d+)?)'
    _TESTS = [{
        # m3u8
        'url': 'http://ruv.is/sarpurinn/ruv-aukaras/fh-valur/20170516',
        'md5': '66347652f4e13e71936817102acc1724',
        'info_dict': {
            'id': '1144499',
            'display_id': 'fh-valur/20170516',
            'ext': 'mp4',
            'title': 'FH - Valur',
            'description': 'Bein útsending frá 3. leik FH og Vals í úrslitum Olísdeildar karla í handbolta.',
            'timestamp': 1494963600,
            'upload_date': '20170516',
        },
    }, {
        # mp3
        'url': 'http://ruv.is/sarpurinn/ras-2/morgunutvarpid/20170619',
        'md5': '395ea250c8a13e5fdb39d4670ef85378',
        'info_dict': {
            'id': '1153630',
            'display_id': 'morgunutvarpid/20170619',
            'ext': 'mp3',
            'title': 'Morgunútvarpið',
            'description': 'md5:a4cf1202c0a1645ca096b06525915418',
            'timestamp': 1497855000,
            'upload_date': '20170619',
        },
    }, {
        'url': 'http://ruv.is/sarpurinn/ruv/frettir/20170614',
        'only_matching': True,
    }, {
        'url': 'http://www.ruv.is/node/1151854',
        'only_matching': True,
    }, {
        'url': 'http://ruv.is/sarpurinn/klippa/secret-soltice-hefst-a-morgun',
        'only_matching': True,
    }, {
        'url': 'http://ruv.is/sarpurinn/ras-1/morgunvaktin/20170619',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)

        title = self._og_search_title(webpage)

        FIELD_RE = r'video\.%s\s*=\s*(["\'])(?P<url>(?:(?!\1).)+)\1'

        media_url = self._html_search_regex(
            FIELD_RE % 'src', webpage, 'video URL', group='url')

        video_id = self._search_regex(
            r'<link\b[^>]+\bhref=["\']https?://www\.ruv\.is/node/(\d+)',
            webpage, 'video id', default=display_id)

        ext = determine_ext(media_url)

        if ext == 'm3u8':
            formats = self._extract_m3u8_formats(
                media_url, video_id, 'mp4', entry_protocol='m3u8_native',
                m3u8_id='hls')
        elif ext == 'mp3':
            formats = [{
                'format_id': 'mp3',
                'url': media_url,
                'vcodec': 'none',
            }]
        else:
            formats = [{
                'url': media_url,
            }]

        description = self._og_search_description(webpage, default=None)
        thumbnail = self._og_search_thumbnail(
            webpage, default=None) or self._search_regex(
            FIELD_RE % 'poster', webpage, 'thumbnail', fatal=False)
        timestamp = unified_timestamp(self._html_search_meta(
            'article:published_time', webpage, 'timestamp', fatal=False))

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'timestamp': timestamp,
            'formats': formats,
        }

class RuvSpilaIE(InfoExtractor):
    IE_NAME = 'ruv.is:spila'
    _VALID_URL = r'https?://(?:www\.)?ruv\.is/(?:(?:sjon|ut)varp|(?:krakka|ung)ruv)/spila/.+/(?P<series_id>[0-9]+)/(?P<id>[a-z0-9]+)'
    _TESTS = [{
        'url': 'https://www.ruv.is/sjonvarp/spila/ithrottir/30657/9jcnd4',
        'info_dict': {
            'id': '9jcnd4',
            'ext': 'mp4',
            'title': '01.02.2022',
            'chapters': 'count:4',
            'timestamp': 1643743500,
            'upload_date': '20220201',
            'thumbnail': 'https://d38kdhuogyllre.cloudfront.net/fit-in/1960x/filters:quality(65)/hd_posters/94boog-iti3jg.jpg',
            'description': 'Íþróttafréttir.',
            'age_limit': 0,
        },
    }, {
        'url': 'https://www.ruv.is/utvarp/spila/i-ljosi-sogunnar/23795/7hqkre',
        'info_dict': {
            'id': '7hqkre',
            'ext': 'mp3',
            'thumbnail': 'https://d38kdhuogyllre.cloudfront.net/fit-in/1960x/filters:quality(65)/hd_posters/7hqkre-7uepao.jpg',
            'description': 'md5:8d7046549daff35e9a3190dc9901a120',
            'chapters': [],
            'upload_date': '20220204',
            'timestamp': 1643965500,
            'title': 'Nellie Bly II',
            'age_limit': 0,
        },
    }, {
        'url': 'https://www.ruv.is/ungruv/spila/ungruv/28046/8beuph',
        'only_matching': True
    }, {
        'url': 'https://www.ruv.is/krakkaruv/spila/krakkafrettir/30712/9jbgb0',
        'only_matching': True
    }]

    def _real_extract(self, url):
        display_id, series_id = self._match_valid_url(url).group('id', 'series_id')
        program = self._download_json(
            'https://www.ruv.is/gql/', display_id, query={'query': '''{
                Program(id: %s){
                    title image description short_description
                    episodes(id: {value: "%s"}) {
                        rating title duration file image firstrun description
                        clips {
                            time text
                        }
                        subtitles {
                            name value
                        }
                    }
                }
            }''' % (series_id, display_id)})['data']['Program']
        episode = program['episodes'][0]

        subs = {}
        for trk in episode.get('subtitles'):
            if trk.get('name') and trk.get('value'):
                subs.setdefault(trk['name'], []).append({'url': trk['value'], 'ext': 'vtt'})

        media_url = episode['file']
        if determine_ext(media_url) == 'm3u8':
            formats = self._extract_m3u8_formats(media_url, display_id)
        else:
            formats = [{'url': media_url}]

        clips = [
            {'start_time': parse_duration(c.get('time')), 'title': c.get('text')}
            for c in episode.get('clips') or []]

        return {
            'id': display_id,
            'title': traverse_obj(program, ('episodes', 0, 'title'), 'title'),
            'description': traverse_obj(
                program, ('episodes', 0, 'description'), 'description', 'short_description',
                expected_type=lambda x: x or None),
            'subtitles': subs,
            'thumbnail': episode.get('image', '').replace('$$IMAGESIZE$$', '1960') or None,
            'timestamp': unified_timestamp(episode.get('firstrun')),
            'formats': formats,
            'age_limit': episode.get('rating'),
            'chapters': clips
        }


code:
import json
import re

from .common import InfoExtractor

from ..compat import (
    compat_parse_qs,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    update_url_query,
)

class SafariBaseIE(InfoExtractor):
    _LOGIN_URL = 'https://learning.oreilly.com/accounts/login/'
    _NETRC_MACHINE = 'safari'

    _API_BASE = 'https://learning.oreilly.com/api/v1'
    _API_FORMAT = 'json'

    LOGGED_IN = False

    def _perform_login(self, username, password):
        _, urlh = self._download_webpage_handle(
            'https://learning.oreilly.com/accounts/login-check/', None,
            'Downloading login page')

        def is_logged(urlh):
            return 'learning.oreilly.com/home/' in urlh.geturl()

        if is_logged(urlh):
            self.LOGGED_IN = True
            return

        redirect_url = urlh.geturl()
        parsed_url = compat_urlparse.urlparse(redirect_url)
        qs = compat_parse_qs(parsed_url.query)
        next_uri = compat_urlparse.urljoin(
            'https://api.oreilly.com', qs['next'][0])

        auth, urlh = self._download_json_handle(
            'https://www.oreilly.com/member/auth/login/', None, 'Logging in',
            data=json.dumps({
                'email': username,
                'password': password,
                'redirect_uri': next_uri,
            }).encode(), headers={
                'Content-Type': 'application/json',
                'Referer': redirect_url,
            }, expected_status=400)

        credentials = auth.get('credentials')
        if (not auth.get('logged_in') and not auth.get('redirect_uri')
                and credentials):
            raise ExtractorError(
                'Unable to login: %s' % credentials, expected=True)

        # oreilly serves two same instances of the following cookies
        # in Set-Cookie header and expects first one to be actually set
        for cookie in ('groot_sessionid', 'orm-jwt', 'orm-rt'):
            self._apply_first_set_cookie_header(urlh, cookie)

        _, urlh = self._download_webpage_handle(
            auth.get('redirect_uri') or next_uri, None, 'Completing login',)

        if is_logged(urlh):
            self.LOGGED_IN = True
            return

        raise ExtractorError('Unable to log in')

class SafariIE(SafariBaseIE):
    IE_NAME = 'safari'
    IE_DESC = 'safaribooksonline.com online video'
    _VALID_URL = r'''(?x)
                        https?://
                            (?:www\.)?(?:safaribooksonline|(?:learning\.)?oreilly)\.com/
                            (?:
                                library/view/[^/]+/(?P<course_id>[^/]+)/(?P<part>[^/?\#&]+)\.html|
                                videos/[^/]+/[^/]+/(?P<reference_id>[^-]+-[^/?\#&]+)
                            )
                    '''

    _TESTS = [{
        'url': 'https://www.safaribooksonline.com/library/view/hadoop-fundamentals-livelessons/9780133392838/part00.html',
        'md5': 'dcc5a425e79f2564148652616af1f2a3',
        'info_dict': {
            'id': '0_qbqx90ic',
            'ext': 'mp4',
            'title': 'Introduction to Hadoop Fundamentals LiveLessons',
            'timestamp': 1437758058,
            'upload_date': '20150724',
            'uploader_id': 'stork',
        },
    }, {
        # non-digits in course id
        'url': 'https://www.safaribooksonline.com/library/view/create-a-nodejs/100000006A0210/part00.html',
        'only_matching': True,
    }, {
        'url': 'https://www.safaribooksonline.com/library/view/learning-path-red/9780134664057/RHCE_Introduction.html',
        'only_matching': True,
    }, {
        'url': 'https://www.safaribooksonline.com/videos/python-programming-language/9780134217314/9780134217314-PYMC_13_00',
        'only_matching': True,
    }, {
        'url': 'https://learning.oreilly.com/videos/hadoop-fundamentals-livelessons/9780133392838/9780133392838-00_SeriesIntro',
        'only_matching': True,
    }, {
        'url': 'https://www.oreilly.com/library/view/hadoop-fundamentals-livelessons/9780133392838/00_SeriesIntro.html',
        'only_matching': True,
    }]

    _PARTNER_ID = '1926081'
    _UICONF_ID = '29375172'

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

        reference_id = mobj.group('reference_id')
        if reference_id:
            video_id = reference_id
            partner_id = self._PARTNER_ID
            ui_id = self._UICONF_ID
        else:
            video_id = '%s-%s' % (mobj.group('course_id'), mobj.group('part'))

            webpage, urlh = self._download_webpage_handle(url, video_id)

            mobj = re.match(self._VALID_URL, urlh.geturl())
            reference_id = mobj.group('reference_id')
            if not reference_id:
                reference_id = self._search_regex(
                    r'data-reference-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
                    webpage, 'kaltura reference id', group='id')
            partner_id = self._search_regex(
                r'data-partner-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
                webpage, 'kaltura widget id', default=self._PARTNER_ID,
                group='id')
            ui_id = self._search_regex(
                r'data-ui-id=(["\'])(?P<id>(?:(?!\1).)+)\1',
                webpage, 'kaltura uiconf id', default=self._UICONF_ID,
                group='id')

        query = {
            'wid': '_%s' % partner_id,
            'uiconf_id': ui_id,
            'flashvars[referenceId]': reference_id,
        }

        if self.LOGGED_IN:
            kaltura_session = self._download_json(
                '%s/player/kaltura_session/?reference_id=%s' % (self._API_BASE, reference_id),
                video_id, 'Downloading kaltura session JSON',
                'Unable to download kaltura session JSON', fatal=False,
                headers={'Accept': 'application/json'})
            if kaltura_session:
                session = kaltura_session.get('session')
                if session:
                    query['flashvars[ks]'] = session

        return self.url_result(update_url_query(
            'https://cdnapisec.kaltura.com/html5/html5lib/v2.37.1/mwEmbedFrame.php', query),
            'Kaltura')

class SafariApiIE(SafariBaseIE):
    IE_NAME = 'safari:api'
    _VALID_URL = r'https?://(?:www\.)?(?:safaribooksonline|(?:learning\.)?oreilly)\.com/api/v1/book/(?P<course_id>[^/]+)/chapter(?:-content)?/(?P<part>[^/?#&]+)\.html'

    _TESTS = [{
        'url': 'https://www.safaribooksonline.com/api/v1/book/9780133392838/chapter/part00.html',
        'only_matching': True,
    }, {
        'url': 'https://www.safaribooksonline.com/api/v1/book/9780134664057/chapter/RHCE_Introduction.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        part = self._download_json(
            url, '%s/%s' % (mobj.group('course_id'), mobj.group('part')),
            'Downloading part JSON')
        web_url = part['web_url']
        if 'library/view' in web_url:
            web_url = web_url.replace('library/view', 'videos')
            natural_keys = part['natural_key']
            web_url = f'{web_url.rsplit("/", 1)[0]}/{natural_keys[0]}-{natural_keys[1][:-5]}'
        return self.url_result(web_url, SafariIE.ie_key())

class SafariCourseIE(SafariBaseIE):
    IE_NAME = 'safari:course'
    IE_DESC = 'safaribooksonline.com online courses'

    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            (?:www\.)?(?:safaribooksonline|(?:learning\.)?oreilly)\.com/
                            (?:
                                library/view/[^/]+|
                                api/v1/book|
                                videos/[^/]+
                            )|
                            techbus\.safaribooksonline\.com
                        )
                        /(?P<id>[^/]+)
                    '''

    _TESTS = [{
        'url': 'https://www.safaribooksonline.com/library/view/hadoop-fundamentals-livelessons/9780133392838/',
        'info_dict': {
            'id': '9780133392838',
            'title': 'Hadoop Fundamentals LiveLessons',
        },
        'playlist_count': 22,
        'skip': 'Requires safaribooksonline account credentials',
    }, {
        'url': 'https://www.safaribooksonline.com/api/v1/book/9781449396459/?override_format=json',
        'only_matching': True,
    }, {
        'url': 'http://techbus.safaribooksonline.com/9780134426365',
        'only_matching': True,
    }, {
        'url': 'https://www.safaribooksonline.com/videos/python-programming-language/9780134217314',
        'only_matching': True,
    }, {
        'url': 'https://learning.oreilly.com/videos/hadoop-fundamentals-livelessons/9780133392838',
        'only_matching': True,
    }, {
        'url': 'https://www.oreilly.com/library/view/hadoop-fundamentals-livelessons/9780133392838/',
        'only_matching': True,
    }]

    @classmethod
    def suitable(cls, url):
        return (False if SafariIE.suitable(url) or SafariApiIE.suitable(url)
                else super(SafariCourseIE, cls).suitable(url))

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

        course_json = self._download_json(
            '%s/book/%s/?override_format=%s' % (self._API_BASE, course_id, self._API_FORMAT),
            course_id, 'Downloading course JSON')

        if 'chapters' not in course_json:
            raise ExtractorError(
                'No chapters found for course %s' % course_id, expected=True)

        entries = [
            self.url_result(chapter, SafariApiIE.ie_key())
            for chapter in course_json['chapters']]

        course_title = course_json['title']

        return self.playlist_result(entries, course_id, course_title)


code:
from .common import InfoExtractor
from ..utils import ExtractorError, try_get

class SaitosanIE(InfoExtractor):
    IE_NAME = 'Saitosan'
    _VALID_URL = r'https?://(?:www\.)?saitosan\.net/bview.html\?id=(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://www.saitosan.net/bview.html?id=10031846',
        'info_dict': {
            'id': '10031846',
            'ext': 'mp4',
            'title': '井下原 和弥',
            'uploader': '井下原 和弥',
            'thumbnail': 'http://111.171.196.85:8088/921f916f-7f55-4c97-b92e-5d9d0fef8f5f/thumb',
            'is_live': True,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'skip': 'Broadcasts are ephemeral',
    },
        {
        'url': 'http://www.saitosan.net/bview.html?id=10031795',
        'info_dict': {
            'id': '10031795',
            'ext': 'mp4',
            'title': '橋本',
            'uploader': '橋本',
            'thumbnail': 'http://111.171.196.85:8088/1a3933e1-a01a-483b-8931-af15f37f8082/thumb',
            'is_live': True,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'skip': 'Broadcasts are ephemeral',
    }]

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

        base = 'http://hankachi.saitosan-api.net:8002/socket.io/?transport=polling&EIO=3'
        sid = self._download_socket_json(base, b_id, note='Opening socket').get('sid')
        base += '&sid=' + sid

        self._download_webpage(base, b_id, note='Polling socket')
        payload = '420["room_start_join",{"room_id":"%s"}]' % b_id
        payload = '%s:%s' % (len(payload), payload)

        self._download_webpage(base, b_id, data=payload, note='Polling socket with payload')
        response = self._download_socket_json(base, b_id, note='Polling socket')
        if not response.get('ok'):
            err = response.get('error') or {}
            raise ExtractorError(
                '%s said: %s - %s' % (self.IE_NAME, err.get('code', '?'), err.get('msg', 'Unknown')) if err
                else 'The socket reported that the broadcast could not be joined. Maybe it\'s offline or the URL is incorrect',
                expected=True, video_id=b_id)

        self._download_webpage(base, b_id, data='26:421["room_finish_join",{}]', note='Polling socket')
        b_data = self._download_socket_json(base, b_id, note='Getting broadcast metadata from socket')
        m3u8_url = b_data.get('url')

        self._download_webpage(base, b_id, data='1:1', note='Closing socket', fatal=False)

        return {
            'id': b_id,
            'title': b_data.get('name'),
            'formats': self._extract_m3u8_formats(m3u8_url, b_id, 'mp4', live=True),
            'thumbnail': m3u8_url.replace('av.m3u8', 'thumb'),
            'uploader': try_get(b_data, lambda x: x['broadcast_user']['name']),  # same as title
            'is_live': True
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    extract_attributes,
    get_element_by_attribute,
    int_or_none,
)

class SampleFocusIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?samplefocus\.com/samples/(?P<id>[^/?&#]+)'
    _TESTS = [{
        'url': 'https://samplefocus.com/samples/lil-peep-sad-emo-guitar',
        'md5': '48c8d62d60be467293912e0e619a5120',
        'info_dict': {
            'id': '40316',
            'display_id': 'lil-peep-sad-emo-guitar',
            'ext': 'mp3',
            'title': 'Lil Peep Sad Emo Guitar',
            'thumbnail': r're:^https?://.+\.png',
            'license': 'Standard License',
            'uploader': 'CapsCtrl',
            'uploader_id': 'capsctrl',
            'like_count': int,
            'comment_count': int,
            'categories': ['Samples', 'Guitar', 'Electric guitar'],
        },
    }, {
        'url': 'https://samplefocus.com/samples/dababy-style-bass-808',
        'only_matching': True
    }, {
        'url': 'https://samplefocus.com/samples/young-chop-kick',
        'only_matching': True
    }]

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

        sample_id = self._search_regex(
            r'<input[^>]+id=(["\'])sample_id\1[^>]+value=(?:["\'])(?P<id>\d+)',
            webpage, 'sample id', group='id')

        title = self._og_search_title(webpage, fatal=False) or self._html_search_regex(
            r'
(.+?)
', webpage, 'title')

        mp3_url = self._search_regex(
            r'<input[^>]+id=(["\'])sample_mp3\1[^>]+value=(["\'])(?P<url>(?:(?!\2).)+)',
            webpage, 'mp3', fatal=False, group='url') or extract_attributes(self._search_regex(
                r'<meta[^>]+itemprop=(["\'])contentUrl\1[^>]*>',
                webpage, 'mp3 url', group=0))['content']

        thumbnail = self._og_search_thumbnail(webpage) or self._html_search_regex(
            r']+class=(?:["\'])waveform responsive-img[^>]+src=(["\'])(?P<url>(?:(?!\1).)+)',
            webpage, 'mp3', fatal=False, group='url')

        comments = []
        for author_id, author, body in re.findall(r'(?s)
]+class="comment-author">]+href="/users/([^"]+)">([^"]+).+?
]+class="comment-body">([^>]+)
', webpage):
            comments.append({
                'author': author,
                'author_id': author_id,
                'text': body,
            })

        uploader_id = uploader = None
        mobj = re.search(r'>By ]+href="/users/([^"]+)"[^>]*>([^<]+)', webpage)
        if mobj:
            uploader_id, uploader = mobj.groups()

        breadcrumb = get_element_by_attribute('typeof', 'BreadcrumbList', webpage)
        categories = []
        if breadcrumb:
            for _, name in re.findall(r']+property=(["\'])name\1[^>]*>([^<]+)', breadcrumb):
                categories.append(name)

        def extract_count(klass):
            return int_or_none(self._html_search_regex(
                r']+class=(?:["\'])?%s-count[^>]*>(\d+)' % klass,
                webpage, klass, fatal=False))

        return {
            'id': sample_id,
            'title': title,
            'url': mp3_url,
            'display_id': display_id,
            'thumbnail': thumbnail,
            'uploader': uploader,
            'license': self._html_search_regex(
                r']+href=(["\'])/license\1[^>]*>(?P<license>[^<]+)<',
                webpage, 'license', fatal=False, group='license'),
            'uploader_id': uploader_id,
            'like_count': extract_count('sample-%s-favorites' % sample_id),
            'comment_count': extract_count('comments'),
            'comments': comments,
            'categories': categories,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    parse_duration,
    unified_strdate,
)

class SapoIE(InfoExtractor):
    IE_DESC = 'SAPO Vídeos'
    _VALID_URL = r'https?://(?:(?:v2|www)\.)?videos\.sapo\.(?:pt|cv|ao|mz|tl)/(?P<id>[\da-zA-Z]{20})'

    _TESTS = [
        {
            'url': 'http://videos.sapo.pt/UBz95kOtiWYUMTA5Ghfi',
            'md5': '79ee523f6ecb9233ac25075dee0eda83',
            'note': 'SD video',
            'info_dict': {
                'id': 'UBz95kOtiWYUMTA5Ghfi',
                'ext': 'mp4',
                'title': 'Benfica - Marcas na Hitória',
                'description': 'md5:c9082000a128c3fd57bf0299e1367f22',
                'duration': 264,
                'uploader': 'tiago_1988',
                'upload_date': '20080229',
                'categories': ['benfica', 'cabral', 'desporto', 'futebol', 'geovanni', 'hooijdonk', 'joao', 'karel', 'lisboa', 'miccoli'],
            },
        },
        {
            'url': 'http://videos.sapo.pt/IyusNAZ791ZdoCY5H5IF',
            'md5': '90a2f283cfb49193fe06e861613a72aa',
            'note': 'HD video',
            'info_dict': {
                'id': 'IyusNAZ791ZdoCY5H5IF',
                'ext': 'mp4',
                'title': 'Codebits VII - Report',
                'description': 'md5:6448d6fd81ce86feac05321f354dbdc8',
                'duration': 144,
                'uploader': 'codebits',
                'upload_date': '20140427',
                'categories': ['codebits', 'codebits2014'],
            },
        },
        {
            'url': 'http://v2.videos.sapo.pt/yLqjzPtbTimsn2wWBKHz',
            'md5': 'e5aa7cc0bdc6db9b33df1a48e49a15ac',
            'note': 'v2 video',
            'info_dict': {
                'id': 'yLqjzPtbTimsn2wWBKHz',
                'ext': 'mp4',
                'title': 'Hipnose Condicionativa 4',
                'description': 'md5:ef0481abf8fb4ae6f525088a6dadbc40',
                'duration': 692,
                'uploader': 'sapozen',
                'upload_date': '20090609',
                'categories': ['condicionativa', 'heloisa', 'hipnose', 'miranda', 'sapo', 'zen'],
            },
        },
    ]

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

        item = self._download_xml(
            'http://rd3.videos.sapo.pt/%s/rss2' % video_id, video_id).find('./channel/item')

        title = item.find('./title').text
        description = item.find('./{http://videos.sapo.pt/mrss/}synopse').text
        thumbnail = item.find('./{http://search.yahoo.com/mrss/}content').get('url')
        duration = parse_duration(item.find('./{http://videos.sapo.pt/mrss/}time').text)
        uploader = item.find('./{http://videos.sapo.pt/mrss/}author').text
        upload_date = unified_strdate(item.find('./pubDate').text)
        view_count = int(item.find('./{http://videos.sapo.pt/mrss/}views').text)
        comment_count = int(item.find('./{http://videos.sapo.pt/mrss/}comment_count').text)
        tags = item.find('./{http://videos.sapo.pt/mrss/}tags').text
        categories = tags.split() if tags else []
        age_limit = 18 if item.find('./{http://videos.sapo.pt/mrss/}m18').text == 'true' else 0

        video_url = item.find('./{http://videos.sapo.pt/mrss/}videoFile').text
        video_size = item.find('./{http://videos.sapo.pt/mrss/}videoSize').text.split('x')

        formats = [{
            'url': video_url,
            'ext': 'mp4',
            'format_id': 'sd',
            'width': int(video_size[0]),
            'height': int(video_size[1]),
        }]

        if item.find('./{http://videos.sapo.pt/mrss/}HD').text == 'true':
            formats.append({
                'url': re.sub(r'/mov/1$', '/mov/39', video_url),
                'ext': 'mp4',
                'format_id': 'hd',
                'width': 1280,
                'height': 720,
            })

        self._sort_formats(formats)

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


code:
import os.path

from .common import InfoExtractor

class SaveFromIE(InfoExtractor):
    IE_NAME = 'savefrom.net'
    _VALID_URL = r'https?://[^.]+\.savefrom\.net/\#url=(?P<url>.*)$'

    _TEST = {
        'url': 'http://en.savefrom.net/#url=http://youtube.com/watch?v=UlVRAPW2WJY&utm_source=youtube.com&utm_medium=short_domains&utm_campaign=ssyoutube.com',
        'info_dict': {
            'id': 'UlVRAPW2WJY',
            'ext': 'mp4',
            'title': 'About Team Radical MMA | MMA Fighting',
            'upload_date': '20120816',
            'uploader': 'Howcast',
            'uploader_id': 'Howcast',
            'description': r're:(?s).* Hi, my name is Rene Dreifuss\. And I\'m here to show you some MMA.*',
        },
        'params': {
            'skip_download': True
        }
    }

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        video_id = os.path.splitext(url.split('/')[-1])[0]

        return self.url_result(mobj.group('url'), video_id=video_id)


code:
from .common import InfoExtractor
from ..utils import (
    smuggle_url,
    ExtractorError,
)

class SBSIE(InfoExtractor):
    IE_DESC = 'sbs.com.au'
    _VALID_URL = r'''(?x)
        https?://(?:www\.)?sbs\.com\.au/(?:
            ondemand(?:
                /video/(?:single/)?|
                /movie/[^/]+/|
                .*?\bplay=|/watch/
            )|news/(?:embeds/)?video/
        )(?P<id>[0-9]+)'''
    _EMBED_REGEX = [r'''(?x)]
            (?:
                <meta\s+property="og:video"\s+content=|
                <iframe[^>]+?src=
            )
            (["\'])(?P<url>https?://(?:www\.)?sbs\.com\.au/ondemand/video/.+?)\1''']

    _TESTS = [{
        # Original URL is handled by the generic IE which finds the iframe:
        # http://www.sbs.com.au/thefeed/blog/2014/08/21/dingo-conservation
        'url': 'http://www.sbs.com.au/ondemand/video/single/320403011771/?source=drupal&vertical=thefeed',
        'md5': '3150cf278965eeabb5b4cea1c963fe0a',
        'info_dict': {
            'id': '_rFBPRPO4pMR',
            'ext': 'mp4',
            'title': 'Dingo Conservation (The Feed)',
            'description': 'md5:f250a9856fca50d22dec0b5b8015f8a5',
            'thumbnail': r're:http://.*\.jpg',
            'duration': 308,
            'timestamp': 1408613220,
            'upload_date': '20140821',
            'uploader': 'SBSC',
        },
    }, {
        'url': 'http://www.sbs.com.au/ondemand/video/320403011771/Dingo-Conservation-The-Feed',
        'only_matching': True,
    }, {
        'url': 'http://www.sbs.com.au/news/video/471395907773/The-Feed-July-9',
        'only_matching': True,
    }, {
        'url': 'https://www.sbs.com.au/ondemand/?play=1836638787723',
        'only_matching': True,
    }, {
        'url': 'https://www.sbs.com.au/ondemand/program/inside-windsor-castle?play=1283505731842',
        'only_matching': True,
    }, {
        'url': 'https://www.sbs.com.au/news/embeds/video/1840778819866',
        'only_matching': True,
    }, {
        'url': 'https://www.sbs.com.au/ondemand/watch/1698704451971',
        'only_matching': True,
    }, {
        'url': 'https://www.sbs.com.au/ondemand/movie/coherence/1469404227931',
        'only_matching': True,
    }, {
        'note': 'Live stream',
        'url': 'https://www.sbs.com.au/ondemand/video/1726824003663/sbs-24x7-live-stream-nsw',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        player_params = self._download_json(
            'http://www.sbs.com.au/api/video_pdkvars/id/%s?form=json' % video_id, video_id)

        error = player_params.get('error')
        if error:
            error_message = 'Sorry, The video you are looking for does not exist.'
            video_data = error.get('results') or {}
            error_code = error.get('errorCode')
            if error_code == 'ComingSoon':
                error_message = '%s is not yet available.' % video_data.get('title', '')
            elif error_code in ('Forbidden', 'intranetAccessOnly'):
                error_message = 'Sorry, This video cannot be accessed via this website'
            elif error_code == 'Expired':
                error_message = 'Sorry, %s is no longer available.' % video_data.get('title', '')
            raise ExtractorError('%s said: %s' % (self.IE_NAME, error_message), expected=True)

        urls = player_params['releaseUrls']
        theplatform_url = (urls.get('progressive') or urls.get('html')
                           or urls.get('standard') or player_params['relatedItemsURL'])

        return {
            '_type': 'url_transparent',
            'ie_key': 'ThePlatform',
            'id': video_id,
            'url': smuggle_url(self._proto_relative_url(theplatform_url), {'force_smil_url': True}),
            'is_live': player_params.get('streamType') == 'live',
        }


code:
import urllib.request

from .common import InfoExtractor
from ..compat import compat_parse_qs
from ..utils import ExtractorError

class ScreencastIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?screencast\.com/t/(?P<id>[a-zA-Z0-9]+)'
    _TESTS = [{
        'url': 'http://www.screencast.com/t/3ZEjQXlT',
        'md5': '917df1c13798a3e96211dd1561fded83',
        'info_dict': {
            'id': '3ZEjQXlT',
            'ext': 'm4v',
            'title': 'Color Measurement with Ocean Optics Spectrometers',
            'description': 'md5:240369cde69d8bed61349a199c5fb153',
            'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
        }
    }, {
        'url': 'http://www.screencast.com/t/V2uXehPJa1ZI',
        'md5': 'e8e4b375a7660a9e7e35c33973410d34',
        'info_dict': {
            'id': 'V2uXehPJa1ZI',
            'ext': 'mov',
            'title': 'The Amadeus Spectrometer',
            'description': 're:^In this video, our friends at.*To learn more about Amadeus, visit',
            'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
        }
    }, {
        'url': 'http://www.screencast.com/t/aAB3iowa',
        'md5': 'dedb2734ed00c9755761ccaee88527cd',
        'info_dict': {
            'id': 'aAB3iowa',
            'ext': 'mp4',
            'title': 'Google Earth Export',
            'description': 'Provides a demo of a CommunityViz export to Google Earth, one of the 3D viewing options.',
            'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
        }
    }, {
        'url': 'http://www.screencast.com/t/X3ddTrYh',
        'md5': '669ee55ff9c51988b4ebc0877cc8b159',
        'info_dict': {
            'id': 'X3ddTrYh',
            'ext': 'wmv',
            'title': 'Toolkit 6 User Group Webinar (2014-03-04) - Default Judgment and First Impression',
            'description': 'md5:7b9f393bc92af02326a5c5889639eab0',
            'thumbnail': r're:^https?://.*\.(?:gif|jpg)$',
        }
    }, {
        'url': 'http://screencast.com/t/aAB3iowa',
        'only_matching': True,
    }]

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

        video_url = self._html_search_regex(
            r'<embed name="Video".*?src="([^"]+)"', webpage,
            'QuickTime embed', default=None)

        if video_url is None:
            flash_vars_s = self._html_search_regex(
                r'<param name="flashVars" value="([^"]+)"', webpage, 'flash vars',
                default=None)
            if not flash_vars_s:
                flash_vars_s = self._html_search_regex(
                    r'<param name="initParams" value="([^"]+)"', webpage, 'flash vars',
                    default=None)
                if flash_vars_s:
                    flash_vars_s = flash_vars_s.replace(',', '&')
            if flash_vars_s:
                flash_vars = compat_parse_qs(flash_vars_s)
                video_url_raw = urllib.request.quote(
                    flash_vars['content'][0])
                video_url = video_url_raw.replace('http%3A', 'http:')

        if video_url is None:
            video_meta = self._html_search_meta(
                'og:video', webpage, default=None)
            if video_meta:
                video_url = self._search_regex(
                    r'src=(.*?)(?:$|&)', video_meta,
                    'meta tag video URL', default=None)

        if video_url is None:
            video_url = self._html_search_regex(
                r'MediaContentUrl["\']\s*:(["\'])(?P<url>(?:(?!\1).)+)\1',
                webpage, 'video url', default=None, group='url')

        if video_url is None:
            video_url = self._html_search_meta(
                'og:video', webpage, default=None)

        if video_url is None:
            raise ExtractorError('Cannot find video')

        title = self._og_search_title(webpage, default=None)
        if title is None:
            title = self._html_search_regex(
                [r'Title: ([^<]+)
',
                 r'class="tabSeperator">>(.+?)<',
                 r'<title>([^<]+)</title>'],
                webpage, 'title')
        thumbnail = self._og_search_thumbnail(webpage)
        description = self._og_search_description(webpage, default=None)
        if description is None:
            description = self._html_search_meta('description', webpage)

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


code:
from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    get_element_by_class,
    int_or_none,
    remove_start,
    strip_or_none,
    unified_strdate,
    urlencode_postdata,
)

class ScreencastOMaticIE(InfoExtractor):
    _VALID_URL = r'https?://screencast-o-matic\.com/(?:(?:watch|player)/|embed\?.*?\bsc=)(?P<id>[0-9a-zA-Z]+)'
    _TESTS = [{
        'url': 'http://screencast-o-matic.com/watch/c2lD3BeOPl',
        'md5': '483583cb80d92588f15ccbedd90f0c18',
        'info_dict': {
            'id': 'c2lD3BeOPl',
            'ext': 'mp4',
            'title': 'Welcome to 3-4 Philosophy @ DECV!',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'as the title says! also: some general info re 1) VCE philosophy and 2) distance learning.',
            'duration': 369,
            'upload_date': '20141216',
        }
    }, {
        'url': 'http://screencast-o-matic.com/player/c2lD3BeOPl',
        'only_matching': True,
    }, {
        'url': 'http://screencast-o-matic.com/embed?ff=true&sc=cbV2r4Q5TL&fromPH=true&a=1',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(
            'https://screencast-o-matic.com/player/' + video_id, video_id)

        if (self._html_extract_title(webpage) == 'Protected Content'
                or 'This video is private and requires a password' in webpage):
            password = self.get_param('videopassword')

            if not password:
                raise ExtractorError('Password protected video, use --video-password <password>', expected=True)

            form = self._search_regex(
                r'(?is)<form[^>]*>(?P<form>.+?)</form>', webpage, 'login form', group='form')
            form_data = self._hidden_inputs(form)
            form_data.update({
                'scPassword': password,
            })

            webpage = self._download_webpage(
                'https://screencast-o-matic.com/player/password', video_id, 'Logging in',
                data=urlencode_postdata(form_data))

            if '<small class="text-danger">Invalid password</small>' in webpage:
                raise ExtractorError('Unable to login: Invalid password', expected=True)

        info = self._parse_html5_media_entries(url, webpage, video_id)[0]
        info.update({
            'id': video_id,
            'title': get_element_by_class('overlayTitle', webpage),
            'description': strip_or_none(get_element_by_class('overlayDescription', webpage)) or None,
            'duration': int_or_none(self._search_regex(
                r'player\.duration\s*=\s*function\(\)\s*{\s*return\s+(\d+);\s*};',
                webpage, 'duration', default=None)),
            'upload_date': unified_strdate(remove_start(
                get_element_by_class('overlayPublished', webpage), 'Published: ')),
        })
        return info


code:
import json
import hashlib

from .aws import AWSIE
from .anvato import AnvatoIE
from .common import InfoExtractor
from ..utils import (
    smuggle_url,
    urlencode_postdata,
    xpath_text,
)

class ScrippsNetworksWatchIE(AWSIE):
    IE_NAME = 'scrippsnetworks:watch'
    _VALID_URL = r'''(?x)
                    https?://
                        watch\.
                        (?P<site>geniuskitchen)\.com/
                        (?:
                            player\.[A-Z0-9]+\.html\#|
                            show/(?:[^/]+/){2}|
                            player/
                        )
                        (?P<id>\d+)
                    '''
    _TESTS = [{
        'url': 'http://watch.geniuskitchen.com/player/3787617/Ample-Hills-Ice-Cream-Bike/',
        'info_dict': {
            'id': '4194875',
            'ext': 'mp4',
            'title': 'Ample Hills Ice Cream Bike',
            'description': 'Courtney Rada churns up a signature GK Now ice cream with The Scoopmaster.',
            'uploader': 'ANV',
            'upload_date': '20171011',
            'timestamp': 1507698000,
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': [AnvatoIE.ie_key()],
    }]

    _SNI_TABLE = {
        'geniuskitchen': 'genius',
    }

    _AWS_API_KEY = 'E7wSQmq0qK6xPrF13WmzKiHo4BQ7tip4pQcSXVl1'
    _AWS_PROXY_HOST = 'web.api.video.snidigital.com'

    _AWS_USER_AGENT = 'aws-sdk-js/2.80.0 callback'

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        site_id, video_id = mobj.group('site', 'id')

        aws_identity_id_json = json.dumps({
            'IdentityId': '%s:7655847c-0ae7-4d9b-80d6-56c062927eb3' % self._AWS_REGION
        }).encode('utf-8')
        token = self._download_json(
            'https://cognito-identity.%s.amazonaws.com/' % self._AWS_REGION, video_id,
            data=aws_identity_id_json,
            headers={
                'Accept': '*/*',
                'Content-Type': 'application/x-amz-json-1.1',
                'Referer': url,
                'X-Amz-Content-Sha256': hashlib.sha256(aws_identity_id_json).hexdigest(),
                'X-Amz-Target': 'AWSCognitoIdentityService.GetOpenIdToken',
                'X-Amz-User-Agent': self._AWS_USER_AGENT,
            })['Token']

        sts = self._download_xml(
            'https://sts.amazonaws.com/', video_id, data=urlencode_postdata({
                'Action': 'AssumeRoleWithWebIdentity',
                'RoleArn': 'arn:aws:iam::710330595350:role/Cognito_WebAPIUnauth_Role',
                'RoleSessionName': 'web-identity',
                'Version': '2011-06-15',
                'WebIdentityToken': token,
            }), headers={
                'Referer': url,
                'X-Amz-User-Agent': self._AWS_USER_AGENT,
                'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
            })

        def get(key):
            return xpath_text(
                sts, './/{https://sts.amazonaws.com/doc/2011-06-15/}%s' % key,
                fatal=True)

        mcp_id = self._aws_execute_api({
            'uri': '/1/web/brands/%s/episodes/scrid/%s' % (self._SNI_TABLE[site_id], video_id),
            'access_key': get('AccessKeyId'),
            'secret_key': get('SecretAccessKey'),
            'session_token': get('SessionToken'),
        }, video_id)['results'][0]['mcpId']

        return self.url_result(
            smuggle_url(
                'anvato:anvato_scripps_app_web_prod_0837996dbe373629133857ae9eb72e740424d80a:%s' % mcp_id,
                {'geo_countries': ['US']}),
            AnvatoIE.ie_key(), video_id=mcp_id)

class ScrippsNetworksIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?P<site>cookingchanneltv|discovery|(?:diy|food)network|hgtv|travelchannel)\.com/videos/[0-9a-z-]+-(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.cookingchanneltv.com/videos/the-best-of-the-best-0260338',
        'info_dict': {
            'id': '0260338',
            'ext': 'mp4',
            'title': 'The Best of the Best',
            'description': 'Catch a new episode of MasterChef Canada Tuedsay at 9/8c.',
            'timestamp': 1475678834,
            'upload_date': '20161005',
            'uploader': 'SCNI-SCND',
        },
        'add_ie': ['ThePlatform'],
    }, {
        'url': 'https://www.diynetwork.com/videos/diy-barnwood-tablet-stand-0265790',
        'only_matching': True,
    }, {
        'url': 'https://www.foodnetwork.com/videos/chocolate-strawberry-cake-roll-7524591',
        'only_matching': True,
    }, {
        'url': 'https://www.hgtv.com/videos/cookie-decorating-101-0301929',
        'only_matching': True,
    }, {
        'url': 'https://www.travelchannel.com/videos/two-climates-one-bag-5302184',
        'only_matching': True,
    }, {
        'url': 'https://www.discovery.com/videos/guardians-of-the-glades-cooking-with-tom-cobb-5578368',
        'only_matching': True,
    }]
    _ACCOUNT_MAP = {
        'cookingchanneltv': 2433005105,
        'discovery': 2706091867,
        'diynetwork': 2433004575,
        'foodnetwork': 2433005105,
        'hgtv': 2433004575,
        'travelchannel': 2433005739,
    }
    _TP_TEMPL = 'https://link.theplatform.com/s/ip77QC/media/guid/%d/%s?mbr=true'

    def _real_extract(self, url):
        site, guid = self._match_valid_url(url).groups()
        return self.url_result(smuggle_url(
            self._TP_TEMPL % (self._ACCOUNT_MAP[site], guid),
            {'force_smil_url': True}), 'ThePlatform', guid)


code:
import json

from .common import InfoExtractor
from ..utils import determine_ext, int_or_none

class ScrolllerIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?scrolller\.com/(?P<id>[\w-]+)'
    _TESTS = [{
        'url': 'https://scrolller.com/a-helping-hand-1k9pxikxkw',
        'info_dict': {
            'id': 'a-helping-hand-1k9pxikxkw',
            'ext': 'mp4',
            'thumbnail': 'https://zepto.scrolller.com/a-helping-hand-3ty9q8x094-540x960.jpg',
            'title': 'A helping hand',
            'age_limit': 0,
        }
    }, {
        'url': 'https://scrolller.com/tigers-chasing-a-drone-c5d1f2so6j',
        'info_dict': {
            'id': 'tigers-chasing-a-drone-c5d1f2so6j',
            'ext': 'mp4',
            'thumbnail': 'https://zepto.scrolller.com/tigers-chasing-a-drone-az9pkpguwe-540x303.jpg',
            'title': 'Tigers chasing a drone',
            'age_limit': 0,
        }
    }, {
        'url': 'https://scrolller.com/baby-rhino-smells-something-9chhugsv9p',
        'info_dict': {
            'id': 'baby-rhino-smells-something-9chhugsv9p',
            'ext': 'mp4',
            'thumbnail': 'https://atto.scrolller.com/hmm-whats-that-smell-bh54mf2c52-300x224.jpg',
            'title': 'Baby rhino smells something',
            'age_limit': 0,
        }
    }, {
        'url': 'https://scrolller.com/its-all-fun-and-games-cco8jjmoh7',
        'info_dict': {
            'id': 'its-all-fun-and-games-cco8jjmoh7',
            'ext': 'mp4',
            'thumbnail': 'https://atto.scrolller.com/its-all-fun-and-games-3amk9vg7m3-540x649.jpg',
            'title': 'It\'s all fun and games...',
            'age_limit': 0,
        }
    }, {
        'url': 'https://scrolller.com/may-the-force-be-with-you-octokuro-yeytg1fs7a',
        'info_dict': {
            'id': 'may-the-force-be-with-you-octokuro-yeytg1fs7a',
            'ext': 'mp4',
            'thumbnail': 'https://thumbs2.redgifs.com/DarkStarchyNautilus-poster.jpg',
            'title': 'May the force be with you (Octokuro)',
            'age_limit': 18,
        }
    }]

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

        query = {
            'query': '''{
                getSubredditPost(url:"/%s"){
                    id
                    title
                    isNsfw
                    mediaSources{
                        url
                        width
                        height
                    }
                }
            }''' % video_id
        }

        video_data = self._download_json(
            'https://api.scrolller.com/api/v2/graphql', video_id, data=json.dumps(query).encode(),
            headers={'Content-Type': 'application/json'})['data']['getSubredditPost']

        formats, thumbnails = [], []
        for source in video_data['mediaSources']:
            if determine_ext(source.get('url')) in ('jpg', 'png'):
                thumbnails.append({
                    'url': source['url'],
                    'width': int_or_none(source.get('width')),
                    'height': int_or_none(source.get('height')),
                })
            elif source.get('url'):
                formats.append({
                    'url': source['url'],
                    'width': int_or_none(source.get('width')),
                    'height': int_or_none(source.get('height')),
                })

        if not formats:
            self.raise_no_formats('There is no video.', expected=True, video_id=video_id)

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video_data.get('title'),
            'thumbnails': thumbnails,
            'formats': formats,
            'age_limit': 18 if video_data.get('isNsfw') else 0
        }


code:
import re

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

class SCTEBaseIE(InfoExtractor):
    _LOGIN_URL = 'https://www.scte.org/SCTE/Sign_In.aspx'
    _NETRC_MACHINE = 'scte'

    def _perform_login(self, username, password):
        login_popup = self._download_webpage(
            self._LOGIN_URL, None, 'Downloading login popup')

        def is_logged(webpage):
            return any(re.search(p, webpage) for p in (
                r'class=["\']welcome\b', r'>Sign Out<'))

        # already logged in
        if is_logged(login_popup):
            return

        login_form = self._hidden_inputs(login_popup)

        login_form.update({
            'ctl01$TemplateBody$WebPartManager1$gwpciNewContactSignInCommon$ciNewContactSignInCommon$signInUserName': username,
            'ctl01$TemplateBody$WebPartManager1$gwpciNewContactSignInCommon$ciNewContactSignInCommon$signInPassword': password,
            'ctl01$TemplateBody$WebPartManager1$gwpciNewContactSignInCommon$ciNewContactSignInCommon$RememberMe': 'on',
        })

        response = self._download_webpage(
            self._LOGIN_URL, None, 'Logging in',
            data=urlencode_postdata(login_form))

        if '|pageRedirect|' not in response and not is_logged(response):
            error = self._html_search_regex(
                r'(?s)<[^>]+class=["\']AsiError["\'][^>]*>(.+?)</',
                response, 'error message', default=None)
            if error:
                raise ExtractorError('Unable to login: %s' % error, expected=True)
            raise ExtractorError('Unable to log in')

class SCTEIE(SCTEBaseIE):
    _VALID_URL = r'https?://learning\.scte\.org/mod/scorm/view\.php?.*?\bid=(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://learning.scte.org/mod/scorm/view.php?id=31484',
        'info_dict': {
            'title': 'Introduction to DOCSIS Engineering Professional',
            'id': '31484',
        },
        'playlist_count': 5,
        'skip': 'Requires account credentials',
    }]

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

        webpage = self._download_webpage(url, video_id)

        title = self._search_regex(r'
(.+?)
', webpage, 'title')

        context_id = self._search_regex(r'context-(\d+)', webpage, video_id)
        content_base = 'https://learning.scte.org/pluginfile.php/%s/mod_scorm/content/8/' % context_id
        context = decode_packed_codes(self._download_webpage(
            '%smobile/data.js' % content_base, video_id))

        data = self._parse_xml(
            self._search_regex(
                r'CreateData\(\s*"(.+?)"', context, 'data').replace(r"\'", "'"),
            video_id)

        entries = []
        for asset in data.findall('.//asset'):
            asset_url = asset.get('url')
            if not asset_url or not asset_url.endswith('.mp4'):
                continue
            asset_id = self._search_regex(
                r'video_([^_]+)_', asset_url, 'asset id', default=None)
            if not asset_id:
                continue
            entries.append({
                'id': asset_id,
                'title': title,
                'url': content_base + asset_url,
            })

        return self.playlist_result(entries, video_id, title)

class SCTECourseIE(SCTEBaseIE):
    _VALID_URL = r'https?://learning\.scte\.org/(?:mod/sub)?course/view\.php?.*?\bid=(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://learning.scte.org/mod/subcourse/view.php?id=31491',
        'only_matching': True,
    }, {
        'url': 'https://learning.scte.org/course/view.php?id=3639',
        'only_matching': True,
    }, {
        'url': 'https://learning.scte.org/course/view.php?id=3073',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, course_id)

        title = self._search_regex(
            r'
(.+?)
', webpage, 'title', default=None)

        entries = []
        for mobj in re.finditer(
                r'''(?x)
                    ]+
                        href=(["\'])
                        (?P<url>
                            https?://learning\.scte\.org/mod/
                            (?P<kind>scorm|subcourse)/view\.php?(?:(?!\1).)*?
                            \bid=\d+
                        )
                    ''',
                webpage):
            item_url = mobj.group('url')
            if item_url == url:
                continue
            ie = (SCTEIE.ie_key() if mobj.group('kind') == 'scorm'
                  else SCTECourseIE.ie_key())
            entries.append(self.url_result(item_url, ie=ie))

        return self.playlist_result(entries, course_id, title)


code:
import re

from .common import InfoExtractor
from ..utils import (
    get_element_by_class,
    strip_or_none,
)

class SeekerIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?seeker\.com/(?P<display_id>.*)-(?P<article_id>\d+)\.html'
    _TESTS = [{
        'url': 'http://www.seeker.com/should-trump-be-required-to-release-his-tax-returns-1833805621.html',
        'md5': '897d44bbe0d8986a2ead96de565a92db',
        'info_dict': {
            'id': 'Elrn3gnY',
            'ext': 'mp4',
            'title': 'Should Trump Be Required To Release His Tax Returns?',
            'description': 'md5:41efa8cfa8d627841045eec7b018eb45',
            'timestamp': 1490090165,
            'upload_date': '20170321',
        }
    }, {
        'url': 'http://www.seeker.com/changes-expected-at-zoos-following-recent-gorilla-lion-shootings-1834116536.html',
        'playlist': [
            {
                'md5': '0497b9f20495174be73ae136949707d2',
                'info_dict': {
                    'id': 'FihYQ8AE',
                    'ext': 'mp4',
                    'title': 'The Pros & Cons Of Zoos',
                    'description': 'md5:d88f99a8ea8e7d25e6ff77f271b1271c',
                    'timestamp': 1490039133,
                    'upload_date': '20170320',
                },
            }
        ],
        'info_dict': {
            'id': '1834116536',
            'title': 'After Gorilla Killing, Changes Ahead for Zoos',
            'description': 'The largest association of zoos and others are hoping to learn from recent incidents that led to the shooting deaths of a gorilla and two lions.',
        },
    }]

    def _real_extract(self, url):
        display_id, article_id = self._match_valid_url(url).groups()
        webpage = self._download_webpage(url, display_id)
        entries = []
        for jwp_id in re.findall(r'data-video-id="([a-zA-Z0-9]{8})"', webpage):
            entries.append(self.url_result(
                'jwplatform:' + jwp_id, 'JWPlatform', jwp_id))
        return self.playlist_result(
            entries, article_id,
            self._og_search_title(webpage),
            strip_or_none(get_element_by_class('subtitle__text', webpage)) or self._og_search_description(webpage))


code:
import re

from .common import InfoExtractor
from ..compat import (
    compat_parse_qs,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    parse_qs,
    unsmuggle_url,
)

_COMMITTEES = {
    'ag': ('76440', 'http://ag-f.akamaihd.net'),
    'aging': ('76442', 'http://aging-f.akamaihd.net'),
    'approps': ('76441', 'http://approps-f.akamaihd.net'),
    'arch': ('', 'http://ussenate-f.akamaihd.net'),
    'armed': ('76445', 'http://armed-f.akamaihd.net'),
    'banking': ('76446', 'http://banking-f.akamaihd.net'),
    'budget': ('76447', 'http://budget-f.akamaihd.net'),
    'cecc': ('76486', 'http://srs-f.akamaihd.net'),
    'commerce': ('80177', 'http://commerce1-f.akamaihd.net'),
    'csce': ('75229', 'http://srs-f.akamaihd.net'),
    'dpc': ('76590', 'http://dpc-f.akamaihd.net'),
    'energy': ('76448', 'http://energy-f.akamaihd.net'),
    'epw': ('76478', 'http://epw-f.akamaihd.net'),
    'ethics': ('76449', 'http://ethics-f.akamaihd.net'),
    'finance': ('76450', 'http://finance-f.akamaihd.net'),
    'foreign': ('76451', 'http://foreign-f.akamaihd.net'),
    'govtaff': ('76453', 'http://govtaff-f.akamaihd.net'),
    'help': ('76452', 'http://help-f.akamaihd.net'),
    'indian': ('76455', 'http://indian-f.akamaihd.net'),
    'intel': ('76456', 'http://intel-f.akamaihd.net'),
    'intlnarc': ('76457', 'http://intlnarc-f.akamaihd.net'),
    'jccic': ('85180', 'http://jccic-f.akamaihd.net'),
    'jec': ('76458', 'http://jec-f.akamaihd.net'),
    'judiciary': ('76459', 'http://judiciary-f.akamaihd.net'),
    'rpc': ('76591', 'http://rpc-f.akamaihd.net'),
    'rules': ('76460', 'http://rules-f.akamaihd.net'),
    'saa': ('76489', 'http://srs-f.akamaihd.net'),
    'smbiz': ('76461', 'http://smbiz-f.akamaihd.net'),
    'srs': ('75229', 'http://srs-f.akamaihd.net'),
    'uscc': ('76487', 'http://srs-f.akamaihd.net'),
    'vetaff': ('76462', 'http://vetaff-f.akamaihd.net'),
}

class SenateISVPIE(InfoExtractor):
    _IE_NAME = 'senate.gov:isvp'
    _VALID_URL = r'https?://(?:www\.)?senate\.gov/isvp/?\?(?P<qs>.+)'
    _EMBED_REGEX = [r"<iframe[^>]+src=['\"](?P<url>https?://www\.senate\.gov/isvp/?\?[^'\"]+)['\"]"]

    _TESTS = [{
        'url': 'http://www.senate.gov/isvp/?comm=judiciary&type=live&stt=&filename=judiciary031715&auto_play=false&wmode=transparent&poster=http%3A%2F%2Fwww.judiciary.senate.gov%2Fthemes%2Fjudiciary%2Fimages%2Fvideo-poster-flash-fit.png',
        'info_dict': {
            'id': 'judiciary031715',
            'ext': 'mp4',
            'title': 'Integrated Senate Video Player',
            'thumbnail': r're:^https?://.*\.(?:jpg|png)$',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'http://www.senate.gov/isvp/?type=live&comm=commerce&filename=commerce011514.mp4&auto_play=false',
        'info_dict': {
            'id': 'commerce011514',
            'ext': 'mp4',
            'title': 'Integrated Senate Video Player'
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'http://www.senate.gov/isvp/?type=arch&comm=intel&filename=intel090613&hc_location=ufi',
        # checksum differs each time
        'info_dict': {
            'id': 'intel090613',
            'ext': 'mp4',
            'title': 'Integrated Senate Video Player'
        }
    }, {
        # From http://www.c-span.org/video/?96791-1
        'url': 'http://www.senate.gov/isvp?type=live&comm=banking&filename=banking012715',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        url, smuggled_data = unsmuggle_url(url, {})

        qs = compat_parse_qs(self._match_valid_url(url).group('qs'))
        if not qs.get('filename') or not qs.get('type') or not qs.get('comm'):
            raise ExtractorError('Invalid URL', expected=True)

        video_id = re.sub(r'.mp4$', '', qs['filename'][0])

        webpage = self._download_webpage(url, video_id)

        if smuggled_data.get('force_title'):
            title = smuggled_data['force_title']
        else:
            title = self._html_extract_title(webpage)
        poster = qs.get('poster')
        thumbnail = poster[0] if poster else None

        video_type = qs['type'][0]
        committee = video_type if video_type == 'arch' else qs['comm'][0]

        stream_num, domain = _COMMITTEES[committee]

        formats = []
        if video_type == 'arch':
            filename = video_id if '.' in video_id else video_id + '.mp4'
            m3u8_url = compat_urlparse.urljoin(domain, 'i/' + filename + '/master.m3u8')
            formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', m3u8_id='m3u8')
        else:
            hdcore_sign = 'hdcore=3.1.0'
            url_params = (domain, video_id, stream_num)
            f4m_url = f'%s/z/%s_1@%s/manifest.f4m?{hdcore_sign}' % url_params
            m3u8_url = '%s/i/%s_1@%s/master.m3u8' % url_params
            for entry in self._extract_f4m_formats(f4m_url, video_id, f4m_id='f4m'):
                # URLs without the extra param induce an 404 error
                entry.update({'extra_param_to_segment_url': hdcore_sign})
                formats.append(entry)
            for entry in self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4', m3u8_id='m3u8'):
                mobj = re.search(r'(?P<tag>(?:-p|-b)).m3u8', entry['url'])
                if mobj:
                    entry['format_id'] += mobj.group('tag')
                formats.append(entry)

            self._sort_formats(formats)

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

class SenateGovIE(InfoExtractor):
    _IE_NAME = 'senate.gov'
    _VALID_URL = r'https?:\/\/(?:www\.)?(help|appropriations|judiciary|banking|armed-services|finance)\.senate\.gov'
    _TESTS = [{
        'url': 'https://www.help.senate.gov/hearings/vaccines-saving-lives-ensuring-confidence-and-protecting-public-health',
        'info_dict': {
            'id': 'help090920',
            'display_id': 'vaccines-saving-lives-ensuring-confidence-and-protecting-public-health',
            'title': 'Vaccines: Saving Lives, Ensuring Confidence, and Protecting Public Health',
            'description': 'The U.S. Senate Committee on Health, Education, Labor & Pensions',
            'ext': 'mp4',
        },
        'params': {'skip_download': 'm3u8'},
    }, {
        'url': 'https://www.appropriations.senate.gov/hearings/watch?hearingid=B8A25434-5056-A066-6020-1F68CB75F0CD',
        'info_dict': {
            'id': 'appropsA051518',
            'display_id': 'watch?hearingid=B8A25434-5056-A066-6020-1F68CB75F0CD',
            'title': 'Review of the FY2019 Budget Request for the U.S. Army',
            'ext': 'mp4',
        },
        'params': {'skip_download': 'm3u8'},
    }, {
        'url': 'https://www.banking.senate.gov/hearings/21st-century-communities-public-transportation-infrastructure-investment-and-fast-act-reauthorization',
        'info_dict': {
            'id': 'banking041521',
            'display_id': '21st-century-communities-public-transportation-infrastructure-investment-and-fast-act-reauthorization',
            'title': '21st Century Communities: Public Transportation Infrastructure Investment and FAST Act Reauthorization',
            'description': 'The Official website of The United States Committee on Banking, Housing, and Urban Affairs',
            'ext': 'mp4',
        },
        'params': {'skip_download': 'm3u8'},
    }]

    def _real_extract(self, url):
        display_id = self._generic_id(url)
        webpage = self._download_webpage(url, display_id)
        parse_info = parse_qs(self._search_regex(
            r'<iframe class="[^>"]*streaminghearing[^>"]*"\s[^>]*\bsrc="([^">]*)', webpage, 'hearing URL'))

        stream_num, stream_domain = _COMMITTEES[parse_info['comm'][-1]]
        filename = parse_info['filename'][-1]

        formats = self._extract_m3u8_formats(
            f'{stream_domain}/i/{filename}_1@{stream_num}/master.m3u8',
            display_id, ext='mp4')
        self._sort_formats(formats)

        title = self._html_search_regex(
            (*self._og_regexes('title'), r'(?s)<title>([^<]*?)</title>'), webpage, 'video title')

        return {
            'id': re.sub(r'.mp4$', '', filename),
            'display_id': display_id,
            'title': re.sub(r'\s+', ' ', title.split('|')[0]).strip(),
            'description': self._og_search_description(webpage, default=None),
            'thumbnail': self._og_search_thumbnail(webpage, default=None),
            'age_limit': self._rta_search(webpage),
            'formats': formats
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    float_or_none,
    parse_iso8601,
    update_url_query,
    int_or_none,
    determine_protocol,
    unescapeHTML,
)

class SendtoNewsIE(InfoExtractor):
    _VALID_URL = r'https?://embed\.sendtonews\.com/player2/embedplayer\.php\?.*\bSC=(?P<id>[0-9A-Za-z-]+)'

    _TEST = {
        # From http://cleveland.cbslocal.com/2016/05/16/indians-score-season-high-15-runs-in-blowout-win-over-reds-rapid-reaction/
        'url': 'http://embed.sendtonews.com/player2/embedplayer.php?SC=GxfCe0Zo7D-175909-5588&type=single&autoplay=on&sound=YES',
        'info_dict': {
            'id': 'GxfCe0Zo7D-175909-5588'
        },
        'playlist_count': 8,
        # test the first video only to prevent lengthy tests
        'playlist': [{
            'info_dict': {
                'id': '240385',
                'ext': 'mp4',
                'title': 'Indians introduce Encarnacion',
                'description': 'Indians president of baseball operations Chris Antonetti and Edwin Encarnacion discuss the slugger\'s three-year contract with Cleveland',
                'duration': 137.898,
                'thumbnail': r're:https?://.*\.jpg$',
                'upload_date': '20170105',
                'timestamp': 1483649762,
            },
        }],
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }

    _URL_TEMPLATE = '//embed.sendtonews.com/player2/embedplayer.php?SC=%s'

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        mobj = re.search(r'''(?x)<script[^>]+src=([\'"])
            (?:https?:)?//embed\.sendtonews\.com/player/responsiveembed\.php\?
                .*\bSC=(?P<SC>[0-9a-zA-Z-]+).*
            \1>''', webpage)
        if mobj:
            sc = mobj.group('SC')
            yield cls._URL_TEMPLATE % sc

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

        data_url = update_url_query(
            url.replace('embedplayer.php', 'data_read.php'),
            {'cmd': 'loadInitial'})
        playlist_data = self._download_json(data_url, playlist_id)

        entries = []
        for video in playlist_data['playlistData'][0]:
            info_dict = self._parse_jwplayer_data(
                video['jwconfiguration'],
                require_title=False, m3u8_id='hls', rtmp_params={'no_resume': True})

            for f in info_dict['formats']:
                if f.get('tbr'):
                    continue
                tbr = int_or_none(self._search_regex(
                    r'/(\d+)k/', f['url'], 'bitrate', default=None))
                if not tbr:
                    continue
                f.update({
                    'format_id': '%s-%d' % (determine_protocol(f), tbr),
                    'tbr': tbr,
                })
            # 'tbr' was explicitly set to be preferred over 'height' originally,
            # So this is being kept unless someone can confirm this is unnecessary
            self._sort_formats(info_dict['formats'], ('tbr', 'res'))

            thumbnails = []
            if video.get('thumbnailUrl'):
                thumbnails.append({
                    'id': 'normal',
                    'url': video['thumbnailUrl'],
                })
            if video.get('smThumbnailUrl'):
                thumbnails.append({
                    'id': 'small',
                    'url': video['smThumbnailUrl'],
                })
            info_dict.update({
                'title': video['S_headLine'].strip(),
                'description': unescapeHTML(video.get('S_fullStory')),
                'thumbnails': thumbnails,
                'duration': float_or_none(video.get('SM_length')),
                'timestamp': parse_iso8601(video.get('S_sysDate'), delimiter=' '),
            })
            entries.append(info_dict)

        return self.playlist_result(entries, playlist_id)


code:
from .common import InfoExtractor
from ..utils import (
    determine_ext,
    float_or_none,
    int_or_none,
    unified_timestamp,
    urlencode_postdata,
    url_or_none,
)

class ServusIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:www\.)?
                        (?:
                            servus\.com/(?:(?:at|de)/p/[^/]+|tv/videos)|
                            (?:servustv|pm-wissen)\.com/videos
                        )
                        /(?P<id>[aA]{2}-\w+|\d+-\d+)
                    '''
    _TESTS = [{
        # new URL schema
        'url': 'https://www.servustv.com/videos/aa-1t6vbu5pw1w12/',
        'md5': '60474d4c21f3eb148838f215c37f02b9',
        'info_dict': {
            'id': 'AA-1T6VBU5PW1W12',
            'ext': 'mp4',
            'title': 'Die Grünen aus Sicht des Volkes',
            'alt_title': 'Talk im Hangar-7 Voxpops Gruene',
            'description': 'md5:1247204d85783afe3682644398ff2ec4',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 62.442,
            'timestamp': 1605193976,
            'upload_date': '20201112',
            'series': 'Talk im Hangar-7',
            'season': 'Season 9',
            'season_number': 9,
            'episode': 'Episode 31 - September 14',
            'episode_number': 31,
        }
    }, {
        # old URL schema
        'url': 'https://www.servus.com/de/p/Die-Gr%C3%BCnen-aus-Sicht-des-Volkes/AA-1T6VBU5PW1W12/',
        'only_matching': True,
    }, {
        'url': 'https://www.servus.com/at/p/Wie-das-Leben-beginnt/1309984137314-381415152/',
        'only_matching': True,
    }, {
        'url': 'https://www.servus.com/tv/videos/aa-1t6vbu5pw1w12/',
        'only_matching': True,
    }, {
        'url': 'https://www.servus.com/tv/videos/1380889096408-1235196658/',
        'only_matching': True,
    }, {
        'url': 'https://www.pm-wissen.com/videos/aa-24mus4g2w2112/',
        'only_matching': True,
    }]

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

        token = self._download_json(
            'https://auth.redbullmediahouse.com/token', video_id,
            'Downloading token', data=urlencode_postdata({
                'grant_type': 'client_credentials',
            }), headers={
                'Authorization': 'Basic SVgtMjJYNEhBNFdEM1cxMTpEdDRVSkFLd2ZOMG5IMjB1NGFBWTBmUFpDNlpoQ1EzNA==',
            })
        access_token = token['access_token']
        token_type = token.get('token_type', 'Bearer')

        video = self._download_json(
            'https://sparkle-api.liiift.io/api/v1/stv/channels/international/assets/%s' % video_id,
            video_id, 'Downloading video JSON', headers={
                'Authorization': '%s %s' % (token_type, access_token),
            })

        formats = []
        thumbnail = None
        for resource in video['resources']:
            if not isinstance(resource, dict):
                continue
            format_url = url_or_none(resource.get('url'))
            if not format_url:
                continue
            extension = resource.get('extension')
            type_ = resource.get('type')
            if extension == 'jpg' or type_ == 'reference_keyframe':
                thumbnail = format_url
                continue
            ext = determine_ext(format_url)
            if type_ == 'dash' or ext == 'mpd':
                formats.extend(self._extract_mpd_formats(
                    format_url, video_id, mpd_id='dash', fatal=False))
            elif type_ == 'hls' or ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    format_url, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            elif extension == 'mp4' or ext == 'mp4':
                formats.append({
                    'url': format_url,
                    'format_id': type_,
                    'width': int_or_none(resource.get('width')),
                    'height': int_or_none(resource.get('height')),
                })
        self._sort_formats(formats)

        attrs = {}
        for attribute in video['attributes']:
            if not isinstance(attribute, dict):
                continue
            key = attribute.get('fieldKey')
            value = attribute.get('fieldValue')
            if not key or not value:
                continue
            attrs[key] = value

        title = attrs.get('title_stv') or video_id
        alt_title = attrs.get('title')
        description = attrs.get('long_description') or attrs.get('short_description')
        series = attrs.get('label')
        season = attrs.get('season')
        episode = attrs.get('chapter')
        duration = float_or_none(attrs.get('duration'), scale=1000)
        season_number = int_or_none(self._search_regex(
            r'Season (\d+)', season or '', 'season number', default=None))
        episode_number = int_or_none(self._search_regex(
            r'Episode (\d+)', episode or '', 'episode number', default=None))

        return {
            'id': video_id,
            'title': title,
            'alt_title': alt_title,
            'description': description,
            'thumbnail': thumbnail,
            'duration': duration,
            'timestamp': unified_timestamp(video.get('lastPublished')),
            'series': series,
            'season': season,
            'season_number': season_number,
            'episode': episode,
            'episode_number': episode_number,
            'formats': formats,
        }


code:
import json
import re

from .brightcove import BrightcoveNewIE
from ..compat import (
    compat_HTTPError,
    compat_str,
)
from ..utils import (
    ExtractorError,
    try_get,
    update_url_query,
)

class SevenPlusIE(BrightcoveNewIE):
    IE_NAME = '7plus'
    _VALID_URL = r'https?://(?:www\.)?7plus\.com\.au/(?P<path>[^?]+\?.*?\bepisode-id=(?P<id>[^&#]+))'
    _TESTS = [{
        'url': 'https://7plus.com.au/MTYS?episode-id=MTYS7-003',
        'info_dict': {
            'id': 'MTYS7-003',
            'ext': 'mp4',
            'title': 'S7 E3 - Wind Surf',
            'description': 'md5:29c6a69f21accda7601278f81b46483d',
            'uploader_id': '5303576322001',
            'upload_date': '20171201',
            'timestamp': 1512106377,
            'series': 'Mighty Ships',
            'season_number': 7,
            'episode_number': 3,
            'episode': 'Wind Surf',
        },
        'params': {
            'skip_download': True,
        }
    }, {
        'url': 'https://7plus.com.au/UUUU?episode-id=AUMS43-001',
        'only_matching': True,
    }]

    def _real_initialize(self):
        self.token = None

        cookies = self._get_cookies('https://7plus.com.au')
        api_key = next((x for x in cookies if x.startswith('glt_')), '')[4:]
        if not api_key:  # Cookies are signed out, skip login
            return

        login_resp = self._download_json(
            'https://login.7plus.com.au/accounts.getJWT', None, 'Logging in', fatal=False,
            query={
                'APIKey': api_key,
                'sdk': 'js_latest',
                'login_token': cookies[f'glt_{api_key}'].value,
                'authMode': 'cookie',
                'pageURL': 'https://7plus.com.au/',
                'sdkBuild': '12471',
                'format': 'json',
            }) or {}

        if 'errorMessage' in login_resp:
            self.report_warning(f'Unable to login: 7plus said: {login_resp["errorMessage"]}')
            return
        id_token = login_resp.get('id_token')
        if not id_token:
            self.report_warning('Unable to login: Could not extract id token')
            return

        token_resp = self._download_json(
            'https://7plus.com.au/auth/token', None, 'Getting auth token', fatal=False,
            headers={'Content-Type': 'application/json'}, data=json.dumps({
                'idToken': id_token,
                'platformId': 'web',
                'regSource': '7plus',
            }).encode('utf-8')) or {}
        self.token = token_resp.get('token')
        if not self.token:
            self.report_warning('Unable to log in: Could not extract auth token')

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

        headers = {}
        if self.token:
            headers['Authorization'] = f'Bearer {self.token}'

        try:
            media = self._download_json(
                'https://videoservice.swm.digital/playback', episode_id, query={
                    'appId': '7plus',
                    'deviceType': 'web',
                    'platformType': 'web',
                    'accountId': 5303576322001,
                    'referenceId': 'ref:' + episode_id,
                    'deliveryId': 'csai',
                    'videoType': 'vod',
                }, headers=headers)['media']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                raise ExtractorError(self._parse_json(
                    e.cause.read().decode(), episode_id)[0]['error_code'], expected=True)
            raise

        for source in media.get('sources', {}):
            src = source.get('src')
            if not src:
                continue
            source['src'] = update_url_query(src, {'rule': ''})

        info = self._parse_brightcove_metadata(media, episode_id)

        content = self._download_json(
            'https://component-cdn.swm.digital/content/' + path,
            episode_id, headers={
                'market-id': 4,
            }, fatal=False) or {}
        for item in content.get('items', {}):
            if item.get('componentData', {}).get('componentType') == 'infoPanel':
                for src_key, dst_key in [('title', 'title'), ('shortSynopsis', 'description')]:
                    value = item.get(src_key)
                    if value:
                        info[dst_key] = value
                info['series'] = try_get(
                    item, lambda x: x['seriesLogo']['name'], compat_str)
                mobj = re.search(r'^S(\d+)\s+E(\d+)\s+-\s+(.+)$', info['title'])
                if mobj:
                    info.update({
                        'season_number': int(mobj.group(1)),
                        'episode_number': int(mobj.group(2)),
                        'episode': mobj.group(3),
                    })

        return info