code:
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 = self._match_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:
import base64
import time
import uuid

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

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

    _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': 'https://w.mgtv.com/b/427837/15588271.html',
        'info_dict': {
            'id': '15588271',
            'ext': 'mp4',
            'title': '春日迟迟再出发 沉浸版',
            'description': 'md5:a7a05a05b1aa87bd50cae619b19bbca6',
            'thumbnail': r're:^https?://.+\.jpg',
            'duration': 4026,
        },
    }, {
        'url': 'https://w.mgtv.com/b/333652/7329822.html',
        'info_dict': {
            'id': '7329822',
            'ext': 'mp4',
            'title': '拜托，请你爱我',
            'description': 'md5:cd81be6499bafe32e4d143abd822bf9c',
            'thumbnail': r're:^https?://.+\.jpg',
            'duration': 2656,
        },
    }, {
        'url': 'https://w.mgtv.com/b/427837/15591647.html',
        'only_matching': True,
    }, {
        'url': 'https://w.mgtv.com/b/388252/15634192.html?fpa=33318&fpos=4&lastp=ch_home',
        'only_matching': True,
    }, {
        'url': 'http://www.mgtv.com/b/301817/3826653.html',
        'only_matching': True,
    }, {
        'url': 'https://w.mgtv.com/b/301817/3826653.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        tk2 = base64.urlsafe_b64encode(
            f'did={str(uuid.uuid4())}|pno=1030|ver=0.3.0301|clit={int(time.time())}'.encode())[::-1]
        try:
            api_data = self._download_json(
                'https://pcweb.api.mgtv.com/player/video', video_id, query={
                    'tk2': tk2,
                    'video_id': video_id,
                    'type': 'pch5'
                }, 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'],
                'tk2': tk2,
                'video_id': video_id,
                'src': 'intelmgtv',
            }, 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=f'Download video info for format #{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': url_or_none(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'),
            'subtitles': self.extract_subtitles(video_id, stream_domain),
        }

    def _get_subtitles(self, video_id, domain):
        info = self._download_json(f'https://pcweb.api.mgtv.com/video/title?videoId={video_id}',
                                   video_id, fatal=False) or {}
        subtitles = {}
        for sub in try_get(info, lambda x: x['data']['title']) or []:
            url_sub = sub.get('url')
            if not url_sub:
                continue
            locale = sub.get('captionSimpleName') or 'en'
            sub = self._download_json(f'{domain}{url_sub}', video_id, fatal=False,
                                      note=f'Download subtitle for locale {sub.get("name")} ({locale})') or {}
            sub_url = url_or_none(sub.get('info'))
            if not sub_url:
                continue
            subtitles.setdefault(locale.lower(), []).append({
                'url': sub_url,
                'name': sub.get('name'),
                'ext': 'srt'
            })
        return subtitles

repo_name: Lesmiscore/ytdl


code:
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_extract_title(webpage)
        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 base64 import b64decode

from .common import InfoExtractor
from ..utils import (
    merge_dicts,
    parse_iso8601,
    parse_duration,
    parse_resolution,
    try_get,
    url_basename,
)

class MicrosoftStreamIE(InfoExtractor):
    IE_NAME = 'microsoftstream'
    IE_DESC = 'Microsoft Stream'
    _VALID_URL = r'https?://(?:web|www|msit)\.microsoftstream\.com/video/(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'

    _TESTS = [{
        'url': 'https://web.microsoftstream.com/video/6e51d928-4f46-4f1c-b141-369925e37b62?list=user&userId=f5491e02-e8fe-4e34-b67c-ec2e79a6ecc0',
        'only_matching': True,
    }, {
        'url': 'https://msit.microsoftstream.com/video/b60f5987-aabd-4e1c-a42f-c559d138f2ca',
        'only_matching': True,
    }]

    def _get_all_subtitles(self, api_url, video_id, headers):
        subtitles = {}
        automatic_captions = {}
        text_tracks = self._download_json(
            f'{api_url}/videos/{video_id}/texttracks', video_id,
            note='Downloading subtitles JSON', fatal=False, headers=headers,
            query={'api-version': '1.4-private'}).get('value') or []
        for track in text_tracks:
            if not track.get('language') or not track.get('url'):
                continue
            sub_dict = automatic_captions if track.get('autoGenerated') else subtitles
            sub_dict.setdefault(track['language'], []).append({
                'ext': 'vtt',
                'url': track.get('url')
            })
        return {
            'subtitles': subtitles,
            'automatic_captions': automatic_captions
        }

    def extract_all_subtitles(self, *args, **kwargs):
        if (self.get_param('writesubtitles', False)
                or self.get_param('writeautomaticsub', False)
                or self.get_param('listsubtitles')):
            return self._get_all_subtitles(*args, **kwargs)
        return {}

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        if '<title>Microsoft Stream</title>' not in webpage:
            self.raise_login_required(method='cookies')

        access_token = self._html_search_regex(r'"AccessToken":"(.+?)"', webpage, 'access token')
        api_url = self._html_search_regex(r'"ApiGatewayUri":"(.+?)"', webpage, 'api url')

        headers = {'Authorization': f'Bearer {access_token}'}

        video_data = self._download_json(
            f'{api_url}/videos/{video_id}', video_id,
            headers=headers, query={
                '$expand': 'creator,tokens,status,liveEvent,extensions',
                'api-version': '1.4-private'
            })
        video_id = video_data.get('id') or video_id
        language = video_data.get('language')

        thumbnails = []
        for thumbnail_id in ('extraSmall', 'small', 'medium', 'large'):
            thumbnail_url = try_get(video_data, lambda x: x['posterImage'][thumbnail_id]['url'], str)
            if not thumbnail_url:
                continue
            thumb = {
                'id': thumbnail_id,
                'url': thumbnail_url,
            }
            thumb_name = url_basename(thumbnail_url)
            thumb_name = str(b64decode(thumb_name + '=' * (-len(thumb_name) % 4)))
            thumb.update(parse_resolution(thumb_name))
            thumbnails.append(thumb)

        formats = []
        for playlist in video_data['playbackUrls']:
            if playlist['mimeType'] == 'application/vnd.apple.mpegurl':
                formats.extend(self._extract_m3u8_formats(
                    playlist['playbackUrl'], video_id,
                    ext='mp4', entry_protocol='m3u8_native', m3u8_id='hls',
                    fatal=False, headers=headers))
            elif playlist['mimeType'] == 'application/dash+xml':
                formats.extend(self._extract_mpd_formats(
                    playlist['playbackUrl'], video_id, mpd_id='dash',
                    fatal=False, headers=headers))
            elif playlist['mimeType'] == 'application/vnd.ms-sstr+xml':
                formats.extend(self._extract_ism_formats(
                    playlist['playbackUrl'], video_id, ism_id='mss',
                    fatal=False, headers=headers))
        formats = [merge_dicts(f, {'language': language}) for f in formats]
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video_data['name'],
            'description': video_data.get('description'),
            'uploader': try_get(video_data, lambda x: x['creator']['name'], str),
            'uploader_id': try_get(video_data, (lambda x: x['creator']['mail'],
                                                lambda x: x['creator']['id']), str),
            'thumbnails': thumbnails,
            **self.extract_all_subtitles(api_url, video_id, headers),
            'timestamp': parse_iso8601(video_data.get('created')),
            'duration': parse_duration(try_get(video_data, lambda x: x['media']['duration'])),
            'webpage_url': f'https://web.microsoftstream.com/video/{video_id}',
            'view_count': try_get(video_data, lambda x: x['metrics']['views'], int),
            'like_count': try_get(video_data, lambda x: x['metrics']['likes'], int),
            'comment_count': try_get(video_data, lambda x: x['metrics']['comments'], int),
            'formats': formats,
        }


code:
import re

from .common import InfoExtractor
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 = self._match_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('.//MediaSources'):
            sources_type = sources.get('videoType')
            for source in sources.findall('./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('.//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 = self._match_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:
import functools
import json
import uuid

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    dict_get,
    ExtractorError,
    float_or_none,
    OnDemandPagedList,
    traverse_obj,
)

class MildomBaseIE(InfoExtractor):
    _GUEST_ID = None

    def _call_api(self, url, video_id, query=None, note='Downloading JSON metadata', body=None):
        if not self._GUEST_ID:
            self._GUEST_ID = f'pc-gp-{str(uuid.uuid4())}'

        content = self._download_json(
            url, video_id, note=note, data=json.dumps(body).encode() if body else None,
            headers={'Content-Type': 'application/json'} if body else {},
            query={
                '__guest_id': self._GUEST_ID,
                '__platform': 'web',
                **(query or {}),
            })

        if content['code'] != 0:
            raise ExtractorError(
                f'Mildom says: {content["message"]} (code {content["code"]})',
                expected=True)
        return content['body']

class MildomIE(MildomBaseIE):
    IE_NAME = 'mildom'
    IE_DESC = 'Record ongoing live by specific user in Mildom'
    _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/(?P<id>\d+)'

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(f'https://www.mildom.com/{video_id}', video_id)

        enterstudio = self._call_api(
            'https://cloudac.mildom.com/nonolive/gappserv/live/enterstudio', video_id,
            note='Downloading live metadata', query={'user_id': video_id})
        result_video_id = enterstudio.get('log_id', video_id)

        servers = self._call_api(
            'https://cloudac.mildom.com/nonolive/gappserv/live/liveserver', result_video_id,
            note='Downloading live server list', query={
                'user_id': video_id,
                'live_server_type': 'hls',
            })

        playback_token = self._call_api(
            'https://cloudac.mildom.com/nonolive/gappserv/live/token', result_video_id,
            note='Obtaining live playback token', body={'host_id': video_id, 'type': 'hls'})
        playback_token = traverse_obj(playback_token, ('data', ..., 'token'), get_all=False)
        if not playback_token:
            raise ExtractorError('Failed to obtain live playback token')

        formats = self._extract_m3u8_formats(
            f'{servers["stream_server"]}/{video_id}_master.m3u8?{playback_token}',
            result_video_id, 'mp4', headers={
                'Referer': 'https://www.mildom.com/',
                'Origin': 'https://www.mildom.com',
            })

        for fmt in formats:
            fmt.setdefault('http_headers', {})['Referer'] = 'https://www.mildom.com/'

        self._sort_formats(formats)

        return {
            'id': result_video_id,
            'title': self._html_search_meta('twitter:description', webpage, default=None) or traverse_obj(enterstudio, 'anchor_intro'),
            'description': traverse_obj(enterstudio, 'intro', 'live_intro', expected_type=str),
            'timestamp': float_or_none(enterstudio.get('live_start_ms'), scale=1000),
            'uploader': self._html_search_meta('twitter:title', webpage, default=None) or traverse_obj(enterstudio, 'loginname'),
            'uploader_id': video_id,
            'formats': formats,
            'is_live': True,
        }

class MildomVodIE(MildomBaseIE):
    IE_NAME = 'mildom:vod'
    IE_DESC = 'VOD in Mildom'
    _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/playback/(?P<user_id>\d+)/(?P<id>(?P=user_id)-[a-zA-Z0-9]+-?[0-9]*)'
    _TESTS = [{
        'url': 'https://www.mildom.com/playback/10882672/10882672-1597662269',
        'info_dict': {
            'id': '10882672-1597662269',
            'ext': 'mp4',
            'title': '始めてのミルダム配信じゃぃ！',
            'thumbnail': r're:^https?://.*\.(png|jpg)$',
            'upload_date': '20200817',
            'duration': 4138.37,
            'description': 'ゲームをしたくて！',
            'timestamp': 1597662269.0,
            'uploader_id': '10882672',
            'uploader': 'kson組長(けいそん)',
        },
    }, {
        'url': 'https://www.mildom.com/playback/10882672/10882672-1597758589870-477',
        'info_dict': {
            'id': '10882672-1597758589870-477',
            'ext': 'mp4',
            'title': '【kson】感染メイズ！麻酔銃で無双する',
            'thumbnail': r're:^https?://.*\.(png|jpg)$',
            'timestamp': 1597759093.0,
            'uploader': 'kson組長(けいそん)',
            'duration': 4302.58,
            'uploader_id': '10882672',
            'description': 'このステージ絶対乗り越えたい',
            'upload_date': '20200818',
        },
    }, {
        'url': 'https://www.mildom.com/playback/10882672/10882672-buha9td2lrn97fk2jme0',
        'info_dict': {
            'id': '10882672-buha9td2lrn97fk2jme0',
            'ext': 'mp4',
            'title': '【kson組長】CART RACER!!!',
            'thumbnail': r're:^https?://.*\.(png|jpg)$',
            'uploader_id': '10882672',
            'uploader': 'kson組長(けいそん)',
            'upload_date': '20201104',
            'timestamp': 1604494797.0,
            'duration': 4657.25,
            'description': 'WTF',
        },
    }]

    def _real_extract(self, url):
        user_id, video_id = self._match_valid_url(url).group('user_id', 'id')
        webpage = self._download_webpage(f'https://www.mildom.com/playback/{user_id}/{video_id}', video_id)

        autoplay = self._call_api(
            'https://cloudac.mildom.com/nonolive/videocontent/playback/getPlaybackDetail', video_id,
            note='Downloading playback metadata', query={
                'v_id': video_id,
            })['playback']

        formats = [{
            'url': autoplay['audio_url'],
            'format_id': 'audio',
            'protocol': 'm3u8_native',
            'vcodec': 'none',
            'acodec': 'aac',
            'ext': 'm4a'
        }]
        for fmt in autoplay['video_link']:
            formats.append({
                'format_id': 'video-%s' % fmt['name'],
                'url': fmt['url'],
                'protocol': 'm3u8_native',
                'width': fmt['level'] * autoplay['video_width'] // autoplay['video_height'],
                'height': fmt['level'],
                'vcodec': 'h264',
                'acodec': 'aac',
                'ext': 'mp4'
            })

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': self._html_search_meta(('og:description', 'description'), webpage, default=None) or autoplay.get('title'),
            'description': traverse_obj(autoplay, 'video_intro'),
            'timestamp': float_or_none(autoplay.get('publish_time'), scale=1000),
            'duration': float_or_none(autoplay.get('video_length'), scale=1000),
            'thumbnail': dict_get(autoplay, ('upload_pic', 'video_pic')),
            'uploader': traverse_obj(autoplay, ('author_info', 'login_name')),
            'uploader_id': user_id,
            'formats': formats,
        }

class MildomClipIE(MildomBaseIE):
    IE_NAME = 'mildom:clip'
    IE_DESC = 'Clip in Mildom'
    _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/clip/(?P<id>(?P<user_id>\d+)-[a-zA-Z0-9]+)'
    _TESTS = [{
        'url': 'https://www.mildom.com/clip/10042245-63921673e7b147ebb0806d42b5ba5ce9',
        'info_dict': {
            'id': '10042245-63921673e7b147ebb0806d42b5ba5ce9',
            'title': '全然違ったよ',
            'timestamp': 1619181890,
            'duration': 59,
            'thumbnail': r're:https?://.+',
            'uploader': 'ざきんぽ',
            'uploader_id': '10042245',
        },
    }, {
        'url': 'https://www.mildom.com/clip/10111524-ebf4036e5aa8411c99fb3a1ae0902864',
        'info_dict': {
            'id': '10111524-ebf4036e5aa8411c99fb3a1ae0902864',
            'title': 'かっこいい',
            'timestamp': 1621094003,
            'duration': 59,
            'thumbnail': r're:https?://.+',
            'uploader': '(ルーキー',
            'uploader_id': '10111524',
        },
    }, {
        'url': 'https://www.mildom.com/clip/10660174-2c539e6e277c4aaeb4b1fbe8d22cb902',
        'info_dict': {
            'id': '10660174-2c539e6e277c4aaeb4b1fbe8d22cb902',
            'title': 'あ',
            'timestamp': 1614769431,
            'duration': 31,
            'thumbnail': r're:https?://.+',
            'uploader': 'ドルゴルスレンギーン＝ダグワドルジ',
            'uploader_id': '10660174',
        },
    }]

    def _real_extract(self, url):
        user_id, video_id = self._match_valid_url(url).group('user_id', 'id')
        webpage = self._download_webpage(f'https://www.mildom.com/clip/{video_id}', video_id)

        clip_detail = self._call_api(
            'https://cloudac-cf-jp.mildom.com/nonolive/videocontent/clip/detail', video_id,
            note='Downloading playback metadata', query={
                'clip_id': video_id,
            })

        return {
            'id': video_id,
            'title': self._html_search_meta(
                ('og:description', 'description'), webpage, default=None) or clip_detail.get('title'),
            'timestamp': float_or_none(clip_detail.get('create_time')),
            'duration': float_or_none(clip_detail.get('length')),
            'thumbnail': clip_detail.get('cover'),
            'uploader': traverse_obj(clip_detail, ('user_info', 'loginname')),
            'uploader_id': user_id,

            'url': clip_detail['url'],
            'ext': determine_ext(clip_detail.get('url'), 'mp4'),
        }

class MildomUserVodIE(MildomBaseIE):
    IE_NAME = 'mildom:user:vod'
    IE_DESC = 'Download all VODs from specific user in Mildom'
    _VALID_URL = r'https?://(?:(?:www|m)\.)mildom\.com/profile/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.mildom.com/profile/10093333',
        'info_dict': {
            'id': '10093333',
            'title': 'Uploads from ねこばたけ',
        },
        'playlist_mincount': 732,
    }, {
        'url': 'https://www.mildom.com/profile/10882672',
        'info_dict': {
            'id': '10882672',
            'title': 'Uploads from kson組長(けいそん)',
        },
        'playlist_mincount': 201,
    }]

    def _fetch_page(self, user_id, page):
        page += 1
        reply = self._call_api(
            'https://cloudac.mildom.com/nonolive/videocontent/profile/playbackList',
            user_id, note=f'Downloading page {page}', query={
                'user_id': user_id,
                'page': page,
                'limit': '30',
            })
        if not reply:
            return
        for x in reply:
            v_id = x.get('v_id')
            if not v_id:
                continue
            yield self.url_result(f'https://www.mildom.com/playback/{user_id}/{v_id}')

    def _real_extract(self, url):
        user_id = self._match_id(url)
        self.to_screen('This will download all VODs belonging to user. To download ongoing live video, use "https://www.mildom.com/%s" instead' % user_id)

        profile = self._call_api(
            'https://cloudac.mildom.com/nonolive/gappserv/user/profileV2', user_id,
            query={'user_id': user_id}, note='Downloading user profile')['user_info']

        return self.playlist_result(
            OnDemandPagedList(functools.partial(self._fetch_page, user_id), 30),
            user_id, f'Uploads from {profile["loginname"]}')


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    clean_html,
    format_field,
    int_or_none,
    str_or_none,
    strip_or_none,
)

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

    def _call_api(self, path, video_id, resource, query=None):
        api_url = 'https://www.minds.com/api/' + path
        token = self._get_cookies(api_url).get('XSRF-TOKEN')
        return self._download_json(
            api_url, video_id, 'Downloading %s JSON metadata' % resource, headers={
                'Referer': 'https://www.minds.com/',
                'X-XSRF-TOKEN': token.value if token else '',
            }, query=query)

class MindsIE(MindsBaseIE):
    IE_NAME = 'minds'
    _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'(?:media|newsfeed|archive/view)/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.minds.com/media/100000000000086822',
        'md5': '215a658184a419764852239d4970b045',
        'info_dict': {
            'id': '100000000000086822',
            'ext': 'mp4',
            'title': 'Minds intro sequence',
            'thumbnail': r're:https?://.+\.png',
            'uploader_id': 'ottman',
            'upload_date': '20130524',
            'timestamp': 1369404826,
            'uploader': 'Bill Ottman',
            'view_count': int,
            'like_count': int,
            'dislike_count': int,
            'tags': ['animation'],
            'comment_count': int,
            'license': 'attribution-cc',
        },
    }, {
        # entity.type == 'activity' and empty title
        'url': 'https://www.minds.com/newsfeed/798025111988506624',
        'md5': 'b2733a74af78d7fd3f541c4cbbaa5950',
        'info_dict': {
            'id': '798022190320226304',
            'ext': 'mp4',
            'title': '798022190320226304',
            'uploader': 'ColinFlaherty',
            'upload_date': '20180111',
            'timestamp': 1515639316,
            'uploader_id': 'ColinFlaherty',
        },
    }, {
        'url': 'https://www.minds.com/archive/view/715172106794442752',
        'only_matching': True,
    }, {
        # youtube perma_url
        'url': 'https://www.minds.com/newsfeed/1197131838022602752',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        entity_id = self._match_id(url)
        entity = self._call_api(
            'v1/entities/entity/' + entity_id, entity_id, 'entity')['entity']
        if entity.get('type') == 'activity':
            if entity.get('custom_type') == 'video':
                video_id = entity['entity_guid']
            else:
                return self.url_result(entity['perma_url'])
        else:
            assert entity['subtype'] == 'video'
            video_id = entity_id
        # 1080p and webm formats available only on the sources array
        video = self._call_api(
            'v2/media/video/' + video_id, video_id, 'video')

        formats = []
        for source in (video.get('sources') or []):
            src = source.get('src')
            if not src:
                continue
            formats.append({
                'format_id': source.get('label'),
                'height': int_or_none(source.get('size')),
                'url': src,
            })
        self._sort_formats(formats)

        entity = video.get('entity') or entity
        owner = entity.get('ownerObj') or {}
        uploader_id = owner.get('username')

        tags = entity.get('tags')
        if tags and isinstance(tags, compat_str):
            tags = [tags]

        thumbnail = None
        poster = video.get('poster') or entity.get('thumbnail_src')
        if poster:
            urlh = self._request_webpage(poster, video_id, fatal=False)
            if urlh:
                thumbnail = urlh.geturl()

        return {
            'id': video_id,
            'title': entity.get('title') or video_id,
            'formats': formats,
            'description': clean_html(entity.get('description')) or None,
            'license': str_or_none(entity.get('license')),
            'timestamp': int_or_none(entity.get('time_created')),
            'uploader': strip_or_none(owner.get('name')),
            'uploader_id': uploader_id,
            'uploader_url': format_field(uploader_id, None, 'https://www.minds.com/%s'),
            'view_count': int_or_none(entity.get('play:count')),
            'like_count': int_or_none(entity.get('thumbs:up:count')),
            'dislike_count': int_or_none(entity.get('thumbs:down:count')),
            'tags': tags,
            'comment_count': int_or_none(entity.get('comments:count')),
            'thumbnail': thumbnail,
        }

class MindsFeedBaseIE(MindsBaseIE):
    _PAGE_SIZE = 150

    def _entries(self, feed_id):
        query = {'limit': self._PAGE_SIZE, 'sync': 1}
        i = 1
        while True:
            data = self._call_api(
                'v2/feeds/container/%s/videos' % feed_id,
                feed_id, 'page %s' % i, query)
            entities = data.get('entities') or []
            for entity in entities:
                guid = entity.get('guid')
                if not guid:
                    continue
                yield self.url_result(
                    'https://www.minds.com/newsfeed/' + guid,
                    MindsIE.ie_key(), guid)
            query['from_timestamp'] = data['load-next']
            if not (query['from_timestamp'] and len(entities) == self._PAGE_SIZE):
                break
            i += 1

    def _real_extract(self, url):
        feed_id = self._match_id(url)
        feed = self._call_api(
            'v1/%s/%s' % (self._FEED_PATH, feed_id),
            feed_id, self._FEED_TYPE)[self._FEED_TYPE]

        return self.playlist_result(
            self._entries(feed['guid']), feed_id,
            strip_or_none(feed.get('name')),
            feed.get('briefdescription'))

class MindsChannelIE(MindsFeedBaseIE):
    _FEED_TYPE = 'channel'
    IE_NAME = 'minds:' + _FEED_TYPE
    _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'(?!(?:newsfeed|media|api|archive|groups)/)(?P<id>[^/?&#]+)'
    _FEED_PATH = 'channel'
    _TEST = {
        'url': 'https://www.minds.com/ottman',
        'info_dict': {
            'id': 'ottman',
            'title': 'Bill Ottman',
            'description': 'Co-creator & CEO @minds',
        },
        'playlist_mincount': 54,
    }

class MindsGroupIE(MindsFeedBaseIE):
    _FEED_TYPE = 'group'
    IE_NAME = 'minds:' + _FEED_TYPE
    _VALID_URL = MindsBaseIE._VALID_URL_BASE + r'groups/profile/(?P<id>[0-9]+)'
    _FEED_PATH = 'groups/group'
    _TEST = {
        'url': 'https://www.minds.com/groups/profile/785582576369672204/feed/videos',
        'info_dict': {
            'id': '785582576369672204',
            'title': 'Cooking Videos',
        },
        'playlist_mincount': 1,
    }


code:
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:
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 = self._match_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')),
                    **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:
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 .common import InfoExtractor
from ..utils import (
    ExtractorError,
    dict_get,
    traverse_obj,
    try_get,
)

class MirrativBaseIE(InfoExtractor):
    def assert_error(self, response):
        error_message = traverse_obj(response, ('status', 'error'))
        if error_message:
            raise ExtractorError('Mirrativ says: %s' % error_message, expected=True)

class MirrativIE(MirrativBaseIE):
    IE_NAME = 'mirrativ'
    _VALID_URL = r'https?://(?:www\.)?mirrativ\.com/live/(?P<id>[^/?#&]+)'

    TESTS = [{
        'url': 'https://mirrativ.com/live/UQomuS7EMgHoxRHjEhNiHw',
        'info_dict': {
            'id': 'UQomuS7EMgHoxRHjEhNiHw',
            'title': 'ねむいぃ、。『参加型』🔰jcが初めてやるCOD✨初見さん大歓迎💗',
            'is_live': True,
            'description': 'md5:bfcd8f77f2fab24c3c672e5620f3f16e',
            'thumbnail': r're:https?://.+',
            'uploader': '# あ ち ゅ 。💡',
            'uploader_id': '118572165',
            'duration': None,
            'view_count': 1241,
            'release_timestamp': 1646229192,
            'timestamp': 1646229167,
            'was_live': False,
        },
        'skip': 'livestream',
    }, {
        'url': 'https://mirrativ.com/live/POxyuG1KmW2982lqlDTuPw',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage('https://www.mirrativ.com/live/%s' % video_id, video_id)
        live_response = self._download_json(f'https://www.mirrativ.com/api/live/live?live_id={video_id}', video_id)
        self.assert_error(live_response)

        hls_url = dict_get(live_response, ('archive_url_hls', 'streaming_url_hls'))
        is_live = bool(live_response.get('is_live'))
        if not hls_url:
            raise ExtractorError('Neither archive nor live is available.', expected=True)

        formats = self._extract_m3u8_formats(
            hls_url, video_id,
            ext='mp4', entry_protocol='m3u8_native',
            m3u8_id='hls', live=is_live)
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': self._og_search_title(webpage, default=None) or self._search_regex(
                r'<title>\s*(.+?) - Mirrativ\s*</title>', webpage) or live_response.get('title'),
            'is_live': is_live,
            'description': live_response.get('description'),
            'formats': formats,
            'thumbnail': live_response.get('image_url'),
            'uploader': traverse_obj(live_response, ('owner', 'name')),
            'uploader_id': traverse_obj(live_response, ('owner', 'user_id')),
            'duration': try_get(live_response, lambda x: x['ended_at'] - x['started_at']) if not is_live else None,
            'view_count': live_response.get('total_viewer_num'),
            'release_timestamp': live_response.get('started_at'),
            'timestamp': live_response.get('created_at'),
            'was_live': bool(live_response.get('is_archive')),
        }

class MirrativUserIE(MirrativBaseIE):
    IE_NAME = 'mirrativ:user'
    _VALID_URL = r'https?://(?:www\.)?mirrativ\.com/user/(?P<id>\d+)'

    _TESTS = [{
        # Live archive is available up to 3 days
        # see: https://helpfeel.com/mirrativ/%E9%8C%B2%E7%94%BB-5e26d3ad7b59ef0017fb49ac (Japanese)
        'url': 'https://www.mirrativ.com/user/110943130',
        'note': 'multiple archives available',
        'only_matching': True,
    }]

    def _entries(self, user_id):
        page = 1
        while page is not None:
            api_response = self._download_json(
                f'https://www.mirrativ.com/api/live/live_history?user_id={user_id}&page={page}', user_id,
                note=f'Downloading page {page}')
            self.assert_error(api_response)
            lives = api_response.get('lives')
            if not lives:
                break
            for live in lives:
                if not live.get('is_archive') and not live.get('is_live'):
                    # neither archive nor live is available, so skip it
                    # or the service will ban your IP address for a while
                    continue
                live_id = live.get('live_id')
                url = 'https://www.mirrativ.com/live/%s' % live_id
                yield self.url_result(url, video_id=live_id, video_title=live.get('title'))
            page = api_response.get('next_page')

    def _real_extract(self, url):
        user_id = self._match_id(url)
        user_info = self._download_json(
            f'https://www.mirrativ.com/api/user/profile?user_id={user_id}', user_id,
            note='Downloading user info', fatal=False)
        self.assert_error(user_info)

        return self.playlist_result(
            self._entries(user_id), user_id,
            user_info.get('name'), user_info.get('description'))


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

class MirrorCoUKIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mirror\.co\.uk/[/+[\w-]+-(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.mirror.co.uk/tv/tv-news/love-island-fans-baffled-after-27163139',
        'info_dict': {
            'id': 'voyyS7SV',
            'ext': 'mp4',
            'title': 'Love Island: Gemma Owen enters the villa',
            'description': 'Love Island: Michael Owen\'s daughter Gemma Owen enters the villa.',
            'thumbnail': 'https://cdn.jwplayer.com/v2/media/voyyS7SV/poster.jpg?width=720',
            'display_id': '27163139',
            'timestamp': 1654547895,
            'duration': 57.0,
            'upload_date': '20220606',
        },
    }, {
        'url': 'https://www.mirror.co.uk/3am/celebrity-news/michael-jacksons-son-blankets-new-25344890',
        'info_dict': {
            'id': 'jyXpdvxp',
            'ext': 'mp4',
            'title': 'Michael Jackson’s son Bigi calls for action on climate change',
            'description': 'md5:d39ceaba2b7a615b4ca6557e7bc40222',
            'thumbnail': 'https://cdn.jwplayer.com/v2/media/jyXpdvxp/poster.jpg?width=720',
            'display_id': '25344890',
            'timestamp': 1635749907,
            'duration': 56.0,
            'upload_date': '20211101',
        },
    }, {
        'url': 'https://www.mirror.co.uk/sport/football/news/antonio-conte-next-tottenham-manager-25346042',
        'info_dict': {
            'id': 'q6FkKa4p',
            'ext': 'mp4',
            'title': 'Nuno sacked by Tottenham after fifth Premier League defeat of the season',
            'description': 'Nuno Espirito Santo has been sacked as Tottenham boss after only four months in charge.',
            'thumbnail': 'https://cdn.jwplayer.com/v2/media/q6FkKa4p/poster.jpg?width=720',
            'display_id': '25346042',
            'timestamp': 1635763157,
            'duration': 40.0,
            'upload_date': '20211101',
        },
    }, {
        'url': 'https://www.mirror.co.uk/3am/celebrity-news/johnny-depp-splashes-50k-curry-27160737',
        'info_dict': {
            'id': 'IT0oa1nH',
            'ext': 'mp4',
            'title': 'Johnny Depp Leaves The Grand Hotel in Birmingham',
            'description': 'Johnny Depp Leaves The Grand Hotel in Birmingham.',
            'thumbnail': 'https://cdn.jwplayer.com/v2/media/IT0oa1nH/poster.jpg?width=720',
            'display_id': '27160737',
            'timestamp': 1654524120,
            'duration': 65.0,
            'upload_date': '20220606',
        },
    }, {
        'url': 'https://www.mirror.co.uk/tv/tv-news/love-islands-liam-could-first-27162602',
        'info_dict': {
            'id': 'EaPr5Z2j',
            'ext': 'mp4',
            'title': 'Love Island: Davide reveals plot twist after receiving text',
            'description': 'Love Island: Davide reveals plot twist after receiving text',
            'thumbnail': 'https://cdn.jwplayer.com/v2/media/EaPr5Z2j/poster.jpg?width=720',
            'display_id': '27162602',
            'timestamp': 1654552597,
            'duration': 23.0,
            'upload_date': '20220606',
        },
    }, {
        'url': 'https://www.mirror.co.uk/news/uk-news/william-kate-sent-message-george-27160572',
        'info_dict': {
            'id': 'ygtceXIu',
            'ext': 'mp4',
            'title': 'Prince William and Kate arrive in Wales with George and Charlotte',
            'description': 'Prince William and Kate Middleton arrive in Wales with children Prince George and Princess Charlotte.',
            'thumbnail': 'https://cdn.jwplayer.com/v2/media/ygtceXIu/poster.jpg?width=720',
            'display_id': '27160572',
            'timestamp': 1654349678,
            'duration': 106.0,
            'upload_date': '20220604',
        },
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        data = self._search_json(r'div\s+class="json-placeholder"\s+data-json="',
                                 webpage, 'data', display_id, transform_source=unescapeHTML)['videoData']

        return {
            '_type': 'url_transparent',
            'url': f'jwplatform:{data["videoId"]}',
            'ie_key': 'JWPlatform',
            'display_id': display_id,
        }


code:
from __future__ import unicode_literals

from .misskey import MisskeyIE, MisskeyUserIE

__all__ = ['MisskeyIE', 'MisskeyUserIE']


code:
from .misskey import MisskeyIE
from ...compat import compat_str

class BaseMisskeyComplement(object):
    """
    This class (Complement) is used for extending support for more formats,
    added by a specific instance.
    """
    _INSTANCE_RE = r''

    def _extract_formats(self, ie: MisskeyIE, video_id: compat_str, file) -> list:
        """
        Return list of formats which is not available in standard extraction.
        Sorting is done in MisskeyIE, so you don't have to do that in it.
        """
        raise Exception('Implement this in subclasses')

class MisskeyIoComplement(BaseMisskeyComplement):
    _INSTANCE_RE = r'^misskey\.io$'

    def _extract_formats(self, ie: MisskeyIE, video_id: compat_str, file) -> list:
        mimetype = file.get('type')
        if not mimetype or not mimetype.startswith('video/'):
            return []
        embed_url = 'https://s3encode.arkjp.net/?video=%s' % file.get('url')
        embed_webpage = ie._download_webpage(
            embed_url, video_id, note='Downloading embed webpage',
            headers={
                'Referer': 'https://misskey.io/',
            })
        vdeliv_embed_id = ie._search_regex(
            r'<iframe src="https://iframe\.videodelivery\.net/([a-zA-Z0-9]+)"',
            embed_webpage, 'videodeliver embed URL', group=1)
        vdeliv_mpd_url = f'https://videodelivery.net/{vdeliv_embed_id}/manifest/video.mpd?parentOrigin=https%3A%2F%2Fs3encode.arkjp.net'

        return ie._extract_mpd_formats(vdeliv_mpd_url, video_id, mpd_id='mpd', fatal=False)

_COMPLEMENTS = [MisskeyIoComplement]

__all__ = ['_COMPLEMENTS']


code:
# coding: utf-8
# AUTOMATICALLY GENERATED FILE. DO NOT EDIT.
# Generated by ./devscripts/make_misskey_instance_list.py
from __future__ import unicode_literals

instances = {
    # list of instances here
    "090509.xyz",
    "0wo.ooo",
    "3.srl",
    "49six.club",
    "829.hu",
    "9cat.vtubertalk.com",
    "aaa.awoo.fun",
    "abandoned.opprimonational.com",
    "ap.gjcloak.top",
    "ark.misskey.online",
    "atto.town",
    "awa.im",
    "b612.moe",
    "bbs.keinpfusch.net",
    "bbs.monocul.us",
    "beta.kokonect.link",
    "beta.zezefans.com",
    "bihlink.com",
    "bilibili.org",
    "birb.space",
    "bkgrdclrschm.link",
    "blog.fulda.social",
    "blue.skye.cx",
    "borg.social",
    "bronyfurry.com",
    "bungle.online",
    "buta.moe",
    "cat.nyan.lgbt",
    "catgirl.life",
    "catgirl.works",
    "chars.moe",
    "chatsubo.cbrpnk.dev",
    "chungus.cc",
    "cliq.social",
    "co.misskey.io",
    "cocoronavi.com",
    "comfy.social",
    "council.azure.icu",
    "cover.pm",
    "cummunity.social",
    "cybre.club",
    "dev.birb.space",
    "dev.mis.b-shock.org",
    "dp.massmist.net",
    "drdr.club",
    "egirls.gay",
    "ente.fun",
    "epic.meme.moe",
    "epitaph.noize.guru",
    "erica.hiyuki2578.net",
    "erisly.social",
    "esrever.cc",
    "estrogen.network",
    "evil.social",
    "f.dyne.link",
    "f.jymuoyu.com",
    "fed.nanao.moe",
    "fedi.intkos.link",
    "fedi.mint.lgbt",
    "fedi.nullob.si",
    "fedi.tyil.nl",
    "fedi.vituperio.xyz",
    "fedi.z8s.de",
    "fedi2.0x7f.one",
    "fediland.nl",
    "fediverse.dotnet00.dev",
    "fediverse.hgoel.dev",
    "fediverse.projectftm.com",
    "fet.bar",
    "fimb.ftls.xyz",
    "freaksonly.space",
    "frennet.xyz",
    "friendsyu.me",
    "fukahire.tokyo",
    "fuzzy.zone",
    "gaku-tab-kill.com",
    "genau.qwertqwefsday.eu",
    "gensoukyou.jp.net",
    "girlcock.eu",
    "godspeed.moe",
    "goodman-wilson.com",
    "gp.lapy.link",
    "gp.miku2go.com",
    "gp.tsukimi.club",
    "gp4.nem.one",
    "groundpolis.app",
    "gyutte.site",
    "hallsofamenti.io",
    "hashi.icu",
    "hell.social",
    "hereisliberty.com",
    "hina.house",
    "honi.club",
    "honi.stesan.dev",
    "hpive.misskey.online",
    "hub.solarnight.net",
    "ip.omorashi.cc",
    "jiji.su",
    "jumanji.co",
    "k.lapy.link",
    "k6.vtubertalk.com",
    "kafka.icu",
    "kagamisskey.com",
    "kevin.birb.space",
    "kitty.social",
    "kokonect.link",
    "kokt.club",
    "kr.akirin.xyz",
    "labo.wovs.tk",
    "lagemeet.duckdns.org",
    "lesbian.energy",
    "lgbt.pm",
    "lolita.social",
    "m.chars.jp",
    "m.d3bu.net",
    "m.dogcraft.cn",
    "m.dogcraft.top",
    "m.filmfreedom.net",
    "m.isle.moe",
    "m.kirishima.cloud",
    "m.mashiro.top",
    "m.moec.top",
    "m.motisn.net",
    "m.soopy.moe",
    "m.x4s.io",
    "m.yama2211.jp",
    "m.zuiho.moe",
    "m11.t8n.dev",
    "m12.t8n.dev",
    "m5y.usuyuki.net",
    "madost.one",
    "makimaki.jp",
    "matchat.trendcreate.net",
    "mei10.nem.one",
    "mei11.nem.one",
    "meisskey.one",
    "meow.zarchbox.fr",
    "meta.x61.uk",
    "metaskey.net",
    "mewl.me",
    "mi.8mitsu.net",
    "mi.cbrx.io",
    "mi.kaioken.space",
    "mi.les-requin.net",
    "mi.maikaze.moe",
    "mi.monado.ren",
    "mi.potofu.moe",
    "mi.shi-jk.com",
    "mi.siotter.xyz",
    "mi.tkw.fm",
    "mi.underscore.world",
    "microblog.social",
    "miki.live4you.one",
    "milkey.homes",
    "minidisc.tokyo",
    "miruku.cafe",
    "misky.herokuapp.com",
    "misky.rikunagiweb.jp",
    "misky.syui.cf",
    "miss.melt-adzuki.ga",
    "miss.mouse.services",
    "miss.nem.one",
    "miss.nyaku.moe",
    "miss.va-11-hall-a.cafe",
    "miss.voided.ee",
    "misskey-cloudrun-5sminlujwq-an.a.run.app",
    "misskey-exp.cyberrex.jp",
    "misskey.04.si",
    "misskey.7ka.org",
    "misskey.ai",
    "misskey.anjara.eu",
    "misskey.assaultli.ly",
    "misskey.azuki.cf",
    "misskey.azunyan.jp",
    "misskey.belligerence.club",
    "misskey.biribiri.dev",
    "misskey.bocc.de",
    "misskey.boon.chat",
    "misskey.bubbletea.dev",
    "misskey.byter.io",
    "misskey.cafkafk.com",
    "misskey.cats-home.net",
    "misskey.cf",
    "misskey.chiffon-sepxipl.com",
    "misskey.chn.moe",
    "misskey.cloud",
    "misskey.de",
    "misskey.dev",
    "misskey.distraction.engineer",
    "misskey.dokodare.ovh",
    "misskey.dyselon.com",
    "misskey.electricrequiem.com",
    "misskey.endaaman.me",
    "misskey.fedi.bzh",
    "misskey.fr",
    "misskey.girak.net",
    "misskey.goochiegoo.net",
    "misskey.gothloli.club",
    "misskey.gray.party",
    "misskey.honahuku.com",
    "misskey.hw2k.com",
    "misskey.id",
    "misskey.io",
    "misskey.ipv6-only.site",
    "misskey.kacco-e.org",
    "misskey.kurume-nct.com",
    "misskey.kusoneko.moe",
    "misskey.lilith.im",
    "misskey.luciferi.st",
    "misskey.m544.net",
    "misskey.magokorobin.com",
    "misskey.myfed.space",
    "misskey.mymte.de",
    "misskey.na2na.dev",
    "misskey.nauzome.com",
    "misskey.nenohi.net",
    "misskey.noellabo.jp",
    "misskey.nokotaro.com",
    "misskey.nokotaro.work",
    "misskey.omhnc.net",
    "misskey.osyakasyama.me",
    "misskey.painted.jp",
    "misskey.pantasystem.com",
    "misskey.phoebeivy.dev",
    "misskey.pixelfilm.nz",
    "misskey.pnyhf.eu",
    "misskey.potp.ro",
    "misskey.pueseso.club",
    "misskey.ranranhome.info",
    "misskey.ruste.red",
    "misskey.sagt.xyz",
    "misskey.sc3.fun",
    "misskey.sda1.net",
    "misskey.seediqbale.xyz",
    "misskey.sharivegas.com",
    "misskey.shinaaamo.com",
    "misskey.silverkey.me",
    "misskey.social",
    "misskey.sss-fc.com",
    "misskey.t-asa2000.net",
    "misskey.talesofmy.life",
    "misskey.tatakaibo.xyz",
    "misskey.tebukuro.xyz",
    "misskey.tech",
    "misskey.tlfoxhuman.net",
    "misskey.traction.jp",
    "misskey.twink.ink",
    "misskey.u0conveni.club",
    "misskey.unraids.ml",
    "misskey.usuyuki.net",
    "misskey.vierkantor.com",
    "misskey.void.gift",
    "misskey.wawebworks.com",
    "misskey.weebcouncil.online",
    "misskey.xianon.net",
    "misskey.xy-space.de",
    "misskey.ycrack.net",
    "misskey.yuangezhizao.cn",
    "misskey.yukineko.me",
    "misskey.zerozaski.tech",
    "misskey.zirconsoft.net",
    "mistli.net",
    "mitsukey.cf",
    "miwkey.miwpayou0808.info",
    "mk.absturztau.be",
    "mk.aetheri.ca",
    "mk.aqr.af",
    "mk.arachnitech.com",
    "mk.asie.pl",
    "mk.autonomy.earth",
    "mk.byte.wtf",
    "mk.catgirlsfor.science",
    "mk.chocoflan.net",
    "mk.cybre.ninja",
    "mk.danksquad.xyz",
    "mk.disqordia.space",
    "mk.eliko.ga",
    "mk.f72u.net",
    "mk.fedcast.ch",
    "mk.froth.zone",
    "mk.godspeed.moe",
    "mk.hellholesys.tk",
    "mk.himbeer.me",
    "mk.igwigg.space",
    "mk.kirota.net",
    "mk.kusako.de",
    "mk.lei202.com",
    "mk.longyap.name.my",
    "mk.mozc-plasma.tech",
    "mk.mozcmozc.club",
    "mk.nixnet.social",
    "mk.outerheaven.club",
    "mk.outv.im",
    "mk.paritybit.ca",
    "mk.possums.xyz",
    "mk.roobre.es",
    "mk.shatteredpalisa.de",
    "mk.shc.kanagawa.jp",
    "mk.shiro.ink",
    "mk.starnix.network",
    "mk.sysk-work.net",
    "mk.szkod.ovh",
    "mk.toast.cafe",
    "mk.togen.xyz",
    "mk.treasuredgem.xyz",
    "mk.udongein.reisen",
    "mk.vulpes.one",
    "mk.wataame.cafe",
    "mk.wolfie.pw",
    "mk.xiupos.net",
    "mk.xn--ponek-1sa8t.eu",
    "mk.yopo.work",
    "mk.yuru.studio",
    "mk2.mstdn.life",
    "mk2.toyoko.in",
    "mkiii.amplifie.red",
    "moien.net",
    "morale.ch",
    "movsw.0x0.st",
    "msk.hoshisaki-h.com",
    "msk.ilnk.info",
    "msk.minetaro12.com",
    "msk.porubu.com",
    "msk.seppuku.club",
    "msnis.radiate038.com",
    "my.medic.cafe",
    "myss.li",
    "natehiggers.online",
    "nca.aqualitore-lily.com",
    "nca10.net",
    "necko.plala.jp",
    "neko.ci",
    "nekomimi.yokohama",
    "never.bid",
    "newskey.cc",
    "next.yncrypt.net",
    "nijimiss.moe",
    "niscii.xyz",
    "nobl.ink",
    "not.acu.lt",
    "novoa.nagoya",
    "nr.akarinext.org",
    "nya.one",
    "nya.social",
    "nyaaa.jeder.pl",
    "nyan.live",
    "nyon.marimo-net.com",
    "odyssey.divineduty.xyz",
    "onsen-musume.fun",
    "oran.ski",
    "p1.a9z.dev",
    "pasta.ga",
    "ping.the-planet.space",
    "pixelart.moe",
    "pixellife.app",
    "plasmatrap.com",
    "pmth.us",
    "pooper.social",
    "port.mk",
    "pouet.fede.re",
    "ppplot.top",
    "prismst.one",
    "priv.menac.at",
    "privacyhub.xyz",
    "puta.gspot.lol",
    "quietplace.xyz",
    "reco.shrieker.net",
    "relict.space",
    "ridixy.com",
    "rin.pink",
    "ristor.me",
    "romca.tk",
    "rumia.tk",
    "s.thenautilus.net",
    "satania.social",
    "sauce.nussnougate.net",
    "seaside.cafe",
    "seediqbale.xyz",
    "shitpost.poridge.club",
    "simkey.net",
    "sjtu.moe",
    "skippers-bin.com",
    "slippy.xyz",
    "smllwrds.thisdevfucks.com",
    "smp.chaoswork.cn",
    "sn.nekojita.moe",
    "sns.tantalum.life",
    "sns.tsubasaori.be",
    "sns.ymn.info",
    "snug.moe",
    "soc.scuttlebug.space",
    "social.boiledscript.com",
    "social.calut.fr",
    "social.creatureofthehill.com",
    "social.cutezy.pink",
    "social.edu.ci",
    "social.jokiller230.tk",
    "social.jsts.xyz",
    "social.kyaru.xyz",
    "social.matsukky.com",
    "social.metamuffin.org",
    "social.metrix.network",
    "social.nocci.xyz",
    "social.noordstar.me",
    "social.pseudo-whiskey.bar",
    "social.qaq.wiki",
    "social.swagg.net",
    "social.zecircle.xyz",
    "social.zuendmasse.de",
    "spiral.sh",
    "springbo.cc",
    "ss.zgjnas.top",
    "st.reco.shrieker.net",
    "stella.ocellaris.dev",
    "stoner.town",
    "stop.voring.me",
    "submarin.online",
    "succubi.services",
    "sunisky.club",
    "sushi.ski",
    "sxv.es",
    "t.nyk.app",
    "tanoshii.site",
    "test3.syuilo.dev",
    "the-box.work",
    "the9thcircle.club",
    "thetooniverse.xyz",
    "thu2.closed.social",
    "ti.akirin.xyz",
    "transient.garden",
    "trebo.chat",
    "tty.pw",
    "tw.9tail.net",
    "twista.283.cloud",
    "tyazzkey.work",
    "ubiqueros.com",
    "uhouho.xyz",
    "uma.milkey.homes",
    "umaskey.net",
    "very.tastytea.de",
    "vivi.moe",
    "ward2.bouvardia.icu",
    "weeb.su",
    "wndp.cloud",
    "www.foxyhole.io",
    "www.i-kaohsiung.com",
    "www.loganjohndarylgraham.xyz",
    "wxw.cat",
    "xe.akirin.xyz",
    "xn--rtta-qoa.nu",
    "xn--s8w913fdga.chn.moe",
    "ygo.pub",
    "ym.rookieo.com",
    "ypolis.net",
    "yuustan.space",
    "z.kvche.ch",
    "z0ne.social",
}

__all__ = ['instances']


code:
# coding: utf-8
from __future__ import unicode_literals
import itertools
import json
import re

try:
    from .instances import instances
except ImportError:
    instances = ()

from ..common import SelfHostedInfoExtractor
from ...utils import (
    ExtractorError,
    determine_ext,
    get_first_group,
    mimetype2ext,
    smuggle_url,
    traverse_obj,
    unified_timestamp,
    unsmuggle_url,
    parse_iso8601,
)
from ...compat import compat_str

known_valid_instances = set()

class MisskeyBaseIE(SelfHostedInfoExtractor):
    _SH_VALID_CONTENT_STRINGS = (
        '<meta name="application-name" content="Misskey"',
        '<meta name="misskey:',
        '<!-- If you are reading this message... how about joining the development of Misskey? -->',
    )

    _HOSTNAME_GROUPS = ('instance', )
    _INSTANCE_LIST = instances
    _DYNAMIC_INSTANCE_LIST = known_valid_instances
    _NODEINFO_SOFTWARE = ('misskey', )
    _SOFTWARE_NAME = 'Misskey'

class MisskeyIE(MisskeyBaseIE):
    IE_NAME = 'misskey'
    _VALID_URL = r'(?P<prefix>(?:misskey|msky|msk):)?https?://(?P<instance>[a-zA-Z0-9._-]+)/notes/(?P<id>[a-zA-Z0-9]+)'
    _TESTS = [{
        'note': 'embed video',
        'url': 'https://misskey.io/notes/8pp0c7gsbm',
        'info_dict': {
            'id': '8pp0c7gsbm',
            'title': 'Misskeyダウンローダーのテストケース',
            'timestamp': 1629529895,
            'uploader': 'nao20010128',
            'uploader_id': '8pp040sbza',
            'visibility': 'public',
            'age_limit': 0,
        },
    }, {
        'note': 'another instance, and have no content (title=NA)',
        'url': 'https://misskey.dev/notes/8o0h93l7bl',
        'info_dict': {
            'id': '8o0h93l7bl',
            'title': None,
            'timestamp': 1625869866,
            'uploader': 'かに座と悪ヨメミ',
            'uploader_id': '8izhlpyfdt',
            'visibility': 'public',
            'age_limit': 0,
        },
    }, {
        'note': 'embed video with YouTube',
        'url': 'https://misskey.io/notes/8pp0di8s4t',
        # we have to port mfm-js in Node.js to mimick embed URL extraction
        # https://github.com/misskey-dev/misskey/blob/develop/src/misc/extract-url-from-mfm.ts
        # https://github.com/misskey-dev/misskey/blob/develop/src/client/ui/chat/note.vue
        # https://github.com/misskey-dev/mfm.js/blob/develop/src/internal/parser.pegjs
        'only_matching': True,
    }, {
        'note': 'no video',
        'url': 'https://misskey.io/notes/8pp04mprzx',
        'only_matching': True,
    }]

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

        url, api_response = unsmuggle_url(url)
        if not api_response:
            api_response = self._download_json(
                'https://%s/api/notes/show' % instance, video_id,
                # building POST payload without using json module
                data=('{"noteId":"%s"}' % video_id).encode())

        title = api_response.get('text')
        timestamp = unified_timestamp(api_response.get('createdAt'))
        uploader = traverse_obj(api_response, ('user', 'name'), ('user', 'username'), expected_type=compat_str)
        uploader_id = traverse_obj(api_response, ('userId', ), ('user', 'id'), expected_type=compat_str)
        visibility = api_response.get('visibility')

        from .complement import _COMPLEMENTS
        complements = [x() for x in _COMPLEMENTS if re.match(x._INSTANCE_RE, instance)]

        files = []
        for idx, file in enumerate(api_response.get('files') or []):
            formats = []
            mimetype = file.get('type')
            if not mimetype or (not mimetype.startswith('video/') and not mimetype.startswith('audio/')):
                continue
            formats.append({
                'format_id': file.get('id'),
                'url': file.get('url'),
                'ext': mimetype2ext(mimetype) or determine_ext(file.get('name')),
                'filesize': file.get('size'),
            })

            # must be here to prevent circular import
            if complements:
                self.to_screen('%d complement(s) found, running them to get more formats' % len(complements))
                for cmpl in complements:
                    try:
                        formats.extend(cmpl._extract_formats(self, video_id, file))
                    except ExtractorError as ex:
                        self.report_warning('Error occured in complement "%s": %s' % (cmpl, ex))

            self._sort_formats(formats)

            files.append({
                'id': '%s-%d' % (video_id, idx),
                'title': title,
                'formats': formats,
                'thumbnail': file.get('thumbnailUrl'),
                'age_limit': 18 if file.get('isSensitive') else 0,
                'timestamp': parse_iso8601(file.get('createdAt')),
            })

        base = {
            'id': video_id,
            'title': title,
            'timestamp': timestamp,
            'uploader': uploader,
            'uploader_id': uploader_id,
            'visibility': visibility,
        }
        if not files:
            raise ExtractorError('This note does not have any media file.', expected=True)
        elif len(files) == 1:
            files[0].update(base)
            return files[0]
        else:
            base.update({
                '_type': 'multi_video',
                'entries': files,
            })
            return base

class MisskeyUserIE(MisskeyBaseIE):
    IE_NAME = 'misskey:user'
    _VALID_URL = r'(?P<prefix>(?:misskey|msky|msk):)?https?://(?P<instance>[a-zA-Z0-9._-]+)/@(?P<id>[a-zA-Z0-9_-]+)(?:@(?P<instance2>[a-zA-Z0-9_.-]+))?'
    _TESTS = [{
        'note': 'refer to another instance',
        'url': 'https://misskey.io/@vitaone@misskey.dev',
        'playlist_mincount': 0,
    }, {
        'url': 'https://misskey.io/@kubaku@misskey.dev',
        'playlist_mincount': 1,
    }, {
        'url': 'https://misskey.dev/@kubaku',
        'playlist_mincount': 1,
    }]

    def _entries(self, instance, user_id):
        until_id = None
        for i in itertools.count(1):
            page = self._download_json(
                'https://%s/api/users/notes' % instance, user_id,
                note='Downloading page %d' % i, data=json.dumps({
                    'limit': 100,
                    'userId': user_id,
                    'withFiles': True,
                    **({'untilId': until_id} if until_id else {}),
                }).encode())
            yield from page
            until_id = traverse_obj(page, (-1, 'id'))
            if not until_id:
                break

    def _mapfilter_items_with_media(self, instance, entries):
        for item in entries:
            mimetypes = [x.get('type') for x in item.get('files') or [] if x]
            if any(x and (x.startswith('video/') or x.startswith('audio/')) for x in mimetypes):
                yield self.url_result(smuggle_url('https://%s/notes/%s' % (instance, item.get('id')), item))

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        instance = get_first_group(mobj, 'instance2', 'instance')
        user_handle = mobj.group('id')

        user_info = self._download_json(
            'https://%s/api/users/show' % instance, user_handle,
            note='Fetching user info',
            # building POST payload without using json module
            data=('{"username":"%s"}' % user_handle).encode())
        user_id = user_info.get('id')
        uploader = user_info.get('name')
        uploader_id = user_info.get('username')
        description = user_info.get('description')

        entries = self._mapfilter_items_with_media(instance, self._entries(instance, user_id))

        return {
            '_type': 'playlist',
            'title': 'Notes from @%s@%s' % (uploader_id, instance),
            'entries': entries,
            'uploader': uploader,
            'uploader_id': uploader_id,
            'description': description,
        }


code:
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 = self._match_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:
from .telecinco import TelecincoIE
from ..utils import (
    int_or_none,
    parse_iso8601,
)

class MiTeleIE(TelecincoIE):
    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',
        },
    }, {
        # 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,
        },
    }, {
        '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_info = self._parse_content(pre_player['video'], url)
        content = pre_player.get('content') or {}
        info = content.get('info') or {}

        video_info.update({
            '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')),
            'age_limit': int_or_none(info.get('rating')),
            'timestamp': parse_iso8601(pre_player.get('publishedTime')),
        })
        return video_info


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

class MixchIE(InfoExtractor):
    IE_NAME = 'mixch'
    _VALID_URL = r'https?://(?:www\.)?mixch\.tv/u/(?P<id>\d+)'

    _TESTS = [{
        'url': 'https://mixch.tv/u/16236849/live',
        'skip': 'don\'t know if this live persists',
        'info_dict': {
            'id': '16236849',
            'title': '24配信シェア⭕投票🙏💦',
            'comment_count': 13145,
            'view_count': 28348,
            'timestamp': 1636189377,
            'uploader': '🦥伊咲👶🏻#フレアワ',
            'uploader_id': '16236849',
        }
    }, {
        'url': 'https://mixch.tv/u/16137876/live',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(f'https://mixch.tv/u/{video_id}/live', video_id)

        initial_js_state = self._parse_json(self._search_regex(
            r'(?m)^\s*window\.__INITIAL_JS_STATE__\s*=\s*(\{.+?\});\s*$', webpage, 'initial JS state'), video_id)
        if not initial_js_state.get('liveInfo'):
            raise ExtractorError('Livestream has ended.', expected=True)

        return {
            'id': video_id,
            'title': traverse_obj(initial_js_state, ('liveInfo', 'title')),
            'comment_count': traverse_obj(initial_js_state, ('liveInfo', 'comments')),
            'view_count': traverse_obj(initial_js_state, ('liveInfo', 'visitor')),
            'timestamp': traverse_obj(initial_js_state, ('liveInfo', 'created')),
            'uploader': traverse_obj(initial_js_state, ('broadcasterInfo', 'name')),
            'uploader_id': video_id,
            'formats': [{
                'format_id': 'hls',
                'url': traverse_obj(initial_js_state, ('liveInfo', 'hls')) or 'https://d1hd0ww6piyb43.cloudfront.net/hls/torte_%s.m3u8' % video_id,
                'ext': 'mp4',
                'protocol': 'm3u8',
            }],
            'is_live': True,
        }

class MixchArchiveIE(InfoExtractor):
    IE_NAME = 'mixch:archive'
    _VALID_URL = r'https?://(?:www\.)?mixch\.tv/archive/(?P<id>\d+)'

    _TESTS = [{
        'url': 'https://mixch.tv/archive/421',
        'skip': 'paid video, no DRM. expires at Jan 23',
        'info_dict': {
            'id': '421',
            'title': '96NEKO SHOW TIME',
        }
    }]

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

        html5_videos = self._parse_html5_media_entries(
            url, webpage.replace('video-js', 'video'), video_id, 'hls')
        if not html5_videos:
            self.raise_login_required(method='cookies')
        infodict = html5_videos[0]
        infodict.update({
            'id': video_id,
            'title': self._html_search_regex(r'class="archive-title">(.+?)</', webpage, 'title')
        })

        return infodict