code:
import re

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    ExtractorError,
    find_xpath_attr,
    fix_xml_ampersands,
    float_or_none,
    HEADRequest,
    int_or_none,
    join_nonempty,
    RegexNotFoundError,
    sanitized_Request,
    strip_or_none,
    timeconvert,
    try_get,
    unescapeHTML,
    update_url_query,
    url_basename,
    xpath_text,
)

def _media_xml_tag(tag):
    return '{http://search.yahoo.com/mrss/}%s' % tag

class MTVServicesInfoExtractor(InfoExtractor):
    _MOBILE_TEMPLATE = None
    _LANG = None

    @staticmethod
    def _id_from_uri(uri):
        return uri.split(':')[-1]

    @staticmethod
    def _remove_template_parameter(url):
        # Remove the templates, like &device={device}
        return re.sub(r'&[^=]*?={.*?}(?=(&|$))', '', url)

    def _get_feed_url(self, uri, url=None):
        return self._FEED_URL

    def _get_thumbnail_url(self, uri, itemdoc):
        search_path = '%s/%s' % (_media_xml_tag('group'), _media_xml_tag('thumbnail'))
        thumb_node = itemdoc.find(search_path)
        if thumb_node is None:
            return None
        return thumb_node.get('url') or thumb_node.text or None

    def _extract_mobile_video_formats(self, mtvn_id):
        webpage_url = self._MOBILE_TEMPLATE % mtvn_id
        req = sanitized_Request(webpage_url)
        # Otherwise we get a webpage that would execute some javascript
        req.add_header('User-Agent', 'curl/7')
        webpage = self._download_webpage(req, mtvn_id,
                                         'Downloading mobile page')
        metrics_url = unescapeHTML(self._search_regex(r'<a href="(http://metrics.+?)"', webpage, 'url'))
        req = HEADRequest(metrics_url)
        response = self._request_webpage(req, mtvn_id, 'Resolving url')
        url = response.geturl()
        # Transform the url to get the best quality:
        url = re.sub(r'.+pxE=mp4', 'http://mtvnmobile.vo.llnwd.net/kip0/_pxn=0+_pxK=18639+_pxE=mp4', url, 1)
        return [{'url': url, 'ext': 'mp4'}]

    def _extract_video_formats(self, mdoc, mtvn_id, video_id):
        if re.match(r'.*/(error_country_block\.swf|geoblock\.mp4|copyright_error\.flv(?:\?geo\b.+?)?)$', mdoc.find('.//src').text) is not None:
            if mtvn_id is not None and self._MOBILE_TEMPLATE is not None:
                self.to_screen('The normal version is not available from your '
                               'country, trying with the mobile version')
                return self._extract_mobile_video_formats(mtvn_id)
            raise ExtractorError('This video is not available from your country.',
                                 expected=True)

        formats = []
        for rendition in mdoc.findall('.//rendition'):
            if rendition.get('method') == 'hls':
                hls_url = rendition.find('./src').text
                formats.extend(self._extract_m3u8_formats(
                    hls_url, video_id, ext='mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            else:
                # fms
                try:
                    _, _, ext = rendition.attrib['type'].partition('/')
                    rtmp_video_url = rendition.find('./src').text
                    if 'error_not_available.swf' in rtmp_video_url:
                        raise ExtractorError(
                            '%s said: video is not available' % self.IE_NAME,
                            expected=True)
                    if rtmp_video_url.endswith('siteunavail.png'):
                        continue
                    formats.extend([{
                        'ext': 'flv' if rtmp_video_url.startswith('rtmp') else ext,
                        'url': rtmp_video_url,
                        'format_id': join_nonempty(
                            'rtmp' if rtmp_video_url.startswith('rtmp') else None,
                            rendition.get('bitrate')),
                        'width': int(rendition.get('width')),
                        'height': int(rendition.get('height')),
                    }])
                except (KeyError, TypeError):
                    raise ExtractorError('Invalid rendition field.')
        if formats:
            self._sort_formats(formats)
        return formats

    def _extract_subtitles(self, mdoc, mtvn_id):
        subtitles = {}
        for transcript in mdoc.findall('.//transcript'):
            if transcript.get('kind') != 'captions':
                continue
            lang = transcript.get('srclang')
            for typographic in transcript.findall('./typographic'):
                sub_src = typographic.get('src')
                if not sub_src:
                    continue
                ext = typographic.get('format')
                if ext == 'cea-608':
                    ext = 'scc'
                subtitles.setdefault(lang, []).append({
                    'url': compat_str(sub_src),
                    'ext': ext
                })
        return subtitles

    def _get_video_info(self, itemdoc, use_hls=True):
        uri = itemdoc.find('guid').text
        video_id = self._id_from_uri(uri)
        self.report_extraction(video_id)
        content_el = itemdoc.find('%s/%s' % (_media_xml_tag('group'), _media_xml_tag('content')))
        mediagen_url = self._remove_template_parameter(content_el.attrib['url'])
        mediagen_url = mediagen_url.replace('device={device}', '')
        if 'acceptMethods' not in mediagen_url:
            mediagen_url += '&' if '?' in mediagen_url else '?'
            mediagen_url += 'acceptMethods='
            mediagen_url += 'hls' if use_hls else 'fms'

        mediagen_doc = self._download_xml(
            mediagen_url, video_id, 'Downloading video urls', fatal=False)

        if mediagen_doc is False:
            return None

        item = mediagen_doc.find('./video/item')
        if item is not None and item.get('type') == 'text':
            message = '%s returned error: ' % self.IE_NAME
            if item.get('code') is not None:
                message += '%s - ' % item.get('code')
            message += item.text
            raise ExtractorError(message, expected=True)

        description = strip_or_none(xpath_text(itemdoc, 'description'))

        timestamp = timeconvert(xpath_text(itemdoc, 'pubDate'))

        title_el = None
        if title_el is None:
            title_el = find_xpath_attr(
                itemdoc, './/{http://search.yahoo.com/mrss/}category',
                'scheme', 'urn:mtvn:video_title')
        if title_el is None:
            title_el = itemdoc.find('.//{http://search.yahoo.com/mrss/}title')
        if title_el is None:
            title_el = itemdoc.find('.//title')
            if title_el.text is None:
                title_el = None

        title = title_el.text
        if title is None:
            raise ExtractorError('Could not find video title')
        title = title.strip()

        series = find_xpath_attr(
            itemdoc, './/{http://search.yahoo.com/mrss/}category',
            'scheme', 'urn:mtvn:franchise')
        season = find_xpath_attr(
            itemdoc, './/{http://search.yahoo.com/mrss/}category',
            'scheme', 'urn:mtvn:seasonN')
        episode = find_xpath_attr(
            itemdoc, './/{http://search.yahoo.com/mrss/}category',
            'scheme', 'urn:mtvn:episodeN')
        series = series.text if series is not None else None
        season = season.text if season is not None else None
        episode = episode.text if episode is not None else None
        if season and episode:
            # episode number includes season, so remove it
            episode = re.sub(r'^%s' % season, '', episode)

        # This a short id that's used in the webpage urls
        mtvn_id = None
        mtvn_id_node = find_xpath_attr(itemdoc, './/{http://search.yahoo.com/mrss/}category',
                                       'scheme', 'urn:mtvn:id')
        if mtvn_id_node is not None:
            mtvn_id = mtvn_id_node.text

        formats = self._extract_video_formats(mediagen_doc, mtvn_id, video_id)

        # Some parts of complete video may be missing (e.g. missing Act 3 in
        # http://www.southpark.de/alle-episoden/s14e01-sexual-healing)
        if not formats:
            return None

        self._sort_formats(formats)

        return {
            'title': title,
            'formats': formats,
            'subtitles': self._extract_subtitles(mediagen_doc, mtvn_id),
            'id': video_id,
            'thumbnail': self._get_thumbnail_url(uri, itemdoc),
            'description': description,
            'duration': float_or_none(content_el.attrib.get('duration')),
            'timestamp': timestamp,
            'series': series,
            'season_number': int_or_none(season),
            'episode_number': int_or_none(episode),
        }

    def _get_feed_query(self, uri):
        data = {'uri': uri}
        if self._LANG:
            data['lang'] = self._LANG
        return data

    def _get_videos_info(self, uri, use_hls=True, url=None):
        video_id = self._id_from_uri(uri)
        feed_url = self._get_feed_url(uri, url)
        info_url = update_url_query(feed_url, self._get_feed_query(uri))
        return self._get_videos_info_from_url(info_url, video_id, use_hls)

    def _get_videos_info_from_url(self, url, video_id, use_hls=True):
        idoc = self._download_xml(
            url, video_id,
            'Downloading info', transform_source=fix_xml_ampersands)

        title = xpath_text(idoc, './channel/title')
        description = xpath_text(idoc, './channel/description')

        entries = []
        for item in idoc.findall('.//item'):
            info = self._get_video_info(item, use_hls)
            if info:
                entries.append(info)

        # TODO: should be multi-video
        return self.playlist_result(
            entries, playlist_title=title, playlist_description=description)

    def _extract_triforce_mgid(self, webpage, data_zone=None, video_id=None):
        triforce_feed = self._parse_json(self._search_regex(
            r'triforceManifestFeed\s*=\s*({.+?})\s*;\s*\n', webpage,
            'triforce feed', default='{}'), video_id, fatal=False)

        data_zone = self._search_regex(
            r'data-zone=(["\'])(?P<zone>.+?_lc_promo.*?)\1', webpage,
            'data zone', default=data_zone, group='zone')

        feed_url = try_get(
            triforce_feed, lambda x: x['manifest']['zones'][data_zone]['feed'],
            compat_str)
        if not feed_url:
            return

        feed = self._download_json(feed_url, video_id, fatal=False)
        if not feed:
            return

        return try_get(feed, lambda x: x['result']['data']['id'], compat_str)

    @staticmethod
    def _extract_child_with_type(parent, t):
        for c in parent['children']:
            if c.get('type') == t:
                return c

    def _extract_mgid(self, webpage):
        try:
            # the url can be http://media.mtvnservices.com/fb/{mgid}.swf
            # or http://media.mtvnservices.com/{mgid}
            og_url = self._og_search_video_url(webpage)
            mgid = url_basename(og_url)
            if mgid.endswith('.swf'):
                mgid = mgid[:-4]
        except RegexNotFoundError:
            mgid = None

        if mgid is None or ':' not in mgid:
            mgid = self._search_regex(
                [r'data-mgid="(.*?)"', r'swfobject\.embedSWF\(".*?(mgid:.*?)"'],
                webpage, 'mgid', default=None)

        if not mgid:
            sm4_embed = self._html_search_meta(
                'sm4:video:embed', webpage, 'sm4 embed', default='')
            mgid = self._search_regex(
                r'embed/(mgid:.+?)["\'&?/]', sm4_embed, 'mgid', default=None)

        if not mgid:
            mgid = self._extract_triforce_mgid(webpage)

        if not mgid:
            data = self._parse_json(self._search_regex(
                r'__DATA__\s*=\s*({.+?});', webpage, 'data'), None)
            main_container = self._extract_child_with_type(data, 'MainContainer')
            ab_testing = self._extract_child_with_type(main_container, 'ABTesting')
            video_player = self._extract_child_with_type(ab_testing or main_container, 'VideoPlayer')
            if video_player:
                mgid = try_get(video_player, lambda x: x['props']['media']['video']['config']['uri'])
            else:
                flex_wrapper = self._extract_child_with_type(ab_testing or main_container, 'FlexWrapper')
                auth_suite_wrapper = self._extract_child_with_type(flex_wrapper, 'AuthSuiteWrapper')
                player = self._extract_child_with_type(auth_suite_wrapper or flex_wrapper, 'Player')
                if player:
                    mgid = try_get(player, lambda x: x['props']['videoDetail']['mgid'])

        if not mgid:
            raise ExtractorError('Could not extract mgid')

        return mgid

    def _real_extract(self, url):
        title = url_basename(url)
        webpage = self._download_webpage(url, title)
        mgid = self._extract_mgid(webpage)
        videos_info = self._get_videos_info(mgid, url=url)
        return videos_info

class MTVServicesEmbeddedIE(MTVServicesInfoExtractor):
    IE_NAME = 'mtvservices:embedded'
    _VALID_URL = r'https?://media\.mtvnservices\.com/embed/(?P<mgid>.+?)(\?|/|$)'
    _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//media\.mtvnservices\.com/embed/.+?)\1']

    _TEST = {
        # From http://www.thewrap.com/peter-dinklage-sums-up-game-of-thrones-in-45-seconds-video/
        'url': 'http://media.mtvnservices.com/embed/mgid:uma:video:mtv.com:1043906/cp~vid%3D1043906%26uri%3Dmgid%3Auma%3Avideo%3Amtv.com%3A1043906',
        'md5': 'cb349b21a7897164cede95bd7bf3fbb9',
        'info_dict': {
            'id': '1043906',
            'ext': 'mp4',
            'title': 'Peter Dinklage Sums Up \'Game Of Thrones\' In 45 Seconds',
            'description': '"Sexy sexy sexy, stabby stabby stabby, beautiful language," says Peter Dinklage as he tries summarizing "Game of Thrones" in under a minute.',
            'timestamp': 1400126400,
            'upload_date': '20140515',
        },
    }

    def _get_feed_url(self, uri, url=None):
        video_id = self._id_from_uri(uri)
        config = self._download_json(
            'http://media.mtvnservices.com/pmt/e1/access/index.html?uri=%s&configtype=edge' % uri, video_id)
        return self._remove_template_parameter(config['feedWithQueryParams'])

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        mgid = mobj.group('mgid')
        return self._get_videos_info(mgid)

class MTVIE(MTVServicesInfoExtractor):
    IE_NAME = 'mtv'
    _VALID_URL = r'https?://(?:www\.)?mtv\.com/(?:video-clips|(?:full-)?episodes)/(?P<id>[^/?#.]+)'
    _FEED_URL = 'http://www.mtv.com/feeds/mrss/'

    _TESTS = [{
        'url': 'http://www.mtv.com/video-clips/vl8qof/unlocking-the-truth-trailer',
        'md5': '1edbcdf1e7628e414a8c5dcebca3d32b',
        'info_dict': {
            'id': '5e14040d-18a4-47c4-a582-43ff602de88e',
            'ext': 'mp4',
            'title': 'Unlocking The Truth|July 18, 2016|1|101|Trailer',
            'description': '"Unlocking the Truth" premieres August 17th at 11/10c.',
            'timestamp': 1468846800,
            'upload_date': '20160718',
        },
    }, {
        'url': 'http://www.mtv.com/full-episodes/94tujl/unlocking-the-truth-gates-of-hell-season-1-ep-101',
        'only_matching': True,
    }, {
        'url': 'http://www.mtv.com/episodes/g8xu7q/teen-mom-2-breaking-the-wall-season-7-ep-713',
        'only_matching': True,
    }]

class MTVJapanIE(MTVServicesInfoExtractor):
    IE_NAME = 'mtvjapan'
    _VALID_URL = r'https?://(?:www\.)?mtvjapan\.com/videos/(?P<id>[0-9a-z]+)'

    _TEST = {
        'url': 'http://www.mtvjapan.com/videos/prayht/fresh-info-cadillac-escalade',
        'info_dict': {
            'id': 'bc01da03-6fe5-4284-8880-f291f4e368f5',
            'ext': 'mp4',
            'title': '【Fresh Info】Cadillac ESCALADE Sport Edition',
        },
        'params': {
            'skip_download': True,
        },
    }
    _GEO_COUNTRIES = ['JP']
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'

    def _get_feed_query(self, uri):
        return {
            'arcEp': 'mtvjapan.com',
            'mgid': uri,
        }

class MTVVideoIE(MTVServicesInfoExtractor):
    IE_NAME = 'mtv:video'
    _VALID_URL = r'''(?x)^https?://
        (?:(?:www\.)?mtv\.com/videos/.+?/(?P<videoid>[0-9]+)/[^/]+$|
           m\.mtv\.com/videos/video\.rbml\?.*?id=(?P<mgid>[^&]+))'''

    _FEED_URL = 'http://www.mtv.com/player/embed/AS3/rss/'

    _TESTS = [
        {
            'url': 'http://www.mtv.com/videos/misc/853555/ours-vh1-storytellers.jhtml',
            'md5': '850f3f143316b1e71fa56a4edfd6e0f8',
            'info_dict': {
                'id': '853555',
                'ext': 'mp4',
                'title': 'Taylor Swift - "Ours (VH1 Storytellers)"',
                'description': 'Album: Taylor Swift performs "Ours" for VH1 Storytellers at Harvey Mudd College.',
                'timestamp': 1352610000,
                'upload_date': '20121111',
            },
        },
    ]

    def _get_thumbnail_url(self, uri, itemdoc):
        return 'http://mtv.mtvnimages.com/uri/' + uri

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        video_id = mobj.group('videoid')
        uri = mobj.groupdict().get('mgid')
        if uri is None:
            webpage = self._download_webpage(url, video_id)

            # Some videos come from Vevo.com
            m_vevo = re.search(
                r'(?s)isVevoVideo = true;.*?vevoVideoId = "(.*?)";', webpage)
            if m_vevo:
                vevo_id = m_vevo.group(1)
                self.to_screen('Vevo video detected: %s' % vevo_id)
                return self.url_result('vevo:%s' % vevo_id, ie='Vevo')

            uri = self._html_search_regex(r'/uri/(.*?)\?', webpage, 'uri')
        return self._get_videos_info(uri)

class MTVDEIE(MTVServicesInfoExtractor):
    IE_NAME = 'mtv.de'
    _VALID_URL = r'https?://(?:www\.)?mtv\.de/(?:musik/videoclips|folgen|news)/(?P<id>[0-9a-z]+)'
    _TESTS = [{
        'url': 'http://www.mtv.de/musik/videoclips/2gpnv7/Traum',
        'info_dict': {
            'id': 'd5d472bc-f5b7-11e5-bffd-a4badb20dab5',
            'ext': 'mp4',
            'title': 'Traum',
            'description': 'Traum',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        },
        'skip': 'Blocked at Travis CI',
    }, {
        # mediagen URL without query (e.g. http://videos.mtvnn.com/mediagen/e865da714c166d18d6f80893195fcb97)
        'url': 'http://www.mtv.de/folgen/6b1ylu/teen-mom-2-enthuellungen-S5-F1',
        'info_dict': {
            'id': '1e5a878b-31c5-11e7-a442-0e40cf2fc285',
            'ext': 'mp4',
            'title': 'Teen Mom 2',
            'description': 'md5:dc65e357ef7e1085ed53e9e9d83146a7',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        },
        'skip': 'Blocked at Travis CI',
    }, {
        'url': 'http://www.mtv.de/news/glolix/77491-mtv-movies-spotlight--pixels--teil-3',
        'info_dict': {
            'id': 'local_playlist-4e760566473c4c8c5344',
            'ext': 'mp4',
            'title': 'Article_mtv-movies-spotlight-pixels-teil-3_short-clips_part1',
            'description': 'MTV Movies Supercut',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        },
        'skip': 'Das Video kann zur Zeit nicht abgespielt werden.',
    }]
    _GEO_COUNTRIES = ['DE']
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'

    def _get_feed_query(self, uri):
        return {
            'arcEp': 'mtv.de',
            'mgid': uri,
        }

class MTVItaliaIE(MTVServicesInfoExtractor):
    IE_NAME = 'mtv.it'
    _VALID_URL = r'https?://(?:www\.)?mtv\.it/(?:episodi|video|musica)/(?P<id>[0-9a-z]+)'
    _TESTS = [{
        'url': 'http://www.mtv.it/episodi/24bqab/mario-una-serie-di-maccio-capatonda-cavoli-amario-episodio-completo-S1-E1',
        'info_dict': {
            'id': '0f0fc78e-45fc-4cce-8f24-971c25477530',
            'ext': 'mp4',
            'title': 'Cavoli amario (episodio completo)',
            'description': 'md5:4962bccea8fed5b7c03b295ae1340660',
            'series': 'Mario - Una Serie Di Maccio Capatonda',
            'season_number': 1,
            'episode_number': 1,
        },
        'params': {
            'skip_download': True,
        },
    }]
    _GEO_COUNTRIES = ['IT']
    _FEED_URL = 'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed'

    def _get_feed_query(self, uri):
        return {
            'arcEp': 'mtv.it',
            'mgid': uri,
        }

class MTVItaliaProgrammaIE(MTVItaliaIE):
    IE_NAME = 'mtv.it:programma'
    _VALID_URL = r'https?://(?:www\.)?mtv\.it/(?:programmi|playlist)/(?P<id>[0-9a-z]+)'
    _TESTS = [{
        # program page: general
        'url': 'http://www.mtv.it/programmi/s2rppv/mario-una-serie-di-maccio-capatonda',
        'info_dict': {
            'id': 'a6f155bc-8220-4640-aa43-9b95f64ffa3d',
            'title': 'Mario - Una Serie Di Maccio Capatonda',
            'description': 'md5:72fbffe1f77ccf4e90757dd4e3216153',
        },
        'playlist_count': 2,
        'params': {
            'skip_download': True,
        },
    }, {
        # program page: specific season
        'url': 'http://www.mtv.it/programmi/d9ncjf/mario-una-serie-di-maccio-capatonda-S2',
        'info_dict': {
            'id': '4deeb5d8-f272-490c-bde2-ff8d261c6dd1',
            'title': 'Mario - Una Serie Di Maccio Capatonda - Stagione 2',
        },
        'playlist_count': 34,
        'params': {
            'skip_download': True,
        },
    }, {
        # playlist page + redirect
        'url': 'http://www.mtv.it/playlist/sexy-videos/ilctal',
        'info_dict': {
            'id': 'dee8f9ee-756d-493b-bf37-16d1d2783359',
            'title': 'Sexy Videos',
        },
        'playlist_mincount': 145,
        'params': {
            'skip_download': True,
        },
    }]
    _GEO_COUNTRIES = ['IT']
    _FEED_URL = 'http://www.mtv.it/feeds/triforce/manifest/v8'

    def _get_entries(self, title, url):
        while True:
            pg = self._search_regex(r'/(\d+)$', url, 'entries', '1')
            entries = self._download_json(url, title, 'page %s' % pg)
            url = try_get(
                entries, lambda x: x['result']['nextPageURL'], compat_str)
            entries = try_get(
                entries, (
                    lambda x: x['result']['data']['items'],
                    lambda x: x['result']['data']['seasons']),
                list)
            for entry in entries or []:
                if entry.get('canonicalURL'):
                    yield self.url_result(entry['canonicalURL'])
            if not url:
                break

    def _real_extract(self, url):
        query = {'url': url}
        info_url = update_url_query(self._FEED_URL, query)
        video_id = self._match_id(url)
        info = self._download_json(info_url, video_id).get('manifest')

        redirect = try_get(
            info, lambda x: x['newLocation']['url'], compat_str)
        if redirect:
            return self.url_result(redirect)

        title = info.get('title')
        video_id = try_get(
            info, lambda x: x['reporting']['itemId'], compat_str)
        parent_id = try_get(
            info, lambda x: x['reporting']['parentId'], compat_str)

        playlist_url = current_url = None
        for z in (info.get('zones') or {}).values():
            if z.get('moduleName') in ('INTL_M304', 'INTL_M209'):
                info_url = z.get('feed')
            if z.get('moduleName') in ('INTL_M308', 'INTL_M317'):
                playlist_url = playlist_url or z.get('feed')
            if z.get('moduleName') in ('INTL_M300',):
                current_url = current_url or z.get('feed')

        if not info_url:
            raise ExtractorError('No info found')

        if video_id == parent_id:
            video_id = self._search_regex(
                r'([^\/]+)/[^\/]+$', info_url, 'video_id')

        info = self._download_json(info_url, video_id, 'Show infos')
        info = try_get(info, lambda x: x['result']['data'], dict)
        title = title or try_get(
            info, (
                lambda x: x['title'],
                lambda x: x['headline']),
            compat_str)
        description = try_get(info, lambda x: x['content'], compat_str)

        if current_url:
            season = try_get(
                self._download_json(playlist_url, video_id, 'Seasons info'),
                lambda x: x['result']['data'], dict)
            current = try_get(
                season, lambda x: x['currentSeason'], compat_str)
            seasons = try_get(
                season, lambda x: x['seasons'], list) or []

            if current in [s.get('eTitle') for s in seasons]:
                playlist_url = current_url

        title = re.sub(
            r'[-|]\s*(?:mtv\s*italia|programma|playlist)',
            '', title, flags=re.IGNORECASE).strip()

        return self.playlist_result(
            self._get_entries(title, playlist_url),
            video_id, title, description)


code:
import json

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

class MuenchenTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?muenchen\.tv/livestream'
    IE_DESC = 'münchen.tv'
    _TEST = {
        'url': 'http://www.muenchen.tv/livestream/',
        'info_dict': {
            'id': '5334',
            'display_id': 'live',
            'ext': 'mp4',
            'title': 're:^münchen.tv-Livestream [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'is_live': True,
            'thumbnail': r're:^https?://.*\.jpg$'
        },
        'params': {
            'skip_download': True,
        }
    }

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

        title = self._og_search_title(webpage)

        data_js = self._search_regex(
            r'(?s)\nplaylist:\s*(\[.*?}\]),',
            webpage, 'playlist configuration')
        data_json = js_to_json(data_js)
        data = json.loads(data_json)[0]

        video_id = data['mediaid']
        thumbnail = data.get('image')

        formats = []
        for format_num, s in enumerate(data['sources']):
            ext = determine_ext(s['file'], None)
            label_str = s.get('label')
            if label_str is None:
                label_str = '_%d' % format_num

            if ext is None:
                format_id = label_str
            else:
                format_id = '%s-%s' % (ext, label_str)

            formats.append({
                'url': s['file'],
                'tbr': int_or_none(s.get('label')),
                'ext': 'mp4',
                'format_id': format_id,
                'preference': -100 if '.smil' in s['file'] else 0,  # Strictly inferior than all other formats?
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'formats': formats,
            'is_live': True,
            'thumbnail': thumbnail,
        }

repo_name: Lesmiscore/ytdl


code:
import functools
import json

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    OnDemandPagedList,
    determine_ext,
    int_or_none,
    try_get,
)

class MurrtubeIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                        (?:
                            murrtube:|
                            https?://murrtube\.net/videos/(?P<slug>[a-z0-9\-]+)\-
                        )
                        (?P<id>[a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{12})
                    '''
    _TEST = {
        'url': 'https://murrtube.net/videos/inferno-x-skyler-148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
        'md5': '169f494812d9a90914b42978e73aa690',
        'info_dict': {
            'id': '148b6f2a-fdcc-4902-affe-9c0f41aaaca0',
            'ext': 'mp4',
            'title': 'Inferno X Skyler',
            'description': 'Humping a very good slutty sheppy (roomate)',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 284,
            'uploader': 'Inferno Wolf',
            'age_limit': 18,
            'comment_count': int,
            'view_count': int,
            'like_count': int,
            'tags': ['hump', 'breed', 'Fursuit', 'murrsuit', 'bareback'],
        }
    }

    def _download_gql(self, video_id, op, note=None, fatal=True):
        result = self._download_json(
            'https://murrtube.net/graphql',
            video_id, note, data=json.dumps(op).encode(), fatal=fatal,
            headers={'Content-Type': 'application/json'})
        return result['data']

    def _real_extract(self, url):
        video_id = self._match_id(url)
        data = self._download_gql(video_id, {
            'operationName': 'Medium',
            'variables': {
                'id': video_id,
            },
            'query': '''\
query Medium($id: ID!) {
  medium(id: $id) {
    title
    description
    key
    duration
    commentsCount
    likesCount
    viewsCount
    thumbnailKey
    tagList
    user {
      name
      __typename
    }
    __typename
  }
}'''})
        meta = data['medium']

        storage_url = 'https://storage.murrtube.net/murrtube/'
        format_url = storage_url + meta.get('key', '')
        thumbnail = storage_url + meta.get('thumbnailKey', '')

        if determine_ext(format_url) == 'm3u8':
            formats = self._extract_m3u8_formats(
                format_url, video_id, 'mp4', entry_protocol='m3u8_native', fatal=False)
        else:
            formats = [{'url': format_url}]

        return {
            'id': video_id,
            'title': meta.get('title'),
            'description': meta.get('description'),
            'formats': formats,
            'thumbnail': thumbnail,
            'duration': int_or_none(meta.get('duration')),
            'uploader': try_get(meta, lambda x: x['user']['name']),
            'view_count': meta.get('viewsCount'),
            'like_count': meta.get('likesCount'),
            'comment_count': meta.get('commentsCount'),
            'tags': meta.get('tagList'),
            'age_limit': 18,
        }

class MurrtubeUserIE(MurrtubeIE):
    IE_DESC = 'Murrtube user profile'
    _VALID_URL = r'https?://murrtube\.net/(?P<id>[^/]+)$'
    _TEST = {
        'url': 'https://murrtube.net/stormy',
        'info_dict': {
            'id': 'stormy',
        },
        'playlist_mincount': 27,
    }
    _PAGE_SIZE = 10

    def _fetch_page(self, username, user_id, page):
        data = self._download_gql(username, {
            'operationName': 'Media',
            'variables': {
                'limit': self._PAGE_SIZE,
                'offset': page * self._PAGE_SIZE,
                'sort': 'latest',
                'userId': user_id,
            },
            'query': '''\
query Media($q: String, $sort: String, $userId: ID, $offset: Int!, $limit: Int!) {
  media(q: $q, sort: $sort, userId: $userId, offset: $offset, limit: $limit) {
    id
    __typename
  }
}'''},
            'Downloading page {0}'.format(page + 1))
        if data is None:
            raise ExtractorError(f'Failed to retrieve video list for page {page + 1}')

        media = data['media']

        for entry in media:
            yield self.url_result('murrtube:{0}'.format(entry['id']), MurrtubeIE.ie_key())

    def _real_extract(self, url):
        username = self._match_id(url)
        data = self._download_gql(username, {
            'operationName': 'User',
            'variables': {
                'id': username,
            },
            'query': '''\
query User($id: ID!) {
  user(id: $id) {
    id
    __typename
  }
}'''},
            'Downloading user info')
        if data is None:
            raise ExtractorError('Failed to fetch user info')

        user = data['user']

        entries = OnDemandPagedList(functools.partial(
            self._fetch_page, username, user.get('id')), self._PAGE_SIZE)

        return self.playlist_result(entries, username)


code:
from .common import InfoExtractor

class MuseScoreIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?musescore\.com/(?:user/\d+|[^/]+)(?:/scores)?/(?P<id>[^#&?]+)'
    _TESTS = [{
        'url': 'https://musescore.com/user/73797/scores/142975',
        'info_dict': {
            'id': '142975',
            'ext': 'mp3',
            'title': 'WA Mozart Marche Turque (Turkish March fingered)',
            'description': 'md5:7ede08230e4eaabd67a4a98bb54d07be',
            'thumbnail': r're:https?://(?:www\.)?musescore\.com/.*\.png[^$]+',
            'uploader': 'PapyPiano',
            'creator': 'Wolfgang Amadeus Mozart',
        }
    }, {
        'url': 'https://musescore.com/user/36164500/scores/6837638',
        'info_dict': {
            'id': '6837638',
            'ext': 'mp3',
            'title': 'Sweet Child O\' Mine  – Guns N\' Roses sweet child',
            'description': 'md5:4dca71191c14abc312a0a4192492eace',
            'thumbnail': r're:https?://(?:www\.)?musescore\.com/.*\.png[^$]+',
            'uploader': 'roxbelviolin',
            'creator': 'Guns N´Roses Arr. Roxbel Violin',
        }
    }, {
        'url': 'https://musescore.com/classicman/fur-elise',
        'info_dict': {
            'id': '33816',
            'ext': 'mp3',
            'title': 'Für Elise – Beethoven',
            'description': 'md5:49515a3556d5ecaf9fa4b2514064ac34',
            'thumbnail': r're:https?://(?:www\.)?musescore\.com/.*\.png[^$]+',
            'uploader': 'ClassicMan',
            'creator': 'Ludwig van Beethoven (1770–1827)',
        }
    }, {
        'url': 'https://musescore.com/minh_cuteee/scores/6555384',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        webpage = self._download_webpage(url, None)
        url = self._og_search_url(webpage) or url
        id = self._match_id(url)
        mp3_url = self._download_json(f'https://musescore.com/api/jmuse?id={id}&index=0&type=mp3&v2=1', id,
                                      headers={'authorization': '63794e5461e4cfa046edfbdddfccc1ac16daffd2'})['info']['url']
        formats = [{
            'url': mp3_url,
            'ext': 'mp3',
            'vcodec': 'none',
        }]

        return {
            'id': id,
            'formats': formats,
            'title': self._og_search_title(webpage),
            'description': self._og_search_description(webpage),
            'thumbnail': self._og_search_thumbnail(webpage),
            'uploader': self._html_search_meta('musescore:author', webpage, 'uploader'),
            'creator': self._html_search_meta('musescore:composer', webpage, 'composer'),
        }


code:
from .common import InfoExtractor
from ..utils import (
    date_from_str,
    format_field,
    try_get,
    unified_strdate,
)

class MusicdexBaseIE(InfoExtractor):
    def _return_info(self, track_json, album_json, id):
        return {
            'id': str(id),
            'title': track_json.get('name'),
            'track': track_json.get('name'),
            'description': track_json.get('description'),
            'track_number': track_json.get('number'),
            'url': format_field(track_json, 'url', 'https://www.musicdex.org/%s'),
            'duration': track_json.get('duration'),
            'genre': [genre.get('name') for genre in track_json.get('genres') or []],
            'like_count': track_json.get('likes_count'),
            'view_count': track_json.get('plays'),
            'artist': [artist.get('name') for artist in track_json.get('artists') or []],
            'album_artist': [artist.get('name') for artist in album_json.get('artists') or []],
            'thumbnail': format_field(album_json, 'image', 'https://www.musicdex.org/%s'),
            'album': album_json.get('name'),
            'release_year': try_get(album_json, lambda x: date_from_str(unified_strdate(x['release_date'])).year),
            'extractor_key': MusicdexSongIE.ie_key(),
            'extractor': 'MusicdexSong',
        }

class MusicdexSongIE(MusicdexBaseIE):
    _VALID_URL = r'https?://(?:www\.)?musicdex\.org/track/(?P<id>\d+)'

    _TESTS = [{
        'url': 'https://www.musicdex.org/track/306/dual-existence',
        'info_dict': {
            'id': '306',
            'ext': 'mp3',
            'title': 'dual existence',
            'description': '#NIPPONSEI @ IRC.RIZON.NET',
            'track': 'dual existence',
            'track_number': 1,
            'duration': 266000,
            'genre': ['Anime'],
            'like_count': int,
            'view_count': int,
            'artist': ['fripSide'],
            'album_artist': ['fripSide'],
            'thumbnail': 'https://www.musicdex.org/storage/album/9iDIam1DHTVqUG4UclFIEq1WAFGXfPW4y0TtZa91.png',
            'album': 'To Aru Kagaku no Railgun T OP2 Single - dual existence',
            'release_year': 2020
        },
        'params': {'skip_download': True}
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        data_json = self._download_json(f'https://www.musicdex.org/secure/tracks/{id}?defaultRelations=true', id)['track']
        return self._return_info(data_json, data_json.get('album') or {}, id)

class MusicdexAlbumIE(MusicdexBaseIE):
    _VALID_URL = r'https?://(?:www\.)?musicdex\.org/album/(?P<id>\d+)'

    _TESTS = [{
        'url': 'https://www.musicdex.org/album/56/tenmon-and-eiichiro-yanagi-minori/ef-a-tale-of-memories-original-soundtrack-2-fortissimo',
        'playlist_mincount': 28,
        'info_dict': {
            'id': '56',
            'genre': ['OST'],
            'view_count': int,
            'artist': ['TENMON & Eiichiro Yanagi / minori'],
            'title': 'ef - a tale of memories Original Soundtrack 2 ~fortissimo~',
            'release_year': 2008,
            'thumbnail': 'https://www.musicdex.org/storage/album/2rSHkyYBYfB7sbvElpEyTMcUn6toY7AohOgJuDlE.jpg',
        },
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        data_json = self._download_json(f'https://www.musicdex.org/secure/albums/{id}?defaultRelations=true', id)['album']
        entries = [self._return_info(track, data_json, track['id']) for track in data_json.get('tracks') or [] if track.get('id')]

        return {
            '_type': 'playlist',
            'id': id,
            'title': data_json.get('name'),
            'description': data_json.get('description'),
            'genre': [genre.get('name') for genre in data_json.get('genres') or []],
            'view_count': data_json.get('plays'),
            'artist': [artist.get('name') for artist in data_json.get('artists') or []],
            'thumbnail': format_field(data_json, 'image', 'https://www.musicdex.org/%s'),
            'release_year': try_get(data_json, lambda x: date_from_str(unified_strdate(x['release_date'])).year),
            'entries': entries,
        }

class MusicdexPageIE(MusicdexBaseIE):
    def _entries(self, id):
        next_page_url = self._API_URL % id
        while next_page_url:
            data_json = self._download_json(next_page_url, id)['pagination']
            for data in data_json.get('data') or []:
                yield data
            next_page_url = data_json.get('next_page_url')

class MusicdexArtistIE(MusicdexPageIE):
    _VALID_URL = r'https?://(?:www\.)?musicdex\.org/artist/(?P<id>\d+)'
    _API_URL = 'https://www.musicdex.org/secure/artists/%s/albums?page=1'

    _TESTS = [{
        'url': 'https://www.musicdex.org/artist/11/fripside',
        'playlist_mincount': 28,
        'info_dict': {
            'id': '11',
            'view_count': int,
            'title': 'fripSide',
            'thumbnail': 'https://www.musicdex.org/storage/artist/ZmOz0lN2vsweegB660em3xWffCjLPmTQHqJls5Xx.jpg',
        },
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        data_json = self._download_json(f'https://www.musicdex.org/secure/artists/{id}', id)['artist']
        entries = []
        for album in self._entries(id):
            entries.extend(self._return_info(track, album, track['id']) for track in album.get('tracks') or [] if track.get('id'))

        return {
            '_type': 'playlist',
            'id': id,
            'title': data_json.get('name'),
            'view_count': data_json.get('plays'),
            'thumbnail': format_field(data_json, 'image_small', 'https://www.musicdex.org/%s'),
            'entries': entries,
        }

class MusicdexPlaylistIE(MusicdexPageIE):
    _VALID_URL = r'https?://(?:www\.)?musicdex\.org/playlist/(?P<id>\d+)'
    _API_URL = 'https://www.musicdex.org/secure/playlists/%s/tracks?perPage=10000&page=1'

    _TESTS = [{
        'url': 'https://www.musicdex.org/playlist/9/test',
        'playlist_mincount': 73,
        'info_dict': {
            'id': '9',
            'view_count': int,
            'title': 'Test',
            'thumbnail': 'https://www.musicdex.org/storage/album/jXATI79f0IbQ2sgsKYOYRCW3zRwF3XsfHhzITCuJ.jpg',
            'description': 'Test 123 123 21312 32121321321321312',
        },
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        data_json = self._download_json(f'https://www.musicdex.org/secure/playlists/{id}', id)['playlist']
        entries = [self._return_info(track, track.get('album') or {}, track['id'])
                   for track in self._entries(id) or [] if track.get('id')]

        return {
            '_type': 'playlist',
            'id': id,
            'title': data_json.get('name'),
            'description': data_json.get('description'),
            'view_count': data_json.get('plays'),
            'thumbnail': format_field(data_json, 'image', 'https://www.musicdex.org/%s'),
            'entries': entries,
        }


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

class MwaveIE(InfoExtractor):
    _VALID_URL = r'https?://mwave\.interest\.me/(?:[^/]+/)?mnettv/videodetail\.m\?searchVideoDetailVO\.clip_id=(?P<id>[0-9]+)'
    _URL_TEMPLATE = 'http://mwave.interest.me/mnettv/videodetail.m?searchVideoDetailVO.clip_id=%s'
    _TESTS = [{
        'url': 'http://mwave.interest.me/mnettv/videodetail.m?searchVideoDetailVO.clip_id=168859',
        # md5 is unstable
        'info_dict': {
            'id': '168859',
            'ext': 'flv',
            'title': '[M COUNTDOWN] SISTAR - SHAKE IT',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'M COUNTDOWN',
            'duration': 206,
            'view_count': int,
        }
    }, {
        'url': 'http://mwave.interest.me/en/mnettv/videodetail.m?searchVideoDetailVO.clip_id=176199',
        'only_matching': True,
    }]

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

        vod_info = self._download_json(
            'http://mwave.interest.me/onair/vod_info.m?vodtype=CL&sectorid=&endinfo=Y&id=%s' % video_id,
            video_id, 'Download vod JSON')

        formats = []
        for num, cdn_info in enumerate(vod_info['cdn']):
            stream_url = cdn_info.get('url')
            if not stream_url:
                continue
            stream_name = cdn_info.get('name') or compat_str(num)
            f4m_stream = self._download_json(
                stream_url, video_id,
                'Download %s stream JSON' % stream_name)
            f4m_url = f4m_stream.get('fileurl')
            if not f4m_url:
                continue
            formats.extend(
                self._extract_f4m_formats(f4m_url + '&hdcore=3.0.3', video_id, f4m_id=stream_name))
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': vod_info['title'],
            'thumbnail': vod_info.get('cover'),
            'uploader': vod_info.get('program_title'),
            'duration': parse_duration(vod_info.get('time')),
            'view_count': int_or_none(vod_info.get('hit')),
            'formats': formats,
        }

class MwaveMeetGreetIE(InfoExtractor):
    _VALID_URL = r'https?://mwave\.interest\.me/(?:[^/]+/)?meetgreet/view/(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://mwave.interest.me/meetgreet/view/256',
        'info_dict': {
            'id': '173294',
            'ext': 'flv',
            'title': '[MEET&GREET] Park BoRam',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Mwave',
            'duration': 3634,
            'view_count': int,
        }
    }, {
        'url': 'http://mwave.interest.me/en/meetgreet/view/256',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        clip_id = self._html_search_regex(
            r'<iframe[^>]+src="/mnettv/ifr_clip\.m\?searchVideoDetailVO\.clip_id=(\d+)',
            webpage, 'clip ID')
        clip_url = MwaveIE._URL_TEMPLATE % clip_id
        return self.url_result(clip_url, 'Mwave', clip_id)


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import try_get

class MxplayerIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mxplayer\.in/(?P<type>movie|show/[-\w]+/[-\w]+)/(?P<display_id>[-\w]+)-(?P<id>\w+)'
    _TESTS = [{
        'url': 'https://www.mxplayer.in/show/watch-my-girlfriend-is-an-alien-hindi-dubbed/season-1/episode-1-online-9d2013d31d5835bb8400e3b3c5e7bb72',
        'info_dict': {
            'id': '9d2013d31d5835bb8400e3b3c5e7bb72',
            'ext': 'mp4',
            'title': 'Episode 1',
            'description': 'md5:62ed43eb9fec5efde5cf3bd1040b7670',
            'season_number': 1,
            'episode_number': 1,
            'duration': 2451,
            'season': 'Season 1',
            'series': 'My Girlfriend Is An Alien (Hindi Dubbed)',
            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/9d2013d31d5835bb8400e3b3c5e7bb72/en/16x9/320x180/9562f5f8df42cad09c9a9c4e69eb1567_1920x1080.webp',
            'episode': 'Episode 1'
        },
        'params': {
            'format': 'bv',
            'skip_download': True,
        },
    }, {
        'url': 'https://www.mxplayer.in/movie/watch-knock-knock-hindi-dubbed-movie-online-b9fa28df3bfb8758874735bbd7d2655a?watch=true',
        'info_dict': {
            'id': 'b9fa28df3bfb8758874735bbd7d2655a',
            'ext': 'mp4',
            'title': 'Knock Knock (Hindi Dubbed)',
            'description': 'md5:b195ba93ff1987309cfa58e2839d2a5b',
            'season_number': 0,
            'episode_number': 0,
            'duration': 5970,
            'season': 'Season 0',
            'series': None,
            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/b9fa28df3bfb8758874735bbd7d2655a/en/16x9/320x180/test_pic1588676032011.webp',
            'episode': 'Episode 0'
        },
        'params': {
            'format': 'bv',
            'skip_download': True,
        },
    }, {
        'url': 'https://www.mxplayer.in/show/watch-shaitaan/season-1/the-infamous-taxi-gang-of-meerut-online-45055d5bcff169ad48f2ad7552a83d6c',
        'info_dict': {
            'id': '45055d5bcff169ad48f2ad7552a83d6c',
            'ext': 'mp4',
            'title': 'The infamous taxi gang of Meerut',
            'description': 'md5:033a0a7e3fd147be4fb7e07a01a3dc28',
            'season_number': 1,
            'episode_number': 1,
            'duration': 2332,
            'season': 'Season 1',
            'series': 'Shaitaan',
            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/45055d5bcff169ad48f2ad7552a83d6c/en/16x9/320x180/voot_8e7d5f8d8183340869279c732c1e3a43.webp',
            'episode': 'Episode 1'
        },
        'params': {
            'format': 'best',
            'skip_download': True,
        },
    }, {
        'url': 'https://www.mxplayer.in/show/watch-aashram/chapter-1/duh-swapna-online-d445579792b0135598ba1bc9088a84cb',
        'info_dict': {
            'id': 'd445579792b0135598ba1bc9088a84cb',
            'ext': 'mp4',
            'title': 'Duh Swapna',
            'description': 'md5:35ff39c4bdac403c53be1e16a04192d8',
            'season_number': 1,
            'episode_number': 3,
            'duration': 2568,
            'season': 'Chapter 1',
            'series': 'Aashram',
            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/d445579792b0135598ba1bc9088a84cb/en/4x3/1600x1200/test_pic1624819307993.webp',
            'episode': 'Episode 3'
        },
        'params': {
            'format': 'bv',
            'skip_download': True,
        },
    }, {
        'url': 'https://www.mxplayer.in/show/watch-dangerous/season-1/chapter-1-online-5a351b4f9fb69436f6bd6ae3a1a75292',
        'info_dict': {
            'id': '5a351b4f9fb69436f6bd6ae3a1a75292',
            'ext': 'mp4',
            'title': 'Chapter 1',
            'description': 'md5:233886b8598bc91648ac098abe1d288f',
            'season_number': 1,
            'episode_number': 1,
            'duration': 1305,
            'season': 'Season 1',
            'series': 'Dangerous',
            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/5a351b4f9fb69436f6bd6ae3a1a75292/en/4x3/1600x1200/test_pic1624706302350.webp',
            'episode': 'Episode 1'
        },
        'params': {
            'format': 'bv',
            'skip_download': True,
        },
    }, {
        'url': 'https://www.mxplayer.in/movie/watch-the-attacks-of-2611-movie-online-0452f0d80226c398d63ce7e3ea40fa2d',
        'info_dict': {
            'id': '0452f0d80226c398d63ce7e3ea40fa2d',
            'ext': 'mp4',
            'title': 'The Attacks of 26/11',
            'description': 'md5:689bacd29e97b3f31eaf519eb14127e5',
            'season_number': 0,
            'episode_number': 0,
            'duration': 6085,
            'season': 'Season 0',
            'series': None,
            'thumbnail': 'https://qqcdnpictest.mxplay.com/pic/0452f0d80226c398d63ce7e3ea40fa2d/en/16x9/320x180/00c8955dab5e5d340dbde643f9b1f6fd_1920x1080.webp',
            'episode': 'Episode 0'
        },
        'params': {
            'format': 'best',
            'skip_download': True,
        },
    }]

    def _real_extract(self, url):
        type, display_id, video_id = self._match_valid_url(url).groups()
        type = 'movie_film' if type == 'movie' else 'tvshow_episode'
        API_URL = 'https://androidapi.mxplay.com/v1/detail/'
        headers = {
            'X-Av-Code': '23',
            'X-Country': 'IN',
            'X-Platform': 'android',
            'X-App-Version': '1370001318',
            'X-Resolution': '3840x2160',
        }
        data_json = self._download_json(f'{API_URL}{type}/{video_id}', display_id, headers=headers)['profile']

        season, series = None, None
        for dct in data_json.get('levelInfos', []):
            if dct.get('type') == 'tvshow_season':
                season = dct.get('name')
            elif dct.get('type') == 'tvshow_show':
                series = dct.get('name')
        thumbnails = []
        for thumb in data_json.get('poster', []):
            thumbnails.append({
                'url': thumb.get('url'),
                'width': thumb.get('width'),
                'height': thumb.get('height'),
            })

        formats = []
        subtitles = {}
        for dct in data_json.get('playInfo', []):
            if dct.get('extension') == 'mpd':
                frmt, subs = self._extract_mpd_formats_and_subtitles(dct.get('playUrl'), display_id, fatal=False)
                formats.extend(frmt)
                subtitles = self._merge_subtitles(subtitles, subs)
            elif dct.get('extension') == 'm3u8':
                frmt, subs = self._extract_m3u8_formats_and_subtitles(dct.get('playUrl'), display_id, fatal=False)
                formats.extend(frmt)
                subtitles = self._merge_subtitles(subtitles, subs)
        self._sort_formats(formats)
        return {
            'id': video_id,
            'display_id': display_id,
            'title': data_json.get('name') or display_id,
            'description': data_json.get('description'),
            'season_number': data_json.get('seasonNum'),
            'episode_number': data_json.get('episodeNum'),
            'duration': data_json.get('duration'),
            'season': season,
            'series': series,
            'thumbnails': thumbnails,
            'formats': formats,
            'subtitles': subtitles,
        }

class MxplayerShowIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mxplayer\.in/show/(?P<display_id>[-\w]+)-(?P<id>\w+)/?(?:$|[#?])'
    _TESTS = [{
        'url': 'https://www.mxplayer.in/show/watch-chakravartin-ashoka-samrat-series-online-a8f44e3cc0814b5601d17772cedf5417',
        'playlist_mincount': 440,
        'info_dict': {
            'id': 'a8f44e3cc0814b5601d17772cedf5417',
            'title': 'Watch Chakravartin Ashoka Samrat Series Online',
        }
    }]

    _API_SHOW_URL = "https://api.mxplay.com/v1/web/detail/tab/tvshowseasons?type=tv_show&id={}&device-density=2&platform=com.mxplay.desktop&content-languages=hi,en"
    _API_EPISODES_URL = "https://api.mxplay.com/v1/web/detail/tab/tvshowepisodes?type=season&id={}&device-density=1&platform=com.mxplay.desktop&content-languages=hi,en&{}"

    def _entries(self, show_id):
        show_json = self._download_json(
            self._API_SHOW_URL.format(show_id),
            video_id=show_id, headers={'Referer': 'https://mxplayer.in'})
        page_num = 0
        for season in show_json.get('items') or []:
            season_id = try_get(season, lambda x: x['id'], compat_str)
            next_url = ''
            while next_url is not None:
                page_num += 1
                season_json = self._download_json(
                    self._API_EPISODES_URL.format(season_id, next_url),
                    video_id=season_id,
                    headers={'Referer': 'https://mxplayer.in'},
                    note='Downloading JSON metadata page %d' % page_num)
                for episode in season_json.get('items') or []:
                    video_url = episode['webUrl']
                    yield self.url_result(
                        'https://mxplayer.in%s' % video_url,
                        ie=MxplayerIE.ie_key(), video_id=video_url.split('-')[-1])
                next_url = season_json.get('next')

    def _real_extract(self, url):
        display_id, show_id = self._match_valid_url(url).groups()
        return self.playlist_result(
            self._entries(show_id), playlist_id=show_id,
            playlist_title=display_id.replace('-', ' ').title())


code:
from .common import InfoExtractor

class MyChannelsIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mychannels\.com/.*(?P<id_type>video|production)_id=(?P<id>[0-9]+)'
    _TEST = {
        'url': 'https://mychannels.com/missholland/miss-holland?production_id=3416',
        'md5': 'b8993daad4262dd68d89d651c0c52c45',
        'info_dict': {
            'id': 'wUUDZZep6vQD',
            'ext': 'mp4',
            'title': 'Miss Holland joins VOTE LEAVE',
            'description': 'Miss Holland | #13 Not a potato',
            'uploader': 'Miss Holland',
        }
    }

    def _real_extract(self, url):
        id_type, url_id = self._match_valid_url(url).groups()
        webpage = self._download_webpage(url, url_id)
        video_data = self._html_search_regex(r'<div([^>]+data-%s-id="%s"[^>]+)>' % (id_type, url_id), webpage, 'video data')

        def extract_data_val(attr, fatal=False):
            return self._html_search_regex(r'data-%s\s*=\s*"([^"]+)"' % attr, video_data, attr, fatal=fatal)
        minoto_id = extract_data_val('minoto-id') or self._search_regex(r'/id/([a-zA-Z0-9]+)', extract_data_val('video-src', True), 'minoto id')

        return {
            '_type': 'url_transparent',
            'url': 'minoto:%s' % minoto_id,
            'id': url_id,
            'title': extract_data_val('title', True),
            'description': extract_data_val('description'),
            'thumbnail': extract_data_val('image'),
            'uploader': extract_data_val('channel'),
        }


code:
import re

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

class MySpaceIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        myspace\.com/[^/]+/
                        (?P<mediatype>
                            video/[^/]+/(?P<video_id>\d+)|
                            music/song/[^/?#&]+-(?P<song_id>\d+)-\d+(?:[/?#&]|$)
                        )
                    '''

    _TESTS = [{
        'url': 'https://myspace.com/fiveminutestothestage/video/little-big-town/109594919',
        'md5': '9c1483c106f4a695c47d2911feed50a7',
        'info_dict': {
            'id': '109594919',
            'ext': 'mp4',
            'title': 'Little Big Town',
            'description': 'This country quartet was all smiles while playing a sold out show at the Pacific Amphitheatre in Orange County, California.',
            'uploader': 'Five Minutes to the Stage',
            'uploader_id': 'fiveminutestothestage',
            'timestamp': 1414108751,
            'upload_date': '20141023',
        },
    }, {
        # songs
        'url': 'https://myspace.com/killsorrow/music/song/of-weakened-soul...-93388656-103880681',
        'md5': '1d7ee4604a3da226dd69a123f748b262',
        'info_dict': {
            'id': '93388656',
            'ext': 'm4a',
            'title': 'Of weakened soul...',
            'uploader': 'Killsorrow',
            'uploader_id': 'killsorrow',
        },
    }, {
        'url': 'https://myspace.com/starset2/music/song/first-light-95799905-106964426',
        'only_matching': True,
    }, {
        'url': 'https://myspace.com/thelargemouthbassband/music/song/02-pure-eyes.mp3-94422330-105113388',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        video_id = mobj.group('video_id') or mobj.group('song_id')
        is_song = mobj.group('mediatype').startswith('music/song')
        webpage = self._download_webpage(url, video_id)
        player_url = self._search_regex(
            r'videoSwf":"([^"?]*)', webpage, 'player URL', fatal=False)

        def formats_from_stream_urls(stream_url, hls_stream_url, http_stream_url, width=None, height=None):
            formats = []
            vcodec = 'none' if is_song else None
            if hls_stream_url:
                formats.append({
                    'format_id': 'hls',
                    'url': hls_stream_url,
                    'protocol': 'm3u8_native',
                    'ext': 'm4a' if is_song else 'mp4',
                    'vcodec': vcodec,
                })
            if stream_url and player_url:
                rtmp_url, play_path = stream_url.split(';', 1)
                formats.append({
                    'format_id': 'rtmp',
                    'url': rtmp_url,
                    'play_path': play_path,
                    'player_url': player_url,
                    'protocol': 'rtmp',
                    'ext': 'flv',
                    'width': width,
                    'height': height,
                    'vcodec': vcodec,
                })
            if http_stream_url:
                formats.append({
                    'format_id': 'http',
                    'url': http_stream_url,
                    'width': width,
                    'height': height,
                    'vcodec': vcodec,
                })
            return formats

        if is_song:
            # songs don't store any useful info in the 'context' variable
            song_data = self._search_regex(
                r'''<button.*data-song-id=(["\'])%s\1.*''' % video_id,
                webpage, 'song_data', default=None, group=0)
            if song_data is None:
                # some songs in an album are not playable
                self.report_warning(
                    '%s: No downloadable song on this page' % video_id)
                return

            def search_data(name):
                return self._search_regex(
                    r'''data-%s=([\'"])(?P<data>.*?)\1''' % name,
                    song_data, name, default='', group='data')
            formats = formats_from_stream_urls(
                search_data('stream-url'), search_data('hls-stream-url'),
                search_data('http-stream-url'))
            if not formats:
                vevo_id = search_data('vevo-id')
                youtube_id = search_data('youtube-id')
                if vevo_id:
                    self.to_screen('Vevo video detected: %s' % vevo_id)
                    return self.url_result('vevo:%s' % vevo_id, ie='Vevo')
                elif youtube_id:
                    self.to_screen('Youtube video detected: %s' % youtube_id)
                    return self.url_result(youtube_id, ie='Youtube')
                else:
                    raise ExtractorError(
                        'Found song but don\'t know how to download it')
            self._sort_formats(formats)
            return {
                'id': video_id,
                'title': self._og_search_title(webpage),
                'uploader': search_data('artist-name'),
                'uploader_id': search_data('artist-username'),
                'thumbnail': self._og_search_thumbnail(webpage),
                'duration': int_or_none(search_data('duration')),
                'formats': formats,
            }
        else:
            video = self._parse_json(self._search_regex(
                r'context = ({.*?});', webpage, 'context'),
                video_id)['video']
            formats = formats_from_stream_urls(
                video.get('streamUrl'), video.get('hlsStreamUrl'),
                video.get('mp4StreamUrl'), int_or_none(video.get('width')),
                int_or_none(video.get('height')))
            self._sort_formats(formats)
            return {
                'id': video_id,
                'title': video['title'],
                'description': video.get('description'),
                'thumbnail': video.get('imageUrl'),
                'uploader': video.get('artistName'),
                'uploader_id': video.get('artistUsername'),
                'duration': int_or_none(video.get('duration')),
                'timestamp': parse_iso8601(video.get('dateAdded')),
                'formats': formats,
            }

class MySpaceAlbumIE(InfoExtractor):
    IE_NAME = 'MySpace:album'
    _VALID_URL = r'https?://myspace\.com/([^/]+)/music/album/(?P<title>.*-)(?P<id>\d+)'

    _TESTS = [{
        'url': 'https://myspace.com/starset2/music/album/transmissions-19455773',
        'info_dict': {
            'title': 'Transmissions',
            'id': '19455773',
        },
        'playlist_count': 14,
        'skip': 'this album is only available in some countries',
    }, {
        'url': 'https://myspace.com/killsorrow/music/album/the-demo-18596029',
        'info_dict': {
            'title': 'The Demo',
            'id': '18596029',
        },
        'playlist_count': 5,
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        playlist_id = mobj.group('id')
        display_id = mobj.group('title') + playlist_id
        webpage = self._download_webpage(url, display_id)
        tracks_paths = re.findall(r'"music:song" content="(.*?)"', webpage)
        if not tracks_paths:
            raise ExtractorError(
                '%s: No songs found, try using proxy' % display_id,
                expected=True)
        entries = [
            self.url_result(t_path, ie=MySpaceIE.ie_key())
            for t_path in tracks_paths]
        return {
            '_type': 'playlist',
            'id': playlist_id,
            'display_id': display_id,
            'title': self._og_search_title(webpage),
            'entries': entries,
        }


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    int_or_none,
    parse_duration,
    xpath_text,
)

class MySpassIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?myspass\.de/(?:[^/]+/)*(?P<id>\d+)/?[^/]*$'
    _TESTS = [{
        'url': 'http://www.myspass.de/myspass/shows/tvshows/absolute-mehrheit/Absolute-Mehrheit-vom-17022013-Die-Highlights-Teil-2--/11741/',
        'md5': '0b49f4844a068f8b33f4b7c88405862b',
        'info_dict': {
            'id': '11741',
            'ext': 'mp4',
            'description': 'md5:9f0db5044c8fe73f528a390498f7ce9b',
            'title': '17.02.2013 - Die Highlights, Teil 2',
            'thumbnail': r're:.*\.jpg',
            'duration': 323.0,
            'episode': '17.02.2013 - Die Highlights, Teil 2',
            'season_id': '544',
            'episode_number': 1,
            'series': 'Absolute Mehrheit',
            'season_number': 2,
            'season': 'Season 2',
        },
    },
        {
        'url': 'https://www.myspass.de/shows/tvshows/tv-total/Novak-Puffovic-bei-bester-Laune--/44996/',
        'md5': 'eb28b7c5e254192046e86ebaf7deac8f',
        'info_dict': {
            'id': '44996',
            'ext': 'mp4',
            'description': 'md5:74c7f886e00834417f1e427ab0da6121',
            'title': 'Novak Puffovic bei bester Laune',
            'thumbnail': r're:.*\.jpg',
            'episode_number': 8,
            'episode': 'Novak Puffovic bei bester Laune',
            'series': 'TV total',
            'season': 'Season 19',
            'season_id': '987',
            'duration': 2941.0,
            'season_number': 19,
        },
    },
        {
        'url': 'https://www.myspass.de/channels/tv-total-raabigramm/17033/20831/',
        'md5': '7b293a6b9f3a7acdd29304c8d0dbb7cc',
        'info_dict': {
            'id': '20831',
            'ext': 'mp4',
            'description': 'Gefühle pur: Schaut euch die ungeschnittene Version von Stefans Liebesbeweis an die Moderationsgrazie von Welt, Verona Feldbusch, an.',
            'title': 'Raabigramm Verona Feldbusch',
            'thumbnail': r're:.*\.jpg',
            'episode_number': 6,
            'episode': 'Raabigramm Verona Feldbusch',
            'series': 'TV total',
            'season': 'Season 1',
            'season_id': '34',
            'duration': 105.0,
            'season_number': 1,
        },
    }]

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

        metadata = self._download_xml('http://www.myspass.de/myspass/includes/apps/video/getvideometadataxml.php?id=' + video_id, video_id)

        title = xpath_text(metadata, 'title', fatal=True)
        video_url = xpath_text(metadata, 'url_flv', 'download url', True)
        video_id_int = int(video_id)
        for group in self._search_regex(r'/myspass2009/\d+/(\d+)/(\d+)/(\d+)/', video_url, 'myspass', group=(1, 2, 3), default=[]):
            group_int = int(group)
            if group_int > video_id_int:
                video_url = video_url.replace(group, compat_str(group_int // video_id_int))

        return {
            'id': video_id,
            'url': video_url,
            'title': title,
            'thumbnail': xpath_text(metadata, 'imagePreview'),
            'description': xpath_text(metadata, 'description'),
            'duration': parse_duration(xpath_text(metadata, 'duration')),
            'series': xpath_text(metadata, 'format'),
            'season_number': int_or_none(xpath_text(metadata, 'season')),
            'season_id': xpath_text(metadata, 'season_id'),
            'episode': title,
            'episode_number': int_or_none(xpath_text(metadata, 'episode')),
        }


code:
from .common import InfoExtractor
from .vimple import SprutoBaseIE

class MyviIE(SprutoBaseIE):
    _VALID_URL = r'''(?x)
                        (?:
                            https?://
                                (?:www\.)?
                                myvi\.
                                (?:
                                    (?:ru/player|tv)/
                                    (?:
                                        (?:
                                            embed/html|
                                            flash|
                                            api/Video/Get
                                        )/|
                                        content/preloader\.swf\?.*\bid=
                                    )|
                                    ru/watch/
                                )|
                            myvi:
                        )
                        (?P<id>[\da-zA-Z_-]+)
                    '''
    _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//myvi\.(?:ru/player|tv)/(?:embed/html|flash)/[^"]+)\1']
    _TESTS = [{
        'url': 'http://myvi.ru/player/embed/html/oOy4euHA6LVwNNAjhD9_Jq5Ha2Qf0rtVMVFMAZav8wObeRTZaCATzucDQIDph8hQU0',
        'md5': '571bbdfba9f9ed229dc6d34cc0f335bf',
        'info_dict': {
            'id': 'f16b2bbd-cde8-481c-a981-7cd48605df43',
            'ext': 'mp4',
            'title': 'хозяин жизни',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 25,
        },
    }, {
        'url': 'http://myvi.ru/player/content/preloader.swf?id=oOy4euHA6LVwNNAjhD9_Jq5Ha2Qf0rtVMVFMAZav8wOYf1WFpPfc_bWTKGVf_Zafr0',
        'only_matching': True,
    }, {
        'url': 'http://myvi.ru/player/api/Video/Get/oOy4euHA6LVwNNAjhD9_Jq5Ha2Qf0rtVMVFMAZav8wObeRTZaCATzucDQIDph8hQU0',
        'only_matching': True,
    }, {
        'url': 'http://myvi.tv/embed/html/oTGTNWdyz4Zwy_u1nraolwZ1odenTd9WkTnRfIL9y8VOgHYqOHApE575x4_xxS9Vn0?ap=0',
        'only_matching': True,
    }, {
        'url': 'http://myvi.ru/player/flash/ocp2qZrHI-eZnHKQBK4cZV60hslH8LALnk0uBfKsB-Q4WnY26SeGoYPi8HWHxu0O30',
        'only_matching': True,
    }, {
        'url': 'https://www.myvi.ru/watch/YwbqszQynUaHPn_s82sx0Q2',
        'only_matching': True,
    }, {
        'url': 'myvi:YwbqszQynUaHPn_s82sx0Q2',
        'only_matching': True,
    }]

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

        spruto = self._download_json(
            'http://myvi.ru/player/api/Video/Get/%s?sig' % video_id, video_id)['sprutoData']

        return self._extract_spruto(spruto, video_id)

class MyviEmbedIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?myvi\.tv/(?:[^?]+\?.*?\bv=|embed/)(?P<id>[\da-z]+)'
    _TESTS = [{
        'url': 'https://www.myvi.tv/embed/ccdqic3wgkqwpb36x9sxg43t4r',
        'info_dict': {
            'id': 'b3ea0663-3234-469d-873e-7fecf36b31d1',
            'ext': 'mp4',
            'title': 'Твоя (original song).mp4',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 277,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.myvi.tv/idmi6o?v=ccdqic3wgkqwpb36x9sxg43t4r#watch',
        'only_matching': True,
    }]

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

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

        webpage = self._download_webpage(
            'https://www.myvi.tv/embed/%s' % video_id, video_id)

        myvi_id = self._search_regex(
            r'CreatePlayer\s*\(\s*["\'].*?\bv=([\da-zA-Z_]+)',
            webpage, 'video id')

        return self.url_result('myvi:%s' % myvi_id, ie=MyviIE.ie_key())


code:
from .common import InfoExtractor
from ..utils import js_to_json

class MyVideoGeIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?myvideo\.ge/v/(?P<id>[0-9]+)'
    _TEST = {
        'url': 'https://www.myvideo.ge/v/3941048',
        'md5': '8c192a7d2b15454ba4f29dc9c9a52ea9',
        'info_dict': {
            'id': '3941048',
            'ext': 'mp4',
            'title': 'The best prikol',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'md5:d72addd357b0dd914e704781f7f777d8',
            'description': 'md5:5c0371f540f5888d603ebfedd46b6df3'
        }
    }

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

        title = self._html_search_regex(r'<h1[^>]*>([^<]+)</h1>', webpage, 'title')
        description = self._og_search_description(webpage)
        thumbnail = self._html_search_meta(['og:image'], webpage)
        uploader = self._search_regex(r'<a[^>]+class="mv_user_name"[^>]*>([^<]+)<', webpage, 'uploader', fatal=False)

        jwplayer_sources = self._parse_json(
            self._search_regex(
                r"(?s)jwplayer\(\"mvplayer\"\).setup\(.*?sources: (.*?])", webpage, 'jwplayer sources'),
            video_id, transform_source=js_to_json)

        def _formats_key(f):
            if f['label'] == 'SD':
                return -1
            elif f['label'] == 'HD':
                return 1
            else:
                return 0

        jwplayer_sources = sorted(jwplayer_sources, key=_formats_key)

        formats = self._parse_jwplayer_formats(jwplayer_sources, video_id)

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


code:
from .common import InfoExtractor

class MyVidsterIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?myvidster\.com/video/(?P<id>\d+)/'

    _TEST = {
        'url': 'http://www.myvidster.com/video/32059805/Hot_chemistry_with_raw_love_making',
        'md5': '95296d0231c1363222c3441af62dc4ca',
        'info_dict': {
            'id': '3685814',
            'title': 'md5:7d8427d6d02c4fbcef50fe269980c749',
            'upload_date': '20141027',
            'uploader': 'utkualp',
            'ext': 'mp4',
            'age_limit': 18,
        },
        'add_ie': ['XHamster'],
    }

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

        return self.url_result(self._html_search_regex(
            r'rel="videolink" href="(?P<real_url>.*)">',
            webpage, 'real video url'))


code:
import re

from .common import InfoExtractor
from ..utils import (
    unified_timestamp,
    extract_attributes,
)

class N1InfoAssetIE(InfoExtractor):
    _VALID_URL = r'https?://best-vod\.umn\.cdn\.united\.cloud/stream\?asset=(?P<id>[^&]+)'
    _TESTS = [{
        'url': 'https://best-vod.umn.cdn.united.cloud/stream?asset=ljsottomazilirija3060921-n1info-si-worldwide&stream=hp1400&t=0&player=m3u8v&sp=n1info&u=n1info&p=n1Sh4redSecre7iNf0',
        'md5': '28b08b32aeaff2b8562736ccd5a66fe7',
        'info_dict': {
            'id': 'ljsottomazilirija3060921-n1info-si-worldwide',
            'ext': 'mp4',
            'title': 'ljsottomazilirija3060921-n1info-si-worldwide',
        }
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        formats = self._extract_m3u8_formats(
            url, video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)

        self._sort_formats(formats)

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

class N1InfoIIE(InfoExtractor):
    IE_NAME = 'N1Info:article'
    _VALID_URL = r'https?://(?:(?:(?:ba|rs|hr)\.)?n1info\.(?:com|si)|nova\.rs)/(?:[^/]+/){1,2}(?P<id>[^/]+)'
    _TESTS = [{
        # Youtube embedded
        'url': 'https://rs.n1info.com/sport-klub/tenis/kako-je-djokovic-propustio-istorijsku-priliku-video/',
        'md5': '01ddb6646d0fd9c4c7d990aa77fe1c5a',
        'info_dict': {
            'id': 'L5Hd4hQVUpk',
            'ext': 'mp4',
            'upload_date': '20210913',
            'title': 'Ozmo i USO21, ep. 13: Novak Đoković – Danil Medvedev | Ključevi Poraza, Budućnost | SPORT KLUB TENIS',
            'description': 'md5:467f330af1effedd2e290f10dc31bb8e',
            'uploader': 'Sport Klub',
            'uploader_id': 'sportklub',
        }
    }, {
        'url': 'https://rs.n1info.com/vesti/djilas-los-plan-za-metro-nece-resiti-nijedan-saobracajni-problem/',
        'info_dict': {
            'id': 'bgmetrosot2409zta20210924174316682-n1info-rs-worldwide',
            'ext': 'mp4',
            'title': 'Đilas: Predlog izgradnje metroa besmislen; SNS odbacuje navode',
            'upload_date': '20210924',
            'timestamp': 1632481347,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://n1info.si/novice/slovenija/zadnji-dnevi-na-kopaliscu-ilirija-ilirija-ni-umrla-ubili-so-jo/',
        'info_dict': {
            'id': 'ljsottomazilirija3060921-n1info-si-worldwide',
            'ext': 'mp4',
            'title': 'Zadnji dnevi na kopališču Ilirija: “Ilirija ni umrla, ubili so jo”',
            'timestamp': 1632567630,
            'upload_date': '20210925',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # Reddit embedded
        'url': 'https://ba.n1info.com/lifestyle/vucic-bolji-od-tita-ako-izgubi-ja-cu-da-crknem-jugoslavija-je-gotova/',
        'info_dict': {
            'id': '2wmfee9eycp71',
            'ext': 'mp4',
            'title': '"Ako Vučić izgubi izbore, ja ću da crknem, Jugoslavija je gotova"',
            'upload_date': '20210924',
            'timestamp': 1632448649.0,
            'uploader': 'YouLotWhatDontStop',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://nova.rs/vesti/politika/zaklina-tatalovic-ani-brnabic-pricate-lazi-video/',
        'info_dict': {
            'id': 'tnjganabrnabicizaklinatatalovic100danavladegp-novas-worldwide',
            'ext': 'mp4',
            'title': 'Žaklina Tatalović Ani Brnabić: Pričate laži (VIDEO)',
            'upload_date': '20211102',
            'timestamp': 1635861677,
        },
    }, {
        'url': 'https://hr.n1info.com/vijesti/pravobraniteljica-o-ubojstvu-u-zagrebu-radi-se-o-doista-nezapamcenoj-situaciji/',
        'only_matching': True,
    }]

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

        title = self._html_search_regex(r'<h1[^>]+>(.+?)</h1>', webpage, 'title')
        timestamp = unified_timestamp(self._html_search_meta('article:published_time', webpage))

        videos = re.findall(r'(?m)(<video[^>]+>)', webpage)
        entries = []
        for video in videos:
            video_data = extract_attributes(video)
            entries.append({
                '_type': 'url_transparent',
                'url': video_data.get('data-url'),
                'id': video_data.get('id'),
                'title': title,
                'thumbnail': video_data.get('data-thumbnail'),
                'timestamp': timestamp,
                'ie_key': 'N1InfoAsset'})

        embedded_videos = re.findall(r'(<iframe[^>]+>)', webpage)
        for embedded_video in embedded_videos:
            video_data = extract_attributes(embedded_video)
            url = video_data.get('src') or ''
            if url.startswith('https://www.youtube.com'):
                entries.append(self.url_result(url, ie='Youtube'))
            elif url.startswith('https://www.redditmedia.com'):
                entries.append(self.url_result(url, ie='RedditR'))

        return {
            '_type': 'playlist',
            'id': video_id,
            'title': title,
            'timestamp': timestamp,
            'entries': entries,
        }


code:
import itertools

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    str_or_none,
    traverse_obj,
    unified_strdate,
)

class NateIE(InfoExtractor):
    _VALID_URL = r'https?://tv\.nate\.com/clip/(?P<id>[0-9]+)'

    _TESTS = [{
        'url': 'https://tv.nate.com/clip/1848976',
        'info_dict': {
            'id': '1848976',
            'ext': 'mp4',
            'title': '[결승 오프닝 타이틀] 2018 LCK 서머 스플릿 결승전 kt Rolster VS Griffin',
            'description': 'md5:e1b79a7dcf0d8d586443f11366f50e6f',
            'thumbnail': r're:^https?://.*\.jpg',
            'upload_date': '20180908',
            'age_limit': 15,
            'duration': 73,
            'uploader': '2018 LCK 서머 스플릿(롤챔스)',
            'channel': '2018 LCK 서머 스플릿(롤챔스)',
            'channel_id': '3606',
            'uploader_id': '3606',
            'tags': 'count:59',
        },
        'params': {'skip_download': True}
    }, {
        'url': 'https://tv.nate.com/clip/4300566',
        'info_dict': {
            'id': '4300566',
            'ext': 'mp4',
            'title': '[심쿵엔딩] 이준호x이세영, 서로를 기억하며 끌어안는 두 사람!💕, MBC 211204 방송',
            'description': 'md5:be1653502d9c13ce344ddf7828e089fa',
            'thumbnail': r're:^https?://.*\.jpg',
            'upload_date': '20211204',
            'age_limit': 15,
            'duration': 201,
            'uploader': '옷소매 붉은 끝동',
            'channel': '옷소매 붉은 끝동',
            'channel_id': '27987',
            'uploader_id': '27987',
            'tags': 'count:20',
        },
        'params': {'skip_download': True}
    }]

    _QUALITY = {
        '36': 2160,
        '35': 1080,
        '34': 720,
        '33': 480,
        '32': 360,
        '31': 270,
    }

    def _real_extract(self, url):
        id = self._match_id(url)
        video_data = self._download_json(f'https://tv.nate.com/api/v1/clip/{id}', id)
        formats = [{
            'format_id': f_url[-2:],
            'url': f_url,
            'height': self._QUALITY.get(f_url[-2:]),
            'quality': int_or_none(f_url[-2:]),
        } for f_url in video_data.get('smcUriList') or []]
        self._sort_formats(formats)
        return {
            'id': id,
            'title': video_data.get('clipTitle'),
            'description': video_data.get('synopsis'),
            'thumbnail': video_data.get('contentImg'),
            'upload_date': unified_strdate(traverse_obj(video_data, 'broadDate', 'regDate')),
            'age_limit': video_data.get('targetAge'),
            'duration': video_data.get('playTime'),
            'formats': formats,
            'uploader': video_data.get('programTitle'),
            'channel': video_data.get('programTitle'),
            'channel_id': str_or_none(video_data.get('programSeq')),
            'uploader_id': str_or_none(video_data.get('programSeq')),
            'tags': video_data['hashTag'].split(',') if video_data.get('hashTag') else None,
        }

class NateProgramIE(InfoExtractor):
    _VALID_URL = r'https?://tv\.nate\.com/program/clips/(?P<id>[0-9]+)'

    _TESTS = [{
        'url': 'https://tv.nate.com/program/clips/27987',
        'playlist_mincount': 191,
        'info_dict': {
            'id': '27987',
        },
    }, {
        'url': 'https://tv.nate.com/program/clips/3606',
        'playlist_mincount': 15,
        'info_dict': {
            'id': '3606',
        },
    }]

    def _entries(self, id):
        for page_num in itertools.count(1):
            program_data = self._download_json(f'https://tv.nate.com/api/v1/program/{id}/clip/ranking?size=20&page={page_num}',
                                               id, note=f'Downloading page {page_num}')
            for clip in program_data.get('content') or []:
                clip_id = clip.get('clipSeq')
                if clip_id:
                    yield self.url_result(
                        'https://tv.nate.com/clip/%s' % clip_id,
                        ie=NateIE.ie_key(), video_id=clip_id)
            if program_data.get('last'):
                break

    def _real_extract(self, url):
        id = self._match_id(url)
        return self.playlist_result(self._entries(id), playlist_id=id)


code:
from .common import InfoExtractor
from .fox import FOXIE
from ..utils import (
    smuggle_url,
    url_basename,
)

class NationalGeographicVideoIE(InfoExtractor):
    IE_NAME = 'natgeo:video'
    _VALID_URL = r'https?://video\.nationalgeographic\.com/.*?'

    _TESTS = [
        {
            'url': 'http://video.nationalgeographic.com/video/news/150210-news-crab-mating-vin?source=featuredvideo',
            'md5': '730855d559abbad6b42c2be1fa584917',
            'info_dict': {
                'id': '0000014b-70a1-dd8c-af7f-f7b559330001',
                'ext': 'mp4',
                'title': 'Mating Crabs Busted by Sharks',
                'description': 'md5:16f25aeffdeba55aaa8ec37e093ad8b3',
                'timestamp': 1423523799,
                'upload_date': '20150209',
                'uploader': 'NAGS',
            },
            'add_ie': ['ThePlatform'],
        },
        {
            'url': 'http://video.nationalgeographic.com/wild/when-sharks-attack/the-real-jaws',
            'md5': '6a3105eb448c070503b3105fb9b320b5',
            'info_dict': {
                'id': 'ngc-I0IauNSWznb_UV008GxSbwY35BZvgi2e',
                'ext': 'mp4',
                'title': 'The Real Jaws',
                'description': 'md5:8d3e09d9d53a85cd397b4b21b2c77be6',
                'timestamp': 1433772632,
                'upload_date': '20150608',
                'uploader': 'NAGS',
            },
            'add_ie': ['ThePlatform'],
        },
    ]

    def _real_extract(self, url):
        name = url_basename(url)

        webpage = self._download_webpage(url, name)
        guid = self._search_regex(
            r'id="(?:videoPlayer|player-container)"[^>]+data-guid="([^"]+)"',
            webpage, 'guid')

        return {
            '_type': 'url_transparent',
            'ie_key': 'ThePlatform',
            'url': smuggle_url(
                'http://link.theplatform.com/s/ngs/media/guid/2423130747/%s?mbr=true' % guid,
                {'force_smil_url': True}),
            'id': guid,
        }

class NationalGeographicTVIE(FOXIE):
    _VALID_URL = r'https?://(?:www\.)?nationalgeographic\.com/tv/watch/(?P<id>[\da-fA-F]+)'
    _TESTS = [{
        'url': 'https://www.nationalgeographic.com/tv/watch/6a875e6e734b479beda26438c9f21138/',
        'info_dict': {
            'id': '6a875e6e734b479beda26438c9f21138',
            'ext': 'mp4',
            'title': 'Why Nat Geo? Valley of the Boom',
            'description': 'The lives of prominent figures in the tech world, including their friendships, rivalries, victories and failures.',
            'timestamp': 1542662458,
            'upload_date': '20181119',
            'age_limit': 14,
        },
        'params': {
            'skip_download': True,
        },
    }]
    _HOME_PAGE_URL = 'https://www.nationalgeographic.com/tv/'
    _API_KEY = '238bb0a0c2aba67922c48709ce0c06fd'