code:
import itertools

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

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

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

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

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

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

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

        if not cloudcast:
            raise ExtractorError('Track not found', expected=True)

        reason = cloudcast.get('restrictedReason')
        if reason == 'tracklist':
            raise ExtractorError('Track unavailable in your country due to licensing restrictions', expected=True)
        elif reason == 'repeat_play':
            raise ExtractorError('You have reached your play limit for this track', expected=True)
        elif reason:
            raise ExtractorError('Track is restricted', expected=True)

        title = cloudcast['name']

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

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

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

        self._sort_formats(formats)

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

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

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

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

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

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

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

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

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

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

            items = playlist.get(playlist_type) or {}
            for edge in items.get('edges', []):
                cloudcast = self._get_cloudcast(edge.get('node') or {})
                cloudcast_url = cloudcast.get('url')
                if not cloudcast_url:
                    continue
                slug = try_get(cloudcast, lambda x: x['slug'], compat_str)
                owner_username = try_get(cloudcast, lambda x: x['owner']['username'], compat_str)
                video_id = '%s_%s' % (owner_username, slug) if slug and owner_username else None
                entries.append(self.url_result(
                    cloudcast_url, MixcloudIE.ie_key(), video_id))

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

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

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

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

    _TITLE_KEY = 'displayName'
    _DESCRIPTION_KEY = 'biog'
    _ROOT_TYPE = 'user'
    _NODE_TEMPLATE = '''slug
          url
          owner { username }'''

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

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

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

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


code:
import re
import urllib.parse
import uuid

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    int_or_none,
    join_nonempty,
    parse_duration,
    parse_iso8601,
    traverse_obj,
    try_get,
)

class MLBBaseIE(InfoExtractor):
    def _real_extract(self, url):
        display_id = self._match_id(url)
        video = self._download_video_data(display_id)
        video_id = video['id']
        title = video['title']
        feed = self._get_feed(video)

        formats = []
        for playback in (feed.get('playbacks') or []):
            playback_url = playback.get('url')
            if not playback_url:
                continue
            name = playback.get('name')
            ext = determine_ext(playback_url)
            if ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    playback_url, video_id, 'mp4',
                    'm3u8_native', m3u8_id=name, fatal=False))
            else:
                f = {
                    'format_id': name,
                    'url': playback_url,
                }
                mobj = re.search(r'_(\d+)K_(\d+)X(\d+)', name)
                if mobj:
                    f.update({
                        'height': int(mobj.group(3)),
                        'tbr': int(mobj.group(1)),
                        'width': int(mobj.group(2)),
                    })
                mobj = re.search(r'_(\d+)x(\d+)_(\d+)_(\d+)K\.mp4', playback_url)
                if mobj:
                    f.update({
                        'fps': int(mobj.group(3)),
                        'height': int(mobj.group(2)),
                        'tbr': int(mobj.group(4)),
                        'width': int(mobj.group(1)),
                    })
                formats.append(f)
        self._sort_formats(formats)

        thumbnails = []
        for cut in (try_get(feed, lambda x: x['image']['cuts'], list) or []):
            src = cut.get('src')
            if not src:
                continue
            thumbnails.append({
                'height': int_or_none(cut.get('height')),
                'url': src,
                'width': int_or_none(cut.get('width')),
            })

        language = (video.get('language') or 'EN').lower()

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': video.get('description'),
            'duration': parse_duration(feed.get('duration')),
            'thumbnails': thumbnails,
            'timestamp': parse_iso8601(video.get(self._TIMESTAMP_KEY)),
            'subtitles': self._extract_mlb_subtitles(feed, language),
        }

class MLBIE(MLBBaseIE):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:[\da-z_-]+\.)*mlb\.com/
                        (?:
                            (?:
                                (?:[^/]+/)*video/[^/]+/c-|
                                (?:
                                    shared/video/embed/(?:embed|m-internal-embed)\.html|
                                    (?:[^/]+/)+(?:play|index)\.jsp|
                                )\?.*?\bcontent_id=
                            )
                            (?P<id>\d+)
                        )
                    '''
    _EMBED_REGEX = [
        r'<iframe[^>]+?src=(["\'])(?P<url>https?://m(?:lb)?\.mlb\.com/shared/video/embed/embed\.html\?.+?)\1',
        r'data-video-link=["\'](?P<url>http://m\.mlb\.com/video/[^"\']+)',
    ]
    _TESTS = [
        {
            'url': 'https://www.mlb.com/mariners/video/ackleys-spectacular-catch/c-34698933',
            'md5': '632358dacfceec06bad823b83d21df2d',
            'info_dict': {
                'id': '34698933',
                'ext': 'mp4',
                'title': "Ackley's spectacular catch",
                'description': 'md5:7f5a981eb4f3cbc8daf2aeffa2215bf0',
                'duration': 66,
                'timestamp': 1405995000,
                'upload_date': '20140722',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/video/stanton-prepares-for-derby/c-34496663',
            'md5': 'bf2619bf9cacc0a564fc35e6aeb9219f',
            'info_dict': {
                'id': '34496663',
                'ext': 'mp4',
                'title': 'Stanton prepares for Derby',
                'description': 'md5:d00ce1e5fd9c9069e9c13ab4faedfa57',
                'duration': 46,
                'timestamp': 1405120200,
                'upload_date': '20140711',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/video/cespedes-repeats-as-derby-champ/c-34578115',
            'md5': '99bb9176531adc600b90880fb8be9328',
            'info_dict': {
                'id': '34578115',
                'ext': 'mp4',
                'title': 'Cespedes repeats as Derby champ',
                'description': 'md5:08df253ce265d4cf6fb09f581fafad07',
                'duration': 488,
                'timestamp': 1405414336,
                'upload_date': '20140715',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/video/bautista-on-home-run-derby/c-34577915',
            'md5': 'da8b57a12b060e7663ee1eebd6f330ec',
            'info_dict': {
                'id': '34577915',
                'ext': 'mp4',
                'title': 'Bautista on Home Run Derby',
                'description': 'md5:b80b34031143d0986dddc64a8839f0fb',
                'duration': 52,
                'timestamp': 1405405122,
                'upload_date': '20140715',
                'thumbnail': r're:^https?://.*\.jpg$',
            },
        },
        {
            'url': 'https://www.mlb.com/video/hargrove-homers-off-caldwell/c-1352023483?tid=67793694',
            'only_matching': True,
        },
        {
            'url': 'http://m.mlb.com/shared/video/embed/embed.html?content_id=35692085&topic_id=6479266&width=400&height=224&property=mlb',
            'only_matching': True,
        },
        {
            'url': 'http://mlb.mlb.com/shared/video/embed/embed.html?content_id=36599553',
            'only_matching': True,
        },
        {
            'url': 'http://mlb.mlb.com/es/video/play.jsp?content_id=36599553',
            'only_matching': True,
        },
        {
            'url': 'https://www.mlb.com/cardinals/video/piscottys-great-sliding-catch/c-51175783',
            'only_matching': True,
        },
        {
            # From http://m.mlb.com/news/article/118550098/blue-jays-kevin-pillar-goes-spidey-up-the-wall-to-rob-tim-beckham-of-a-homer
            'url': 'http://mlb.mlb.com/shared/video/embed/m-internal-embed.html?content_id=75609783&property=mlb&autoplay=true&hashmode=false&siteSection=mlb/multimedia/article_118550098/article_embed&club=mlb',
            'only_matching': True,
        },
    ]
    _TIMESTAMP_KEY = 'date'

    @staticmethod
    def _get_feed(video):
        return video

    @staticmethod
    def _extract_mlb_subtitles(feed, language):
        subtitles = {}
        for keyword in (feed.get('keywordsAll') or []):
            keyword_type = keyword.get('type')
            if keyword_type and keyword_type.startswith('closed_captions_location_'):
                cc_location = keyword.get('value')
                if cc_location:
                    subtitles.setdefault(language, []).append({
                        'url': cc_location,
                    })
        return subtitles

    def _download_video_data(self, display_id):
        return self._download_json(
            'http://content.mlb.com/mlb/item/id/v1/%s/details/web-v1.json' % display_id,
            display_id)

class MLBVideoIE(MLBBaseIE):
    _VALID_URL = r'https?://(?:www\.)?mlb\.com/(?:[^/]+/)*video/(?P<id>[^/?&#]+)'
    _TEST = {
        'url': 'https://www.mlb.com/mariners/video/ackley-s-spectacular-catch-c34698933',
        'md5': '632358dacfceec06bad823b83d21df2d',
        'info_dict': {
            'id': 'c04a8863-f569-42e6-9f87-992393657614',
            'ext': 'mp4',
            'title': "Ackley's spectacular catch",
            'description': 'md5:7f5a981eb4f3cbc8daf2aeffa2215bf0',
            'duration': 66,
            'timestamp': 1405995000,
            'upload_date': '20140722',
            'thumbnail': r're:^https?://.+',
        },
    }
    _TIMESTAMP_KEY = 'timestamp'

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

    @staticmethod
    def _get_feed(video):
        return video['feeds'][0]

    @staticmethod
    def _extract_mlb_subtitles(feed, language):
        subtitles = {}
        for cc_location in (feed.get('closedCaptions') or []):
            subtitles.setdefault(language, []).append({
                'url': cc_location,
            })

    def _download_video_data(self, display_id):
        # https://www.mlb.com/data-service/en/videos/[SLUG]
        return self._download_json(
            'https://fastball-gateway.mlb.com/graphql',
            display_id, query={
                'query': '''{
  mediaPlayback(ids: "%s") {
    description
    feeds(types: CMS) {
      closedCaptions
      duration
      image {
        cuts {
          width
          height
          src
        }
      }
      playbacks {
        name
        url
      }
    }
    id
    timestamp
    title
  }
}''' % display_id,
            })['data']['mediaPlayback'][0]

class MLBTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?mlb\.com/tv/g(?P<id>\d{6})'
    _NETRC_MACHINE = 'mlb'

    _TESTS = [{
        'url': 'https://www.mlb.com/tv/g661581/vee2eff5f-a7df-4c20-bdb4-7b926fa12638',
        'info_dict': {
            'id': '661581',
            'ext': 'mp4',
            'title': '2022-07-02 - St. Louis Cardinals @ Philadelphia Phillies',
        },
        'params': {
            'skip_download': True,
        },
    }]
    _access_token = None

    def _real_initialize(self):
        if not self._access_token:
            self.raise_login_required(
                'All videos are only available to registered users', method='password')

    def _perform_login(self, username, password):
        data = f'grant_type=password&username={urllib.parse.quote(username)}&password={urllib.parse.quote(password)}&scope=openid offline_access&client_id=0oa3e1nutA1HLzAKG356'
        access_token = self._download_json(
            'https://ids.mlb.com/oauth2/aus1m088yK07noBfh356/v1/token', None,
            headers={
                'User-Agent': 'okhttp/3.12.1',
                'Content-Type': 'application/x-www-form-urlencoded'
            }, data=data.encode())['access_token']

        entitlement = self._download_webpage(
            f'https://media-entitlement.mlb.com/api/v3/jwt?os=Android&appname=AtBat&did={str(uuid.uuid4())}', None,
            headers={
                'User-Agent': 'okhttp/3.12.1',
                'Authorization': f'Bearer {access_token}'
            })

        data = f'grant_type=urn:ietf:params:oauth:grant-type:token-exchange&subject_token={entitlement}&subject_token_type=urn:ietf:params:oauth:token-type:jwt&platform=android-tv'
        self._access_token = self._download_json(
            'https://us.edge.bamgrid.com/token', None,
            headers={
                'Accept': 'application/json',
                'Authorization': 'Bearer bWxidHYmYW5kcm9pZCYxLjAuMA.6LZMbH2r--rbXcgEabaDdIslpo4RyZrlVfWZhsAgXIk',
                'Content-Type': 'application/x-www-form-urlencoded'
            }, data=data.encode())['access_token']

    def _real_extract(self, url):
        video_id = self._match_id(url)
        airings = self._download_json(
            f'https://search-api-mlbtv.mlb.com/svc/search/v2/graphql/persisted/query/core/Airings?variables=%7B%22partnerProgramIds%22%3A%5B%22{video_id}%22%5D%2C%22applyEsniMediaRightsLabels%22%3Atrue%7D',
            video_id)['data']['Airings']

        formats, subtitles = [], {}
        for airing in airings:
            m3u8_url = self._download_json(
                airing['playbackUrls'][0]['href'].format(scenario='browser~csai'), video_id,
                headers={
                    'Authorization': self._access_token,
                    'Accept': 'application/vnd.media-service+json; version=2'
                })['stream']['complete']
            f, s = self._extract_m3u8_formats_and_subtitles(
                m3u8_url, video_id, 'mp4', m3u8_id=join_nonempty(airing.get('feedType'), airing.get('feedLanguage')))
            formats.extend(f)
            self._merge_subtitles(s, target=subtitles)

        self._sort_formats(formats)
        return {
            'id': video_id,
            'title': traverse_obj(airings, (..., 'titles', 0, 'episodeName'), get_all=False),
            'is_live': traverse_obj(airings, (..., 'mediaConfig', 'productType'), get_all=False) == 'LIVE',
            'formats': formats,
            'subtitles': subtitles,
            'http_headers': {'Authorization': f'Bearer {self._access_token}'},
        }

repo_name: Lesmiscore/ytdl


code:
from .common import InfoExtractor

class MLSSoccerIE(InfoExtractor):
    _VALID_DOMAINS = r'(?:(?:cfmontreal|intermiamicf|lagalaxy|lafc|houstondynamofc|dcunited|atlutd|mlssoccer|fcdallas|columbuscrew|coloradorapids|fccincinnati|chicagofirefc|austinfc|nashvillesc|whitecapsfc|sportingkc|soundersfc|sjearthquakes|rsl|timbers|philadelphiaunion|orlandocitysc|newyorkredbulls|nycfc)\.com|(?:torontofc)\.ca|(?:revolutionsoccer)\.net)'
    _VALID_URL = r'https?://(?:www\.)?%s/video/#?(?P<id>[^/&$#?]+)' % _VALID_DOMAINS

    _TESTS = [{
        'url': 'https://www.mlssoccer.com/video/the-octagon-can-alphonso-davies-lead-canada-to-first-world-cup-since-1986#the-octagon-can-alphonso-davies-lead-canada-to-first-world-cup-since-1986',
        'info_dict': {
            'id': '6276033198001',
            'ext': 'mp4',
            'title': 'The Octagon | Can Alphonso Davies lead Canada to first World Cup since 1986?',
            'description': 'md5:f0a883ee33592a0221798f451a98be8f',
            'thumbnail': 'https://cf-images.us-east-1.prod.boltdns.net/v1/static/5530036772001/1bbc44f6-c63c-4981-82fa-46b0c1f891e0/5c1ca44a-a033-4e98-b531-ff24c4947608/160x90/match/image.jpg',
            'duration': 350.165,
            'timestamp': 1633627291,
            'uploader_id': '5530036772001',
            'tags': ['club/canada'],
            'is_live': False,
            'upload_date': '20211007',
            'filesize_approx': 255193528.83200002
        },
        'params': {'skip_download': True}
    }, {
        'url': 'https://www.whitecapsfc.com/video/highlights-san-jose-earthquakes-vs-vancouver-whitecaps-fc-october-23-2021#highlights-san-jose-earthquakes-vs-vancouver-whitecaps-fc-october-23-2021',
        'only_matching': True
    }, {
        'url': 'https://www.torontofc.ca/video/highlights-toronto-fc-vs-cf-montreal-october-23-2021-x6733#highlights-toronto-fc-vs-cf-montreal-october-23-2021-x6733',
        'only_matching': True
    }, {
        'url': 'https://www.sportingkc.com/video/post-match-press-conference-john-pulskamp-oct-27-2021#post-match-press-conference-john-pulskamp-oct-27-2021',
        'only_matching': True
    }, {
        'url': 'https://www.soundersfc.com/video/highlights-seattle-sounders-fc-vs-sporting-kansas-city-october-23-2021',
        'only_matching': True
    }, {
        'url': 'https://www.sjearthquakes.com/video/#highlights-austin-fc-vs-san-jose-earthquakes-june-19-2021',
        'only_matching': True
    }, {
        'url': 'https://www.rsl.com/video/2021-u-of-u-health-mic-d-up-vs-colorado-10-16-21#2021-u-of-u-health-mic-d-up-vs-colorado-10-16-21',
        'only_matching': True
    }, {
        'url': 'https://www.timbers.com/video/highlights-d-chara-asprilla-with-goals-in-portland-timbers-2-0-win-over-san-jose#highlights-d-chara-asprilla-with-goals-in-portland-timbers-2-0-win-over-san-jose',
        'only_matching': True
    }, {
        'url': 'https://www.philadelphiaunion.com/video/highlights-torvphi',
        'only_matching': True
    }, {
        'url': 'https://www.orlandocitysc.com/video/highlight-columbus-crew-vs-orlando-city-sc',
        'only_matching': True
    }, {
        'url': 'https://www.newyorkredbulls.com/video/all-access-matchday-double-derby-week#all-access-matchday-double-derby-week',
        'only_matching': True
    }, {
        'url': 'https://www.nycfc.com/video/highlights-nycfc-1-0-chicago-fire-fc#highlights-nycfc-1-0-chicago-fire-fc',
        'only_matching': True
    }, {
        'url': 'https://www.revolutionsoccer.net/video/two-minute-highlights-revs-1-rapids-0-october-27-2021#two-minute-highlights-revs-1-rapids-0-october-27-2021',
        'only_matching': True
    }, {
        'url': 'https://www.nashvillesc.com/video/goal-c-j-sapong-nashville-sc-92nd-minute',
        'only_matching': True
    }, {
        'url': 'https://www.cfmontreal.com/video/faits-saillants-tor-v-mtl#faits-saillants-orl-v-mtl-x5645',
        'only_matching': True
    }, {
        'url': 'https://www.intermiamicf.com/video/all-access-victory-vs-nashville-sc-by-ukg#all-access-victory-vs-nashville-sc-by-ukg',
        'only_matching': True
    }, {
        'url': 'https://www.lagalaxy.com/video/#moment-of-the-month-presented-by-san-manuel-casino-rayan-raveloson-scores-his-se',
        'only_matching': True
    }, {
        'url': 'https://www.lafc.com/video/breaking-down-lafc-s-final-6-matches-of-the-2021-mls-regular-season#breaking-down-lafc-s-final-6-matches-of-the-2021-mls-regular-season',
        'only_matching': True
    }, {
        'url': 'https://www.houstondynamofc.com/video/postgame-press-conference-michael-nelson-presented-by-coushatta-casino-res-x9660#postgame-press-conference-michael-nelson-presented-by-coushatta-casino-res-x9660',
        'only_matching': True
    }, {
        'url': 'https://www.dcunited.com/video/tony-alfaro-my-family-pushed-me-to-believe-everything-was-possible',
        'only_matching': True
    }, {
        'url': 'https://www.fcdallas.com/video/highlights-fc-dallas-vs-minnesota-united-fc-october-02-2021#highlights-fc-dallas-vs-minnesota-united-fc-october-02-2021',
        'only_matching': True
    }, {
        'url': 'https://www.columbuscrew.com/video/match-rewind-columbus-crew-vs-new-york-red-bulls-october-23-2021',
        'only_matching': True
    }, {
        'url': 'https://www.coloradorapids.com/video/postgame-reaction-robin-fraser-october-27#postgame-reaction-robin-fraser-october-27',
        'only_matching': True
    }, {
        'url': 'https://www.fccincinnati.com/video/#keeping-cincy-chill-presented-by-coors-lite',
        'only_matching': True
    }, {
        'url': 'https://www.chicagofirefc.com/video/all-access-fire-score-dramatic-road-win-in-cincy#all-access-fire-score-dramatic-road-win-in-cincy',
        'only_matching': True
    }, {
        'url': 'https://www.austinfc.com/video/highlights-colorado-rapids-vs-austin-fc-september-29-2021#highlights-colorado-rapids-vs-austin-fc-september-29-2021',
        'only_matching': True
    }, {
        'url': 'https://www.atlutd.com/video/goal-josef-martinez-scores-in-the-73rd-minute#goal-josef-martinez-scores-in-the-73rd-minute',
        'only_matching': True
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        webpage = self._download_webpage(url, id)
        data_json = self._parse_json(self._html_search_regex(r'data-options\=\"([^\"]+)\"', webpage, 'json'), id)['videoList'][0]
        return {
            'id': id,
            '_type': 'url',
            'url': 'https://players.brightcove.net/%s/default_default/index.html?videoId=%s' % (data_json['accountId'], data_json['videoId']),
            'ie_key': 'BrightcoveNew',
        }


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

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

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

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

        title = info['title']

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

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

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


code:
from .common import InfoExtractor
from ..utils import int_or_none, traverse_obj

class MochaVideoIE(InfoExtractor):
    _VALID_URL = r'https?://video.mocha.com.vn/(?P<video_slug>[\w-]+)'
    _TESTS = [{
        'url': 'http://video.mocha.com.vn/chuyen-meo-gia-su-tu-thong-diep-cuoc-song-v18694039',
        'info_dict': {
            'id': '18694039',
            'title': 'Chuyện mèo giả sư tử | Thông điệp cuộc sống',
            'ext': 'mp4',
            'view_count': int,
            'like_count': int,
            'dislike_count': int,
            'display_id': 'chuyen-meo-gia-su-tu-thong-diep-cuoc-song',
            'thumbnail': 'http://mcvideomd1fr.keeng.net/playnow/images/20220505/ad0a055d-2f69-42ca-b888-4790041fe6bc_640x480.jpg',
            'description': '',
            'duration': 70,
            'timestamp': 1652254203,
            'upload_date': '20220511',
            'comment_count': int,
            'categories': ['Kids']
        }
    }]

    def _real_extract(self, url):
        video_slug = self._match_valid_url(url).group('video_slug')
        json_data = self._download_json(
            'http://apivideo.mocha.com.vn:8081/onMediaBackendBiz/mochavideo/getVideoDetail',
            video_slug, query={'url': url, 'token': ''})['data']['videoDetail']
        video_id = str(json_data['id'])
        video_urls = (json_data.get('list_resolution') or []) + [json_data.get('original_path')]

        formats, subtitles = [], {}
        for video in video_urls:
            if isinstance(video, str):
                formats.extend([{'url': video, 'ext': 'mp4'}])
            else:
                fmts, subs = self._extract_m3u8_formats_and_subtitles(
                    video.get('video_path'), video_id, ext='mp4')
                formats.extend(fmts)
                self._merge_subtitles(subs, target=subtitles)

        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': json_data.get('slug') or video_slug,
            'title': json_data.get('name'),
            'formats': formats,
            'subtitles': subtitles,
            'description': json_data.get('description'),
            'duration': json_data.get('durationS'),
            'view_count': json_data.get('total_view'),
            'like_count': json_data.get('total_like'),
            'dislike_count': json_data.get('total_unlike'),
            'thumbnail': json_data.get('image_path_thumb'),
            'timestamp': int_or_none(json_data.get('publish_time'), scale=1000),
            'is_live': json_data.get('isLive'),
            'channel': traverse_obj(json_data, ('channels', '0', 'name')),
            'channel_id': traverse_obj(json_data, ('channels', '0', 'id')),
            'channel_follower_count': traverse_obj(json_data, ('channels', '0', 'numfollow')),
            'categories': traverse_obj(json_data, ('categories', ..., 'categoryname')),
            'comment_count': json_data.get('total_comment'),
        }


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

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

    def _real_extract(self, url):
        host, video_id = self._match_valid_url(url).groups()

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

        title = self._og_search_title(webpage)

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

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


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

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

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

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

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

        return info

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

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


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

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

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

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

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

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

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


code:
from .common import InfoExtractor

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

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

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

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


code:
import datetime
import re

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

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

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

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

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

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

        upload_date = unified_strdate(self._search_regex(
            r'class=["\']count[^>]+>(\d+\s+[a-zA-Z]{3}\s+\d{4})<', webpage,
            'upload date', default=None))
        if not upload_date:
            uploaded_ago = self._search_regex(
                r'>\s*(\d+[hd])\s+[aA]go\b', webpage, 'uploaded ago',
                default=None)
            if uploaded_ago:
                delta = int(uploaded_ago[:-1])
                _AGO_UNITS = {
                    'h': 'hours',
                    'd': 'days',
                }
                kwargs = {_AGO_UNITS.get(uploaded_ago[-1]): delta}
                upload_date = (datetime.datetime.utcnow() - datetime.timedelta(**kwargs)).strftime('%Y%m%d')

        comment_count = webpage.count('class="media-comment-contents"')
        uploader_id = self._html_search_regex(
            (r'"media-meta-member">\s+<a href="/m/([^"]+)"',
             r']+\bclass="username">([^<]+)'),
            webpage, 'uploader_id', fatal=False)
        categories = self._html_search_meta('keywords', webpage, default=None)
        if categories:
            categories = [cat.strip() for cat in categories.split(',')]

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

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

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

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

    def _real_extract(self, url):
        group_id = self._match_id(url)
        page_url = compat_urlparse.urljoin(url, '/gv/%s' % group_id)
        webpage = self._download_webpage(page_url, group_id)
        title = self._search_regex(
            r'<title>([\w\s]+\w)\s+-', webpage, 'title', fatal=False)
        description = self._html_search_meta(
            'description', webpage, fatal=False)
        page_count = self._int(self._search_regex(
            r'(\d+)</(?:a|span)><(?:a|span)[^>]+rel="next">',
            webpage, 'page_count', default=0), 'page_count')
        if not page_count:
            message = self._search_regex(
                r'class="error-page"[^>]*>\s*
]*>\s*(?P<error_msg>[^<]+)(?<=\S)\s*',
                webpage, 'error_msg', default=None) or 'This group has no videos.'
            self.report_warning(message, group_id)
        PAGE_SIZE = 80

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

        playlist = InAdvancePagedList(_get_page, page_count, PAGE_SIZE)

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


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

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

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

        iframe_path = self._html_search_regex(
            r'<iframe id="player_iframe"[^>]+src="([^"]+)"', webpage, 'iframe path', default=None)

        if iframe_path is None:
            iframe_path = self._html_search_regex(
                r'<iframe [^>]*\bsrc="(https://motorsport\.tv/embed/[^"]+)', webpage, 'embed iframe path')
            return self.url_result(iframe_path)

        iframe = self._download_webpage(
            compat_urlparse.urljoin(url, iframe_path), display_id,
            'Downloading iframe')
        youtube_id = self._search_regex(
            r'www.youtube.com/embed/(.{11})', iframe, 'youtube id')

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


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

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

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

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


code:
from .dailymotion import DailymotionIE
from .common import InfoExtractor
from ..utils import (
    parse_iso8601,
    try_get,
)

import re

class MoviepilotIE(InfoExtractor):
    _IE_NAME = 'moviepilot'
    _IE_DESC = 'Moviepilot trailer'
    _VALID_URL = r'https?://(?:www\.)?moviepilot\.de/movies/(?P<id>[^/]+)'

    _TESTS = [{
        'url': 'https://www.moviepilot.de/movies/interstellar-2/',
        'info_dict': {
            'id': 'x7xdut5',
            'display_id': 'interstellar-2',
            'ext': 'mp4',
            'title': 'Interstellar',
            'thumbnail': r're:https://\w+\.dmcdn\.net/v/SaXev1VvzitVZMFsR/x720',
            'timestamp': 1400491705,
            'description': 'md5:7dfc5c1758e7322a7346934f1f0c489c',
            'uploader': 'Moviepilot',
            'like_count': int,
            'view_count': int,
            'uploader_id': 'x6nd9k',
            'upload_date': '20140519',
            'duration': 140,
            'age_limit': 0,
            'tags': ['Alle Trailer', 'Movie', 'Third Party'],
        },
    }, {
        'url': 'https://www.moviepilot.de/movies/interstellar-2/trailer',
        'only_matching': True,
    }, {
        'url': 'https://www.moviepilot.de/movies/interstellar-2/kinoprogramm/berlin',
        'only_matching': True,
    }, {
        'url': 'https://www.moviepilot.de/movies/queen-slim/trailer',
        'info_dict': {
            'id': 'x7xj6o7',
            'display_id': 'queen-slim',
            'title': 'Queen & Slim',
            'ext': 'mp4',
            'thumbnail': r're:https://\w+\.dmcdn\.net/v/SbUM71WtomSjVmI_q/x720',
            'timestamp': 1571838685,
            'description': 'md5:73058bcd030aa12d991e4280d65fbebe',
            'uploader': 'Moviepilot',
            'like_count': int,
            'view_count': int,
            'uploader_id': 'x6nd9k',
            'upload_date': '20191023',
            'duration': 138,
            'age_limit': 0,
            'tags': ['Movie', 'Verleih', 'Neue Trailer'],
        },
    }, {
        'url': 'https://www.moviepilot.de/movies/der-geiger-von-florenz/trailer',
        'info_dict': {
            'id': 'der-geiger-von-florenz',
            'title': 'Der Geiger von Florenz',
            'ext': 'mp4',
        },
        'skip': 'No trailer for this movie.',
    }, {
        'url': 'https://www.moviepilot.de/movies/muellers-buero/',
        'info_dict': {
            'id': 'x7xcw1i',
            'display_id': 'muellers-buero',
            'title': 'Müllers Büro',
            'ext': 'mp4',
            'description': 'md5:57501251c05cdc61ca314b7633e0312e',
            'timestamp': 1287584475,
            'age_limit': 0,
            'duration': 82,
            'upload_date': '20101020',
            'thumbnail': r're:https://\w+\.dmcdn\.net/v/SaMes1WfAm1d6maq_/x720',
            'uploader': 'Moviepilot',
            'like_count': int,
            'view_count': int,
            'tags': ['Alle Trailer', 'Movie', 'Verleih'],
            'uploader_id': 'x6nd9k',
        },
    }]

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

        webpage = self._download_webpage(f'https://www.moviepilot.de/movies/{video_id}/trailer', video_id)

        duration = try_get(
            re.match(r'P(?P<hours>\d+)H(?P<mins>\d+)M(?P<secs>\d+)S',
                     self._html_search_meta('duration', webpage, fatal=False) or ''),
            lambda mobj: sum(float(x) * y for x, y in zip(mobj.groups(), (3600, 60, 1))))
        # _html_search_meta is not used since we don't want name=description to match
        description = self._html_search_regex(
            '<meta[^>]+itemprop="description"[^>]+content="([^>"]+)"', webpage, 'description', fatal=False)

        return {
            '_type': 'url_transparent',
            'ie_key': DailymotionIE.ie_key(),
            'display_id': video_id,
            'title': self._og_search_title(webpage),
            'url': self._html_search_meta('embedURL', webpage),
            'thumbnail': self._html_search_meta('thumbnailURL', webpage),
            'description': description,
            'duration': duration,
            'timestamp': parse_iso8601(self._html_search_meta('uploadDate', webpage), delimiter=' ')
        }


code:
from .jixie import JixieBaseIE

class MoviewPlayIE(JixieBaseIE):
    _VALID_URL = r'https?://www\.moview\.id/play/\d+/(?P<id>[\w-]+)'
    _TESTS = [
        {
            # drm hls, only use direct link
            'url': 'https://www.moview.id/play/174/Candy-Monster',
            'info_dict': {
                'id': '146182',
                'ext': 'mp4',
                'display_id': 'Candy-Monster',
                'uploader_id': 'Mo165qXUUf',
                'duration': 528.2,
                'title': 'Candy Monster',
                'description': 'Mengapa Candy Monster ingin mengambil permen Chloe?',
                'thumbnail': 'https://video.jixie.media/1034/146182/146182_1280x720.jpg',
            }
        }, {
            # non-drm hls
            'url': 'https://www.moview.id/play/75/Paris-Van-Java-Episode-16',
            'info_dict': {
                'id': '28210',
                'ext': 'mp4',
                'duration': 2595.666667,
                'display_id': 'Paris-Van-Java-Episode-16',
                'uploader_id': 'Mo165qXUUf',
                'thumbnail': 'https://video.jixie.media/1003/28210/28210_1280x720.jpg',
                'description': 'md5:2a5e18d98eef9b39d7895029cac96c63',
                'title': 'Paris Van Java Episode 16',
            }
        }
    ]

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

        video_id = self._search_regex(
            r'video_id\s*=\s*"(?P<video_id>[^"]+)', webpage, 'video_id')

        return self._extract_data_from_jixie_id(display_id, video_id, webpage)


code:
from .common import InfoExtractor

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

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

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

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

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

        self._sort_formats(formats)

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


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

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

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

        webpage = self._download_webpage(url, video_id)

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

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

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

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


code:
import re

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

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

    def _real_extract(self, url):
        display_id, page_id = self._match_valid_url(url).groups()

        webpage = self._download_webpage(url, display_id)

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

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

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

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

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

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

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

        return self.playlist_result(entries, page_id)