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

class CommonMistakesIE(InfoExtractor):
    IE_DESC = False  # Do not list
    _VALID_URL = r'(?:url|URL|yt-dlp)$'

    _TESTS = [{
        'url': 'url',
        'only_matching': True,
    }, {
        'url': 'URL',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        msg = (
            'You\'ve asked yt-dlp to download the URL "%s". '
            'That doesn\'t make any sense. '
            'Simply remove the parameter in your command or configuration.'
        ) % url
        if not self.get_param('verbose'):
            msg += ' Add -v to the command line to see what arguments and configuration yt-dlp has'
        raise ExtractorError(msg, expected=True)

class UnicodeBOMIE(InfoExtractor):
    IE_DESC = False
    _VALID_URL = r'(?P<bom>\ufeff)(?P<id>.*)$'

    _TESTS = [{
        'url': '\ufeffhttp://www.youtube.com/watch?v=BaW_jenozKc',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        real_url = self._match_id(url)
        self.report_warning(
            'Your URL starts with a Byte Order Mark (BOM). '
            'Removing the BOM and looking for "%s" ...' % real_url)
        return self.url_result(real_url)

repo_name: Lesmiscore/ytdl


code:
import urllib.parse

from .common import InfoExtractor

class RtmpIE(InfoExtractor):
    IE_DESC = False  # Do not list
    _VALID_URL = r'(?i)rtmp[est]?://.+'

    _TESTS = [{
        'url': 'rtmp://cp44293.edgefcs.net/ondemand?auth=daEcTdydfdqcsb8cZcDbAaCbhamacbbawaS-bw7dBb-bWG-GqpGFqCpNCnGoyL&aifp=v001&slist=public/unsecure/audio/2c97899446428e4301471a8cb72b4b97--audio--pmg-20110908-0900a_flv_aac_med_int.mp4',
        'only_matching': True,
    }, {
        'url': 'rtmp://edge.live.hitbox.tv/live/dimak',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._generic_id(url)
        title = self._generic_title(url)
        return {
            'id': video_id,
            'title': title,
            'formats': [{
                'url': url,
                'ext': 'flv',
                'format_id': urllib.parse.urlparse(url).scheme,
            }],
        }

class MmsIE(InfoExtractor):
    IE_DESC = False  # Do not list
    _VALID_URL = r'(?i)mms://.+'

    _TEST = {
        # Direct MMS link
        'url': 'mms://kentro.kaist.ac.kr/200907/MilesReid(0709).wmv',
        'info_dict': {
            'id': 'MilesReid(0709)',
            'ext': 'wmv',
            'title': 'MilesReid(0709)',
        },
        'params': {
            'skip_download': True,  # rtsp downloads, requiring mplayer or mpv
        },
    }

    def _real_extract(self, url):
        video_id = self._generic_id(url)
        title = self._generic_title(url)

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


code:
import re

from .common import InfoExtractor
from ..compat import (
    compat_urllib_parse_urlparse,
    compat_urlparse,
)
from ..utils import (
    determine_ext,
    extract_attributes,
    int_or_none,
    js_to_json,
    mimetype2ext,
    orderedSet,
    parse_iso8601,
    strip_or_none,
    try_get,
)

class CondeNastIE(InfoExtractor):
    """
    Condé Nast is a media group, some of its sites use a custom HTML5 player
    that works the same in all of them.
    """

    # The keys are the supported sites and the values are the name to be shown
    # to the user and in the extractor description.
    _SITES = {
        'allure': 'Allure',
        'architecturaldigest': 'Architectural Digest',
        'arstechnica': 'Ars Technica',
        'bonappetit': 'Bon Appétit',
        'brides': 'Brides',
        'cnevids': 'Condé Nast',
        'cntraveler': 'Condé Nast Traveler',
        'details': 'Details',
        'epicurious': 'Epicurious',
        'glamour': 'Glamour',
        'golfdigest': 'Golf Digest',
        'gq': 'GQ',
        'newyorker': 'The New Yorker',
        'self': 'SELF',
        'teenvogue': 'Teen Vogue',
        'vanityfair': 'Vanity Fair',
        'vogue': 'Vogue',
        'wired': 'WIRED',
        'wmagazine': 'W Magazine',
    }

    _VALID_URL = r'''(?x)https?://(?:video|www|player(?:-backend)?)\.(?:%s)\.com/
        (?:
            (?:
                embed(?:js)?|
                (?:script|inline)/video
            )/(?P<id>[0-9a-f]{24})(?:/(?P<player_id>[0-9a-f]{24}))?(?:.+?\btarget=(?P<target>[^&]+))?|
            (?P<type>watch|series|video)/(?P<display_id>[^/?#]+)
        )''' % '|'.join(_SITES.keys())
    IE_DESC = 'Condé Nast media group: %s' % ', '.join(sorted(_SITES.values()))

    _EMBED_REGEX = [r'''(?x)
        <(?:iframe|script)[^>]+?src=(["\'])(?P<url>
            (?:https?:)?//player(?:-backend)?\.(?:%s)\.com/(?:embed(?:js)?|(?:script|inline)/video)/.+?
        )\1''' % '|'.join(_SITES.keys())]

    _TESTS = [{
        'url': 'http://video.wired.com/watch/3d-printed-speakers-lit-with-led',
        'md5': '1921f713ed48aabd715691f774c451f7',
        'info_dict': {
            'id': '5171b343c2b4c00dd0c1ccb3',
            'ext': 'mp4',
            'title': '3D Printed Speakers Lit With LED',
            'description': 'Check out these beautiful 3D printed LED speakers.  You can\'t actually buy them, but LumiGeek is working on a board that will let you make you\'re own.',
            'uploader': 'wired',
            'upload_date': '20130314',
            'timestamp': 1363219200,
        }
    }, {
        'url': 'http://video.gq.com/watch/the-closer-with-keith-olbermann-the-only-true-surprise-trump-s-an-idiot?c=series',
        'info_dict': {
            'id': '58d1865bfd2e6126e2000015',
            'ext': 'mp4',
            'title': 'The Only True Surprise? Trump’s an Idiot',
            'uploader': 'gq',
            'upload_date': '20170321',
            'timestamp': 1490126427,
            'description': 'How much grimmer would things be if these people were competent?',
        },
    }, {
        # JS embed
        'url': 'http://player.cnevids.com/embedjs/55f9cf8b61646d1acf00000c/5511d76261646d5566020000.js',
        'md5': 'f1a6f9cafb7083bab74a710f65d08999',
        'info_dict': {
            'id': '55f9cf8b61646d1acf00000c',
            'ext': 'mp4',
            'title': '3D printed TSA Travel Sentry keys really do open TSA locks',
            'uploader': 'arstechnica',
            'upload_date': '20150916',
            'timestamp': 1442434920,
        }
    }, {
        'url': 'https://player.cnevids.com/inline/video/59138decb57ac36b83000005.js?target=js-cne-player',
        'only_matching': True,
    }, {
        'url': 'http://player-backend.cnevids.com/script/video/59138decb57ac36b83000005.js',
        'only_matching': True,
    }]

    def _extract_series(self, url, webpage):
        title = self._html_search_regex(
            r'(?s)
.*?
(.+?)
',
            webpage, 'series title')
        url_object = compat_urllib_parse_urlparse(url)
        base_url = '%s://%s' % (url_object.scheme, url_object.netloc)
        m_paths = re.finditer(
            r'(?s)
.*?<a href="(/watch/.+?)["\?]', webpage)
        paths = orderedSet(m.group(1) for m in m_paths)
        build_url = lambda path: compat_urlparse.urljoin(base_url, path)
        entries = [self.url_result(build_url(path), 'CondeNast') for path in paths]
        return self.playlist_result(entries, playlist_title=title)

    def _extract_video_params(self, webpage, display_id):
        query = self._parse_json(
            self._search_regex(
                r'(?s)var\s+params\s*=\s*({.+?})[;,]', webpage, 'player params',
                default='{}'),
            display_id, transform_source=js_to_json, fatal=False)
        if query:
            query['videoId'] = self._search_regex(
                r'(?:data-video-id=|currentVideoId\s*=\s*)["\']([\da-f]+)',
                webpage, 'video id', default=None)
        else:
            params = extract_attributes(self._search_regex(
                r'(<[^>]+data-js="video-player"[^>]+>)',
                webpage, 'player params element'))
            query.update({
                'videoId': params['data-video'],
                'playerId': params['data-player'],
                'target': params['id'],
            })
        return query

    def _extract_video(self, params):
        video_id = params['videoId']

        video_info = None

        # New API path
        query = params.copy()
        query['embedType'] = 'inline'
        info_page = self._download_json(
            'http://player.cnevids.com/embed-api.json', video_id,
            'Downloading embed info', fatal=False, query=query)

        # Old fallbacks
        if not info_page:
            if params.get('playerId'):
                info_page = self._download_json(
                    'http://player.cnevids.com/player/video.js', video_id,
                    'Downloading video info', fatal=False, query=params)
        if info_page:
            video_info = info_page.get('video')
        if not video_info:
            info_page = self._download_webpage(
                'http://player.cnevids.com/player/loader.js',
                video_id, 'Downloading loader info', query=params)
        if not video_info:
            info_page = self._download_webpage(
                'https://player.cnevids.com/inline/video/%s.js' % video_id,
                video_id, 'Downloading inline info', query={
                    'target': params.get('target', 'embedplayer')
                })

        if not video_info:
            video_info = self._parse_json(
                self._search_regex(
                    r'(?s)var\s+config\s*=\s*({.+?});', info_page, 'config'),
                video_id, transform_source=js_to_json)['video']

        title = video_info['title']

        formats = []
        for fdata in video_info['sources']:
            src = fdata.get('src')
            if not src:
                continue
            ext = mimetype2ext(fdata.get('type')) or determine_ext(src)
            if ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    src, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
                continue
            quality = fdata.get('quality')
            formats.append({
                'format_id': ext + ('-%s' % quality if quality else ''),
                'url': src,
                'ext': ext,
                'quality': 1 if quality == 'high' else 0,
            })
        self._sort_formats(formats)

        subtitles = {}
        for t, caption in video_info.get('captions', {}).items():
            caption_url = caption.get('src')
            if not (t in ('vtt', 'srt', 'tml') and caption_url):
                continue
            subtitles.setdefault('en', []).append({'url': caption_url})

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'thumbnail': video_info.get('poster_frame'),
            'uploader': video_info.get('brand'),
            'duration': int_or_none(video_info.get('duration')),
            'tags': video_info.get('tags'),
            'series': video_info.get('series_title'),
            'season': video_info.get('season_title'),
            'timestamp': parse_iso8601(video_info.get('premiere_date')),
            'categories': video_info.get('categories'),
            'subtitles': subtitles,
        }

    def _real_extract(self, url):
        video_id, player_id, target, url_type, display_id = self._match_valid_url(url).groups()

        if video_id:
            return self._extract_video({
                'videoId': video_id,
                'playerId': player_id,
                'target': target,
            })

        webpage = self._download_webpage(url, display_id)

        if url_type == 'series':
            return self._extract_series(url, webpage)
        else:
            video = try_get(self._parse_json(self._search_regex(
                r'__PRELOADED_STATE__\s*=\s*({.+?});', webpage,
                'preload state', '{}'), display_id),
                lambda x: x['transformed']['video'])
            if video:
                params = {'videoId': video['id']}
                info = {'description': strip_or_none(video.get('description'))}
            else:
                params = self._extract_video_params(webpage, display_id)
                info = self._search_json_ld(
                    webpage, display_id, fatal=False)
            info.update(self._extract_video(params))
            return info


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

class CONtvIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?contv\.com/details-movie/(?P<id>[^/]+)'
    _TESTS = [{
        'url': 'https://www.contv.com/details-movie/CEG10022949/days-of-thrills-&-laughter',
        'info_dict': {
            'id': 'CEG10022949',
            'ext': 'mp4',
            'title': 'Days Of Thrills & Laughter',
            'description': 'md5:5d6b3d0b1829bb93eb72898c734802eb',
            'upload_date': '20180703',
            'timestamp': 1530634789.61,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'https://www.contv.com/details-movie/CLIP-show_fotld_bts/fight-of-the-living-dead:-behind-the-scenes-bites',
        'info_dict': {
            'id': 'CLIP-show_fotld_bts',
            'title': 'Fight of the Living Dead: Behind the Scenes Bites',
        },
        'playlist_mincount': 7,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        details = self._download_json(
            'http://metax.contv.live.junctiontv.net/metax/2.5/details/' + video_id,
            video_id, query={'device': 'web'})

        if details.get('type') == 'episodic':
            seasons = self._download_json(
                'http://metax.contv.live.junctiontv.net/metax/2.5/seriesfeed/json/' + video_id,
                video_id)
            entries = []
            for season in seasons:
                for episode in season.get('episodes', []):
                    episode_id = episode.get('id')
                    if not episode_id:
                        continue
                    entries.append(self.url_result(
                        'https://www.contv.com/details-movie/' + episode_id,
                        CONtvIE.ie_key(), episode_id))
            return self.playlist_result(entries, video_id, details.get('title'))

        m_details = details['details']
        title = details['title']

        formats = []

        media_hls_url = m_details.get('media_hls_url')
        if media_hls_url:
            formats.extend(self._extract_m3u8_formats(
                media_hls_url, video_id, 'mp4',
                m3u8_id='hls', fatal=False))

        media_mp4_url = m_details.get('media_mp4_url')
        if media_mp4_url:
            formats.append({
                'format_id': 'http',
                'url': media_mp4_url,
            })

        self._sort_formats(formats)

        subtitles = {}
        captions = m_details.get('captions') or {}
        for caption_url in captions.values():
            subtitles.setdefault('en', []).append({
                'url': caption_url
            })

        thumbnails = []
        for image in m_details.get('images', []):
            image_url = image.get('url')
            if not image_url:
                continue
            thumbnails.append({
                'url': image_url,
                'width': int_or_none(image.get('width')),
                'height': int_or_none(image.get('height')),
            })

        description = None
        for p in ('large_', 'medium_', 'small_', ''):
            d = m_details.get(p + 'description')
            if d:
                description = d
                break

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'thumbnails': thumbnails,
            'description': description,
            'timestamp': float_or_none(details.get('metax_added_on'), 1000),
            'subtitles': subtitles,
            'duration': float_or_none(m_details.get('duration'), 1000),
            'view_count': int_or_none(details.get('num_watched')),
            'like_count': int_or_none(details.get('num_fav')),
            'categories': details.get('category'),
            'tags': details.get('tags'),
            'season_number': int_or_none(details.get('season')),
            'episode_number': int_or_none(details.get('episode')),
            'release_year': int_or_none(details.get('pub_year')),
        }


code:
from .theplatform import ThePlatformFeedIE
from ..utils import (
    dict_get,
    ExtractorError,
    float_or_none,
    int_or_none,
)

class CorusIE(ThePlatformFeedIE):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:www\.)?
                        (?P<domain>
                            (?:
                                globaltv|
                                etcanada|
                                seriesplus|
                                wnetwork|
                                ytv
                            )\.com|
                            (?:
                                hgtv|
                                foodnetwork|
                                slice|
                                history|
                                showcase|
                                bigbrothercanada|
                                abcspark|
                                disney(?:channel|lachaine)
                            )\.ca
                        )
                        /(?:[^/]+/)*
                        (?:
                            video\.html\?.*?\bv=|
                            videos?/(?:[^/]+/)*(?:[a-z0-9-]+-)?
                        )
                        (?P<id>
                            [\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}|
                            (?:[A-Z]{4})?\d{12,20}
                        )
                    '''
    _TESTS = [{
        'url': 'http://www.hgtv.ca/shows/bryan-inc/videos/movie-night-popcorn-with-bryan-870923331648/',
        'info_dict': {
            'id': '870923331648',
            'ext': 'mp4',
            'title': 'Movie Night Popcorn with Bryan',
            'description': 'Bryan whips up homemade popcorn, the old fashion way for Jojo and Lincoln.',
            'upload_date': '20170206',
            'timestamp': 1486392197,
        },
        'params': {
            'skip_download': True,
        },
        'expected_warnings': ['Failed to parse JSON'],
    }, {
        'url': 'http://www.foodnetwork.ca/shows/chopped/video/episode/chocolate-obsession/video.html?v=872683587753',
        'only_matching': True,
    }, {
        'url': 'http://etcanada.com/video/873675331955/meet-the-survivor-game-changers-castaways-part-2/',
        'only_matching': True,
    }, {
        'url': 'http://www.history.ca/the-world-without-canada/video/full-episodes/natural-resources/video.html?v=955054659646#video',
        'only_matching': True,
    }, {
        'url': 'http://www.showcase.ca/eyewitness/video/eyewitness++106/video.html?v=955070531919&p=1&s=da#video',
        'only_matching': True,
    }, {
        'url': 'http://www.bigbrothercanada.ca/video/1457812035894/',
        'only_matching': True
    }, {
        'url': 'https://www.bigbrothercanada.ca/video/big-brother-canada-704/1457812035894/',
        'only_matching': True
    }, {
        'url': 'https://www.seriesplus.com/emissions/dre-mary-mort-sur-ordonnance/videos/deux-coeurs-battant/SERP0055626330000200/',
        'only_matching': True
    }, {
        'url': 'https://www.disneychannel.ca/shows/gabby-duran-the-unsittables/video/crybaby-duran-clip/2f557eec-0588-11ea-ae2b-e2c6776b770e/',
        'only_matching': True
    }]
    _GEO_BYPASS = False
    _SITE_MAP = {
        'globaltv': 'series',
        'etcanada': 'series',
        'foodnetwork': 'food',
        'bigbrothercanada': 'series',
        'disneychannel': 'disneyen',
        'disneylachaine': 'disneyfr',
    }

    def _real_extract(self, url):
        domain, video_id = self._match_valid_url(url).groups()
        site = domain.split('.')[0]
        path = self._SITE_MAP.get(site, site)
        if path != 'series':
            path = 'migration/' + path
        video = self._download_json(
            'https://globalcontent.corusappservices.com/templates/%s/playlist/' % path,
            video_id, query={'byId': video_id},
            headers={'Accept': 'application/json'})[0]
        title = video['title']

        formats = []
        for source in video.get('sources', []):
            smil_url = source.get('file')
            if not smil_url:
                continue
            source_type = source.get('type')
            note = 'Downloading%s smil file' % (' ' + source_type if source_type else '')
            resp = self._download_webpage(
                smil_url, video_id, note, fatal=False,
                headers=self.geo_verification_headers())
            if not resp:
                continue
            error = self._parse_json(resp, video_id, fatal=False)
            if error:
                if error.get('exception') == 'GeoLocationBlocked':
                    self.raise_geo_restricted(countries=['CA'])
                raise ExtractorError(error['description'])
            smil = self._parse_xml(resp, video_id, fatal=False)
            if smil is None:
                continue
            namespace = self._parse_smil_namespace(smil)
            formats.extend(self._parse_smil_formats(
                smil, smil_url, video_id, namespace))
        if not formats and video.get('drm'):
            self.report_drm(video_id)
        self._sort_formats(formats)

        subtitles = {}
        for track in video.get('tracks', []):
            track_url = track.get('file')
            if not track_url:
                continue
            lang = 'fr' if site in ('disneylachaine', 'seriesplus') else 'en'
            subtitles.setdefault(lang, []).append({'url': track_url})

        metadata = video.get('metadata') or {}
        get_number = lambda x: int_or_none(video.get('pl1$' + x) or metadata.get(x + 'Number'))

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'thumbnail': dict_get(video, ('defaultThumbnailUrl', 'thumbnail', 'image')),
            'description': video.get('description'),
            'timestamp': int_or_none(video.get('availableDate'), 1000),
            'subtitles': subtitles,
            'duration': float_or_none(metadata.get('duration')),
            'series': dict_get(video, ('show', 'pl1$show')),
            'season_number': get_number('season'),
            'episode_number': get_number('episode'),
        }


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

class CoubIE(InfoExtractor):
    _VALID_URL = r'(?:coub:|https?://(?:coub\.com/(?:view|embed|coubs)/|c-cdn\.coub\.com/fb-player\.swf\?.*\bcoub(?:ID|id)=))(?P<id>[\da-z]+)'

    _TESTS = [{
        'url': 'http://coub.com/view/5u5n1',
        'info_dict': {
            'id': '5u5n1',
            'ext': 'mp4',
            'title': 'The Matrix Moonwalk',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 4.6,
            'timestamp': 1428527772,
            'upload_date': '20150408',
            'uploader': 'Artyom Loskutnikov',
            'uploader_id': 'artyom.loskutnikov',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'age_limit': 0,
        },
    }, {
        'url': 'http://c-cdn.coub.com/fb-player.swf?bot_type=vk&coubID=7w5a4',
        'only_matching': True,
    }, {
        'url': 'coub:5u5n1',
        'only_matching': True,
    }, {
        # longer video id
        'url': 'http://coub.com/view/237d5l5h',
        'only_matching': True,
    }]

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

        coub = self._download_json(
            'http://coub.com/api/v2/coubs/%s.json' % video_id, video_id)

        if coub.get('error'):
            raise ExtractorError(
                '%s said: %s' % (self.IE_NAME, coub['error']), expected=True)

        title = coub['title']

        file_versions = coub['file_versions']

        QUALITIES = ('low', 'med', 'high', 'higher')

        MOBILE = 'mobile'
        IPHONE = 'iphone'
        HTML5 = 'html5'

        SOURCE_PREFERENCE = (MOBILE, IPHONE, HTML5)

        quality_key = qualities(QUALITIES)
        preference_key = qualities(SOURCE_PREFERENCE)

        formats = []

        for kind, items in file_versions.get(HTML5, {}).items():
            if kind not in ('video', 'audio'):
                continue
            if not isinstance(items, dict):
                continue
            for quality, item in items.items():
                if not isinstance(item, dict):
                    continue
                item_url = item.get('url')
                if not item_url:
                    continue
                formats.append({
                    'url': item_url,
                    'format_id': '%s-%s-%s' % (HTML5, kind, quality),
                    'filesize': int_or_none(item.get('size')),
                    'vcodec': 'none' if kind == 'audio' else None,
                    'acodec': 'none' if kind == 'video' else None,
                    'quality': quality_key(quality),
                    'source_preference': preference_key(HTML5),
                })

        iphone_url = file_versions.get(IPHONE, {}).get('url')
        if iphone_url:
            formats.append({
                'url': iphone_url,
                'format_id': IPHONE,
                'source_preference': preference_key(IPHONE),
            })

        mobile_url = file_versions.get(MOBILE, {}).get('audio_url')
        if mobile_url:
            formats.append({
                'url': mobile_url,
                'format_id': '%s-audio' % MOBILE,
                'source_preference': preference_key(MOBILE),
            })

        self._sort_formats(formats)

        thumbnail = coub.get('picture')
        duration = float_or_none(coub.get('duration'))
        timestamp = parse_iso8601(coub.get('published_at') or coub.get('created_at'))
        uploader = coub.get('channel', {}).get('title')
        uploader_id = coub.get('channel', {}).get('permalink')

        view_count = int_or_none(coub.get('views_count') or coub.get('views_increase_count'))
        like_count = int_or_none(coub.get('likes_count'))
        repost_count = int_or_none(coub.get('recoubs_count'))

        age_restricted = coub.get('age_restricted', coub.get('age_restricted_by_admin'))
        if age_restricted is not None:
            age_limit = 18 if age_restricted is True else 0
        else:
            age_limit = None

        return {
            'id': video_id,
            'title': title,
            'thumbnail': thumbnail,
            'duration': duration,
            'timestamp': timestamp,
            'uploader': uploader,
            'uploader_id': uploader_id,
            'view_count': view_count,
            'like_count': like_count,
            'repost_count': repost_count,
            'age_limit': age_limit,
            'formats': formats,
        }


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

class CozyTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?cozy\.tv/(?P<uploader>[^/]+)/replays/(?P<id>[^/$#&?]+)'

    _TESTS = [{
        'url': 'https://cozy.tv/beardson/replays/2021-11-19_1',
        'info_dict': {
            'id': 'beardson-2021-11-19_1',
            'ext': 'mp4',
            'title': 'pokemon pt2',
            'uploader': 'beardson',
            'upload_date': '20211119',
            'was_live': True,
            'duration': 7981,
        },
        'params': {'skip_download': True}
    }]

    def _real_extract(self, url):
        uploader, date = self._match_valid_url(url).groups()
        id = f'{uploader}-{date}'
        data_json = self._download_json(f'https://api.cozy.tv/cache/{uploader}/replay/{date}', id)
        formats, subtitles = self._extract_m3u8_formats_and_subtitles(
            f'https://cozycdn.foxtrotstream.xyz/replays/{uploader}/{date}/index.m3u8', id, ext='mp4')
        return {
            'id': id,
            'title': data_json.get('title'),
            'uploader': data_json.get('user') or uploader,
            'upload_date': unified_strdate(data_json.get('date')),
            'was_live': True,
            'duration': data_json.get('duration'),
            'formats': formats,
            'subtitles': subtitles,
        }


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    int_or_none,
    str_or_none,
    try_get,
    unified_timestamp,
    update_url_query,
    urljoin,
)

class CPACIE(InfoExtractor):
    IE_NAME = 'cpac'
    _VALID_URL = r'https?://(?:www\.)?cpac\.ca/(?P<fr>l-)?episode\?id=(?P<id>[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12})'
    _TEST = {
        # 'url': 'http://www.cpac.ca/en/programs/primetime-politics/episodes/65490909',
        'url': 'https://www.cpac.ca/episode?id=fc7edcae-4660-47e1-ba61-5b7f29a9db0f',
        'md5': 'e46ad699caafd7aa6024279f2614e8fa',
        'info_dict': {
            'id': 'fc7edcae-4660-47e1-ba61-5b7f29a9db0f',
            'ext': 'mp4',
            'upload_date': '20220215',
            'title': 'News Conference to Celebrate National Kindness Week – February 15, 2022',
            'description': 'md5:466a206abd21f3a6f776cdef290c23fb',
            'timestamp': 1644901200,
        },
        'params': {
            'format': 'bestvideo',
            'hls_prefer_native': True,
        },
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        url_lang = 'fr' if '/l-episode?' in url else 'en'

        content = self._download_json(
            'https://www.cpac.ca/api/1/services/contentModel.json?url=/site/website/episode/index.xml&crafterSite=cpacca&id=' + video_id,
            video_id)
        video_url = try_get(content, lambda x: x['page']['details']['videoUrl'], compat_str)
        formats = []
        if video_url:
            content = content['page']
            title = str_or_none(content['details']['title_%s_t' % (url_lang, )])
            formats = self._extract_m3u8_formats(video_url, video_id, m3u8_id='hls', ext='mp4')
            for fmt in formats:
                # prefer language to match URL
                fmt_lang = fmt.get('language')
                if fmt_lang == url_lang:
                    fmt['language_preference'] = 10
                elif not fmt_lang:
                    fmt['language_preference'] = -1
                else:
                    fmt['language_preference'] = -10

        self._sort_formats(formats)

        category = str_or_none(content['details']['category_%s_t' % (url_lang, )])

        def is_live(v_type):
            return (v_type == 'live') if v_type is not None else None

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'description': str_or_none(content['details'].get('description_%s_t' % (url_lang, ))),
            'timestamp': unified_timestamp(content['details'].get('liveDateTime')),
            'category': [category] if category else None,
            'thumbnail': urljoin(url, str_or_none(content['details'].get('image_%s_s' % (url_lang, )))),
            'is_live': is_live(content['details'].get('type')),
        }

class CPACPlaylistIE(InfoExtractor):
    IE_NAME = 'cpac:playlist'
    _VALID_URL = r'(?i)https?://(?:www\.)?cpac\.ca/(?:program|search|(?P<fr>emission|rechercher))\?(?:[^&]+&)*?(?P<id>(?:id=\d+|programId=\d+|key=[^&]+))'

    _TESTS = [{
        'url': 'https://www.cpac.ca/program?id=6',
        'info_dict': {
            'id': 'id=6',
            'title': 'Headline Politics',
            'description': 'Watch CPAC’s signature long-form coverage of the day’s pressing political events as they unfold.',
        },
        'playlist_count': 10,
    }, {
        'url': 'https://www.cpac.ca/search?key=hudson&type=all&order=desc',
        'info_dict': {
            'id': 'key=hudson',
            'title': 'hudson',
        },
        'playlist_count': 22,
    }, {
        'url': 'https://www.cpac.ca/search?programId=50',
        'info_dict': {
            'id': 'programId=50',
            'title': '50',
        },
        'playlist_count': 9,
    }, {
        'url': 'https://www.cpac.ca/emission?id=6',
        'only_matching': True,
    }, {
        'url': 'https://www.cpac.ca/rechercher?key=hudson&type=all&order=desc',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        url_lang = 'fr' if any(x in url for x in ('/emission?', '/rechercher?')) else 'en'
        pl_type, list_type = ('program', 'itemList') if any(x in url for x in ('/program?', '/emission?')) else ('search', 'searchResult')
        api_url = (
            'https://www.cpac.ca/api/1/services/contentModel.json?url=/site/website/%s/index.xml&crafterSite=cpacca&%s'
            % (pl_type, video_id, ))
        content = self._download_json(api_url, video_id)
        entries = []
        total_pages = int_or_none(try_get(content, lambda x: x['page'][list_type]['totalPages']), default=1)
        for page in range(1, total_pages + 1):
            if page > 1:
                api_url = update_url_query(api_url, {'page': '%d' % (page, ), })
                content = self._download_json(
                    api_url, video_id,
                    note='Downloading continuation - %d' % (page, ),
                    fatal=False)

            for item in try_get(content, lambda x: x['page'][list_type]['item'], list) or []:
                episode_url = urljoin(url, try_get(item, lambda x: x['url_%s_s' % (url_lang, )]))
                if episode_url:
                    entries.append(episode_url)

        return self.playlist_result(
            (self.url_result(entry) for entry in entries),
            playlist_id=video_id,
            playlist_title=try_get(content, lambda x: x['page']['program']['title_%s_t' % (url_lang, )]) or video_id.split('=')[-1],
            playlist_description=try_get(content, lambda x: x['page']['program']['description_%s_t' % (url_lang, )]),
        )


code:
import re

from .common import InfoExtractor
from .youtube import YoutubeIE
from ..utils import (
    parse_iso8601,
    str_to_int,
)

class CrackedIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?cracked\.com/video_(?P<id>\d+)_[\da-z-]+\.html'
    _TESTS = [{
        'url': 'http://www.cracked.com/video_19070_if-animal-actors-got-e21-true-hollywood-stories.html',
        'md5': '89b90b9824e3806ca95072c4d78f13f7',
        'info_dict': {
            'id': '19070',
            'ext': 'mp4',
            'title': 'If Animal Actors Got E! True Hollywood Stories',
            'timestamp': 1404954000,
            'upload_date': '20140710',
        }
    }, {
        # youtube embed
        'url': 'http://www.cracked.com/video_19006_4-plot-holes-you-didnt-notice-in-your-favorite-movies.html',
        'md5': 'ccd52866b50bde63a6ef3b35016ba8c7',
        'info_dict': {
            'id': 'EjI00A3rZD0',
            'ext': 'mp4',
            'title': "4 Plot Holes You Didn't Notice in Your Favorite Movies - The Spit Take",
            'description': 'md5:c603708c718b796fe6079e2b3351ffc7',
            'upload_date': '20140725',
            'uploader_id': 'Cracked',
            'uploader': 'Cracked',
        }
    }]

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

        webpage = self._download_webpage(url, video_id)

        youtube_url = YoutubeIE._extract_url(webpage)
        if youtube_url:
            return self.url_result(youtube_url, ie=YoutubeIE.ie_key())

        video_url = self._html_search_regex(
            [r'var\s+CK_vidSrc\s*=\s*"([^"]+)"', r'<video\s+src="([^"]+)"'],
            webpage, 'video URL')

        title = self._search_regex(
            [r'property="?og:title"?\s+content="([^"]+)"', r'class="?title"?>([^<]+)'],
            webpage, 'title')

        description = self._search_regex(
            r'name="?(?:og:)?description"?\s+content="([^"]+)"',
            webpage, 'description', default=None)

        timestamp = self._html_search_regex(
            r'"date"\s*:\s*"([^"]+)"', webpage, 'upload date', fatal=False)
        if timestamp:
            timestamp = parse_iso8601(timestamp[:-6])

        view_count = str_to_int(self._html_search_regex(
            r'([\d,\.]+) Views',
            webpage, 'view count', fatal=False))
        comment_count = str_to_int(self._html_search_regex(
            r'([\d,\.]+)',
            webpage, 'comment count', fatal=False))

        m = re.search(r'_(?P<width>\d+)X(?P<height>\d+)\.mp4$', video_url)
        if m:
            width = int(m.group('width'))
            height = int(m.group('height'))
        else:
            width = height = None

        return {
            'id': video_id,
            'url': video_url,
            'title': title,
            'description': description,
            'timestamp': timestamp,
            'view_count': view_count,
            'comment_count': comment_count,
            'height': height,
            'width': width,
        }


code:
import hashlib
import hmac
import re
import time

from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    determine_ext,
    float_or_none,
    int_or_none,
    orderedSet,
    parse_age_limit,
    parse_duration,
    url_or_none,
    ExtractorError
)

class CrackleIE(InfoExtractor):
    _VALID_URL = r'(?:crackle:|https?://(?:(?:www|m)\.)?(?:sony)?crackle\.com/(?:playlist/\d+/|(?:[^/]+/)+))(?P<id>\d+)'
    _TESTS = [{
        # Crackle is available in the United States and territories
        'url': 'https://www.crackle.com/thanksgiving/2510064',
        'info_dict': {
            'id': '2510064',
            'ext': 'mp4',
            'title': 'Touch Football',
            'description': 'md5:cfbb513cf5de41e8b56d7ab756cff4df',
            'duration': 1398,
            'view_count': int,
            'average_rating': 0,
            'age_limit': 17,
            'genre': 'Comedy',
            'creator': 'Daniel Powell',
            'artist': 'Chris Elliott, Amy Sedaris',
            'release_year': 2016,
            'series': 'Thanksgiving',
            'episode': 'Touch Football',
            'season_number': 1,
            'episode_number': 1,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'expected_warnings': [
            'Trying with a list of known countries'
        ],
    }, {
        'url': 'https://www.sonycrackle.com/thanksgiving/2510064',
        'only_matching': True,
    }]

    _MEDIA_FILE_SLOTS = {
        '360p.mp4': {
            'width': 640,
            'height': 360,
        },
        '480p.mp4': {
            'width': 768,
            'height': 432,
        },
        '480p_1mbps.mp4': {
            'width': 852,
            'height': 480,
        },
    }

    def _download_json(self, url, *args, **kwargs):
        # Authorization generation algorithm is reverse engineered from:
        # https://www.sonycrackle.com/static/js/main.ea93451f.chunk.js
        timestamp = time.strftime('%Y%m%d%H%M', time.gmtime())
        h = hmac.new(b'IGSLUQCBDFHEOIFM', '|'.join([url, timestamp]).encode(), hashlib.sha1).hexdigest().upper()
        headers = {
            'Accept': 'application/json',
            'Authorization': '|'.join([h, timestamp, '117', '1']),
        }
        return InfoExtractor._download_json(self, url, *args, headers=headers, **kwargs)

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

        geo_bypass_country = self.get_param('geo_bypass_country', None)
        countries = orderedSet((geo_bypass_country, 'US', 'AU', 'CA', 'AS', 'FM', 'GU', 'MP', 'PR', 'PW', 'MH', 'VI', ''))
        num_countries, num = len(countries) - 1, 0

        media = {}
        for num, country in enumerate(countries):
            if num == 1:  # start hard-coded list
                self.report_warning('%s. Trying with a list of known countries' % (
                    'Unable to obtain video formats from %s API' % geo_bypass_country if geo_bypass_country
                    else 'No country code was given using --geo-bypass-country'))
            elif num == num_countries:  # end of list
                geo_info = self._download_json(
                    'https://web-api-us.crackle.com/Service.svc/geo/country',
                    video_id, fatal=False, note='Downloading geo-location information from crackle API',
                    errnote='Unable to fetch geo-location information from crackle') or {}
                country = geo_info.get('CountryCode')
                if country is None:
                    continue
                self.to_screen('%s identified country as %s' % (self.IE_NAME, country))
                if country in countries:
                    self.to_screen('Downloading from %s API was already attempted. Skipping...' % country)
                    continue

            if country is None:
                continue
            try:
                media = self._download_json(
                    'https://web-api-us.crackle.com/Service.svc/details/media/%s/%s?disableProtocols=true' % (video_id, country),
                    video_id, note='Downloading media JSON from %s API' % country,
                    errnote='Unable to download media JSON')
            except ExtractorError as e:
                # 401 means geo restriction, trying next country
                if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                    continue
                raise

            status = media.get('status')
            if status.get('messageCode') != '0':
                raise ExtractorError(
                    '%s said: %s %s - %s' % (
                        self.IE_NAME, status.get('messageCodeDescription'), status.get('messageCode'), status.get('message')),
                    expected=True)

            # Found video formats
            if isinstance(media.get('MediaURLs'), list):
                break

        ignore_no_formats = self.get_param('ignore_no_formats_error')

        if not media or (not media.get('MediaURLs') and not ignore_no_formats):
            raise ExtractorError(
                'Unable to access the crackle API. Try passing your country code '
                'to --geo-bypass-country. If it still does not work and the '
                'video is available in your country')
        title = media['Title']

        formats, subtitles = [], {}
        has_drm = False
        for e in media.get('MediaURLs') or []:
            if e.get('UseDRM'):
                has_drm = True
                format_url = url_or_none(e.get('DRMPath'))
            else:
                format_url = url_or_none(e.get('Path'))
            if not format_url:
                continue
            ext = determine_ext(format_url)
            if ext == 'm3u8':
                fmts, subs = self._extract_m3u8_formats_and_subtitles(
                    format_url, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False)
                formats.extend(fmts)
                subtitles = self._merge_subtitles(subtitles, subs)
            elif ext == 'mpd':
                fmts, subs = self._extract_mpd_formats_and_subtitles(
                    format_url, video_id, mpd_id='dash', fatal=False)
                formats.extend(fmts)
                subtitles = self._merge_subtitles(subtitles, subs)
            elif format_url.endswith('.ism/Manifest'):
                fmts, subs = self._extract_ism_formats_and_subtitles(
                    format_url, video_id, ism_id='mss', fatal=False)
                formats.extend(fmts)
                subtitles = self._merge_subtitles(subtitles, subs)
            else:
                mfs_path = e.get('Type')
                mfs_info = self._MEDIA_FILE_SLOTS.get(mfs_path)
                if not mfs_info:
                    continue
                formats.append({
                    'url': format_url,
                    'format_id': 'http-' + mfs_path.split('.')[0],
                    'width': mfs_info['width'],
                    'height': mfs_info['height'],
                })
        if not formats and has_drm:
            self.report_drm(video_id)
        self._sort_formats(formats)

        description = media.get('Description')
        duration = int_or_none(media.get(
            'DurationInSeconds')) or parse_duration(media.get('Duration'))
        view_count = int_or_none(media.get('CountViews'))
        average_rating = float_or_none(media.get('UserRating'))
        age_limit = parse_age_limit(media.get('Rating'))
        genre = media.get('Genre')
        release_year = int_or_none(media.get('ReleaseYear'))
        creator = media.get('Directors')
        artist = media.get('Cast')

        if media.get('MediaTypeDisplayValue') == 'Full Episode':
            series = media.get('ShowName')
            episode = title
            season_number = int_or_none(media.get('Season'))
            episode_number = int_or_none(media.get('Episode'))
        else:
            series = episode = season_number = episode_number = None

        cc_files = media.get('ClosedCaptionFiles')
        if isinstance(cc_files, list):
            for cc_file in cc_files:
                if not isinstance(cc_file, dict):
                    continue
                cc_url = url_or_none(cc_file.get('Path'))
                if not cc_url:
                    continue
                lang = cc_file.get('Locale') or 'en'
                subtitles.setdefault(lang, []).append({'url': cc_url})

        thumbnails = []
        images = media.get('Images')
        if isinstance(images, list):
            for image_key, image_url in images.items():
                mobj = re.search(r'Img_(\d+)[xX](\d+)', image_key)
                if not mobj:
                    continue
                thumbnails.append({
                    'url': image_url,
                    'width': int(mobj.group(1)),
                    'height': int(mobj.group(2)),
                })

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'duration': duration,
            'view_count': view_count,
            'average_rating': average_rating,
            'age_limit': age_limit,
            'genre': genre,
            'creator': creator,
            'artist': artist,
            'release_year': release_year,
            'series': series,
            'episode': episode,
            'season_number': season_number,
            'episode_number': episode_number,
            'thumbnails': thumbnails,
            'subtitles': subtitles,
            'formats': formats,
        }


code:
from .brightcove import BrightcoveNewIE
from .common import InfoExtractor

from ..utils import (
    dict_get,
    get_element_by_id,
    js_to_json,
    traverse_obj,
)

class CraftsyIE(InfoExtractor):
    _VALID_URL = r'https?://www.craftsy.com/class/(?P<id>[a-z0-9_-]+)/'
    _TESTS = [{
        'url': 'https://www.craftsy.com/class/the-midnight-quilt-show-season-5/',
        'info_dict': {
            'id': 'the-midnight-quilt-show-season-5',
            'title': 'The Midnight Quilt Show Season 5',
            'description': 'md5:113eda818e985d1a566625fb2f833b7a',
        },
        'playlist_count': 10,
    }, {
        'url': 'https://www.craftsy.com/class/sew-your-own-designer-handbag/',
        'info_dict': {
            'id': 'sew-your-own-designer-handbag',
            'title': 'Sew Your Own Designer Handbag',
            'description': 'md5:8270d0ef5427d3c895a27351aeaac276',
        },
        'playlist_mincount': 1,
    }, {
        'url': 'https://www.craftsy.com/class/all-access-estes-park-wool-market/',
        'info_dict': {
            'id': 'all-access-estes-park-wool-market',
            'title': 'All Access: Estes Park Wool Market',
            'description': 'md5:aded1bd8d38ae2fae4dae936c0ae01e7',
        },
        'playlist_count': 6,
    }]

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

        video_data = self._parse_json(self._search_regex(
            r'class_video_player_vars\s*=\s*({.*})\s*;',
            get_element_by_id('vidstore-classes_class-video-player-js-extra', webpage),
            'video data'), video_id, transform_source=js_to_json)

        account_id = traverse_obj(video_data, ('video_player', 'bc_account_id'))

        entries = []
        class_preview = traverse_obj(video_data, ('video_player', 'class_preview'))
        if class_preview:
            v_id = class_preview.get('video_id')
            entries.append(self.url_result(
                f'http://players.brightcove.net/{account_id}/default_default/index.html?videoId={v_id}',
                BrightcoveNewIE, v_id, class_preview.get('title')))

        if dict_get(video_data, ('is_free', 'user_has_access')):
            entries += [
                self.url_result(
                    f'http://players.brightcove.net/{account_id}/default_default/index.html?videoId={lesson["video_id"]}',
                    BrightcoveNewIE, lesson['video_id'], lesson.get('title'))
                for lesson in video_data['lessons']]

        return self.playlist_result(
            entries, video_id, video_data.get('class_title'),
            self._html_search_meta(('og:description', 'description'), webpage, default=None))


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

class CrooksAndLiarsIE(InfoExtractor):
    _VALID_URL = r'https?://embed\.crooksandliars\.com/(?:embed|v)/(?P<id>[A-Za-z0-9]+)'
    _EMBED_REGEX = [r'<(?:iframe[^>]+src|param[^>]+value)=(["\'])(?P<url>(?:https?:)?//embed\.crooksandliars\.com/(?:embed|v)/.+?)\1']

    _TESTS = [{
        'url': 'https://embed.crooksandliars.com/embed/8RUoRhRi',
        'info_dict': {
            'id': '8RUoRhRi',
            'ext': 'mp4',
            'title': 'Fox & Friends Says Protecting Atheists From Discrimination Is Anti-Christian!',
            'description': 'md5:e1a46ad1650e3a5ec7196d432799127f',
            'thumbnail': r're:^https?://.*\.jpg',
            'timestamp': 1428207000,
            'upload_date': '20150405',
            'uploader': 'Heather',
            'duration': 236,
        }
    }, {
        'url': 'http://embed.crooksandliars.com/v/MTE3MjUtMzQ2MzA',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            'http://embed.crooksandliars.com/embed/%s' % video_id, video_id)

        manifest = self._parse_json(
            self._search_regex(
                r'var\s+manifest\s*=\s*({.+?})\n', webpage, 'manifest JSON'),
            video_id)

        quality = qualities(('webm_low', 'mp4_low', 'webm_high', 'mp4_high'))

        formats = [{
            'url': item['url'],
            'format_id': item['type'],
            'quality': quality(item['type']),
        } for item in manifest['flavors'] if item['mime'].startswith('video/')]
        self._sort_formats(formats)

        return {
            'url': url,
            'id': video_id,
            'title': manifest['title'],
            'description': manifest.get('description'),
            'thumbnail': self._proto_relative_url(manifest.get('poster')),
            'timestamp': int_or_none(manifest.get('created')),
            'uploader': manifest.get('author'),
            'duration': int_or_none(manifest.get('duration')),
            'formats': formats,
        }


code:
import itertools

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

class CrowdBunkerIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?crowdbunker\.com/v/(?P<id>[^/?#$&]+)'

    _TESTS = [{
        'url': 'https://crowdbunker.com/v/0z4Kms8pi8I',
        'info_dict': {
            'id': '0z4Kms8pi8I',
            'ext': 'mp4',
            'title': '117) Pass vax et solutions',
            'description': 'md5:86bcb422c29475dbd2b5dcfa6ec3749c',
            'view_count': int,
            'duration': 5386,
            'uploader': 'Jérémie Mercier',
            'uploader_id': 'UCeN_qQV829NYf0pvPJhW5dQ',
            'like_count': int,
            'upload_date': '20211218',
            'thumbnail': 'https://scw.divulg.org/cb-medias4/images/0z4Kms8pi8I/maxres.jpg'
        },
        'params': {'skip_download': True}
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        data_json = self._download_json(f'https://api.divulg.org/post/{id}/details',
                                        id, headers={'accept': 'application/json, text/plain, */*'})
        video_json = data_json['video']
        formats, subtitles = [], {}
        for sub in video_json.get('captions') or []:
            sub_url = try_get(sub, lambda x: x['file']['url'])
            if not sub_url:
                continue
            subtitles.setdefault(sub.get('languageCode', 'fr'), []).append({
                'url': sub_url,
            })

        mpd_url = try_get(video_json, lambda x: x['dashManifest']['url'])
        if mpd_url:
            fmts, subs = self._extract_mpd_formats_and_subtitles(mpd_url, id)
            formats.extend(fmts)
            subtitles = self._merge_subtitles(subtitles, subs)
        m3u8_url = try_get(video_json, lambda x: x['hlsManifest']['url'])
        if m3u8_url:
            fmts, subs = self._extract_m3u8_formats_and_subtitles(mpd_url, id)
            formats.extend(fmts)
            subtitles = self._merge_subtitles(subtitles, subs)

        thumbnails = [{
            'url': image['url'],
            'height': int_or_none(image.get('height')),
            'width': int_or_none(image.get('width')),
        } for image in video_json.get('thumbnails') or [] if image.get('url')]

        self._sort_formats(formats)
        return {
            'id': id,
            'title': video_json.get('title'),
            'description': video_json.get('description'),
            'view_count': video_json.get('viewCount'),
            'duration': video_json.get('duration'),
            'uploader': try_get(data_json, lambda x: x['channel']['name']),
            'uploader_id': try_get(data_json, lambda x: x['channel']['id']),
            'like_count': data_json.get('likesCount'),
            'upload_date': unified_strdate(video_json.get('publishedAt') or video_json.get('createdAt')),
            'thumbnails': thumbnails,
            'formats': formats,
            'subtitles': subtitles,
        }

class CrowdBunkerChannelIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?crowdbunker\.com/@(?P<id>[^/?#$&]+)'

    _TESTS = [{
        'url': 'https://crowdbunker.com/@Milan_UHRIN',
        'playlist_mincount': 14,
        'info_dict': {
            'id': 'Milan_UHRIN',
        },
    }]

    def _entries(self, id):
        last = None

        for page in itertools.count():
            channel_json = self._download_json(
                f'https://api.divulg.org/organization/{id}/posts', id, headers={'accept': 'application/json, text/plain, */*'},
                query={'after': last} if last else {}, note=f'Downloading Page {page}')
            for item in channel_json.get('items') or []:
                v_id = item.get('uid')
                if not v_id:
                    continue
                yield self.url_result(
                    'https://crowdbunker.com/v/%s' % v_id, ie=CrowdBunkerIE.ie_key(), video_id=v_id)
            last = channel_json.get('last')
            if not last:
                break

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


code:
import base64
import json
import re
import urllib.request
import xml.etree.ElementTree
import zlib
from hashlib import sha1
from math import floor, pow, sqrt

from .common import InfoExtractor
from .vrv import VRVBaseIE
from ..aes import aes_cbc_decrypt
from ..compat import (
    compat_b64decode,
    compat_etree_fromstring,
    compat_str,
    compat_urllib_parse_urlencode,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    bytes_to_intlist,
    extract_attributes,
    float_or_none,
    format_field,
    int_or_none,
    intlist_to_bytes,
    join_nonempty,
    lowercase_escape,
    merge_dicts,
    parse_iso8601,
    qualities,
    remove_end,
    sanitized_Request,
    traverse_obj,
    try_get,
    xpath_text,
)

class CrunchyrollBaseIE(InfoExtractor):
    _LOGIN_URL = 'https://www.crunchyroll.com/welcome/login'
    _API_BASE = 'https://api.crunchyroll.com'
    _NETRC_MACHINE = 'crunchyroll'

    def _call_rpc_api(self, method, video_id, note=None, data=None):
        data = data or {}
        data['req'] = 'RpcApi' + method
        data = compat_urllib_parse_urlencode(data).encode('utf-8')
        return self._download_xml(
            'https://www.crunchyroll.com/xml/',
            video_id, note, fatal=False, data=data, headers={
                'Content-Type': 'application/x-www-form-urlencoded',
            })

    def _perform_login(self, username, password):
        if self._get_cookies(self._LOGIN_URL).get('etp_rt'):
            return

        upsell_response = self._download_json(
            f'{self._API_BASE}/get_upsell_data.0.json', None, 'Getting session id',
            query={
                'sess_id': 1,
                'device_id': 'whatvalueshouldbeforweb',
                'device_type': 'com.crunchyroll.static',
                'access_token': 'giKq5eY27ny3cqz',
                'referer': self._LOGIN_URL
            })
        if upsell_response['code'] != 'ok':
            raise ExtractorError('Could not get session id')
        session_id = upsell_response['data']['session_id']

        login_response = self._download_json(
            f'{self._API_BASE}/login.1.json', None, 'Logging in',
            data=compat_urllib_parse_urlencode({
                'account': username,
                'password': password,
                'session_id': session_id
            }).encode('ascii'))
        if login_response['code'] != 'ok':
            raise ExtractorError('Login failed. Server message: %s' % login_response['message'], expected=True)
        if not self._get_cookies(self._LOGIN_URL).get('etp_rt'):
            raise ExtractorError('Login succeeded but did not set etp_rt cookie')

    # Beta-specific, but needed for redirects
    def _get_beta_embedded_json(self, webpage, display_id):
        initial_state = self._parse_json(self._search_regex(
            r'__INITIAL_STATE__\s*=\s*({.+?})\s*;', webpage, 'initial state'), display_id)
        app_config = self._parse_json(self._search_regex(
            r'__APP_CONFIG__\s*=\s*({.+?})\s*;', webpage, 'app config'), display_id)
        return initial_state, app_config

    def _redirect_to_beta(self, webpage, iekey, video_id):
        if not self._get_cookies(self._LOGIN_URL).get('etp_rt'):
            raise ExtractorError('Received a beta page from non-beta url when not logged in.')
        initial_state, app_config = self._get_beta_embedded_json(webpage, video_id)
        url = app_config['baseSiteUrl'] + initial_state['router']['locations']['current']['pathname']
        self.to_screen(f'{video_id}: Redirected to beta site - {url}')
        return self.url_result(f'{url}', iekey, video_id)

    @staticmethod
    def _add_skip_wall(url):
        parsed_url = compat_urlparse.urlparse(url)
        qs = compat_urlparse.parse_qs(parsed_url.query)
        # Always force skip_wall to bypass maturity wall, namely 18+ confirmation message:
        # > This content may be inappropriate for some people.
        # > Are you sure you want to continue?
        # since it's not disabled by default in crunchyroll account's settings.
        # See https://github.com/ytdl-org/youtube-dl/issues/7202.
        qs['skip_wall'] = ['1']
        return compat_urlparse.urlunparse(
            parsed_url._replace(query=compat_urllib_parse_urlencode(qs, True)))

class CrunchyrollIE(CrunchyrollBaseIE, VRVBaseIE):
    IE_NAME = 'crunchyroll'
    _VALID_URL = r'''(?x)
        https?://(?:(?P<prefix>www|m)\.)?(?P<url>
            crunchyroll\.(?:com|fr)/(?:
                media(?:-|/\?id=)|
                (?!series/|watch/)(?:[^/]+/){1,2}[^/?&#]*?
            )(?P<id>[0-9]+)
        )(?:[/?&#]|$)'''

    _TESTS = [{
        'url': 'http://www.crunchyroll.com/wanna-be-the-strongest-in-the-world/episode-1-an-idol-wrestler-is-born-645513',
        'info_dict': {
            'id': '645513',
            'ext': 'mp4',
            'title': 'Wanna be the Strongest in the World Episode 1 – An Idol-Wrestler is Born!',
            'description': 'md5:2d17137920c64f2f49981a7797d275ef',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Yomiuri Telecasting Corporation (YTV)',
            'upload_date': '20131013',
            'url': 're:(?!.*&amp)',
        },
        'params': {
            # rtmp
            'skip_download': True,
        },
        'skip': 'Video gone',
    }, {
        'url': 'http://www.crunchyroll.com/media-589804/culture-japan-1',
        'info_dict': {
            'id': '589804',
            'ext': 'flv',
            'title': 'Culture Japan Episode 1 – Rebuilding Japan after the 3.11',
            'description': 'md5:2fbc01f90b87e8e9137296f37b461c12',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Danny Choo Network',
            'upload_date': '20120213',
        },
        'params': {
            # rtmp
            'skip_download': True,
        },
        'skip': 'Video gone',
    }, {
        'url': 'http://www.crunchyroll.com/rezero-starting-life-in-another-world-/episode-5-the-morning-of-our-promise-is-still-distant-702409',
        'info_dict': {
            'id': '702409',
            'ext': 'mp4',
            'title': compat_str,
            'description': compat_str,
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Re:Zero Partners',
            'timestamp': 1462098900,
            'upload_date': '20160501',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'http://www.crunchyroll.com/konosuba-gods-blessing-on-this-wonderful-world/episode-1-give-me-deliverance-from-this-judicial-injustice-727589',
        'info_dict': {
            'id': '727589',
            'ext': 'mp4',
            'title': compat_str,
            'description': compat_str,
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Kadokawa Pictures Inc.',
            'timestamp': 1484130900,
            'upload_date': '20170111',
            'series': compat_str,
            'season': "KONOSUBA -God's blessing on this wonderful world! 2",
            'season_number': 2,
            'episode': 'Give Me Deliverance From This Judicial Injustice!',
            'episode_number': 1,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'http://www.crunchyroll.fr/girl-friend-beta/episode-11-goodbye-la-mode-661697',
        'only_matching': True,
    }, {
        # geo-restricted (US), 18+ maturity wall, non-premium available
        'url': 'http://www.crunchyroll.com/cosplay-complex-ova/episode-1-the-birth-of-the-cosplay-club-565617',
        'only_matching': True,
    }, {
        # A description with double quotes
        'url': 'http://www.crunchyroll.com/11eyes/episode-1-piros-jszaka-red-night-535080',
        'info_dict': {
            'id': '535080',
            'ext': 'mp4',
            'title': compat_str,
            'description': compat_str,
            'uploader': 'Marvelous AQL Inc.',
            'timestamp': 1255512600,
            'upload_date': '20091014',
        },
        'params': {
            # Just test metadata extraction
            'skip_download': True,
        },
    }, {
        # make sure we can extract an uploader name that's not a link
        'url': 'http://www.crunchyroll.com/hakuoki-reimeiroku/episode-1-dawn-of-the-divine-warriors-606899',
        'info_dict': {
            'id': '606899',
            'ext': 'mp4',
            'title': 'Hakuoki Reimeiroku Episode 1 – Dawn of the Divine Warriors',
            'description': 'Ryunosuke was left to die, but Serizawa-san asked him a simple question "Do you want to live?"',
            'uploader': 'Geneon Entertainment',
            'upload_date': '20120717',
        },
        'params': {
            # just test metadata extraction
            'skip_download': True,
        },
        'skip': 'Video gone',
    }, {
        # A video with a vastly different season name compared to the series name
        'url': 'http://www.crunchyroll.com/nyarko-san-another-crawling-chaos/episode-1-test-590532',
        'info_dict': {
            'id': '590532',
            'ext': 'mp4',
            'title': compat_str,
            'description': compat_str,
            'uploader': 'TV TOKYO',
            'timestamp': 1330956000,
            'upload_date': '20120305',
            'series': 'Nyarko-san: Another Crawling Chaos',
            'season': 'Haiyoru! Nyaruani (ONA)',
        },
        'params': {
            # Just test metadata extraction
            'skip_download': True,
        },
    }, {
        'url': 'http://www.crunchyroll.com/media-723735',
        'only_matching': True,
    }, {
        'url': 'https://www.crunchyroll.com/en-gb/mob-psycho-100/episode-2-urban-legends-encountering-rumors-780921',
        'only_matching': True,
    }]

    _FORMAT_IDS = {
        '360': ('60', '106'),
        '480': ('61', '106'),
        '720': ('62', '106'),
        '1080': ('80', '108'),
    }

    def _download_webpage(self, url_or_request, *args, **kwargs):
        request = (url_or_request if isinstance(url_or_request, urllib.request.Request)
                   else sanitized_Request(url_or_request))
        # Accept-Language must be set explicitly to accept any language to avoid issues
        # similar to https://github.com/ytdl-org/youtube-dl/issues/6797.
        # Along with IP address Crunchyroll uses Accept-Language to guess whether georestriction
        # should be imposed or not (from what I can see it just takes the first language
        # ignoring the priority and requires it to correspond the IP). By the way this causes
        # Crunchyroll to not work in georestriction cases in some browsers that don't place
        # the locale lang first in header. However allowing any language seems to workaround the issue.
        request.add_header('Accept-Language', '*')
        return super(CrunchyrollBaseIE, self)._download_webpage(request, *args, **kwargs)

    def _decrypt_subtitles(self, data, iv, id):
        data = bytes_to_intlist(compat_b64decode(data))
        iv = bytes_to_intlist(compat_b64decode(iv))
        id = int(id)

        def obfuscate_key_aux(count, modulo, start):
            output = list(start)
            for _ in range(count):
                output.append(output[-1] + output[-2])
            # cut off start values
            output = output[2:]
            output = list(map(lambda x: x % modulo + 33, output))
            return output

        def obfuscate_key(key):
            num1 = int(floor(pow(2, 25) * sqrt(6.9)))
            num2 = (num1 ^ key) << 5
            num3 = key ^ num1
            num4 = num3 ^ (num3 >> 3) ^ num2
            prefix = intlist_to_bytes(obfuscate_key_aux(20, 97, (1, 2)))
            shaHash = bytes_to_intlist(sha1(prefix + str(num4).encode('ascii')).digest())
            # Extend 160 Bit hash to 256 Bit
            return shaHash + [0] * 12

        key = obfuscate_key(id)

        decrypted_data = intlist_to_bytes(aes_cbc_decrypt(data, key, iv))
        return zlib.decompress(decrypted_data)

    def _convert_subtitles_to_srt(self, sub_root):
        output = ''

        for i, event in enumerate(sub_root.findall('./events/event'), 1):
            start = event.attrib['start'].replace('.', ',')
            end = event.attrib['end'].replace('.', ',')
            text = event.attrib['text'].replace('\\N', '\n')
            output += '%d\n%s --> %s\n%s\n\n' % (i, start, end, text)
        return output

    def _convert_subtitles_to_ass(self, sub_root):
        output = ''

        def ass_bool(strvalue):
            assvalue = '0'
            if strvalue == '1':
                assvalue = '-1'
            return assvalue

        output = '[Script Info]\n'
        output += 'Title: %s\n' % sub_root.attrib['title']
        output += 'ScriptType: v4.00+\n'
        output += 'WrapStyle: %s\n' % sub_root.attrib['wrap_style']
        output += 'PlayResX: %s\n' % sub_root.attrib['play_res_x']
        output += 'PlayResY: %s\n' % sub_root.attrib['play_res_y']
        output += """
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
"""
        for style in sub_root.findall('./styles/style'):
            output += 'Style: ' + style.attrib['name']
            output += ',' + style.attrib['font_name']
            output += ',' + style.attrib['font_size']
            output += ',' + style.attrib['primary_colour']
            output += ',' + style.attrib['secondary_colour']
            output += ',' + style.attrib['outline_colour']
            output += ',' + style.attrib['back_colour']
            output += ',' + ass_bool(style.attrib['bold'])
            output += ',' + ass_bool(style.attrib['italic'])
            output += ',' + ass_bool(style.attrib['underline'])
            output += ',' + ass_bool(style.attrib['strikeout'])
            output += ',' + style.attrib['scale_x']
            output += ',' + style.attrib['scale_y']
            output += ',' + style.attrib['spacing']
            output += ',' + style.attrib['angle']
            output += ',' + style.attrib['border_style']
            output += ',' + style.attrib['outline']
            output += ',' + style.attrib['shadow']
            output += ',' + style.attrib['alignment']
            output += ',' + style.attrib['margin_l']
            output += ',' + style.attrib['margin_r']
            output += ',' + style.attrib['margin_v']
            output += ',' + style.attrib['encoding']
            output += '\n'

        output += """
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
"""
        for event in sub_root.findall('./events/event'):
            output += 'Dialogue: 0'
            output += ',' + event.attrib['start']
            output += ',' + event.attrib['end']
            output += ',' + event.attrib['style']
            output += ',' + event.attrib['name']
            output += ',' + event.attrib['margin_l']
            output += ',' + event.attrib['margin_r']
            output += ',' + event.attrib['margin_v']
            output += ',' + event.attrib['effect']
            output += ',' + event.attrib['text']
            output += '\n'

        return output

    def _extract_subtitles(self, subtitle):
        sub_root = compat_etree_fromstring(subtitle)
        return [{
            'ext': 'srt',
            'data': self._convert_subtitles_to_srt(sub_root),
        }, {
            'ext': 'ass',
            'data': self._convert_subtitles_to_ass(sub_root),
        }]

    def _get_subtitles(self, video_id, webpage):
        subtitles = {}
        for sub_id, sub_name in re.findall(r'\bssid=([0-9]+)"[^>]+?\btitle="([^"]+)', webpage):
            sub_doc = self._call_rpc_api(
                'Subtitle_GetXml', video_id,
                'Downloading subtitles for ' + sub_name, data={
                    'subtitle_script_id': sub_id,
                })
            if not isinstance(sub_doc, xml.etree.ElementTree.Element):
                continue
            sid = sub_doc.get('id')
            iv = xpath_text(sub_doc, 'iv', 'subtitle iv')
            data = xpath_text(sub_doc, 'data', 'subtitle data')
            if not sid or not iv or not data:
                continue
            subtitle = self._decrypt_subtitles(data, iv, sid).decode('utf-8')
            lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
            if not lang_code:
                continue
            subtitles[lang_code] = self._extract_subtitles(subtitle)
        return subtitles

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

        if mobj.group('prefix') == 'm':
            mobile_webpage = self._download_webpage(url, video_id, 'Downloading mobile webpage')
            webpage_url = self._search_regex(r'<link rel="canonical" href="([^"]+)" />', mobile_webpage, 'webpage_url')
        else:
            webpage_url = 'http://www.' + mobj.group('url')

        webpage = self._download_webpage(
            self._add_skip_wall(webpage_url), video_id,
            headers=self.geo_verification_headers())
        if re.search(r'
', webpage):
            return self._redirect_to_beta(webpage, CrunchyrollBetaIE.ie_key(), video_id)
        note_m = self._html_search_regex(
            r'
(.+?)
',
            webpage, 'trailer-notice', default='')
        if note_m:
            raise ExtractorError(note_m, expected=True)

        mobj = re.search(r'Page\.messaging_box_controller\.addItems\(\[(?P<msg>{.+?})\]\)', webpage)
        if mobj:
            msg = json.loads(mobj.group('msg'))
            if msg.get('type') == 'error':
                raise ExtractorError('crunchyroll returned error: %s' % msg['message_body'], expected=True)

        if 'To view this, please log in to verify you are 18 or older.' in webpage:
            self.raise_login_required()

        media = self._parse_json(self._search_regex(
            r'vilos\.config\.media\s*=\s*({.+?});',
            webpage, 'vilos media', default='{}'), video_id)
        media_metadata = media.get('metadata') or {}

        language = self._search_regex(
            r'(?:vilos\.config\.player\.language|LOCALE)\s*=\s*(["\'])(?P<lang>(?:(?!\1).)+)\1',
            webpage, 'language', default=None, group='lang')

        video_title = self._html_search_regex(
            (r'(?s)
]*>((?:(?!<h1).)*?<(?:span[^>]+itemprop=["\']title["\']|meta[^>]+itemprop=["\']position["\'])[^>]*>(?:(?!<h1).)+?)
',
             r'<title>(.+?),\s+-\s+.+? Crunchyroll'),
            webpage, 'video_title', default=None)
        if not video_title:
            video_title = re.sub(r'^Watch\s+', '', self._og_search_description(webpage))
        video_title = re.sub(r' {2,}', ' ', video_title)
        video_description = (self._parse_json(self._html_search_regex(
            r'


code:
import re

from .common import InfoExtractor
from ..compat import compat_HTMLParseError
from ..utils import (
    determine_ext,
    ExtractorError,
    extract_attributes,
    find_xpath_attr,
    get_element_by_attribute,
    get_element_by_class,
    int_or_none,
    join_nonempty,
    js_to_json,
    merge_dicts,
    parse_iso8601,
    parse_qs,
    smuggle_url,
    str_to_int,
    unescapeHTML,
)
from .senategov import SenateISVPIE
from .ustream import UstreamIE

class CSpanIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?c-span\.org/video/\?(?P<id>[0-9a-f]+)'
    IE_DESC = 'C-SPAN'
    _TESTS = [{
        'url': 'http://www.c-span.org/video/?313572-1/HolderonV',
        'md5': '94b29a4f131ff03d23471dd6f60b6a1d',
        'info_dict': {
            'id': '315139',
            'title': 'Attorney General Eric Holder on Voting Rights Act Decision',
        },
        'playlist_mincount': 2,
        'skip': 'Regularly fails on travis, for unknown reasons',
    }, {
        'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
        # md5 is unstable
        'info_dict': {
            'id': 'c4486943',
            'ext': 'mp4',
            'title': 'CSPAN - International Health Care Models',
            'description': 'md5:7a985a2d595dba00af3d9c9f0783c967',
        }
    }, {
        'url': 'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall',
        'info_dict': {
            'id': '342759',
            'title': 'General Motors Ignition Switch Recall',
        },
        'playlist_mincount': 6,
    }, {
        # Video from senate.gov
        'url': 'http://www.c-span.org/video/?104517-1/immigration-reforms-needed-protect-skilled-american-workers',
        'info_dict': {
            'id': 'judiciary031715',
            'ext': 'mp4',
            'title': 'Immigration Reforms Needed to Protect Skilled American Workers',
        },
        'params': {
            'skip_download': True,  # m3u8 downloads
        }
    }, {
        # Ustream embedded video
        'url': 'https://www.c-span.org/video/?114917-1/armed-services',
        'info_dict': {
            'id': '58428542',
            'ext': 'flv',
            'title': 'USHR07 Armed Services Committee',
            'description': 'hsas00-2118-20150204-1000et-07\n\n\nUSHR07 Armed Services Committee',
            'timestamp': 1423060374,
            'upload_date': '20150204',
            'uploader': 'HouseCommittee',
            'uploader_id': '12987475',
        },
    }, {
        # Audio Only
        'url': 'https://www.c-span.org/video/?437336-1/judiciary-antitrust-competition-policy-consumer-rights',
        'only_matching': True,
    }]
    BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'

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

        ustream_url = UstreamIE._extract_url(webpage)
        if ustream_url:
            return self.url_result(ustream_url, UstreamIE.ie_key())

        if '&vod' not in url:
            bc = self._search_regex(
                r"(<[^>]+id='brightcove-player-embed'[^>]+>)",
                webpage, 'brightcove embed', default=None)
            if bc:
                bc_attr = extract_attributes(bc)
                bc_url = self.BRIGHTCOVE_URL_TEMPLATE % (
                    bc_attr.get('data-bcaccountid', '3162030207001'),
                    bc_attr.get('data-noprebcplayerid', 'SyGGpuJy3g'),
                    bc_attr.get('data-newbcplayerid', 'default'),
                    bc_attr['data-bcid'])
                return self.url_result(smuggle_url(bc_url, {'source_url': url}))

        def add_referer(formats):
            for f in formats:
                f.setdefault('http_headers', {})['Referer'] = url

        # As of 01.12.2020 this path looks to cover all cases making the rest
        # of the code unnecessary
        jwsetup = self._parse_json(
            self._search_regex(
                r'(?s)jwsetup\s*=\s*({.+?})\s*;', webpage, 'jwsetup',
                default='{}'),
            video_id, transform_source=js_to_json, fatal=False)
        if jwsetup:
            info = self._parse_jwplayer_data(
                jwsetup, video_id, require_title=False, m3u8_id='hls',
                base_url=url)
            add_referer(info['formats'])
            for subtitles in info['subtitles'].values():
                for subtitle in subtitles:
                    ext = determine_ext(subtitle['url'])
                    if ext == 'php':
                        ext = 'vtt'
                    subtitle['ext'] = ext
            ld_info = self._search_json_ld(webpage, video_id, default={})
            try:
                title = get_element_by_class('video-page-title', webpage)
            except compat_HTMLParseError:
                title = None
            if title is None:
                title = self._og_search_title(webpage)
            description = get_element_by_attribute('itemprop', 'description', webpage) or \
                self._html_search_meta(['og:description', 'description'], webpage)
            return merge_dicts(info, ld_info, {
                'title': title,
                'thumbnail': get_element_by_attribute('itemprop', 'thumbnailUrl', webpage),
                'description': description,
                'timestamp': parse_iso8601(get_element_by_attribute('itemprop', 'uploadDate', webpage)),
                'location': get_element_by_attribute('itemprop', 'contentLocation', webpage),
                'duration': int_or_none(self._search_regex(
                    r'jwsetup\.seclength\s*=\s*(\d+);',
                    webpage, 'duration', fatal=False)),
                'view_count': str_to_int(self._search_regex(
                    r"<span[^>]+class='views'[^>]*>([\d,]+)\s+Views</span>",
                    webpage, 'views', fatal=False)),
            })

        # Obsolete
        # We first look for clipid, because clipprog always appears before
        patterns = [r'id=\'clip(%s)\'\s*value=\'([0-9]+)\'' % t for t in ('id', 'prog')]
        results = list(filter(None, (re.search(p, webpage) for p in patterns)))
        if results:
            matches = results[0]
            video_type, video_id = matches.groups()
            video_type = 'clip' if video_type == 'id' else 'program'
        else:
            m = re.search(r'data-(?P<type>clip|prog)id=["\'](?P<id>\d+)', webpage)
            if m:
                video_id = m.group('id')
                video_type = 'program' if m.group('type') == 'prog' else 'clip'
            else:
                senate_isvp_url = SenateISVPIE._extract_url(webpage)
                if senate_isvp_url:
                    title = self._og_search_title(webpage)
                    surl = smuggle_url(senate_isvp_url, {'force_title': title})
                    return self.url_result(surl, 'SenateISVP', video_id, title)
                video_id = self._search_regex(
                    r'jwsetup\.clipprog\s*=\s*(\d+);',
                    webpage, 'jwsetup program id', default=None)
                if video_id:
                    video_type = 'program'
        if video_type is None or video_id is None:
            error_message = get_element_by_class('VLplayer-error-message', webpage)
            if error_message:
                raise ExtractorError(error_message)
            raise ExtractorError('unable to find video id and type')

        def get_text_attr(d, attr):
            return d.get(attr, {}).get('#text')

        data = self._download_json(
            'http://www.c-span.org/assets/player/ajax-player.php?os=android&html5=%s&id=%s' % (video_type, video_id),
            video_id)['video']
        if data['@status'] != 'Success':
            raise ExtractorError('%s said: %s' % (self.IE_NAME, get_text_attr(data, 'error')), expected=True)

        doc = self._download_xml(
            'http://www.c-span.org/common/services/flashXml.php?%sid=%s' % (video_type, video_id),
            video_id)

        description = self._html_search_meta('description', webpage)

        title = find_xpath_attr(doc, './/string', 'name', 'title').text
        thumbnail = find_xpath_attr(doc, './/string', 'name', 'poster').text

        files = data['files']
        capfile = get_text_attr(data, 'capfile')

        entries = []
        for partnum, f in enumerate(files):
            formats = []
            for quality in f.get('qualities', []):
                formats.append({
                    'format_id': '%s-%sp' % (get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
                    'url': unescapeHTML(get_text_attr(quality, 'file')),
                    'height': int_or_none(get_text_attr(quality, 'height')),
                    'tbr': int_or_none(get_text_attr(quality, 'bitrate')),
                })
            if not formats:
                path = unescapeHTML(get_text_attr(f, 'path'))
                if not path:
                    continue
                formats = self._extract_m3u8_formats(
                    path, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls') if determine_ext(path) == 'm3u8' else [{'url': path, }]
            add_referer(formats)
            self._sort_formats(formats)
            entries.append({
                'id': '%s_%d' % (video_id, partnum + 1),
                'title': (
                    title if len(files) == 1 else
                    '%s part %d' % (title, partnum + 1)),
                'formats': formats,
                'description': description,
                'thumbnail': thumbnail,
                'duration': int_or_none(get_text_attr(f, 'length')),
                'subtitles': {
                    'en': [{
                        'url': capfile,
                        'ext': determine_ext(capfile, 'dfxp')
                    }],
                } if capfile else None,
            })

        if len(entries) == 1:
            entry = dict(entries[0])
            entry['id'] = 'c' + video_id if video_type == 'clip' else video_id
            return entry
        else:
            return {
                '_type': 'playlist',
                'entries': entries,
                'title': title,
                'id': 'c' + video_id if video_type == 'clip' else video_id,
            }

class CSpanCongressIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?c-span\.org/congress/'
    _TESTS = [{
        'url': 'https://www.c-span.org/congress/?chamber=house&date=2017-12-13&t=1513208380',
        'info_dict': {
            'id': 'house_2017-12-13',
            'title': 'Congressional Chronicle - Members of Congress, Hearings and More',
            'description': 'md5:54c264b7a8f219937987610243305a84',
            'thumbnail': r're:https://ximage.c-spanvideo.org/.+',
            'ext': 'mp4'
        }
    }]

    def _real_extract(self, url):
        query = parse_qs(url)
        video_date = query.get('date', [None])[0]
        video_id = join_nonempty(query.get('chamber', ['senate'])[0], video_date, delim='_')
        webpage = self._download_webpage(url, video_id)
        if not video_date:
            jwp_date = re.search(r'jwsetup.clipprogdate = \'(?P<date>\d{4}-\d{2}-\d{2})\';', webpage)
            if jwp_date:
                video_id = f'{video_id}_{jwp_date.group("date")}'
        jwplayer_data = self._parse_json(
            self._search_regex(r'jwsetup\s*=\s*({(?:.|\n)[^;]+});', webpage, 'player config'),
            video_id, transform_source=js_to_json)

        title = (self._og_search_title(webpage, default=None)
                 or self._html_extract_title(webpage, 'video title'))
        description = (self._og_search_description(webpage, default=None)
                       or self._html_search_meta('description', webpage, 'description', default=None))

        return {
            **self._parse_jwplayer_data(jwplayer_data, video_id, False),
            'title': re.sub(r'\s+', ' ', title.split('|')[0]).strip(),
            'description': description,
            'http_headers': {'Referer': 'https://www.c-span.org/'},
        }


code:
from .common import InfoExtractor
from ..utils import unified_timestamp
from .youtube import YoutubeIE

class CtsNewsIE(InfoExtractor):
    IE_DESC = '華視新聞'
    _VALID_URL = r'https?://news\.cts\.com\.tw/[a-z]+/[a-z]+/\d+/(?P<id>\d+)\.html'
    _TESTS = [{
        'url': 'http://news.cts.com.tw/cts/international/201501/201501291578109.html',
        'md5': 'a9875cb790252b08431186d741beaabe',
        'info_dict': {
            'id': '201501291578109',
            'ext': 'mp4',
            'title': '以色列.真主黨交火 3人死亡 - 華視新聞網',
            'description': '以色列和黎巴嫩真主黨，爆發五年最嚴重衝突，雙方砲轟交火，兩名以軍死亡，還有一名西班牙籍的聯合國維和人員也不幸罹難。大陸陝西、河南、安徽、江蘇和湖北五個省份出現大暴雪，嚴重影響陸空交通，不過九華山卻出現...',
            'timestamp': 1422528540,
            'upload_date': '20150129',
        }
    }, {
        # News count not appear on page but still available in database
        'url': 'http://news.cts.com.tw/cts/international/201309/201309031304098.html',
        'md5': '3aee7e0df7cdff94e43581f54c22619e',
        'info_dict': {
            'id': '201309031304098',
            'ext': 'mp4',
            'title': '韓國31歲童顏男 貌如十多歲小孩 - 華視新聞網',
            'description': '越有年紀的人，越希望看起來年輕一點，而南韓卻有一位31歲的男子，看起來像是11、12歲的小孩，身...',
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1378205880,
            'upload_date': '20130903',
        }
    }, {
        # With Youtube embedded video
        'url': 'http://news.cts.com.tw/cts/money/201501/201501291578003.html',
        'md5': 'e4726b2ccd70ba2c319865e28f0a91d1',
        'info_dict': {
            'id': 'OVbfO7d0_hQ',
            'ext': 'mp4',
            'title': 'iPhone6熱銷 蘋果財報亮眼',
            'description': 'md5:f395d4f485487bb0f992ed2c4b07aa7d',
            'thumbnail': r're:^https?://.*\.jpg$',
            'upload_date': '20150128',
            'uploader_id': 'TBSCTS',
            'uploader': '中華電視公司',
        },
        'add_ie': ['Youtube'],
    }]

    def _real_extract(self, url):
        news_id = self._match_id(url)
        page = self._download_webpage(url, news_id)

        news_id = self._hidden_inputs(page).get('get_id')

        if news_id:
            mp4_feed = self._download_json(
                'http://news.cts.com.tw/action/test_mp4feed.php',
                news_id, note='Fetching feed', query={'news_id': news_id})
            video_url = mp4_feed['source_url']
        else:
            self.to_screen('Not CTSPlayer video, trying Youtube...')
            youtube_url = YoutubeIE._extract_url(page)

            return self.url_result(youtube_url, ie='Youtube')

        description = self._html_search_meta('description', page)
        title = self._html_search_meta('title', page, fatal=True)
        thumbnail = self._html_search_meta('image', page)

        datetime_str = self._html_search_regex(
            r'(\d{4}/\d{2}/\d{2} \d{2}:\d{2})', page, 'date and time', fatal=False)
        timestamp = None
        if datetime_str:
            timestamp = unified_timestamp(datetime_str) - 8 * 3600

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


code:
from .common import InfoExtractor

class CTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?ctv\.ca/(?P<id>(?:show|movie)s/[^/]+/[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.ctv.ca/shows/your-morning/wednesday-december-23-2020-s5e88',
        'info_dict': {
            'id': '2102249',
            'ext': 'flv',
            'title': 'Wednesday, December 23, 2020',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'Your Morning delivers original perspectives and unique insights into the headlines of the day.',
            'timestamp': 1608732000,
            'upload_date': '20201223',
            'series': 'Your Morning',
            'season': '2020-2021',
            'season_number': 5,
            'episode_number': 88,
            'tags': ['Your Morning'],
            'categories': ['Talk Show'],
            'duration': 7467.126,
        },
    }, {
        'url': 'https://www.ctv.ca/movies/adam-sandlers-eight-crazy-nights/adam-sandlers-eight-crazy-nights',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        content = self._download_json(
            'https://www.ctv.ca/space-graphql/graphql', display_id, query={
                'query': '''{
  resolvedPath(path: "/%s") {
    lastSegment {
      content {
        ... on AxisContent {
          axisId
          videoPlayerDestCode
        }
      }
    }
  }
}''' % display_id,
            })['data']['resolvedPath']['lastSegment']['content']
        video_id = content['axisId']
        return self.url_result(
            '9c9media:%s:%s' % (content['videoPlayerDestCode'], video_id),
            'NineCNineMedia', video_id)


code:
import re

from .common import InfoExtractor
from ..utils import orderedSet

class CTVNewsIE(InfoExtractor):
    _VALID_URL = r'https?://(?:.+?\.)?ctvnews\.ca/(?:video\?(?:clip|playlist|bin)Id=|.*?)(?P<id>[0-9.]+)'
    _TESTS = [{
        'url': 'http://www.ctvnews.ca/video?clipId=901995',
        'md5': '9b8624ba66351a23e0b6e1391971f9af',
        'info_dict': {
            'id': '901995',
            'ext': 'flv',
            'title': 'Extended: \'That person cannot be me\' Johnson says',
            'description': 'md5:958dd3b4f5bbbf0ed4d045c790d89285',
            'timestamp': 1467286284,
            'upload_date': '20160630',
        }
    }, {
        'url': 'http://www.ctvnews.ca/video?playlistId=1.2966224',
        'info_dict':
        {
            'id': '1.2966224',
        },
        'playlist_mincount': 19,
    }, {
        'url': 'http://www.ctvnews.ca/video?binId=1.2876780',
        'info_dict':
        {
            'id': '1.2876780',
        },
        'playlist_mincount': 100,
    }, {
        'url': 'http://www.ctvnews.ca/1.810401',
        'only_matching': True,
    }, {
        'url': 'http://www.ctvnews.ca/canadiens-send-p-k-subban-to-nashville-in-blockbuster-trade-1.2967231',
        'only_matching': True,
    }, {
        'url': 'http://vancouverisland.ctvnews.ca/video?clipId=761241',
        'only_matching': True,
    }]

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

        def ninecninemedia_url_result(clip_id):
            return {
                '_type': 'url_transparent',
                'id': clip_id,
                'url': '9c9media:ctvnews_web:%s' % clip_id,
                'ie_key': 'NineCNineMedia',
            }

        if page_id.isdigit():
            return ninecninemedia_url_result(page_id)
        else:
            webpage = self._download_webpage('http://www.ctvnews.ca/%s' % page_id, page_id, query={
                'ot': 'example.AjaxPageLayout.ot',
                'maxItemsPerPage': 1000000,
            })
            entries = [ninecninemedia_url_result(clip_id) for clip_id in orderedSet(
                re.findall(r'clip\.id\s*=\s*(\d+);', webpage))]
            if not entries:
                webpage = self._download_webpage(url, page_id)
                if 'getAuthStates("' in webpage:
                    entries = [ninecninemedia_url_result(clip_id) for clip_id in
                               self._search_regex(r'getAuthStates\("([\d+,]+)"', webpage, 'clip ids').split(',')]
            return self.playlist_result(entries, page_id)


code:
import time

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

class CultureUnpluggedIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?cultureunplugged\.com/documentary/watch-online/play/(?P<id>\d+)(?:/(?P<display_id>[^/]+))?'
    _TESTS = [{
        'url': 'http://www.cultureunplugged.com/documentary/watch-online/play/53662/The-Next--Best-West',
        'md5': 'ac6c093b089f7d05e79934dcb3d228fc',
        'info_dict': {
            'id': '53662',
            'display_id': 'The-Next--Best-West',
            'ext': 'mp4',
            'title': 'The Next, Best West',
            'description': 'md5:0423cd00833dea1519cf014e9d0903b1',
            'thumbnail': r're:^https?://.*\.jpg$',
            'creator': 'Coldstream Creative',
            'duration': 2203,
            'view_count': int,
        }
    }, {
        'url': 'http://www.cultureunplugged.com/documentary/watch-online/play/53662',
        'only_matching': True,
    }]

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

        # request setClientTimezone.php to get PHPSESSID cookie which is need to get valid json data in the next request
        self._request_webpage(HEADRequest(
            'http://www.cultureunplugged.com/setClientTimezone.php?timeOffset=%d' % -(time.timezone / 3600)), display_id)
        movie_data = self._download_json(
            'http://www.cultureunplugged.com/movie-data/cu-%s.json' % video_id, display_id)

        video_url = movie_data['url']
        title = movie_data['title']

        description = movie_data.get('synopsis')
        creator = movie_data.get('producer')
        duration = int_or_none(movie_data.get('duration'))
        view_count = int_or_none(movie_data.get('views'))

        thumbnails = [{
            'url': movie_data['%s_thumb' % size],
            'id': size,
            'preference': preference,
        } for preference, size in enumerate((
            'small', 'large')) if movie_data.get('%s_thumb' % size)]

        return {
            'id': video_id,
            'display_id': display_id,
            'url': video_url,
            'title': title,
            'description': description,
            'creator': creator,
            'duration': duration,
            'view_count': view_count,
            'thumbnails': thumbnails,
        }