code:
import json

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

class KhanAcademyBaseIE(InfoExtractor):
    _VALID_URL_TEMPL = r'https?://(?:www\.)?khanacademy\.org/(?P<id>(?:[^/]+/){%s}%s[^?#/&]+)'

    def _parse_video(self, video):
        return {
            '_type': 'url_transparent',
            'url': video['youtubeId'],
            'id': video.get('slug'),
            'title': video.get('title'),
            'thumbnail': video.get('imageUrl') or video.get('thumbnailUrl'),
            'duration': int_or_none(video.get('duration')),
            'description': video.get('description'),
            'ie_key': 'Youtube',
        }

    def _real_extract(self, url):
        display_id = self._match_id(url)
        content = self._download_json(
            'https://www.khanacademy.org/api/internal/graphql/FetchContentData',
            display_id, query={
                'fastly_cacheable': 'persist_until_publish',
                'hash': '4134764944',
                'lang': 'en',
                'variables': json.dumps({
                    'path': display_id,
                    'queryParams': 'lang=en',
                    'isModal': False,
                    'followRedirects': True,
                    'countryCode': 'US',
                }),
            })['data']['contentJson']
        return self._parse_component_props(self._parse_json(content, display_id)['componentProps'])

class KhanAcademyIE(KhanAcademyBaseIE):
    IE_NAME = 'khanacademy'
    _VALID_URL = KhanAcademyBaseIE._VALID_URL_TEMPL % ('4', 'v/')
    _TEST = {
        'url': 'https://www.khanacademy.org/computing/computer-science/cryptography/crypt/v/one-time-pad',
        'md5': '9c84b7b06f9ebb80d22a5c8dedefb9a0',
        'info_dict': {
            'id': 'FlIG3TvQCBQ',
            'ext': 'mp4',
            'title': 'The one-time pad',
            'description': 'The perfect cipher',
            'duration': 176,
            'uploader': 'Brit Cruise',
            'uploader_id': 'khanacademy',
            'upload_date': '20120411',
            'timestamp': 1334170113,
            'license': 'cc-by-nc-sa',
        },
        'add_ie': ['Youtube'],
    }

    def _parse_component_props(self, component_props):
        video = component_props['tutorialPageData']['contentModel']
        info = self._parse_video(video)
        author_names = video.get('authorNames')
        info.update({
            'uploader': ', '.join(author_names) if author_names else None,
            'timestamp': parse_iso8601(video.get('dateAdded')),
            'license': video.get('kaUserLicense'),
        })
        return info

class KhanAcademyUnitIE(KhanAcademyBaseIE):
    IE_NAME = 'khanacademy:unit'
    _VALID_URL = (KhanAcademyBaseIE._VALID_URL_TEMPL % ('2', '')) + '/?(?:[?#&]|$)'
    _TEST = {
        'url': 'https://www.khanacademy.org/computing/computer-science/cryptography',
        'info_dict': {
            'id': 'cryptography',
            'title': 'Cryptography',
            'description': 'How have humans protected their secret messages through history? What has changed today?',
        },
        'playlist_mincount': 31,
    }

    def _parse_component_props(self, component_props):
        curation = component_props['curation']

        entries = []
        tutorials = try_get(curation, lambda x: x['tabs'][0]['modules'][0]['tutorials'], list) or []
        for tutorial_number, tutorial in enumerate(tutorials, 1):
            chapter_info = {
                'chapter': tutorial.get('title'),
                'chapter_number': tutorial_number,
                'chapter_id': tutorial.get('id'),
            }
            for content_item in (tutorial.get('contentItems') or []):
                if content_item.get('kind') == 'Video':
                    info = self._parse_video(content_item)
                    info.update(chapter_info)
                    entries.append(info)

        return self.playlist_result(
            entries, curation.get('unit'), curation.get('title'),
            curation.get('description'))


code:
from .common import InfoExtractor
from .dailymotion import DailymotionIE

class KickerIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)kicker\.(?:de)/(?P<id>[\w-]+)/video'
    _TESTS = [{
        'url': 'https://www.kicker.de/pogba-dembel-co-die-top-11-der-abloesefreien-spieler-905049/video',
        'info_dict': {
            'id': 'km04mrK0DrRAVxy2GcA',
            'title': 'md5:b91d145bac5745ac58d5479d8347a875',
            'ext': 'mp4',
            'duration': 350,
            'description': 'md5:a5a3dd77dbb6550dbfb997be100b9998',
            'uploader_id': 'x2dfupo',
            'timestamp': 1654677626,
            'like_count': int,
            'uploader': 'kicker.de',
            'view_count': int,
            'age_limit': 0,
            'thumbnail': r're:https://s\d+\.dmcdn\.net/v/T-x741YeYAx8aSZ0Z/x1080',
            'tags': ['published', 'category.InternationalSoccer'],
            'upload_date': '20220608'
        }
    }, {
        'url': 'https://www.kicker.de/ex-unioner-in-der-bezirksliga-felix-kroos-vereinschallenge-in-pankow-902825/video',
        'info_dict': {
            'id': 'k2omNsJKdZ3TxwxYSFJ',
            'title': 'md5:72ec24d7f84b8436fe1e89d198152adf',
            'ext': 'mp4',
            'uploader_id': 'x2dfupo',
            'duration': 331,
            'timestamp': 1652966015,
            'thumbnail': r're:https?://s\d+\.dmcdn\.net/v/TxU4Z1YYCmtisTbMq/x1080',
            'tags': ['FELIX KROOS', 'EINFACH MAL LUPPEN', 'KROOS', 'FSV FORTUNA PANKOW', 'published', 'category.Amateurs', 'marketingpreset.Spreekick'],
            'age_limit': 0,
            'view_count': int,
            'upload_date': '20220519',
            'uploader': 'kicker.de',
            'description': 'md5:0c2060c899a91c8bf40f578f78c5846f',
            'like_count': int,
        }
    }]

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

        webpage = self._download_webpage(url, video_slug)
        dailymotion_video_id = self._search_regex(
            r'data-dmprivateid\s*=\s*[\'"](?P<video_id>\w+)', webpage,
            'video id', group='video_id')

        return self.url_result(
            f'https://www.dailymotion.com/video/{dailymotion_video_id}',
            ie=DailymotionIE, video_title=self._html_extract_title(webpage))

repo_name: Lesmiscore/ytdl


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

class KickStarterIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?kickstarter\.com/projects/(?P<id>[^/]*)/.*'
    _TESTS = [{
        'url': 'https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant/description',
        'md5': 'c81addca81327ffa66c642b5d8b08cab',
        'info_dict': {
            'id': '1404461844',
            'ext': 'mp4',
            'title': 'Intersection: The Story of Josh Grant by Kyle Cowling',
            'description': (
                'A unique motocross documentary that examines the '
                'life and mind of one of sports most elite athletes: Josh Grant.'
            ),
        },
    }, {
        'note': 'Embedded video (not using the native kickstarter video service)',
        'url': 'https://www.kickstarter.com/projects/597507018/pebble-e-paper-watch-for-iphone-and-android/posts/659178',
        'info_dict': {
            'id': '78704821',
            'ext': 'mp4',
            'uploader_id': 'pebble',
            'uploader': 'Pebble Technology',
            'title': 'Pebble iOS Notifications',
        },
        'add_ie': ['Vimeo'],
    }, {
        'url': 'https://www.kickstarter.com/projects/1420158244/power-drive-2000/widget/video.html',
        'info_dict': {
            'id': '1420158244',
            'ext': 'mp4',
            'title': 'Power Drive 2000',
        },
    }]

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

        title = self._html_search_regex(
            r'<title>\s*(.*?)(?:\s*—\s*Kickstarter)?\s*</title>',
            webpage, 'title')
        video_url = self._search_regex(
            r'data-video-url="(.*?)"',
            webpage, 'video URL', default=None)
        if video_url is None:  # No native kickstarter, look for embedded videos
            return {
                '_type': 'url_transparent',
                'ie_key': 'Generic',
                'url': smuggle_url(url, {'to_generic': True}),
                'title': title,
            }

        thumbnail = self._og_search_thumbnail(webpage, default=None)
        if thumbnail is None:
            thumbnail = self._html_search_regex(
                r'<img[^>]+class="[^"]+\s*poster\s*[^"]+"[^>]+src="([^"]+)"',
                webpage, 'thumbnail image', fatal=False)
        return {
            'id': video_id,
            'url': video_url,
            'title': title,
            'description': self._og_search_description(webpage, default=None),
            'thumbnail': thumbnail,
        }


code:
from .common import InfoExtractor
from ..compat import (
    compat_str,
    compat_urllib_parse_unquote,
)
from ..utils import (
    int_or_none,
    parse_iso8601,
    strip_or_none,
    try_get,
)

class KinjaEmbedIE(InfoExtractor):
    IENAME = 'kinja:embed'
    _DOMAIN_REGEX = r'''(?:[^.]+\.)?
        (?:
            avclub|
            clickhole|
            deadspin|
            gizmodo|
            jalopnik|
            jezebel|
            kinja|
            kotaku|
            lifehacker|
            splinternews|
            the(?:inventory|onion|root|takeout)
        )\.com'''
    _COMMON_REGEX = r'''/
        (?:
            ajax/inset|
            embed/video
        )/iframe\?.*?\bid='''
    _VALID_URL = r'''(?x)https?://%s%s
        (?P<type>
            fb|
            imgur|
            instagram|
            jwp(?:layer)?-video|
            kinjavideo|
            mcp|
            megaphone|
            ooyala|
            soundcloud(?:-playlist)?|
            tumblr-post|
            twitch-stream|
            twitter|
            ustream-channel|
            vimeo|
            vine|
            youtube-(?:list|video)
        )-(?P<id>[^&]+)''' % (_DOMAIN_REGEX, _COMMON_REGEX)
    _EMBED_REGEX = [rf'(?x)<iframe[^>]+?src=(?P<q>["\'])(?P<url>(?:(?:https?:)?//{_DOMAIN_REGEX})?{_COMMON_REGEX}(?:(?!\1).)+)\1']
    _TESTS = [{
        'url': 'https://kinja.com/ajax/inset/iframe?id=fb-10103303356633621',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=kinjavideo-100313',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=megaphone-PPY1300931075',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=ooyala-xzMXhleDpopuT0u1ijt_qZj3Va-34pEX%2FZTIxYmJjZDM2NWYzZDViZGRiOWJjYzc5',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=soundcloud-128574047',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=soundcloud-playlist-317413750',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=tumblr-post-160130699814-daydreams-at-midnight',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=twitch-stream-libratus_extra',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=twitter-1068875942473404422',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=ustream-channel-10414700',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=vimeo-120153502',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=vine-5BlvV5qqPrD',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=youtube-list-BCQ3KyrPjgA/PLE6509247C270A72E',
        'only_matching': True,
    }, {
        'url': 'https://kinja.com/ajax/inset/iframe?id=youtube-video-00QyL0AgPAE',
        'only_matching': True,
    }]
    _JWPLATFORM_PROVIDER = ('cdn.jwplayer.com/v2/media/', 'JWPlatform')
    _PROVIDER_MAP = {
        'fb': ('facebook.com/video.php?v=', 'Facebook'),
        'imgur': ('imgur.com/', 'Imgur'),
        'instagram': ('instagram.com/p/', 'Instagram'),
        'jwplayer-video': _JWPLATFORM_PROVIDER,
        'jwp-video': _JWPLATFORM_PROVIDER,
        'megaphone': ('player.megaphone.fm/', 'Generic'),
        'ooyala': ('player.ooyala.com/player.js?embedCode=', 'Ooyala'),
        'soundcloud': ('api.soundcloud.com/tracks/', 'Soundcloud'),
        'soundcloud-playlist': ('api.soundcloud.com/playlists/', 'SoundcloudPlaylist'),
        'tumblr-post': ('%s.tumblr.com/post/%s', 'Tumblr'),
        'twitch-stream': ('twitch.tv/', 'TwitchStream'),
        'twitter': ('twitter.com/i/cards/tfw/v1/', 'TwitterCard'),
        'ustream-channel': ('ustream.tv/embed/', 'Ustream'),
        'vimeo': ('vimeo.com/', 'Vimeo'),
        'vine': ('vine.co/v/', 'Vine'),
        'youtube-list': ('youtube.com/embed/%s?list=%s', 'YoutubePlaylist'),
        'youtube-video': ('youtube.com/embed/', 'Youtube'),
    }

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

        provider = self._PROVIDER_MAP.get(video_type)
        if provider:
            video_id = compat_urllib_parse_unquote(video_id)
            if video_type == 'tumblr-post':
                video_id, blog = video_id.split('-', 1)
                result_url = provider[0] % (blog, video_id)
            elif video_type == 'youtube-list':
                video_id, playlist_id = video_id.split('/')
                result_url = provider[0] % (video_id, playlist_id)
            else:
                if video_type == 'ooyala':
                    video_id = video_id.split('/')[0]
                result_url = provider[0] + video_id
            return self.url_result('http://' + result_url, provider[1])

        if video_type == 'kinjavideo':
            data = self._download_json(
                'https://kinja.com/api/core/video/views/videoById',
                video_id, query={'videoId': video_id})['data']
            title = data['title']

            formats = []
            for k in ('signedPlaylist', 'streaming'):
                m3u8_url = data.get(k + 'Url')
                if m3u8_url:
                    formats.extend(self._extract_m3u8_formats(
                        m3u8_url, video_id, 'mp4', 'm3u8_native',
                        m3u8_id='hls', fatal=False))
            self._sort_formats(formats)

            thumbnail = None
            poster = data.get('poster') or {}
            poster_id = poster.get('id')
            if poster_id:
                thumbnail = 'https://i.kinja-img.com/gawker-media/image/upload/%s.%s' % (poster_id, poster.get('format') or 'jpg')

            return {
                'id': video_id,
                'title': title,
                'description': strip_or_none(data.get('description')),
                'formats': formats,
                'tags': data.get('tags'),
                'timestamp': int_or_none(try_get(
                    data, lambda x: x['postInfo']['publishTimeMillis']), 1000),
                'thumbnail': thumbnail,
                'uploader': data.get('network'),
            }
        else:
            video_data = self._download_json(
                'https://api.vmh.univision.com/metadata/v1/content/' + video_id,
                video_id)['videoMetadata']
            iptc = video_data['photoVideoMetadataIPTC']
            title = iptc['title']['en']
            fmg = video_data.get('photoVideoMetadata_fmg') or {}
            tvss_domain = fmg.get('tvssDomain') or 'https://auth.univision.com'
            data = self._download_json(
                tvss_domain + '/api/v3/video-auth/url-signature-tokens',
                video_id, query={'mcpids': video_id})['data'][0]
            formats = []

            rendition_url = data.get('renditionUrl')
            if rendition_url:
                formats = self._extract_m3u8_formats(
                    rendition_url, video_id, 'mp4',
                    'm3u8_native', m3u8_id='hls', fatal=False)

            fallback_rendition_url = data.get('fallbackRenditionUrl')
            if fallback_rendition_url:
                formats.append({
                    'format_id': 'fallback',
                    'tbr': int_or_none(self._search_regex(
                        r'_(\d+)\.mp4', fallback_rendition_url,
                        'bitrate', default=None)),
                    'url': fallback_rendition_url,
                })

            self._sort_formats(formats)

            return {
                'id': video_id,
                'title': title,
                'thumbnail': try_get(iptc, lambda x: x['cloudinaryLink']['link'], compat_str),
                'uploader': fmg.get('network'),
                'duration': int_or_none(iptc.get('fileDuration')),
                'formats': formats,
                'description': try_get(iptc, lambda x: x['description']['en'], compat_str),
                'timestamp': parse_iso8601(iptc.get('dateReleased')),
            }


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

class KinoPoiskIE(InfoExtractor):
    _GEO_COUNTRIES = ['RU']
    _VALID_URL = r'https?://(?:www\.)?kinopoisk\.ru/film/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.kinopoisk.ru/film/81041/watch/',
        'md5': '4f71c80baea10dfa54a837a46111d326',
        'info_dict': {
            'id': '81041',
            'ext': 'mp4',
            'title': 'Алеша попович и тугарин змей',
            'description': 'md5:43787e673d68b805d0aa1df5a5aea701',
            'thumbnail': r're:^https?://.*',
            'duration': 4533,
            'age_limit': 12,
        },
    }, {
        'url': 'https://www.kinopoisk.ru/film/81041',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            'https://ott-widget.kinopoisk.ru/v1/kp/', video_id,
            query={'kpId': video_id})

        data = self._parse_json(
            self._search_regex(
                r'(?s)<script[^>]+\btype=["\']application/json[^>]+>(.+?)<',
                webpage, 'data'),
            video_id)['models']

        film = data['filmStatus']
        title = film.get('title') or film['originalTitle']

        formats = self._extract_m3u8_formats(
            data['playlistEntity']['uri'], video_id, 'mp4',
            entry_protocol='m3u8_native', m3u8_id='hls')
        self._sort_formats(formats)

        description = dict_get(
            film, ('descriptscription', 'description',
                   'shortDescriptscription', 'shortDescription'))
        thumbnail = film.get('coverUrl') or film.get('posterUrl')
        duration = int_or_none(film.get('duration'))
        age_limit = int_or_none(film.get('restrictionAge'))

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


code:
from .jixie import JixieBaseIE

class KompasVideoIE(JixieBaseIE):
    _VALID_URL = r'https?://video\.kompas\.com/\w+/(?P<id>\d+)/(?P<slug>[\w-]+)'
    _TESTS = [{
        'url': 'https://video.kompas.com/watch/164474/kim-jong-un-siap-kirim-nuklir-lawan-as-dan-korsel',
        'info_dict': {
            'id': '164474',
            'ext': 'mp4',
            'title': 'Kim Jong Un Siap Kirim Nuklir Lawan AS dan Korsel',
            'description': 'md5:262530c4fb7462398235f9a5dba92456',
            'uploader_id': '9262bf2590d558736cac4fff7978fcb1',
            'display_id': 'kim-jong-un-siap-kirim-nuklir-lawan-as-dan-korsel',
            'duration': 85.066667,
            'categories': ['news'],
            'thumbnail': 'https://video.jixie.media/1001/164474/164474_1280x720.jpg',
            'tags': 'count:9',
        }
    }]

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

        return self._extract_data_from_jixie_id(display_id, video_id, webpage)


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

class KonserthusetPlayIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?:konserthusetplay|rspoplay)\.se/\?.*\bm=(?P<id>[^&]+)'
    _TESTS = [{
        'url': 'http://www.konserthusetplay.se/?m=CKDDnlCY-dhWAAqiMERd-A',
        'md5': 'e3fd47bf44e864bd23c08e487abe1967',
        'info_dict': {
            'id': 'CKDDnlCY-dhWAAqiMERd-A',
            'ext': 'mp4',
            'title': 'Orkesterns instrument: Valthornen',
            'description': 'md5:f10e1f0030202020396a4d712d2fa827',
            'thumbnail': 're:^https?://.*$',
            'duration': 398.76,
        },
    }, {
        'url': 'http://rspoplay.se/?m=elWuEH34SMKvaO4wO_cHBw',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        e = self._search_regex(
            r'https?://csp\.picsearch\.com/rest\?.*\be=(.+?)[&"\']', webpage, 'e')

        rest = self._download_json(
            'http://csp.picsearch.com/rest?e=%s&containerId=mediaplayer&i=object' % e,
            video_id, transform_source=lambda s: s[s.index('{'):s.rindex('}') + 1])

        media = rest['media']
        player_config = media['playerconfig']
        playlist = player_config['playlist']

        source = next(f for f in playlist if f.get('bitrates') or f.get('provider'))

        FORMAT_ID_REGEX = r'_([^_]+)_h264m\.mp4'

        formats = []

        m3u8_url = source.get('url')
        if m3u8_url and determine_ext(m3u8_url) == 'm3u8':
            formats.extend(self._extract_m3u8_formats(
                m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native',
                m3u8_id='hls', fatal=False))

        fallback_url = source.get('fallbackUrl')
        fallback_format_id = None
        if fallback_url:
            fallback_format_id = self._search_regex(
                FORMAT_ID_REGEX, fallback_url, 'format id', default=None)

        connection_url = (player_config.get('rtmp', {}).get(
            'netConnectionUrl') or player_config.get(
            'plugins', {}).get('bwcheck', {}).get('netConnectionUrl'))
        if connection_url:
            for f in source['bitrates']:
                video_url = f.get('url')
                if not video_url:
                    continue
                format_id = self._search_regex(
                    FORMAT_ID_REGEX, video_url, 'format id', default=None)
                f_common = {
                    'vbr': int_or_none(f.get('bitrate')),
                    'width': int_or_none(f.get('width')),
                    'height': int_or_none(f.get('height')),
                }
                f = f_common.copy()
                f.update({
                    'url': connection_url,
                    'play_path': video_url,
                    'format_id': 'rtmp-%s' % format_id if format_id else 'rtmp',
                    'ext': 'flv',
                })
                formats.append(f)
                if format_id and format_id == fallback_format_id:
                    f = f_common.copy()
                    f.update({
                        'url': fallback_url,
                        'format_id': 'http-%s' % format_id if format_id else 'http',
                    })
                    formats.append(f)

        if not formats and fallback_url:
            formats.append({
                'url': fallback_url,
            })

        self._sort_formats(formats)

        title = player_config.get('title') or media['title']
        description = player_config.get('mediaInfo', {}).get('description')
        thumbnail = media.get('image')
        duration = float_or_none(media.get('duration'), 1000)

        subtitles = {}
        captions = source.get('captionsAvailableLanguages')
        if isinstance(captions, dict):
            for lang, subtitle_url in captions.items():
                subtitle_url = url_or_none(subtitle_url)
                if lang != 'none' and subtitle_url:
                    subtitles.setdefault(lang, []).append({'url': subtitle_url})

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


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

class KooIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?kooapp\.com/koo/[^/]+/(?P<id>[^/&#$?]+)'
    _TESTS = [{  # Test for video in the comments
        'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/946c4189-bc2d-4524-b95b-43f641e2adde',
        'info_dict': {
            'id': '946c4189-bc2d-4524-b95b-43f641e2adde',
            'ext': 'mp4',
            'title': 'test for video in comment',
            'description': 'md5:daa77dc214add4da8b6ea7d2226776e7',
            'timestamp': 1632215195,
            'uploader_id': 'ytdlpTestAccount',
            'uploader': 'yt-dlpTestAccount',
            'duration': 7000,
            'upload_date': '20210921'
        },
        'params': {'skip_download': True}
    }, {  # Test for koo with long title
        'url': 'https://www.kooapp.com/koo/laxman_kumarDBFEC/33decbf7-5e1e-4bb8-bfd7-04744a064361',
        'info_dict': {
            'id': '33decbf7-5e1e-4bb8-bfd7-04744a064361',
            'ext': 'mp4',
            'title': 'md5:47a71c2337295330c5a19a8af1bbf450',
            'description': 'md5:06a6a84e9321499486dab541693d8425',
            'timestamp': 1632106884,
            'uploader_id': 'laxman_kumarDBFEC',
            'uploader': 'Laxman Kumar 🇮🇳',
            'duration': 46000,
            'upload_date': '20210920'
        },
        'params': {'skip_download': True}
    }, {  # Test for audio
        'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/a2a9c88e-ce4b-4d2d-952f-d06361c5b602',
        'info_dict': {
            'id': 'a2a9c88e-ce4b-4d2d-952f-d06361c5b602',
            'ext': 'mp4',
            'title': 'Test for audio',
            'description': 'md5:ecb9a2b6a5d34b736cecb53788cb11e8',
            'timestamp': 1632211634,
            'uploader_id': 'ytdlpTestAccount',
            'uploader': 'yt-dlpTestAccount',
            'duration': 214000,
            'upload_date': '20210921'
        },
        'params': {'skip_download': True}
    }, {  # Test for video
        'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/a3e56c53-c1ed-4ac9-ac02-ed1630e6b1d1',
        'info_dict': {
            'id': 'a3e56c53-c1ed-4ac9-ac02-ed1630e6b1d1',
            'ext': 'mp4',
            'title': 'Test for video',
            'description': 'md5:7afc4eb839074ddeb2beea5dd6fe9500',
            'timestamp': 1632211468,
            'uploader_id': 'ytdlpTestAccount',
            'uploader': 'yt-dlpTestAccount',
            'duration': 14000,
            'upload_date': '20210921'
        },
        'params': {'skip_download': True}
    }, {  # Test for link
        'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/01bf5b94-81a5-4d8e-a387-5f732022e15a',
        'skip': 'No video/audio found at the provided url.',
        'info_dict': {
            'id': '01bf5b94-81a5-4d8e-a387-5f732022e15a',
            'title': 'Test for link',
            'ext': 'none',
        },
    }, {  # Test for images
        'url': 'https://www.kooapp.com/koo/ytdlpTestAccount/dc05d9cd-a61d-45fd-bb07-e8019d8ca8cb',
        'skip': 'No video/audio found at the provided url.',
        'info_dict': {
            'id': 'dc05d9cd-a61d-45fd-bb07-e8019d8ca8cb',
            'title': 'Test for images',
            'ext': 'none',
        },
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        data_json = self._download_json(f'https://www.kooapp.com/apiV1/ku/{id}?limit=20&offset=0&showSimilarKoos=true', id)['parentContent']
        item_json = next(content['items'][0] for content in data_json
                         if try_get(content, lambda x: x['items'][0]['id']) == id)
        media_json = item_json['mediaMap']
        formats = []

        mp4_url = media_json.get('videoMp4')
        video_m3u8_url = media_json.get('videoHls')
        if mp4_url:
            formats.append({
                'url': mp4_url,
                'ext': 'mp4',
            })
        if video_m3u8_url:
            formats.extend(self._extract_m3u8_formats(video_m3u8_url, id, fatal=False, ext='mp4'))
        if not formats:
            self.raise_no_formats('No video/audio found at the provided url.', expected=True)

        self._sort_formats(formats)
        return {
            'id': id,
            'title': clean_html(item_json.get('title')),
            'description': f'{clean_html(item_json.get("title"))}\n\n{clean_html(item_json.get("enTransliteration"))}',
            'timestamp': item_json.get('createdAt'),
            'uploader_id': item_json.get('handle'),
            'uploader': item_json.get('name'),
            'duration': media_json.get('duration'),
            'formats': formats,
        }


code:
import json

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

class KrasViewIE(InfoExtractor):
    IE_DESC = 'Красвью'
    _VALID_URL = r'https?://krasview\.ru/(?:video|embed)/(?P<id>\d+)'

    _TEST = {
        'url': 'http://krasview.ru/video/512228',
        'md5': '3b91003cf85fc5db277870c8ebd98eae',
        'info_dict': {
            'id': '512228',
            'ext': 'mp4',
            'title': 'Снег, лёд, заносы',
            'description': 'Снято в городе Нягань, в Ханты-Мансийском автономном округе.',
            'duration': 27,
            'thumbnail': r're:^https?://.*\.jpg',
        },
        'params': {
            'skip_download': 'Not accessible from Travis CI server',
        },
    }

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

        webpage = self._download_webpage(url, video_id)

        flashvars = json.loads(js_to_json(self._search_regex(
            r'video_Init\(({.+?})', webpage, 'flashvars')))

        video_url = flashvars['url']
        title = self._og_search_title(webpage)
        description = self._og_search_description(webpage, default=None)
        thumbnail = flashvars.get('image') or self._og_search_thumbnail(webpage)
        duration = int_or_none(flashvars.get('duration'))
        width = int_or_none(self._og_search_property(
            'video:width', webpage, 'video width', default=None))
        height = int_or_none(self._og_search_property(
            'video:height', webpage, 'video height', default=None))

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


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

class KTHIE(InfoExtractor):
    _VALID_URL = r'https?://play\.kth\.se/(?:[^/]+/)+(?P<id>[a-z0-9_]+)'
    _TEST = {
        'url': 'https://play.kth.se/media/Lunch+breakA+De+nya+aff%C3%A4rerna+inom+Fordonsdalen/0_uoop6oz9',
        'md5': 'd83ada6d00ca98b73243a88efe19e8a6',
        'info_dict': {
            'id': '0_uoop6oz9',
            'ext': 'mp4',
            'title': 'md5:bd1d6931facb6828762a33e6ce865f37',
            'thumbnail': 're:https?://.+/thumbnail/.+',
            'duration': 3516,
            'timestamp': 1647345358,
            'upload_date': '20220315',
            'uploader_id': 'md5:0ec23e33a89e795a4512930c8102509f',
        }
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        result = self.url_result(
            smuggle_url('kaltura:308:%s' % video_id, {
                'service_url': 'https://api.kaltura.nordu.net'}),
            'Kaltura')
        return result


code:
from .common import InfoExtractor

class Ku6IE(InfoExtractor):
    _VALID_URL = r'https?://v\.ku6\.com/show/(?P<id>[a-zA-Z0-9\-\_]+)(?:\.)*html'
    _TEST = {
        'url': 'http://v.ku6.com/show/JG-8yS14xzBr4bCn1pu0xw...html',
        'md5': '01203549b9efbb45f4b87d55bdea1ed1',
        'info_dict': {
            'id': 'JG-8yS14xzBr4bCn1pu0xw',
            'ext': 'f4v',
            'title': 'techniques test',
        }
    }

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

        title = self._html_search_regex(
            r'
(.*?)
', webpage, 'title')
        dataUrl = 'http://v.ku6.com/fetchVideo4Player/%s.html' % video_id
        jsonData = self._download_json(dataUrl, video_id)
        downloadUrl = jsonData['data']['f']

        return {
            'id': video_id,
            'title': title,
            'url': downloadUrl
        }


code:
import random
import urllib.parse

from .common import InfoExtractor
from ..utils import (
    float_or_none,
    int_or_none,
    timeconvert,
    update_url_query,
    xpath_text,
)

class KUSIIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?kusi\.com/(?P<path>story/.+|video\?clipId=(?P<clipId>\d+))'
    _TESTS = [{
        'url': 'http://www.kusi.com/story/32849881/turko-files-refused-to-help-it-aint-right',
        'md5': '4e76ce8e53660ce9697d06c0ba6fc47d',
        'info_dict': {
            'id': '12689020',
            'ext': 'mp4',
            'title': "Turko Files: Refused to Help, It Ain't Right!",
            'duration': 223.586,
            'upload_date': '20160826',
            'timestamp': 1472233118,
            'thumbnail': r're:^https?://.*\.jpg$'
        },
    }, {
        'url': 'http://kusi.com/video?clipId=12203019',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        clip_id = mobj.group('clipId')
        video_id = clip_id or mobj.group('path')

        webpage = self._download_webpage(url, video_id)

        if clip_id is None:
            video_id = clip_id = self._html_search_regex(
                r'"clipId"\s*,\s*"(\d+)"', webpage, 'clip id')

        affiliate_id = self._search_regex(
            r'affiliateId\s*:\s*\'([^\']+)\'', webpage, 'affiliate id')

        # See __Packages/worldnow/model/GalleryModel.as of WNGallery.swf
        xml_url = update_url_query('http://www.kusi.com/build.asp', {
            'buildtype': 'buildfeaturexmlrequest',
            'featureType': 'Clip',
            'featureid': clip_id,
            'affiliateno': affiliate_id,
            'clientgroupid': '1',
            'rnd': int(round(random.random() * 1000000)),
        })

        doc = self._download_xml(xml_url, video_id)

        video_title = xpath_text(doc, 'HEADLINE', fatal=True)
        duration = float_or_none(xpath_text(doc, 'DURATION'), scale=1000)
        description = xpath_text(doc, 'ABSTRACT')
        thumbnail = xpath_text(doc, './THUMBNAILIMAGE/FILENAME')
        creation_time = timeconvert(xpath_text(doc, 'rfc822creationdate'))

        quality_options = doc.find('{http://search.yahoo.com/mrss/}group').findall('{http://search.yahoo.com/mrss/}content')
        formats = []
        for quality in quality_options:
            formats.append({
                'url': urllib.parse.unquote_plus(quality.attrib['url']),
                'height': int_or_none(quality.attrib.get('height')),
                'width': int_or_none(quality.attrib.get('width')),
                'vbr': float_or_none(quality.attrib.get('bitratebits'), scale=1000),
            })
        self._sort_formats(formats)

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


code:
import re

from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    get_element_by_id,
    clean_html,
    ExtractorError,
    InAdvancePagedList,
    remove_start,
)

class KuwoBaseIE(InfoExtractor):
    _FORMATS = [
        {'format': 'ape', 'ext': 'ape', 'preference': 100},
        {'format': 'mp3-320', 'ext': 'mp3', 'br': '320kmp3', 'abr': 320, 'preference': 80},
        {'format': 'mp3-192', 'ext': 'mp3', 'br': '192kmp3', 'abr': 192, 'preference': 70},
        {'format': 'mp3-128', 'ext': 'mp3', 'br': '128kmp3', 'abr': 128, 'preference': 60},
        {'format': 'wma', 'ext': 'wma', 'preference': 20},
        {'format': 'aac', 'ext': 'aac', 'abr': 48, 'preference': 10}
    ]

    def _get_formats(self, song_id, tolerate_ip_deny=False):
        formats = []
        for file_format in self._FORMATS:
            query = {
                'format': file_format['ext'],
                'br': file_format.get('br', ''),
                'rid': 'MUSIC_%s' % song_id,
                'type': 'convert_url',
                'response': 'url'
            }

            song_url = self._download_webpage(
                'http://antiserver.kuwo.cn/anti.s',
                song_id, note='Download %s url info' % file_format['format'],
                query=query, headers=self.geo_verification_headers(),
            )

            if song_url == 'IPDeny' and not tolerate_ip_deny:
                raise ExtractorError('This song is blocked in this region', expected=True)

            if song_url.startswith('http://') or song_url.startswith('https://'):
                formats.append({
                    'url': song_url,
                    'format_id': file_format['format'],
                    'format': file_format['format'],
                    'quality': file_format['preference'],
                    'abr': file_format.get('abr'),
                })

        return formats

class KuwoIE(KuwoBaseIE):
    IE_NAME = 'kuwo:song'
    IE_DESC = '酷我音乐'
    _VALID_URL = r'https?://(?:www\.)?kuwo\.cn/yinyue/(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://www.kuwo.cn/yinyue/635632/',
        'info_dict': {
            'id': '635632',
            'ext': 'ape',
            'title': '爱我别走',
            'creator': '张震岳',
            'upload_date': '20080122',
            'description': 'md5:ed13f58e3c3bf3f7fd9fbc4e5a7aa75c'
        },
        'skip': 'this song has been offline because of copyright issues',
    }, {
        'url': 'http://www.kuwo.cn/yinyue/6446136/',
        'info_dict': {
            'id': '6446136',
            'ext': 'mp3',
            'title': '心',
            'description': 'md5:5d0e947b242c35dc0eb1d2fce9fbf02c',
            'creator': 'IU',
            'upload_date': '20150518',
        },
        'params': {
            'format': 'mp3-320',
        },
    }, {
        'url': 'http://www.kuwo.cn/yinyue/3197154?catalog=yueku2016',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        song_id = self._match_id(url)
        webpage, urlh = self._download_webpage_handle(
            url, song_id, note='Download song detail info',
            errnote='Unable to get song detail info')
        if song_id not in urlh.geturl() or '对不起，该歌曲由于版权问题已被下线，将返回网站首页' in webpage:
            raise ExtractorError('this song has been offline because of copyright issues', expected=True)

        song_name = self._html_search_regex(
            r'
]+id="lrcName">([^<]+)
', webpage, 'song name')
        singer_name = remove_start(self._html_search_regex(
            r']+href="http://www\.kuwo\.cn/artist/content\?name=([^"]+)">',
            webpage, 'singer name', fatal=False), '歌手')
        lrc_content = clean_html(get_element_by_id('lrcContent', webpage))
        if lrc_content == '暂无':     # indicates no lyrics
            lrc_content = None

        formats = self._get_formats(song_id)
        self._sort_formats(formats)

        album_id = self._html_search_regex(
            r']+href="http://www\.kuwo\.cn/album/(\d+)/"',
            webpage, 'album id', fatal=False)

        publish_time = None
        if album_id is not None:
            album_info_page = self._download_webpage(
                'http://www.kuwo.cn/album/%s/' % album_id, song_id,
                note='Download album detail info',
                errnote='Unable to get album detail info')

            publish_time = self._html_search_regex(
                r'发行时间：(\d{4}-\d{2}-\d{2})', album_info_page,
                'publish time', fatal=False)
            if publish_time:
                publish_time = publish_time.replace('-', '')

        return {
            'id': song_id,
            'title': song_name,
            'creator': singer_name,
            'upload_date': publish_time,
            'description': lrc_content,
            'formats': formats,
        }

class KuwoAlbumIE(InfoExtractor):
    IE_NAME = 'kuwo:album'
    IE_DESC = '酷我音乐 - 专辑'
    _VALID_URL = r'https?://(?:www\.)?kuwo\.cn/album/(?P<id>\d+?)/'
    _TEST = {
        'url': 'http://www.kuwo.cn/album/502294/',
        'info_dict': {
            'id': '502294',
            'title': 'Made\xa0Series\xa0《M》',
            'description': 'md5:d463f0d8a0ff3c3ea3d6ed7452a9483f',
        },
        'playlist_count': 2,
    }

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

        webpage = self._download_webpage(
            url, album_id, note='Download album info',
            errnote='Unable to get album info')

        album_name = self._html_search_regex(
            r'
]+class="comm"[^<]+
]+title="([^"]+)"', webpage,
            'album name')
        album_intro = remove_start(
            clean_html(get_element_by_id('intro', webpage)),
            '%s简介：' % album_name)

        entries = [
            self.url_result(song_url, 'Kuwo') for song_url in re.findall(
                r'
]+class="listen">]+href="(http://www\.kuwo\.cn/yinyue/\d+/)"',
                webpage)
        ]
        return self.playlist_result(entries, album_id, album_name, album_intro)

class KuwoChartIE(InfoExtractor):
    IE_NAME = 'kuwo:chart'
    IE_DESC = '酷我音乐 - 排行榜'
    _VALID_URL = r'https?://yinyue\.kuwo\.cn/billboard_(?P<id>[^.]+).htm'
    _TEST = {
        'url': 'http://yinyue.kuwo.cn/billboard_香港中文龙虎榜.htm',
        'info_dict': {
            'id': '香港中文龙虎榜',
        },
        'playlist_mincount': 7,
    }

    def _real_extract(self, url):
        chart_id = self._match_id(url)
        webpage = self._download_webpage(
            url, chart_id, note='Download chart info',
            errnote='Unable to get chart info')

        entries = [
            self.url_result(song_url, 'Kuwo') for song_url in re.findall(
                r']+href="(http://www\.kuwo\.cn/yinyue/\d+)', webpage)
        ]
        return self.playlist_result(entries, chart_id)

class KuwoSingerIE(InfoExtractor):
    IE_NAME = 'kuwo:singer'
    IE_DESC = '酷我音乐 - 歌手'
    _VALID_URL = r'https?://(?:www\.)?kuwo\.cn/mingxing/(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'http://www.kuwo.cn/mingxing/bruno+mars/',
        'info_dict': {
            'id': 'bruno+mars',
            'title': 'Bruno\xa0Mars',
        },
        'playlist_mincount': 329,
    }, {
        'url': 'http://www.kuwo.cn/mingxing/Ali/music.htm',
        'info_dict': {
            'id': 'Ali',
            'title': 'Ali',
        },
        'playlist_mincount': 95,
        'skip': 'Regularly stalls travis build',  # See https://travis-ci.org/ytdl-org/youtube-dl/jobs/78878540
    }]

    PAGE_SIZE = 15

    def _real_extract(self, url):
        singer_id = self._match_id(url)
        webpage = self._download_webpage(
            url, singer_id, note='Download singer info',
            errnote='Unable to get singer info')

        singer_name = self._html_search_regex(
            r'
([^<]+)
', webpage, 'singer name')

        artist_id = self._html_search_regex(
            r'data-artistid="(\d+)"', webpage, 'artist id')

        page_count = int(self._html_search_regex(
            r'data-page="(\d+)"', webpage, 'page count'))

        def page_func(page_num):
            webpage = self._download_webpage(
                'http://www.kuwo.cn/artist/contentMusicsAjax',
                singer_id, note='Download song list page #%d' % (page_num + 1),
                errnote='Unable to get song list page #%d' % (page_num + 1),
                query={'artistId': artist_id, 'pn': page_num, 'rn': self.PAGE_SIZE})

            return [
                self.url_result(compat_urlparse.urljoin(url, song_url), 'Kuwo')
                for song_url in re.findall(
                    r'
]+class="name">]+href="(/yinyue/\d+)',
                    webpage)
            ]

        entries = InAdvancePagedList(page_func, page_count, self.PAGE_SIZE)

        return self.playlist_result(entries, singer_id, singer_name)

class KuwoCategoryIE(InfoExtractor):
    IE_NAME = 'kuwo:category'
    IE_DESC = '酷我音乐 - 分类'
    _VALID_URL = r'https?://yinyue\.kuwo\.cn/yy/cinfo_(?P<id>\d+?).htm'
    _TEST = {
        'url': 'http://yinyue.kuwo.cn/yy/cinfo_86375.htm',
        'info_dict': {
            'id': '86375',
            'title': '八十年代精选',
            'description': '这些都是属于八十年代的回忆！',
        },
        'playlist_mincount': 24,
    }

    def _real_extract(self, url):
        category_id = self._match_id(url)
        webpage = self._download_webpage(
            url, category_id, note='Download category info',
            errnote='Unable to get category info')

        category_name = self._html_search_regex(
            r'
]+title="([^<>]+?)">[^<>]+?
', webpage, 'category name')

        category_desc = remove_start(
            get_element_by_id('intro', webpage).strip(),
            '%s简介：' % category_name)
        if category_desc == '暂无':
            category_desc = None

        jsonm = self._parse_json(self._html_search_regex(
            r'var\s+jsonm\s*=\s*([^;]+);', webpage, 'category songs'), category_id)

        entries = [
            self.url_result('http://www.kuwo.cn/yinyue/%s/' % song['musicrid'], 'Kuwo')
            for song in jsonm['musiclist']
        ]
        return self.playlist_result(entries, category_id, category_name, category_desc)

class KuwoMvIE(KuwoBaseIE):
    IE_NAME = 'kuwo:mv'
    IE_DESC = '酷我音乐 - MV'
    _VALID_URL = r'https?://(?:www\.)?kuwo\.cn/mv/(?P<id>\d+?)/'
    _TEST = {
        'url': 'http://www.kuwo.cn/mv/6480076/',
        'info_dict': {
            'id': '6480076',
            'ext': 'mp4',
            'title': 'My HouseMV',
            'creator': '2PM',
        },
        # In this video, music URLs (anti.s) are blocked outside China and
        # USA, while the MV URL (mvurl) is available globally, so force the MV
        # URL for consistent results in different countries
        'params': {
            'format': 'mv',
        },
    }
    _FORMATS = KuwoBaseIE._FORMATS + [
        {'format': 'mkv', 'ext': 'mkv', 'preference': 250},
        {'format': 'mp4', 'ext': 'mp4', 'preference': 200},
    ]

    def _real_extract(self, url):
        song_id = self._match_id(url)
        webpage = self._download_webpage(
            url, song_id, note='Download mv detail info: %s' % song_id,
            errnote='Unable to get mv detail info: %s' % song_id)

        mobj = re.search(
            r'
]+title="(?P<song>[^"]+)">[^<]+]+title="(?P<singer>[^"]+)"',
            webpage)
        if mobj:
            song_name = mobj.group('song')
            singer_name = mobj.group('singer')
        else:
            raise ExtractorError('Unable to find song or singer names')

        formats = self._get_formats(song_id, tolerate_ip_deny=True)

        mv_url = self._download_webpage(
            'http://www.kuwo.cn/yy/st/mvurl?rid=MUSIC_%s' % song_id,
            song_id, note='Download %s MV URL' % song_id)
        formats.append({
            'url': mv_url,
            'format_id': 'mv',
        })

        self._sort_formats(formats)

        return {
            'id': song_id,
            'title': song_name,
            'creator': singer_name,
            'formats': formats,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    float_or_none,
    HEADRequest,
    int_or_none,
    parse_duration,
    unified_strdate,
)

class LA7IE(InfoExtractor):
    IE_NAME = 'la7.it'
    _VALID_URL = r'''(?x)(https?://)?(?:
        (?:www\.)?la7\.it/([^/]+)/(?:rivedila7|video)/|
        tg\.la7\.it/repliche-tgla7\?id=
    )(?P<id>.+)'''

    _TESTS = [{
        # 'src' is a plain URL
        'url': 'http://www.la7.it/crozza/video/inccool8-02-10-2015-163722',
        'md5': '8b613ffc0c4bf9b9e377169fc19c214c',
        'info_dict': {
            'id': 'inccool8-02-10-2015-163722',
            'ext': 'mp4',
            'title': 'Inc.Cool8',
            'description': 'Benvenuti nell\'incredibile mondo della INC. COOL. 8. dove “INC.” sta per “Incorporated” “COOL” sta per “fashion” ed Eight sta per il gesto atletico',
            'thumbnail': 're:^https?://.*',
            'upload_date': '20151002',
        },
    }, {
        'url': 'http://www.la7.it/omnibus/rivedila7/omnibus-news-02-07-2016-189077',
        'only_matching': True,
    }]
    _HOST = 'https://awsvodpkg.iltrovatore.it'

    def _generate_mp4_url(self, quality, m3u8_formats):
        for f in m3u8_formats:
            if f['vcodec'] != 'none' and quality in f['url']:
                http_url = '%s%s.mp4' % (self._HOST, quality)

                urlh = self._request_webpage(
                    HEADRequest(http_url), quality,
                    note='Check filesize', fatal=False)
                if urlh:
                    http_f = f.copy()
                    del http_f['manifest_url']
                    http_f.update({
                        'format_id': http_f['format_id'].replace('hls-', 'https-'),
                        'url': http_url,
                        'protocol': 'https',
                        'filesize_approx': int_or_none(urlh.headers.get('Content-Length', None)),
                    })
                    return http_f
                return None

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

        if not url.startswith('http'):
            url = '%s//%s' % (self.http_scheme(), url)

        webpage = self._download_webpage(url, video_id)
        video_path = self._search_regex(r'(/content/.*?).mp4', webpage, 'video_path')

        formats = self._extract_mpd_formats(
            f'{self._HOST}/local/dash/,{video_path}.mp4.urlset/manifest.mpd',
            video_id, mpd_id='dash', fatal=False)
        m3u8_formats = self._extract_m3u8_formats(
            f'{self._HOST}/local/hls/,{video_path}.mp4.urlset/master.m3u8',
            video_id, 'mp4', m3u8_id='hls', fatal=False)
        formats.extend(m3u8_formats)

        for q in filter(None, video_path.split(',')):
            http_f = self._generate_mp4_url(q, m3u8_formats)
            if http_f:
                formats.append(http_f)

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': self._og_search_title(webpage, default=None),
            'description': self._og_search_description(webpage, default=None),
            'thumbnail': self._og_search_thumbnail(webpage, default=None),
            'formats': formats,
            'upload_date': unified_strdate(self._search_regex(r'datetime="(.+?)"', webpage, 'upload_date', fatal=False))
        }

class LA7PodcastEpisodeIE(InfoExtractor):
    IE_NAME = 'la7.it:pod:episode'
    _VALID_URL = r'''(?x)(https?://)?
        (?:www\.)?la7\.it/[^/]+/podcast/([^/]+-)?(?P<id>\d+)'''

    _TESTS = [{
        'url': 'https://www.la7.it/voicetown/podcast/la-carezza-delle-memoria-di-carlo-verdone-23-03-2021-371497',
        'md5': '7737d4d79b3c1a34b3de3e16297119ed',
        'info_dict': {
            'id': '371497',
            'ext': 'mp3',
            'title': '"La carezza delle memoria" di Carlo Verdone',
            'description': 'md5:5abf07c3c551a687db80af3f9ceb7d52',
            'thumbnail': 'https://www.la7.it/sites/default/files/podcast/371497.jpg',
            'upload_date': '20210323',
        },
    }, {
        # embed url
        'url': 'https://www.la7.it/embed/podcast/371497',
        'only_matching': True,
    }, {
        # date already in the title
        'url': 'https://www.la7.it/propagandalive/podcast/lintervista-di-diego-bianchi-ad-annalisa-cuzzocrea-puntata-del-1932021-20-03-2021-371130',
        'only_matching': True,
    }, {
        # title same as show_title
        'url': 'https://www.la7.it/otto-e-mezzo/podcast/otto-e-mezzo-26-03-2021-372340',
        'only_matching': True,
    }]

    def _extract_info(self, webpage, video_id=None, ppn=None):
        if not video_id:
            video_id = self._search_regex(
                r'data-nid=([\'"])(?P<vid>\d+)\1',
                webpage, 'video_id', group='vid')

        media_url = self._search_regex(
            (r'src:\s*([\'"])(?P<url>.+?mp3.+?)\1',
             r'data-podcast=([\'"])(?P<url>.+?mp3.+?)\1'),
            webpage, 'media_url', group='url')
        ext = determine_ext(media_url)
        formats = [{
            'url': media_url,
            'format_id': ext,
            'ext': ext,
        }]
        self._sort_formats(formats)

        title = self._html_search_regex(
            (r'
(?P<title>.+?)</',
             r'<title>(?P<title>[^<]+)</title>',
             r'title:\s*([\'"])(?P<title>.+?)\1'),
            webpage, 'title', group='title')

        description = (
            self._html_search_regex(
                (r'
(.+?)
',
                 r'
(.+?)
',
                 r'
([^<]+?)
',
                 r'

(.+?)

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

        thumb = self._html_search_regex(
            (r'

',
             r'<div class="container-embed"[^<]+url\((.+?)\);">',
             r'
<img src="(.+?)"'),
            webpage, 'thumbnail', fatal=False, default=None)

        duration = parse_duration(self._html_search_regex(
            r'([\d:]+)',
            webpage, 'duration', fatal=False, default=None))

        date = self._html_search_regex(
            r'class="data">\s*(?:)?([\d\.]+)\s*</',
            webpage, 'date', default=None)

        date_alt = self._search_regex(
            r'(\d+[\./]\d+[\./]\d+)', title, 'date_alt', default=None)
        ppn = ppn or self._search_regex(
            r'ppN:\s*([\'"])(?P<ppn>.+?)\1',
            webpage, 'ppn', group='ppn', default=None)
        # if the date is not in the title
        # and title is the same as the show_title
        # add the date to the title
        if date and not date_alt and ppn and ppn.lower() == title.lower():
            title += ' del %s' % date
        return {
            'id': video_id,
            'title': title,
            'description': description,
            'duration': float_or_none(duration),
            'formats': formats,
            'thumbnail': thumb,
            'upload_date': unified_strdate(date),
        }

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

        return self._extract_info(webpage, video_id)

class LA7PodcastIE(LA7PodcastEpisodeIE):
    IE_NAME = 'la7.it:podcast'
    _VALID_URL = r'(https?://)?(www\.)?la7\.it/(?P<id>[^/]+)/podcast/?(?:$|[#?])'

    _TESTS = [{
        'url': 'https://www.la7.it/propagandalive/podcast',
        'info_dict': {
            'id': 'propagandalive',
            'title': "Propaganda Live",
        },
        'playlist_count': 10,
    }]

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

        title = (
            self._html_search_regex(
                r'
(.+?)
', webpage, 'title', fatal=False, default=None)
            or self._og_search_title(webpage))
        ppn = self._search_regex(
            r'window\.ppN\s*=\s*([\'"])(?P<ppn>.+?)\1',
            webpage, 'ppn', group='ppn', default=None)

        entries = []
        for episode in re.finditer(
                r'
([\s\S]+?)(?:
\s*){3}',
                webpage):
            entries.append(self._extract_info(episode.group(1), ppn=ppn))

        return self.playlist_result(entries, playlist_id, title)


code:
import json
import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    unified_strdate,
    urlencode_postdata,
    xpath_element,
    xpath_text,
    update_url_query,
    js_to_json,
)

class Laola1TvEmbedIE(InfoExtractor):
    IE_NAME = 'laola1tv:embed'
    _VALID_URL = r'https?://(?:www\.)?laola1\.tv/titanplayer\.php\?.*?\bvideoid=(?P<id>\d+)'
    _TESTS = [{
        # flashvars.premium = "false";
        'url': 'https://www.laola1.tv/titanplayer.php?videoid=708065&type=V&lang=en&portal=int&customer=1024',
        'info_dict': {
            'id': '708065',
            'ext': 'mp4',
            'title': 'MA Long CHN - FAN Zhendong CHN',
            'uploader': 'ITTF - International Table Tennis Federation',
            'upload_date': '20161211',
        },
    }]

    def _extract_token_url(self, stream_access_url, video_id, data):
        return self._download_json(
            self._proto_relative_url(stream_access_url, 'https:'), video_id,
            headers={
                'Content-Type': 'application/json',
            }, data=json.dumps(data).encode())['data']['stream-access'][0]

    def _extract_formats(self, token_url, video_id):
        token_doc = self._download_xml(
            token_url, video_id, 'Downloading token',
            headers=self.geo_verification_headers())

        token_attrib = xpath_element(token_doc, './/token').attrib

        if token_attrib['status'] != '0':
            raise ExtractorError(
                'Token error: %s' % token_attrib['comment'], expected=True)

        formats = self._extract_akamai_formats(
            '%s?hdnea=%s' % (token_attrib['url'], token_attrib['auth']),
            video_id)
        self._sort_formats(formats)
        return formats

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        flash_vars = self._search_regex(
            r'(?s)flashvars\s*=\s*({.+?});', webpage, 'flash vars')

        def get_flashvar(x, *args, **kwargs):
            flash_var = self._search_regex(
                r'%s\s*:\s*"([^"]+)"' % x,
                flash_vars, x, default=None)
            if not flash_var:
                flash_var = self._search_regex([
                    r'flashvars\.%s\s*=\s*"([^"]+)"' % x,
                    r'%s\s*=\s*"([^"]+)"' % x],
                    webpage, x, *args, **kwargs)
            return flash_var

        hd_doc = self._download_xml(
            'http://www.laola1.tv/server/hd_video.php', video_id, query={
                'play': get_flashvar('streamid'),
                'partner': get_flashvar('partnerid'),
                'portal': get_flashvar('portalid'),
                'lang': get_flashvar('sprache'),
                'v5ident': '',
            })

        _v = lambda x, **k: xpath_text(hd_doc, './/video/' + x, **k)
        title = _v('title', fatal=True)

        token_url = None
        premium = get_flashvar('premium', default=None)
        if premium:
            token_url = update_url_query(
                _v('url', fatal=True), {
                    'timestamp': get_flashvar('timestamp'),
                    'auth': get_flashvar('auth'),
                })
        else:
            data_abo = urlencode_postdata(
                dict((i, v) for i, v in enumerate(_v('req_liga_abos').split(','))))
            stream_access_url = update_url_query(
                'https://club.laola1.tv/sp/laola1/api/v3/user/session/premium/player/stream-access', {
                    'videoId': _v('id'),
                    'target': self._search_regex(r'vs_target = (\d+);', webpage, 'vs target'),
                    'label': _v('label'),
                    'area': _v('area'),
                })
            token_url = self._extract_token_url(stream_access_url, video_id, data_abo)

        formats = self._extract_formats(token_url, video_id)

        categories_str = _v('meta_sports')
        categories = categories_str.split(',') if categories_str else []
        is_live = _v('islive') == 'true'

        return {
            'id': video_id,
            'title': title,
            'upload_date': unified_strdate(_v('time_date')),
            'uploader': _v('meta_organisation'),
            'categories': categories,
            'is_live': is_live,
            'formats': formats,
        }

class Laola1TvBaseIE(Laola1TvEmbedIE):
    def _extract_video(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)

        if 'Dieser Livestream ist bereits beendet.' in webpage:
            raise ExtractorError('This live stream has already finished.', expected=True)

        conf = self._parse_json(self._search_regex(
            r'(?s)conf\s*=\s*({.+?});', webpage, 'conf'),
            display_id,
            transform_source=lambda s: js_to_json(re.sub(r'shareurl:.+,', '', s)))
        video_id = conf['videoid']

        config = self._download_json(conf['configUrl'], video_id, query={
            'videoid': video_id,
            'partnerid': conf['partnerid'],
            'language': conf.get('language', ''),
            'portal': conf.get('portalid', ''),
        })
        error = config.get('error')
        if error:
            raise ExtractorError('%s said: %s' % (self.IE_NAME, error), expected=True)

        video_data = config['video']
        title = video_data['title']
        is_live = video_data.get('isLivestream') and video_data.get('isLive')
        meta = video_data.get('metaInformation')
        sports = meta.get('sports')
        categories = sports.split(',') if sports else []

        token_url = self._extract_token_url(
            video_data['streamAccess'], video_id,
            video_data['abo']['required'])

        formats = self._extract_formats(token_url, video_id)

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': video_data.get('description'),
            'thumbnail': video_data.get('image'),
            'categories': categories,
            'formats': formats,
            'is_live': is_live,
        }

class Laola1TvIE(Laola1TvBaseIE):
    IE_NAME = 'laola1tv'
    _VALID_URL = r'https?://(?:www\.)?laola1\.tv/[a-z]+-[a-z]+/[^/]+/(?P<id>[^/?#&]+)'

    _TESTS = [{
        'url': 'http://www.laola1.tv/de-de/video/straubing-tigers-koelner-haie/227883.html',
        'info_dict': {
            'id': '227883',
            'display_id': 'straubing-tigers-koelner-haie',
            'ext': 'flv',
            'title': 'Straubing Tigers - Kölner Haie',
            'upload_date': '20140912',
            'is_live': False,
            'categories': ['Eishockey'],
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://www.laola1.tv/de-de/video/straubing-tigers-koelner-haie',
        'info_dict': {
            'id': '464602',
            'display_id': 'straubing-tigers-koelner-haie',
            'ext': 'flv',
            'title': 'Straubing Tigers - Kölner Haie',
            'upload_date': '20160129',
            'is_live': False,
            'categories': ['Eishockey'],
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'http://www.laola1.tv/de-de/livestream/2016-03-22-belogorie-belgorod-trentino-diatec-lde',
        'info_dict': {
            'id': '487850',
            'display_id': '2016-03-22-belogorie-belgorod-trentino-diatec-lde',
            'ext': 'flv',
            'title': 'Belogorie BELGOROD - TRENTINO Diatec',
            'upload_date': '20160322',
            'uploader': 'CEV - Europäischer Volleyball Verband',
            'is_live': True,
            'categories': ['Volleyball'],
        },
        'params': {
            'skip_download': True,
        },
        'skip': 'This live stream has already finished.',
    }]

    def _real_extract(self, url):
        return self._extract_video(url)

class EHFTVIE(Laola1TvBaseIE):
    IE_NAME = 'ehftv'
    _VALID_URL = r'https?://(?:www\.)?ehftv\.com/[a-z]+(?:-[a-z]+)?/[^/]+/(?P<id>[^/?#&]+)'

    _TESTS = [{
        'url': 'https://www.ehftv.com/int/video/paris-saint-germain-handball-pge-vive-kielce/1166761',
        'info_dict': {
            'id': '1166761',
            'display_id': 'paris-saint-germain-handball-pge-vive-kielce',
            'ext': 'mp4',
            'title': 'Paris Saint-Germain Handball - PGE Vive Kielce',
            'is_live': False,
            'categories': ['Handball'],
        },
        'params': {
            'skip_download': True,
        },
    }]

    def _real_extract(self, url):
        return self._extract_video(url)

class ITTFIE(InfoExtractor):
    _VALID_URL = r'https?://tv\.ittf\.com/video/[^/]+/(?P<id>\d+)'
    _TEST = {
        'url': 'https://tv.ittf.com/video/peng-wang-wei-matsudaira-kenta/951802',
        'only_matching': True,
    }

    def _real_extract(self, url):
        return self.url_result(
            update_url_query('https://www.laola1.tv/titanplayer.php', {
                'videoid': self._match_id(url),
                'type': 'V',
                'lang': 'en',
                'portal': 'int',
                'customer': 1024,
            }), Laola1TvEmbedIE.ie_key())


code:
import re

from .common import InfoExtractor
from ..utils import int_or_none, format_field

class LastFMPlaylistBaseIE(InfoExtractor):
    def _entries(self, url, playlist_id):
        webpage = self._download_webpage(url, playlist_id)
        start_page_number = int_or_none(self._search_regex(
            r'\bpage=(\d+)', url, 'page', default=None)) or 1
        last_page_number = int_or_none(self._search_regex(
            r'>(\d+)[^<]*
[^<]*
]+class="pagination-next', webpage, 'last_page', default=None))

        for page_number in range(start_page_number, (last_page_number or start_page_number) + 1):
            webpage = self._download_webpage(
                url, playlist_id,
                note='Downloading page %d%s' % (page_number, format_field(last_page_number, None, ' of %d')),
                query={'page': page_number})
            page_entries = [
                self.url_result(player_url, 'Youtube')
                for player_url in set(re.findall(r'data-youtube-url="([^"]+)"', webpage))
            ]

            for e in page_entries:
                yield e

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

class LastFMPlaylistIE(LastFMPlaylistBaseIE):
    _VALID_URL = r'https?://(?:www\.)?last\.fm/(music|tag)/(?P<id>[^/]+)(?:/[^/]+)?/?(?:[?#]|$)'
    _TESTS = [{
        'url': 'https://www.last.fm/music/Oasis/(What%27s+the+Story)+Morning+Glory%3F',
        'info_dict': {
            'id': 'Oasis',
        },
        'playlist_count': 11,
    }, {
        'url': 'https://www.last.fm/music/Oasis',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis?top_tracks_date_preset=ALL#top-tracks',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/+tracks',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/+tracks?page=2',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Oasis/+tracks?date_preset=LAST_90_DAYS#top-tracks',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/tag/rock',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/tag/rock/tracks',
        'only_matching': True,
    }]

class LastFMUserIE(LastFMPlaylistBaseIE):
    _VALID_URL = r'https?://(?:www\.)?last\.fm/user/[^/]+/playlists/(?P<id>[^/#?]+)'
    _TESTS = [{
        'url': 'https://www.last.fm/user/mehq/playlists/12319471',
        'info_dict': {
            'id': '12319471',
        },
        'playlist_count': 30,
    }]

class LastFMIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?last\.fm/music(?:/[^/]+){2}/(?P<id>[^/#?]+)'
    _TESTS = [{
        'url': 'https://www.last.fm/music/Oasis/_/Wonderwall',
        'md5': '9c4a70c2e84c03d54fe24229b9e13b7b',
        'info_dict': {
            'id': '6hzrDeceEKc',
            'ext': 'mp4',
            'title': 'Oasis - Wonderwall  (Official Video)',
            'thumbnail': r're:^https?://i.ytimg.com/.*\.jpg$',
            'description': 'md5:0848669853c10687cc28e88b5756738f',
            'uploader': 'Oasis',
            'uploader_id': 'oasisinetofficial',
            'upload_date': '20080207',
            'album': '(What\'s The Story) Morning Glory? (Remastered)',
            'track': 'Wonderwall (Remastered)',
            'channel_id': 'UCUDVBtnOQi4c7E8jebpjc9Q',
            'view_count': int,
            'live_status': 'not_live',
            'channel_url': 'https://www.youtube.com/channel/UCUDVBtnOQi4c7E8jebpjc9Q',
            'tags': 'count:39',
            'creator': 'Oasis',
            'uploader_url': 're:^https?://www.youtube.com/user/oasisinetofficial',
            'duration': 279,
            'alt_title': 'Wonderwall (Remastered)',
            'age_limit': 0,
            'channel': 'Oasis',
            'channel_follower_count': int,
            'categories': ['Music'],
            'availability': 'public',
            'like_count': int,
            'playable_in_embed': True,
            'artist': 'Oasis',
        },
        'add_ie': ['Youtube'],
    }, {
        'url': 'https://www.last.fm/music/Oasis/_/Don%27t+Look+Back+In+Anger+-+Remastered/',
        'only_matching': True,
    }, {
        'url': 'https://www.last.fm/music/Guns+N%27+Roses/_/Sweet+Child+o%27+Mine',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        player_url = self._search_regex(r'(?s)class="header-new-playlink"\s+href="([^"]+)"', webpage, 'player_url')
        return self.url_result(player_url, 'Youtube')


code:
import functools
import json

from .common import InfoExtractor
from ..compat import compat_str, compat_urllib_parse_unquote
from ..utils import (
    ExtractorError,
    HEADRequest,
    OnDemandPagedList,
    UnsupportedError,
    determine_ext,
    int_or_none,
    mimetype2ext,
    parse_qs,
    try_get,
    urljoin,
)

class LBRYBaseIE(InfoExtractor):
    _BASE_URL_REGEX = r'(?:https?://(?:www\.)?(?:lbry\.tv|odysee\.com)/|lbry://)'
    _CLAIM_ID_REGEX = r'[0-9a-f]{1,40}'
    _OPT_CLAIM_ID = '[^:/?#&]+(?:[:#]%s)?' % _CLAIM_ID_REGEX
    _SUPPORTED_STREAM_TYPES = ['video', 'audio']

    def _call_api_proxy(self, method, display_id, params, resource):
        response = self._download_json(
            'https://api.lbry.tv/api/v1/proxy',
            display_id, 'Downloading %s JSON metadata' % resource,
            headers={'Content-Type': 'application/json-rpc'},
            data=json.dumps({
                'method': method,
                'params': params,
            }).encode())
        err = response.get('error')
        if err:
            raise ExtractorError(
                f'{self.IE_NAME} said: {err.get("code")} - {err.get("message")}', expected=True)
        return response['result']

    def _resolve_url(self, url, display_id, resource):
        return self._call_api_proxy(
            'resolve', display_id, {'urls': url}, resource)[url]

    def _permanent_url(self, url, claim_name, claim_id):
        return urljoin(
            url.replace('lbry://', 'https://lbry.tv/'),
            '/%s:%s' % (claim_name, claim_id))

    def _parse_stream(self, stream, url):
        stream_value = stream.get('value') or {}
        stream_type = stream_value.get('stream_type')
        source = stream_value.get('source') or {}
        media = stream_value.get(stream_type) or {}
        signing_channel = stream.get('signing_channel') or {}
        channel_name = signing_channel.get('name')
        channel_claim_id = signing_channel.get('claim_id')
        channel_url = None
        if channel_name and channel_claim_id:
            channel_url = self._permanent_url(url, channel_name, channel_claim_id)

        info = {
            'thumbnail': try_get(stream_value, lambda x: x['thumbnail']['url'], compat_str),
            'description': stream_value.get('description'),
            'license': stream_value.get('license'),
            'timestamp': int_or_none(stream.get('timestamp')),
            'release_timestamp': int_or_none(stream_value.get('release_time')),
            'tags': stream_value.get('tags'),
            'duration': int_or_none(media.get('duration')),
            'channel': try_get(signing_channel, lambda x: x['value']['title']),
            'channel_id': channel_claim_id,
            'channel_url': channel_url,
            'ext': determine_ext(source.get('name')) or mimetype2ext(source.get('media_type')),
            'filesize': int_or_none(source.get('size')),
        }
        if stream_type == 'audio':
            info['vcodec'] = 'none'
        else:
            info.update({
                'width': int_or_none(media.get('width')),
                'height': int_or_none(media.get('height')),
            })
        return info

class LBRYIE(LBRYBaseIE):
    IE_NAME = 'lbry'
    _VALID_URL = LBRYBaseIE._BASE_URL_REGEX + r'(?P<id>\$/[^/]+/[^/]+/{1}|@{0}/{0}|(?!@){0})'.format(LBRYBaseIE._OPT_CLAIM_ID, LBRYBaseIE._CLAIM_ID_REGEX)
    _TESTS = [{
        # Video
        'url': 'https://lbry.tv/@Mantega:1/First-day-LBRY:1',
        'md5': 'fffd15d76062e9a985c22c7c7f2f4805',
        'info_dict': {
            'id': '17f983b61f53091fb8ea58a9c56804e4ff8cff4d',
            'ext': 'mp4',
            'title': 'First day in LBRY? Start HERE!',
            'description': 'md5:f6cb5c704b332d37f5119313c2c98f51',
            'timestamp': 1595694354,
            'upload_date': '20200725',
            'release_timestamp': 1595340697,
            'release_date': '20200721',
            'width': 1280,
            'height': 720,
            'thumbnail': 'https://spee.ch/7/67f2d809c263288c.png',
            'license': 'None',
            'duration': 346,
            'channel': 'LBRY/Odysee rats united!!!',
            'channel_id': '1c8ad6a2ab4e889a71146ae4deeb23bb92dab627',
            'channel_url': 'https://lbry.tv/@Mantega:1c8ad6a2ab4e889a71146ae4deeb23bb92dab627',
            'tags': [
                'first day in lbry',
                'lbc',
                'lbry',
                'start',
                'tutorial'
            ],
        }
    }, {
        # Audio
        'url': 'https://lbry.tv/@LBRYFoundation:0/Episode-1:e',
        'md5': 'c94017d3eba9b49ce085a8fad6b98d00',
        'info_dict': {
            'id': 'e7d93d772bd87e2b62d5ab993c1c3ced86ebb396',
            'ext': 'mp3',
            'title': 'The LBRY Foundation Community Podcast Episode 1 - Introduction, Streaming on LBRY, Transcoding',
            'description': 'md5:661ac4f1db09f31728931d7b88807a61',
            'timestamp': 1591312601,
            'upload_date': '20200604',
            'release_timestamp': 1591312421,
            'release_date': '20200604',
            'tags': list,
            'duration': 2570,
            'channel': 'The LBRY Foundation',
            'channel_id': '0ed629d2b9c601300cacf7eabe9da0be79010212',
            'channel_url': 'https://lbry.tv/@LBRYFoundation:0ed629d2b9c601300cacf7eabe9da0be79010212',
            'vcodec': 'none',
            'thumbnail': 'https://spee.ch/d/0bc63b0e6bf1492d.png',
            'license': 'None',
        }
    }, {
        # HLS
        'url': 'https://odysee.com/@gardeningincanada:b/plants-i-will-never-grow-again.-the:e',
        'md5': '25049011f3c8bc2f8b60ad88a031837e',
        'info_dict': {
            'id': 'e51671357333fe22ae88aad320bde2f6f96b1410',
            'ext': 'mp4',
            'title': 'PLANTS I WILL NEVER GROW AGAIN. THE BLACK LIST PLANTS FOR A CANADIAN GARDEN | Gardening in Canada 🍁',
            'description': 'md5:9c539c6a03fb843956de61a4d5288d5e',
            'timestamp': 1618254123,
            'upload_date': '20210412',
            'release_timestamp': 1618254002,
            'release_date': '20210412',
            'tags': list,
            'duration': 554,
            'channel': 'Gardening In Canada',
            'channel_id': 'b8be0e93b423dad221abe29545fbe8ec36e806bc',
            'channel_url': 'https://odysee.com/@gardeningincanada:b8be0e93b423dad221abe29545fbe8ec36e806bc',
            'formats': 'mincount:3',
            'thumbnail': 'https://thumbnails.lbry.com/AgHSc_HzrrE',
            'license': 'Copyrighted (contact publisher)',
        }
    }, {
        'url': 'https://odysee.com/@BrodieRobertson:5/apple-is-tracking-everything-you-do-on:e',
        'only_matching': True,
    }, {
        'url': 'https://odysee.com/@ScammerRevolts:b0/I-SYSKEY\'D-THE-SAME-SCAMMERS-3-TIMES!:b',
        'only_matching': True,
    }, {
        'url': 'https://lbry.tv/Episode-1:e7d93d772bd87e2b62d5ab993c1c3ced86ebb396',
        'only_matching': True,
    }, {
        'url': 'https://lbry.tv/$/embed/Episode-1/e7d93d772bd87e2b62d5ab993c1c3ced86ebb396',
        'only_matching': True,
    }, {
        'url': 'https://lbry.tv/Episode-1:e7',
        'only_matching': True,
    }, {
        'url': 'https://lbry.tv/@LBRYFoundation/Episode-1',
        'only_matching': True,
    }, {
        'url': 'https://lbry.tv/$/download/Episode-1/e7d93d772bd87e2b62d5ab993c1c3ced86ebb396',
        'only_matching': True,
    }, {
        'url': 'https://lbry.tv/@lacajadepandora:a/TRUMP-EST%C3%81-BIEN-PUESTO-con-Pilar-Baselga,-Carlos-Senra,-Luis-Palacios-(720p_30fps_H264-192kbit_AAC):1',
        'only_matching': True,
    }, {
        'url': 'lbry://@lbry#3f/odysee#7',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        if display_id.startswith('$/'):
            display_id = display_id.split('/', 2)[-1].replace('/', ':')
        else:
            display_id = display_id.replace(':', '#')
        display_id = compat_urllib_parse_unquote(display_id)
        uri = 'lbry://' + display_id
        result = self._resolve_url(uri, display_id, 'stream')
        if result['value'].get('stream_type') in self._SUPPORTED_STREAM_TYPES:
            claim_id, is_live, headers = result['claim_id'], False, {}
            streaming_url = self._call_api_proxy(
                'get', claim_id, {'uri': uri}, 'streaming url')['streaming_url']
            final_url = self._request_webpage(
                HEADRequest(streaming_url), display_id,
                note='Downloading streaming redirect url info').geturl()
        elif result.get('value_type') == 'stream':
            claim_id, is_live = result['signing_channel']['claim_id'], True
            headers = {'referer': 'https://player.odysee.live/'}
            live_data = self._download_json(
                'https://api.odysee.live/livestream/is_live', claim_id,
                query={'channel_claim_id': claim_id},
                note='Downloading livestream JSON metadata')['data']
            streaming_url = final_url = live_data.get('VideoURL')
            if not final_url and not live_data.get('Live'):
                self.raise_no_formats('This stream is not live', True, claim_id)
        else:
            raise UnsupportedError(url)

        info = self._parse_stream(result, url)
        if determine_ext(final_url) == 'm3u8':
            info['formats'] = self._extract_m3u8_formats(
                final_url, display_id, 'mp4', 'm3u8_native', m3u8_id='hls', live=is_live, headers=headers)
            self._sort_formats(info['formats'])
        else:
            info['url'] = streaming_url
        return {
            **info,
            'id': claim_id,
            'title': result['value']['title'],
            'is_live': is_live,
            'http_headers': headers,
        }

class LBRYChannelIE(LBRYBaseIE):
    IE_NAME = 'lbry:channel'
    _VALID_URL = LBRYBaseIE._BASE_URL_REGEX + r'(?P<id>@%s)/?(?:[?&]|$)' % LBRYBaseIE._OPT_CLAIM_ID
    _TESTS = [{
        'url': 'https://lbry.tv/@LBRYFoundation:0',
        'info_dict': {
            'id': '0ed629d2b9c601300cacf7eabe9da0be79010212',
            'title': 'The LBRY Foundation',
            'description': 'Channel for the LBRY Foundation. Follow for updates and news.',
        },
        'playlist_mincount': 29,
    }, {
        'url': 'https://lbry.tv/@LBRYFoundation',
        'only_matching': True,
    }, {
        'url': 'lbry://@lbry#3f',
        'only_matching': True,
    }]
    _PAGE_SIZE = 50

    def _fetch_page(self, claim_id, url, params, page):
        page += 1
        page_params = {
            'channel_ids': [claim_id],
            'claim_type': 'stream',
            'no_totals': True,
            'page': page,
            'page_size': self._PAGE_SIZE,
        }
        page_params.update(params)
        result = self._call_api_proxy(
            'claim_search', claim_id, page_params, 'page %d' % page)
        for item in (result.get('items') or []):
            stream_claim_name = item.get('name')
            stream_claim_id = item.get('claim_id')
            if not (stream_claim_name and stream_claim_id):
                continue

            info = self._parse_stream(item, url)
            info.update({
                '_type': 'url',
                'id': stream_claim_id,
                'title': try_get(item, lambda x: x['value']['title']),
                'url': self._permanent_url(url, stream_claim_name, stream_claim_id),
            })
            yield info

    def _real_extract(self, url):
        display_id = self._match_id(url).replace(':', '#')
        result = self._resolve_url(
            'lbry://' + display_id, display_id, 'channel')
        claim_id = result['claim_id']
        qs = parse_qs(url)
        content = qs.get('content', [None])[0]
        params = {
            'fee_amount': qs.get('fee_amount', ['>=0'])[0],
            'order_by': {
                'new': ['release_time'],
                'top': ['effective_amount'],
                'trending': ['trending_group', 'trending_mixed'],
            }[qs.get('order', ['new'])[0]],
            'stream_types': [content] if content in ['audio', 'video'] else self._SUPPORTED_STREAM_TYPES,
        }
        duration = qs.get('duration', [None])[0]
        if duration:
            params['duration'] = {
                'long': '>=1200',
                'short': '<=240',
            }[duration]
        language = qs.get('language', ['all'])[0]
        if language != 'all':
            languages = [language]
            if language == 'en':
                languages.append('none')
            params['any_languages'] = languages
        entries = OnDemandPagedList(
            functools.partial(self._fetch_page, claim_id, url, params),
            self._PAGE_SIZE)
        result_value = result.get('value') or {}
        return self.playlist_result(
            entries, claim_id, result_value.get('title'),
            result_value.get('description'))


code:
from .common import InfoExtractor

class LCIIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?:lci|tf1info)\.fr/[^/]+/[\w-]+-(?P<id>\d+)\.html'
    _TESTS = [{
        'url': 'https://www.tf1info.fr/politique/election-presidentielle-2022-second-tour-j-2-marine-le-pen-et-emmanuel-macron-en-interview-de-lci-vendredi-soir-2217486.html',
        'info_dict': {
            'id': '13875948',
            'ext': 'mp4',
            'title': 'md5:660df5481fd418bc3bbb0d070e6fdb5a',
            'thumbnail': 'https://photos.tf1.fr/1280/720/presidentielle-2022-marine-le-pen-et-emmanuel-macron-invites-de-lci-ce-vendredi-9c0e73-e1a036-0@1x.jpg',
            'upload_date': '20220422',
            'duration': 33,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.lci.fr/politique/election-presidentielle-2022-second-tour-j-2-marine-le-pen-et-emmanuel-macron-en-interview-de-lci-vendredi-soir-2217486.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        wat_id = self._search_regex(r'watId["\']?\s*:\s*["\']?(\d+)', webpage, 'wat id')
        return self.url_result('wat:' + wat_id, 'Wat', wat_id)


code:
from .common import InfoExtractor
from .arkena import ArkenaIE

class LcpPlayIE(ArkenaIE):
    _VALID_URL = r'https?://play\.lcp\.fr/embed/(?P<id>[^/]+)/(?P<account_id>[^/]+)/[^/]+/[^/]+'
    _TESTS = [{
        'url': 'http://play.lcp.fr/embed/327336/131064/darkmatter/0',
        'md5': 'b8bd9298542929c06c1c15788b1f277a',
        'info_dict': {
            'id': '327336',
            'ext': 'mp4',
            'title': '327336',
            'timestamp': 1456391602,
            'upload_date': '20160225',
        },
        'params': {
            'skip_download': True,
        },
    }]

class LcpIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?lcp\.fr/(?:[^/]+/)*(?P<id>[^/]+)'

    _TESTS = [{
        # arkena embed
        'url': 'http://www.lcp.fr/la-politique-en-video/schwartzenberg-prg-preconise-francois-hollande-de-participer-une-primaire',
        'md5': 'b8bd9298542929c06c1c15788b1f277a',
        'info_dict': {
            'id': 'd56d03e9',
            'ext': 'mp4',
            'title': 'Schwartzenberg (PRG) préconise à François Hollande de participer à une primaire à gauche',
            'description': 'md5:96ad55009548da9dea19f4120c6c16a8',
            'timestamp': 1456488895,
            'upload_date': '20160226',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # dailymotion live stream
        'url': 'http://www.lcp.fr/le-direct',
        'info_dict': {
            'id': 'xji3qy',
            'ext': 'mp4',
            'title': 'La Chaine Parlementaire (LCP), Live TNT',
            'description': 'md5:5c69593f2de0f38bd9a949f2c95e870b',
            'uploader': 'LCP',
            'uploader_id': 'xbz33d',
            'timestamp': 1308923058,
            'upload_date': '20110624',
        },
        'params': {
            # m3u8 live stream
            'skip_download': True,
        },
    }, {
        'url': 'http://www.lcp.fr/emissions/277792-les-volontaires',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, display_id)

        play_url = self._search_regex(
            r'<iframe[^>]+src=(["\'])(?P<url>%s?(?:(?!\1).)*)\1' % LcpPlayIE._VALID_URL,
            webpage, 'play iframe', default=None, group='url')

        if not play_url:
            return self.url_result(url, 'Generic')

        title = self._og_search_title(webpage, default=None) or self._html_search_meta(
            'twitter:title', webpage, fatal=True)
        description = self._html_search_meta(
            ('description', 'twitter:description'), webpage)

        return {
            '_type': 'url_transparent',
            'ie_key': LcpPlayIE.ie_key(),
            'url': play_url,
            'display_id': display_id,
            'title': title,
            'description': description,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    determine_protocol,
    parse_duration,
    int_or_none,
)

class Lecture2GoIE(InfoExtractor):
    _VALID_URL = r'https?://lecture2go\.uni-hamburg\.de/veranstaltungen/-/v/(?P<id>\d+)'
    _TEST = {
        'url': 'https://lecture2go.uni-hamburg.de/veranstaltungen/-/v/17473',
        'md5': 'ac02b570883020d208d405d5a3fd2f7f',
        'info_dict': {
            'id': '17473',
            'ext': 'mp4',
            'title': '2 - Endliche Automaten und reguläre Sprachen',
            'creator': 'Frank Heitmann',
            'duration': 5220,
        },
        'params': {
            # m3u8 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_regex(r']+class="title">(.+)', webpage, 'title')

        formats = []
        for url in set(re.findall(r'var\s+playerUri\d+\s*=\s*"([^"]+)"', webpage)):
            ext = determine_ext(url)
            protocol = determine_protocol({'url': url})
            if ext == 'f4m':
                formats.extend(self._extract_f4m_formats(url, video_id, f4m_id='hds'))
            elif ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(url, video_id, ext='mp4', m3u8_id='hls'))
            else:
                if protocol == 'rtmp':
                    continue  # XXX: currently broken
                formats.append({
                    'format_id': protocol,
                    'url': url,
                })

        self._sort_formats(formats)

        creator = self._html_search_regex(
            r'
]+id="description">([^<]+)
', webpage, 'creator', fatal=False)
        duration = parse_duration(self._html_search_regex(
            r'Duration:\s*\s*]*>([^<]+)', webpage, 'duration', fatal=False))
        view_count = int_or_none(self._html_search_regex(
            r'Views:\s*\s*]+>(\d+)', webpage, 'view count', fatal=False))

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'creator': creator,
            'duration': duration,
            'view_count': view_count,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    clean_html,
    determine_ext,
    ExtractorError,
    float_or_none,
    int_or_none,
    str_or_none,
    url_or_none,
    urlencode_postdata,
    urljoin,
)

class LecturioBaseIE(InfoExtractor):
    _API_BASE_URL = 'https://app.lecturio.com/api/en/latest/html5/'
    _LOGIN_URL = 'https://app.lecturio.com/en/login'
    _NETRC_MACHINE = 'lecturio'

    def _perform_login(self, username, password):
        # Sets some cookies
        _, urlh = self._download_webpage_handle(
            self._LOGIN_URL, None, 'Downloading login popup')

        def is_logged(url_handle):
            return self._LOGIN_URL not in url_handle.geturl()

        # Already logged in
        if is_logged(urlh):
            return

        login_form = {
            'signin[email]': username,
            'signin[password]': password,
            'signin[remember]': 'on',
        }

        response, urlh = self._download_webpage_handle(
            self._LOGIN_URL, None, 'Logging in',
            data=urlencode_postdata(login_form))

        # Logged in successfully
        if is_logged(urlh):
            return

        errors = self._html_search_regex(
            r'(?s)<ul[^>]+class=["\']error_list[^>]+>(.+?)</ul>', response,
            'errors', default=None)
        if errors:
            raise ExtractorError('Unable to login: %s' % errors, expected=True)
        raise ExtractorError('Unable to log in')

class LecturioIE(LecturioBaseIE):
    _VALID_URL = r'''(?x)
                    https://
                        (?:
                            app\.lecturio\.com/([^/]+/(?P<nt>[^/?#&]+)\.lecture|(?:\#/)?lecture/c/\d+/(?P<id>\d+))|
                            (?:www\.)?lecturio\.de/[^/]+/(?P<nt_de>[^/?#&]+)\.vortrag
                        )
                    '''
    _TESTS = [{
        'url': 'https://app.lecturio.com/medical-courses/important-concepts-and-terms-introduction-to-microbiology.lecture#tab/videos',
        'md5': '9a42cf1d8282a6311bf7211bbde26fde',
        'info_dict': {
            'id': '39634',
            'ext': 'mp4',
            'title': 'Important Concepts and Terms — Introduction to Microbiology',
        },
        'skip': 'Requires lecturio account credentials',
    }, {
        'url': 'https://www.lecturio.de/jura/oeffentliches-recht-staatsexamen.vortrag',
        'only_matching': True,
    }, {
        'url': 'https://app.lecturio.com/#/lecture/c/6434/39634',
        'only_matching': True,
    }]

    _CC_LANGS = {
        'Arabic': 'ar',
        'Bulgarian': 'bg',
        'German': 'de',
        'English': 'en',
        'Spanish': 'es',
        'Persian': 'fa',
        'French': 'fr',
        'Japanese': 'ja',
        'Polish': 'pl',
        'Pashto': 'ps',
        'Russian': 'ru',
    }

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        nt = mobj.group('nt') or mobj.group('nt_de')
        lecture_id = mobj.group('id')
        display_id = nt or lecture_id
        api_path = 'lectures/' + lecture_id if lecture_id else 'lecture/' + nt + '.json'
        video = self._download_json(
            self._API_BASE_URL + api_path, display_id)
        title = video['title'].strip()
        if not lecture_id:
            pid = video.get('productId') or video.get('uid')
            if pid:
                spid = pid.split('_')
                if spid and len(spid) == 2:
                    lecture_id = spid[1]

        formats = []
        for format_ in video['content']['media']:
            if not isinstance(format_, dict):
                continue
            file_ = format_.get('file')
            if not file_:
                continue
            ext = determine_ext(file_)
            if ext == 'smil':
                # smil contains only broken RTMP formats anyway
                continue
            file_url = url_or_none(file_)
            if not file_url:
                continue
            label = str_or_none(format_.get('label'))
            filesize = int_or_none(format_.get('fileSize'))
            f = {
                'url': file_url,
                'format_id': label,
                'filesize': float_or_none(filesize, invscale=1000)
            }
            if label:
                mobj = re.match(r'(\d+)p\s*\(([^)]+)\)', label)
                if mobj:
                    f.update({
                        'format_id': mobj.group(2),
                        'height': int(mobj.group(1)),
                    })
            formats.append(f)
        self._sort_formats(formats)

        subtitles = {}
        automatic_captions = {}
        captions = video.get('captions') or []
        for cc in captions:
            cc_url = cc.get('url')
            if not cc_url:
                continue
            cc_label = cc.get('translatedCode')
            lang = cc.get('languageCode') or self._search_regex(
                r'/([a-z]{2})_', cc_url, 'lang',
                default=cc_label.split()[0] if cc_label else 'en')
            original_lang = self._search_regex(
                r'/[a-z]{2}_([a-z]{2})_', cc_url, 'original lang',
                default=None)
            sub_dict = (automatic_captions
                        if 'auto-translated' in cc_label or original_lang
                        else subtitles)
            sub_dict.setdefault(self._CC_LANGS.get(lang, lang), []).append({
                'url': cc_url,
            })

        return {
            'id': lecture_id or nt,
            'title': title,
            'formats': formats,
            'subtitles': subtitles,
            'automatic_captions': automatic_captions,
        }

class LecturioCourseIE(LecturioBaseIE):
    _VALID_URL = r'https://app\.lecturio\.com/(?:[^/]+/(?P<nt>[^/?#&]+)\.course|(?:#/)?course/c/(?P<id>\d+))'
    _TESTS = [{
        'url': 'https://app.lecturio.com/medical-courses/microbiology-introduction.course#/',
        'info_dict': {
            'id': 'microbiology-introduction',
            'title': 'Microbiology: Introduction',
            'description': 'md5:13da8500c25880c6016ae1e6d78c386a',
        },
        'playlist_count': 45,
        'skip': 'Requires lecturio account credentials',
    }, {
        'url': 'https://app.lecturio.com/#/course/c/6434',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        nt, course_id = self._match_valid_url(url).groups()
        display_id = nt or course_id
        api_path = 'courses/' + course_id if course_id else 'course/content/' + nt + '.json'
        course = self._download_json(
            self._API_BASE_URL + api_path, display_id)
        entries = []
        for lecture in course.get('lectures', []):
            lecture_id = str_or_none(lecture.get('id'))
            lecture_url = lecture.get('url')
            if lecture_url:
                lecture_url = urljoin(url, lecture_url)
            else:
                lecture_url = 'https://app.lecturio.com/#/lecture/c/%s/%s' % (course_id, lecture_id)
            entries.append(self.url_result(
                lecture_url, ie=LecturioIE.ie_key(), video_id=lecture_id))
        return self.playlist_result(
            entries, display_id, course.get('title'),
            clean_html(course.get('description')))

class LecturioDeCourseIE(LecturioBaseIE):
    _VALID_URL = r'https://(?:www\.)?lecturio\.de/[^/]+/(?P<id>[^/?#&]+)\.kurs'
    _TEST = {
        'url': 'https://www.lecturio.de/jura/grundrechte.kurs',
        'only_matching': True,
    }

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

        webpage = self._download_webpage(url, display_id)

        entries = []
        for mobj in re.finditer(
                r'(?s)<td[^>]+\bdata-lecture-id=["\'](?P<id>\d+).+?\bhref=(["\'])(?P<url>(?:(?!\2).)+\.vortrag)\b[^>]+>',
                webpage):
            lecture_url = urljoin(url, mobj.group('url'))
            lecture_id = mobj.group('id')
            entries.append(self.url_result(
                lecture_url, ie=LecturioIE.ie_key(), video_id=lecture_id))

        title = self._search_regex(
            r'<h1[^>]*>([^<]+)', webpage, 'title', default=None)

        return self.playlist_result(entries, display_id, title)