code:
import json
import re

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

class IviIE(InfoExtractor):
    IE_DESC = 'ivi.ru'
    IE_NAME = 'ivi'
    _VALID_URL = r'https?://(?:www\.)?ivi\.(?:ru|tv)/(?:watch/(?:[^/]+/)?|video/player\?.*?videoId=)(?P<id>\d+)'
    _EMBED_REGEX = [r'<embed[^>]+?src=(["\'])(?P<url>https?://(?:www\.)?ivi\.ru/video/player.+?)\1']
    _GEO_BYPASS = False
    _GEO_COUNTRIES = ['RU']
    _LIGHT_KEY = b'\xf1\x02\x32\xb7\xbc\x5c\x7a\xe8\xf7\x96\xc1\x33\x2b\x27\xa1\x8c'
    _LIGHT_URL = 'https://api.ivi.ru/light/'

    _TESTS = [
        # Single movie
        {
            'url': 'http://www.ivi.ru/watch/53141',
            'md5': '6ff5be2254e796ed346251d117196cf4',
            'info_dict': {
                'id': '53141',
                'ext': 'mp4',
                'title': 'Иван Васильевич меняет профессию',
                'description': 'md5:b924063ea1677c8fe343d8a72ac2195f',
                'duration': 5498,
                'thumbnail': r're:^https?://.*\.jpg$',
            },
            'skip': 'Only works from Russia',
        },
        # Serial's series
        {
            'url': 'http://www.ivi.ru/watch/dvoe_iz_lartsa/9549',
            'md5': '221f56b35e3ed815fde2df71032f4b3e',
            'info_dict': {
                'id': '9549',
                'ext': 'mp4',
                'title': 'Двое из ларца - Дело Гольдберга (1 часть)',
                'series': 'Двое из ларца',
                'season': 'Сезон 1',
                'season_number': 1,
                'episode': 'Дело Гольдберга (1 часть)',
                'episode_number': 1,
                'duration': 2655,
                'thumbnail': r're:^https?://.*\.jpg$',
            },
            'skip': 'Only works from Russia',
        },
        {
            # with MP4-HD720 format
            'url': 'http://www.ivi.ru/watch/146500',
            'md5': 'd63d35cdbfa1ea61a5eafec7cc523e1e',
            'info_dict': {
                'id': '146500',
                'ext': 'mp4',
                'title': 'Кукла',
                'description': 'md5:ffca9372399976a2d260a407cc74cce6',
                'duration': 5599,
                'thumbnail': r're:^https?://.*\.jpg$',
            },
            'skip': 'Only works from Russia',
        },
        {
            'url': 'https://www.ivi.tv/watch/33560/',
            'only_matching': True,
        },
    ]

    # Sorted by quality
    _KNOWN_FORMATS = (
        'MP4-low-mobile', 'MP4-mobile', 'FLV-lo', 'MP4-lo', 'FLV-hi', 'MP4-hi',
        'MP4-SHQ', 'MP4-HD720', 'MP4-HD1080')

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

        data = json.dumps({
            'method': 'da.content.get',
            'params': [
                video_id, {
                    'site': 's%d',
                    'referrer': 'http://www.ivi.ru/watch/%s' % video_id,
                    'contentid': video_id
                }
            ]
        })

        for site in (353, 183):
            content_data = (data % site).encode()
            if site == 353:
                try:
                    from Cryptodome.Cipher import Blowfish
                    from Cryptodome.Hash import CMAC
                    pycryptodome_found = True
                except ImportError:
                    try:
                        from Crypto.Cipher import Blowfish
                        from Crypto.Hash import CMAC
                        pycryptodome_found = True
                    except ImportError:
                        pycryptodome_found = False
                        continue

                timestamp = (self._download_json(
                    self._LIGHT_URL, video_id,
                    'Downloading timestamp JSON', data=json.dumps({
                        'method': 'da.timestamp.get',
                        'params': []
                    }).encode(), fatal=False) or {}).get('result')
                if not timestamp:
                    continue

                query = {
                    'ts': timestamp,
                    'sign': CMAC.new(self._LIGHT_KEY, timestamp.encode() + content_data, Blowfish).hexdigest(),
                }
            else:
                query = {}

            video_json = self._download_json(
                self._LIGHT_URL, video_id,
                'Downloading video JSON', data=content_data, query=query)

            error = video_json.get('error')
            if error:
                origin = error.get('origin')
                message = error.get('message') or error.get('user_message')
                extractor_msg = 'Unable to download video %s'
                if origin == 'NotAllowedForLocation':
                    self.raise_geo_restricted(message, self._GEO_COUNTRIES)
                elif origin == 'NoRedisValidData':
                    extractor_msg = 'Video %s does not exist'
                elif site == 353:
                    continue
                elif not pycryptodome_found:
                    raise ExtractorError('pycryptodomex not found. Please install', expected=True)
                elif message:
                    extractor_msg += ': ' + message
                raise ExtractorError(extractor_msg % video_id, expected=True)
            else:
                break

        result = video_json['result']
        title = result['title']

        quality = qualities(self._KNOWN_FORMATS)

        formats = []
        for f in result.get('files', []):
            f_url = f.get('url')
            content_format = f.get('content_format')
            if not f_url:
                continue
            if (not self.get_param('allow_unplayable_formats')
                    and ('-MDRM-' in content_format or '-FPS-' in content_format)):
                continue
            formats.append({
                'url': f_url,
                'format_id': content_format,
                'quality': quality(content_format),
                'filesize': int_or_none(f.get('size_in_bytes')),
            })
        self._sort_formats(formats)

        compilation = result.get('compilation')
        episode = title if compilation else None

        title = '%s - %s' % (compilation, title) if compilation is not None else title

        thumbnails = [{
            'url': preview['url'],
            'id': preview.get('content_format'),
        } for preview in result.get('preview', []) if preview.get('url')]

        webpage = self._download_webpage(url, video_id)

        season = self._search_regex(
            r'<li[^>]+class="season active"[^>]*><a[^>]+>([^<]+)',
            webpage, 'season', default=None)
        season_number = int_or_none(self._search_regex(
            r'<li[^>]+class="season active"[^>]*><a[^>]+data-season(?:-index)?="(\d+)"',
            webpage, 'season number', default=None))

        episode_number = int_or_none(self._search_regex(
            r'[^>]+itemprop="episode"[^>]*>\s*<meta[^>]+itemprop="episodeNumber"[^>]+content="(\d+)',
            webpage, 'episode number', default=None))

        description = self._og_search_description(webpage, default=None) or self._html_search_meta(
            'description', webpage, 'description', default=None)

        return {
            'id': video_id,
            'title': title,
            'series': compilation,
            'season': season,
            'season_number': season_number,
            'episode': episode,
            'episode_number': episode_number,
            'thumbnails': thumbnails,
            'description': description,
            'duration': int_or_none(result.get('duration')),
            'formats': formats,
        }

class IviCompilationIE(InfoExtractor):
    IE_DESC = 'ivi.ru compilations'
    IE_NAME = 'ivi:compilation'
    _VALID_URL = r'https?://(?:www\.)?ivi\.ru/watch/(?!\d+)(?P<compilationid>[a-z\d_-]+)(?:/season(?P<seasonid>\d+))?$'
    _TESTS = [{
        'url': 'http://www.ivi.ru/watch/dvoe_iz_lartsa',
        'info_dict': {
            'id': 'dvoe_iz_lartsa',
            'title': 'Двое из ларца (2006 - 2008)',
        },
        'playlist_mincount': 24,
    }, {
        'url': 'http://www.ivi.ru/watch/dvoe_iz_lartsa/season1',
        'info_dict': {
            'id': 'dvoe_iz_lartsa/season1',
            'title': 'Двое из ларца (2006 - 2008) 1 сезон',
        },
        'playlist_mincount': 12,
    }]

    def _extract_entries(self, html, compilation_id):
        return [
            self.url_result(
                'http://www.ivi.ru/watch/%s/%s' % (compilation_id, serie), IviIE.ie_key())
            for serie in re.findall(
                r'<a\b[^>]+\bhref=["\']/watch/%s/(\d+)["\']' % compilation_id, html)]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        compilation_id = mobj.group('compilationid')
        season_id = mobj.group('seasonid')

        if season_id is not None:  # Season link
            season_page = self._download_webpage(
                url, compilation_id, 'Downloading season %s web page' % season_id)
            playlist_id = '%s/season%s' % (compilation_id, season_id)
            playlist_title = self._html_search_meta('title', season_page, 'title')
            entries = self._extract_entries(season_page, compilation_id)
        else:  # Compilation link
            compilation_page = self._download_webpage(url, compilation_id, 'Downloading compilation web page')
            playlist_id = compilation_id
            playlist_title = self._html_search_meta('title', compilation_page, 'title')
            seasons = re.findall(
                r'<a href="/watch/%s/season(\d+)' % compilation_id, compilation_page)
            if not seasons:  # No seasons in this compilation
                entries = self._extract_entries(compilation_page, compilation_id)
            else:
                entries = []
                for season_id in seasons:
                    season_page = self._download_webpage(
                        'http://www.ivi.ru/watch/%s/season%s' % (compilation_id, season_id),
                        compilation_id, 'Downloading season %s web page' % season_id)
                    entries.extend(self._extract_entries(season_page, compilation_id))

        return self.playlist_result(entries, playlist_id, playlist_title)


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

class IvideonIE(InfoExtractor):
    IE_NAME = 'ivideon'
    IE_DESC = 'Ivideon TV'
    _VALID_URL = r'https?://(?:www\.)?ivideon\.com/tv/(?:[^/]+/)*camera/(?P<id>\d+-[\da-f]+)/(?P<camera_id>\d+)'
    _TESTS = [{
        'url': 'https://www.ivideon.com/tv/camera/100-916ca13b5c4ad9f564266424a026386d/0/',
        'info_dict': {
            'id': '100-916ca13b5c4ad9f564266424a026386d',
            'ext': 'flv',
            'title': 're:^Касса [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'description': 'Основное предназначение - запись действий кассиров. Плюс общий вид.',
            'is_live': True,
        },
        'params': {
            'skip_download': True,
        }
    }, {
        'url': 'https://www.ivideon.com/tv/camera/100-c4ee4cb9ede885cf62dfbe93d7b53783/589824/?lang=ru',
        'only_matching': True,
    }, {
        'url': 'https://www.ivideon.com/tv/map/22.917923/-31.816406/16/camera/100-e7bc16c7d4b5bbd633fd5350b66dfa9a/0',
        'only_matching': True,
    }]

    _QUALITIES = ('low', 'mid', 'hi')

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        server_id, camera_id = mobj.group('id'), mobj.group('camera_id')
        camera_name, description = None, None
        camera_url = compat_urlparse.urljoin(
            url, '/tv/camera/%s/%s/' % (server_id, camera_id))

        webpage = self._download_webpage(camera_url, server_id, fatal=False)
        if webpage:
            config_string = self._search_regex(
                r'var\s+config\s*=\s*({.+?});', webpage, 'config', default=None)
            if config_string:
                config = self._parse_json(config_string, server_id, fatal=False)
                camera_info = config.get('ivTvAppOptions', {}).get('currentCameraInfo')
                if camera_info:
                    camera_name = camera_info.get('camera_name')
                    description = camera_info.get('misc', {}).get('description')
            if not camera_name:
                camera_name = self._html_search_meta(
                    'name', webpage, 'camera name', default=None) or self._search_regex(
                    r'<h1[^>]+class="b-video-title"[^>]*>([^<]+)', webpage, 'camera name', default=None)

        quality = qualities(self._QUALITIES)

        formats = [{
            'url': 'https://streaming.ivideon.com/flv/live?%s' % compat_urllib_parse_urlencode({
                'server': server_id,
                'camera': camera_id,
                'sessionId': 'demo',
                'q': quality(format_id),
            }),
            'format_id': format_id,
            'ext': 'flv',
            'quality': quality(format_id),
        } for format_id in self._QUALITIES]
        self._sort_formats(formats)

        return {
            'id': server_id,
            'title': camera_name or server_id,
            'description': description,
            'is_live': True,
            'formats': formats,
        }

repo_name: Lesmiscore/ytdl


code:
import itertools
import re
import urllib.parse

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    mimetype2ext,
    remove_end,
    strip_or_none,
    unified_strdate,
    url_or_none,
    urljoin,
)

class IwaraBaseIE(InfoExtractor):
    _BASE_REGEX = r'(?P<base_url>https?://(?:www\.|ecchi\.)?iwara\.tv)'

    def _extract_playlist(self, base_url, webpage):
        for path in re.findall(r'class="title">\s*<a[^<]+href="(/videos/[^"]+)', webpage):
            yield self.url_result(urljoin(base_url, path))

class IwaraIE(InfoExtractor):
    IE_NAME = 'iwara'
    _VALID_URL = r'https?://(?:www\.|ecchi\.)?iwara\.tv/videos/(?P<id>[a-zA-Z0-9]+)'
    _TESTS = [{
        'url': 'http://iwara.tv/videos/amVwUl1EHpAD9RD',
        # md5 is unstable
        'info_dict': {
            'id': 'amVwUl1EHpAD9RD',
            'ext': 'mp4',
            'title': '【MMD R-18】ガールフレンド carry_me_off',
            'age_limit': 18,
            'thumbnail': 'https://i.iwara.tv/sites/default/files/videos/thumbnails/7951/thumbnail-7951_0001.png',
            'uploader': 'Reimu丨Action',
            'upload_date': '20150828',
            'description': 'md5:1d4905ce48c66c9299c617f08e106e0f',
        },
    }, {
        'url': 'http://ecchi.iwara.tv/videos/Vb4yf2yZspkzkBO',
        'md5': '7e5f1f359cd51a027ba4a7b7710a50f0',
        'info_dict': {
            'id': '0B1LvuHnL-sRFNXB1WHNqbGw4SXc',
            'ext': 'mp4',
            'title': '[3D Hentai] Kyonyu × Genkai × Emaki Shinobi Girls.mp4',
            'age_limit': 18,
        },
        'add_ie': ['GoogleDrive'],
    }, {
        'url': 'http://www.iwara.tv/videos/nawkaumd6ilezzgq',
        # md5 is unstable
        'info_dict': {
            'id': '6liAP9s2Ojc',
            'ext': 'mp4',
            'age_limit': 18,
            'title': '[MMD] Do It Again Ver.2 [1080p 60FPS] (Motion,Camera,Wav+DL)',
            'description': 'md5:590c12c0df1443d833fbebe05da8c47a',
            'upload_date': '20160910',
            'uploader': 'aMMDsork',
            'uploader_id': 'UCVOFyOSCyFkXTYYHITtqB7A',
        },
        'add_ie': ['Youtube'],
    }]

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

        webpage, urlh = self._download_webpage_handle(url, video_id)

        hostname = urllib.parse.urlparse(urlh.geturl()).hostname
        # ecchi is 'sexy' in Japanese
        age_limit = 18 if hostname.split('.')[0] == 'ecchi' else 0

        video_data = self._download_json('http://www.iwara.tv/api/video/%s' % video_id, video_id)

        if not video_data:
            iframe_url = self._html_search_regex(
                r']+src=([\'"])(?P<url>[^\'"]+)\1',
                webpage, 'iframe URL', group='url')
            return {
                '_type': 'url_transparent',
                'url': iframe_url,
                'age_limit': age_limit,
            }

        title = remove_end(self._html_extract_title(webpage), ' | Iwara')

        thumbnail = self._html_search_regex(
            r'poster=[\'"]([^\'"]+)', webpage, 'thumbnail', default=None)

        uploader = self._html_search_regex(
            r'class="username">([^<]+)', webpage, 'uploader', fatal=False)

        upload_date = unified_strdate(self._html_search_regex(
            r'作成日:([^\s]+)', webpage, 'upload_date', fatal=False))

        description = strip_or_none(self._search_regex(
            r'
(.+?(?=</div))', webpage, 'description', fatal=False,
            flags=re.DOTALL))

        formats = []
        for a_format in video_data:
            format_uri = url_or_none(a_format.get('uri'))
            if not format_uri:
                continue
            format_id = a_format.get('resolution')
            height = int_or_none(self._search_regex(
                r'(\d+)p', format_id, 'height', default=None))
            formats.append({
                'url': self._proto_relative_url(format_uri, 'https:'),
                'format_id': format_id,
                'ext': mimetype2ext(a_format.get('mime')) or 'mp4',
                'height': height,
                'quality': 1 if format_id == 'Source' else 0,
            })

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'age_limit': age_limit,
            'formats': formats,
            'thumbnail': self._proto_relative_url(thumbnail, 'https:'),
            'uploader': uploader,
            'upload_date': upload_date,
            'description': description,
        }

class IwaraUserIE(IwaraBaseIE):
    _VALID_URL = fr'{IwaraBaseIE._BASE_REGEX}/users/(?P<id>[^/?#&]+)'
    IE_NAME = 'iwara:user'

    _TESTS = [{
        'note': 'number of all videos page is just 1 page. less than 40 videos',
        'url': 'https://ecchi.iwara.tv/users/infinityyukarip',
        'info_dict': {
            'id': 'infinityyukarip',
        },
        'playlist_mincount': 39,
    }, {
        'note': 'no even all videos page. probably less than 10 videos',
        'url': 'https://ecchi.iwara.tv/users/mmd-quintet',
        'info_dict': {
            'id': 'mmd-quintet',
        },
        'playlist_mincount': 6,
    }, {
        'note': 'has paging. more than 40 videos',
        'url': 'https://ecchi.iwara.tv/users/theblackbirdcalls',
        'info_dict': {
            'id': 'theblackbirdcalls',
        },
        'playlist_mincount': 420,
    }, {
        'note': 'foreign chars in URL. there must be foreign characters in URL',
        'url': 'https://ecchi.iwara.tv/users/ぶた丼',
        'info_dict': {
            'id': 'ぶた丼',
        },
        'playlist_mincount': 170,
    }]

    def _entries(self, playlist_id, base_url):
        webpage = self._download_webpage(
            f'{base_url}/users/{playlist_id}', playlist_id)
        videos_url = self._search_regex(r'', webpage, 'all videos url', default=None)
        if not videos_url:
            yield from self._extract_playlist(base_url, webpage)
            return

        videos_url = urljoin(base_url, videos_url)

        for n in itertools.count(1):
            page = self._download_webpage(
                videos_url, playlist_id, note=f'Downloading playlist page {n}',
                query={'page': str(n - 1)} if n > 1 else {})
            yield from self._extract_playlist(
                base_url, page)

            if f'page={n}' not in page:
                break

    def _real_extract(self, url):
        playlist_id, base_url = self._match_valid_url(url).group('id', 'base_url')
        playlist_id = urllib.parse.unquote(playlist_id)

        return self.playlist_result(
            self._entries(playlist_id, base_url), playlist_id)

class IwaraUser2IE(InfoExtractor):
    IE_NAME = 'iwara:user2'
    _VALID_URL = r'https?://(?:www\.|ecchi\.)?iwara\.tv/users/(?P<id>[^/?&#]+)/videos'
    IE_DESC = False  # do not list this
    _TESTS = [{
        'note': 'number of all videos page is just 1 page',
        'url': 'https://ecchi.iwara.tv/users/infinityyukarip/videos',
        'info_dict': {},
        'add_ie': [IwaraUserIE.ie_key()],
    }, {
        'note': 'no even all videos page',
        'url': 'https://ecchi.iwara.tv/users/mmd-quintet/videos',
        'info_dict': {},
        'add_ie': [IwaraUserIE.ie_key()],
    }, {
        'note': 'has paging',
        'url': 'https://ecchi.iwara.tv/users/theblackbirdcalls/videos',
        'info_dict': {},
        'add_ie': [IwaraUserIE.ie_key()],
    }, {
        'note': 'foreign chars in URL',
        'url': 'https://ecchi.iwara.tv/users/ぶた丼/videos',
        'info_dict': {},
        'add_ie': [IwaraUserIE.ie_key()],
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id, note='Repairing URL')
        videos_url = self._search_regex(r'', webpage, 'user page url')
        videos_url = urljoin(url, videos_url)
        return self.url_result(videos_url, ie=IwaraUserIE.ie_key())

class IwaraPlaylistIE(InfoExtractor):
    IE_NAME = 'iwara:playlist'
    _VALID_URL = r'https?://(?:www\.|ecchi\.)?iwara\.tv/playlist/(?P<id>[a-zA-Z0-9-]+)'
    _TESTS = [{
        'url': 'https://ecchi.iwara.tv/playlist/best-enf',
        'info_dict': {
            'title': 'Best enf',
            'uploader_id': 'Jared98112',
            'id': 'best-enf',
        },
        'playlist_mincount': 50,
    }]

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

        return {
            '_type': 'playlist',
            'id': playlist_id,
            'uploader_id': self._html_search_regex(
                r'
\s*\s*
(.*?)
',
                webpage, 'uploader_id'),
            'title': self._html_search_regex(
                (r'
]*?>(.*?)
',
                 r'<title>(.*?)\s+\|\s*Iwara'), webpage, 'title'),
            'entries': (self.url_result(urljoin(url, u))
                        for u in re.findall(
                            r'
\s*', webpage)),
        }


code:
import base64

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    get_element_by_id,
    int_or_none,
    js_to_json,
    str_or_none,
    traverse_obj,
)

class IxiguaIE(InfoExtractor):
    _VALID_URL = r'https?://(?:\w+\.)?ixigua\.com/(?:video/)?(?P<id>\d+).+'
    _TESTS = [{
        'url': 'https://www.ixigua.com/6996881461559165471',
        'info_dict': {
            'id': '6996881461559165471',
            'ext': 'mp4',
            'title': '盲目涉水风险大，亲身示范高水位行车注意事项',
            'description': 'md5:8c82f46186299add4a1c455430740229',
            'tags': ['video_car'],
            'like_count': int,
            'dislike_count': int,
            'view_count': int,
            'uploader': '懂车帝原创',
            'uploader_id': '6480145787',
            'thumbnail': r're:^https?://.+\.(avif|webp)',
            'timestamp': 1629088414,
            'duration': 1030,
        }
    }]

    def _get_json_data(self, webpage, video_id):
        js_data = get_element_by_id('SSR_HYDRATED_DATA', webpage)
        if not js_data:
            if self._cookies_passed:
                raise ExtractorError('Failed to get SSR_HYDRATED_DATA')
            raise ExtractorError('Cookies (not necessarily logged in) are needed', expected=True)

        return self._parse_json(
            js_data.replace('window._SSR_HYDRATED_DATA=', ''), video_id, transform_source=js_to_json)

    def _media_selector(self, json_data):
        for path, override in (
            (('video_list', ), {}),
            (('dynamic_video', 'dynamic_video_list'), {'acodec': 'none'}),
            (('dynamic_video', 'dynamic_audio_list'), {'vcodec': 'none', 'ext': 'm4a'}),
        ):
            for media in traverse_obj(json_data, (..., *path, lambda _, v: v['main_url'])):
                yield {
                    'url': base64.b64decode(media['main_url']).decode(),
                    'width': int_or_none(media.get('vwidth')),
                    'height': int_or_none(media.get('vheight')),
                    'fps': int_or_none(media.get('fps')),
                    'vcodec': media.get('codec_type'),
                    'format_id': str_or_none(media.get('quality_type')),
                    'filesize': int_or_none(media.get('size')),
                    'ext': 'mp4',
                    **override,
                }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        json_data = self._get_json_data(webpage, video_id)['anyVideo']['gidInformation']['packerData']['video']

        formats = list(self._media_selector(json_data.get('videoResource')))
        self._sort_formats(formats)
        return {
            'id': video_id,
            'title': json_data.get('title'),
            'description': json_data.get('video_abstract'),
            'formats': formats,
            'like_count': json_data.get('video_like_count'),
            'duration': int_or_none(json_data.get('duration')),
            'tags': [json_data.get('tag')],
            'uploader_id': traverse_obj(json_data, ('user_info', 'user_id')),
            'uploader': traverse_obj(json_data, ('user_info', 'name')),
            'view_count': json_data.get('video_watch_count'),
            'dislike_count': json_data.get('video_unlike_count'),
            'timestamp': int_or_none(json_data.get('video_publish_time')),
        }


code:
from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urllib_parse_unquote,
)
from ..utils import (
    determine_ext,
    float_or_none,
    get_element_by_id,
    int_or_none,
    parse_iso8601,
    str_to_int,
)

class IzleseneIE(InfoExtractor):
    _VALID_URL = r'''(?x)
        https?://(?:(?:www|m)\.)?izlesene\.com/
        (?:video|embedplayer)/(?:[^/]+/)?(?P<id>[0-9]+)
        '''
    _TESTS = [
        {
            'url': 'http://www.izlesene.com/video/sevincten-cildirtan-dogum-gunu-hediyesi/7599694',
            'md5': '4384f9f0ea65086734b881085ee05ac2',
            'info_dict': {
                'id': '7599694',
                'ext': 'mp4',
                'title': 'Sevinçten Çıldırtan Doğum Günü Hediyesi',
                'description': 'md5:253753e2655dde93f59f74b572454f6d',
                'thumbnail': r're:^https?://.*\.jpg',
                'uploader_id': 'pelikzzle',
                'timestamp': int,
                'upload_date': '20140702',
                'duration': 95.395,
                'age_limit': 0,
            }
        },
        {
            'url': 'http://www.izlesene.com/video/tarkan-dortmund-2006-konseri/17997',
            'md5': '97f09b6872bffa284cb7fa4f6910cb72',
            'info_dict': {
                'id': '17997',
                'ext': 'mp4',
                'title': 'Tarkan Dortmund 2006 Konseri',
                'thumbnail': r're:^https://.*\.jpg',
                'uploader_id': 'parlayankiz',
                'timestamp': int,
                'upload_date': '20061112',
                'duration': 253.666,
                'age_limit': 0,
            }
        },
    ]

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

        webpage = self._download_webpage('http://www.izlesene.com/video/%s' % video_id, video_id)

        video = self._parse_json(
            self._search_regex(
                r'videoObj\s*=\s*({.+?})\s*;\s*\n', webpage, 'streams'),
            video_id)

        title = video.get('videoTitle') or self._og_search_title(webpage)

        formats = []
        for stream in video['media']['level']:
            source_url = stream.get('source')
            if not source_url or not isinstance(source_url, compat_str):
                continue
            ext = determine_ext(url, 'mp4')
            quality = stream.get('value')
            height = int_or_none(quality)
            formats.append({
                'format_id': '%sp' % quality if quality else 'sd',
                'url': compat_urllib_parse_unquote(source_url),
                'ext': ext,
                'height': height,
            })
        self._sort_formats(formats)

        description = self._og_search_description(webpage, default=None)
        thumbnail = video.get('posterURL') or self._proto_relative_url(
            self._og_search_thumbnail(webpage), scheme='http:')

        uploader = self._html_search_regex(
            r"adduserUsername\s*=\s*'([^']+)';",
            webpage, 'uploader', fatal=False)
        timestamp = parse_iso8601(self._html_search_meta(
            'uploadDate', webpage, 'upload date'))

        duration = float_or_none(video.get('duration') or self._html_search_regex(
            r'videoduration["\']?\s*=\s*(["\'])(?P<value>(?:(?!\1).)+)\1',
            webpage, 'duration', fatal=False, group='value'), scale=1000)

        view_count = str_to_int(get_element_by_id('videoViewCount', webpage))
        comment_count = self._html_search_regex(
            r'comment_count\s*=\s*\'([^\']+)\';',
            webpage, 'comment_count', fatal=False)

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'uploader_id': uploader,
            'timestamp': timestamp,
            'duration': duration,
            'view_count': int_or_none(view_count),
            'comment_count': int_or_none(comment_count),
            'age_limit': self._family_friendly_search(webpage),
            'formats': formats,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    InAdvancePagedList,
    int_or_none,
    orderedSet,
    unified_strdate,
)

class JableIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?jable.tv/videos/(?P<id>[\w-]+)'
    _TESTS = [{
        'url': 'https://jable.tv/videos/pppd-812/',
        'md5': 'f1537283a9bc073c31ff86ca35d9b2a6',
        'info_dict': {
            'id': 'pppd-812',
            'ext': 'mp4',
            'title': 'PPPD-812 只要表現好巨乳女教師吉根柚莉愛就獎勵學生們在白虎穴內射出精液',
            'description': 'md5:5b6d4199a854f62c5e56e26ccad19967',
            'thumbnail': r're:^https?://.*\.jpg$',
            'age_limit': 18,
            'like_count': int,
            'view_count': int,
        },
    }, {
        'url': 'https://jable.tv/videos/apak-220/',
        'md5': '71f9239d69ced58ab74a816908847cc1',
        'info_dict': {
            'id': 'apak-220',
            'ext': 'mp4',
            'title': 'md5:5c3861b7cf80112a6e2b70bccf170824',
            'description': '',
            'thumbnail': r're:^https?://.*\.jpg$',
            'age_limit': 18,
            'like_count': int,
            'view_count': int,
            'upload_date': '20220319',
        },
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        formats = self._extract_m3u8_formats(
            self._search_regex(r'var\s+hlsUrl\s*=\s*\'([^\']+)', webpage, 'hls_url'), video_id, 'mp4', m3u8_id='hls')
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': self._og_search_title(webpage),
            'description': self._og_search_description(webpage, default=''),
            'thumbnail': self._og_search_thumbnail(webpage, default=None),
            'formats': formats,
            'age_limit': 18,
            'upload_date': unified_strdate(self._search_regex(
                r'class="inactive-color">\D+\s+(\d{4}-\d+-\d+)', webpage, 'upload_date', default=None)),
            'view_count': int_or_none(self._search_regex(
                r'#icon-eye"></use></svg>\n*([\d ]+)',
                webpage, 'view_count', default='').replace(' ', '')),
            'like_count': int_or_none(self._search_regex(
                r'#icon-heart"></use></svg>(\d+)', webpage, 'link_count', default=None)),
        }

class JablePlaylistIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?jable.tv/(?:categories|models|tags)/(?P<id>[\w-]+)'
    _TESTS = [{
        'url': 'https://jable.tv/models/kaede-karen/',
        'info_dict': {
            'id': 'kaede-karen',
            'title': '楓カレン',
        },
        'playlist_count': 34,
    }, {
        'url': 'https://jable.tv/categories/roleplay/',
        'only_matching': True,
    }, {
        'url': 'https://jable.tv/tags/girl/',
        'only_matching': True,
    }]

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

        def page_func(page_num):
            return [
                self.url_result(player_url, JableIE)
                for player_url in orderedSet(re.findall(
                    r'href="(https://jable.tv/videos/[\w-]+/?)"',
                    self._download_webpage(url, playlist_id, query={
                        'mode': 'async',
                        'from': page_num + 1,
                        'function': 'get_block',
                        'block_id': 'list_videos_common_videos_list',
                    }, note=f'Downloading page {page_num + 1}')))]

        return self.playlist_result(
            InAdvancePagedList(page_func, int_or_none(self._search_regex(
                r'from:(\d+)">[^<]+\s*»', webpage, 'last page number', default=1)), 24),
            playlist_id, self._search_regex(
                r'
([^<]+)', webpage, 'playlist title', default=None))


code:
import hashlib
import random

from ..compat import compat_str
from .common import InfoExtractor
from ..utils import (
    clean_html,
    int_or_none,
    try_get,
)

class JamendoIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            licensing\.jamendo\.com/[^/]+|
                            (?:www\.)?jamendo\.com
                        )
                        /track/(?P<id>[0-9]+)(?:/(?P<display_id>[^/?#&]+))?
                    '''
    _TESTS = [{
        'url': 'https://www.jamendo.com/track/196219/stories-from-emona-i',
        'md5': '6e9e82ed6db98678f171c25a8ed09ffd',
        'info_dict': {
            'id': '196219',
            'display_id': 'stories-from-emona-i',
            'ext': 'flac',
            # 'title': 'Maya Filipič - Stories from Emona I',
            'title': 'Stories from Emona I',
            'artist': 'Maya Filipič',
            'album': 'Between two worlds',
            'track': 'Stories from Emona I',
            'duration': 210,
            'thumbnail': 'https://usercontent.jamendo.com?type=album&id=29279&width=300&trackid=196219',
            'timestamp': 1217438117,
            'upload_date': '20080730',
            'license': 'by-nc-nd',
            'view_count': int,
            'like_count': int,
            'average_rating': int,
            'tags': ['piano', 'peaceful', 'newage', 'strings', 'upbeat'],
        }
    }, {
        'url': 'https://licensing.jamendo.com/en/track/1496667/energetic-rock',
        'only_matching': True,
    }]

    def _call_api(self, resource, resource_id, fatal=True):
        path = '/api/%ss' % resource
        rand = compat_str(random.random())
        return self._download_json(
            'https://www.jamendo.com' + path, resource_id, fatal=fatal, query={
                'id[]': resource_id,
            }, headers={
                'X-Jam-Call': '$%s*%s~' % (hashlib.sha1((path + rand).encode()).hexdigest(), rand)
            })[0]

    def _real_extract(self, url):
        track_id, display_id = self._match_valid_url(url).groups()
        # webpage = self._download_webpage(
        #     'https://www.jamendo.com/track/' + track_id, track_id)
        # models = self._parse_json(self._html_search_regex(
        #     r"data-bundled-models='([^']+)",
        #     webpage, 'bundled models'), track_id)
        # track = models['track']['models'][0]
        track = self._call_api('track', track_id)
        title = track_name = track['name']
        # get_model = lambda x: try_get(models, lambda y: y[x]['models'][0], dict) or {}
        # artist = get_model('artist')
        # artist_name = artist.get('name')
        # if artist_name:
        #     title = '%s - %s' % (artist_name, title)
        # album = get_model('album')
        artist = self._call_api("artist", track.get('artistId'), fatal=False)
        album = self._call_api("album", track.get('albumId'), fatal=False)

        formats = [{
            'url': 'https://%s.jamendo.com/?trackid=%s&format=%s&from=app-97dab294'
                   % (sub_domain, track_id, format_id),
            'format_id': format_id,
            'ext': ext,
            'quality': quality,
        } for quality, (format_id, sub_domain, ext) in enumerate((
            ('mp31', 'mp3l', 'mp3'),
            ('mp32', 'mp3d', 'mp3'),
            ('ogg1', 'ogg', 'ogg'),
            ('flac', 'flac', 'flac'),
        ))]
        self._sort_formats(formats)

        urls = []
        thumbnails = []
        for covers in (track.get('cover') or {}).values():
            for cover_id, cover_url in covers.items():
                if not cover_url or cover_url in urls:
                    continue
                urls.append(cover_url)
                size = int_or_none(cover_id.lstrip('size'))
                thumbnails.append({
                    'id': cover_id,
                    'url': cover_url,
                    'width': size,
                    'height': size,
                })

        tags = []
        for tag in (track.get('tags') or []):
            tag_name = tag.get('name')
            if not tag_name:
                continue
            tags.append(tag_name)

        stats = track.get('stats') or {}
        license = track.get('licenseCC') or []

        return {
            'id': track_id,
            'display_id': display_id,
            'thumbnails': thumbnails,
            'title': title,
            'description': track.get('description'),
            'duration': int_or_none(track.get('duration')),
            'artist': artist.get('name'),
            'track': track_name,
            'album': album.get('name'),
            'formats': formats,
            'license': '-'.join(license) if license else None,
            'timestamp': int_or_none(track.get('dateCreated')),
            'view_count': int_or_none(stats.get('listenedAll')),
            'like_count': int_or_none(stats.get('favorited')),
            'average_rating': int_or_none(stats.get('averageNote')),
            'tags': tags,
        }

class JamendoAlbumIE(JamendoIE):
    _VALID_URL = r'https?://(?:www\.)?jamendo\.com/album/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.jamendo.com/album/121486/duck-on-cover',
        'info_dict': {
            'id': '121486',
            'title': 'Duck On Cover',
            'description': 'md5:c2920eaeef07d7af5b96d7c64daf1239',
        },
        'playlist': [{
            'md5': 'e1a2fcb42bda30dfac990212924149a8',
            'info_dict': {
                'id': '1032333',
                'ext': 'flac',
                'title': 'Warmachine',
                'artist': 'Shearer',
                'track': 'Warmachine',
                'timestamp': 1368089771,
                'upload_date': '20130509',
                'view_count': int,
                'thumbnail': 'https://usercontent.jamendo.com?type=album&id=121486&width=300&trackid=1032333',
                'duration': 190,
                'license': 'by',
                'album': 'Duck On Cover',
                'average_rating': 4,
                'tags': ['rock', 'drums', 'bass', 'world', 'punk', 'neutral'],
                'like_count': int,
            }
        }, {
            'md5': '1f358d7b2f98edfe90fd55dac0799d50',
            'info_dict': {
                'id': '1032330',
                'ext': 'flac',
                'title': 'Without Your Ghost',
                'artist': 'Shearer',
                'track': 'Without Your Ghost',
                'timestamp': 1368089771,
                'upload_date': '20130509',
                'duration': 192,
                'tags': ['rock', 'drums', 'bass', 'world', 'punk'],
                'album': 'Duck On Cover',
                'thumbnail': 'https://usercontent.jamendo.com?type=album&id=121486&width=300&trackid=1032330',
                'view_count': int,
                'average_rating': 4,
                'license': 'by',
                'like_count': int,
            }
        }],
        'params': {
            'playlistend': 2
        }
    }]

    def _real_extract(self, url):
        album_id = self._match_id(url)
        album = self._call_api('album', album_id)
        album_name = album.get('name')

        entries = []
        for track in (album.get('tracks') or []):
            track_id = track.get('id')
            if not track_id:
                continue
            track_id = compat_str(track_id)
            entries.append({
                '_type': 'url_transparent',
                'url': 'https://www.jamendo.com/track/' + track_id,
                'ie_key': JamendoIE.ie_key(),
                'id': track_id,
                'album': album_name,
            })

        return self.playlist_result(
            entries, album_id, album_name,
            clean_html(try_get(album, lambda x: x['description']['en'], compat_str)))


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

import re

from ..utils import (
    clean_html,
    float_or_none,
    int_or_none,
    smuggle_url,
    unsmuggle_url
)
from .common import InfoExtractor

class ShugiinItvBaseIE(InfoExtractor):
    @classmethod
    def _find_rooms(cls, webpage):
        return [{
            '_type': 'url',
            'id': x.group(1),
            'title': clean_html(x.group(2)).strip(),
            'url': smuggle_url(f'https://www.shugiintv.go.jp/jp/index.php?room_id={x.group(1)}', x.groups()),
            'ie_key': ShugiinItvLiveIE.ie_key(),
        } for x in re.finditer(r'(.+?)	', webpage)]

    @staticmethod
    def _parse_japanese_date(text):
        if not text:
            return None
        ERA_TABLE = {
            '明治': 1868,
            '大正': 1912,
            '昭和': 1926,
            '平成': 1989,
            '令和': 2019,
        }
        ERA_RE = '|'.join(map(re.escape, ERA_TABLE.keys()))
        mobj = re.search(rf'({ERA_RE})?(\d+)年(\d+)月(\d+)日', re.sub(r'[\s\u3000]+', '', text))
        if not mobj:
            return None
        era, year, month, day = mobj.groups()
        year, month, day = map(int, (year, month, day))
        if era:
            # example input: 令和5年3月34日
            # even though each era have their end, don't check here
            year += ERA_TABLE[era]
        return '%04d%02d%02d' % (year, month, day)

    @staticmethod
    def _parse_japanese_duration(text):
        if not text:
            return None
        mobj = re.search(r'(?:(\d+)日間?)?(?:(\d+)時間?)?(?:(\d+)分)?(?:(\d+)秒)?', re.sub(r'[\s\u3000]+', '', text))
        if not mobj:
            return None
        days, hours, mins, secs = map(int_or_none, mobj.groups())

        duration = 0
        if secs:
            duration += float(secs)
        if mins:
            duration += float(mins) * 60
        if hours:
            duration += float(hours) * 60 * 60
        if days:
            duration += float(days) * 24 * 60 * 60
        return duration

class ShugiinItvLiveIE(ShugiinItvBaseIE):
    _VALID_URL = r'https?://(?:www\.)?shugiintv\.go\.jp/(?:jp|en)(?:/index\.php)?'
    IE_DESC = '衆議院インターネット審議中継'

    @classmethod
    def suitable(cls, url):
        return super().suitable(url) and not any(x.suitable(url) for x in (ShugiinItvLiveRoomIE, ShugiinItvVodIE))

    def _real_extract(self, url):
        self.report_warning('Listing up all running proceedings as of now. To specify one proceedings to record, use link direct from the website.')
        webpage = self._download_webpage(
            'https://www.shugiintv.go.jp/jp/index.php', None,
            encoding='euc-jp')
        return self.playlist_result(self._find_rooms(webpage))

class ShugiinItvLiveRoomIE(ShugiinItvBaseIE):
    _VALID_URL = r'https?://(?:www\.)?shugiintv\.go\.jp/(?:jp|en)/index\.php\?room_id=(?P<id>room\d+)'
    IE_DESC = '衆議院インターネット審議中継 (中継)'

    def _real_extract(self, url):
        url, smug = unsmuggle_url(url)
        if smug:
            room_id, title = smug
        else:
            room_id = self._match_id(url)
            webpage = self._download_webpage(
                'https://www.shugiintv.go.jp/jp/index.php', None,
                encoding='euc-jp')
            title = next((x.get('id') == room_id for x in self._find_rooms(webpage)), {}).get('title')

        # I doubt there's also subtitles
        formats, subtitles = self._extract_m3u8_formats_and_subtitles(
            f'https://hlslive.shugiintv.go.jp/{room_id}/amlst:{room_id}/playlist.m3u8',
            room_id, ext='mp4')
        self._sort_formats(formats)

        return {
            'id': room_id,
            'title': title,
            'formats': formats,
            'subtitles': subtitles,
            'is_live': True,
        }

class ShugiinItvVodIE(ShugiinItvBaseIE):
    _VALID_URL = r'https?://(?:www\.)?shugiintv\.go\.jp/(?:jp|en)/index\.php\?ex=VL(?:\&[^=]+=[^&]*)*\&deli_id=(?P<id>\d+)'
    IE_DESC = '衆議院インターネット審議中継 (ビデオライブラリ)'
    _TESTS = [{
        'url': 'https://www.shugiintv.go.jp/jp/index.php?ex=VL&media_type=&deli_id=53846',
        'info_dict': {
            'id': '53846',
            'title': 'ウクライナ大統領国会演説（オンライン）',
            'release_date': '20220323',
            'chapters': 'count:4',
        }
    }, {
        'url': 'https://www.shugiintv.go.jp/en/index.php?ex=VL&media_type=&deli_id=53846',
        'only_matching': True
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(
            f'https://www.shugiintv.go.jp/jp/index.php?ex=VL&media_type=&deli_id={video_id}', video_id,
            encoding='euc-jp')

        m3u8_url = self._search_regex(
            r'id="vtag_src_base_vod"\s*value="(http.+?\.m3u8)"', webpage, 'm3u8 url')
        m3u8_url = re.sub(r'^http://', 'https://', m3u8_url)
        # I doubt there's also subtitles
        formats, subtitles = self._extract_m3u8_formats_and_subtitles(
            m3u8_url, video_id, ext='mp4')
        self._sort_formats(formats)

        title = self._html_search_regex(
            (r'(.+)\s*\(\d+分\)',
             r'<TD.+?(.+?)<IMG'), webpage, 'title', fatal=False)

        release_date = self._parse_japanese_date(self._html_search_regex(
            r'開会日	\s*\s*(.+?)	',
            webpage, 'title', fatal=False))

        # NOTE: chapters are sparse, because of how the website serves the video
        chapters = []
        for chp in re.finditer(r'(?!<img)(.+)</[Aa]>', webpage):
            chapters.append({
                'title': clean_html(chp.group(2)).strip(),
                'start_time': float_or_none(chp.group(1).strip()),
            })
        # the exact duration of the last chapter is unknown! (we can get at most minutes of granularity)
        for idx in range(len(chapters) - 1):
            chapters[idx]['end_time'] = chapters[idx + 1]['start_time']

        last_tr = re.findall(r'(?s)
(.+?)
', webpage)[-1]
        if last_tr and chapters:
            last_td = re.findall(r'<TD.+?	', last_tr.group(0))[-1]
            if last_td:
                chapters[-1]['end_time'] = chapters[-1]['start_time'] + self._parse_japanese_duration(clean_html(last_td.group(0)))

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

# IDR=Internet Deliberation Relay
class SangiinIDRIE(InfoExtractor):
    def _real_extract(self, url):
        return super()._real_extract(url)


code:
from .common import InfoExtractor

class JeuxVideoIE(InfoExtractor):
    _VALID_URL = r'https?://.*?\.jeuxvideo\.com/.*/(.*?)\.htm'

    _TESTS = [{
        'url': 'http://www.jeuxvideo.com/reportages-videos-jeux/0004/00046170/tearaway-playstation-vita-gc-2013-tearaway-nous-presente-ses-papiers-d-identite-00115182.htm',
        'md5': '046e491afb32a8aaac1f44dd4ddd54ee',
        'info_dict': {
            'id': '114765',
            'ext': 'mp4',
            'title': 'Tearaway : GC 2013 : Tearaway nous présente ses papiers d\'identité',
            'description': 'Lorsque les développeurs de LittleBigPlanet proposent un nouveau titre, on ne peut que s\'attendre à un résultat original et fort attrayant.',
        },
    }, {
        'url': 'http://www.jeuxvideo.com/videos/chroniques/434220/l-histoire-du-jeu-video-la-saturn.htm',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        title = mobj.group(1)
        webpage = self._download_webpage(url, title)
        title = self._html_search_meta('name', webpage) or self._og_search_title(webpage)
        config_url = self._html_search_regex(
            r'data-src(?:set-video)?="(/contenu/medias/video\.php.*?)"',
            webpage, 'config URL')
        config_url = 'http://www.jeuxvideo.com' + config_url

        video_id = self._search_regex(
            r'id=(\d+)',
            config_url, 'video ID')

        config = self._download_json(
            config_url, title, 'Downloading JSON config')

        formats = [{
            'url': source['file'],
            'format_id': source['label'],
            'resolution': source['label'],
        } for source in reversed(config['sources'])]

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': self._og_search_description(webpage),
            'thumbnail': config.get('image'),
        }


code:
from .common import InfoExtractor
from ..utils import clean_html, float_or_none, traverse_obj, try_call

class JixieBaseIE(InfoExtractor):
    """
    API Reference:
        https://jixie.atlassian.net/servicedesk/customer/portal/2/article/1339654214?src=-1456335525,
        https://scripts.jixie.media/jxvideo.3.1.min.js
    """

    def _extract_data_from_jixie_id(self, display_id, video_id, webpage):
        json_data = self._download_json(
            'https://apidam.jixie.io/api/public/stream', display_id,
            query={'metadata': 'full', 'video_id': video_id})['data']

        formats, subtitles = [], {}
        for stream in json_data['streams']:
            if stream.get('type') == 'HLS':
                fmt, sub = self._extract_m3u8_formats_and_subtitles(stream.get('url'), display_id, ext='mp4')
                if json_data.get('drm'):
                    for f in fmt:
                        f['has_drm'] = True
                formats.extend(fmt)
                self._merge_subtitles(sub, target=subtitles)
            else:
                formats.append({
                    'url': stream.get('url'),
                    'width': stream.get('width'),
                    'height': stream.get('height'),
                    'ext': 'mp4',
                })

        self._sort_formats(formats)
        return {
            'id': video_id,
            'display_id': display_id,
            'formats': formats,
            'subtitles': subtitles,
            'title': json_data.get('title') or self._html_search_meta(['og:title', 'twitter:title'], webpage),
            'description': (clean_html(traverse_obj(json_data, ('metadata', 'description')))
                            or self._html_search_meta(['description', 'og:description', 'twitter:description'], webpage)),
            'thumbnails': traverse_obj(json_data, ('metadata', 'thumbnails')),
            'duration': float_or_none(traverse_obj(json_data, ('metadata', 'duration'))),
            'tags': try_call(lambda: (json_data['metadata']['keywords'] or None).split(',')),
            'categories': try_call(lambda: (json_data['metadata']['categories'] or None).split(',')),
            'uploader_id': json_data.get('owner_id'),
        }


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    format_field,
    int_or_none,
    js_to_json,
    try_get,
)

class JojIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    (?:
                        joj:|
                        https?://media\.joj\.sk/embed/
                    )
                    (?P<id>[^/?#^]+)
                '''
    _EMBED_REGEX = [r'<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?//media\.joj\.sk/embed/(?:(?!\1).)+)\1']
    _TESTS = [{
        'url': 'https://media.joj.sk/embed/a388ec4c-6019-4a4a-9312-b1bee194e932',
        'info_dict': {
            'id': 'a388ec4c-6019-4a4a-9312-b1bee194e932',
            'ext': 'mp4',
            'title': 'NOVÉ BÝVANIE',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 3118,
        }
    }, {
        'url': 'https://media.joj.sk/embed/9i1cxv',
        'only_matching': True,
    }, {
        'url': 'joj:a388ec4c-6019-4a4a-9312-b1bee194e932',
        'only_matching': True,
    }, {
        'url': 'joj:9i1cxv',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            'https://media.joj.sk/embed/%s' % video_id, video_id)

        title = self._search_regex(
            (r'videoTitle\s*:\s*(["\'])(?P<title>(?:(?!\1).)+)\1',
             r'<title>(?P<title>[^<]+)'), webpage, 'title',
            default=None, group='title') or self._og_search_title(webpage)

        bitrates = self._parse_json(
            self._search_regex(
                r'(?s)(?:src|bitrates)\s*=\s*({.+?});', webpage, 'bitrates',
                default='{}'),
            video_id, transform_source=js_to_json, fatal=False)

        formats = []
        for format_url in try_get(bitrates, lambda x: x['mp4'], list) or []:
            if isinstance(format_url, compat_str):
                height = self._search_regex(
                    r'(\d+)[pP]\.', format_url, 'height', default=None)
                formats.append({
                    'url': format_url,
                    'format_id': format_field(height, None, '%sp'),
                    'height': int(height),
                })
        if not formats:
            playlist = self._download_xml(
                'https://media.joj.sk/services/Video.php?clip=%s' % video_id,
                video_id)
            for file_el in playlist.findall('./files/file'):
                path = file_el.get('path')
                if not path:
                    continue
                format_id = file_el.get('id') or file_el.get('label')
                formats.append({
                    'url': 'http://n16.joj.sk/storage/%s' % path.replace(
                        'dat/', '', 1),
                    'format_id': format_id,
                    'height': int_or_none(self._search_regex(
                        r'(\d+)[pP]', format_id or path, 'height',
                        default=None)),
                })
        self._sort_formats(formats)

        thumbnail = self._og_search_thumbnail(webpage)

        duration = int_or_none(self._search_regex(
            r'videoDuration\s*:\s*(\d+)', webpage, 'duration', fatal=False))

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


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

class JoveIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?jove\.com/video/(?P<id>[0-9]+)'
    _CHAPTERS_URL = 'http://www.jove.com/video-chapters?videoid={video_id:}'
    _TESTS = [
        {
            'url': 'http://www.jove.com/video/2744/electrode-positioning-montage-transcranial-direct-current',
            'md5': '93723888d82dbd6ba8b3d7d0cd65dd2b',
            'info_dict': {
                'id': '2744',
                'ext': 'mp4',
                'title': 'Electrode Positioning and Montage in Transcranial Direct Current Stimulation',
                'description': 'md5:015dd4509649c0908bc27f049e0262c6',
                'thumbnail': r're:^https?://.*\.png$',
                'upload_date': '20110523',
            }
        },
        {
            'url': 'http://www.jove.com/video/51796/culturing-caenorhabditis-elegans-axenic-liquid-media-creation',
            'md5': '914aeb356f416811d911996434811beb',
            'info_dict': {
                'id': '51796',
                'ext': 'mp4',
                'title': 'Culturing Caenorhabditis elegans in Axenic Liquid Media and Creation of Transgenic Worms by Microparticle Bombardment',
                'description': 'md5:35ff029261900583970c4023b70f1dc9',
                'thumbnail': r're:^https?://.*\.png$',
                'upload_date': '20140802',
            }
        },

    ]

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

        webpage = self._download_webpage(url, video_id)

        chapters_id = self._html_search_regex(
            r'/video-chapters\?videoid=([0-9]+)', webpage, 'chapters id')

        chapters_xml = self._download_xml(
            self._CHAPTERS_URL.format(video_id=chapters_id),
            video_id, note='Downloading chapters XML',
            errnote='Failed to download chapters XML')

        video_url = chapters_xml.attrib.get('video')
        if not video_url:
            raise ExtractorError('Failed to get the video URL')

        title = self._html_search_meta('citation_title', webpage, 'title')
        thumbnail = self._og_search_thumbnail(webpage)
        description = self._html_search_regex(
            r'

(.+?)
',
            webpage, 'description', fatal=False)
        publish_date = unified_strdate(self._html_search_meta(
            'citation_publication_date', webpage, 'publish date', fatal=False))
        comment_count = int(self._html_search_regex(
            r'<meta name="num_comments" content="(\d+) Comments?"',
            webpage, 'comment count', fatal=False))

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


code:
import re

from .common import InfoExtractor
from ..utils import unsmuggle_url

class JWPlatformIE(InfoExtractor):
    _VALID_URL = r'(?:https?://(?:content\.jwplatform|cdn\.jwplayer)\.com/(?:(?:feed|player|thumb|preview|manifest)s|jw6|v2/media)/|jwplatform:)(?P<id>[a-zA-Z0-9]{8})'
    _TESTS = [{
        'url': 'http://content.jwplatform.com/players/nPripu9l-ALJ3XQCI.js',
        'md5': 'fa8899fa601eb7c83a64e9d568bdf325',
        'info_dict': {
            'id': 'nPripu9l',
            'ext': 'mov',
            'title': 'Big Buck Bunny Trailer',
            'description': 'Big Buck Bunny is a short animated film by the Blender Institute. It is made using free and open source software.',
            'upload_date': '20081127',
            'timestamp': 1227796140,
        }
    }, {
        'url': 'https://cdn.jwplayer.com/players/nPripu9l-ALJ3XQCI.js',
        'only_matching': True,
    }]

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        for tag, key in ((r'(?:script|iframe)', 'src'), ('input', 'value')):
            # <input value=URL> is used by hyland.com
            # if we find <iframe>, dont look for <input>
            ret = re.findall(
                r'<%s[^>]+?%s=["\']((?:https?:)?//(?:content\.jwplatform|cdn\.jwplayer)\.com/players/[a-zA-Z0-9]{8})' % (tag, key),
                webpage)
            if ret:
                return ret
        mobj = re.search(r'<div\b[^>]* data-video-jw-id="([a-zA-Z0-9]{8})"', webpage)
        if mobj:
            return [f'jwplatform:{mobj.group(1)}']

    def _real_extract(self, url):
        url, smuggled_data = unsmuggle_url(url, {})
        self._initialize_geo_bypass({
            'countries': smuggled_data.get('geo_countries'),
        })
        video_id = self._match_id(url)
        json_data = self._download_json('https://cdn.jwplayer.com/v2/media/' + video_id, video_id)
        return self._parse_jwplayer_data(json_data, video_id)


code:
from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    ExtractorError,
    int_or_none,
    strip_or_none,
    str_or_none,
    traverse_obj,
    unified_timestamp,
)

class KakaoIE(InfoExtractor):
    _VALID_URL = r'https?://(?:play-)?tv\.kakao\.com/(?:channel/\d+|embed/player)/cliplink/(?P<id>\d+|[^?#&]+@my)'
    _API_BASE_TMPL = 'http://tv.kakao.com/api/v1/ft/playmeta/cliplink/%s/'
    _CDN_API = 'https://tv.kakao.com/katz/v1/ft/cliplink/%s/readyNplay?'

    _TESTS = [{
        'url': 'http://tv.kakao.com/channel/2671005/cliplink/301965083',
        'md5': '702b2fbdeb51ad82f5c904e8c0766340',
        'info_dict': {
            'id': '301965083',
            'ext': 'mp4',
            'title': '乃木坂46 バナナマン 「3期生紹介コーナーが始動！顔高低差GPも！」 『乃木坂工事中』',
            'description': '',
            'uploader_id': '2671005',
            'uploader': '그랑그랑이',
            'timestamp': 1488160199,
            'upload_date': '20170227',
            'like_count': int,
            'thumbnail': r're:http://.+/thumb\.png',
            'tags': ['乃木坂'],
            'view_count': int,
            'duration': 1503,
            'comment_count': int,
        }
    }, {
        'url': 'http://tv.kakao.com/channel/2653210/cliplink/300103180',
        'md5': 'a8917742069a4dd442516b86e7d66529',
        'info_dict': {
            'id': '300103180',
            'ext': 'mp4',
            'description': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)\r\n\r\n[쇼! 음악중심] 20160611, 507회',
            'title': '러블리즈 - Destiny (나의 지구) (Lovelyz - Destiny)',
            'uploader_id': '2653210',
            'uploader': '쇼! 음악중심',
            'timestamp': 1485684628,
            'upload_date': '20170129',
            'like_count': int,
            'thumbnail': r're:http://.+/thumb\.png',
            'tags': 'count:28',
            'view_count': int,
            'duration': 184,
            'comment_count': int,
        }
    }, {
        # geo restricted
        'url': 'https://tv.kakao.com/channel/3643855/cliplink/412069491',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        api_base = self._API_BASE_TMPL % video_id
        cdn_api_base = self._CDN_API % video_id

        query = {
            'player': 'monet_html5',
            'referer': url,
            'uuid': '',
            'service': 'kakao_tv',
            'section': '',
            'dteType': 'PC',
            'fields': ','.join([
                '-*', 'tid', 'clipLink', 'displayTitle', 'clip', 'title',
                'description', 'channelId', 'createTime', 'duration', 'playCount',
                'likeCount', 'commentCount', 'tagList', 'channel', 'name',
                'clipChapterThumbnailList', 'thumbnailUrl', 'timeInSec', 'isDefault',
                'videoOutputList', 'width', 'height', 'kbps', 'profile', 'label'])
        }

        api_json = self._download_json(
            api_base, video_id, 'Downloading video info')

        clip_link = api_json['clipLink']
        clip = clip_link['clip']

        title = clip.get('title') or clip_link.get('displayTitle')

        formats = []
        for fmt in clip.get('videoOutputList') or []:
            profile_name = fmt.get('profile')
            if not profile_name or profile_name == 'AUDIO':
                continue
            query.update({
                'profile': profile_name,
                'fields': '-*,code,message,url',
            })
            try:
                fmt_url_json = self._download_json(
                    cdn_api_base, video_id, query=query,
                    note='Downloading video URL for profile %s' % profile_name)
            except ExtractorError as e:
                if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                    resp = self._parse_json(e.cause.read().decode(), video_id)
                    if resp.get('code') == 'GeoBlocked':
                        self.raise_geo_restricted()
                raise

            fmt_url = traverse_obj(fmt_url_json, ('videoLocation', 'url'))
            if not fmt_url:
                continue

            formats.append({
                'url': fmt_url,
                'format_id': profile_name,
                'width': int_or_none(fmt.get('width')),
                'height': int_or_none(fmt.get('height')),
                'format_note': fmt.get('label'),
                'filesize': int_or_none(fmt.get('filesize')),
                'tbr': int_or_none(fmt.get('kbps')),
            })
        self._sort_formats(formats)

        thumbs = []
        for thumb in clip.get('clipChapterThumbnailList') or []:
            thumbs.append({
                'url': thumb.get('thumbnailUrl'),
                'id': str(thumb.get('timeInSec')),
                'preference': -1 if thumb.get('isDefault') else 0
            })
        top_thumbnail = clip.get('thumbnailUrl')
        if top_thumbnail:
            thumbs.append({
                'url': top_thumbnail,
                'preference': 10,
            })

        return {
            'id': video_id,
            'title': title,
            'description': strip_or_none(clip.get('description')),
            'uploader': traverse_obj(clip_link, ('channel', 'name')),
            'uploader_id': str_or_none(clip_link.get('channelId')),
            'thumbnails': thumbs,
            'timestamp': unified_timestamp(clip_link.get('createTime')),
            'duration': int_or_none(clip.get('duration')),
            'view_count': int_or_none(clip.get('playCount')),
            'like_count': int_or_none(clip.get('likeCount')),
            'comment_count': int_or_none(clip.get('commentCount')),
            'formats': formats,
            'tags': clip.get('tagList'),
        }


code:
import base64
import json
import re

from .common import InfoExtractor
from ..compat import (
    compat_urlparse,
    compat_parse_qs,
)
from ..utils import (
    clean_html,
    ExtractorError,
    format_field,
    int_or_none,
    unsmuggle_url,
    smuggle_url,
    traverse_obj,
)

class KalturaIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                (?:
                    kaltura:(?P<partner_id>\d+):(?P<id>[0-9a-z_]+)|
                    https?://
                        (:?(?:www|cdnapi(?:sec)?)\.)?kaltura\.com(?::\d+)?/
                        (?:
                            (?:
                                # flash player
                                index\.php/(?:kwidget|extwidget/preview)|
                                # html5 player
                                html5/html5lib/[^/]+/mwEmbedFrame\.php
                            )
                        )(?:/(?P<path>[^?]+))?(?:\?(?P<query>.*))?
                )
                '''
    _SERVICE_URL = 'http://cdnapi.kaltura.com'
    _SERVICE_BASE = '/api_v3/service/multirequest'
    # See https://github.com/kaltura/server/blob/master/plugins/content/caption/base/lib/model/enums/CaptionType.php
    _CAPTION_TYPES = {
        1: 'srt',
        2: 'ttml',
        3: 'vtt',
    }
    _TESTS = [
        {
            'url': 'kaltura:269692:1_1jc2y3e4',
            'md5': '3adcbdb3dcc02d647539e53f284ba171',
            'info_dict': {
                'id': '1_1jc2y3e4',
                'ext': 'mp4',
                'title': 'Straight from the Heart',
                'upload_date': '20131219',
                'uploader_id': 'mlundberg@wolfgangsvault.com',
                'description': 'The Allman Brothers Band, 12/16/1981',
                'thumbnail': 're:^https?://.*/thumbnail/.*',
                'timestamp': int,
            },
        },
        {
            'url': 'http://www.kaltura.com/index.php/kwidget/cache_st/1300318621/wid/_269692/uiconf_id/3873291/entry_id/1_1jc2y3e4',
            'only_matching': True,
        },
        {
            'url': 'https://cdnapisec.kaltura.com/index.php/kwidget/wid/_557781/uiconf_id/22845202/entry_id/1_plr1syf3',
            'only_matching': True,
        },
        {
            'url': 'https://cdnapisec.kaltura.com/html5/html5lib/v2.30.2/mwEmbedFrame.php/p/1337/uiconf_id/20540612/entry_id/1_sf5ovm7u?wid=_243342',
            'only_matching': True,
        },
        {
            # video with subtitles
            'url': 'kaltura:111032:1_cw786r8q',
            'only_matching': True,
        },
        {
            # video with ttml subtitles (no fileExt)
            'url': 'kaltura:1926081:0_l5ye1133',
            'info_dict': {
                'id': '0_l5ye1133',
                'ext': 'mp4',
                'title': 'What Can You Do With Python?',
                'upload_date': '20160221',
                'uploader_id': 'stork',
                'thumbnail': 're:^https?://.*/thumbnail/.*',
                'timestamp': int,
                'subtitles': {
                    'en': [{
                        'ext': 'ttml',
                    }],
                },
            },
            'skip': 'Gone. Maybe https://www.safaribooksonline.com/library/tutorials/introduction-to-python-anon/3469/',
            'params': {
                'skip_download': True,
            },
        },
        {
            'url': 'https://www.kaltura.com/index.php/extwidget/preview/partner_id/1770401/uiconf_id/37307382/entry_id/0_58u8kme7/embed/iframe?&flashvars[streamerType]=auto',
            'only_matching': True,
        },
        {
            'url': 'https://www.kaltura.com:443/index.php/extwidget/preview/partner_id/1770401/uiconf_id/37307382/entry_id/0_58u8kme7/embed/iframe?&flashvars[streamerType]=auto',
            'only_matching': True,
        },
        {
            # unavailable source format
            'url': 'kaltura:513551:1_66x4rg7o',
            'only_matching': True,
        }
    ]

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        # Embed codes: https://knowledge.kaltura.com/embedding-kaltura-media-players-your-site
        finditer = (
            list(re.finditer(
                r"""(?xs)
                    kWidget\.(?:thumb)?[Ee]mbed\(
                    \{.*?
                        (?P<q1>['"])wid(?P=q1)\s*:\s*
                        (?P<q2>['"])_?(?P<partner_id>(?:(?!(?P=q2)).)+)(?P=q2),.*?
                        (?P<q3>['"])entry_?[Ii]d(?P=q3)\s*:\s*
                        (?P<q4>['"])(?P<id>(?:(?!(?P=q4)).)+)(?P=q4)(?:,|\s*\})
                """, webpage))
            or list(re.finditer(
                r'''(?xs)
                    (?P<q1>["'])
                        (?:https?:)?//cdnapi(?:sec)?\.kaltura\.com(?::\d+)?/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)(?:(?!(?P=q1)).)*
                    (?P=q1).*?
                    (?:
                        (?:
                            entry_?[Ii]d|
                            (?P<q2>["'])entry_?[Ii]d(?P=q2)
                        )\s*:\s*|
                        \[\s*(?P<q2_1>["'])entry_?[Ii]d(?P=q2_1)\s*\]\s*=\s*
                    )
                    (?P<q3>["'])(?P<id>(?:(?!(?P=q3)).)+)(?P=q3)
                ''', webpage))
            or list(re.finditer(
                r'''(?xs)
                    <(?:iframe[^>]+src|meta[^>]+\bcontent)=(?P<q1>["'])\s*
                      (?:https?:)?//(?:(?:www|cdnapi(?:sec)?)\.)?kaltura\.com/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)
                      (?:(?!(?P=q1)).)*
                      [?&;]entry_id=(?P<id>(?:(?!(?P=q1))[^&])+)
                      (?:(?!(?P=q1)).)*
                    (?P=q1)
                ''', webpage))
        )
        urls = []
        for mobj in finditer:
            embed_info = mobj.groupdict()
            for k, v in embed_info.items():
                if v:
                    embed_info[k] = v.strip()
            embed_url = 'kaltura:%(partner_id)s:%(id)s' % embed_info
            escaped_pid = re.escape(embed_info['partner_id'])
            service_mobj = re.search(
                r'<script[^>]+src=(["\'])(?P<id>(?:https?:)?//(?:(?!\1).)+)/p/%s/sp/%s00/embedIframeJs' % (escaped_pid, escaped_pid),
                webpage)
            if service_mobj:
                embed_url = smuggle_url(embed_url, {'service_url': service_mobj.group('id')})
            urls.append(embed_url)
        return urls

    def _kaltura_api_call(self, video_id, actions, service_url=None, *args, **kwargs):
        params = actions[0]
        params.update({i: a for i, a in enumerate(actions[1:], start=1)})

        data = self._download_json(
            (service_url or self._SERVICE_URL) + self._SERVICE_BASE,
            video_id, data=json.dumps(params).encode('utf-8'),
            headers={
                'Content-Type': 'application/json',
                'Accept-Encoding': 'gzip, deflate, br',
            }, *args, **kwargs)

        for idx, status in enumerate(data):
            if not isinstance(status, dict):
                continue
            if status.get('objectType') == 'KalturaAPIException':
                raise ExtractorError(
                    '%s said: %s (%d)' % (self.IE_NAME, status['message'], idx))

        data[1] = traverse_obj(data, (1, 'objects', 0))

        return data

    def _get_video_info(self, video_id, partner_id, service_url=None):
        actions = [
            {
                'apiVersion': '3.3.0',
                'clientTag': 'html5:v3.1.0',
                'format': 1,  # JSON, 2 = XML, 3 = PHP
                'ks': '',
                'partnerId': partner_id,
            },
            {
                'expiry': 86400,
                'service': 'session',
                'action': 'startWidgetSession',
                'widgetId': '_%s' % partner_id,
            },
            {
                'action': 'list',
                'filter': {'redirectFromEntryId': video_id},
                'service': 'baseentry',
                'ks': '{1:result:ks}',
                'responseProfile': {
                    'type': 1,
                    'fields': 'createdAt,dataUrl,duration,name,plays,thumbnailUrl,userId',
                },
            },
            {
                'action': 'getbyentryid',
                'entryId': video_id,
                'service': 'flavorAsset',
                'ks': '{1:result:ks}',
            },
            {
                'action': 'list',
                'filter:entryIdEqual': video_id,
                'service': 'caption_captionasset',
                'ks': '{1:result:ks}',
            },
        ]
        return self._kaltura_api_call(
            video_id, actions, service_url, note='Downloading video info JSON')

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

        mobj = self._match_valid_url(url)
        partner_id, entry_id = mobj.group('partner_id', 'id')
        ks = None
        captions = None
        if partner_id and entry_id:
            _, info, flavor_assets, captions = self._get_video_info(entry_id, partner_id, smuggled_data.get('service_url'))
        else:
            path, query = mobj.group('path', 'query')
            if not path and not query:
                raise ExtractorError('Invalid URL', expected=True)
            params = {}
            if query:
                params = compat_parse_qs(query)
            if path:
                splitted_path = path.split('/')
                params.update(dict((zip(splitted_path[::2], [[v] for v in splitted_path[1::2]]))))
            if 'wid' in params:
                partner_id = params['wid'][0][1:]
            elif 'p' in params:
                partner_id = params['p'][0]
            elif 'partner_id' in params:
                partner_id = params['partner_id'][0]
            else:
                raise ExtractorError('Invalid URL', expected=True)
            if 'entry_id' in params:
                entry_id = params['entry_id'][0]
                _, info, flavor_assets, captions = self._get_video_info(entry_id, partner_id)
            elif 'uiconf_id' in params and 'flashvars[referenceId]' in params:
                reference_id = params['flashvars[referenceId]'][0]
                webpage = self._download_webpage(url, reference_id)
                entry_data = self._parse_json(self._search_regex(
                    r'window\.kalturaIframePackageData\s*=\s*({.*});',
                    webpage, 'kalturaIframePackageData'),
                    reference_id)['entryResult']
                info, flavor_assets = entry_data['meta'], entry_data['contextData']['flavorAssets']
                entry_id = info['id']
                # Unfortunately, data returned in kalturaIframePackageData lacks
                # captions so we will try requesting the complete data using
                # regular approach since we now know the entry_id
                try:
                    _, info, flavor_assets, captions = self._get_video_info(
                        entry_id, partner_id)
                except ExtractorError:
                    # Regular scenario failed but we already have everything
                    # extracted apart from captions and can process at least
                    # with this
                    pass
            else:
                raise ExtractorError('Invalid URL', expected=True)
            ks = params.get('flashvars[ks]', [None])[0]

        source_url = smuggled_data.get('source_url')
        if source_url:
            referrer = base64.b64encode(
                '://'.join(compat_urlparse.urlparse(source_url)[:2])
                .encode('utf-8')).decode('utf-8')
        else:
            referrer = None

        def sign_url(unsigned_url):
            if ks:
                unsigned_url += '/ks/%s' % ks
            if referrer:
                unsigned_url += '?referrer=%s' % referrer
            return unsigned_url

        data_url = info['dataUrl']
        if '/flvclipper/' in data_url:
            data_url = re.sub(r'/flvclipper/.*', '/serveFlavor', data_url)

        formats = []
        subtitles = {}
        for f in flavor_assets:
            # Continue if asset is not ready
            if f.get('status') != 2:
                continue
            # Original format that's not available (e.g. kaltura:1926081:0_c03e1b5g)
            # skip for now.
            if f.get('fileExt') == 'chun':
                continue
            # DRM-protected video, cannot be decrypted
            if not self.get_param('allow_unplayable_formats') and f.get('fileExt') == 'wvm':
                continue
            if not f.get('fileExt'):
                # QT indicates QuickTime; some videos have broken fileExt
                if f.get('containerFormat') == 'qt':
                    f['fileExt'] = 'mov'
                else:
                    f['fileExt'] = 'mp4'
            video_url = sign_url(
                '%s/flavorId/%s' % (data_url, f['id']))
            format_id = '%(fileExt)s-%(bitrate)s' % f
            # Source format may not be available (e.g. kaltura:513551:1_66x4rg7o)
            if f.get('isOriginal') is True and not self._is_valid_url(
                    video_url, entry_id, format_id):
                continue
            # audio-only has no videoCodecId (e.g. kaltura:1926081:0_c03e1b5g
            # -f mp4-56)
            vcodec = 'none' if 'videoCodecId' not in f and f.get(
                'frameRate') == 0 else f.get('videoCodecId')
            formats.append({
                'format_id': format_id,
                'ext': f.get('fileExt'),
                'tbr': int_or_none(f['bitrate']),
                'fps': int_or_none(f.get('frameRate')),
                'filesize_approx': int_or_none(f.get('size'), invscale=1024),
                'container': f.get('containerFormat'),
                'vcodec': vcodec,
                'height': int_or_none(f.get('height')),
                'width': int_or_none(f.get('width')),
                'url': video_url,
            })
        if '/playManifest/' in data_url:
            m3u8_url = sign_url(data_url.replace(
                'format/url', 'format/applehttp'))
            fmts, subs = self._extract_m3u8_formats_and_subtitles(
                m3u8_url, entry_id, 'mp4', 'm3u8_native',
                m3u8_id='hls', fatal=False)
            formats.extend(fmts)
            self._merge_subtitles(subs, target=subtitles)

        self._sort_formats(formats)

        if captions:
            for caption in captions.get('objects', []):
                # Continue if caption is not ready
                if caption.get('status') != 2:
                    continue
                if not caption.get('id'):
                    continue
                caption_format = int_or_none(caption.get('format'))
                subtitles.setdefault(caption.get('languageCode') or caption.get('language'), []).append({
                    'url': '%s/api_v3/service/caption_captionasset/action/serve/captionAssetId/%s' % (self._SERVICE_URL, caption['id']),
                    'ext': caption.get('fileExt') or self._CAPTION_TYPES.get(caption_format) or 'ttml',
                })

        return {
            'id': entry_id,
            'title': info['name'],
            'formats': formats,
            'subtitles': subtitles,
            'description': clean_html(info.get('description')),
            'thumbnail': info.get('thumbnailUrl'),
            'duration': info.get('duration'),
            'timestamp': info.get('createdAt'),
            'uploader_id': format_field(info, 'userId', ignore=('None', None)),
            'view_count': int_or_none(info.get('plays')),
        }


code:
from .common import InfoExtractor

class KaraoketvIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?karaoketv\.co\.il/[^/]+/(?P<id>\d+)'
    _TEST = {
        'url': 'http://www.karaoketv.co.il/%D7%A9%D7%99%D7%A8%D7%99_%D7%A7%D7%A8%D7%99%D7%95%D7%A7%D7%99/58356/%D7%90%D7%99%D7%96%D7%95%D7%9F',
        'info_dict': {
            'id': '58356',
            'ext': 'flv',
            'title': 'קריוקי של איזון',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        }
    }

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

        webpage = self._download_webpage(url, video_id)
        api_page_url = self._search_regex(
            r'<iframe[^>]+src=(["\'])(?P<url>https?://www\.karaoke\.co\.il/api_play\.php\?.+?)\1',
            webpage, 'API play URL', group='url')

        api_page = self._download_webpage(api_page_url, video_id)
        video_cdn_url = self._search_regex(
            r'<iframe[^>]+src=(["\'])(?P<url>https?://www\.video-cdn\.com/embed/iframe/.+?)\1',
            api_page, 'video cdn URL', group='url')

        video_cdn = self._download_webpage(video_cdn_url, video_id)
        play_path = self._parse_json(
            self._search_regex(
                r'var\s+options\s*=\s*({.+?});', video_cdn, 'options'),
            video_id)['clip']['url']

        settings = self._parse_json(
            self._search_regex(
                r'var\s+settings\s*=\s*({.+?});', video_cdn, 'servers', default='{}'),
            video_id, fatal=False) or {}

        servers = settings.get('servers')
        if not servers or not isinstance(servers, list):
            servers = ('wowzail.video-cdn.com:80/vodcdn', )

        formats = [{
            'url': 'rtmp://%s' % server if not server.startswith('rtmp') else server,
            'play_path': play_path,
            'app': 'vodcdn',
            'page_url': video_cdn_url,
            'player_url': 'http://www.video-cdn.com/assets/flowplayer/flowplayer.commercial-3.2.18.swf',
            'rtmp_real_time': True,
            'ext': 'flv',
        } for server in servers]

        return {
            'id': video_id,
            'title': self._og_search_title(webpage),
            'formats': formats,
        }


code:
from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    fix_xml_ampersands,
    float_or_none,
    xpath_with_ns,
    xpath_text,
)

class KarriereVideosIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?karrierevideos\.at(?:/[^/]+)+/(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'http://www.karrierevideos.at/berufsvideos/mittlere-hoehere-schulen/altenpflegerin',
        'info_dict': {
            'id': '32c91',
            'ext': 'flv',
            'title': 'AltenpflegerIn',
            'description': 'md5:dbadd1259fde2159a9b28667cb664ae2',
            'thumbnail': r're:^http://.*\.png',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        }
    }, {
        # broken ampersands
        'url': 'http://www.karrierevideos.at/orientierung/vaeterkarenz-und-neue-chancen-fuer-muetter-baby-was-nun',
        'info_dict': {
            'id': '5sniu',
            'ext': 'flv',
            'title': 'Väterkarenz und neue Chancen für Mütter - "Baby - was nun?"',
            'description': 'md5:97092c6ad1fd7d38e9d6a5fdeb2bcc33',
            'thumbnail': r're:^http://.*\.png',
        },
        'params': {
            # rtmp download
            'skip_download': True,
        }
    }]

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

        webpage = self._download_webpage(url, video_id)

        title = (self._html_search_meta('title', webpage, default=None)
                 or self._search_regex(r'
([^<]+)
', webpage, 'video title'))

        video_id = self._search_regex(
            r'/config/video/(.+?)\.xml', webpage, 'video id')
        # Server returns malformed headers
        # Force Accept-Encoding: * to prevent gzipped results
        playlist = self._download_xml(
            'http://www.karrierevideos.at/player-playlist.xml.php?p=%s' % video_id,
            video_id, transform_source=fix_xml_ampersands,
            headers={'Accept-Encoding': '*'})

        NS_MAP = {
            'jwplayer': 'http://developer.longtailvideo.com/trac/wiki/FlashFormats'
        }

        def ns(path):
            return xpath_with_ns(path, NS_MAP)

        item = playlist.find('./tracklist/item')
        video_file = xpath_text(
            item, ns('./jwplayer:file'), 'video url', fatal=True)
        streamer = xpath_text(
            item, ns('./jwplayer:streamer'), 'streamer', fatal=True)

        uploader = xpath_text(
            item, ns('./jwplayer:author'), 'uploader')
        duration = float_or_none(
            xpath_text(item, ns('./jwplayer:duration'), 'duration'))

        description = self._html_search_regex(
            r'(?s)
(.+?)
',
            webpage, 'description')

        thumbnail = self._html_search_meta(
            'thumbnail', webpage, 'thumbnail')
        if thumbnail:
            thumbnail = compat_urlparse.urljoin(url, thumbnail)

        return {
            'id': video_id,
            'url': streamer.replace('rtmpt', 'rtmp'),
            'play_path': 'mp4:%s' % video_file,
            'ext': 'flv',
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'uploader': uploader,
            'duration': duration,
        }


code:
import re

from .common import InfoExtractor
from ..aes import aes_decrypt_text
from ..compat import compat_urllib_parse_unquote
from ..utils import (
    determine_ext,
    ExtractorError,
    format_field,
    int_or_none,
    str_to_int,
    strip_or_none,
    url_or_none,
)

class KeezMoviesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?keezmovies\.com/video/(?:(?P<display_id>[^/]+)-)?(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.keezmovies.com/video/arab-wife-want-it-so-bad-i-see-she-thirsty-and-has-tiny-money-18070681',
        'md5': '2ac69cdb882055f71d82db4311732a1a',
        'info_dict': {
            'id': '18070681',
            'display_id': 'arab-wife-want-it-so-bad-i-see-she-thirsty-and-has-tiny-money',
            'ext': 'mp4',
            'title': 'Arab wife want it so bad I see she thirsty and has tiny money.',
            'thumbnail': None,
            'view_count': int,
            'age_limit': 18,
        }
    }, {
        'url': 'http://www.keezmovies.com/video/18070681',
        'only_matching': True,
    }]

    def _extract_info(self, url, fatal=True):
        mobj = self._match_valid_url(url)
        video_id = mobj.group('id')
        display_id = (mobj.group('display_id')
                      if 'display_id' in mobj.groupdict()
                      else None) or mobj.group('id')

        webpage = self._download_webpage(
            url, display_id, headers={'Cookie': 'age_verified=1'})

        formats = []
        format_urls = set()

        title = None
        thumbnail = None
        duration = None
        encrypted = False

        def extract_format(format_url, height=None):
            format_url = url_or_none(format_url)
            if not format_url or not format_url.startswith(('http', '//')):
                return
            if format_url in format_urls:
                return
            format_urls.add(format_url)
            tbr = int_or_none(self._search_regex(
                r'[/_](\d+)[kK][/_]', format_url, 'tbr', default=None))
            if not height:
                height = int_or_none(self._search_regex(
                    r'[/_](\d+)[pP][/_]', format_url, 'height', default=None))
            if encrypted:
                format_url = aes_decrypt_text(
                    video_url, title, 32).decode('utf-8')
            formats.append({
                'url': format_url,
                'format_id': format_field(height, None, '%dp'),
                'height': height,
                'tbr': tbr,
            })

        flashvars = self._parse_json(
            self._search_regex(
                r'flashvars\s*=\s*({.+?});', webpage,
                'flashvars', default='{}'),
            display_id, fatal=False)

        if flashvars:
            title = flashvars.get('video_title')
            thumbnail = flashvars.get('image_url')
            duration = int_or_none(flashvars.get('video_duration'))
            encrypted = flashvars.get('encrypted') is True
            for key, value in flashvars.items():
                mobj = re.search(r'quality_(\d+)[pP]', key)
                if mobj:
                    extract_format(value, int(mobj.group(1)))
            video_url = flashvars.get('video_url')
            if video_url and determine_ext(video_url, None):
                extract_format(video_url)

        video_url = self._html_search_regex(
            r'flashvars\.video_url\s*=\s*(["\'])(?P<url>http.+?)\1',
            webpage, 'video url', default=None, group='url')
        if video_url:
            extract_format(compat_urllib_parse_unquote(video_url))

        if not formats:
            if 'title="This video is no longer available"' in webpage:
                self.raise_no_formats(
                    'Video %s is no longer available' % video_id, expected=True)

        try:
            self._sort_formats(formats)
        except ExtractorError:
            if fatal:
                raise

        if not title:
            title = self._html_search_regex(
                r'<h1[^>]*>([^<]+)', webpage, 'title')

        return webpage, {
            'id': video_id,
            'display_id': display_id,
            'title': strip_or_none(title),
            'thumbnail': thumbnail,
            'duration': duration,
            'age_limit': 18,
            'formats': formats,
        }

    def _real_extract(self, url):
        webpage, info = self._extract_info(url, fatal=False)
        if not info['formats']:
            return self.url_result(url, 'Generic')
        info['view_count'] = str_to_int(self._search_regex(
            r'<b>([\d,.]+)</b> Views?', webpage, 'view count', fatal=False))
        return info


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

class KelbyOneIE(InfoExtractor):
    _VALID_URL = r'https?://members\.kelbyone\.com/course/(?P<id>[^$&?#/]+)'

    _TESTS = [{
        'url': 'https://members.kelbyone.com/course/glyn-dewis-mastering-selections/',
        'playlist_mincount': 1,
        'info_dict': {
            'id': 'glyn-dewis-mastering-selections',
            'title': 'Trailer - Mastering Selections in Photoshop',
        },
        'playlist': [{
            'info_dict': {
                'id': 'MkiOnLqK',
                'ext': 'mp4',
                'title': 'Trailer - Mastering Selections in Photoshop',
                'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
                'thumbnail': 'https://content.jwplatform.com/v2/media/MkiOnLqK/poster.jpg?width=720',
                'timestamp': 1601568639,
                'duration': 90,
                'upload_date': '20201001',
            },
        }]
    }]

    def _entries(self, playlist):
        for item in playlist:
            video_id = item['mediaid']
            thumbnails = [{
                'url': image.get('src'),
                'width': int_or_none(image.get('width')),
            } for image in item.get('images') or []]
            formats, subtitles = [], {}
            for source in item.get('sources') or []:
                if not source.get('file'):
                    continue
                if source.get('type') == 'application/vnd.apple.mpegurl':
                    fmts, subs = self._extract_m3u8_formats_and_subtitles(source['file'], video_id)
                    formats.extend(fmts)
                    subtitles = self._merge_subtitles(subs, subtitles)
                elif source.get('type') == 'audio/mp4':
                    formats.append({
                        'format_id': source.get('label'),
                        'url': source['file'],
                        'vcodec': 'none',
                    })
                else:
                    formats.append({
                        'format_id': source.get('label'),
                        'height': source.get('height'),
                        'width': source.get('width'),
                        'url': source['file'],
                    })
            for track in item.get('tracks'):
                if track.get('kind') == 'captions' and track.get('file'):
                    subtitles.setdefault('en', []).append({
                        'url': track['file'],
                    })
            self._sort_formats(formats)
            yield {
                'id': video_id,
                'title': item['title'],
                'description': item.get('description'),
                'thumbnails': thumbnails,
                'thumbnail': item.get('image'),
                'timestamp': item.get('pubdate'),
                'duration': item.get('duration'),
                'formats': formats,
                'subtitles': subtitles,
            }

    def _real_extract(self, url):
        item_id = self._match_id(url)
        webpage = self._download_webpage(url, item_id)
        playlist_url = self._html_search_regex(r'playlist"\:"(https.*content\.jwplatform\.com.*json)"', webpage, 'playlist url').replace('\\', '')
        course_data = self._download_json(playlist_url, item_id)
        return self.playlist_result(self._entries(course_data['playlist']), item_id,
                                    course_data.get('title'), course_data.get('description'))


code:
from .canvas import CanvasIE
from .common import InfoExtractor
from ..compat import compat_urllib_parse_unquote
from ..utils import (
    int_or_none,
    parse_iso8601,
)

class KetnetIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?ketnet\.be/(?P<id>(?:[^/]+/)*[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.ketnet.be/kijken/n/nachtwacht/3/nachtwacht-s3a1-de-greystook',
        'md5': '37b2b7bb9b3dcaa05b67058dc3a714a9',
        'info_dict': {
            'id': 'pbs-pub-aef8b526-115e-4006-aa24-e59ff6c6ef6f$vid-ddb815bf-c8e7-467b-8879-6bad7a32cebd',
            'ext': 'mp4',
            'title': 'Nachtwacht - Reeks 3: Aflevering 1',
            'description': 'De Nachtwacht krijgt te maken met een parasiet',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 1468.02,
            'timestamp': 1609225200,
            'upload_date': '20201229',
            'series': 'Nachtwacht',
            'season': 'Reeks 3',
            'episode': 'De Greystook',
            'episode_number': 1,
        },
        'expected_warnings': ['is not a supported codec', 'Unknown MIME type'],
    }, {
        'url': 'https://www.ketnet.be/themas/karrewiet/jaaroverzicht-20200/karrewiet-het-jaar-van-black-mamba',
        'only_matching': True,
    }]

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

        video = self._download_json(
            'https://senior-bff.ketnet.be/graphql', display_id, query={
                'query': '''{
  video(id: "content/ketnet/nl/%s.model.json") {
    description
    episodeNr
    imageUrl
    mediaReference
    programTitle
    publicationDate
    seasonTitle
    subtitleVideodetail
    titleVideodetail
  }
}''' % display_id,
            })['data']['video']

        mz_id = compat_urllib_parse_unquote(video['mediaReference'])

        return {
            '_type': 'url_transparent',
            'id': mz_id,
            'title': video['titleVideodetail'],
            'url': 'https://mediazone.vrt.be/api/v1/ketnet/assets/' + mz_id,
            'thumbnail': video.get('imageUrl'),
            'description': video.get('description'),
            'timestamp': parse_iso8601(video.get('publicationDate')),
            'series': video.get('programTitle'),
            'season': video.get('seasonTitle'),
            'episode': video.get('subtitleVideodetail'),
            'episode_number': int_or_none(video.get('episodeNr')),
            'ie_key': CanvasIE.ie_key(),
        }