code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    int_or_none,
    urlencode_postdata,
)

class LyndaBaseIE(InfoExtractor):
    _SIGNIN_URL = 'https://www.lynda.com/signin/lynda'
    _PASSWORD_URL = 'https://www.lynda.com/signin/password'
    _USER_URL = 'https://www.lynda.com/signin/user'
    _ACCOUNT_CREDENTIALS_HINT = 'Use --username and --password options to provide lynda.com account credentials.'
    _NETRC_MACHINE = 'lynda'

    def _real_initialize(self):
        self._login()

    @staticmethod
    def _check_error(json_string, key_or_keys):
        keys = [key_or_keys] if isinstance(key_or_keys, compat_str) else key_or_keys
        for key in keys:
            error = json_string.get(key)
            if error:
                raise ExtractorError('Unable to login: %s' % error, expected=True)

    def _login_step(self, form_html, fallback_action_url, extra_form_data, note, referrer_url):
        action_url = self._search_regex(
            r'<form[^>]+action=(["\'])(?P<url>.+?)\1', form_html,
            'post url', default=fallback_action_url, group='url')

        if not action_url.startswith('http'):
            action_url = compat_urlparse.urljoin(self._SIGNIN_URL, action_url)

        form_data = self._hidden_inputs(form_html)
        form_data.update(extra_form_data)

        response = self._download_json(
            action_url, None, note,
            data=urlencode_postdata(form_data),
            headers={
                'Referer': referrer_url,
                'X-Requested-With': 'XMLHttpRequest',
            }, expected_status=(418, 500, ))

        self._check_error(response, ('email', 'password', 'ErrorMessage'))

        return response, action_url

    def _login(self):
        username, password = self._get_login_info()
        if username is None:
            return

        # Step 1: download signin page
        signin_page = self._download_webpage(
            self._SIGNIN_URL, None, 'Downloading signin page')

        # Already logged in
        if any(re.search(p, signin_page) for p in (
                r'isLoggedIn\s*:\s*true', r'logout\.aspx', r'>Log out<')):
            return

        # Step 2: submit email
        signin_form = self._search_regex(
            r'(?s)(<form[^>]+data-form-name=["\']signin["\'][^>]*>.+?</form>)',
            signin_page, 'signin form')
        signin_page, signin_url = self._login_step(
            signin_form, self._PASSWORD_URL, {'email': username},
            'Submitting email', self._SIGNIN_URL)

        # Step 3: submit password
        password_form = signin_page['body']
        self._login_step(
            password_form, self._USER_URL, {'email': username, 'password': password},
            'Submitting password', signin_url)

class LyndaIE(LyndaBaseIE):
    IE_NAME = 'lynda'
    IE_DESC = 'lynda.com videos'
    _VALID_URL = r'''(?x)
                    https?://
                        (?:www\.)?(?:lynda\.com|educourse\.ga)/
                        (?:
                            (?:[^/]+/){2,3}(?P<course_id>\d+)|
                            player/embed
                        )/
                        (?P<id>\d+)
                    '''

    _TIMECODE_REGEX = r'\[(?P<timecode>\d+:\d+:\d+[\.,]\d+)\]'

    _TESTS = [{
        'url': 'https://www.lynda.com/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html',
        # md5 is unstable
        'info_dict': {
            'id': '114408',
            'ext': 'mp4',
            'title': 'Using the exercise files',
            'duration': 68
        }
    }, {
        'url': 'https://www.lynda.com/player/embed/133770?tr=foo=1;bar=g;fizz=rt&fs=0',
        'only_matching': True,
    }, {
        'url': 'https://educourse.ga/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html',
        'only_matching': True,
    }, {
        'url': 'https://www.lynda.com/de/Graphic-Design-tutorials/Willkommen-Grundlagen-guten-Gestaltung/393570/393572-4.html',
        'only_matching': True,
    }, {
        # Status="NotFound", Message="Transcript not found"
        'url': 'https://www.lynda.com/ASP-NET-tutorials/What-you-should-know/5034180/2811512-4.html',
        'only_matching': True,
    }]

    def _raise_unavailable(self, video_id):
        self.raise_login_required(
            'Video %s is only available for members' % video_id)

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id')
        course_id = mobj.group('course_id')

        query = {
            'videoId': video_id,
            'type': 'video',
        }

        video = self._download_json(
            'https://www.lynda.com/ajax/player', video_id,
            'Downloading video JSON', fatal=False, query=query)

        # Fallback scenario
        if not video:
            query['courseId'] = course_id

            play = self._download_json(
                'https://www.lynda.com/ajax/course/%s/%s/play'
                % (course_id, video_id), video_id, 'Downloading play JSON')

            if not play:
                self._raise_unavailable(video_id)

            formats = []
            for formats_dict in play:
                urls = formats_dict.get('urls')
                if not isinstance(urls, dict):
                    continue
                cdn = formats_dict.get('name')
                for format_id, format_url in urls.items():
                    if not format_url:
                        continue
                    formats.append({
                        'url': format_url,
                        'format_id': '%s-%s' % (cdn, format_id) if cdn else format_id,
                        'height': int_or_none(format_id),
                    })
            self._sort_formats(formats)

            conviva = self._download_json(
                'https://www.lynda.com/ajax/player/conviva', video_id,
                'Downloading conviva JSON', query=query)

            return {
                'id': video_id,
                'title': conviva['VideoTitle'],
                'description': conviva.get('VideoDescription'),
                'release_year': int_or_none(conviva.get('ReleaseYear')),
                'duration': int_or_none(conviva.get('Duration')),
                'creator': conviva.get('Author'),
                'formats': formats,
            }

        if 'Status' in video:
            raise ExtractorError(
                'lynda returned error: %s' % video['Message'], expected=True)

        if video.get('HasAccess') is False:
            self._raise_unavailable(video_id)

        video_id = compat_str(video.get('ID') or video_id)
        duration = int_or_none(video.get('DurationInSeconds'))
        title = video['Title']

        formats = []

        fmts = video.get('Formats')
        if fmts:
            formats.extend([{
                'url': f['Url'],
                'ext': f.get('Extension'),
                'width': int_or_none(f.get('Width')),
                'height': int_or_none(f.get('Height')),
                'filesize': int_or_none(f.get('FileSize')),
                'format_id': compat_str(f.get('Resolution')) if f.get('Resolution') else None,
            } for f in fmts if f.get('Url')])

        prioritized_streams = video.get('PrioritizedStreams')
        if prioritized_streams:
            for prioritized_stream_id, prioritized_stream in prioritized_streams.items():
                formats.extend([{
                    'url': video_url,
                    'height': int_or_none(format_id),
                    'format_id': '%s-%s' % (prioritized_stream_id, format_id),
                } for format_id, video_url in prioritized_stream.items()])

        self._check_formats(formats, video_id)
        self._sort_formats(formats)

        subtitles = self.extract_subtitles(video_id)

        return {
            'id': video_id,
            'title': title,
            'duration': duration,
            'subtitles': subtitles,
            'formats': formats
        }

    def _fix_subtitles(self, subs):
        srt = ''
        seq_counter = 0
        for pos in range(0, len(subs) - 1):
            seq_current = subs[pos]
            m_current = re.match(self._TIMECODE_REGEX, seq_current['Timecode'])
            if m_current is None:
                continue
            seq_next = subs[pos + 1]
            m_next = re.match(self._TIMECODE_REGEX, seq_next['Timecode'])
            if m_next is None:
                continue
            appear_time = m_current.group('timecode')
            disappear_time = m_next.group('timecode')
            text = seq_current['Caption'].strip()
            if text:
                seq_counter += 1
                srt += '%s\r\n%s --> %s\r\n%s\r\n\r\n' % (seq_counter, appear_time, disappear_time, text)
        if srt:
            return srt

    def _get_subtitles(self, video_id):
        url = 'https://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
        subs = self._download_webpage(
            url, video_id, 'Downloading subtitles JSON', fatal=False)
        if not subs or 'Status="NotFound"' in subs:
            return {}
        subs = self._parse_json(subs, video_id, fatal=False)
        if not subs:
            return {}
        fixed_subs = self._fix_subtitles(subs)
        if fixed_subs:
            return {'en': [{'ext': 'srt', 'data': fixed_subs}]}
        return {}

class LyndaCourseIE(LyndaBaseIE):
    IE_NAME = 'lynda:course'
    IE_DESC = 'lynda.com online courses'

    # Course link equals to welcome/introduction video link of same course
    # We will recognize it as course link
    _VALID_URL = r'https?://(?:www|m)\.(?:lynda\.com|educourse\.ga)/(?P<coursepath>(?:[^/]+/){2,3}(?P<courseid>\d+))-2\.html'

    _TESTS = [{
        'url': 'https://www.lynda.com/Graphic-Design-tutorials/Grundlagen-guten-Gestaltung/393570-2.html',
        'only_matching': True,
    }, {
        'url': 'https://www.lynda.com/de/Graphic-Design-tutorials/Grundlagen-guten-Gestaltung/393570-2.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        course_path = mobj.group('coursepath')
        course_id = mobj.group('courseid')

        item_template = 'https://www.lynda.com/%s/%%s-4.html' % course_path

        course = self._download_json(
            'https://www.lynda.com/ajax/player?courseId=%s&type=course' % course_id,
            course_id, 'Downloading course JSON', fatal=False)

        if not course:
            webpage = self._download_webpage(url, course_id)
            entries = [
                self.url_result(
                    item_template % video_id, ie=LyndaIE.ie_key(),
                    video_id=video_id)
                for video_id in re.findall(
                    r'data-video-id=["\'](\d+)', webpage)]
            return self.playlist_result(
                entries, course_id,
                self._og_search_title(webpage, fatal=False),
                self._og_search_description(webpage))

        if course.get('Status') == 'NotFound':
            raise ExtractorError(
                'Course %s does not exist' % course_id, expected=True)

        unaccessible_videos = 0
        entries = []

        # Might want to extract videos right here from video['Formats'] as it seems 'Formats' is not provided
        # by single video API anymore

        for chapter in course['Chapters']:
            for video in chapter.get('Videos', []):
                if video.get('HasAccess') is False:
                    unaccessible_videos += 1
                    continue
                video_id = video.get('ID')
                if video_id:
                    entries.append({
                        '_type': 'url_transparent',
                        'url': item_template % video_id,
                        'ie_key': LyndaIE.ie_key(),
                        'chapter': chapter.get('Title'),
                        'chapter_number': int_or_none(chapter.get('ChapterIndex')),
                        'chapter_id': compat_str(chapter.get('ID')),
                    })

        if unaccessible_videos > 0:
            self._downloader.report_warning(
                '%s videos are only available for members (or paid members) and will not be downloaded. '
                % unaccessible_videos + self._ACCOUNT_CREDENTIALS_HINT)

        course_title = course.get('Title')
        course_description = course.get('Description')

        return self.playlist_result(entries, course_id, course_title, course_description)


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor

class M6IE(InfoExtractor):
    IE_NAME = 'm6'
    _VALID_URL = r'https?://(?:www\.)?m6\.fr/[^/]+/videos/(?P<id>\d+)-[^\.]+\.html'

    _TEST = {
        'url': 'http://www.m6.fr/emission-les_reines_du_shopping/videos/11323908-emeline_est_la_reine_du_shopping_sur_le_theme_ma_fete_d_8217_anniversaire.html',
        'md5': '242994a87de2c316891428e0176bcb77',
        'info_dict': {
            'id': '11323908',
            'ext': 'mp4',
            'title': 'Emeline est la Reine du Shopping sur le thème « Ma fête d’anniversaire ! »',
            'description': 'md5:1212ae8fb4b7baa4dc3886c5676007c2',
            'duration': 100,
        }
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        return self.url_result('6play:%s' % video_id, 'SixPlay', video_id)

repo_name: rrosajp/youtube


code:
# coding: utf-8
from __future__ import unicode_literals

import itertools
import json
import re

from .common import InfoExtractor
from ..compat import compat_urllib_parse_unquote
from ..utils import (
    int_or_none,
    parse_duration,
    remove_end,
    try_get,
)

class MailRuIE(InfoExtractor):
    IE_NAME = 'mailru'
    IE_DESC = 'Видео@Mail.Ru'
    _VALID_URL = r'''(?x)
                    https?://
                        (?:(?:www|m)\.)?my\.mail\.ru/+
                        (?:
                            video/.*\#video=/?(?P<idv1>(?:[^/]+/){3}\d+)|
                            (?:(?P<idv2prefix>(?:[^/]+/+){2})video/(?P<idv2suffix>[^/]+/\d+))\.html|
                            (?:video/embed|\+/video/meta)/(?P<metaid>\d+)
                        )
                    '''
    _TESTS = [
        {
            'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76',
            'md5': 'dea205f03120046894db4ebb6159879a',
            'info_dict': {
                'id': '46301138_76',
                'ext': 'mp4',
                'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро',
                'timestamp': 1393235077,
                'upload_date': '20140224',
                'uploader': 'sonypicturesrus',
                'uploader_id': 'sonypicturesrus@mail.ru',
                'duration': 184,
            },
            'skip': 'Not accessible from Travis CI server',
        },
        {
            'url': 'http://my.mail.ru/corp/hitech/video/news_hi-tech_mail_ru/1263.html',
            'md5': '00a91a58c3402204dcced523777b475f',
            'info_dict': {
                'id': '46843144_1263',
                'ext': 'mp4',
                'title': 'Samsung Galaxy S5 Hammer Smash Fail Battery Explosion',
                'timestamp': 1397039888,
                'upload_date': '20140409',
                'uploader': 'hitech',
                'uploader_id': 'hitech@corp.mail.ru',
                'duration': 245,
            },
            'skip': 'Not accessible from Travis CI server',
        },
        {
            # only available via metaUrl API
            'url': 'http://my.mail.ru/mail/720pizle/video/_myvideo/502.html',
            'md5': '3b26d2491c6949d031a32b96bd97c096',
            'info_dict': {
                'id': '56664382_502',
                'ext': 'mp4',
                'title': ':8336',
                'timestamp': 1449094163,
                'upload_date': '20151202',
                'uploader': '720pizle@mail.ru',
                'uploader_id': '720pizle@mail.ru',
                'duration': 6001,
            },
            'skip': 'Not accessible from Travis CI server',
        },
        {
            'url': 'http://m.my.mail.ru/mail/3sktvtr/video/_myvideo/138.html',
            'only_matching': True,
        },
        {
            'url': 'https://my.mail.ru/video/embed/7949340477499637815',
            'only_matching': True,
        },
        {
            'url': 'http://my.mail.ru/+/video/meta/7949340477499637815',
            'only_matching': True,
        },
        {
            'url': 'https://my.mail.ru//list/sinyutin10/video/_myvideo/4.html',
            'only_matching': True,
        },
        {
            'url': 'https://my.mail.ru//list//sinyutin10/video/_myvideo/4.html',
            'only_matching': True,
        }
    ]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        meta_id = mobj.group('metaid')

        video_id = None
        if meta_id:
            meta_url = 'https://my.mail.ru/+/video/meta/%s' % meta_id
        else:
            video_id = mobj.group('idv1')
            if not video_id:
                video_id = mobj.group('idv2prefix') + mobj.group('idv2suffix')
            webpage = self._download_webpage(url, video_id)
            page_config = self._parse_json(self._search_regex(
                r'(?s)<script[^>]+class="sp-video__page-config"[^>]*>(.+?)</script>',
                webpage, 'page config', default='{}'), video_id, fatal=False)
            if page_config:
                meta_url = page_config.get('metaUrl') or page_config.get('video', {}).get('metaUrl')
            else:
                meta_url = None

        video_data = None
        if meta_url:
            video_data = self._download_json(
                meta_url, video_id or meta_id, 'Downloading video meta JSON',
                fatal=not video_id)

        # Fallback old approach
        if not video_data:
            video_data = self._download_json(
                'http://api.video.mail.ru/videos/%s.json?new=1' % video_id,
                video_id, 'Downloading video JSON')

        headers = {}

        video_key = self._get_cookies('https://my.mail.ru').get('video_key')
        if video_key:
            headers['Cookie'] = 'video_key=%s' % video_key.value

        formats = []
        for f in video_data['videos']:
            video_url = f.get('url')
            if not video_url:
                continue
            format_id = f.get('key')
            height = int_or_none(self._search_regex(
                r'^(\d+)[pP]$', format_id, 'height', default=None)) if format_id else None
            formats.append({
                'url': video_url,
                'format_id': format_id,
                'height': height,
                'http_headers': headers,
            })
        self._sort_formats(formats)

        meta_data = video_data['meta']
        title = remove_end(meta_data['title'], '.mp4')

        author = video_data.get('author')
        uploader = author.get('name')
        uploader_id = author.get('id') or author.get('email')
        view_count = int_or_none(video_data.get('viewsCount') or video_data.get('views_count'))

        acc_id = meta_data.get('accId')
        item_id = meta_data.get('itemId')
        content_id = '%s_%s' % (acc_id, item_id) if acc_id and item_id else video_id

        thumbnail = meta_data.get('poster')
        duration = int_or_none(meta_data.get('duration'))
        timestamp = int_or_none(meta_data.get('timestamp'))

        return {
            'id': content_id,
            'title': title,
            'thumbnail': thumbnail,
            'timestamp': timestamp,
            'uploader': uploader,
            'uploader_id': uploader_id,
            'duration': duration,
            'view_count': view_count,
            'formats': formats,
        }

class MailRuMusicSearchBaseIE(InfoExtractor):
    def _search(self, query, url, audio_id, limit=100, offset=0):
        search = self._download_json(
            'https://my.mail.ru/cgi-bin/my/ajax', audio_id,
            'Downloading songs JSON page %d' % (offset // limit + 1),
            headers={
                'Referer': url,
                'X-Requested-With': 'XMLHttpRequest',
            }, query={
                'xemail': '',
                'ajax_call': '1',
                'func_name': 'music.search',
                'mna': '',
                'mnb': '',
                'arg_query': query,
                'arg_extended': '1',
                'arg_search_params': json.dumps({
                    'music': {
                        'limit': limit,
                        'offset': offset,
                    },
                }),
                'arg_limit': limit,
                'arg_offset': offset,
            })
        return next(e for e in search if isinstance(e, dict))

    @staticmethod
    def _extract_track(t, fatal=True):
        audio_url = t['URL'] if fatal else t.get('URL')
        if not audio_url:
            return

        audio_id = t['File'] if fatal else t.get('File')
        if not audio_id:
            return

        thumbnail = t.get('AlbumCoverURL') or t.get('FiledAlbumCover')
        uploader = t.get('OwnerName') or t.get('OwnerName_Text_HTML')
        uploader_id = t.get('UploaderID')
        duration = int_or_none(t.get('DurationInSeconds')) or parse_duration(
            t.get('Duration') or t.get('DurationStr'))
        view_count = int_or_none(t.get('PlayCount') or t.get('PlayCount_hr'))

        track = t.get('Name') or t.get('Name_Text_HTML')
        artist = t.get('Author') or t.get('Author_Text_HTML')

        if track:
            title = '%s - %s' % (artist, track) if artist else track
        else:
            title = audio_id

        return {
            'extractor_key': MailRuMusicIE.ie_key(),
            'id': audio_id,
            'title': title,
            'thumbnail': thumbnail,
            'uploader': uploader,
            'uploader_id': uploader_id,
            'duration': duration,
            'view_count': view_count,
            'vcodec': 'none',
            'abr': int_or_none(t.get('BitRate')),
            'track': track,
            'artist': artist,
            'album': t.get('Album'),
            'url': audio_url,
        }

class MailRuMusicIE(MailRuMusicSearchBaseIE):
    IE_NAME = 'mailru:music'
    IE_DESC = 'Музыка@Mail.Ru'
    _VALID_URL = r'https?://my\.mail\.ru/+music/+songs/+[^/?#&]+-(?P<id>[\da-f]+)'
    _TESTS = [{
        'url': 'https://my.mail.ru/music/songs/%D0%BC8%D0%BB8%D1%82%D1%85-l-a-h-luciferian-aesthetics-of-herrschaft-single-2017-4e31f7125d0dfaef505d947642366893',
        'md5': '0f8c22ef8c5d665b13ac709e63025610',
        'info_dict': {
            'id': '4e31f7125d0dfaef505d947642366893',
            'ext': 'mp3',
            'title': 'L.A.H. (Luciferian Aesthetics of Herrschaft) single, 2017 - М8Л8ТХ',
            'uploader': 'Игорь Мудрый',
            'uploader_id': '1459196328',
            'duration': 280,
            'view_count': int,
            'vcodec': 'none',
            'abr': 320,
            'track': 'L.A.H. (Luciferian Aesthetics of Herrschaft) single, 2017',
            'artist': 'М8Л8ТХ',
        },
    }]

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

        webpage = self._download_webpage(url, audio_id)

        title = self._og_search_title(webpage)
        music_data = self._search(title, url, audio_id)['MusicData']
        t = next(t for t in music_data if t.get('File') == audio_id)

        info = self._extract_track(t)
        info['title'] = title
        return info

class MailRuMusicSearchIE(MailRuMusicSearchBaseIE):
    IE_NAME = 'mailru:music:search'
    IE_DESC = 'Музыка@Mail.Ru'
    _VALID_URL = r'https?://my\.mail\.ru/+music/+search/+(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://my.mail.ru/music/search/black%20shadow',
        'info_dict': {
            'id': 'black shadow',
        },
        'playlist_mincount': 532,
    }]

    def _real_extract(self, url):
        query = compat_urllib_parse_unquote(self._match_id(url))

        entries = []

        LIMIT = 100
        offset = 0

        for _ in itertools.count(1):
            search = self._search(query, url, query, LIMIT, offset)

            music_data = search.get('MusicData')
            if not music_data or not isinstance(music_data, list):
                break

            for t in music_data:
                track = self._extract_track(t, fatal=False)
                if track:
                    entries.append(track)

            total = try_get(
                search, lambda x: x['Results']['music']['Total'], int)

            if total is not None:
                if offset > total:
                    break

            offset += LIMIT

        return self.playlist_result(entries, query)


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import merge_dicts

class MallTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:www|sk)\.)?mall\.tv/(?:[^/]+/)*(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.mall.tv/18-miliard-pro-neziskovky-opravdu-jsou-sportovci-nebo-clovek-v-tisni-pijavice',
        'md5': '1c4a37f080e1f3023103a7b43458e518',
        'info_dict': {
            'id': 't0zzt0',
            'display_id': '18-miliard-pro-neziskovky-opravdu-jsou-sportovci-nebo-clovek-v-tisni-pijavice',
            'ext': 'mp4',
            'title': '18 miliard pro neziskovky. Opravdu jsou sportovci nebo Člověk v tísni pijavice?',
            'description': 'md5:25fc0ec42a72ba602b602c683fa29deb',
            'duration': 216,
            'timestamp': 1538870400,
            'upload_date': '20181007',
            'view_count': int,
        }
    }, {
        'url': 'https://www.mall.tv/kdo-to-plati/18-miliard-pro-neziskovky-opravdu-jsou-sportovci-nebo-clovek-v-tisni-pijavice',
        'only_matching': True,
    }, {
        'url': 'https://sk.mall.tv/gejmhaus/reklamacia-nehreje-vyrobnik-tepla-alebo-spekacka',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            url, display_id, headers=self.geo_verification_headers())

        SOURCE_RE = r'(<source[^>]+\bsrc=(?:(["\'])(?:(?!\2).)+|[^\s]+)/(?P<id>[\da-z]+)/index)\b'
        video_id = self._search_regex(
            SOURCE_RE, webpage, 'video id', group='id')

        media = self._parse_html5_media_entries(
            url, re.sub(SOURCE_RE, r'\1.m3u8', webpage), video_id,
            m3u8_id='hls', m3u8_entry_protocol='m3u8_native')[0]

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

        return merge_dicts(media, info, {
            'id': video_id,
            'display_id': display_id,
            'title': self._og_search_title(webpage, default=None) or display_id,
            'description': self._og_search_description(webpage, default=None),
            'thumbnail': self._og_search_thumbnail(webpage, default=None),
        })


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..compat import (
    compat_b64decode,
    compat_urllib_parse_unquote,
)
from ..utils import int_or_none

class MangomoloBaseIE(InfoExtractor):
    _BASE_REGEX = r'https?://(?:admin\.mangomolo\.com/analytics/index\.php/customers/embed/|player\.mangomolo\.com/v1/)'

    def _get_real_id(self, page_id):
        return page_id

    def _real_extract(self, url):
        page_id = self._get_real_id(self._match_id(url))
        webpage = self._download_webpage(
            'https://player.mangomolo.com/v1/%s?%s' % (self._TYPE, url.split('?')[1]), page_id)
        hidden_inputs = self._hidden_inputs(webpage)
        m3u8_entry_protocol = 'm3u8' if self._IS_LIVE else 'm3u8_native'

        format_url = self._html_search_regex(
            [
                r'(?:file|src)\s*:\s*"(https?://[^"]+?/playlist\.m3u8)',
                r'<a[^>]+href="(rtsp://[^"]+)"'
            ], webpage, 'format url')
        formats = self._extract_wowza_formats(
            format_url, page_id, m3u8_entry_protocol, ['smil'])
        self._sort_formats(formats)

        return {
            'id': page_id,
            'title': self._live_title(page_id) if self._IS_LIVE else page_id,
            'uploader_id': hidden_inputs.get('userid'),
            'duration': int_or_none(hidden_inputs.get('duration')),
            'is_live': self._IS_LIVE,
            'formats': formats,
        }

class MangomoloVideoIE(MangomoloBaseIE):
    _TYPE = 'video'
    IE_NAME = 'mangomolo:' + _TYPE
    _VALID_URL = MangomoloBaseIE._BASE_REGEX + r'video\?.*?\bid=(?P<id>\d+)'
    _IS_LIVE = False

class MangomoloLiveIE(MangomoloBaseIE):
    _TYPE = 'live'
    IE_NAME = 'mangomolo:' + _TYPE
    _VALID_URL = MangomoloBaseIE._BASE_REGEX + r'(live|index)\?.*?\bchannelid=(?P<id>(?:[A-Za-z0-9+/=]|%2B|%2F|%3D)+)'
    _IS_LIVE = True

    def _get_real_id(self, page_id):
        return compat_b64decode(compat_urllib_parse_unquote(page_id)).decode()


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    int_or_none,
    str_to_int,
    urlencode_postdata,
)

class ManyVidsIE(InfoExtractor):
    _VALID_URL = r'(?i)https?://(?:www\.)?manyvids\.com/video/(?P<id>\d+)'
    _TESTS = [{
        # preview video
        'url': 'https://www.manyvids.com/Video/133957/everthing-about-me/',
        'md5': '03f11bb21c52dd12a05be21a5c7dcc97',
        'info_dict': {
            'id': '133957',
            'ext': 'mp4',
            'title': 'everthing about me (Preview)',
            'view_count': int,
            'like_count': int,
        },
    }, {
        # full video
        'url': 'https://www.manyvids.com/Video/935718/MY-FACE-REVEAL/',
        'md5': 'f3e8f7086409e9b470e2643edb96bdcc',
        'info_dict': {
            'id': '935718',
            'ext': 'mp4',
            'title': 'MY FACE REVEAL',
            'view_count': int,
            'like_count': int,
        },
    }]

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

        webpage = self._download_webpage(url, video_id)

        video_url = self._search_regex(
            r'data-(?:video-filepath|meta-video)\s*=s*(["\'])(?P<url>(?:(?!\1).)+)\1',
            webpage, 'video URL', group='url')

        title = self._html_search_regex(
            (r'<span[^>]+class=["\']item-title[^>]+>([^<]+)',
             r'<h2[^>]+class=["\']h2 m-0["\'][^>]*>([^<]+)'),
            webpage, 'title', default=None) or self._html_search_meta(
            'twitter:title', webpage, 'title', fatal=True)

        if any(p in webpage for p in ('preview_videos', '_preview.mp4')):
            title += ' (Preview)'

        mv_token = self._search_regex(
            r'data-mvtoken=(["\'])(?P<value>(?:(?!\1).)+)\1', webpage,
            'mv token', default=None, group='value')

        if mv_token:
            # Sets some cookies
            self._download_webpage(
                'https://www.manyvids.com/includes/ajax_repository/you_had_me_at_hello.php',
                video_id, fatal=False, data=urlencode_postdata({
                    'mvtoken': mv_token,
                    'vid': video_id,
                }), headers={
                    'Referer': url,
                    'X-Requested-With': 'XMLHttpRequest'
                })

        if determine_ext(video_url) == 'm3u8':
            formats = self._extract_m3u8_formats(
                video_url, video_id, 'mp4', entry_protocol='m3u8_native',
                m3u8_id='hls')
        else:
            formats = [{'url': video_url}]

        like_count = int_or_none(self._search_regex(
            r'data-likes=["\'](\d+)', webpage, 'like count', default=None))
        view_count = str_to_int(self._html_search_regex(
            r'(?s)<span[^>]+class="views-wrapper"[^>]*>(.+?)</span', webpage,
            'view count', default=None))

        return {
            'id': video_id,
            'title': title,
            'view_count': view_count,
            'like_count': like_count,
            'formats': formats,
        }


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    orderedSet,
    parse_duration,
    try_get,
)

class MarkizaIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?videoarchiv\.markiza\.sk/(?:video/(?:[^/]+/)*|embed/)(?P<id>\d+)(?:[_/]|$)'
    _TESTS = [{
        'url': 'http://videoarchiv.markiza.sk/video/oteckovia/84723_oteckovia-109',
        'md5': 'ada4e9fad038abeed971843aa028c7b0',
        'info_dict': {
            'id': '139078',
            'ext': 'mp4',
            'title': 'Oteckovia 109',
            'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 2760,
        },
    }, {
        'url': 'http://videoarchiv.markiza.sk/video/televizne-noviny/televizne-noviny/85430_televizne-noviny',
        'info_dict': {
            'id': '85430',
            'title': 'Televízne noviny',
        },
        'playlist_count': 23,
    }, {
        'url': 'http://videoarchiv.markiza.sk/video/oteckovia/84723',
        'only_matching': True,
    }, {
        'url': 'http://videoarchiv.markiza.sk/video/84723',
        'only_matching': True,
    }, {
        'url': 'http://videoarchiv.markiza.sk/video/filmy/85190_kamenak',
        'only_matching': True,
    }, {
        'url': 'http://videoarchiv.markiza.sk/video/reflex/zo-zakulisia/84651_pribeh-alzbetky',
        'only_matching': True,
    }, {
        'url': 'http://videoarchiv.markiza.sk/embed/85295',
        'only_matching': True,
    }]

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

        data = self._download_json(
            'http://videoarchiv.markiza.sk/json/video_jwplayer7.json',
            video_id, query={'id': video_id})

        info = self._parse_jwplayer_data(data, m3u8_id='hls', mpd_id='dash')

        if info.get('_type') == 'playlist':
            info.update({
                'id': video_id,
                'title': try_get(
                    data, lambda x: x['details']['name'], compat_str),
            })
        else:
            info['duration'] = parse_duration(
                try_get(data, lambda x: x['details']['duration'], compat_str))
        return info

class MarkizaPageIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?:(?:[^/]+\.)?markiza|tvnoviny)\.sk/(?:[^/]+/)*(?P<id>\d+)_'
    _TESTS = [{
        'url': 'http://www.markiza.sk/soubiz/zahranicny/1923705_oteckovia-maju-svoj-den-ti-slavni-nie-su-o-nic-menej-rozkosni',
        'md5': 'ada4e9fad038abeed971843aa028c7b0',
        'info_dict': {
            'id': '139355',
            'ext': 'mp4',
            'title': 'Oteckovia 110',
            'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 2604,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://dajto.markiza.sk/filmy-a-serialy/1774695_frajeri-vo-vegas',
        'only_matching': True,
    }, {
        'url': 'http://superstar.markiza.sk/aktualne/1923870_to-je-ale-telo-spevacka-ukazala-sexy-postavicku-v-bikinach',
        'only_matching': True,
    }, {
        'url': 'http://hybsa.markiza.sk/aktualne/1923790_uzasna-atmosfera-na-hybsa-v-poprade-superstaristi-si-prve-koncerty-pred-davom-ludi-poriadne-uzili',
        'only_matching': True,
    }, {
        'url': 'http://doma.markiza.sk/filmy/1885250_moja-vysnivana-svadba',
        'only_matching': True,
    }, {
        'url': 'http://www.tvnoviny.sk/domace/1923887_po-smrti-manzela-ju-cakalo-poriadne-prekvapenie',
        'only_matching': True,
    }]

    @classmethod
    def suitable(cls, url):
        return False if MarkizaIE.suitable(url) else super(MarkizaPageIE, cls).suitable(url)

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

        webpage = self._download_webpage(
            # Downloading for some hosts (e.g. dajto, doma) fails with 500
            # although everything seems to be OK, so considering 500
            # status code to be expected.
            url, playlist_id, expected_status=500)

        entries = [
            self.url_result('http://videoarchiv.markiza.sk/video/%s' % video_id)
            for video_id in orderedSet(re.findall(
                r'(?:initPlayer_|data-entity=["\']|id=["\']player_)(\d+)',
                webpage))]

        return self.playlist_result(entries, playlist_id)


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    clean_html,
    determine_ext,
    int_or_none,
    js_to_json,
    mimetype2ext,
    parse_filesize,
)

class MassengeschmackTVIE(InfoExtractor):
    IE_NAME = 'massengeschmack.tv'
    _VALID_URL = r'https?://(?:www\.)?massengeschmack\.tv/play/(?P<id>[^?&#]+)'

    _TEST = {
        'url': 'https://massengeschmack.tv/play/fktv202',
        'md5': 'a9e054db9c2b5a08f0a0527cc201e8d3',
        'info_dict': {
            'id': 'fktv202',
            'ext': 'mp4',
            'title': 'Fernsehkritik-TV - Folge 202',
        },
    }

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

        webpage = self._download_webpage(url, episode)
        title = clean_html(self._html_search_regex(
            '
([^<]+)
', webpage, 'title'))
        thumbnail = self._search_regex(r'POSTER\s*=\s*"([^"]+)', webpage, 'thumbnail', fatal=False)
        sources = self._parse_json(self._search_regex(r'(?s)MEDIA\s*=\s*(\[.+?\]);', webpage, 'media'), episode, js_to_json)

        formats = []
        for source in sources:
            furl = source.get('src')
            if not furl:
                continue
            furl = self._proto_relative_url(furl)
            ext = determine_ext(furl) or mimetype2ext(source.get('type'))
            if ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    furl, episode, 'mp4', 'm3u8_native',
                    m3u8_id='hls', fatal=False))
            else:
                formats.append({
                    'url': furl,
                    'format_id': determine_ext(furl),
                })

        for (durl, format_id, width, height, filesize) in re.findall(r'''(?x)
                                   ]+?href="(?P<url>(?:https:)?//[^"]+)".*?
                                   (?P<format_id>.+?).*?
                                   (?:(?P<width>\d+)x(?P<height>\d+))?\s+?\((?P<filesize>[\d,]+\s*[GM]iB)\)
                                ''', webpage):
            formats.append({
                'url': durl,
                'format_id': format_id,
                'width': int_or_none(width),
                'height': int_or_none(height),
                'filesize': parse_filesize(filesize),
                'vcodec': 'none' if format_id.startswith('Audio') else None,
            })

        self._sort_formats(formats, ('width', 'height', 'filesize', 'tbr'))

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


code:
# coding: utf-8
from __future__ import unicode_literals

import random

from .common import InfoExtractor
from ..utils import xpath_text

class MatchTVIE(InfoExtractor):
    _VALID_URL = r'https?://matchtv\.ru(?:/on-air|/?#live-player)'
    _TESTS = [{
        'url': 'http://matchtv.ru/#live-player',
        'info_dict': {
            'id': 'matchtv-live',
            'ext': 'flv',
            'title': r're:^Матч ТВ - Прямой эфир \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
            'is_live': True,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://matchtv.ru/on-air/',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = 'matchtv-live'
        video_url = self._download_json(
            'http://player.matchtv.ntvplus.tv/player/smil', video_id,
            query={
                'ts': '',
                'quality': 'SD',
                'contentId': '561d2c0df7159b37178b4567',
                'sign': '',
                'includeHighlights': '0',
                'userId': '',
                'sessionId': random.randint(1, 1000000000),
                'contentType': 'channel',
                'timeShift': '0',
                'platform': 'portal',
            },
            headers={
                'Referer': 'http://player.matchtv.ntvplus.tv/embed-player/NTVEmbedPlayer.swf',
            })['data']['videoUrl']
        f4m_url = xpath_text(self._download_xml(video_url, video_id), './to')
        formats = self._extract_f4m_formats(f4m_url, video_id)
        self._sort_formats(formats)
        return {
            'id': video_id,
            'title': self._live_title('Матч ТВ - Прямой эфир'),
            'is_live': True,
            'formats': formats,
        }


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    determine_ext,
    int_or_none,
    parse_duration,
    parse_iso8601,
    xpath_text,
)

class MDRIE(InfoExtractor):
    IE_DESC = 'MDR.DE and KiKA'
    _VALID_URL = r'https?://(?:www\.)?(?:mdr|kika)\.de/(?:.*)/[a-z-]+-?(?P<id>\d+)(?:_.+?)?\.html'

    _TESTS = [{
        # MDR regularly deletes its videos
        'url': 'http://www.mdr.de/fakt/video189002.html',
        'only_matching': True,
    }, {
        # audio
        'url': 'http://www.mdr.de/kultur/audio1312272_zc-15948bad_zs-86171fdd.html',
        'md5': '64c4ee50f0a791deb9479cd7bbe9d2fa',
        'info_dict': {
            'id': '1312272',
            'ext': 'mp3',
            'title': 'Feuilleton vom 30. Oktober 2015',
            'duration': 250,
            'uploader': 'MITTELDEUTSCHER RUNDFUNK',
        },
        'skip': '404 not found',
    }, {
        'url': 'http://www.kika.de/baumhaus/videos/video19636.html',
        'md5': '4930515e36b06c111213e80d1e4aad0e',
        'info_dict': {
            'id': '19636',
            'ext': 'mp4',
            'title': 'Baumhaus vom 30. Oktober 2015',
            'duration': 134,
            'uploader': 'KIKA',
        },
        'skip': '404 not found',
    }, {
        'url': 'http://www.kika.de/sendungen/einzelsendungen/weihnachtsprogramm/videos/video8182.html',
        'md5': '5fe9c4dd7d71e3b238f04b8fdd588357',
        'info_dict': {
            'id': '8182',
            'ext': 'mp4',
            'title': 'Beutolomäus und der geheime Weihnachtswunsch',
            'description': 'md5:b69d32d7b2c55cbe86945ab309d39bbd',
            'timestamp': 1482541200,
            'upload_date': '20161224',
            'duration': 4628,
            'uploader': 'KIKA',
        },
    }, {
        # audio with alternative playerURL pattern
        'url': 'http://www.mdr.de/kultur/videos-und-audios/audio-radio/operation-mindfuck-robert-wilson100.html',
        'info_dict': {
            'id': '100',
            'ext': 'mp4',
            'title': 'Feature: Operation Mindfuck - Robert Anton Wilson',
            'duration': 3239,
            'uploader': 'MITTELDEUTSCHER RUNDFUNK',
        },
    }, {
        'url': 'http://www.kika.de/baumhaus/sendungen/video19636_zc-fea7f8a0_zs-4bf89c60.html',
        'only_matching': True,
    }, {
        'url': 'http://www.kika.de/sendungen/einzelsendungen/weihnachtsprogramm/einzelsendung2534.html',
        'only_matching': True,
    }, {
        'url': 'http://www.mdr.de/mediathek/mdr-videos/a/video-1334.html',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        data_url = self._search_regex(
            r'(?:dataURL|playerXml(?:["\'])?)\s*:\s*(["\'])(?P<url>.+?-avCustom\.xml)\1',
            webpage, 'data url', group='url').replace(r'\/', '/')

        doc = self._download_xml(
            compat_urlparse.urljoin(url, data_url), video_id)

        title = xpath_text(doc, ['./title', './broadcast/broadcastName'], 'title', fatal=True)

        formats = []
        processed_urls = []
        for asset in doc.findall('./assets/asset'):
            for source in (
                    'progressiveDownload',
                    'dynamicHttpStreamingRedirector',
                    'adaptiveHttpStreamingRedirector'):
                url_el = asset.find('./%sUrl' % source)
                if url_el is None:
                    continue

                video_url = url_el.text
                if video_url in processed_urls:
                    continue

                processed_urls.append(video_url)

                vbr = int_or_none(xpath_text(asset, './bitrateVideo', 'vbr'), 1000)
                abr = int_or_none(xpath_text(asset, './bitrateAudio', 'abr'), 1000)

                ext = determine_ext(url_el.text)
                if ext == 'm3u8':
                    url_formats = self._extract_m3u8_formats(
                        video_url, video_id, 'mp4', entry_protocol='m3u8_native',
                        preference=0, m3u8_id='HLS', fatal=False)
                elif ext == 'f4m':
                    url_formats = self._extract_f4m_formats(
                        video_url + '?hdcore=3.7.0&plugin=aasp-3.7.0.39.44', video_id,
                        preference=0, f4m_id='HDS', fatal=False)
                else:
                    media_type = xpath_text(asset, './mediaType', 'media type', default='MP4')
                    vbr = int_or_none(xpath_text(asset, './bitrateVideo', 'vbr'), 1000)
                    abr = int_or_none(xpath_text(asset, './bitrateAudio', 'abr'), 1000)
                    filesize = int_or_none(xpath_text(asset, './fileSize', 'file size'))

                    f = {
                        'url': video_url,
                        'format_id': '%s-%d' % (media_type, vbr or abr),
                        'filesize': filesize,
                        'abr': abr,
                        'preference': 1,
                    }

                    if vbr:
                        width = int_or_none(xpath_text(asset, './frameWidth', 'width'))
                        height = int_or_none(xpath_text(asset, './frameHeight', 'height'))
                        f.update({
                            'vbr': vbr,
                            'width': width,
                            'height': height,
                        })

                    url_formats = [f]

                if not url_formats:
                    continue

                if not vbr:
                    for f in url_formats:
                        abr = f.get('tbr') or abr
                        if 'tbr' in f:
                            del f['tbr']
                        f.update({
                            'abr': abr,
                            'vcodec': 'none',
                        })

                formats.extend(url_formats)

        self._sort_formats(formats)

        description = xpath_text(doc, './broadcast/broadcastDescription', 'description')
        timestamp = parse_iso8601(
            xpath_text(
                doc, [
                    './broadcast/broadcastDate',
                    './broadcast/broadcastStartDate',
                    './broadcast/broadcastEndDate'],
                'timestamp', default=None))
        duration = parse_duration(xpath_text(doc, './duration', 'duration'))
        uploader = xpath_text(doc, './rights', 'uploader')

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'timestamp': timestamp,
            'duration': duration,
            'uploader': uploader,
            'formats': formats,
        }


code:
from __future__ import unicode_literals

import re

from .gigya import GigyaBaseIE

from ..compat import compat_str
from ..utils import (
    int_or_none,
    parse_duration,
    try_get,
    unified_timestamp,
)

class MedialaanIE(GigyaBaseIE):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:www\.|nieuws\.)?
                        (?:
                            (?P<site_id>vtm|q2|vtmkzoom)\.be/
                            (?:
                                video(?:/[^/]+/id/|/?\?.*?\baid=)|
                                (?:[^/]+/)*
                            )
                        )
                        (?P<id>[^/?#&]+)
                    '''
    _NETRC_MACHINE = 'medialaan'
    _APIKEY = '3_HZ0FtkMW_gOyKlqQzW5_0FHRC7Nd5XpXJZcDdXY4pk5eES2ZWmejRW5egwVm4ug-'
    _SITE_TO_APP_ID = {
        'vtm': 'vtm_watch',
        'q2': 'q2',
        'vtmkzoom': 'vtmkzoom',
    }
    _TESTS = [{
        # vod
        'url': 'http://vtm.be/video/volledige-afleveringen/id/vtm_20170219_VM0678361_vtmwatch',
        'info_dict': {
            'id': 'vtm_20170219_VM0678361_vtmwatch',
            'ext': 'mp4',
            'title': 'Allemaal Chris afl. 6',
            'description': 'md5:4be86427521e7b07e0adb0c9c554ddb2',
            'timestamp': 1487533280,
            'upload_date': '20170219',
            'duration': 2562,
            'series': 'Allemaal Chris',
            'season': 'Allemaal Chris',
            'season_number': 1,
            'season_id': '256936078124527',
            'episode': 'Allemaal Chris afl. 6',
            'episode_number': 6,
            'episode_id': '256936078591527',
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'Requires account credentials',
    }, {
        # clip
        'url': 'http://vtm.be/video?aid=168332',
        'info_dict': {
            'id': '168332',
            'ext': 'mp4',
            'title': '"Veronique liegt!"',
            'description': 'md5:1385e2b743923afe54ba4adc38476155',
            'timestamp': 1489002029,
            'upload_date': '20170308',
            'duration': 96,
        },
    }, {
        # vod
        'url': 'http://vtm.be/video/volledige-afleveringen/id/257107153551000',
        'only_matching': True,
    }, {
        # vod
        'url': 'http://vtm.be/video?aid=163157',
        'only_matching': True,
    }, {
        # vod
        'url': 'http://www.q2.be/video/volledige-afleveringen/id/2be_20170301_VM0684442_q2',
        'only_matching': True,
    }, {
        # clip
        'url': 'http://vtmkzoom.be/k3-dansstudio/een-nieuw-seizoen-van-k3-dansstudio',
        'only_matching': True,
    }, {
        # http/s redirect
        'url': 'https://vtmkzoom.be/video?aid=45724',
        'info_dict': {
            'id': '257136373657000',
            'ext': 'mp4',
            'title': 'K3 Dansstudio Ushuaia afl.6',
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'Requires account credentials',
    }, {
        # nieuws.vtm.be
        'url': 'https://nieuws.vtm.be/stadion/stadion/genk-nog-moeilijk-programma',
        'only_matching': True,
    }]

    def _real_initialize(self):
        self._logged_in = False

    def _login(self):
        username, password = self._get_login_info()
        if username is None:
            self.raise_login_required()

        auth_data = {
            'APIKey': self._APIKEY,
            'sdk': 'js_6.1',
            'format': 'json',
            'loginID': username,
            'password': password,
        }

        auth_info = self._gigya_login(auth_data)

        self._uid = auth_info['UID']
        self._uid_signature = auth_info['UIDSignature']
        self._signature_timestamp = auth_info['signatureTimestamp']

        self._logged_in = True

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id, site_id = mobj.group('id', 'site_id')

        webpage = self._download_webpage(url, video_id)

        config = self._parse_json(
            self._search_regex(
                r'videoJSConfig\s*=\s*JSON\.parse\(\'({.+?})\'\);',
                webpage, 'config', default='{}'), video_id,
            transform_source=lambda s: s.replace(
                '\\\\', '\\').replace(r'\"', '"').replace(r"\'", "'"))

        vod_id = config.get('vodId') or self._search_regex(
            (r'\\"vodId\\"\s*:\s*\\"(.+?)\\"',
             r'"vodId"\s*:\s*"(.+?)"',
             r'<[^>]+id=["\']vod-(\d+)'),
            webpage, 'video_id', default=None)

        # clip, no authentication required
        if not vod_id:
            player = self._parse_json(
                self._search_regex(
                    r'vmmaplayer\(({.+?})\);', webpage, 'vmma player',
                    default=''),
                video_id, transform_source=lambda s: '[%s]' % s, fatal=False)
            if player:
                video = player[-1]
                if video['videoUrl'] in ('http', 'https'):
                    return self.url_result(video['url'], MedialaanIE.ie_key())
                info = {
                    'id': video_id,
                    'url': video['videoUrl'],
                    'title': video['title'],
                    'thumbnail': video.get('imageUrl'),
                    'timestamp': int_or_none(video.get('createdDate')),
                    'duration': int_or_none(video.get('duration')),
                }
            else:
                info = self._parse_html5_media_entries(
                    url, webpage, video_id, m3u8_id='hls')[0]
                info.update({
                    'id': video_id,
                    'title': self._html_search_meta('description', webpage),
                    'duration': parse_duration(self._html_search_meta('duration', webpage)),
                })
        # vod, authentication required
        else:
            if not self._logged_in:
                self._login()

            settings = self._parse_json(
                self._search_regex(
                    r'jQuery\.extend\(Drupal\.settings\s*,\s*({.+?})\);',
                    webpage, 'drupal settings', default='{}'),
                video_id)

            def get(container, item):
                return try_get(
                    settings, lambda x: x[container][item],
                    compat_str) or self._search_regex(
                    r'"%s"\s*:\s*"([^"]+)' % item, webpage, item,
                    default=None)

            app_id = get('vod', 'app_id') or self._SITE_TO_APP_ID.get(site_id, 'vtm_watch')
            sso = get('vod', 'gigyaDatabase') or 'vtm-sso'

            data = self._download_json(
                'http://vod.medialaan.io/api/1.0/item/%s/video' % vod_id,
                video_id, query={
                    'app_id': app_id,
                    'user_network': sso,
                    'UID': self._uid,
                    'UIDSignature': self._uid_signature,
                    'signatureTimestamp': self._signature_timestamp,
                })

            formats = self._extract_m3u8_formats(
                data['response']['uri'], video_id, entry_protocol='m3u8_native',
                ext='mp4', m3u8_id='hls')

            self._sort_formats(formats)

            info = {
                'id': vod_id,
                'formats': formats,
            }

            api_key = get('vod', 'apiKey')
            channel = get('medialaanGigya', 'channel')

            if api_key:
                videos = self._download_json(
                    'http://vod.medialaan.io/vod/v2/videos', video_id, fatal=False,
                    query={
                        'channels': channel,
                        'ids': vod_id,
                        'limit': 1,
                        'apikey': api_key,
                    })
                if videos:
                    video = try_get(
                        videos, lambda x: x['response']['videos'][0], dict)
                    if video:
                        def get(container, item, expected_type=None):
                            return try_get(
                                video, lambda x: x[container][item], expected_type)

                        def get_string(container, item):
                            return get(container, item, compat_str)

                        info.update({
                            'series': get_string('program', 'title'),
                            'season': get_string('season', 'title'),
                            'season_number': int_or_none(get('season', 'number')),
                            'season_id': get_string('season', 'id'),
                            'episode': get_string('episode', 'title'),
                            'episode_number': int_or_none(get('episode', 'number')),
                            'episode_id': get_string('episode', 'id'),
                            'duration': int_or_none(
                                video.get('duration')) or int_or_none(
                                video.get('durationMillis'), scale=1000),
                            'title': get_string('episode', 'title'),
                            'description': get_string('episode', 'text'),
                            'timestamp': unified_timestamp(get_string(
                                'publication', 'begin')),
                        })

            if not info.get('title'):
                info['title'] = try_get(
                    config, lambda x: x['videoConfig']['title'],
                    compat_str) or self._html_search_regex(
                    r'\\"title\\"\s*:\s*\\"(.+?)\\"', webpage, 'title',
                    default=None) or self._og_search_title(webpage)

        if not info.get('description'):
            info['description'] = self._html_search_regex(
                r'<div[^>]+class="field-item\s+even">\s*<p>(.+?)</p>',
                webpage, 'description', default=None)

        return info


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .theplatform import ThePlatformBaseIE
from ..compat import (
    compat_parse_qs,
    compat_urllib_parse_urlparse,
)
from ..utils import (
    ExtractorError,
    int_or_none,
    update_url_query,
)

class MediasetIE(ThePlatformBaseIE):
    _TP_TLD = 'eu'
    _VALID_URL = r'''(?x)
                    (?:
                        mediaset:|
                        https?://
                            (?:(?:www|static3)\.)?mediasetplay\.mediaset\.it/
                            (?:
                                (?:video|on-demand)/(?:[^/]+/)+[^/]+_|
                                player/index\.html\?.*?\bprogramGuid=
                            )
                    )(?P<id>[0-9A-Z]{16,})
                    '''
    _TESTS = [{
        # full episode
        'url': 'https://www.mediasetplay.mediaset.it/video/hellogoodbye/quarta-puntata_FAFU000000661824',
        'md5': '9b75534d42c44ecef7bf1ffeacb7f85d',
        'info_dict': {
            'id': 'FAFU000000661824',
            'ext': 'mp4',
            'title': 'Quarta puntata',
            'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 1414.26,
            'upload_date': '20161107',
            'series': 'Hello Goodbye',
            'timestamp': 1478532900,
            'uploader': 'Rete 4',
            'uploader_id': 'R4',
        },
    }, {
        'url': 'https://www.mediasetplay.mediaset.it/video/matrix/puntata-del-25-maggio_F309013801000501',
        'md5': '288532f0ad18307705b01e581304cd7b',
        'info_dict': {
            'id': 'F309013801000501',
            'ext': 'mp4',
            'title': 'Puntata del 25 maggio',
            'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 6565.007,
            'upload_date': '20180526',
            'series': 'Matrix',
            'timestamp': 1527326245,
            'uploader': 'Canale 5',
            'uploader_id': 'C5',
        },
    }, {
        # clip
        'url': 'https://www.mediasetplay.mediaset.it/video/gogglebox/un-grande-classico-della-commedia-sexy_FAFU000000661680',
        'only_matching': True,
    }, {
        # iframe simple
        'url': 'https://static3.mediasetplay.mediaset.it/player/index.html?appKey=5ad3966b1de1c4000d5cec48&programGuid=FAFU000000665924&id=665924',
        'only_matching': True,
    }, {
        # iframe twitter (from http://www.wittytv.it/se-prima-mi-fidavo-zero/)
        'url': 'https://static3.mediasetplay.mediaset.it/player/index.html?appKey=5ad3966b1de1c4000d5cec48&programGuid=FAFU000000665104&id=665104',
        'only_matching': True,
    }, {
        'url': 'mediaset:FAFU000000665924',
        'only_matching': True,
    }, {
        'url': 'https://www.mediasetplay.mediaset.it/video/mediasethaacuoreilfuturo/palmieri-alicudi-lisola-dei-tre-bambini-felici--un-decreto-per-alicudi-e-tutte-le-microscuole_FD00000000102295',
        'only_matching': True,
    }, {
        'url': 'https://www.mediasetplay.mediaset.it/video/cherryseason/anticipazioni-degli-episodi-del-23-ottobre_F306837101005C02',
        'only_matching': True,
    }, {
        'url': 'https://www.mediasetplay.mediaset.it/video/tg5/ambiente-onda-umana-per-salvare-il-pianeta_F309453601079D01',
        'only_matching': True,
    }, {
        'url': 'https://www.mediasetplay.mediaset.it/video/grandefratellovip/benedetta-una-doccia-gelata_F309344401044C135',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(ie, webpage):
        def _qs(url):
            return compat_parse_qs(compat_urllib_parse_urlparse(url).query)

        def _program_guid(qs):
            return qs.get('programGuid', [None])[0]

        entries = []
        for mobj in re.finditer(
                r'<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?//(?:www\.)?video\.mediaset\.it/player/playerIFrame(?:Twitter)?\.shtml.*?)\1',
                webpage):
            embed_url = mobj.group('url')
            embed_qs = _qs(embed_url)
            program_guid = _program_guid(embed_qs)
            if program_guid:
                entries.append(embed_url)
                continue
            video_id = embed_qs.get('id', [None])[0]
            if not video_id:
                continue
            urlh = ie._request_webpage(
                embed_url, video_id, note='Following embed URL redirect')
            embed_url = urlh.geturl()
            program_guid = _program_guid(_qs(embed_url))
            if program_guid:
                entries.append(embed_url)
        return entries

    def _parse_smil_formats(self, smil, smil_url, video_id, namespace=None, f4m_params=None, transform_rtmp_url=None):
        for video in smil.findall(self._xpath_ns('.//video', namespace)):
            video.attrib['src'] = re.sub(r'(https?://vod05)t(-mediaset-it\.akamaized\.net/.+?.mpd)\?.+', r'\1\2', video.attrib['src'])
        return super(MediasetIE, self)._parse_smil_formats(smil, smil_url, video_id, namespace, f4m_params, transform_rtmp_url)

    def _real_extract(self, url):
        guid = self._match_id(url)
        tp_path = 'PR1GhC/media/guid/2702976343/' + guid
        info = self._extract_theplatform_metadata(tp_path, guid)

        formats = []
        subtitles = {}
        first_e = None
        for asset_type in ('SD', 'HD'):
            # TODO: fixup ISM+none manifest URLs
            for f in ('MPEG4', 'MPEG-DASH+none', 'M3U+none'):
                try:
                    tp_formats, tp_subtitles = self._extract_theplatform_smil(
                        update_url_query('http://link.theplatform.%s/s/%s' % (self._TP_TLD, tp_path), {
                            'mbr': 'true',
                            'formats': f,
                            'assetTypes': asset_type,
                        }), guid, 'Downloading %s %s SMIL data' % (f.split('+')[0], asset_type))
                except ExtractorError as e:
                    if not first_e:
                        first_e = e
                    break
                for tp_f in tp_formats:
                    tp_f['quality'] = 1 if asset_type == 'HD' else 0
                formats.extend(tp_formats)
                subtitles = self._merge_subtitles(subtitles, tp_subtitles)
        if first_e and not formats:
            raise first_e
        self._sort_formats(formats)

        fields = []
        for templ, repls in (('tvSeason%sNumber', ('', 'Episode')), ('mediasetprogram$%s', ('brandTitle', 'numberOfViews', 'publishInfo'))):
            fields.extend(templ % repl for repl in repls)
        feed_data = self._download_json(
            'https://feed.entertainment.tv.theplatform.eu/f/PR1GhC/mediaset-prod-all-programs/guid/-/' + guid,
            guid, fatal=False, query={'fields': ','.join(fields)})
        if feed_data:
            publish_info = feed_data.get('mediasetprogram$publishInfo') or {}
            info.update({
                'episode_number': int_or_none(feed_data.get('tvSeasonEpisodeNumber')),
                'season_number': int_or_none(feed_data.get('tvSeasonNumber')),
                'series': feed_data.get('mediasetprogram$brandTitle'),
                'uploader': publish_info.get('description'),
                'uploader_id': publish_info.get('channel'),
                'view_count': int_or_none(feed_data.get('mediasetprogram$numberOfViews')),
            })

        info.update({
            'id': guid,
            'formats': formats,
            'subtitles': subtitles,
        })
        return info


code:
# coding: utf-8
from __future__ import unicode_literals

import re
import json

from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    float_or_none,
    mimetype2ext,
    str_or_none,
    try_get,
    unescapeHTML,
    unsmuggle_url,
    url_or_none,
    urljoin,
)

_ID_RE = r'(?:[0-9a-f]{32,34}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12,14})'

class MediasiteIE(InfoExtractor):
    _VALID_URL = r'(?xi)https?://[^/]+/Mediasite/(?:Play|Showcase/(?:default|livebroadcast)/Presentation)/(?P<id>%s)(?P<query>\?[^#]+|)' % _ID_RE
    _TESTS = [
        {
            'url': 'https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271681e4f199af3c60d1f82869b1d',
            'info_dict': {
                'id': '2db6c271681e4f199af3c60d1f82869b1d',
                'ext': 'mp4',
                'title': 'Lecture: Tuesday, September 20, 2016 - Sir Andrew Wiles',
                'description': 'Sir Andrew Wiles: “Equations in arithmetic”\\n\\nI will describe some of the interactions between modern number theory and the problem of solving equations in rational numbers or integers\\u0027.',
                'timestamp': 1474268400.0,
                'upload_date': '20160919',
            },
        },
        {
            'url': 'http://mediasite.uib.no/Mediasite/Play/90bb363295d945d6b548c867d01181361d?catalog=a452b7df-9ae1-46b7-a3ba-aceeb285f3eb',
            'info_dict': {
                'id': '90bb363295d945d6b548c867d01181361d',
                'ext': 'mp4',
                'upload_date': '20150429',
                'title': '5) IT-forum 2015-Dag 1  - Dungbeetle -  How and why Rain created a tiny bug tracker for Unity',
                'timestamp': 1430311380.0,
            },
        },
        {
            'url': 'https://collegerama.tudelft.nl/Mediasite/Play/585a43626e544bdd97aeb71a0ec907a01d',
            'md5': '481fda1c11f67588c0d9d8fbdced4e39',
            'info_dict': {
                'id': '585a43626e544bdd97aeb71a0ec907a01d',
                'ext': 'mp4',
                'title': 'Een nieuwe wereld: waarden, bewustzijn en techniek van de mensheid 2.0.',
                'description': '',
                'thumbnail': r're:^https?://.*\.jpg(?:\?.*)?$',
                'duration': 7713.088,
                'timestamp': 1413309600,
                'upload_date': '20141014',
            },
        },
        {
            'url': 'https://collegerama.tudelft.nl/Mediasite/Play/86a9ea9f53e149079fbdb4202b521ed21d?catalog=fd32fd35-6c99-466c-89d4-cd3c431bc8a4',
            'md5': 'ef1fdded95bdf19b12c5999949419c92',
            'info_dict': {
                'id': '86a9ea9f53e149079fbdb4202b521ed21d',
                'ext': 'wmv',
                'title': '64ste Vakantiecursus: Afvalwater',
                'description': 'md5:7fd774865cc69d972f542b157c328305',
                'thumbnail': r're:^https?://.*\.jpg(?:\?.*?)?$',
                'duration': 10853,
                'timestamp': 1326446400,
                'upload_date': '20120113',
            },
        },
        {
            'url': 'http://digitalops.sandia.gov/Mediasite/Play/24aace4429fc450fb5b38cdbf424a66e1d',
            'md5': '9422edc9b9a60151727e4b6d8bef393d',
            'info_dict': {
                'id': '24aace4429fc450fb5b38cdbf424a66e1d',
                'ext': 'mp4',
                'title': 'Xyce Software Training - Section 1',
                'description': r're:(?s)SAND Number: SAND 2013-7800.{200,}',
                'upload_date': '20120409',
                'timestamp': 1333983600,
                'duration': 7794,
            }
        },
        {
            'url': 'https://collegerama.tudelft.nl/Mediasite/Showcase/livebroadcast/Presentation/ada7020854f743c49fbb45c9ec7dbb351d',
            'only_matching': True,
        },
        {
            'url': 'https://mediasite.ntnu.no/Mediasite/Showcase/default/Presentation/7d8b913259334b688986e970fae6fcb31d',
            'only_matching': True,
        },
        {
            # dashed id
            'url': 'https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271-681e-4f19-9af3-c60d1f82869b1d',
            'only_matching': True,
        }
    ]

    # look in Mediasite.Core.js (Mediasite.ContentStreamType[*])
    _STREAM_TYPES = {
        0: 'video1',  # the main video
        2: 'slide',
        3: 'presentation',
        4: 'video2',  # screencast?
        5: 'video3',
    }

    @staticmethod
    def _extract_urls(webpage):
        return [
            unescapeHTML(mobj.group('url'))
            for mobj in re.finditer(
                r'(?xi)<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:(?:https?:)?//[^/]+)?/Mediasite/Play/%s(?:\?.*?)?)\1' % _ID_RE,
                webpage)]

    def _real_extract(self, url):
        url, data = unsmuggle_url(url, {})
        mobj = re.match(self._VALID_URL, url)
        resource_id = mobj.group('id')
        query = mobj.group('query')

        webpage, urlh = self._download_webpage_handle(url, resource_id)  # XXX: add UrlReferrer?
        redirect_url = urlh.geturl()

        # XXX: might have also extracted UrlReferrer and QueryString from the html
        service_path = compat_urlparse.urljoin(redirect_url, self._html_search_regex(
            r'<div[^>]+\bid=["\']ServicePath[^>]+>(.+?)</div>', webpage, resource_id,
            default='/Mediasite/PlayerService/PlayerService.svc/json'))

        player_options = self._download_json(
            '%s/GetPlayerOptions' % service_path, resource_id,
            headers={
                'Content-type': 'application/json; charset=utf-8',
                'X-Requested-With': 'XMLHttpRequest',
            },
            data=json.dumps({
                'getPlayerOptionsRequest': {
                    'ResourceId': resource_id,
                    'QueryString': query,
                    'UrlReferrer': data.get('UrlReferrer', ''),
                    'UseScreenReader': False,
                }
            }).encode('utf-8'))['d']

        presentation = player_options['Presentation']
        title = presentation['Title']

        if presentation is None:
            raise ExtractorError(
                'Mediasite says: %s' % player_options['PlayerPresentationStatusMessage'],
                expected=True)

        thumbnails = []
        formats = []
        for snum, Stream in enumerate(presentation['Streams']):
            stream_type = Stream.get('StreamType')
            if stream_type is None:
                continue

            video_urls = Stream.get('VideoUrls')
            if not isinstance(video_urls, list):
                video_urls = []

            stream_id = self._STREAM_TYPES.get(
                stream_type, 'type%u' % stream_type)

            stream_formats = []
            for unum, VideoUrl in enumerate(video_urls):
                video_url = url_or_none(VideoUrl.get('Location'))
                if not video_url:
                    continue
                # XXX: if Stream.get('CanChangeScheme', False), switch scheme to HTTP/HTTPS

                media_type = VideoUrl.get('MediaType')
                if media_type == 'SS':
                    stream_formats.extend(self._extract_ism_formats(
                        video_url, resource_id,
                        ism_id='%s-%u.%u' % (stream_id, snum, unum),
                        fatal=False))
                elif media_type == 'Dash':
                    stream_formats.extend(self._extract_mpd_formats(
                        video_url, resource_id,
                        mpd_id='%s-%u.%u' % (stream_id, snum, unum),
                        fatal=False))
                else:
                    stream_formats.append({
                        'format_id': '%s-%u.%u' % (stream_id, snum, unum),
                        'url': video_url,
                        'ext': mimetype2ext(VideoUrl.get('MimeType')),
                    })

            # TODO: if Stream['HasSlideContent']:
            # synthesise an MJPEG video stream '%s-%u.slides' % (stream_type, snum)
            # from Stream['Slides']
            # this will require writing a custom downloader...

            # disprefer 'secondary' streams
            if stream_type != 0:
                for fmt in stream_formats:
                    fmt['preference'] = -1

            thumbnail_url = Stream.get('ThumbnailUrl')
            if thumbnail_url:
                thumbnails.append({
                    'id': '%s-%u' % (stream_id, snum),
                    'url': urljoin(redirect_url, thumbnail_url),
                    'preference': -1 if stream_type != 0 else 0,
                })
            formats.extend(stream_formats)

        self._sort_formats(formats)

        # XXX: Presentation['Presenters']
        # XXX: Presentation['Transcript']

        return {
            'id': resource_id,
            'title': title,
            'description': presentation.get('Description'),
            'duration': float_or_none(presentation.get('Duration'), 1000),
            'timestamp': float_or_none(presentation.get('UnixTime'), 1000),
            'formats': formats,
            'thumbnails': thumbnails,
        }

class MediasiteCatalogIE(InfoExtractor):
    _VALID_URL = r'''(?xi)
                        (?P<url>https?://[^/]+/Mediasite)
                        /Catalog/Full/
                        (?P<catalog_id>{0})
                        (?:
                            /(?P<current_folder_id>{0})
                            /(?P<root_dynamic_folder_id>{0})
                        )?
                    '''.format(_ID_RE)
    _TESTS = [{
        'url': 'http://events7.mediasite.com/Mediasite/Catalog/Full/631f9e48530d454381549f955d08c75e21',
        'info_dict': {
            'id': '631f9e48530d454381549f955d08c75e21',
            'title': 'WCET Summit: Adaptive Learning in Higher Ed: Improving Outcomes Dynamically',
        },
        'playlist_count': 6,
        'expected_warnings': ['is not a supported codec'],
    }, {
        # with CurrentFolderId and RootDynamicFolderId
        'url': 'https://medaudio.medicine.iu.edu/Mediasite/Catalog/Full/9518c4a6c5cf4993b21cbd53e828a92521/97a9db45f7ab47428c77cd2ed74bb98f14/9518c4a6c5cf4993b21cbd53e828a92521',
        'info_dict': {
            'id': '9518c4a6c5cf4993b21cbd53e828a92521',
            'title': 'IUSM Family and Friends Sessions',
        },
        'playlist_count': 2,
    }, {
        'url': 'http://uipsyc.mediasite.com/mediasite/Catalog/Full/d5d79287c75243c58c50fef50174ec1b21',
        'only_matching': True,
    }, {
        # no AntiForgeryToken
        'url': 'https://live.libraries.psu.edu/Mediasite/Catalog/Full/8376d4b24dd1457ea3bfe4cf9163feda21',
        'only_matching': True,
    }, {
        'url': 'https://medaudio.medicine.iu.edu/Mediasite/Catalog/Full/9518c4a6c5cf4993b21cbd53e828a92521/97a9db45f7ab47428c77cd2ed74bb98f14/9518c4a6c5cf4993b21cbd53e828a92521',
        'only_matching': True,
    }, {
        # dashed id
        'url': 'http://events7.mediasite.com/Mediasite/Catalog/Full/631f9e48-530d-4543-8154-9f955d08c75e',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        mediasite_url = mobj.group('url')
        catalog_id = mobj.group('catalog_id')
        current_folder_id = mobj.group('current_folder_id') or catalog_id
        root_dynamic_folder_id = mobj.group('root_dynamic_folder_id')

        webpage = self._download_webpage(url, catalog_id)

        # AntiForgeryToken is optional (e.g. [1])
        # 1. https://live.libraries.psu.edu/Mediasite/Catalog/Full/8376d4b24dd1457ea3bfe4cf9163feda21
        anti_forgery_token = self._search_regex(
            r'AntiForgeryToken\s*:\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
            webpage, 'anti forgery token', default=None, group='value')
        if anti_forgery_token:
            anti_forgery_header = self._search_regex(
                r'AntiForgeryHeaderName\s*:\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
                webpage, 'anti forgery header name',
                default='X-SOFO-AntiForgeryHeader', group='value')

        data = {
            'IsViewPage': True,
            'IsNewFolder': True,
            'AuthTicket': None,
            'CatalogId': catalog_id,
            'CurrentFolderId': current_folder_id,
            'RootDynamicFolderId': root_dynamic_folder_id,
            'ItemsPerPage': 1000,
            'PageIndex': 0,
            'PermissionMask': 'Execute',
            'CatalogSearchType': 'SearchInFolder',
            'SortBy': 'Date',
            'SortDirection': 'Descending',
            'StartDate': None,
            'EndDate': None,
            'StatusFilterList': None,
            'PreviewKey': None,
            'Tags': [],
        }

        headers = {
            'Content-Type': 'application/json; charset=UTF-8',
            'Referer': url,
            'X-Requested-With': 'XMLHttpRequest',
        }
        if anti_forgery_token:
            headers[anti_forgery_header] = anti_forgery_token

        catalog = self._download_json(
            '%s/Catalog/Data/GetPresentationsForFolder' % mediasite_url,
            catalog_id, data=json.dumps(data).encode(), headers=headers)

        entries = []
        for video in catalog['PresentationDetailsList']:
            if not isinstance(video, dict):
                continue
            video_id = str_or_none(video.get('Id'))
            if not video_id:
                continue
            entries.append(self.url_result(
                '%s/Play/%s' % (mediasite_url, video_id),
                ie=MediasiteIE.ie_key(), video_id=video_id))

        title = try_get(
            catalog, lambda x: x['CurrentFolder']['Name'], compat_str)

        return self.playlist_result(entries, catalog_id, title,)

class MediasiteNamedCatalogIE(InfoExtractor):
    _VALID_URL = r'(?xi)(?P<url>https?://[^/]+/Mediasite)/Catalog/catalogs/(?P<catalog_name>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://msite.misis.ru/Mediasite/Catalog/catalogs/2016-industrial-management-skriabin-o-o',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        mediasite_url = mobj.group('url')
        catalog_name = mobj.group('catalog_name')

        webpage = self._download_webpage(url, catalog_name)

        catalog_id = self._search_regex(
            r'CatalogId\s*:\s*["\'](%s)' % _ID_RE, webpage, 'catalog id')

        return self.url_result(
            '%s/Catalog/Full/%s' % (mediasite_url, catalog_id),
            ie=MediasiteCatalogIE.ie_key(), video_id=catalog_id)


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import (
    unified_strdate,
    update_url_query,
    urlencode_postdata,
)

class MediciIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?medici\.tv/#!/(?P<id>[^?#&]+)'
    _TEST = {
        'url': 'http://www.medici.tv/#!/daniel-harding-frans-helmerson-verbier-festival-music-camp',
        'md5': '004c21bb0a57248085b6ff3fec72719d',
        'info_dict': {
            'id': '3059',
            'ext': 'flv',
            'title': 'Daniel Harding conducts the Verbier Festival Music Camp \u2013 With Frans Helmerson',
            'description': 'md5:322a1e952bafb725174fd8c1a8212f58',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20170408',
        },
    }

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

        # Sets csrftoken cookie
        self._download_webpage(url, video_id)

        MEDICI_URL = 'http://www.medici.tv/'

        data = self._download_json(
            MEDICI_URL, video_id,
            data=urlencode_postdata({
                'json': 'true',
                'page': '/%s' % video_id,
                'timezone_offset': -420,
            }), headers={
                'X-CSRFToken': self._get_cookies(url)['csrftoken'].value,
                'X-Requested-With': 'XMLHttpRequest',
                'Referer': MEDICI_URL,
                'Content-Type': 'application/x-www-form-urlencoded',
            })

        video = data['video']['videos']['video1']

        title = video.get('nom') or data['title']

        video_id = video.get('id') or video_id
        formats = self._extract_f4m_formats(
            update_url_query(video['url_akamai'], {
                'hdcore': '3.1.0',
                'plugin=aasp': '3.1.0.43.124',
            }), video_id, f4m_id='hds')

        description = data.get('meta_description')
        thumbnail = video.get('url_thumbnail') or data.get('main_image')
        upload_date = unified_strdate(data['video'].get('date'))

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


code:
# coding: utf-8
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import js_to_json

class MegaphoneIE(InfoExtractor):
    IE_NAME = 'megaphone.fm'
    IE_DESC = 'megaphone.fm embedded players'
    _VALID_URL = r'https://player\.megaphone\.fm/(?P<id>[A-Z0-9]+)'
    _TEST = {
        'url': 'https://player.megaphone.fm/GLT9749789991?"',
        'md5': '4816a0de523eb3e972dc0dda2c191f96',
        'info_dict': {
            'id': 'GLT9749789991',
            'ext': 'mp3',
            'title': '#97 What Kind Of Idiot Gets Phished?',
            'thumbnail': r're:^https://.*\.png.*$',
            'duration': 1776.26375,
            'author': 'Reply All',
        },
    }

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

        title = self._og_search_property('audio:title', webpage)
        author = self._og_search_property('audio:artist', webpage)
        thumbnail = self._og_search_thumbnail(webpage)

        episode_json = self._search_regex(r'(?s)var\s+episode\s*=\s*(\{.+?\});', webpage, 'episode JSON')
        episode_data = self._parse_json(episode_json, video_id, js_to_json)
        video_url = self._proto_relative_url(episode_data['mediaUrl'], 'https:')

        formats = [{
            'url': video_url,
        }]

        return {
            'id': video_id,
            'thumbnail': thumbnail,
            'title': title,
            'author': author,
            'duration': episode_data['duration'],
            'formats': formats,
        }

    @classmethod
    def _extract_urls(cls, webpage):
        return [m[0] for m in re.findall(
            r'<iframe[^>]*?\ssrc=["\'](%s)' % cls._VALID_URL, webpage)]


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    parse_duration,
    unified_timestamp,
)

class MeipaiIE(InfoExtractor):
    IE_DESC = '美拍'
    _VALID_URL = r'https?://(?:www\.)?meipai\.com/media/(?P<id>[0-9]+)'
    _TESTS = [{
        # regular uploaded video
        'url': 'http://www.meipai.com/media/531697625',
        'md5': 'e3e9600f9e55a302daecc90825854b4f',
        'info_dict': {
            'id': '531697625',
            'ext': 'mp4',
            'title': '#葉子##阿桑##余姿昀##超級女聲#',
            'description': '#葉子##阿桑##余姿昀##超級女聲#',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 152,
            'timestamp': 1465492420,
            'upload_date': '20160609',
            'view_count': 35511,
            'creator': '她她-TATA',
            'tags': ['葉子', '阿桑', '余姿昀', '超級女聲'],
        }
    }, {
        # record of live streaming
        'url': 'http://www.meipai.com/media/585526361',
        'md5': 'ff7d6afdbc6143342408223d4f5fb99a',
        'info_dict': {
            'id': '585526361',
            'ext': 'mp4',
            'title': '姿昀和善願 練歌練琴啦😁😁😁',
            'description': '姿昀和善願 練歌練琴啦😁😁😁',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 5975,
            'timestamp': 1474311799,
            'upload_date': '20160919',
            'view_count': 1215,
            'creator': '她她-TATA',
        }
    }]

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

        title = self._og_search_title(
            webpage, default=None) or self._html_search_regex(
            r'<title[^>]*>([^<]+)</title>', webpage, 'title')

        formats = []

        # recorded playback of live streaming
        m3u8_url = self._html_search_regex(
            r'file:\s*encodeURIComponent\((["\'])(?P<url>(?:(?!\1).)+)\1\)',
            webpage, 'm3u8 url', group='url', default=None)
        if m3u8_url:
            formats.extend(self._extract_m3u8_formats(
                m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native',
                m3u8_id='hls', fatal=False))

        if not formats:
            # regular uploaded video
            video_url = self._search_regex(
                r'data-video=(["\'])(?P<url>(?:(?!\1).)+)\1', webpage, 'video url',
                group='url', default=None)
            if video_url:
                formats.append({
                    'url': video_url,
                    'format_id': 'http',
                })

        timestamp = unified_timestamp(self._og_search_property(
            'video:release_date', webpage, 'release date', fatal=False))

        tags = self._og_search_property(
            'video:tag', webpage, 'tags', default='').split(',')

        view_count = int_or_none(self._html_search_meta(
            'interactionCount', webpage, 'view count'))
        duration = parse_duration(self._html_search_meta(
            'duration', webpage, 'duration'))
        creator = self._og_search_property(
            'video:director', webpage, 'creator', fatal=False)

        return {
            'id': video_id,
            'title': title,
            'description': self._og_search_description(webpage),
            'thumbnail': self._og_search_thumbnail(webpage),
            'duration': duration,
            'timestamp': timestamp,
            'view_count': view_count,
            'creator': creator,
            'tags': tags,
            'formats': formats,
        }


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    urljoin,
)

class MelonVODIE(InfoExtractor):
    _VALID_URL = r'https?://vod\.melon\.com/video/detail2\.html?\?.*?mvId=(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://vod.melon.com/video/detail2.htm?mvId=50158734',
        'info_dict': {
            'id': '50158734',
            'ext': 'mp4',
            'title': "Jessica 'Wonderland' MV Making Film",
            'thumbnail': r're:^https?://.*\.jpg$',
            'artist': 'Jessica (제시카)',
            'upload_date': '20161212',
            'duration': 203,
        },
        'params': {
            'skip_download': 'm3u8 download',
        }
    }

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

        play_info = self._download_json(
            'http://vod.melon.com/video/playerInfo.json', video_id,
            note='Downloading player info JSON', query={'mvId': video_id})

        title = play_info['mvInfo']['MVTITLE']

        info = self._download_json(
            'http://vod.melon.com/delivery/streamingInfo.json', video_id,
            note='Downloading streaming info JSON',
            query={
                'contsId': video_id,
                'contsType': 'VIDEO',
            })

        stream_info = info['streamingInfo']

        formats = self._extract_m3u8_formats(
            stream_info['encUrl'], video_id, 'mp4', m3u8_id='hls')
        self._sort_formats(formats)

        artist_list = play_info.get('artistList')
        artist = None
        if isinstance(artist_list, list):
            artist = ', '.join(
                [a['ARTISTNAMEWEBLIST']
                 for a in artist_list if a.get('ARTISTNAMEWEBLIST')])

        thumbnail = urljoin(info.get('staticDomain'), stream_info.get('imgPath'))

        duration = int_or_none(stream_info.get('playTime'))
        upload_date = stream_info.get('mvSvcOpenDt', '')[:8] or None

        return {
            'id': video_id,
            'title': title,
            'artist': artist,
            'thumbnail': thumbnail,
            'upload_date': upload_date,
            'duration': duration,
            'formats': formats
        }


code:
# coding: utf-8
from __future__ import unicode_literals

from .common import InfoExtractor
from .pladform import PladformIE
from ..utils import (
    unescapeHTML,
    int_or_none,
    ExtractorError,
)

class METAIE(InfoExtractor):
    _VALID_URL = r'https?://video\.meta\.ua/(?:iframe/)?(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://video.meta.ua/5502115.video',
        'md5': '71b6f3ee274bef16f1ab410f7f56b476',
        'info_dict': {
            'id': '5502115',
            'ext': 'mp4',
            'title': 'Sony Xperia Z camera test [HQ]',
            'description': 'Xperia Z shoots video in FullHD HDR.',
            'uploader_id': 'nomobile',
            'uploader': 'CHЁZA.TV',
            'upload_date': '20130211',
        },
        'add_ie': ['Youtube'],
    }, {
        'url': 'http://video.meta.ua/iframe/5502115',
        'only_matching': True,
    }, {
        # pladform embed
        'url': 'http://video.meta.ua/7121015.video',
        'only_matching': True,
    }]

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

        st_html5 = self._search_regex(
            r"st_html5\s*=\s*'#([^']+)'", webpage, 'uppod html5 st', default=None)

        if st_html5:
            # uppod st decryption algorithm is reverse engineered from function un(s) at uppod.js
            json_str = ''
            for i in range(0, len(st_html5), 3):
                json_str += '&#x0%s;' % st_html5[i:i + 3]
            uppod_data = self._parse_json(unescapeHTML(json_str), video_id)
            error = uppod_data.get('customnotfound')
            if error:
                raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)

            video_url = uppod_data['file']
            info = {
                'id': video_id,
                'url': video_url,
                'title': uppod_data.get('comment') or self._og_search_title(webpage),
                'description': self._og_search_description(webpage, default=None),
                'thumbnail': uppod_data.get('poster') or self._og_search_thumbnail(webpage),
                'duration': int_or_none(self._og_search_property(
                    'video:duration', webpage, default=None)),
            }
            if 'youtube.com/' in video_url:
                info.update({
                    '_type': 'url_transparent',
                    'ie_key': 'Youtube',
                })
            return info

        pladform_url = PladformIE._extract_url(webpage)
        if pladform_url:
            return self.url_result(pladform_url)