code:
import functools
import hashlib
import json
import random
import time

from .adobepass import AdobePassIE
from .common import InfoExtractor
from .youtube import YoutubeIE
from ..compat import (
    compat_HTTPError,
    compat_str,
)
from ..utils import (
    clean_html,
    ExtractorError,
    int_or_none,
    OnDemandPagedList,
    parse_age_limit,
    str_or_none,
    try_get,
)

class ViceBaseIE(InfoExtractor):
    def _call_api(self, resource, resource_key, resource_id, locale, fields, args=''):
        return self._download_json(
            'https://video.vice.com/api/v1/graphql', resource_id, query={
                'query': '''{
  %s(locale: "%s", %s: "%s"%s) {
    %s
  }
}''' % (resource, locale, resource_key, resource_id, args, fields),
            })['data'][resource]

class ViceIE(ViceBaseIE, AdobePassIE):
    IE_NAME = 'vice'
    _VALID_URL = r'https?://(?:(?:video|vms)\.vice|(?:www\.)?vice(?:land|tv))\.com/(?P<locale>[^/]+)/(?:video/[^/]+|embed)/(?P<id>[\da-f]{24})'
    _EMBED_REGEX = [r'<iframe\b[^>]+\bsrc=["\'](?P<url>(?:https?:)?//video\.vice\.com/[^/]+/embed/[\da-f]{24})']
    _TESTS = [{
        'url': 'https://video.vice.com/en_us/video/pet-cremator/58c69e38a55424f1227dc3f7',
        'info_dict': {
            'id': '58c69e38a55424f1227dc3f7',
            'ext': 'mp4',
            'title': '10 Questions You Always Wanted To Ask: Pet Cremator',
            'description': 'md5:fe856caacf61fe0e74fab15ce2b07ca5',
            'uploader': 'vice',
            'uploader_id': '57a204088cb727dec794c67b',
            'timestamp': 1489664942,
            'upload_date': '20170316',
            'age_limit': 14,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        # geo restricted to US
        'url': 'https://video.vice.com/en_us/video/the-signal-from-tolva/5816510690b70e6c5fd39a56',
        'info_dict': {
            'id': '5816510690b70e6c5fd39a56',
            'ext': 'mp4',
            'uploader': 'vice',
            'title': 'The Signal From Tölva',
            'description': 'md5:3927e3c79f9e8094606a2b3c5b5e55d5',
            'uploader_id': '57a204088cb727dec794c67b',
            'timestamp': 1477941983,
            'upload_date': '20161031',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'https://video.vice.com/alps/video/ulfs-wien-beruchtigste-grafitti-crew-part-1/581b12b60a0e1f4c0fb6ea2f',
        'info_dict': {
            'id': '581b12b60a0e1f4c0fb6ea2f',
            'ext': 'mp4',
            'title': 'ULFs - Wien berüchtigste Grafitti Crew - Part 1',
            'description': 'Zwischen Hinterzimmer-Tattoos und U-Bahnschächten erzählen uns die Ulfs, wie es ist, "süchtig nach Sachbeschädigung" zu sein.',
            'uploader': 'vice',
            'uploader_id': '57a204088cb727dec794c67b',
            'timestamp': 1485368119,
            'upload_date': '20170125',
            'age_limit': 14,
        },
        'params': {
            # AES-encrypted m3u8
            'skip_download': True,
        },
    }, {
        'url': 'https://video.vice.com/en_us/video/pizza-show-trailer/56d8c9a54d286ed92f7f30e4',
        'only_matching': True,
    }, {
        'url': 'https://video.vice.com/en_us/embed/57f41d3556a0a80f54726060',
        'only_matching': True,
    }, {
        'url': 'https://vms.vice.com/en_us/video/preplay/58c69e38a55424f1227dc3f7',
        'only_matching': True,
    }, {
        'url': 'https://www.viceland.com/en_us/video/thursday-march-1-2018/5a8f2d7ff1cdb332dd446ec1',
        'only_matching': True,
    }]

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

        video = self._call_api('videos', 'id', video_id, locale, '''body
    locked
    rating
    thumbnail_url
    title''')[0]
        title = video['title'].strip()
        rating = video.get('rating')

        query = {}
        if video.get('locked'):
            resource = self._get_mvpd_resource(
                'VICELAND', title, video_id, rating)
            query['tvetoken'] = self._extract_mvpd_auth(
                url, video_id, 'VICELAND', resource)

        # signature generation algorithm is reverse engineered from signatureGenerator in
        # webpack:///../shared/~/vice-player/dist/js/vice-player.js in
        # https://www.viceland.com/assets/common/js/web.vendor.bundle.js
        # new JS is located here https://vice-web-statics-cdn.vice.com/vice-player/player-embed.js
        exp = int(time.time()) + 1440

        query.update({
            'exp': exp,
            'sign': hashlib.sha512(('%s:GET:%d' % (video_id, exp)).encode()).hexdigest(),
            'skipadstitching': 1,
            'platform': 'desktop',
            'rn': random.randint(10000, 100000),
        })

        try:
            preplay = self._download_json(
                'https://vms.vice.com/%s/video/preplay/%s' % (locale, video_id),
                video_id, query=query)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code in (400, 401):
                error = json.loads(e.cause.read().decode())
                error_message = error.get('error_description') or error['details']
                raise ExtractorError('%s said: %s' % (
                    self.IE_NAME, error_message), expected=True)
            raise

        video_data = preplay['video']
        formats = self._extract_m3u8_formats(
            preplay['playURL'], video_id, 'mp4', 'm3u8_native')
        self._sort_formats(formats)
        episode = video_data.get('episode') or {}
        channel = video_data.get('channel') or {}
        season = video_data.get('season') or {}

        subtitles = {}
        for subtitle in preplay.get('subtitleURLs', []):
            cc_url = subtitle.get('url')
            if not cc_url:
                continue
            language_code = try_get(subtitle, lambda x: x['languages'][0]['language_code'], compat_str) or 'en'
            subtitles.setdefault(language_code, []).append({
                'url': cc_url,
            })

        return {
            'formats': formats,
            'id': video_id,
            'title': title,
            'description': clean_html(video.get('body')),
            'thumbnail': video.get('thumbnail_url'),
            'duration': int_or_none(video_data.get('video_duration')),
            'timestamp': int_or_none(video_data.get('created_at'), 1000),
            'age_limit': parse_age_limit(video_data.get('video_rating') or rating),
            'series': try_get(video_data, lambda x: x['show']['base']['display_title'], compat_str),
            'episode_number': int_or_none(episode.get('episode_number')),
            'episode_id': str_or_none(episode.get('id') or video_data.get('episode_id')),
            'season_number': int_or_none(season.get('season_number')),
            'season_id': str_or_none(season.get('id') or video_data.get('season_id')),
            'uploader': channel.get('name'),
            'uploader_id': str_or_none(channel.get('id')),
            'subtitles': subtitles,
        }

class ViceShowIE(ViceBaseIE):
    IE_NAME = 'vice:show'
    _VALID_URL = r'https?://(?:video\.vice|(?:www\.)?vice(?:land|tv))\.com/(?P<locale>[^/]+)/show/(?P<id>[^/?#&]+)'
    _PAGE_SIZE = 25
    _TESTS = [{
        'url': 'https://video.vice.com/en_us/show/fck-thats-delicious',
        'info_dict': {
            'id': '57a2040c8cb727dec794c901',
            'title': 'F*ck, That’s Delicious',
            'description': 'The life and eating habits of rap’s greatest bon vivant, Action Bronson.',
        },
        'playlist_mincount': 64,
    }, {
        'url': 'https://www.vicetv.com/en_us/show/fck-thats-delicious',
        'only_matching': True,
    }]

    def _fetch_page(self, locale, show_id, page):
        videos = self._call_api('videos', 'show_id', show_id, locale, '''body
    id
    url''', ', page: %d, per_page: %d' % (page + 1, self._PAGE_SIZE))
        for video in videos:
            yield self.url_result(
                video['url'], ViceIE.ie_key(), video.get('id'))

    def _real_extract(self, url):
        locale, display_id = self._match_valid_url(url).groups()
        show = self._call_api('shows', 'slug', display_id, locale, '''dek
    id
    title''')[0]
        show_id = show['id']

        entries = OnDemandPagedList(
            functools.partial(self._fetch_page, locale, show_id),
            self._PAGE_SIZE)

        return self.playlist_result(
            entries, show_id, show.get('title'), show.get('dek'))

class ViceArticleIE(ViceBaseIE):
    IE_NAME = 'vice:article'
    _VALID_URL = r'https://(?:www\.)?vice\.com/(?P<locale>[^/]+)/article/(?:[0-9a-z]{6}/)?(?P<id>[^?#]+)'

    _TESTS = [{
        'url': 'https://www.vice.com/en_us/article/on-set-with-the-woman-making-mormon-porn-in-utah',
        'info_dict': {
            'id': '58dc0a3dee202d2a0ccfcbd8',
            'ext': 'mp4',
            'title': 'Mormon War on Porn',
            'description': 'md5:1c5d91fe25fa8aa304f9def118b92dbf',
            'uploader': 'vice',
            'uploader_id': '57a204088cb727dec794c67b',
            'timestamp': 1491883129,
            'upload_date': '20170411',
            'age_limit': 17,
        },
        'params': {
            # AES-encrypted m3u8
            'skip_download': True,
        },
        'add_ie': [ViceIE.ie_key()],
    }, {
        'url': 'https://www.vice.com/en_us/article/how-to-hack-a-car',
        'md5': '13010ee0bc694ea87ec40724397c2349',
        'info_dict': {
            'id': '3jstaBeXgAs',
            'ext': 'mp4',
            'title': 'How to Hack a Car: Phreaked Out (Episode 2)',
            'description': 'md5:ee95453f7ff495db8efe14ae8bf56f30',
            'uploader': 'Motherboard',
            'uploader_id': 'MotherboardTV',
            'upload_date': '20140529',
        },
        'add_ie': [YoutubeIE.ie_key()],
    }, {
        'url': 'https://www.vice.com/en_us/article/znm9dx/karley-sciortino-slutever-reloaded',
        'md5': 'a7ecf64ee4fa19b916c16f4b56184ae2',
        'info_dict': {
            'id': '57f41d3556a0a80f54726060',
            'ext': 'mp4',
            'title': "Making The World's First Male Sex Doll",
            'description': 'md5:19b00b215b99961cf869c40fbe9df755',
            'uploader': 'vice',
            'uploader_id': '57a204088cb727dec794c67b',
            'timestamp': 1476919911,
            'upload_date': '20161019',
            'age_limit': 17,
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': [ViceIE.ie_key()],
    }, {
        'url': 'https://www.vice.com/en_us/article/cowboy-capitalists-part-1',
        'only_matching': True,
    }, {
        'url': 'https://www.vice.com/ru/article/big-night-out-ibiza-clive-martin-229',
        'only_matching': True,
    }]

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

        article = self._call_api('articles', 'slug', display_id, locale, '''body
    embed_code''')[0]
        body = article['body']

        def _url_res(video_url, ie_key):
            return {
                '_type': 'url_transparent',
                'url': video_url,
                'display_id': display_id,
                'ie_key': ie_key,
            }

        vice_url = ViceIE._extract_url(body)
        if vice_url:
            return _url_res(vice_url, ViceIE.ie_key())

        embed_code = self._search_regex(
            r'embedCode=([^&\'"]+)', body,
            'ooyala embed code', default=None)
        if embed_code:
            return _url_res('ooyala:%s' % embed_code, 'Ooyala')

        youtube_url = YoutubeIE._extract_url(body)
        if youtube_url:
            return _url_res(youtube_url, YoutubeIE.ie_key())

        video_url = self._html_search_regex(
            r'data-video-url="([^"]+)"',
            article['embed_code'], 'video URL')

        return _url_res(video_url, ViceIE.ie_key())


code:
from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    int_or_none,
    js_to_json,
    remove_end,
    unified_strdate,
)

class VidbitIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?vidbit\.co/(?:watch|embed)\?.*?\bv=(?P<id>[\da-zA-Z]+)'
    _TESTS = [{
        'url': 'http://www.vidbit.co/watch?v=jkL2yDOEq2',
        'md5': '1a34b7f14defe3b8fafca9796892924d',
        'info_dict': {
            'id': 'jkL2yDOEq2',
            'ext': 'mp4',
            'title': 'Intro to VidBit',
            'description': 'md5:5e0d6142eec00b766cbf114bfd3d16b7',
            'thumbnail': r're:https?://.*\.jpg$',
            'upload_date': '20160618',
            'view_count': int,
            'comment_count': int,
        }
    }, {
        'url': 'http://www.vidbit.co/embed?v=jkL2yDOEq2&auto=0&water=0',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            compat_urlparse.urljoin(url, '/watch?v=%s' % video_id), video_id)

        video_url, title = [None] * 2

        config = self._parse_json(self._search_regex(
            r'(?s)\.setup\(({.+?})\);', webpage, 'setup', default='{}'),
            video_id, transform_source=js_to_json)
        if config:
            if config.get('file'):
                video_url = compat_urlparse.urljoin(url, config['file'])
            title = config.get('title')

        if not video_url:
            video_url = compat_urlparse.urljoin(url, self._search_regex(
                r'file\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1',
                webpage, 'video URL', group='url'))

        if not title:
            title = remove_end(
                self._html_search_regex(
                    (r'
(.+?)
', r'<title>(.+?)</title>'),
                    webpage, 'title', default=None) or self._og_search_title(webpage),
                ' - VidBit')

        description = self._html_search_meta(
            ('description', 'og:description', 'twitter:description'),
            webpage, 'description')

        upload_date = unified_strdate(self._html_search_meta(
            'datePublished', webpage, 'upload date'))

        view_count = int_or_none(self._search_regex(
            r'(\d+) views',
            webpage, 'view count', fatal=False))
        comment_count = int_or_none(self._search_regex(
            r'id=["\']cmt_num["\'][^>]*>\((\d+)\)',
            webpage, 'comment count', fatal=False))

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

repo_name: Lesmiscore/ytdl


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

class ViddlerIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?viddler\.com/(?:v|embed|player)/(?P<id>[a-z0-9]+)(?:.+?\bsecret=(\d+))?'
    _EMBED_REGEX = [r'<(?:iframe[^>]+?src|param[^>]+?value)=(["\'])(?P<url>(?:https?:)?//(?:www\.)?viddler\.com/(?:embed|player)/.+?)\1']

    _TESTS = [{
        'url': 'http://www.viddler.com/v/43903784',
        'md5': '9eee21161d2c7f5b39690c3e325fab2f',
        'info_dict': {
            'id': '43903784',
            'ext': 'mov',
            'title': 'Video Made Easy',
            'description': 'md5:6a697ebd844ff3093bd2e82c37b409cd',
            'uploader': 'viddler',
            'timestamp': 1335371429,
            'upload_date': '20120425',
            'duration': 100.89,
            'thumbnail': r're:^https?://.*\.jpg$',
            'view_count': int,
            'comment_count': int,
            'categories': ['video content', 'high quality video', 'video made easy', 'how to produce video with limited resources', 'viddler'],
        }
    }, {
        'url': 'http://www.viddler.com/v/4d03aad9/',
        'md5': 'f12c5a7fa839c47a79363bfdf69404fb',
        'info_dict': {
            'id': '4d03aad9',
            'ext': 'ts',
            'title': 'WALL-TO-GORTAT',
            'upload_date': '20150126',
            'uploader': 'deadspin',
            'timestamp': 1422285291,
            'view_count': int,
            'comment_count': int,
        }
    }, {
        'url': 'http://www.viddler.com/player/221ebbbd/0/',
        'md5': '740511f61d3d1bb71dc14a0fe01a1c10',
        'info_dict': {
            'id': '221ebbbd',
            'ext': 'mov',
            'title': 'LETeens-Grammar-snack-third-conditional',
            'description': ' ',
            'upload_date': '20140929',
            'uploader': 'BCLETeens',
            'timestamp': 1411997190,
            'view_count': int,
            'comment_count': int,
        }
    }, {
        # secret protected
        'url': 'http://www.viddler.com/v/890c0985?secret=34051570',
        'info_dict': {
            'id': '890c0985',
            'ext': 'mp4',
            'title': 'Complete Property Training - Traineeships',
            'description': ' ',
            'upload_date': '20130606',
            'uploader': 'TiffanyBowtell',
            'timestamp': 1370496993,
            'view_count': int,
            'comment_count': int,
        },
        'params': {
            'skip_download': True,
        },
    }]

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

        query = {
            'video_id': video_id,
            'key': 'v0vhrt7bg2xq1vyxhkct',
        }
        if secret:
            query['secret'] = secret

        data = self._download_json(
            'http://api.viddler.com/api/v2/viddler.videos.getPlaybackDetails.json',
            video_id, headers={'Referer': url}, query=query)['video']

        formats = []
        for filed in data['files']:
            if filed.get('status', 'ready') != 'ready':
                continue
            format_id = filed.get('profile_id') or filed['profile_name']
            f = {
                'format_id': format_id,
                'format_note': filed['profile_name'],
                'url': self._proto_relative_url(filed['url']),
                'width': int_or_none(filed.get('width')),
                'height': int_or_none(filed.get('height')),
                'filesize': int_or_none(filed.get('size')),
                'ext': filed.get('ext'),
                'source_preference': -1,
            }
            formats.append(f)

            if filed.get('cdn_url'):
                f = f.copy()
                f['url'] = self._proto_relative_url(filed['cdn_url'], 'http:')
                f['format_id'] = format_id + '-cdn'
                f['source_preference'] = 1
                formats.append(f)

            if filed.get('html5_video_source'):
                f = f.copy()
                f['url'] = self._proto_relative_url(filed['html5_video_source'])
                f['format_id'] = format_id + '-html5'
                f['source_preference'] = 0
                formats.append(f)
        self._sort_formats(formats)

        categories = [
            t.get('text') for t in data.get('tags', []) if 'text' in t]

        return {
            'id': video_id,
            'title': data['title'],
            'formats': formats,
            'description': data.get('description'),
            'timestamp': int_or_none(data.get('upload_time')),
            'thumbnail': self._proto_relative_url(data.get('thumbnail_url')),
            'uploader': data.get('author'),
            'duration': float_or_none(data.get('length')),
            'view_count': int_or_none(data.get('view_count')),
            'comment_count': int_or_none(data.get('comment_count')),
            'categories': categories,
        }


code:
import random
import string
import struct

from .common import InfoExtractor
from ..compat import compat_b64decode, compat_ord
from ..utils import (
    ExtractorError,
    int_or_none,
    mimetype2ext,
    parse_codecs,
    parse_qs,
    update_url_query,
    urljoin,
    xpath_element,
    xpath_text,
)

class VideaIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        videa(?:kid)?\.hu/
                        (?:
                            videok/(?:[^/]+/)*[^?#&]+-|
                            (?:videojs_)?player\?.*?\bv=|
                            player/v/
                        )
                        (?P<id>[^?#&]+)
                    '''
    _EMBED_REGEX = [r'<iframe[^>]+src=(["\'])(?P<url>(?:https?:)?//videa\.hu/player\?.*?\bv=.+?)\1']
    _TESTS = [{
        'url': 'http://videa.hu/videok/allatok/az-orult-kigyasz-285-kigyot-kigyo-8YfIAjxwWGwT8HVQ',
        'md5': '97a7af41faeaffd9f1fc864a7c7e7603',
        'info_dict': {
            'id': '8YfIAjxwWGwT8HVQ',
            'ext': 'mp4',
            'title': 'Az őrült kígyász 285 kígyót enged szabadon',
            'thumbnail': r're:^https?://.*',
            'duration': 21,
        },
    }, {
        'url': 'http://videa.hu/videok/origo/jarmuvek/supercars-elozes-jAHDWfWSJH5XuFhH',
        'md5': 'd57ccd8812c7fd491d33b1eab8c99975',
        'info_dict': {
            'id': 'jAHDWfWSJH5XuFhH',
            'ext': 'mp4',
            'title': 'Supercars előzés',
            'thumbnail': r're:^https?://.*',
            'duration': 64,
        },
    }, {
        'url': 'http://videa.hu/player?v=8YfIAjxwWGwT8HVQ',
        'md5': '97a7af41faeaffd9f1fc864a7c7e7603',
        'info_dict': {
            'id': '8YfIAjxwWGwT8HVQ',
            'ext': 'mp4',
            'title': 'Az őrült kígyász 285 kígyót enged szabadon',
            'thumbnail': r're:^https?://.*',
            'duration': 21,
        },
    }, {
        'url': 'http://videa.hu/player/v/8YfIAjxwWGwT8HVQ?autoplay=1',
        'only_matching': True,
    }, {
        'url': 'https://videakid.hu/videok/origo/jarmuvek/supercars-elozes-jAHDWfWSJH5XuFhH',
        'only_matching': True,
    }, {
        'url': 'https://videakid.hu/player?v=8YfIAjxwWGwT8HVQ',
        'only_matching': True,
    }, {
        'url': 'https://videakid.hu/player/v/8YfIAjxwWGwT8HVQ?autoplay=1',
        'only_matching': True,
    }]
    _STATIC_SECRET = 'xHb0ZvME5q8CBcoQi6AngerDu3FGO9fkUlwPmLVY_RTzj2hJIS4NasXWKy1td7p'

    @staticmethod
    def rc4(cipher_text, key):
        res = b''

        key_len = len(key)
        S = list(range(256))

        j = 0
        for i in range(256):
            j = (j + S[i] + ord(key[i % key_len])) % 256
            S[i], S[j] = S[j], S[i]

        i = 0
        j = 0
        for m in range(len(cipher_text)):
            i = (i + 1) % 256
            j = (j + S[i]) % 256
            S[i], S[j] = S[j], S[i]
            k = S[(S[i] + S[j]) % 256]
            res += struct.pack('B', k ^ compat_ord(cipher_text[m]))

        return res.decode()

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

        if 'videa.hu/player' in url:
            player_url = url
            player_page = video_page
        else:
            player_url = self._search_regex(
                r'<iframe.*?src="(/player\?[^"]+)"', video_page, 'player url')
            player_url = urljoin(url, player_url)
            player_page = self._download_webpage(player_url, video_id)

        nonce = self._search_regex(
            r'_xt\s*=\s*"([^"]+)"', player_page, 'nonce')
        l = nonce[:32]
        s = nonce[32:]
        result = ''
        for i in range(0, 32):
            result += s[i - (self._STATIC_SECRET.index(l[i]) - 31)]

        query = parse_qs(player_url)
        random_seed = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(8))
        query['_s'] = random_seed
        query['_t'] = result[:16]

        b64_info, handle = self._download_webpage_handle(
            'http://videa.hu/videaplayer_get_xml.php', video_id, query=query)
        if b64_info.startswith('<?xml'):
            info = self._parse_xml(b64_info, video_id)
        else:
            key = result[16:] + random_seed + handle.headers['x-videa-xs']
            info = self._parse_xml(self.rc4(
                compat_b64decode(b64_info), key), video_id)

        video = xpath_element(info, './video', 'video')
        if video is None:
            raise ExtractorError(xpath_element(
                info, './error', fatal=True), expected=True)
        sources = xpath_element(
            info, './video_sources', 'sources', fatal=True)
        hash_values = xpath_element(
            info, './hash_values', 'hash values', fatal=False)

        title = xpath_text(video, './title', fatal=True)

        formats = []
        for source in sources.findall('./video_source'):
            source_url = source.text
            source_name = source.get('name')
            source_exp = source.get('exp')
            if not (source_url and source_name):
                continue
            hash_value = (
                xpath_text(hash_values, 'hash_value_' + source_name)
                if hash_values is not None else None)
            if hash_value and source_exp:
                source_url = update_url_query(source_url, {
                    'md5': hash_value,
                    'expires': source_exp,
                })
            f = parse_codecs(source.get('codecs'))
            f.update({
                'url': self._proto_relative_url(source_url),
                'ext': mimetype2ext(source.get('mimetype')) or 'mp4',
                'format_id': source.get('name'),
                'width': int_or_none(source.get('width')),
                'height': int_or_none(source.get('height')),
            })
            formats.append(f)
        self._sort_formats(formats)

        thumbnail = self._proto_relative_url(xpath_text(video, './poster_src'))

        age_limit = None
        is_adult = xpath_text(video, './is_adult_content', default=None)
        if is_adult:
            age_limit = 18 if is_adult == '1' else 0

        return {
            'id': video_id,
            'title': title,
            'thumbnail': thumbnail,
            'duration': int_or_none(xpath_text(video, './duration')),
            'age_limit': age_limit,
            'formats': formats,
        }


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

import re

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

class VideobinBaseIE(InfoExtractor):
    IE_DESC = False  # Do not list

class VideobinIE(VideobinBaseIE):
    IE_NAME = 'videobin'
    _VALID_URL = r'https?:\/\/(?:www\.)?videobin\.co/(?P<id>[a-z0-9]{10,})'
    _TEST = {}
    _URL_REGEX = re.compile(r'(?ms)sources:\s*(\[\s*"[^"]+"\s*(?:,\s*"[^"]*"\s*)*])\s*,')

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

        title = 'Watch mp4'

        entries = self._parse_json(self._URL_REGEX.search(webpage).group(1), video_id)
        if not entries:
            raise ExtractorError('Video is unavailable for some reasons', expected=True)
        entry = {
            'formats': []
        }
        for url in entries:
            entry['formats'] += self._media_formats(url, 'video', video_id)
        self._sort_formats(entry['formats'])
        entry.update({
            'id': video_id,
            'title': title,
        })
        return entry

    def _media_formats(self, full_url, cur_media_type, video_id):
        ext = determine_ext(full_url)
        if ext == 'm3u8':
            formats = self._extract_m3u8_formats(
                full_url, video_id, ext='mp4', fatal=False)
        elif ext == 'mpd':
            formats = self._extract_mpd_formats(
                full_url, video_id, fatal=False)
        else:
            formats = [{
                'url': full_url,
                'vcodec': 'none' if cur_media_type == 'audio' else None,
            }]
        return formats


code:
import functools
import re

from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import ExtractorError, OnDemandPagedList, urlencode_postdata

class VideocampusSachsenIE(InfoExtractor):
    IE_NAME = 'ViMP'
    _INSTANCES = (
        'bergauf.tv',
        'campus.demo.vimp.com',
        'corporate.demo.vimp.com',
        'dancehalldatabase.com',
        'drehzahl.tv',
        'educhannel.hs-gesundheit.de',
        'emedia.ls.haw-hamburg.de',
        'globale-evolution.net',
        'hohu.tv',
        'htvideos.hightechhigh.org',
        'k210039.vimp.mivitec.net',
        'media.cmslegal.com',
        'media.hs-furtwangen.de',
        'media.hwr-berlin.de',
        'mediathek.dkfz.de',
        'mediathek.htw-berlin.de',
        'mediathek.polizei-bw.de',
        'medien.hs-merseburg.de',
        'mportal.europa-uni.de',
        'pacific.demo.vimp.com',
        'slctv.com',
        'streaming.prairiesouth.ca',
        'tube.isbonline.cn',
        'univideo.uni-kassel.de',
        'ursula2.genetics.emory.edu',
        'ursulablicklevideoarchiv.com',
        'v.agrarumweltpaedagogik.at',
        'video.eplay-tv.de',
        'video.fh-dortmund.de',
        'video.hs-offenburg.de',
        'video.hs-pforzheim.de',
        'video.hspv.nrw.de',
        'video.irtshdf.fr',
        'video.pareygo.de',
        'video.tu-freiberg.de',
        'videocampus.sachsen.de',
        'videoportal.uni-freiburg.de',
        'videoportal.vm.uni-freiburg.de',
        'videos.duoc.cl',
        'videos.uni-paderborn.de',
        'vimp-bemus.udk-berlin.de',
        'vimp.aekwl.de',
        'vimp.hs-mittweida.de',
        'vimp.oth-regensburg.de',
        'vimp.ph-heidelberg.de',
        'vimp.sma-events.com',
        'vimp.weka-fachmedien.de',
        'webtv.univ-montp3.fr',
        'www.b-tu.de/media',
        'www.bergauf.tv',
        'www.bigcitytv.de',
        'www.cad-videos.de',
        'www.drehzahl.tv',
        'www.fh-bielefeld.de/medienportal',
        'www.hohu.tv',
        'www.orvovideo.com',
        'www.rwe.tv',
        'www.salzi.tv',
        'www.wenglor-media.com',
        'www2.univ-sba.dz',
    )
    _VALID_URL = r'''(?x)https?://(?P<host>%s)/(?:
        m/(?P<tmp_id>[0-9a-f]+)|
        (?:category/)?video/(?P<display_id>[\w-]+)/(?P<id>[0-9a-f]{32})|
        media/embed.*(?:\?|&)key=(?P<embed_id>[0-9a-f]{32}&?)
    )''' % ('|'.join(map(re.escape, _INSTANCES)))

    _TESTS = [
        {
            'url': 'https://videocampus.sachsen.de/m/e0d6c8ce6e394c188f1342f1ab7c50ed6fc4490b808699801def5cb2e46d76ca7367f622a9f516c542ffb805b24d6b643bd7c81f385acaac4c59081b87a2767b',
            'info_dict': {
                'id': 'e6b9349905c1628631f175712250f2a1',
                'title': 'Konstruktiver Entwicklungsprozess Vorlesung 7',
                'description': 'Konstruktiver Entwicklungsprozess Vorlesung 7',
                'thumbnail': 'https://videocampus.sachsen.de/cache/1a985379ad3aecba8097a6902c7daa4e.jpg',
                'ext': 'mp4',
            },
        },
        {
            'url': 'https://videocampus.sachsen.de/video/Was-ist-selbstgesteuertes-Lernen/fc99c527e4205b121cb7c74433469262',
            'info_dict': {
                'id': 'fc99c527e4205b121cb7c74433469262',
                'title': 'Was ist selbstgesteuertes Lernen?',
                'description': 'md5:196aa3b0509a526db62f84679522a2f5',
                'thumbnail': 'https://videocampus.sachsen.de/cache/6f4a85096ba24cb398e6ce54446b57ae.jpg',
                'display_id': 'Was-ist-selbstgesteuertes-Lernen',
                'ext': 'mp4',
            },
        },
        {
            'url': 'https://videocampus.sachsen.de/category/video/Tutorial-zur-Nutzung-von-Adobe-Connect-aus-Veranstalter-Sicht/09d4ed029002eb1bdda610f1103dd54c/100',
            'info_dict': {
                'id': '09d4ed029002eb1bdda610f1103dd54c',
                'title': 'Tutorial zur Nutzung von Adobe Connect aus Veranstalter-Sicht',
                'description': 'md5:3d379ca3cc17b9da6784d7f58cca4d58',
                'thumbnail': 'https://videocampus.sachsen.de/cache/2452498fe8c2d5a7dc79a05d30f407b6.jpg',
                'display_id': 'Tutorial-zur-Nutzung-von-Adobe-Connect-aus-Veranstalter-Sicht',
                'ext': 'mp4',
            },
        },
        {
            'url': 'https://www2.univ-sba.dz/video/Presentation-de-la-Faculte-de-droit-et-des-sciences-politiques-Journee-portes-ouvertes-202122/0183356e41af7bfb83d7667b20d9b6a3',
            'info_dict': {
                'url': 'https://www2.univ-sba.dz/getMedium/0183356e41af7bfb83d7667b20d9b6a3.mp4',
                'id': '0183356e41af7bfb83d7667b20d9b6a3',
                'title': 'Présentation de la Faculté de droit et des sciences politiques - Journée portes ouvertes 2021/22',
                'description': 'md5:508958bd93e0ca002ac731d94182a54f',
                'thumbnail': 'https://www2.univ-sba.dz/cache/4d5d4a0b4189271a8cc6cb5328e14769.jpg',
                'display_id': 'Presentation-de-la-Faculte-de-droit-et-des-sciences-politiques-Journee-portes-ouvertes-202122',
                'ext': 'mp4',
            }
        },
        {
            'url': 'https://vimp.weka-fachmedien.de/video/Preisverleihung-Produkte-des-Jahres-2022/c8816f1cc942c12b6cce57c835cffd7c',
            'info_dict': {
                'id': 'c8816f1cc942c12b6cce57c835cffd7c',
                'title': 'Preisverleihung »Produkte des Jahres 2022«',
                'description': 'md5:60c347568ca89aa25b772c4ea564ebd3',
                'thumbnail': 'https://vimp.weka-fachmedien.de/cache/da9f3090e9227b25beacf67ccf94de14.png',
                'display_id': 'Preisverleihung-Produkte-des-Jahres-2022',
                'ext': 'mp4',
            },
        },
        {
            'url': 'https://videocampus.sachsen.de/media/embed?key=fc99c527e4205b121cb7c74433469262',
            'info_dict': {
                'id': 'fc99c527e4205b121cb7c74433469262',
                'title': 'Was ist selbstgesteuertes Lernen?',
                'ext': 'mp4',
            },
        },
    ]

    def _real_extract(self, url):
        host, video_id, tmp_id, display_id, embed_id = self._match_valid_url(url).group(
            'host', 'id', 'tmp_id', 'display_id', 'embed_id')
        webpage = self._download_webpage(url, video_id or tmp_id, fatal=False) or ''

        if not video_id:
            video_id = embed_id or self._html_search_regex(
                rf'src="https?://{host}/media/embed.*(?:\?|&)key=([0-9a-f]+)&?',
                webpage, 'video_id')

        if not (display_id or tmp_id):
            # Title, description from embedded page's meta wouldn't be correct
            title = self._html_search_regex(r'<video-js[^>]* data-piwik-title="([^"<]+)"', webpage, 'title', fatal=False)
            description = None
            thumbnail = None
        else:
            title = self._html_search_meta(('og:title', 'twitter:title', 'title'), webpage, fatal=False)
            description = self._html_search_meta(
                ('og:description', 'twitter:description', 'description'), webpage, fatal=False)
            thumbnail = self._html_search_meta(('og:image', 'twitter:image'), webpage, fatal=False)

        formats, subtitles = [], {}
        try:
            formats, subtitles = self._extract_m3u8_formats_and_subtitles(
                f'https://{host}/media/hlsMedium/key/{video_id}/format/auto/ext/mp4/learning/0/path/m3u8',
                video_id, 'mp4', m3u8_id='hls', fatal=True)
        except ExtractorError as e:
            if not isinstance(e.cause, compat_HTTPError) or e.cause.code not in (404, 500):
                raise

        formats.append({'url': f'https://{host}/getMedium/{video_id}.mp4'})
        self._sort_formats(formats)

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

class ViMPPlaylistIE(InfoExtractor):
    IE_NAME = 'ViMP:Playlist'
    _VALID_URL = r'''(?x)(?P<host>https?://(?:%s))/(?:
        album/view/aid/(?P<album_id>[0-9]+)|
        (?P<mode>category|channel)/(?P<name>[\w-]+)/(?P<id>[0-9]+)
    )''' % '|'.join(map(re.escape, VideocampusSachsenIE._INSTANCES))

    _TESTS = [{
        'url': 'https://vimp.oth-regensburg.de/channel/Designtheorie-1-SoSe-2020/3',
        'info_dict': {
            'id': 'channel-3',
            'title': 'Designtheorie 1 SoSe 2020 :: Channels :: ViMP OTH Regensburg',
        },
        'playlist_mincount': 9,
    }, {
        'url': 'https://www.fh-bielefeld.de/medienportal/album/view/aid/208',
        'info_dict': {
            'id': 'album-208',
            'title': 'KG Praktikum ABT/MEC :: Playlists :: FH-Medienportal',
        },
        'playlist_mincount': 4,
    }, {
        'url': 'https://videocampus.sachsen.de/category/online-tutorials-onyx/91',
        'info_dict': {
            'id': 'category-91',
            'title': 'Online-Seminare ONYX - BPS - Bildungseinrichtungen - VCS',
        },
        'playlist_mincount': 7,
    }]
    _PAGE_SIZE = 10

    def _fetch_page(self, host, url_part, id, data, page):
        webpage = self._download_webpage(
            f'{host}/media/ajax/component/boxList/{url_part}', id,
            query={'page': page, 'page_only': 1}, data=urlencode_postdata(data))
        urls = re.findall(r'"([^"]+/video/[^"]+)"', webpage)

        for url in urls:
            yield self.url_result(host + url, VideocampusSachsenIE)

    def _real_extract(self, url):
        host, album_id, mode, name, id = self._match_valid_url(url).group(
            'host', 'album_id', 'mode', 'name', 'id')

        webpage = self._download_webpage(url, album_id or id, fatal=False) or ''
        title = (self._html_search_meta('title', webpage, fatal=False)
                 or self._html_extract_title(webpage))

        url_part = (f'aid/{album_id}' if album_id
                    else f'category/{name}/category_id/{id}' if mode == 'category'
                    else f'title/{name}/channel/{id}')

        mode = mode or 'album'
        data = {
            'vars[mode]': mode,
            f'vars[{mode}]': album_id or id,
            'vars[context]': '4' if album_id else '1' if mode == 'category' else '3',
            'vars[context_id]': album_id or id,
            'vars[layout]': 'thumb',
            'vars[per_page][thumb]': str(self._PAGE_SIZE),
        }

        return self.playlist_result(
            OnDemandPagedList(functools.partial(
                self._fetch_page, host, url_part, album_id or id, data), self._PAGE_SIZE),
            playlist_title=title, id=f'{mode}-{album_id or id}')


code:
from .common import InfoExtractor
from .internetvideoarchive import InternetVideoArchiveIE

class VideoDetectiveIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?videodetective\.com/[^/]+/[^/]+/(?P<id>\d+)'

    _TEST = {
        'url': 'http://www.videodetective.com/movies/kick-ass-2/194487',
        'info_dict': {
            'id': '194487',
            'ext': 'mp4',
            'title': 'Kick-Ass 2',
            'description': 'md5:c189d5b7280400630a1d3dd17eaa8d8a',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        query = 'customerid=69249&publishedid=' + video_id
        return self.url_result(
            InternetVideoArchiveIE._build_json_url(query),
            ie=InternetVideoArchiveIE.ie_key())


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

class VideofyMeIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.videofy\.me/.+?|p\.videofy\.me/v)/(?P<id>\d+)(&|#|$)'
    IE_NAME = 'videofy.me'

    _TEST = {
        'url': 'http://www.videofy.me/thisisvideofyme/1100701',
        'md5': 'c77d700bdc16ae2e9f3c26019bd96143',
        'info_dict': {
            'id': '1100701',
            'ext': 'mp4',
            'title': 'This is VideofyMe',
            'description': '',
            'upload_date': '20130326',
            'timestamp': 1364288959,
            'uploader': 'VideofyMe',
            'uploader_id': 'thisisvideofyme',
            'view_count': int,
            'likes': int,
            'comment_count': int,
        },
    }

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

        config = self._download_json('http://vf-player-info-loader.herokuapp.com/%s.json' % video_id, video_id)['videoinfo']

        video = config.get('video')
        blog = config.get('blog', {})

        return {
            'id': video_id,
            'title': video['title'],
            'url': video['sources']['source']['url'],
            'thumbnail': video.get('thumb'),
            'description': video.get('description'),
            'timestamp': parse_iso8601(video.get('date')),
            'uploader': blog.get('name'),
            'uploader_id': blog.get('identifier'),
            'view_count': int_or_none(self._search_regex(r'([0-9]+)', video.get('views'), 'view count', fatal=False)),
            'likes': int_or_none(video.get('likes')),
            'comment_count': int_or_none(video.get('nrOfComments')),
        }


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

class VideomoreBaseIE(InfoExtractor):
    _API_BASE_URL = 'https://more.tv/api/v3/web/'
    _VALID_URL_BASE = r'https?://(?:videomore\.ru|more\.tv)/'

    def _download_page_data(self, display_id):
        return self._download_json(
            self._API_BASE_URL + 'PageData', display_id, query={
                'url': '/' + display_id,
            })['attributes']['response']['data']

    def _track_url_result(self, track):
        track_vod = track['trackVod']
        video_url = track_vod.get('playerLink') or track_vod['link']
        return self.url_result(
            video_url, VideomoreIE.ie_key(), track_vod.get('hubId'))

class VideomoreIE(InfoExtractor):
    IE_NAME = 'videomore'
    _VALID_URL = r'''(?x)
                    videomore:(?P<sid>\d+)$|
                    https?://
                        (?:
                            videomore\.ru/
                            (?:
                                embed|
                                [^/]+/[^/]+
                            )/|
                            (?:
                                (?:player\.)?videomore\.ru|
                                siren\.more\.tv/player
                            )/[^/]*\?.*?\btrack_id=|
                            odysseus\.more.tv/player/(?P<partner_id>\d+)/
                        )
                        (?P<id>\d+)
                        (?:[/?#&]|\.(?:xml|json)|$)
                    '''
    _EMBED_REGEX = [r'''(?x)
        (?:
            <iframe[^>]+src=([\'"])|
            <object[^>]+data=(["\'])https?://videomore\.ru/player\.swf\?.*config=
        )(?P<url>https?://videomore\.ru/[^?#"']+/\d+(?:\.xml)?)
    ''']
    _TESTS = [{
        'url': 'http://videomore.ru/kino_v_detalayah/5_sezon/367617',
        'md5': '44455a346edc0d509ac5b5a5b531dc35',
        'info_dict': {
            'id': '367617',
            'ext': 'flv',
            'title': 'Кино в деталях 5 сезон В гостях Алексей Чумаков и Юлия Ковальчук',
            'series': 'Кино в деталях',
            'episode': 'В гостях Алексей Чумаков и Юлия Ковальчук',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 2910,
            'view_count': int,
            'comment_count': int,
            'age_limit': 16,
        },
        'skip': 'The video is not available for viewing.',
    }, {
        'url': 'http://videomore.ru/embed/259974',
        'info_dict': {
            'id': '259974',
            'ext': 'mp4',
            'title': 'Молодежка 2 сезон 40 серия',
            'series': 'Молодежка',
            'season': '2 сезон',
            'episode': '40 серия',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 2789,
            'view_count': int,
            'age_limit': 16,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://videomore.ru/molodezhka/sezon_promo/341073',
        'info_dict': {
            'id': '341073',
            'ext': 'flv',
            'title': 'Промо Команда проиграла из-за Бакина?',
            'episode': 'Команда проиграла из-за Бакина?',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 29,
            'age_limit': 16,
            'view_count': int,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'The video is not available for viewing.',
    }, {
        'url': 'http://videomore.ru/elki_3?track_id=364623',
        'only_matching': True,
    }, {
        'url': 'http://videomore.ru/embed/364623',
        'only_matching': True,
    }, {
        'url': 'http://videomore.ru/video/tracks/364623.xml',
        'only_matching': True,
    }, {
        'url': 'http://videomore.ru/video/tracks/364623.json',
        'only_matching': True,
    }, {
        'url': 'http://videomore.ru/video/tracks/158031/quotes/33248',
        'only_matching': True,
    }, {
        'url': 'videomore:367617',
        'only_matching': True,
    }, {
        'url': 'https://player.videomore.ru/?partner_id=97&track_id=736234&autoplay=0&userToken=',
        'only_matching': True,
    }, {
        'url': 'https://odysseus.more.tv/player/1788/352317',
        'only_matching': True,
    }, {
        'url': 'https://siren.more.tv/player/config?track_id=352317&partner_id=1788&user_token=',
        'only_matching': True,
    }]
    _GEO_BYPASS = False

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        video_id = mobj.group('sid') or mobj.group('id')
        partner_id = mobj.group('partner_id') or parse_qs(url).get('partner_id', [None])[0] or '97'

        item = self._download_json(
            'https://siren.more.tv/player/config', video_id, query={
                'partner_id': partner_id,
                'track_id': video_id,
            })['data']['playlist']['items'][0]

        title = item.get('title')
        series = item.get('project_name')
        season = item.get('season_name')
        episode = item.get('episode_name')
        if not title:
            title = []
            for v in (series, season, episode):
                if v:
                    title.append(v)
            title = ' '.join(title)

        streams = item.get('streams') or []
        for protocol in ('DASH', 'HLS'):
            stream_url = item.get(protocol.lower() + '_url')
            if stream_url:
                streams.append({'protocol': protocol, 'url': stream_url})

        formats = []
        for stream in streams:
            stream_url = stream.get('url')
            if not stream_url:
                continue
            protocol = stream.get('protocol')
            if protocol == 'DASH':
                formats.extend(self._extract_mpd_formats(
                    stream_url, video_id, mpd_id='dash', fatal=False))
            elif protocol == 'HLS':
                formats.extend(self._extract_m3u8_formats(
                    stream_url, video_id, 'mp4', 'm3u8_native',
                    m3u8_id='hls', fatal=False))
            elif protocol == 'MSS':
                formats.extend(self._extract_ism_formats(
                    stream_url, video_id, ism_id='mss', fatal=False))

        if not formats:
            error = item.get('error')
            if error:
                if error in ('Данное видео недоступно для просмотра на территории этой страны', 'Данное видео доступно для просмотра только на территории России'):
                    self.raise_geo_restricted(countries=['RU'], metadata_available=True)
                self.raise_no_formats(error, expected=True)
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'series': series,
            'season': season,
            'episode': episode,
            'thumbnail': item.get('thumbnail_url'),
            'duration': int_or_none(item.get('duration')),
            'view_count': int_or_none(item.get('views')),
            'age_limit': int_or_none(item.get('min_age')),
            'formats': formats,
        }

class VideomoreVideoIE(VideomoreBaseIE):
    IE_NAME = 'videomore:video'
    _VALID_URL = VideomoreBaseIE._VALID_URL_BASE + r'(?P<id>(?:(?:[^/]+/){2})?[^/?#&]+)(?:/*|[?#&].*?)$'
    _TESTS = [{
        # single video with og:video:iframe
        'url': 'http://videomore.ru/elki_3',
        'info_dict': {
            'id': '364623',
            'ext': 'flv',
            'title': 'Ёлки 3',
            'description': '',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 5579,
            'age_limit': 6,
            'view_count': int,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'Requires logging in',
    }, {
        # season single series with og:video:iframe
        'url': 'http://videomore.ru/poslednii_ment/1_sezon/14_seriya',
        'info_dict': {
            'id': '352317',
            'ext': 'mp4',
            'title': 'Последний мент 1 сезон 14 серия',
            'series': 'Последний мент',
            'season': '1 сезон',
            'episode': '14 серия',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 2464,
            'age_limit': 16,
            'view_count': int,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://videomore.ru/sejchas_v_seti/serii_221-240/226_vypusk',
        'only_matching': True,
    }, {
        # single video without og:video:iframe
        'url': 'http://videomore.ru/marin_i_ego_druzya',
        'info_dict': {
            'id': '359073',
            'ext': 'flv',
            'title': '1 серия. Здравствуй, Аквавилль!',
            'description': 'md5:c6003179538b5d353e7bcd5b1372b2d7',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 754,
            'age_limit': 6,
            'view_count': int,
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'redirects to https://more.tv/'
    }, {
        'url': 'https://videomore.ru/molodezhka/6_sezon/29_seriya?utm_so',
        'only_matching': True,
    }, {
        'url': 'https://more.tv/poslednii_ment/1_sezon/14_seriya',
        'only_matching': True,
    }]

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

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

class VideomoreSeasonIE(VideomoreBaseIE):
    IE_NAME = 'videomore:season'
    _VALID_URL = VideomoreBaseIE._VALID_URL_BASE + r'(?!embed)(?P<id>[^/]+/[^/?#&]+)(?:/*|[?#&].*?)$'
    _TESTS = [{
        'url': 'http://videomore.ru/molodezhka/film_o_filme',
        'info_dict': {
            'id': 'molodezhka/film_o_filme',
            'title': 'Фильм о фильме',
        },
        'playlist_mincount': 3,
    }, {
        'url': 'http://videomore.ru/molodezhka/sezon_promo?utm_so',
        'only_matching': True,
    }, {
        'url': 'https://more.tv/molodezhka/film_o_filme',
        'only_matching': True,
    }]

    @classmethod
    def suitable(cls, url):
        return (False if (VideomoreIE.suitable(url) or VideomoreVideoIE.suitable(url))
                else super(VideomoreSeasonIE, cls).suitable(url))

    def _real_extract(self, url):
        display_id = self._match_id(url)
        season = self._download_page_data(display_id)
        season_id = compat_str(season['id'])
        tracks = self._download_json(
            self._API_BASE_URL + 'seasons/%s/tracks' % season_id,
            season_id)['data']
        entries = []
        for track in tracks:
            entries.append(self._track_url_result(track))
        return self.playlist_result(entries, display_id, season.get('title'))


code:
from .common import InfoExtractor
from ..utils import (
    determine_ext,
    float_or_none,
    int_or_none,
    parse_age_limit,
    qualities,
    random_birthday,
    unified_timestamp,
    urljoin,
)

class VideoPressIE(InfoExtractor):
    _ID_REGEX = r'[\da-zA-Z]{8}'
    _PATH_REGEX = r'video(?:\.word)?press\.com/embed/'
    _VALID_URL = r'https?://%s(?P<id>%s)' % (_PATH_REGEX, _ID_REGEX)
    _EMBED_REGEX = [rf'<iframe[^>]+src=["\'](?P<url>(?:https?://)?{_PATH_REGEX}{_ID_REGEX})']
    _TESTS = [{
        'url': 'https://videopress.com/embed/kUJmAcSf',
        'md5': '706956a6c875873d51010921310e4bc6',
        'info_dict': {
            'id': 'kUJmAcSf',
            'ext': 'mp4',
            'title': 'VideoPress Demo',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 634.6,
            'timestamp': 1434983935,
            'upload_date': '20150622',
            'age_limit': 0,
        },
    }, {
        # 17+, requires birth_* params
        'url': 'https://videopress.com/embed/iH3gstfZ',
        'only_matching': True,
    }, {
        'url': 'https://video.wordpress.com/embed/kUJmAcSf',
        'only_matching': True,
    }]

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

        query = random_birthday('birth_year', 'birth_month', 'birth_day')
        query['fields'] = 'description,duration,file_url_base,files,height,original,poster,rating,title,upload_date,width'
        video = self._download_json(
            'https://public-api.wordpress.com/rest/v1.1/videos/%s' % video_id,
            video_id, query=query)

        title = video['title']

        file_url_base = video.get('file_url_base') or {}
        base_url = file_url_base.get('https') or file_url_base.get('http')

        QUALITIES = ('std', 'dvd', 'hd')
        quality = qualities(QUALITIES)

        formats = []
        for format_id, f in (video.get('files') or {}).items():
            if not isinstance(f, dict):
                continue
            for ext, path in f.items():
                if ext in ('mp4', 'ogg'):
                    formats.append({
                        'url': urljoin(base_url, path),
                        'format_id': '%s-%s' % (format_id, ext),
                        'ext': determine_ext(path, ext),
                        'quality': quality(format_id),
                    })
        original_url = video.get('original')
        if original_url:
            formats.append({
                'url': original_url,
                'format_id': 'original',
                'quality': len(QUALITIES),
                'width': int_or_none(video.get('width')),
                'height': int_or_none(video.get('height')),
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': video.get('description'),
            'thumbnail': video.get('poster'),
            'duration': float_or_none(video.get('duration'), 1000),
            'timestamp': unified_timestamp(video.get('upload_date')),
            'age_limit': parse_age_limit(video.get('rating')),
            'formats': formats,
        }


code:
from .common import InfoExtractor
from ..utils import (
    clean_html,
    ExtractorError,
    format_field,
    get_element_by_class,
    int_or_none,
    parse_iso8601,
    smuggle_url,
    str_or_none,
    strip_or_none,
    try_get,
    unsmuggle_url,
    urlencode_postdata,
)

class VidioBaseIE(InfoExtractor):
    _LOGIN_URL = 'https://www.vidio.com/users/login'
    _NETRC_MACHINE = 'vidio'

    def _perform_login(self, username, password):
        def is_logged_in():
            res = self._download_json(
                'https://www.vidio.com/interactions.json', None, 'Checking if logged in', fatal=False) or {}
            return bool(res.get('current_user'))

        if is_logged_in():
            return

        login_page = self._download_webpage(
            self._LOGIN_URL, None, 'Downloading log in page')

        login_form = self._form_hidden_inputs("login-form", login_page)
        login_form.update({
            'user[login]': username,
            'user[password]': password,
        })
        login_post, login_post_urlh = self._download_webpage_handle(
            self._LOGIN_URL, None, 'Logging in', data=urlencode_postdata(login_form), expected_status=[302, 401])

        if login_post_urlh.status == 401:
            if get_element_by_class('onboarding-content-register-popup__title', login_post):
                raise ExtractorError(
                    'Unable to log in: The provided email has not registered yet.', expected=True)

            reason = get_element_by_class('onboarding-form__general-error', login_post) or get_element_by_class('onboarding-modal__title', login_post)
            if 'Akun terhubung ke' in reason:
                raise ExtractorError(
                    'Unable to log in: Your account is linked to a social media account. '
                    'Use --cookies to provide account credentials instead', expected=True)
            elif reason:
                subreason = get_element_by_class('onboarding-modal__description-text', login_post) or ''
                raise ExtractorError(
                    'Unable to log in: %s. %s' % (reason, clean_html(subreason)), expected=True)
            raise ExtractorError('Unable to log in')

    def _initialize_pre_login(self):
        self._api_key = self._download_json(
            'https://www.vidio.com/auth', None, data=b'')['api_key']

    def _call_api(self, url, video_id, note=None):
        return self._download_json(url, video_id, note=note, headers={
            'Content-Type': 'application/vnd.api+json',
            'X-API-KEY': self._api_key,
        })

class VidioIE(VidioBaseIE):
    _VALID_URL = r'https?://(?:www\.)?vidio\.com/(watch|embed)/(?P<id>\d+)-(?P<display_id>[^/?#&]+)'
    _TESTS = [{
        'url': 'http://www.vidio.com/watch/165683-dj_ambred-booyah-live-2015',
        'md5': 'abac81b1a205a8d94c609a473b5ea62a',
        'info_dict': {
            'id': '165683',
            'display_id': 'dj_ambred-booyah-live-2015',
            'ext': 'mp4',
            'title': 'DJ_AMBRED - Booyah (Live 2015)',
            'description': 'md5:27dc15f819b6a78a626490881adbadf8',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 149,
            'like_count': int,
            'uploader': 'TWELVE Pic',
            'timestamp': 1444902800,
            'upload_date': '20151015',
            'uploader_id': 'twelvepictures',
            'channel': 'Cover Music Video',
            'channel_id': '280236',
            'view_count': int,
            'dislike_count': int,
            'comment_count': int,
            'tags': 'count:3',
            'uploader_url': 'https://www.vidio.com/@twelvepictures',
        },
    }, {
        'url': 'https://www.vidio.com/watch/77949-south-korea-test-fires-missile-that-can-strike-all-of-the-north',
        'only_matching': True,
    }, {
        # Premier-exclusive video
        'url': 'https://www.vidio.com/watch/1550718-stand-by-me-doraemon',
        'only_matching': True
    }, {
        # embed url from https://enamplus.liputan6.com/read/5033648/video-fakta-temuan-suspek-cacar-monyet-di-jawa-tengah
        'url': 'https://www.vidio.com/embed/7115874-fakta-temuan-suspek-cacar-monyet-di-jawa-tengah',
        'info_dict': {
            'id': '7115874',
            'ext': 'mp4',
            'channel_id': '40172876',
            'comment_count': int,
            'uploader_id': 'liputan6',
            'view_count': int,
            'dislike_count': int,
            'upload_date': '20220804',
            'uploader': 'Liputan6.com',
            'display_id': 'fakta-temuan-suspek-cacar-monyet-di-jawa-tengah',
            'channel': 'ENAM PLUS 165',
            'timestamp': 1659605520,
            'title': 'Fakta Temuan Suspek Cacar Monyet di Jawa Tengah',
            'duration': 59,
            'like_count': int,
            'tags': ['monkeypox indonesia', 'cacar monyet menyebar', 'suspek cacar monyet di indonesia', 'fakta', 'hoax atau bukan?', 'jawa tengah'],
            'thumbnail': 'https://thumbor.prod.vidiocdn.com/83PN-_BKm5sS7emLtRxl506MLqQ=/640x360/filters:quality(70)/vidio-web-prod-video/uploads/video/image/7115874/fakta-suspek-cacar-monyet-di-jawa-tengah-24555a.jpg',
            'uploader_url': 'https://www.vidio.com/@liputan6',
            'description': 'md5:6d595a18d3b19ee378e335a6f288d5ac',
        },
    }]

    def _real_extract(self, url):
        match = self._match_valid_url(url).groupdict()
        video_id, display_id = match.get('id'), match.get('display_id')
        data = self._call_api('https://api.vidio.com/videos/' + video_id, display_id)
        video = data['videos'][0]
        title = video['title'].strip()
        is_premium = video.get('is_premium')

        if is_premium:
            sources = self._download_json(
                'https://www.vidio.com/interactions_stream.json?video_id=%s&type=videos' % video_id,
                display_id, note='Downloading premier API JSON')
            if not (sources.get('source') or sources.get('source_dash')):
                self.raise_login_required('This video is only available for registered users with the appropriate subscription')

            formats, subs = [], {}
            if sources.get('source'):
                hls_formats, hls_subs = self._extract_m3u8_formats_and_subtitles(
                    sources['source'], display_id, 'mp4', 'm3u8_native')
                formats.extend(hls_formats)
                subs.update(hls_subs)
            if sources.get('source_dash'):  # TODO: Find video example with source_dash
                dash_formats, dash_subs = self._extract_mpd_formats_and_subtitles(
                    sources['source_dash'], display_id, 'dash')
                formats.extend(dash_formats)
                subs.update(dash_subs)
        else:
            hls_url = data['clips'][0]['hls_url']
            formats, subs = self._extract_m3u8_formats_and_subtitles(
                hls_url, display_id, 'mp4', 'm3u8_native')

        self._sort_formats(formats)

        get_first = lambda x: try_get(data, lambda y: y[x + 's'][0], dict) or {}
        channel = get_first('channel')
        user = get_first('user')
        username = user.get('username')
        get_count = lambda x: int_or_none(video.get('total_' + x))

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': strip_or_none(video.get('description')),
            'thumbnail': video.get('image_url_medium'),
            'duration': int_or_none(video.get('duration')),
            'like_count': get_count('likes'),
            'formats': formats,
            'subtitles': subs,
            'uploader': user.get('name'),
            'timestamp': parse_iso8601(video.get('created_at')),
            'uploader_id': username,
            'uploader_url': format_field(username, None, 'https://www.vidio.com/@%s'),
            'channel': channel.get('name'),
            'channel_id': str_or_none(channel.get('id')),
            'view_count': get_count('view_count'),
            'dislike_count': get_count('dislikes'),
            'comment_count': get_count('comments'),
            'tags': video.get('tag_list'),
        }

class VidioPremierIE(VidioBaseIE):
    _VALID_URL = r'https?://(?:www\.)?vidio\.com/premier/(?P<id>\d+)/(?P<display_id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.vidio.com/premier/2885/badai-pasti-berlalu',
        'playlist_mincount': 14,
    }, {
        # Series with both free and premier-exclusive videos
        'url': 'https://www.vidio.com/premier/2567/sosmed',
        'only_matching': True,
    }]

    def _playlist_entries(self, playlist_url, display_id):
        index = 1
        while playlist_url:
            playlist_json = self._call_api(playlist_url, display_id, 'Downloading API JSON page %s' % index)
            for video_json in playlist_json.get('data', []):
                link = video_json['links']['watchpage']
                yield self.url_result(link, 'Vidio', video_json['id'])
            playlist_url = try_get(playlist_json, lambda x: x['links']['next'])
            index += 1

    def _real_extract(self, url):
        url, idata = unsmuggle_url(url, {})
        playlist_id, display_id = self._match_valid_url(url).groups()

        playlist_url = idata.get('url')
        if playlist_url:  # Smuggled data contains an API URL. Download only that playlist
            playlist_id = idata['id']
            return self.playlist_result(
                self._playlist_entries(playlist_url, playlist_id),
                playlist_id=playlist_id, playlist_title=idata.get('title'))

        playlist_data = self._call_api('https://api.vidio.com/content_profiles/%s/playlists' % playlist_id, display_id)

        return self.playlist_from_matches(
            playlist_data.get('data', []), playlist_id=playlist_id, ie=self.ie_key(),
            getter=lambda data: smuggle_url(url, {
                'url': data['relationships']['videos']['links']['related'],
                'id': data['id'],
                'title': try_get(data, lambda x: x['attributes']['name'])
            }))

class VidioLiveIE(VidioBaseIE):
    _VALID_URL = r'https?://(?:www\.)?vidio\.com/live/(?P<id>\d+)-(?P<display_id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.vidio.com/live/204-sctv',
        'info_dict': {
            'id': '204',
            'title': 'SCTV',
            'uploader': 'SCTV',
            'uploader_id': 'sctv',
            'thumbnail': r're:^https?://.*\.jpg$',
        },
    }, {
        # Premier-exclusive livestream
        'url': 'https://www.vidio.com/live/6362-tvn',
        'only_matching': True,
    }, {
        # DRM premier-exclusive livestream
        'url': 'https://www.vidio.com/live/6299-bein-1',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id, display_id = self._match_valid_url(url).groups()
        stream_data = self._call_api(
            'https://www.vidio.com/api/livestreamings/%s/detail' % video_id, display_id)
        stream_meta = stream_data['livestreamings'][0]
        user = stream_data.get('users', [{}])[0]

        title = stream_meta.get('title')
        username = user.get('username')

        formats = []
        if stream_meta.get('is_drm'):
            if not self.get_param('allow_unplayable_formats'):
                self.report_drm(video_id)
        if stream_meta.get('is_premium'):
            sources = self._download_json(
                'https://www.vidio.com/interactions_stream.json?video_id=%s&type=livestreamings' % video_id,
                display_id, note='Downloading premier API JSON')
            if not (sources.get('source') or sources.get('source_dash')):
                self.raise_login_required('This video is only available for registered users with the appropriate subscription')

            if str_or_none(sources.get('source')):
                token_json = self._download_json(
                    'https://www.vidio.com/live/%s/tokens' % video_id,
                    display_id, note='Downloading HLS token JSON', data=b'')
                formats.extend(self._extract_m3u8_formats(
                    sources['source'] + '?' + token_json.get('token', ''), display_id, 'mp4', 'm3u8_native'))
            if str_or_none(sources.get('source_dash')):
                pass
        else:
            if stream_meta.get('stream_token_url'):
                token_json = self._download_json(
                    'https://www.vidio.com/live/%s/tokens' % video_id,
                    display_id, note='Downloading HLS token JSON', data=b'')
                formats.extend(self._extract_m3u8_formats(
                    stream_meta['stream_token_url'] + '?' + token_json.get('token', ''),
                    display_id, 'mp4', 'm3u8_native'))
            if stream_meta.get('stream_dash_url'):
                pass
            if stream_meta.get('stream_url'):
                formats.extend(self._extract_m3u8_formats(
                    stream_meta['stream_url'], display_id, 'mp4', 'm3u8_native'))
        self._sort_formats(formats)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'is_live': True,
            'description': strip_or_none(stream_meta.get('description')),
            'thumbnail': stream_meta.get('image'),
            'like_count': int_or_none(stream_meta.get('like')),
            'dislike_count': int_or_none(stream_meta.get('dislike')),
            'formats': formats,
            'uploader': user.get('name'),
            'timestamp': parse_iso8601(stream_meta.get('start_time')),
            'uploader_id': username,
            'uploader_url': format_field(username, None, 'https://www.vidio.com/@%s'),
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    HEADRequest,
    format_field,
    float_or_none,
    get_element_by_id,
    int_or_none,
    str_to_int,
    strip_or_none,
    unified_strdate,
    urljoin,
)

class VidLiiIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?vidlii\.com/(?:watch|embed)\?.*?\bv=(?P<id>[0-9A-Za-z_-]{11})'
    _TESTS = [{
        'url': 'https://www.vidlii.com/watch?v=tJluaH4BJ3v',
        'md5': '9bf7d1e005dfa909b6efb0a1ff5175e2',
        'info_dict': {
            'id': 'tJluaH4BJ3v',
            'ext': 'mp4',
            'title': 'Vidlii is against me',
            'description': 'md5:fa3f119287a2bfb922623b52b1856145',
            'thumbnail': 're:https://.*.jpg',
            'uploader': 'APPle5auc31995',
            'uploader_url': 'https://www.vidlii.com/user/APPle5auc31995',
            'upload_date': '20171107',
            'duration': 212,
            'view_count': int,
            'comment_count': int,
            'average_rating': float,
            'categories': ['News & Politics'],
            'tags': ['Vidlii', 'Jan', 'Videogames'],
        }
    }, {
        'url': 'https://www.vidlii.com/watch?v=zTAtaAgOLKt',
        'md5': '5778f7366aa4c569b77002f8bf6b614f',
        'info_dict': {
            'id': 'zTAtaAgOLKt',
            'ext': 'mp4',
            'title': 'FULPTUBE SUCKS.',
            'description': 'md5:087b2ca355d4c8f8f77e97c43e72d711',
            'thumbnail': 'https://www.vidlii.com/usfi/thmp/zTAtaAgOLKt.jpg',
            'uploader': 'Homicide',
            'uploader_url': 'https://www.vidlii.com/user/Homicide',
            'upload_date': '20210612',
            'duration': 89,
            'view_count': int,
            'comment_count': int,
            'average_rating': float,
            'categories': ['News & Politics'],
            'tags': ['fulp', 'tube', 'sucks', 'bad', 'fulptube'],
        },
    }, {
        'url': 'https://www.vidlii.com/embed?v=tJluaH4BJ3v&a=0',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            'https://www.vidlii.com/watch?v=%s' % video_id, video_id)
        formats = []

        sources = [source[1] for source in re.findall(
            r'src\s*:\s*(["\'])(?P<url>(?:https?://)?(?:(?!\1).)+)\1',
            webpage) or []]
        for source in sources:
            height = int(self._search_regex(r'(\d+).mp4', source, 'height', default=360))
            if self._request_webpage(HEADRequest(source), video_id, f'Checking {height}p url', errnote=False):
                formats.append({
                    'url': source,
                    'format_id': f'{height}p',
                    'height': height,
                })
        self._sort_formats(formats)

        title = self._search_regex(
            (r'
([^<]+)
', r'<title>([^<]+) - VidLii<'), webpage,
            'title')

        description = self._html_search_meta(
            ('description', 'twitter:description'), webpage,
            default=None) or strip_or_none(
            get_element_by_id('des_text', webpage))

        thumbnail = self._html_search_meta(
            'twitter:image', webpage, default=None)
        if not thumbnail:
            thumbnail_path = self._search_regex(
                r'img\s*:\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage,
                'thumbnail', fatal=False, group='url')
            if thumbnail_path:
                thumbnail = urljoin(url, thumbnail_path)

        uploader = self._search_regex(
            r'
]+class=["\']wt_person[^>]+>\s*]+\bhref=["\']/user/[^>]+>([^<]+)',
            webpage, 'uploader', fatal=False)
        uploader_url = format_field(uploader, None, 'https://www.vidlii.com/user/%s')

        upload_date = unified_strdate(self._html_search_meta(
            'datePublished', webpage, default=None) or self._search_regex(
            r'<date>([^<]+)', webpage, 'upload date', fatal=False))

        duration = int_or_none(self._html_search_meta(
            'video:duration', webpage, 'duration',
            default=None) or self._search_regex(
            r'duration\s*:\s*(\d+)', webpage, 'duration', fatal=False))

        view_count = str_to_int(self._search_regex(
            (r'([,0-9]+) views',
             r'Views\s*:\s*([,0-9]+)'),
            webpage, 'view count', fatal=False))

        comment_count = int_or_none(self._search_regex(
            (r']+id=["\']cmt_num[^>]+>(\d+)',
             r'Comments\s*:\s*(\d+)'),
            webpage, 'comment count', fatal=False))

        average_rating = float_or_none(self._search_regex(
            r'rating\s*:\s*([\d.]+)', webpage, 'average rating', fatal=False))

        category = self._html_search_regex(
            r'
Category\s*:\s*
\s*
\s*]+>([^<]+)', webpage,
            'category', fatal=False)
        categories = [category] if category else None

        tags = [
            strip_or_none(tag)
            for tag in re.findall(
                r']+\bhref=["\']/results\?.*?q=[^>]*>([^<]+)',
                webpage) if strip_or_none(tag)
        ] or None

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'uploader': uploader,
            'formats': formats,
            'uploader_url': uploader_url,
            'upload_date': upload_date,
            'duration': duration,
            'view_count': view_count,
            'comment_count': comment_count,
            'average_rating': average_rating,
            'categories': categories,
            'tags': tags,
        }


code:
import json

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

class ViewLiftBaseIE(InfoExtractor):
    _API_BASE = 'https://prod-api.viewlift.com/'
    _DOMAINS_REGEX = r'(?:(?:main\.)?snagfilms|snagxtreme|funnyforfree|kiddovid|winnersview|(?:monumental|lax)sportsnetwork|vayafilm|failarmy|ftfnext|lnppass\.legapallacanestro|moviespree|app\.myoutdoortv|neoufitness|pflmma|theidentitytb)\.com|(?:hoichoi|app\.horseandcountry|kronon|marquee|supercrosslive)\.tv'
    _SITE_MAP = {
        'ftfnext': 'lax',
        'funnyforfree': 'snagfilms',
        'hoichoi': 'hoichoitv',
        'kiddovid': 'snagfilms',
        'laxsportsnetwork': 'lax',
        'legapallacanestro': 'lnp',
        'marquee': 'marquee-tv',
        'monumentalsportsnetwork': 'monumental-network',
        'moviespree': 'bingeflix',
        'pflmma': 'pfl',
        'snagxtreme': 'snagfilms',
        'theidentitytb': 'tampabay',
        'vayafilm': 'snagfilms',
    }
    _TOKENS = {}

    def _fetch_token(self, site, url):
        if self._TOKENS.get(site):
            return

        cookies = self._get_cookies(url)
        if cookies and cookies.get('token'):
            self._TOKENS[site] = self._search_regex(r'22authorizationToken\%22:\%22([^\%]+)\%22', cookies['token'].value, 'token')
        if not self._TOKENS.get(site):
            self.raise_login_required('Cookies (not necessarily logged in) are needed to download from this website', method='cookies')

    def _call_api(self, site, path, video_id, url, query):
        self._fetch_token(site, url)
        try:
            return self._download_json(
                self._API_BASE + path, video_id, headers={'Authorization': self._TOKENS.get(site)}, query=query)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                webpage = e.cause.read().decode()
                try:
                    error_message = traverse_obj(json.loads(webpage), 'errorMessage', 'message')
                except json.JSONDecodeError:
                    raise ExtractorError(f'{site} said: {webpage}', cause=e.cause)
                if error_message:
                    if 'has not purchased' in error_message:
                        self.raise_login_required(method='cookies')
                    raise ExtractorError(error_message, expected=True)
            raise

class ViewLiftEmbedIE(ViewLiftBaseIE):
    IE_NAME = 'viewlift:embed'
    _VALID_URL = r'https?://(?:(?:www|embed)\.)?(?P<domain>%s)/embed/player\?.*\bfilmId=(?P<id>[\da-f]{8}-(?:[\da-f]{4}-){3}[\da-f]{12})' % ViewLiftBaseIE._DOMAINS_REGEX
    _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:embed\.)?(?:%s)/embed/player.+?)\1' % ViewLiftBaseIE._DOMAINS_REGEX]
    _TESTS = [{
        'url': 'http://embed.snagfilms.com/embed/player?filmId=74849a00-85a9-11e1-9660-123139220831&w=500',
        'md5': '2924e9215c6eff7a55ed35b72276bd93',
        'info_dict': {
            'id': '74849a00-85a9-11e1-9660-123139220831',
            'ext': 'mp4',
            'title': '#whilewewatch',
            'description': 'md5:b542bef32a6f657dadd0df06e26fb0c8',
            'timestamp': 1334350096,
            'upload_date': '20120413',
        }
    }, {
        # invalid labels, 360p is better that 480p
        'url': 'http://www.snagfilms.com/embed/player?filmId=17ca0950-a74a-11e0-a92a-0026bb61d036',
        'md5': '882fca19b9eb27ef865efeeaed376a48',
        'info_dict': {
            'id': '17ca0950-a74a-11e0-a92a-0026bb61d036',
            'ext': 'mp4',
            'title': 'Life in Limbo',
        },
        'skip': 'The video does not exist',
    }, {
        'url': 'http://www.snagfilms.com/embed/player?filmId=0000014c-de2f-d5d6-abcf-ffef58af0017',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        domain, film_id = self._match_valid_url(url).groups()
        site = domain.split('.')[-2]
        if site in self._SITE_MAP:
            site = self._SITE_MAP[site]

        content_data = self._call_api(
            site, 'entitlement/video/status', film_id, url, {
                'id': film_id
            })['video']
        gist = content_data['gist']
        title = gist['title']
        video_assets = content_data['streamingInfo']['videoAssets']

        hls_url = video_assets.get('hls')
        formats, subtitles = [], {}
        if hls_url:
            formats, subtitles = self._extract_m3u8_formats_and_subtitles(
                hls_url, film_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False)

        for video_asset in video_assets.get('mpeg') or []:
            video_asset_url = video_asset.get('url')
            if not video_asset_url:
                continue
            bitrate = int_or_none(video_asset.get('bitrate'))
            height = int_or_none(self._search_regex(
                r'^_?(\d+)[pP]$', video_asset.get('renditionValue'),
                'height', default=None))
            formats.append({
                'url': video_asset_url,
                'format_id': 'http%s' % ('-%d' % bitrate if bitrate else ''),
                'tbr': bitrate,
                'height': height,
                'vcodec': video_asset.get('codec'),
            })

        subs = {}
        for sub in traverse_obj(content_data, ('contentDetails', 'closedCaptions')) or []:
            sub_url = sub.get('url')
            if not sub_url:
                continue
            subs.setdefault(sub.get('language', 'English'), []).append({
                'url': sub_url,
            })

        self._sort_formats(formats)
        return {
            'id': film_id,
            'title': title,
            'description': gist.get('description'),
            'thumbnail': gist.get('videoImageUrl'),
            'duration': int_or_none(gist.get('runtime')),
            'age_limit': parse_age_limit(content_data.get('parentalRating')),
            'timestamp': int_or_none(gist.get('publishDate'), 1000),
            'formats': formats,
            'subtitles': self._merge_subtitles(subs, subtitles),
            'categories': traverse_obj(content_data, ('categories', ..., 'title')),
            'tags': traverse_obj(content_data, ('tags', ..., 'title')),
        }

class ViewLiftIE(ViewLiftBaseIE):
    IE_NAME = 'viewlift'
    _API_BASE = 'https://prod-api-cached-2.viewlift.com/'
    _VALID_URL = r'https?://(?:www\.)?(?P<domain>%s)(?P<path>(?:/(?:films/title|show|(?:news/)?videos?|watch))?/(?P<id>[^?#]+))' % ViewLiftBaseIE._DOMAINS_REGEX
    _TESTS = [{
        'url': 'http://www.snagfilms.com/films/title/lost_for_life',
        'md5': '19844f897b35af219773fd63bdec2942',
        'info_dict': {
            'id': '0000014c-de2f-d5d6-abcf-ffef58af0017',
            'display_id': 'lost_for_life',
            'ext': 'mp4',
            'title': 'Lost for Life',
            'description': 'md5:ea10b5a50405ae1f7b5269a6ec594102',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 4489,
            'categories': 'mincount:3',
            'age_limit': 14,
            'upload_date': '20150421',
            'timestamp': 1429656820,
        }
    }, {
        'url': 'http://www.snagfilms.com/show/the_world_cut_project/india',
        'md5': 'e6292e5b837642bbda82d7f8bf3fbdfd',
        'info_dict': {
            'id': '00000145-d75c-d96e-a9c7-ff5c67b20000',
            'display_id': 'the_world_cut_project/india',
            'ext': 'mp4',
            'title': 'India',
            'description': 'md5:5c168c5a8f4719c146aad2e0dfac6f5f',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 979,
            'timestamp': 1399478279,
            'upload_date': '20140507',
        }
    }, {
        'url': 'http://main.snagfilms.com/augie_alone/s_2_ep_12_love',
        'info_dict': {
            'id': '00000148-7b53-de26-a9fb-fbf306f70020',
            'display_id': 'augie_alone/s_2_ep_12_love',
            'ext': 'mp4',
            'title': 'S. 2 Ep. 12 - Love',
            'description': 'Augie finds love.',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 107,
            'upload_date': '20141012',
            'timestamp': 1413129540,
            'age_limit': 17,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://main.snagfilms.com/films/title/the_freebie',
        'only_matching': True,
    }, {
        # Film is not playable in your area.
        'url': 'http://www.snagfilms.com/films/title/inside_mecca',
        'only_matching': True,
    }, {
        # Film is not available.
        'url': 'http://www.snagfilms.com/show/augie_alone/flirting',
        'only_matching': True,
    }, {
        'url': 'http://www.winnersview.com/videos/the-good-son',
        'only_matching': True,
    }, {
        # Was once Kaltura embed
        'url': 'https://www.monumentalsportsnetwork.com/videos/john-carlson-postgame-2-25-15',
        'only_matching': True,
    }, {
        'url': 'https://www.marquee.tv/watch/sadlerswells-sacredmonsters',
        'only_matching': True,
    }, {  # Free film with langauge code
        'url': 'https://www.hoichoi.tv/bn/films/title/shuyopoka',
        'info_dict': {
            'id': '7a7a9d33-1f4c-4771-9173-ee4fb6dbf196',
            'ext': 'mp4',
            'title': 'Shuyopoka',
            'description': 'md5:e28f2fb8680096a69c944d37c1fa5ffc',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20211006',
            'series': None
        },
        'params': {'skip_download': True},
    }, {  # Free film
        'url': 'https://www.hoichoi.tv/films/title/dadu-no1',
        'info_dict': {
            'id': '0000015b-b009-d126-a1db-b81ff3780000',
            'ext': 'mp4',
            'title': 'Dadu No.1',
            'description': 'md5:605cba408e51a79dafcb824bdeded51e',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20210827',
            'series': None
        },
        'params': {'skip_download': True},
    }, {  # Free episode
        'url': 'https://www.hoichoi.tv/webseries/case-jaundice-s01-e01',
        'info_dict': {
            'id': 'f779e07c-30c8-459c-8612-5a834ab5e5ba',
            'ext': 'mp4',
            'title': 'Humans Vs. Corona',
            'description': 'md5:ca30a682b4528d02a3eb6d0427dd0f87',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20210830',
            'series': 'Case Jaundice'
        },
        'params': {'skip_download': True},
    }, {  # Free video
        'url': 'https://www.hoichoi.tv/videos/1549072415320-six-episode-02-hindi',
        'info_dict': {
            'id': 'b41fa1ce-aca6-47b6-b208-283ff0a2de30',
            'ext': 'mp4',
            'title': 'Woman in red - Hindi',
            'description': 'md5:9d21edc1827d32f8633eb67c2054fc31',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20211006',
            'series': 'Six (Hindi)'
        },
        'params': {'skip_download': True},
    }, {  # Free episode
        'url': 'https://www.hoichoi.tv/shows/watch-asian-paints-moner-thikana-online-season-1-episode-1',
        'info_dict': {
            'id': '1f45d185-8500-455c-b88d-13252307c3eb',
            'ext': 'mp4',
            'title': 'Jisshu Sengupta',
            'description': 'md5:ef6ffae01a3d83438597367400f824ed',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20211004',
            'series': 'Asian Paints Moner Thikana'
        },
        'params': {'skip_download': True},
    }, {  # Free series
        'url': 'https://www.hoichoi.tv/shows/watch-moner-thikana-bengali-web-series-online',
        'playlist_mincount': 5,
        'info_dict': {
            'id': 'watch-moner-thikana-bengali-web-series-online',
        },
    }, {  # Premium series
        'url': 'https://www.hoichoi.tv/shows/watch-byomkesh-bengali-web-series-online',
        'playlist_mincount': 14,
        'info_dict': {
            'id': 'watch-byomkesh-bengali-web-series-online',
        },
    }, {  # Premium movie
        'url': 'https://www.hoichoi.tv/movies/detective-2020',
        'only_matching': True
    }]

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

    def _show_entries(self, domain, seasons):
        for season in seasons:
            for episode in season.get('episodes') or []:
                path = traverse_obj(episode, ('gist', 'permalink'))
                if path:
                    yield self.url_result(f'https://www.{domain}{path}', ie=self.ie_key())

    def _real_extract(self, url):
        domain, path, display_id = self._match_valid_url(url).groups()
        site = domain.split('.')[-2]
        if site in self._SITE_MAP:
            site = self._SITE_MAP[site]
        modules = self._call_api(
            site, 'content/pages', display_id, url, {
                'includeContent': 'true',
                'moduleOffset': 1,
                'path': path,
                'site': site,
            })['modules']

        seasons = next((m['contentData'][0]['seasons'] for m in modules if m.get('moduleType') == 'ShowDetailModule'), None)
        if seasons:
            return self.playlist_result(self._show_entries(domain, seasons), display_id)

        film_id = next(m['contentData'][0]['gist']['id'] for m in modules if m.get('moduleType') == 'VideoDetailModule')
        return {
            '_type': 'url_transparent',
            'url': 'http://%s/embed/player?filmId=%s' % (domain, film_id),
            'id': film_id,
            'display_id': display_id,
            'ie_key': 'ViewLiftEmbed',
        }


code:
import re

from .common import InfoExtractor
from ..compat import (
    compat_HTTPError,
    compat_str,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    js_to_json,
    parse_duration,
    parse_iso8601,
)

class ViideaIE(InfoExtractor):
    _VALID_URL = r'''(?x)https?://(?:www\.)?(?:
            videolectures\.net|
            flexilearn\.viidea\.net|
            presentations\.ocwconsortium\.org|
            video\.travel-zoom\.si|
            video\.pomp-forum\.si|
            tv\.nil\.si|
            video\.hekovnik.com|
            video\.szko\.si|
            kpk\.viidea\.com|
            inside\.viidea\.net|
            video\.kiberpipa\.org|
            bvvideo\.si|
            kongres\.viidea\.net|
            edemokracija\.viidea\.com
        )(?:/lecture)?/(?P<id>[^/]+)(?:/video/(?P<part>\d+))?/*(?:[#?].*)?$'''

    _TESTS = [{
        'url': 'http://videolectures.net/promogram_igor_mekjavic_eng/',
        'info_dict': {
            'id': '20171',
            'display_id': 'promogram_igor_mekjavic_eng',
            'ext': 'mp4',
            'title': 'Automatics, robotics and biocybernetics',
            'description': 'md5:815fc1deb6b3a2bff99de2d5325be482',
            'thumbnail': r're:http://.*\.jpg',
            'timestamp': 1372349289,
            'upload_date': '20130627',
            'duration': 565,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        # video with invalid direct format links (HTTP 403)
        'url': 'http://videolectures.net/russir2010_filippova_nlp/',
        'info_dict': {
            'id': '14891',
            'display_id': 'russir2010_filippova_nlp',
            'ext': 'flv',
            'title': 'NLP at Google',
            'description': 'md5:fc7a6d9bf0302d7cc0e53f7ca23747b3',
            'thumbnail': r're:http://.*\.jpg',
            'timestamp': 1284375600,
            'upload_date': '20100913',
            'duration': 5352,
        },
        'params': {
            # rtmp download
            'skip_download': True,
        },
    }, {
        # event playlist
        'url': 'http://videolectures.net/deeplearning2015_montreal/',
        'info_dict': {
            'id': '23181',
            'title': 'Deep Learning Summer School, Montreal 2015',
            'description': 'md5:0533a85e4bd918df52a01f0e1ebe87b7',
            'thumbnail': r're:http://.*\.jpg',
            'timestamp': 1438560000,
        },
        'playlist_count': 30,
    }, {
        # multi part lecture
        'url': 'http://videolectures.net/mlss09uk_bishop_ibi/',
        'info_dict': {
            'id': '9737',
            'display_id': 'mlss09uk_bishop_ibi',
            'title': 'Introduction To Bayesian Inference',
            'thumbnail': r're:http://.*\.jpg',
            'timestamp': 1251622800,
        },
        'playlist': [{
            'info_dict': {
                'id': '9737_part1',
                'display_id': 'mlss09uk_bishop_ibi_part1',
                'ext': 'wmv',
                'title': 'Introduction To Bayesian Inference (Part 1)',
                'thumbnail': r're:http://.*\.jpg',
                'duration': 4622,
                'timestamp': 1251622800,
                'upload_date': '20090830',
            },
        }, {
            'info_dict': {
                'id': '9737_part2',
                'display_id': 'mlss09uk_bishop_ibi_part2',
                'ext': 'wmv',
                'title': 'Introduction To Bayesian Inference (Part 2)',
                'thumbnail': r're:http://.*\.jpg',
                'duration': 5641,
                'timestamp': 1251622800,
                'upload_date': '20090830',
            },
        }],
        'playlist_count': 2,
    }]

    def _real_extract(self, url):
        lecture_slug, explicit_part_id = self._match_valid_url(url).groups()

        webpage = self._download_webpage(url, lecture_slug)

        cfg = self._parse_json(self._search_regex(
            [r'cfg\s*:\s*({.+?})\s*,\s*[\da-zA-Z_]+\s*:\s*\(?\s*function',
             r'cfg\s*:\s*({[^}]+})'],
            webpage, 'cfg'), lecture_slug, js_to_json)

        lecture_id = compat_str(cfg['obj_id'])

        base_url = self._proto_relative_url(cfg['livepipe'], 'http:')

        try:
            lecture_data = self._download_json(
                '%s/site/api/lecture/%s?format=json' % (base_url, lecture_id),
                lecture_id)['lecture'][0]
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                msg = self._parse_json(
                    e.cause.read().decode('utf-8'), lecture_id)
                raise ExtractorError(msg['detail'], expected=True)
            raise

        lecture_info = {
            'id': lecture_id,
            'display_id': lecture_slug,
            'title': lecture_data['title'],
            'timestamp': parse_iso8601(lecture_data.get('time')),
            'description': lecture_data.get('description_wiki'),
            'thumbnail': lecture_data.get('thumb'),
        }

        playlist_entries = []
        lecture_type = lecture_data.get('type')
        parts = [compat_str(video) for video in cfg.get('videos', [])]
        if parts:
            multipart = len(parts) > 1

            def extract_part(part_id):
                smil_url = '%s/%s/video/%s/smil.xml' % (base_url, lecture_slug, part_id)
                smil = self._download_smil(smil_url, lecture_id)
                info = self._parse_smil(smil, smil_url, lecture_id)
                self._sort_formats(info['formats'])
                info['id'] = lecture_id if not multipart else '%s_part%s' % (lecture_id, part_id)
                info['display_id'] = lecture_slug if not multipart else '%s_part%s' % (lecture_slug, part_id)
                if multipart:
                    info['title'] += ' (Part %s)' % part_id
                switch = smil.find('.//switch')
                if switch is not None:
                    info['duration'] = parse_duration(switch.attrib.get('dur'))
                item_info = lecture_info.copy()
                item_info.update(info)
                return item_info

            if explicit_part_id or not multipart:
                result = extract_part(explicit_part_id or parts[0])
            else:
                result = {
                    '_type': 'multi_video',
                    'entries': [extract_part(part) for part in parts],
                }
                result.update(lecture_info)

            # Immediately return explicitly requested part or non event item
            if explicit_part_id or lecture_type != 'evt':
                return result

            playlist_entries.append(result)

        # It's probably a playlist
        if not parts or lecture_type == 'evt':
            playlist_webpage = self._download_webpage(
                '%s/site/ajax/drilldown/?id=%s' % (base_url, lecture_id), lecture_id)
            entries = [
                self.url_result(compat_urlparse.urljoin(url, video_url), 'Viidea')
                for _, video_url in re.findall(
                    r'<a[^>]+href=(["\'])(.+?)\1[^>]+id=["\']lec=\d+', playlist_webpage)]
            playlist_entries.extend(entries)

        playlist = self.playlist_result(playlist_entries, lecture_id)
        playlist.update(lecture_info)
        return playlist