code:
from __future__ import unicode_literals

import json
import re

from .common import InfoExtractor
from ..compat import (
    compat_parse_qs,
    compat_urllib_parse,
    compat_urllib_parse_unquote,
)
from ..utils import (
    determine_ext,
    ExtractorError,
    int_or_none,
    get_element_by_attribute,
    mimetype2ext,
)

class MetacafeIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?metacafe\.com/watch/(?P<video_id>[^/]+)/(?P<display_id>[^/?#]+)'
    _DISCLAIMER = 'http://www.metacafe.com/family_filter/'
    _FILTER_POST = 'http://www.metacafe.com/f/index.php?inputType=filter&controllerGroup=user'
    IE_NAME = 'metacafe'
    _TESTS = [
        # Youtube video
        {
            'add_ie': ['Youtube'],
            'url': 'http://metacafe.com/watch/yt-_aUehQsCQtM/the_electric_company_short_i_pbs_kids_go/',
            'info_dict': {
                'id': '_aUehQsCQtM',
                'ext': 'mp4',
                'upload_date': '20090102',
                'title': 'The Electric Company | "Short I" | PBS KIDS GO!',
                'description': 'md5:2439a8ef6d5a70e380c22f5ad323e5a8',
                'uploader': 'PBS',
                'uploader_id': 'PBS'
            }
        },
        # Normal metacafe video
        {
            'url': 'http://www.metacafe.com/watch/11121940/news_stuff_you_wont_do_with_your_playstation_4/',
            'md5': '6e0bca200eaad2552e6915ed6fd4d9ad',
            'info_dict': {
                'id': '11121940',
                'ext': 'mp4',
                'title': 'News: Stuff You Won\'t Do with Your PlayStation 4',
                'uploader': 'ign',
                'description': 'Sony released a massive FAQ on the PlayStation Blog detailing the PS4\'s capabilities and limitations.',
            },
            'skip': 'Page is temporarily unavailable.',
        },
        # metacafe video with family filter
        {
            'url': 'http://www.metacafe.com/watch/2155630/adult_art_by_david_hart_156/',
            'md5': 'b06082c5079bbdcde677a6291fbdf376',
            'info_dict': {
                'id': '2155630',
                'ext': 'mp4',
                'title': 'Adult Art By David Hart 156',
                'uploader': '63346',
                'description': 'md5:9afac8fc885252201ad14563694040fc',
            },
            'params': {
                'skip_download': True,
            },
        },
        # AnyClip video
        {
            'url': 'http://www.metacafe.com/watch/an-dVVXnuY7Jh77J/the_andromeda_strain_1971_stop_the_bomb_part_3/',
            'info_dict': {
                'id': 'an-dVVXnuY7Jh77J',
                'ext': 'mp4',
                'title': 'The Andromeda Strain (1971): Stop the Bomb Part 3',
                'uploader': 'AnyClip',
                'description': 'md5:cbef0460d31e3807f6feb4e7a5952e5b',
            },
        },
        # age-restricted video
        {
            'url': 'http://www.metacafe.com/watch/5186653/bbc_internal_christmas_tape_79_uncensored_outtakes_etc/',
            'md5': '98dde7c1a35d02178e8ab7560fe8bd09',
            'info_dict': {
                'id': '5186653',
                'ext': 'mp4',
                'title': 'BBC INTERNAL Christmas Tape \'79 - UNCENSORED Outtakes, Etc.',
                'uploader': 'Dwayne Pipe',
                'description': 'md5:950bf4c581e2c059911fa3ffbe377e4b',
                'age_limit': 18,
            },
        },
        # cbs video
        {
            'url': 'http://www.metacafe.com/watch/cb-8VD4r_Zws8VP/open_this_is_face_the_nation_february_9/',
            'info_dict': {
                'id': '8VD4r_Zws8VP',
                'ext': 'flv',
                'title': 'Open: This is Face the Nation, February 9',
                'description': 'md5:8a9ceec26d1f7ed6eab610834cc1a476',
                'duration': 96,
                'uploader': 'CBSI-NEW',
                'upload_date': '20140209',
                'timestamp': 1391959800,
            },
            'params': {
                # rtmp download
                'skip_download': True,
            },
        },
        # Movieclips.com video
        {
            'url': 'http://www.metacafe.com/watch/mv-Wy7ZU/my_week_with_marilyn_do_you_love_me/',
            'info_dict': {
                'id': 'mv-Wy7ZU',
                'ext': 'mp4',
                'title': 'My Week with Marilyn - Do You Love Me?',
                'description': 'From the movie My Week with Marilyn - Colin (Eddie Redmayne) professes his love to Marilyn (Michelle Williams) and gets her to promise to return to set and finish the movie.',
                'uploader': 'movie_trailers',
                'duration': 176,
            },
            'params': {
                'skip_download': 'requires rtmpdump',
            }
        }
    ]

    def report_disclaimer(self):
        self.to_screen('Retrieving disclaimer')

    def _real_extract(self, url):
        # Extract id and simplified title from URL
        video_id, display_id = re.match(self._VALID_URL, url).groups()

        # the video may come from an external site
        m_external = re.match(r'^(\w{2})-(.*)$', video_id)
        if m_external is not None:
            prefix, ext_id = m_external.groups()
            # Check if video comes from YouTube
            if prefix == 'yt':
                return self.url_result('http://www.youtube.com/watch?v=%s' % ext_id, 'Youtube')
            # CBS videos use theplatform.com
            if prefix == 'cb':
                return self.url_result('theplatform:%s' % ext_id, 'ThePlatform')

        headers = {
            # Disable family filter
            'Cookie': 'user=%s; ' % compat_urllib_parse.quote(json.dumps({'ffilter': False}))
        }

        # AnyClip videos require the flashversion cookie so that we get the link
        # to the mp4 file
        if video_id.startswith('an-'):
            headers['Cookie'] += 'flashVersion=0; '

        # Retrieve video webpage to extract further information
        webpage = self._download_webpage(url, video_id, headers=headers)

        error = get_element_by_attribute(
            'class', 'notfound-page-title', webpage)
        if error:
            raise ExtractorError(error, expected=True)

        video_title = self._html_search_meta(
            ['og:title', 'twitter:title'], webpage, 'title', default=None) or self._search_regex(r'<h1>(.*?)</h1>', webpage, 'title')

        # Extract URL, uploader and title from webpage
        self.report_extraction(video_id)
        video_url = None
        mobj = re.search(r'(?m)&(?:media|video)URL=([^&]+)', webpage)
        if mobj is not None:
            mediaURL = compat_urllib_parse_unquote(mobj.group(1))
            video_ext = determine_ext(mediaURL)

            # Extract gdaKey if available
            mobj = re.search(r'(?m)&gdaKey=(.*?)&', webpage)
            if mobj is None:
                video_url = mediaURL
            else:
                gdaKey = mobj.group(1)
                video_url = '%s?__gda__=%s' % (mediaURL, gdaKey)
        if video_url is None:
            mobj = re.search(r'<video src="([^"]+)"', webpage)
            if mobj:
                video_url = mobj.group(1)
                video_ext = 'mp4'
        if video_url is None:
            flashvars = self._search_regex(
                r' name="flashvars" value="(.*?)"', webpage, 'flashvars',
                default=None)
            if flashvars:
                vardict = compat_parse_qs(flashvars)
                if 'mediaData' not in vardict:
                    raise ExtractorError('Unable to extract media URL')
                mobj = re.search(
                    r'"mediaURL":"(?P<mediaURL>http.*?)",(.*?)"key":"(?P<key>.*?)"', vardict['mediaData'][0])
                if mobj is None:
                    raise ExtractorError('Unable to extract media URL')
                mediaURL = mobj.group('mediaURL').replace('\\/', '/')
                video_url = '%s?__gda__=%s' % (mediaURL, mobj.group('key'))
                video_ext = determine_ext(video_url)
        if video_url is None:
            player_url = self._search_regex(
                r"swfobject\.embedSWF\('([^']+)'",
                webpage, 'config URL', default=None)
            if player_url:
                config_url = self._search_regex(
                    r'config=(.+)$', player_url, 'config URL')
                config_doc = self._download_xml(
                    config_url, video_id,
                    note='Downloading video config')
                smil_url = config_doc.find('.//properties').attrib['smil_file']
                smil_doc = self._download_xml(
                    smil_url, video_id,
                    note='Downloading SMIL document')
                base_url = smil_doc.find('./head/meta').attrib['base']
                video_url = []
                for vn in smil_doc.findall('.//video'):
                    br = int(vn.attrib['system-bitrate'])
                    play_path = vn.attrib['src']
                    video_url.append({
                        'format_id': 'smil-%d' % br,
                        'url': base_url,
                        'play_path': play_path,
                        'page_url': url,
                        'player_url': player_url,
                        'ext': play_path.partition(':')[0],
                    })
        if video_url is None:
            flashvars = self._parse_json(self._search_regex(
                r'flashvars\s*=\s*({.*});', webpage, 'flashvars',
                default=None), video_id, fatal=False)
            if flashvars:
                video_url = []
                for source in flashvars.get('sources'):
                    source_url = source.get('src')
                    if not source_url:
                        continue
                    ext = mimetype2ext(source.get('type')) or determine_ext(source_url)
                    if ext == 'm3u8':
                        video_url.extend(self._extract_m3u8_formats(
                            source_url, video_id, 'mp4',
                            'm3u8_native', m3u8_id='hls', fatal=False))
                    else:
                        video_url.append({
                            'url': source_url,
                            'ext': ext,
                        })

        if video_url is None:
            raise ExtractorError('Unsupported video type')

        description = self._html_search_meta(
            ['og:description', 'twitter:description', 'description'],
            webpage, 'title', fatal=False)
        thumbnail = self._html_search_meta(
            ['og:image', 'twitter:image'], webpage, 'title', fatal=False)
        video_uploader = self._html_search_regex(
            r'submitter=(.*?);|googletag\.pubads\(\)\.setTargeting\("(?:channel|submiter)","([^"]+)"\);',
            webpage, 'uploader nickname', fatal=False)
        duration = int_or_none(
            self._html_search_meta('video:duration', webpage, default=None))
        age_limit = (
            18
            if re.search(r'(?:"contentRating":|"rating",)"restricted"', webpage)
            else 0)

        if isinstance(video_url, list):
            formats = video_url
        else:
            formats = [{
                'url': video_url,
                'ext': video_ext,
            }]
        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': display_id,
            'description': description,
            'uploader': video_uploader,
            'title': video_title,
            'thumbnail': thumbnail,
            'age_limit': age_limit,
            'formats': formats,
            'duration': duration,
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    fix_xml_ampersands,
)

class MetacriticIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?metacritic\.com/.+?/trailers/(?P<id>\d+)'

    _TESTS = [{
        'url': 'http://www.metacritic.com/game/playstation-4/infamous-second-son/trailers/3698222',
        'info_dict': {
            'id': '3698222',
            'ext': 'mp4',
            'title': 'inFamous: Second Son - inSide Sucker Punch: Smoke & Mirrors',
            'description': 'Take a peak behind-the-scenes to see how Sucker Punch brings smoke into the universe of inFAMOUS Second Son on the PS4.',
            'duration': 221,
        },
        'skip': 'Not providing trailers anymore',
    }, {
        'url': 'http://www.metacritic.com/game/playstation-4/tales-from-the-borderlands-a-telltale-game-series/trailers/5740315',
        'info_dict': {
            'id': '5740315',
            'ext': 'mp4',
            'title': 'Tales from the Borderlands - Finale: The Vault of the Traveler',
            'description': 'In the final episode of the season, all hell breaks loose. Jack is now in control of Helios\' systems, and he\'s ready to reclaim his rightful place as king of Hyperion (with or without you).',
            'duration': 114,
        },
    }]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id')
        webpage = self._download_webpage(url, video_id)
        # The xml is not well formatted, there are raw '&'
        info = self._download_xml('http://www.metacritic.com/video_data?video=' + video_id,
                                  video_id, 'Downloading info xml', transform_source=fix_xml_ampersands)

        clip = next(c for c in info.findall('playList/clip') if c.find('id').text == video_id)
        formats = []
        for videoFile in clip.findall('httpURI/videoFile'):
            rate_str = videoFile.find('rate').text
            video_url = videoFile.find('filePath').text
            formats.append({
                'url': video_url,
                'ext': 'mp4',
                'format_id': rate_str,
                'tbr': int(rate_str),
            })
        self._sort_formats(formats)

        description = self._html_search_regex(r'Description:(.*?)
',
                                              webpage, 'description', flags=re.DOTALL)

        return {
            'id': video_id,
            'title': clip.find('title').text,
            'formats': formats,
            'description': description,
            'duration': int(clip.find('duration').text),
        }

repo_name: rrosajp/youtube


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

import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    qualities,
    unified_strdate,
)

class MgoonIE(InfoExtractor):
    _VALID_URL = r'''(?x)https?://(?:www\.)?
    (?:(:?m\.)?mgoon\.com/(?:ch/(?:.+)/v|play/view)|
        video\.mgoon\.com)/(?P<id>[0-9]+)'''
    _API_URL = 'http://mpos.mgoon.com/player/video?id={0:}'
    _TESTS = [
        {
            'url': 'http://m.mgoon.com/ch/hi6618/v/5582148',
            'md5': 'dd46bb66ab35cf6d51cc812fd82da79d',
            'info_dict': {
                'id': '5582148',
                'uploader_id': 'hi6618',
                'duration': 240.419,
                'upload_date': '20131220',
                'ext': 'mp4',
                'title': 'md5:543aa4c27a4931d371c3f433e8cebebc',
                'thumbnail': r're:^https?://.*\.jpg$',
            }
        },
        {
            'url': 'http://www.mgoon.com/play/view/5582148',
            'only_matching': True,
        },
        {
            'url': 'http://video.mgoon.com/5582148',
            'only_matching': True,
        },
    ]

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

        data = self._download_json(self._API_URL.format(video_id), video_id)

        if data.get('errorInfo', {}).get('code') != 'NONE':
            raise ExtractorError('%s encountered an error: %s' % (
                self.IE_NAME, data['errorInfo']['message']), expected=True)

        v_info = data['videoInfo']
        title = v_info.get('v_title')
        thumbnail = v_info.get('v_thumbnail')
        duration = v_info.get('v_duration')
        upload_date = unified_strdate(v_info.get('v_reg_date'))
        uploader_id = data.get('userInfo', {}).get('u_alias')
        if duration:
            duration /= 1000.0

        age_limit = None
        if data.get('accessInfo', {}).get('code') == 'VIDEO_STATUS_ADULT':
            age_limit = 18

        formats = []
        get_quality = qualities(['360p', '480p', '720p', '1080p'])
        for fmt in data['videoFiles']:
            formats.append({
                'format_id': fmt['label'],
                'quality': get_quality(fmt['label']),
                'url': fmt['url'],
                'ext': fmt['format'],

            })
        self._sort_formats(formats)

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


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

import base64
import time
import uuid

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

class MGTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mgtv\.com/(v|b)/(?:[^/]+/)*(?P<id>\d+)\.html'
    IE_DESC = '芒果TV'
    _GEO_COUNTRIES = ['CN']

    _TESTS = [{
        'url': 'http://www.mgtv.com/v/1/290525/f/3116640.html',
        'info_dict': {
            'id': '3116640',
            'ext': 'mp4',
            'title': '我是歌手 第四季',
            'description': '我是歌手第四季双年巅峰会',
            'duration': 7461,
            'thumbnail': r're:^https?://.*\.jpg$',
        },
    }, {
        'url': 'http://www.mgtv.com/b/301817/3826653.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        try:
            api_data = self._download_json(
                'https://pcweb.api.mgtv.com/player/video', video_id, query={
                    'tk2': base64.urlsafe_b64encode(b'did=%s|pno=1030|ver=0.3.0301|clit=%d' % (compat_str(uuid.uuid4()).encode(), time.time()))[::-1],
                    'video_id': video_id,
                }, headers=self.geo_verification_headers())['data']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                error = self._parse_json(e.cause.read().decode(), None)
                if error.get('code') == 40005:
                    self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
                raise ExtractorError(error['msg'], expected=True)
            raise
        info = api_data['info']
        title = info['title'].strip()
        stream_data = self._download_json(
            'https://pcweb.api.mgtv.com/player/getSource', video_id, query={
                'pm2': api_data['atc']['pm2'],
                'video_id': video_id,
            }, headers=self.geo_verification_headers())['data']
        stream_domain = stream_data['stream_domain'][0]

        formats = []
        for idx, stream in enumerate(stream_data['stream']):
            stream_path = stream.get('url')
            if not stream_path:
                continue
            format_data = self._download_json(
                stream_domain + stream_path, video_id,
                note='Download video info for format #%d' % idx)
            format_url = format_data.get('info')
            if not format_url:
                continue
            tbr = int_or_none(stream.get('filebitrate') or self._search_regex(
                r'_(\d+)_mp4/', format_url, 'tbr', default=None))
            formats.append({
                'format_id': compat_str(tbr or idx),
                'url': format_url,
                'ext': 'mp4',
                'tbr': tbr,
                'protocol': 'm3u8_native',
                'http_headers': {
                    'Referer': url,
                },
                'format_note': stream.get('name'),
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': info.get('desc'),
            'duration': int_or_none(info.get('duration')),
            'thumbnail': info.get('thumb'),
        }


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

from .common import InfoExtractor

class MiaoPaiIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?miaopai\.com/show/(?P<id>[-A-Za-z0-9~_]+)'
    _TEST = {
        'url': 'http://www.miaopai.com/show/n~0hO7sfV1nBEw4Y29-Hqg__.htm',
        'md5': '095ed3f1cd96b821add957bdc29f845b',
        'info_dict': {
            'id': 'n~0hO7sfV1nBEw4Y29-Hqg__',
            'ext': 'mp4',
            'title': '西游记音乐会的秒拍视频',
            'thumbnail': 're:^https?://.*/n~0hO7sfV1nBEw4Y29-Hqg___m.jpg',
        }
    }

    _USER_AGENT_IPAD = 'Mozilla/5.0 (iPad; CPU OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1'

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(
            url, video_id, headers={'User-Agent': self._USER_AGENT_IPAD})

        title = self._html_search_regex(
            r'<title>([^<]+)</title>', webpage, 'title')
        thumbnail = self._html_search_regex(
            r'<div[^>]+class=(?P<q1>[\'"]).*\bvideo_img\b.*(?P=q1)[^>]+data-url=(?P<q2>[\'"])(?P<url>[^\'"]+)(?P=q2)',
            webpage, 'thumbnail', fatal=False, group='url')
        videos = self._parse_html5_media_entries(url, webpage, video_id)
        info = videos[0]

        info.update({
            'id': video_id,
            'title': title,
            'thumbnail': thumbnail,
        })
        return info


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import (
    compat_xpath,
)
from ..utils import (
    int_or_none,
    parse_duration,
    smuggle_url,
    unsmuggle_url,
    xpath_text,
)

class MicrosoftVirtualAcademyBaseIE(InfoExtractor):
    def _extract_base_url(self, course_id, display_id):
        return self._download_json(
            'https://api-mlxprod.microsoft.com/services/products/anonymous/%s' % course_id,
            display_id, 'Downloading course base URL')

    def _extract_chapter_and_title(self, title):
        if not title:
            return None, None
        m = re.search(r'(?P<chapter>\d+)\s*\|\s*(?P<title>.+)', title)
        return (int(m.group('chapter')), m.group('title')) if m else (None, title)

class MicrosoftVirtualAcademyIE(MicrosoftVirtualAcademyBaseIE):
    IE_NAME = 'mva'
    IE_DESC = 'Microsoft Virtual Academy videos'
    _VALID_URL = r'(?:%s:|https?://(?:mva\.microsoft|(?:www\.)?microsoftvirtualacademy)\.com/[^/]+/training-courses/[^/?#&]+-)(?P<course_id>\d+)(?::|\?l=)(?P<id>[\da-zA-Z]+_\d+)' % IE_NAME

    _TESTS = [{
        'url': 'https://mva.microsoft.com/en-US/training-courses/microsoft-azure-fundamentals-virtual-machines-11788?l=gfVXISmEB_6804984382',
        'md5': '7826c44fc31678b12ad8db11f6b5abb9',
        'info_dict': {
            'id': 'gfVXISmEB_6804984382',
            'ext': 'mp4',
            'title': 'Course Introduction',
            'formats': 'mincount:3',
            'subtitles': {
                'en': [{
                    'ext': 'ttml',
                }],
            },
        }
    }, {
        'url': 'mva:11788:gfVXISmEB_6804984382',
        'only_matching': True,
    }]

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

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

        base_url = smuggled_data.get('base_url') or self._extract_base_url(course_id, video_id)

        settings = self._download_xml(
            '%s/content/content_%s/videosettings.xml?v=1' % (base_url, video_id),
            video_id, 'Downloading video settings XML')

        _, title = self._extract_chapter_and_title(xpath_text(
            settings, './/Title', 'title', fatal=True))

        formats = []

        for sources in settings.findall(compat_xpath('.//MediaSources')):
            sources_type = sources.get('videoType')
            for source in sources.findall(compat_xpath('./MediaSource')):
                video_url = source.text
                if not video_url or not video_url.startswith('http'):
                    continue
                if sources_type == 'smoothstreaming':
                    formats.extend(self._extract_ism_formats(
                        video_url, video_id, 'mss', fatal=False))
                    continue
                video_mode = source.get('videoMode')
                height = int_or_none(self._search_regex(
                    r'^(\d+)[pP]$', video_mode or '', 'height', default=None))
                codec = source.get('codec')
                acodec, vcodec = [None] * 2
                if codec:
                    codecs = codec.split(',')
                    if len(codecs) == 2:
                        acodec, vcodec = codecs
                    elif len(codecs) == 1:
                        vcodec = codecs[0]
                formats.append({
                    'url': video_url,
                    'format_id': video_mode,
                    'height': height,
                    'acodec': acodec,
                    'vcodec': vcodec,
                })
        self._sort_formats(formats)

        subtitles = {}
        for source in settings.findall(compat_xpath('.//MarkerResourceSource')):
            subtitle_url = source.text
            if not subtitle_url:
                continue
            subtitles.setdefault('en', []).append({
                'url': '%s/%s' % (base_url, subtitle_url),
                'ext': source.get('type'),
            })

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

class MicrosoftVirtualAcademyCourseIE(MicrosoftVirtualAcademyBaseIE):
    IE_NAME = 'mva:course'
    IE_DESC = 'Microsoft Virtual Academy courses'
    _VALID_URL = r'(?:%s:|https?://(?:mva\.microsoft|(?:www\.)?microsoftvirtualacademy)\.com/[^/]+/training-courses/(?P<display_id>[^/?#&]+)-)(?P<id>\d+)' % IE_NAME

    _TESTS = [{
        'url': 'https://mva.microsoft.com/en-US/training-courses/microsoft-azure-fundamentals-virtual-machines-11788',
        'info_dict': {
            'id': '11788',
            'title': 'Microsoft Azure Fundamentals: Virtual Machines',
        },
        'playlist_count': 36,
    }, {
        # with emphasized chapters
        'url': 'https://mva.microsoft.com/en-US/training-courses/developing-windows-10-games-with-construct-2-16335',
        'info_dict': {
            'id': '16335',
            'title': 'Developing Windows 10 Games with Construct 2',
        },
        'playlist_count': 10,
    }, {
        'url': 'https://www.microsoftvirtualacademy.com/en-US/training-courses/microsoft-azure-fundamentals-virtual-machines-11788',
        'only_matching': True,
    }, {
        'url': 'mva:course:11788',
        'only_matching': True,
    }]

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

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

        base_url = self._extract_base_url(course_id, display_id)

        manifest = self._download_json(
            '%s/imsmanifestlite.json' % base_url,
            display_id, 'Downloading course manifest JSON')['manifest']

        organization = manifest['organizations']['organization'][0]

        entries = []
        for chapter in organization['item']:
            chapter_number, chapter_title = self._extract_chapter_and_title(chapter.get('title'))
            chapter_id = chapter.get('@identifier')
            for item in chapter.get('item', []):
                item_id = item.get('@identifier')
                if not item_id:
                    continue
                metadata = item.get('resource', {}).get('metadata') or {}
                if metadata.get('learningresourcetype') != 'Video':
                    continue
                _, title = self._extract_chapter_and_title(item.get('title'))
                duration = parse_duration(metadata.get('duration'))
                description = metadata.get('description')
                entries.append({
                    '_type': 'url_transparent',
                    'url': smuggle_url(
                        'mva:%s:%s' % (course_id, item_id), {'base_url': base_url}),
                    'title': title,
                    'description': description,
                    'duration': duration,
                    'chapter': chapter_title,
                    'chapter_number': chapter_number,
                    'chapter_id': chapter_id,
                })

        title = organization.get('title') or manifest.get('metadata', {}).get('title')

        return self.playlist_result(entries, course_id, title)


code:
from __future__ import unicode_literals

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

class MinistryGridIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?ministrygrid\.com/([^/?#]*/)*(?P<id>[^/#?]+)/?(?:$|[?#])'

    _TEST = {
        'url': 'http://www.ministrygrid.com/training-viewer/-/training/t4g-2014-conference/the-gospel-by-numbers-4/the-gospel-by-numbers',
        'md5': '844be0d2a1340422759c2a9101bab017',
        'info_dict': {
            'id': '3453494717001',
            'ext': 'mp4',
            'title': 'The Gospel by Numbers',
            'thumbnail': r're:^https?://.*\.jpg',
            'upload_date': '20140410',
            'description': 'Coming soon from T4G 2014!',
            'uploader_id': '2034960640001',
            'timestamp': 1397145591,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'add_ie': ['TDSLifeway'],
    }

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

        webpage = self._download_webpage(url, video_id)
        portlets = self._parse_json(self._search_regex(
            r'Liferay\.Portlet\.list=(\[.+?\])', webpage, 'portlet list'),
            video_id)
        pl_id = self._search_regex(
            r'getPlid:function\(\){return"(\d+)"}', webpage, 'p_l_id')

        for i, portlet in enumerate(portlets):
            portlet_url = 'http://www.ministrygrid.com/c/portal/render_portlet?p_l_id=%s&p_p_id=%s' % (pl_id, portlet)
            portlet_code = self._download_webpage(
                portlet_url, video_id,
                note='Looking in portlet %s (%d/%d)' % (portlet, i + 1, len(portlets)),
                fatal=False)
            video_iframe_url = self._search_regex(
                r'<iframe.*?src="([^"]+)"', portlet_code, 'video iframe',
                default=None)
            if video_iframe_url:
                return self.url_result(
                    smuggle_url(video_iframe_url, {'force_videoid': video_id}),
                    video_id=video_id)

        raise ExtractorError('Could not find video iframe in any portlets')


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

import re

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

class MinotoIE(InfoExtractor):
    _VALID_URL = r'(?:minoto:|https?://(?:play|iframe|embed)\.minoto-video\.com/(?P<player_id>[0-9]+)/)(?P<id>[a-zA-Z0-9]+)'

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        player_id = mobj.group('player_id') or '1'
        video_id = mobj.group('id')
        video_data = self._download_json('http://play.minoto-video.com/%s/%s.js' % (player_id, video_id), video_id)
        video_metadata = video_data['video-metadata']
        formats = []
        for fmt in video_data['video-files']:
            fmt_url = fmt.get('url')
            if not fmt_url:
                continue
            container = fmt.get('container')
            if container == 'hls':
                formats.extend(fmt_url, video_id, 'mp4', m3u8_id='hls', fatal=False)
            else:
                fmt_profile = fmt.get('profile') or {}
                formats.append({
                    'format_id': fmt_profile.get('name-short'),
                    'format_note': fmt_profile.get('name'),
                    'url': fmt_url,
                    'container': container,
                    'tbr': int_or_none(fmt.get('bitrate')),
                    'filesize': int_or_none(fmt.get('filesize')),
                    'width': int_or_none(fmt.get('width')),
                    'height': int_or_none(fmt.get('height')),
                    'codecs': parse_codecs(fmt.get('codecs')),
                })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video_metadata['title'],
            'description': video_metadata.get('description'),
            'thumbnail': video_metadata.get('video-poster', {}).get('url'),
            'formats': formats,
        }


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

import random

from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    xpath_text,
    int_or_none,
    ExtractorError,
    sanitized_Request,
)

class MioMioIE(InfoExtractor):
    IE_NAME = 'miomio.tv'
    _VALID_URL = r'https?://(?:www\.)?miomio\.tv/watch/cc(?P<id>[0-9]+)'
    _TESTS = [{
        # "type=video" in flashvars
        'url': 'http://www.miomio.tv/watch/cc88912/',
        'info_dict': {
            'id': '88912',
            'ext': 'flv',
            'title': '【SKY】字幕 铠武昭和VS平成 假面骑士大战FEAT战队 魔星字幕组 字幕',
            'duration': 5923,
        },
        'skip': 'Unable to load videos',
    }, {
        'url': 'http://www.miomio.tv/watch/cc184024/',
        'info_dict': {
            'id': '43729',
            'title': '《动漫同人插画绘制》',
        },
        'playlist_mincount': 86,
        'skip': 'Unable to load videos',
    }, {
        'url': 'http://www.miomio.tv/watch/cc173113/',
        'info_dict': {
            'id': '173113',
            'title': 'The New Macbook 2015 上手试玩与简评'
        },
        'playlist_mincount': 2,
        'skip': 'Unable to load videos',
    }, {
        # new 'h5' player
        'url': 'http://www.miomio.tv/watch/cc273997/',
        'md5': '0b27a4b4495055d826813f8c3a6b2070',
        'info_dict': {
            'id': '273997',
            'ext': 'mp4',
            'title': 'マツコの知らない世界【劇的進化SP！ビニール傘＆冷凍食品2016】 1_2 - 16 05 31',
        },
        'skip': 'Unable to load videos',
    }]

    def _extract_mioplayer(self, webpage, video_id, title, http_headers):
        xml_config = self._search_regex(
            r'flashvars="type=(?:sina|video)&(.+?)&',
            webpage, 'xml config')

        # skipping the following page causes lags and eventually connection drop-outs
        self._request_webpage(
            'http://www.miomio.tv/mioplayer/mioplayerconfigfiles/xml.php?id=%s&r=%s' % (id, random.randint(100, 999)),
            video_id)

        vid_config_request = sanitized_Request(
            'http://www.miomio.tv/mioplayer/mioplayerconfigfiles/sina.php?{0}'.format(xml_config),
            headers=http_headers)

        # the following xml contains the actual configuration information on the video file(s)
        vid_config = self._download_xml(vid_config_request, video_id)

        if not int_or_none(xpath_text(vid_config, 'timelength')):
            raise ExtractorError('Unable to load videos!', expected=True)

        entries = []
        for f in vid_config.findall('./durl'):
            segment_url = xpath_text(f, 'url', 'video url')
            if not segment_url:
                continue
            order = xpath_text(f, 'order', 'order')
            segment_id = video_id
            segment_title = title
            if order:
                segment_id += '-%s' % order
                segment_title += ' part %s' % order
            entries.append({
                'id': segment_id,
                'url': segment_url,
                'title': segment_title,
                'duration': int_or_none(xpath_text(f, 'length', 'duration'), 1000),
                'http_headers': http_headers,
            })

        return entries

    def _download_chinese_webpage(self, *args, **kwargs):
        # Requests with English locales return garbage
        headers = {
            'Accept-Language': 'zh-TW,en-US;q=0.7,en;q=0.3',
        }
        kwargs.setdefault('headers', {}).update(headers)
        return self._download_webpage(*args, **kwargs)

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

        title = self._html_search_meta(
            'description', webpage, 'title', fatal=True)

        mioplayer_path = self._search_regex(
            r'src="(/mioplayer(?:_h5)?/[^"]+)"', webpage, 'ref_path')

        if '_h5' in mioplayer_path:
            player_url = compat_urlparse.urljoin(url, mioplayer_path)
            player_webpage = self._download_chinese_webpage(
                player_url, video_id,
                note='Downloading player webpage', headers={'Referer': url})
            entries = self._parse_html5_media_entries(player_url, player_webpage, video_id)
            http_headers = {'Referer': player_url}
        else:
            http_headers = {'Referer': 'http://www.miomio.tv%s' % mioplayer_path}
            entries = self._extract_mioplayer(webpage, video_id, title, http_headers)

        if len(entries) == 1:
            segment = entries[0]
            segment['id'] = video_id
            segment['title'] = title
            segment['http_headers'] = http_headers
            return segment

        return {
            '_type': 'multi_video',
            'id': video_id,
            'entries': entries,
            'title': title,
            'http_headers': http_headers,
        }


code:
from __future__ import unicode_literals

import re
import json

from .common import InfoExtractor
from .youtube import YoutubeIE
from ..utils import (
    clean_html,
    ExtractorError,
    get_element_by_id,
)

class TechTVMITIE(InfoExtractor):
    IE_NAME = 'techtv.mit.edu'
    _VALID_URL = r'https?://techtv\.mit\.edu/(?:videos|embeds)/(?P<id>\d+)'

    _TEST = {
        'url': 'http://techtv.mit.edu/videos/25418-mit-dna-learning-center-set',
        'md5': '00a3a27ee20d44bcaa0933ccec4a2cf7',
        'info_dict': {
            'id': '25418',
            'ext': 'mp4',
            'title': 'MIT DNA and Protein Sets',
            'description': 'md5:46f5c69ce434f0a97e7c628cc142802d',
        },
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        raw_page = self._download_webpage(
            'http://techtv.mit.edu/videos/%s' % video_id, video_id)
        clean_page = re.compile(r'<!--.*?-->', re.S).sub('', raw_page)

        base_url = self._proto_relative_url(self._search_regex(
            r'ipadUrl: \'(.+?cloudfront.net/)', raw_page, 'base url'), 'http:')
        formats_json = self._search_regex(
            r'bitrates: (\[.+?\])', raw_page, 'video formats')
        formats_mit = json.loads(formats_json)
        formats = [
            {
                'format_id': f['label'],
                'url': base_url + f['url'].partition(':')[2],
                'ext': f['url'].partition(':')[0],
                'format': f['label'],
                'width': f['width'],
                'vbr': f['bitrate'],
            }
            for f in formats_mit
        ]

        title = get_element_by_id('edit-title', clean_page)
        description = clean_html(get_element_by_id('edit-description', clean_page))
        thumbnail = self._search_regex(
            r'playlist:.*?url: \'(.+?)\'',
            raw_page, 'thumbnail', flags=re.DOTALL)

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

class OCWMITIE(InfoExtractor):
    IE_NAME = 'ocw.mit.edu'
    _VALID_URL = r'^https?://ocw\.mit\.edu/courses/(?P<topic>[a-z0-9\-]+)'
    _BASE_URL = 'http://ocw.mit.edu/'

    _TESTS = [
        {
            'url': 'http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-041-probabilistic-systems-analysis-and-applied-probability-fall-2010/video-lectures/lecture-7-multiple-variables-expectations-independence/',
            'info_dict': {
                'id': 'EObHWIEKGjA',
                'ext': 'webm',
                'title': 'Lecture 7: Multiple Discrete Random Variables: Expectations, Conditioning, Independence',
                'description': 'In this lecture, the professor discussed multiple random variables, expectations, and binomial distribution.',
                'upload_date': '20121109',
                'uploader_id': 'MIT',
                'uploader': 'MIT OpenCourseWare',
            }
        },
        {
            'url': 'http://ocw.mit.edu/courses/mathematics/18-01sc-single-variable-calculus-fall-2010/1.-differentiation/part-a-definition-and-basic-rules/session-1-introduction-to-derivatives/',
            'info_dict': {
                'id': '7K1sB05pE0A',
                'ext': 'mp4',
                'title': 'Session 1: Introduction to Derivatives',
                'upload_date': '20090818',
                'uploader_id': 'MIT',
                'uploader': 'MIT OpenCourseWare',
                'description': 'This section contains lecture video excerpts, lecture notes, an interactive mathlet with supporting documents, and problem solving videos.',
            }
        }
    ]

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

        webpage = self._download_webpage(url, topic)
        title = self._html_search_meta('WT.cg_s', webpage)
        description = self._html_search_meta('Description', webpage)

        # search for call to ocw_embed_chapter_media(container_id, media_url, provider, page_url, image_url, start, stop, captions_file)
        embed_chapter_media = re.search(r'ocw_embed_chapter_media\((.+?)\)', webpage)
        if embed_chapter_media:
            metadata = re.sub(r'[\'"]', '', embed_chapter_media.group(1))
            metadata = re.split(r', ?', metadata)
            yt = metadata[1]
        else:
            # search for call to ocw_embed_chapter_media(container_id, media_url, provider, page_url, image_url, captions_file)
            embed_media = re.search(r'ocw_embed_media\((.+?)\)', webpage)
            if embed_media:
                metadata = re.sub(r'[\'"]', '', embed_media.group(1))
                metadata = re.split(r', ?', metadata)
                yt = metadata[1]
            else:
                raise ExtractorError('Unable to find embedded YouTube video.')
        video_id = YoutubeIE.extract_id(yt)

        return {
            '_type': 'url_transparent',
            'id': video_id,
            'title': title,
            'description': description,
            'url': yt,
            'ie_key': 'Youtube',
        }


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

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    parse_iso8601,
    smuggle_url,
)

class MiTeleIE(InfoExtractor):
    IE_DESC = 'mitele.es'
    _VALID_URL = r'https?://(?:www\.)?mitele\.es/(?:[^/]+/)+(?P<id>[^/]+)/player'

    _TESTS = [{
        'url': 'http://www.mitele.es/programas-tv/diario-de/57b0dfb9c715da65618b4afa/player',
        'info_dict': {
            'id': 'FhYW1iNTE6J6H7NkQRIEzfne6t2quqPg',
            'ext': 'mp4',
            'title': 'Diario de La redacción Programa 144',
            'description': 'md5:07c35a7b11abb05876a6a79185b58d27',
            'series': 'Diario de',
            'season': 'Season 14',
            'season_number': 14,
            'episode': 'Tor, la web invisible',
            'episode_number': 3,
            'thumbnail': r're:(?i)^https?://.*\.jpg$',
            'duration': 2913,
            'age_limit': 16,
            'timestamp': 1471209401,
            'upload_date': '20160814',
        },
        'add_ie': ['Ooyala'],
    }, {
        # no explicit title
        'url': 'http://www.mitele.es/programas-tv/cuarto-milenio/57b0de3dc915da14058b4876/player',
        'info_dict': {
            'id': 'oyNG1iNTE6TAPP-JmCjbwfwJqqMMX3Vq',
            'ext': 'mp4',
            'title': 'Cuarto Milenio Temporada 6 Programa 226',
            'description': 'md5:5ff132013f0cd968ffbf1f5f3538a65f',
            'series': 'Cuarto Milenio',
            'season': 'Season 6',
            'season_number': 6,
            'episode': 'Episode 24',
            'episode_number': 24,
            'thumbnail': r're:(?i)^https?://.*\.jpg$',
            'duration': 7313,
            'age_limit': 12,
            'timestamp': 1471209021,
            'upload_date': '20160814',
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': ['Ooyala'],
    }, {
        'url': 'http://www.mitele.es/series-online/la-que-se-avecina/57aac5c1c915da951a8b45ed/player',
        'only_matching': True,
    }, {
        'url': 'https://www.mitele.es/programas-tv/diario-de/la-redaccion/programa-144-40_1006364575251/player/',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        pre_player = self._parse_json(self._search_regex(
            r'window\.\$REACTBASE_STATE\.prePlayer_mtweb\s*=\s*({.+})',
            webpage, 'Pre Player'), display_id)['prePlayer']
        title = pre_player['title']
        video = pre_player['video']
        video_id = video['dataMediaId']
        content = pre_player.get('content') or {}
        info = content.get('info') or {}

        return {
            '_type': 'url_transparent',
            # for some reason only HLS is supported
            'url': smuggle_url('ooyala:' + video_id, {'supportedformats': 'm3u8,dash'}),
            'id': video_id,
            'title': title,
            'description': info.get('synopsis'),
            'series': content.get('title'),
            'season_number': int_or_none(info.get('season_number')),
            'episode': content.get('subtitle'),
            'episode_number': int_or_none(info.get('episode_number')),
            'duration': int_or_none(info.get('duration')),
            'thumbnail': video.get('dataPoster'),
            'age_limit': int_or_none(info.get('rating')),
            'timestamp': parse_iso8601(pre_player.get('publishedTime')),
        }


code:
from __future__ import unicode_literals

import itertools
import re

from .common import InfoExtractor
from ..compat import (
    compat_b64decode,
    compat_chr,
    compat_ord,
    compat_str,
    compat_urllib_parse_unquote,
    compat_zip
)
from ..utils import (
    int_or_none,
    parse_iso8601,
    strip_or_none,
    try_get,
)

class MixcloudBaseIE(InfoExtractor):
    def _call_api(self, object_type, object_fields, display_id, username, slug=None):
        lookup_key = object_type + 'Lookup'
        return self._download_json(
            'https://www.mixcloud.com/graphql', display_id, query={
                'query': '''{
  %s(lookup: {username: "%s"%s}) {
    %s
  }
}''' % (lookup_key, username, ', slug: "%s"' % slug if slug else '', object_fields)
            })['data'][lookup_key]

class MixcloudIE(MixcloudBaseIE):
    _VALID_URL = r'https?://(?:(?:www|beta|m)\.)?mixcloud\.com/([^/]+)/(?!stream|uploads|favorites|listens|playlists)([^/]+)'
    IE_NAME = 'mixcloud'

    _TESTS = [{
        'url': 'http://www.mixcloud.com/dholbach/cryptkeeper/',
        'info_dict': {
            'id': 'dholbach_cryptkeeper',
            'ext': 'm4a',
            'title': 'Cryptkeeper',
            'description': 'After quite a long silence from myself, finally another Drum\'n\'Bass mix with my favourite current dance floor bangers.',
            'uploader': 'Daniel Holbach',
            'uploader_id': 'dholbach',
            'thumbnail': r're:https?://.*\.jpg',
            'view_count': int,
            'timestamp': 1321359578,
            'upload_date': '20111115',
        },
    }, {
        'url': 'http://www.mixcloud.com/gillespeterson/caribou-7-inch-vinyl-mix-chat/',
        'info_dict': {
            'id': 'gillespeterson_caribou-7-inch-vinyl-mix-chat',
            'ext': 'mp3',
            'title': 'Caribou 7 inch Vinyl Mix & Chat',
            'description': 'md5:2b8aec6adce69f9d41724647c65875e8',
            'uploader': 'Gilles Peterson Worldwide',
            'uploader_id': 'gillespeterson',
            'thumbnail': 're:https?://.*',
            'view_count': int,
            'timestamp': 1422987057,
            'upload_date': '20150203',
        },
    }, {
        'url': 'https://beta.mixcloud.com/RedLightRadio/nosedrip-15-red-light-radio-01-18-2016/',
        'only_matching': True,
    }]
    _DECRYPTION_KEY = 'IFYOUWANTTHEARTISTSTOGETPAIDDONOTDOWNLOADFROMMIXCLOUD'

    @staticmethod
    def _decrypt_xor_cipher(key, ciphertext):
        """Encrypt/Decrypt XOR cipher. Both ways are possible because it's XOR."""
        return ''.join([
            compat_chr(compat_ord(ch) ^ compat_ord(k))
            for ch, k in compat_zip(ciphertext, itertools.cycle(key))])

    def _real_extract(self, url):
        username, slug = re.match(self._VALID_URL, url).groups()
        username, slug = compat_urllib_parse_unquote(username), compat_urllib_parse_unquote(slug)
        track_id = '%s_%s' % (username, slug)

        cloudcast = self._call_api('cloudcast', '''audioLength
    comments(first: 100) {
      edges {
        node {
          comment
          created
          user {
            displayName
            username
          }
        }
      }
      totalCount
    }
    description
    favorites {
      totalCount
    }
    featuringArtistList
    isExclusive
    name
    owner {
      displayName
      url
      username
    }
    picture(width: 1024, height: 1024) {
        url
    }
    plays
    publishDate
    reposts {
      totalCount
    }
    streamInfo {
      dashUrl
      hlsUrl
      url
    }
    tags {
      tag {
        name
      }
    }''', track_id, username, slug)

        title = cloudcast['name']

        stream_info = cloudcast['streamInfo']
        formats = []

        for url_key in ('url', 'hlsUrl', 'dashUrl'):
            format_url = stream_info.get(url_key)
            if not format_url:
                continue
            decrypted = self._decrypt_xor_cipher(
                self._DECRYPTION_KEY, compat_b64decode(format_url))
            if url_key == 'hlsUrl':
                formats.extend(self._extract_m3u8_formats(
                    decrypted, track_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            elif url_key == 'dashUrl':
                formats.extend(self._extract_mpd_formats(
                    decrypted, track_id, mpd_id='dash', fatal=False))
            else:
                formats.append({
                    'format_id': 'http',
                    'url': decrypted,
                    'downloader_options': {
                        # Mixcloud starts throttling at >~5M
                        'http_chunk_size': 5242880,
                    },
                })

        if not formats and cloudcast.get('isExclusive'):
            self.raise_login_required()

        self._sort_formats(formats)

        comments = []
        for edge in (try_get(cloudcast, lambda x: x['comments']['edges']) or []):
            node = edge.get('node') or {}
            text = strip_or_none(node.get('comment'))
            if not text:
                continue
            user = node.get('user') or {}
            comments.append({
                'author': user.get('displayName'),
                'author_id': user.get('username'),
                'text': text,
                'timestamp': parse_iso8601(node.get('created')),
            })

        tags = []
        for t in cloudcast.get('tags'):
            tag = try_get(t, lambda x: x['tag']['name'], compat_str)
            if not tag:
                tags.append(tag)

        get_count = lambda x: int_or_none(try_get(cloudcast, lambda y: y[x]['totalCount']))

        owner = cloudcast.get('owner') or {}

        return {
            'id': track_id,
            'title': title,
            'formats': formats,
            'description': cloudcast.get('description'),
            'thumbnail': try_get(cloudcast, lambda x: x['picture']['url'], compat_str),
            'uploader': owner.get('displayName'),
            'timestamp': parse_iso8601(cloudcast.get('publishDate')),
            'uploader_id': owner.get('username'),
            'uploader_url': owner.get('url'),
            'duration': int_or_none(cloudcast.get('audioLength')),
            'view_count': int_or_none(cloudcast.get('plays')),
            'like_count': get_count('favorites'),
            'repost_count': get_count('reposts'),
            'comment_count': get_count('comments'),
            'comments': comments,
            'tags': tags,
            'artist': ', '.join(cloudcast.get('featuringArtistList') or []) or None,
        }

class MixcloudPlaylistBaseIE(MixcloudBaseIE):
    def _get_cloudcast(self, node):
        return node

    def _get_playlist_title(self, title, slug):
        return title

    def _real_extract(self, url):
        username, slug = re.match(self._VALID_URL, url).groups()
        username = compat_urllib_parse_unquote(username)
        if not slug:
            slug = 'uploads'
        else:
            slug = compat_urllib_parse_unquote(slug)
        playlist_id = '%s_%s' % (username, slug)

        is_playlist_type = self._ROOT_TYPE == 'playlist'
        playlist_type = 'items' if is_playlist_type else slug
        list_filter = ''

        has_next_page = True
        entries = []
        while has_next_page:
            playlist = self._call_api(
                self._ROOT_TYPE, '''%s
    %s
    %s(first: 100%s) {
      edges {
        node {
          %s
        }
      }
      pageInfo {
        endCursor
        hasNextPage
      }
    }''' % (self._TITLE_KEY, self._DESCRIPTION_KEY, playlist_type, list_filter, self._NODE_TEMPLATE),
                playlist_id, username, slug if is_playlist_type else None)

            items = playlist.get(playlist_type) or {}
            for edge in items.get('edges', []):
                cloudcast = self._get_cloudcast(edge.get('node') or {})
                cloudcast_url = cloudcast.get('url')
                if not cloudcast_url:
                    continue
                entries.append(self.url_result(
                    cloudcast_url, MixcloudIE.ie_key(), cloudcast.get('slug')))

            page_info = items['pageInfo']
            has_next_page = page_info['hasNextPage']
            list_filter = ', after: "%s"' % page_info['endCursor']

        return self.playlist_result(
            entries, playlist_id,
            self._get_playlist_title(playlist[self._TITLE_KEY], slug),
            playlist.get(self._DESCRIPTION_KEY))

class MixcloudUserIE(MixcloudPlaylistBaseIE):
    _VALID_URL = r'https?://(?:www\.)?mixcloud\.com/(?P<id>[^/]+)/(?P<type>uploads|favorites|listens|stream)?/?$'
    IE_NAME = 'mixcloud:user'

    _TESTS = [{
        'url': 'http://www.mixcloud.com/dholbach/',
        'info_dict': {
            'id': 'dholbach_uploads',
            'title': 'Daniel Holbach (uploads)',
            'description': 'md5:b60d776f0bab534c5dabe0a34e47a789',
        },
        'playlist_mincount': 36,
    }, {
        'url': 'http://www.mixcloud.com/dholbach/uploads/',
        'info_dict': {
            'id': 'dholbach_uploads',
            'title': 'Daniel Holbach (uploads)',
            'description': 'md5:b60d776f0bab534c5dabe0a34e47a789',
        },
        'playlist_mincount': 36,
    }, {
        'url': 'http://www.mixcloud.com/dholbach/favorites/',
        'info_dict': {
            'id': 'dholbach_favorites',
            'title': 'Daniel Holbach (favorites)',
            'description': 'md5:b60d776f0bab534c5dabe0a34e47a789',
        },
        # 'params': {
        #     'playlist_items': '1-100',
        # },
        'playlist_mincount': 396,
    }, {
        'url': 'http://www.mixcloud.com/dholbach/listens/',
        'info_dict': {
            'id': 'dholbach_listens',
            'title': 'Daniel Holbach (listens)',
            'description': 'md5:b60d776f0bab534c5dabe0a34e47a789',
        },
        # 'params': {
        #     'playlist_items': '1-100',
        # },
        'playlist_mincount': 1623,
        'skip': 'Large list',
    }, {
        'url': 'https://www.mixcloud.com/FirstEar/stream/',
        'info_dict': {
            'id': 'FirstEar_stream',
            'title': 'First Ear (stream)',
            'description': 'Curators of good music\r\n\r\nfirstearmusic.com',
        },
        'playlist_mincount': 271,
    }]

    _TITLE_KEY = 'displayName'
    _DESCRIPTION_KEY = 'biog'
    _ROOT_TYPE = 'user'
    _NODE_TEMPLATE = '''slug
          url'''

    def _get_playlist_title(self, title, slug):
        return '%s (%s)' % (title, slug)

class MixcloudPlaylistIE(MixcloudPlaylistBaseIE):
    _VALID_URL = r'https?://(?:www\.)?mixcloud\.com/(?P<user>[^/]+)/playlists/(?P<playlist>[^/]+)/?$'
    IE_NAME = 'mixcloud:playlist'

    _TESTS = [{
        'url': 'https://www.mixcloud.com/maxvibes/playlists/jazzcat-on-ness-radio/',
        'info_dict': {
            'id': 'maxvibes_jazzcat-on-ness-radio',
            'title': 'Ness Radio sessions',
        },
        'playlist_mincount': 59,
    }]
    _TITLE_KEY = 'name'
    _DESCRIPTION_KEY = 'description'
    _ROOT_TYPE = 'playlist'
    _NODE_TEMPLATE = '''cloudcast {
            slug
            url
          }'''

    def _get_cloudcast(self, node):
        return node.get('cloudcast') or {}


code:
from __future__ import unicode_literals

from .nhl import NHLBaseIE

class MLBIE(NHLBaseIE):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:[\da-z_-]+\.)*(?P<site>mlb)\.com/
                        (?:
                            (?:
                                (?:[^/]+/)*c-|
                                (?:
                                    shared/video/embed/(?:embed|m-internal-embed)\.html|
                                    (?:[^/]+/)+(?:play|index)\.jsp|
                                )\?.*?\bcontent_id=
                            )
                            (?P<id>\d+)
                        )
                    '''
    _CONTENT_DOMAIN = 'content.mlb.com'
    _TESTS = [
        {
            'url': 'https://www.mlb.com/mariners/video/ackleys-spectacular-catch/c-34698933',
            'md5': '632358dacfceec06bad823b83d21df2d',
            'info_dict': {
                'id': '34698933',
                'ext': 'mp4',
                'title': "Ackley's spectacular catch",
                'description': 'md5:7f5a981eb4f3cbc8daf2aeffa2215bf0',
                'duration': 66,
                'timestamp': 1405995000,
                'upload_date': '20140722',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/video/stanton-prepares-for-derby/c-34496663',
            'md5': 'bf2619bf9cacc0a564fc35e6aeb9219f',
            'info_dict': {
                'id': '34496663',
                'ext': 'mp4',
                'title': 'Stanton prepares for Derby',
                'description': 'md5:d00ce1e5fd9c9069e9c13ab4faedfa57',
                'duration': 46,
                'timestamp': 1405120200,
                'upload_date': '20140711',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/video/cespedes-repeats-as-derby-champ/c-34578115',
            'md5': '99bb9176531adc600b90880fb8be9328',
            'info_dict': {
                'id': '34578115',
                'ext': 'mp4',
                'title': 'Cespedes repeats as Derby champ',
                'description': 'md5:08df253ce265d4cf6fb09f581fafad07',
                'duration': 488,
                'timestamp': 1405414336,
                'upload_date': '20140715',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/video/bautista-on-home-run-derby/c-34577915',
            'md5': 'da8b57a12b060e7663ee1eebd6f330ec',
            'info_dict': {
                'id': '34577915',
                'ext': 'mp4',
                'title': 'Bautista on Home Run Derby',
                'description': 'md5:b80b34031143d0986dddc64a8839f0fb',
                'duration': 52,
                'timestamp': 1405405122,
                'upload_date': '20140715',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/news/blue-jays-kevin-pillar-goes-spidey-up-the-wall-to-rob-tim-beckham-of-a-homer/c-118550098',
            'md5': 'e09e37b552351fddbf4d9e699c924d68',
            'info_dict': {
                'id': '75609783',
                'ext': 'mp4',
                'title': 'Must C: Pillar climbs for catch',
                'description': '4/15/15: Blue Jays outfielder Kevin Pillar continues his defensive dominance by climbing the wall in left to rob Tim Beckham of a home run',
                'timestamp': 1429139220,
                'upload_date': '20150415',
            }
        },
        {
            'url': 'https://www.mlb.com/video/hargrove-homers-off-caldwell/c-1352023483?tid=67793694',
            'only_matching': True,
        },
        {
            'url': 'http://m.mlb.com/shared/video/embed/embed.html?content_id=35692085&topic_id=6479266&width=400&height=224&property=mlb',
            'only_matching': True,
        },
        {
            'url': 'http://mlb.mlb.com/shared/video/embed/embed.html?content_id=36599553',
            'only_matching': True,
        },
        {
            'url': 'http://mlb.mlb.com/es/video/play.jsp?content_id=36599553',
            'only_matching': True,
        },
        {
            'url': 'https://www.mlb.com/cardinals/video/piscottys-great-sliding-catch/c-51175783',
            'only_matching': True,
        },
        {
            # From http://m.mlb.com/news/article/118550098/blue-jays-kevin-pillar-goes-spidey-up-the-wall-to-rob-tim-beckham-of-a-homer
            'url': 'http://mlb.mlb.com/shared/video/embed/m-internal-embed.html?content_id=75609783&property=mlb&autoplay=true&hashmode=false&siteSection=mlb/multimedia/article_118550098/article_embed&club=mlb',
            'only_matching': True,
        },
        {
            'url': 'https://www.mlb.com/cut4/carlos-gomez-borrowed-sunglasses-from-an-as-fan/c-278912842',
            'only_matching': True,
        }
    ]


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

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

class MnetIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mnet\.(?:com|interest\.me)/tv/vod/(?:.*?\bclip_id=)?(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://www.mnet.com/tv/vod/171008',
        'info_dict': {
            'id': '171008',
            'title': 'SS_이해인@히든박스',
            'description': 'md5:b9efa592c3918b615ba69fe9f8a05c55',
            'duration': 88,
            'upload_date': '20151231',
            'timestamp': 1451564040,
            'age_limit': 0,
            'thumbnails': 'mincount:5',
            'thumbnail': r're:^https?://.*\.jpg$',
            'ext': 'flv',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        },
    }, {
        'url': 'http://mnet.interest.me/tv/vod/172790',
        'only_matching': True,
    }, {
        'url': 'http://www.mnet.com/tv/vod/vod_view.asp?clip_id=172790&tabMenu=',
        'only_matching': True,
    }]

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

        # TODO: extract rtmp formats
        # no stype -> rtmp url
        # stype=H -> m3u8 url
        # stype=M -> mpd url
        info = self._download_json(
            'http://content.api.mnet.com/player/vodConfig',
            video_id, 'Downloading vod config JSON', query={
                'id': video_id,
                'ctype': 'CLIP',
                'stype': 'H',
            })['data']['info']

        title = info['title']

        cdn_data = self._download_json(
            info['cdn'], video_id, 'Downloading vod cdn JSON')['data'][0]
        m3u8_url = cdn_data['url']
        token = cdn_data.get('token')
        if token and token != '-':
            m3u8_url += '?' + token
        formats = self._extract_wowza_formats(
            m3u8_url, video_id, skip_protocols=['rtmp', 'rtsp', 'f4m'])
        self._sort_formats(formats)

        description = info.get('ment')
        duration = parse_duration(info.get('time'))
        timestamp = parse_iso8601(info.get('date'), delimiter=' ')
        age_limit = info.get('adult')
        if age_limit is not None:
            age_limit = 0 if age_limit == 'N' else 18
        thumbnails = [{
            'id': thumb_format,
            'url': thumb['url'],
            'width': int_or_none(thumb.get('width')),
            'height': int_or_none(thumb.get('height')),
        } for thumb_format, thumb in info.get('cover', {}).items() if thumb.get('url')]

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


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

import re

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

class MoeVideoIE(InfoExtractor):
    IE_DESC = 'LetitBit video services: moevideo.net, playreplay.net and videochart.net'
    _VALID_URL = r'''(?x)
        https?://(?P<host>(?:www\.)?
        (?:(?:moevideo|playreplay|videochart)\.net|thesame\.tv))/
        (?:video|framevideo|embed)/(?P<id>[0-9a-z]+\.[0-9A-Za-z]+)'''
    _API_URL = 'http://api.letitbit.net/'
    _API_KEY = 'tVL0gjqo5'
    _TESTS = [
        {
            'url': 'http://moevideo.net/video/00297.0036103fe3d513ef27915216fd29',
            'md5': '129f5ae1f6585d0e9bb4f38e774ffb3a',
            'info_dict': {
                'id': '00297.0036103fe3d513ef27915216fd29',
                'ext': 'flv',
                'title': 'Sink cut out machine',
                'description': 'md5:f29ff97b663aefa760bf7ca63c8ca8a8',
                'thumbnail': r're:^https?://.*\.jpg$',
                'width': 540,
                'height': 360,
                'duration': 179,
                'filesize': 17822500,
            },
            'skip': 'Video has been removed',
        },
        {
            'url': 'http://playreplay.net/video/77107.7f325710a627383d40540d8e991a',
            'md5': '74f0a014d5b661f0f0e2361300d1620e',
            'info_dict': {
                'id': '77107.7f325710a627383d40540d8e991a',
                'ext': 'flv',
                'title': 'Operacion Condor.',
                'description': 'md5:7e68cb2fcda66833d5081c542491a9a3',
                'thumbnail': r're:^https?://.*\.jpg$',
                'width': 480,
                'height': 296,
                'duration': 6027,
                'filesize': 588257923,
            },
            'skip': 'Video has been removed',
        },
    ]

    def _real_extract(self, url):
        host, video_id = re.match(self._VALID_URL, url).groups()

        webpage = self._download_webpage(
            'http://%s/video/%s' % (host, video_id),
            video_id, 'Downloading webpage')

        title = self._og_search_title(webpage)

        embed_webpage = self._download_webpage(
            'http://%s/embed/%s' % (host, video_id),
            video_id, 'Downloading embed webpage')
        video = self._parse_json(self._search_regex(
            r'mvplayer\("#player"\s*,\s*({.+})',
            embed_webpage, 'mvplayer'), video_id)['video']

        return {
            'id': video_id,
            'title': title,
            'thumbnail': video.get('poster') or self._og_search_thumbnail(webpage),
            'description': clean_html(self._og_search_description(webpage)),
            'duration': int_or_none(self._og_search_property('video:duration', webpage)),
            'url': video['ourUrl'],
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    str_to_int,
    unified_strdate,
)
from .keezmovies import KeezMoviesIE

class MofosexIE(KeezMoviesIE):
    _VALID_URL = r'https?://(?:www\.)?mofosex\.com/videos/(?P<id>\d+)/(?P<display_id>[^/?#&.]+)\.html'
    _TESTS = [{
        'url': 'http://www.mofosex.com/videos/318131/amateur-teen-playing-and-masturbating-318131.html',
        'md5': '558fcdafbb63a87c019218d6e49daf8a',
        'info_dict': {
            'id': '318131',
            'display_id': 'amateur-teen-playing-and-masturbating-318131',
            'ext': 'mp4',
            'title': 'amateur teen playing and masturbating',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20121114',
            'view_count': int,
            'like_count': int,
            'dislike_count': int,
            'age_limit': 18,
        }
    }, {
        # This video is no longer available
        'url': 'http://www.mofosex.com/videos/5018/japanese-teen-music-video.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        webpage, info = self._extract_info(url)

        view_count = str_to_int(self._search_regex(
            r'VIEWS:\s*([\d,.]+)', webpage, 'view count', fatal=False))
        like_count = int_or_none(self._search_regex(
            r'id=["\']amountLikes["\'][^>]*>(\d+)', webpage,
            'like count', fatal=False))
        dislike_count = int_or_none(self._search_regex(
            r'id=["\']amountDislikes["\'][^>]*>(\d+)', webpage,
            'like count', fatal=False))
        upload_date = unified_strdate(self._html_search_regex(
            r'Added:([^<]+)', webpage, 'upload date', fatal=False))

        info.update({
            'view_count': view_count,
            'like_count': like_count,
            'dislike_count': dislike_count,
            'upload_date': upload_date,
            'thumbnail': self._og_search_thumbnail(webpage),
        })

        return info

class MofosexEmbedIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mofosex\.com/embed/?\?.*?\bvideoid=(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.mofosex.com/embed/?videoid=318131&referrer=KM',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return re.findall(
            r']+\bsrc=["\']((?:https?:)?//(?:www\.)?mofosex\.com/embed/?\?.*?\bvideoid=\d+)',
            webpage)

    def _real_extract(self, url):
        video_id = self._match_id(url)
        return self.url_result(
            'http://www.mofosex.com/videos/{0}/{0}.html'.format(video_id),
            ie=MofosexIE.ie_key(), video_id=video_id)


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

import re

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

class MojvideoIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mojvideo\.com/video-(?P<display_id>[^/]+)/(?P<id>[a-f0-9]+)'
    _TEST = {
        'url': 'http://www.mojvideo.com/video-v-avtu-pred-mano-rdecelaska-alfi-nipic/3d1ed4497707730b2906',
        'md5': 'f7fd662cc8ce2be107b0d4f2c0483ae7',
        'info_dict': {
            'id': '3d1ed4497707730b2906',
            'display_id': 'v-avtu-pred-mano-rdecelaska-alfi-nipic',
            'ext': 'mp4',
            'title': 'V avtu pred mano rdečelaska - Alfi Nipič',
            'thumbnail': r're:^http://.*\.jpg$',
            'duration': 242,
        }
    }

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

        # XML is malformed
        playerapi = self._download_webpage(
            'http://www.mojvideo.com/playerapi.php?v=%s&t=1' % video_id, display_id)

        if '<error>true</error>' in playerapi:
            error_desc = self._html_search_regex(
                r'<errordesc>([^<]*)</errordesc>', playerapi, 'error description', fatal=False)
            raise ExtractorError('%s said: %s' % (self.IE_NAME, error_desc), expected=True)

        title = self._html_search_regex(
            r'<title>([^<]+)</title>', playerapi, 'title')
        video_url = self._html_search_regex(
            r'<file>([^<]+)</file>', playerapi, 'video URL')
        thumbnail = self._html_search_regex(
            r'<preview>([^<]+)</preview>', playerapi, 'thumbnail', fatal=False)
        duration = parse_duration(self._html_search_regex(
            r'<duration>([^<]+)</duration>', playerapi, 'duration', fatal=False))

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


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

import re

from .common import InfoExtractor

class MorningstarIE(InfoExtractor):
    IE_DESC = 'morningstar.com'
    _VALID_URL = r'https?://(?:(?:www|news)\.)morningstar\.com/[cC]over/video[cC]enter\.aspx\?id=(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://www.morningstar.com/cover/videocenter.aspx?id=615869',
        'md5': '6c0acface7a787aadc8391e4bbf7b0f5',
        'info_dict': {
            'id': '615869',
            'ext': 'mp4',
            'title': 'Get Ahead of the Curve on 2013 Taxes',
            'description': "Vanguard's Joel Dickson on managing higher tax rates for high-income earners and fund capital-gain distributions in 2013.",
            'thumbnail': r're:^https?://.*m(?:orning)?star\.com/.+thumb\.jpg$'
        }
    }, {
        'url': 'http://news.morningstar.com/cover/videocenter.aspx?id=825556',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)
        title = self._html_search_regex(
            r'
(.*?)
', webpage, 'title')
        video_url = self._html_search_regex(
            r'<input type="hidden" id="hidVideoUrl" value="([^"]+)"',
            webpage, 'video URL')
        thumbnail = self._html_search_regex(
            r'<input type="hidden" id="hidSnapshot" value="([^"]+)"',
            webpage, 'thumbnail', fatal=False)
        description = self._html_search_regex(
            r'
(.*?)
',
            webpage, 'description', fatal=False)

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


code:
from __future__ import unicode_literals

import datetime
import re

from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    ExtractorError,
    InAdvancePagedList,
    orderedSet,
    str_to_int,
    unified_strdate,
)

class MotherlessIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?motherless\.com/(?:g/[a-z0-9_]+/)?(?P<id>[A-Z0-9]+)'
    _TESTS = [{
        'url': 'http://motherless.com/AC3FFE1',
        'md5': '310f62e325a9fafe64f68c0bccb6e75f',
        'info_dict': {
            'id': 'AC3FFE1',
            'ext': 'mp4',
            'title': 'Fucked in the ass while playing PS3',
            'categories': ['Gaming', 'anal', 'reluctant', 'rough', 'Wife'],
            'upload_date': '20100913',
            'uploader_id': 'famouslyfuckedup',
            'thumbnail': r're:https?://.*\.jpg',
            'age_limit': 18,
        }
    }, {
        'url': 'http://motherless.com/532291B',
        'md5': 'bc59a6b47d1f958e61fbd38a4d31b131',
        'info_dict': {
            'id': '532291B',
            'ext': 'mp4',
            'title': 'Amazing girl playing the omegle game, PERFECT!',
            'categories': ['Amateur', 'webcam', 'omegle', 'pink', 'young', 'masturbate', 'teen',
                           'game', 'hairy'],
            'upload_date': '20140622',
            'uploader_id': 'Sulivana7x',
            'thumbnail': r're:https?://.*\.jpg',
            'age_limit': 18,
        },
        'skip': '404',
    }, {
        'url': 'http://motherless.com/g/cosplay/633979F',
        'md5': '0b2a43f447a49c3e649c93ad1fafa4a0',
        'info_dict': {
            'id': '633979F',
            'ext': 'mp4',
            'title': 'Turtlette',
            'categories': ['superheroine heroine  superher'],
            'upload_date': '20140827',
            'uploader_id': 'shade0230',
            'thumbnail': r're:https?://.*\.jpg',
            'age_limit': 18,
        }
    }, {
        # no keywords
        'url': 'http://motherless.com/8B4BBC1',
        'only_matching': True,
    }]

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

        if any(p in webpage for p in (
                '<title>404 - MOTHERLESS.COM<',
                ">The page you're looking for cannot be found.<")):
            raise ExtractorError('Video %s does not exist' % video_id, expected=True)

        if '>The content you are trying to view is for friends only.' in webpage:
            raise ExtractorError('Video %s is for friends only' % video_id, expected=True)

        title = self._html_search_regex(
            (r'(?s)
]+\bclass=["\']media-meta-title[^>]+>(.+?)
',
             r'id="view-upload-title">\s+([^<]+)<'), webpage, 'title')
        video_url = (self._html_search_regex(
            (r'setup\(\{\s*["\']file["\']\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1',
             r'fileurl\s*=\s*(["\'])(?P<url>(?:(?!\1).)+)\1'),
            webpage, 'video URL', default=None, group='url')
            or 'http://cdn4.videos.motherlessmedia.com/videos/%s.mp4?fs=opencloud' % video_id)
        age_limit = self._rta_search(webpage)
        view_count = str_to_int(self._html_search_regex(
            (r'>(\d+)\s+Views<', r'Views\s+([^<]+)<'),
            webpage, 'view count', fatal=False))
        like_count = str_to_int(self._html_search_regex(
            (r'>(\d+)\s+Favorites<', r'Favorited\s+([^<]+)<'),
            webpage, 'like count', fatal=False))

        upload_date = self._html_search_regex(
            (r'class=["\']count[^>]+>(\d+\s+[a-zA-Z]{3}\s+\d{4})<',
             r'Uploaded\s+([^<]+)<'), webpage, 'upload date')
        if 'Ago' in upload_date:
            days = int(re.search(r'([0-9]+)', upload_date).group(1))
            upload_date = (datetime.datetime.now() - datetime.timedelta(days=days)).strftime('%Y%m%d')
        else:
            upload_date = unified_strdate(upload_date)

        comment_count = webpage.count('class="media-comment-contents"')
        uploader_id = self._html_search_regex(
            r'"thumb-member-username">\s+<a href="/m/([^"]+)"',
            webpage, 'uploader_id')

        categories = self._html_search_meta('keywords', webpage, default=None)
        if categories:
            categories = [cat.strip() for cat in categories.split(',')]

        return {
            'id': video_id,
            'title': title,
            'upload_date': upload_date,
            'uploader_id': uploader_id,
            'thumbnail': self._og_search_thumbnail(webpage),
            'categories': categories,
            'view_count': view_count,
            'like_count': like_count,
            'comment_count': comment_count,
            'age_limit': age_limit,
            'url': video_url,
        }

class MotherlessGroupIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?motherless\.com/gv?/(?P<id>[a-z0-9_]+)'
    _TESTS = [{
        'url': 'http://motherless.com/g/movie_scenes',
        'info_dict': {
            'id': 'movie_scenes',
            'title': 'Movie Scenes',
            'description': 'Hot and sexy scenes from "regular" movies... '
                           'Beautiful actresses fully nude... A looot of '
                           'skin! :)Enjoy!',
        },
        'playlist_mincount': 662,
    }, {
        'url': 'http://motherless.com/gv/sex_must_be_funny',
        'info_dict': {
            'id': 'sex_must_be_funny',
            'title': 'Sex must be funny',
            'description': 'Sex can be funny. Wide smiles,laugh, games, fun of '
                           'any kind!'
        },
        'playlist_mincount': 9,
    }]

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

    def _extract_entries(self, webpage, base):
        entries = []
        for mobj in re.finditer(
                r'href="(?P<href>/[^"]+)"[^>]*>(?:\s*]+alt="[^-]+-\s(?P<title>[^"]+)")?',
                webpage):
            video_url = compat_urlparse.urljoin(base, mobj.group('href'))
            if not MotherlessIE.suitable(video_url):
                continue
            video_id = MotherlessIE._match_id(video_url)
            title = mobj.group('title')
            entries.append(self.url_result(
                video_url, ie=MotherlessIE.ie_key(), video_id=video_id,
                video_title=title))
        # Alternative fallback
        if not entries:
            entries = [
                self.url_result(
                    compat_urlparse.urljoin(base, '/' + entry_id),
                    ie=MotherlessIE.ie_key(), video_id=entry_id)
                for entry_id in orderedSet(re.findall(
                    r'data-codename=["\']([A-Z0-9]+)', webpage))]
        return entries

    def _real_extract(self, url):
        group_id = self._match_id(url)
        page_url = compat_urlparse.urljoin(url, '/gv/%s' % group_id)
        webpage = self._download_webpage(page_url, group_id)
        title = self._search_regex(
            r'<title>([\w\s]+\w)\s+-', webpage, 'title', fatal=False)
        description = self._html_search_meta(
            'description', webpage, fatal=False)
        page_count = self._int(self._search_regex(
            r'(\d+)</(?:a|span)><(?:a|span)[^>]+>\s*NEXT',
            webpage, 'page_count'), 'page_count')
        PAGE_SIZE = 80

        def _get_page(idx):
            webpage = self._download_webpage(
                page_url, group_id, query={'page': idx + 1},
                note='Downloading page %d/%d' % (idx + 1, page_count)
            )
            for entry in self._extract_entries(webpage, url):
                yield entry

        playlist = InAdvancePagedList(_get_page, page_count, PAGE_SIZE)

        return {
            '_type': 'playlist',
            'id': group_id,
            'title': title,
            'description': description,
            'entries': playlist
        }


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

from .common import InfoExtractor
from ..compat import (
    compat_urlparse,
)

class MotorsportIE(InfoExtractor):
    IE_DESC = 'motorsport.com'
    _VALID_URL = r'https?://(?:www\.)?motorsport\.com/[^/?#]+/video/(?:[^/?#]+/)(?P<id>[^/]+)/?(?:$|[?#])'
    _TEST = {
        'url': 'http://www.motorsport.com/f1/video/main-gallery/red-bull-racing-2014-rules-explained/',
        'info_dict': {
            'id': '2-T3WuR-KMM',
            'ext': 'mp4',
            'title': 'Red Bull Racing: 2014 Rules Explained',
            'duration': 208,
            'description': 'A new clip from Red Bull sees Daniel Ricciardo and Sebastian Vettel explain the 2014 Formula One regulations – which are arguably the most complex the sport has ever seen.',
            'uploader': 'mcomstaff',
            'uploader_id': 'UC334JIYKkVnyFoNCclfZtHQ',
            'upload_date': '20140903',
            'thumbnail': r're:^https?://.+\.jpg$'
        },
        'add_ie': ['Youtube'],
        'params': {
            'skip_download': True,
        },
    }

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

        iframe_path = self._html_search_regex(
            r'<iframe id="player_iframe"[^>]+src="([^"]+)"', webpage,
            'iframe path')
        iframe = self._download_webpage(
            compat_urlparse.urljoin(url, iframe_path), display_id,
            'Downloading iframe')
        youtube_id = self._search_regex(
            r'www.youtube.com/embed/(.{11})', iframe, 'youtube id')

        return {
            '_type': 'url_transparent',
            'display_id': display_id,
            'url': 'https://youtube.com/watch?v=%s' % youtube_id,
        }


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

from .common import InfoExtractor
from ..utils import (
    smuggle_url,
    float_or_none,
    parse_iso8601,
    update_url_query,
)

class MovieClipsIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?movieclips\.com/videos/.+-(?P<id>\d+)(?:\?|$)'
    _TEST = {
        'url': 'http://www.movieclips.com/videos/warcraft-trailer-1-561180739597',
        'md5': '42b5a0352d4933a7bd54f2104f481244',
        'info_dict': {
            'id': 'pKIGmG83AqD9',
            'ext': 'mp4',
            'title': 'Warcraft Trailer 1',
            'description': 'Watch Trailer 1 from Warcraft (2016). Legendary’s WARCRAFT is a 3D epic adventure of world-colliding conflict based.',
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1446843055,
            'upload_date': '20151106',
            'uploader': 'Movieclips',
        },
        'add_ie': ['ThePlatform'],
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        video = next(v for v in self._parse_json(self._search_regex(
            r'var\s+__REACT_ENGINE__\s*=\s*({.+});',
            webpage, 'react engine'), video_id)['playlist']['videos'] if v['id'] == video_id)

        return {
            '_type': 'url_transparent',
            'ie_key': 'ThePlatform',
            'url': smuggle_url(update_url_query(
                video['contentUrl'], {'mbr': 'true'}), {'force_smil_url': True}),
            'title': self._og_search_title(webpage),
            'description': self._html_search_meta('description', webpage),
            'duration': float_or_none(video.get('duration')),
            'timestamp': parse_iso8601(video.get('dateCreated')),
            'thumbnail': video.get('defaultImage'),
            'uploader': video.get('provider'),
        }


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

import re

from .common import InfoExtractor

class MoviezineIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?moviezine\.se/video/(?P<id>[^?#]+)'

    _TEST = {
        'url': 'http://www.moviezine.se/video/205866',
        'info_dict': {
            'id': '205866',
            'ext': 'mp4',
            'title': 'Oculus - Trailer 1',
            'description': 'md5:40cc6790fc81d931850ca9249b40e8a4',
            'thumbnail': r're:http://.*\.jpg',
        },
    }

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

        webpage = self._download_webpage(url, video_id)
        jsplayer = self._download_webpage('http://www.moviezine.se/api/player.js?video=%s' % video_id, video_id, 'Downloading js api player')

        formats = [{
            'format_id': 'sd',
            'url': self._html_search_regex(r'file: "(.+?)",', jsplayer, 'file'),
            'quality': 0,
            'ext': 'mp4',
        }]

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': self._search_regex(r'title: "(.+?)",', jsplayer, 'title'),
            'thumbnail': self._search_regex(r'image: "(.+?)",', jsplayer, 'image'),
            'formats': formats,
            'description': self._og_search_description(webpage),
        }


code:
from __future__ import unicode_literals

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

class MovingImageIE(InfoExtractor):
    _VALID_URL = r'https?://movingimage\.nls\.uk/film/(?P<id>\d+)'
    _TEST = {
        'url': 'http://movingimage.nls.uk/film/3561',
        'md5': '4caa05c2b38453e6f862197571a7be2f',
        'info_dict': {
            'id': '3561',
            'ext': 'mp4',
            'title': 'SHETLAND WOOL',
            'description': 'md5:c5afca6871ad59b4271e7704fe50ab04',
            'duration': 900,
            'thumbnail': r're:^https?://.*\.jpg$',
        },
    }

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

        webpage = self._download_webpage(url, video_id)

        formats = self._extract_m3u8_formats(
            self._html_search_regex(r'file\s*:\s*"([^"]+)"', webpage, 'm3u8 manifest URL'),
            video_id, ext='mp4', entry_protocol='m3u8_native')

        def search_field(field_name, fatal=False):
            return self._search_regex(
                r'%s:\s*([^<]+)' % field_name,
                webpage, 'title', fatal=fatal)

        title = unescapeHTML(search_field('Title', fatal=True)).strip('()[]')
        description = unescapeHTML(search_field('Description'))
        duration = parse_duration(search_field('Running time'))
        thumbnail = self._search_regex(
            r"image\s*:\s*'([^']+)'", webpage, 'thumbnail', fatal=False)

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


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

import re

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    determine_ext,
    ExtractorError,
    int_or_none,
    unescapeHTML,
)

class MSNIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:www|preview)\.)?msn\.com/(?:[^/]+/)+(?P<display_id>[^/]+)/[a-z]{2}-(?P<id>[\da-zA-Z]+)'
    _TESTS = [{
        'url': 'https://www.msn.com/en-in/money/video/7-ways-to-get-rid-of-chest-congestion/vi-BBPxU6d',
        'md5': '087548191d273c5c55d05028f8d2cbcd',
        'info_dict': {
            'id': 'BBPxU6d',
            'display_id': '7-ways-to-get-rid-of-chest-congestion',
            'ext': 'mp4',
            'title': 'Seven ways to get rid of chest congestion',
            'description': '7 Ways to Get Rid of Chest Congestion',
            'duration': 88,
            'uploader': 'Health',
            'uploader_id': 'BBPrMqa',
        },
    }, {
        # Article, multiple Dailymotion Embeds
        'url': 'https://www.msn.com/en-in/money/sports/hottest-football-wags-greatest-footballers-turned-managers-and-more/ar-BBpc7Nl',
        'info_dict': {
            'id': 'BBpc7Nl',
        },
        'playlist_mincount': 4,
    }, {
        'url': 'http://www.msn.com/en-ae/news/offbeat/meet-the-nine-year-old-self-made-millionaire/ar-BBt6ZKf',
        'only_matching': True,
    }, {
        'url': 'http://www.msn.com/en-ae/video/watch/obama-a-lot-of-people-will-be-disappointed/vi-AAhxUMH',
        'only_matching': True,
    }, {
        # geo restricted
        'url': 'http://www.msn.com/en-ae/foodanddrink/joinourtable/the-first-fart-makes-you-laugh-the-last-fart-makes-you-cry/vp-AAhzIBU',
        'only_matching': True,
    }, {
        'url': 'http://www.msn.com/en-ae/entertainment/bollywood/watch-how-salman-khan-reacted-when-asked-if-he-would-apologize-for-his-‘raped-woman’-comment/vi-AAhvzW6',
        'only_matching': True,
    }, {
        # Vidible(AOL) Embed
        'url': 'https://www.msn.com/en-us/money/other/jupiter-is-about-to-come-so-close-you-can-see-its-moons-with-binoculars/vi-AACqsHR',
        'only_matching': True,
    }, {
        # Dailymotion Embed
        'url': 'https://www.msn.com/es-ve/entretenimiento/watch/winston-salem-paire-refait-des-siennes-en-perdant-sa-raquette-au-service/vp-AAG704L',
        'only_matching': True,
    }, {
        # YouTube Embed
        'url': 'https://www.msn.com/en-in/money/news/meet-vikram-%E2%80%94-chandrayaan-2s-lander/vi-AAGUr0v',
        'only_matching': True,
    }, {
        # NBCSports Embed
        'url': 'https://www.msn.com/en-us/money/football_nfl/week-13-preview-redskins-vs-panthers/vi-BBXsCDb',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id, page_id = re.match(self._VALID_URL, url).groups()

        webpage = self._download_webpage(url, display_id)

        entries = []
        for _, metadata in re.findall(r'data-metadata\s*=\s*(["\'])(?P<data>.+?)\1', webpage):
            video = self._parse_json(unescapeHTML(metadata), display_id)

            provider_id = video.get('providerId')
            player_name = video.get('playerName')
            if player_name and provider_id:
                entry = None
                if player_name == 'AOL':
                    if provider_id.startswith('http'):
                        provider_id = self._search_regex(
                            r'https?://delivery\.vidible\.tv/video/redirect/([0-9a-f]{24})',
                            provider_id, 'vidible id')
                    entry = self.url_result(
                        'aol-video:' + provider_id, 'Aol', provider_id)
                elif player_name == 'Dailymotion':
                    entry = self.url_result(
                        'https://www.dailymotion.com/video/' + provider_id,
                        'Dailymotion', provider_id)
                elif player_name == 'YouTube':
                    entry = self.url_result(
                        provider_id, 'Youtube', provider_id)
                elif player_name == 'NBCSports':
                    entry = self.url_result(
                        'http://vplayer.nbcsports.com/p/BxmELC/nbcsports_embed/select/media/' + provider_id,
                        'NBCSportsVPlayer', provider_id)
                if entry:
                    entries.append(entry)
                    continue

            video_id = video['uuid']
            title = video['title']

            formats = []
            for file_ in video.get('videoFiles', []):
                format_url = file_.get('url')
                if not format_url:
                    continue
                if 'format=m3u8-aapl' in format_url:
                    # m3u8_native should not be used here until
                    # https://github.com/ytdl-org/youtube-dl/issues/9913 is fixed
                    formats.extend(self._extract_m3u8_formats(
                        format_url, display_id, 'mp4',
                        m3u8_id='hls', fatal=False))
                elif 'format=mpd-time-csf' in format_url:
                    formats.extend(self._extract_mpd_formats(
                        format_url, display_id, 'dash', fatal=False))
                elif '.ism' in format_url:
                    if format_url.endswith('.ism'):
                        format_url += '/manifest'
                    formats.extend(self._extract_ism_formats(
                        format_url, display_id, 'mss', fatal=False))
                else:
                    format_id = file_.get('formatCode')
                    formats.append({
                        'url': format_url,
                        'ext': 'mp4',
                        'format_id': format_id,
                        'width': int_or_none(file_.get('width')),
                        'height': int_or_none(file_.get('height')),
                        'vbr': int_or_none(self._search_regex(r'_(\d+)\.mp4', format_url, 'vbr', default=None)),
                        'preference': 1 if format_id == '1001' else None,
                    })
            self._sort_formats(formats)

            subtitles = {}
            for file_ in video.get('files', []):
                format_url = file_.get('url')
                format_code = file_.get('formatCode')
                if not format_url or not format_code:
                    continue
                if compat_str(format_code) == '3100':
                    subtitles.setdefault(file_.get('culture', 'en'), []).append({
                        'ext': determine_ext(format_url, 'ttml'),
                        'url': format_url,
                    })

            entries.append({
                'id': video_id,
                'display_id': display_id,
                'title': title,
                'description': video.get('description'),
                'thumbnail': video.get('headlineImage', {}).get('url'),
                'duration': int_or_none(video.get('durationSecs')),
                'uploader': video.get('sourceFriendly'),
                'uploader_id': video.get('providerId'),
                'creator': video.get('creator'),
                'subtitles': subtitles,
                'formats': formats,
            })

        if not entries:
            error = unescapeHTML(self._search_regex(
                r'data-error=(["\'])(?P<error>.+?)\1',
                webpage, 'error', group='error'))
            raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)

        return self.playlist_result(entries, page_id)