code:
from __future__ import unicode_literals

import sys

from .common import InfoExtractor
from ..utils import ExtractorError

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

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

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

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

    # Disable test for python 3.2 since BOM is broken in re in this version
    # (see https://github.com/ytdl-org/youtube-dl/issues/9751)
    _TESTS = [] if (3, 0) < sys.version_info <= (3, 3) else [{
        '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: rrosajp/youtube


code:
from __future__ import unicode_literals

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

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': compat_urlparse.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:
# coding: utf-8
from __future__ import unicode_literals

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,
)

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_URL = r'(?:https?:)?//player(?:-backend)?\.(?:%s)\.com/(?:embed(?:js)?|(?:script|inline)/video)/.+?' % '|'.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,
        },
    }, {
        # 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': 1442434955,
        }
    }, {
        '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)

        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'),
        }

    def _real_extract(self, url):
        video_id, player_id, target, url_type, display_id = re.match(self._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:
            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:
# coding: utf-8
from __future__ import unicode_literals

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:
# coding: utf-8
from __future__ import unicode_literals

import re

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': {
            'format': 'bestvideo',
            '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 = re.match(self._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'):
            raise ExtractorError('This video is DRM protected.', expected=True)
        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:
# coding: utf-8
from __future__ import unicode_literals

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')

        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,
                    'quality': quality_key(quality),
                    'preference': preference_key(HTML5),
                })

        iphone_url = file_versions.get(IPHONE, {}).get('url')
        if iphone_url:
            formats.append({
                'url': iphone_url,
                'format_id': IPHONE,
                '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,
                '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 __future__ import unicode_literals

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:
# coding: utf-8
from __future__ import unicode_literals, division

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,
    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 = [{
        # geo restricted to CA
        'url': 'https://www.crackle.com/andromeda/2502343',
        'info_dict': {
            'id': '2502343',
            'ext': 'mp4',
            'title': 'Under The Night',
            'description': 'md5:d2b8ca816579ae8a7bf28bfff8cefc8a',
            'duration': 2583,
            'view_count': int,
            'average_rating': 0,
            'age_limit': 14,
            'genre': 'Action, Sci-Fi',
            'creator': 'Allan Kroeker',
            'artist': 'Keith Hamilton Cobb, Kevin Sorbo, Lisa Ryder, Lexa Doig, Robert Hewitt Wolfe',
            'release_year': 2000,
            'series': 'Andromeda',
            'episode': 'Under The Night',
            'season_number': 1,
            'episode_number': 1,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }, {
        'url': 'https://www.sonycrackle.com/andromeda/2502343',
        '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 _real_extract(self, url):
        video_id = self._match_id(url)

        country_code = self._downloader.params.get('geo_bypass_country', None)
        countries = [country_code] if country_code else (
            'US', 'AU', 'CA', 'AS', 'FM', 'GU', 'MP', 'PR', 'PW', 'MH', 'VI')

        last_e = None

        for country in countries:
            try:
                # Authorization generation algorithm is reverse engineered from:
                # https://www.sonycrackle.com/static/js/main.ea93451f.chunk.js
                media_detail_url = 'https://web-api-us.crackle.com/Service.svc/details/media/%s/%s?disableProtocols=true' % (video_id, country)
                timestamp = time.strftime('%Y%m%d%H%M', time.gmtime())
                h = hmac.new(b'IGSLUQCBDFHEOIFM', '|'.join([media_detail_url, timestamp]).encode(), hashlib.sha1).hexdigest().upper()
                media = self._download_json(
                    media_detail_url, video_id, 'Downloading media JSON as %s' % country,
                    'Unable to download media JSON', headers={
                        'Accept': 'application/json',
                        'Authorization': '|'.join([h, timestamp, '117', '1']),
                    })
            except ExtractorError as e:
                # 401 means geo restriction, trying next country
                if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                    last_e = e
                    continue
                raise

            media_urls = media.get('MediaURLs')
            if not media_urls or not isinstance(media_urls, list):
                continue

            title = media['Title']

            formats = []
            for e in media['MediaURLs']:
                if e.get('UseDRM') is True:
                    continue
                format_url = url_or_none(e.get('Path'))
                if not format_url:
                    continue
                ext = determine_ext(format_url)
                if ext == 'm3u8':
                    formats.extend(self._extract_m3u8_formats(
                        format_url, video_id, 'mp4', entry_protocol='m3u8_native',
                        m3u8_id='hls', fatal=False))
                elif ext == 'mpd':
                    formats.extend(self._extract_mpd_formats(
                        format_url, video_id, mpd_id='dash', fatal=False))
                elif format_url.endswith('.ism/Manifest'):
                    formats.extend(self._extract_ism_formats(
                        format_url, video_id, ism_id='mss', fatal=False))
                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'],
                    })
            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

            subtitles = {}
            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,
            }

        raise last_e


code:
from __future__ import unicode_literals

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]+)'
    _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:
# coding: utf-8
from __future__ import unicode_literals

import re
import json
import zlib

from hashlib import sha1
from math import pow, sqrt, floor
from .common import InfoExtractor
from .vrv import VRVIE
from ..compat import (
    compat_b64decode,
    compat_etree_Element,
    compat_etree_fromstring,
    compat_str,
    compat_urllib_parse_urlencode,
    compat_urllib_request,
    compat_urlparse,
)
from ..utils import (
    ExtractorError,
    bytes_to_intlist,
    extract_attributes,
    float_or_none,
    intlist_to_bytes,
    int_or_none,
    lowercase_escape,
    merge_dicts,
    remove_end,
    sanitized_Request,
    urlencode_postdata,
    xpath_text,
)
from ..aes import (
    aes_cbc_decrypt,
)

class CrunchyrollBaseIE(InfoExtractor):
    _LOGIN_URL = 'https://www.crunchyroll.com/login'
    _LOGIN_FORM = 'login_form'
    _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 _login(self):
        username, password = self._get_login_info()
        if username is None:
            return

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

        def is_logged(webpage):
            return 'href="/logout"' in webpage

        # Already logged in
        if is_logged(login_page):
            return

        login_form_str = self._search_regex(
            r'(?P<form><form[^>]+?id=(["\'])%s\2[^>]*>)' % self._LOGIN_FORM,
            login_page, 'login form', group='form')

        post_url = extract_attributes(login_form_str).get('action')
        if not post_url:
            post_url = self._LOGIN_URL
        elif not post_url.startswith('http'):
            post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)

        login_form = self._form_hidden_inputs(self._LOGIN_FORM, login_page)

        login_form.update({
            'login_form[name]': username,
            'login_form[password]': password,
        })

        response = self._download_webpage(
            post_url, None, 'Logging in', 'Wrong login info',
            data=urlencode_postdata(login_form),
            headers={'Content-Type': 'application/x-www-form-urlencoded'})

        # Successful login
        if is_logged(response):
            return

        error = self._html_search_regex(
            '(?s)
]+class=["\']messages["\'][^>]*>(.+?)
',
            response, 'error message', default=None)
        if error:
            raise ExtractorError('Unable to login: %s' % error, expected=True)

        raise ExtractorError('Unable to log in')

    def _real_initialize(self):
        self._login()

    @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, VRVIE):
    IE_NAME = 'crunchyroll'
    _VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.(?:com|fr)/(?:media(?:-|/\?id=)|(?:[^/]*/){1,2}[^/?&]*?)(?P<video_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, compat_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, compat_etree_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 = re.match(self._VALID_URL, url)
        video_id = mobj.group('video_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())
        note_m = self._html_search_regex(
            r'
(.+?)
',
            webpage, 'trailer-notice', default='')
        if note_m:
            raise ExtractorError(note_m)

        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:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    ExtractorError,
    extract_attributes,
    find_xpath_attr,
    get_element_by_class,
    int_or_none,
    smuggle_url,
    unescapeHTML,
)
from .senateisvp 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}))

        # 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._search_iframe_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, }]
            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,
            }


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

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:
# coding: utf-8
from __future__ import unicode_literals

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))]
            return self.playlist_result(entries, page_id)


code:
from __future__ import unicode_literals

import re
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 = re.match(self._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,
        }


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

import re

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    urlencode_postdata,
    compat_str,
    ExtractorError,
)

class CuriosityStreamBaseIE(InfoExtractor):
    _NETRC_MACHINE = 'curiositystream'
    _auth_token = None
    _API_BASE_URL = 'https://api.curiositystream.com/v1/'

    def _handle_errors(self, result):
        error = result.get('error', {}).get('message')
        if error:
            if isinstance(error, dict):
                error = ', '.join(error.values())
            raise ExtractorError(
                '%s said: %s' % (self.IE_NAME, error), expected=True)

    def _call_api(self, path, video_id):
        headers = {}
        if self._auth_token:
            headers['X-Auth-Token'] = self._auth_token
        result = self._download_json(
            self._API_BASE_URL + path, video_id, headers=headers)
        self._handle_errors(result)
        return result['data']

    def _real_initialize(self):
        email, password = self._get_login_info()
        if email is None:
            return
        result = self._download_json(
            self._API_BASE_URL + 'login', None, data=urlencode_postdata({
                'email': email,
                'password': password,
            }))
        self._handle_errors(result)
        self._auth_token = result['message']['auth_token']

class CuriosityStreamIE(CuriosityStreamBaseIE):
    IE_NAME = 'curiositystream'
    _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/video/(?P<id>\d+)'
    _TEST = {
        'url': 'https://app.curiositystream.com/video/2',
        'md5': '262bb2f257ff301115f1973540de8983',
        'info_dict': {
            'id': '2',
            'ext': 'mp4',
            'title': 'How Did You Develop The Internet?',
            'description': 'Vint Cerf, Google\'s Chief Internet Evangelist, describes how he and Bob Kahn created the internet.',
        }
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        media = self._call_api('media/' + video_id, video_id)
        title = media['title']

        formats = []
        for encoding in media.get('encodings', []):
            m3u8_url = encoding.get('master_playlist_url')
            if m3u8_url:
                formats.extend(self._extract_m3u8_formats(
                    m3u8_url, video_id, 'mp4', 'm3u8_native',
                    m3u8_id='hls', fatal=False))
            encoding_url = encoding.get('url')
            file_url = encoding.get('file_url')
            if not encoding_url and not file_url:
                continue
            f = {
                'width': int_or_none(encoding.get('width')),
                'height': int_or_none(encoding.get('height')),
                'vbr': int_or_none(encoding.get('video_bitrate')),
                'abr': int_or_none(encoding.get('audio_bitrate')),
                'filesize': int_or_none(encoding.get('size_in_bytes')),
                'vcodec': encoding.get('video_codec'),
                'acodec': encoding.get('audio_codec'),
                'container': encoding.get('container_type'),
            }
            for f_url in (encoding_url, file_url):
                if not f_url:
                    continue
                fmt = f.copy()
                rtmp = re.search(r'^(?P<url>rtmpe?://(?P<host>[^/]+)/(?P<app>.+))/(?P<playpath>mp[34]:.+)$', f_url)
                if rtmp:
                    fmt.update({
                        'url': rtmp.group('url'),
                        'play_path': rtmp.group('playpath'),
                        'app': rtmp.group('app'),
                        'ext': 'flv',
                        'format_id': 'rtmp',
                    })
                else:
                    fmt.update({
                        'url': f_url,
                        'format_id': 'http',
                    })
                formats.append(fmt)
        self._sort_formats(formats)

        subtitles = {}
        for closed_caption in media.get('closed_captions', []):
            sub_url = closed_caption.get('file')
            if not sub_url:
                continue
            lang = closed_caption.get('code') or closed_caption.get('language') or 'en'
            subtitles.setdefault(lang, []).append({
                'url': sub_url,
            })

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'description': media.get('description'),
            'thumbnail': media.get('image_large') or media.get('image_medium') or media.get('image_small'),
            'duration': int_or_none(media.get('duration')),
            'tags': media.get('tags'),
            'subtitles': subtitles,
        }

class CuriosityStreamCollectionIE(CuriosityStreamBaseIE):
    IE_NAME = 'curiositystream:collection'
    _VALID_URL = r'https?://(?:app\.)?curiositystream\.com/(?:collection|series)/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://app.curiositystream.com/collection/2',
        'info_dict': {
            'id': '2',
            'title': 'Curious Minds: The Internet',
            'description': 'How is the internet shaping our lives in the 21st Century?',
        },
        'playlist_mincount': 17,
    }, {
        'url': 'https://curiositystream.com/series/2',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        collection_id = self._match_id(url)
        collection = self._call_api(
            'collections/' + collection_id, collection_id)
        entries = []
        for media in collection.get('media', []):
            media_id = compat_str(media.get('id'))
            entries.append(self.url_result(
                'https://curiositystream.com/video/' + media_id,
                CuriosityStreamIE.ie_key(), media_id))
        return self.playlist_result(
            entries, collection_id,
            collection.get('title'), collection.get('description'))


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

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    int_or_none,
    parse_age_limit,
    parse_iso8601,
    smuggle_url,
    str_or_none,
)

class CWTVIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?cw(?:tv(?:pr)?|seed)\.com/(?:shows/)?(?:[^/]+/)+[^?]*\?.*\b(?:play|watch)=(?P<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})'
    _TESTS = [{
        'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?play=6b15e985-9345-4f60-baf8-56e96be57c63',
        'info_dict': {
            'id': '6b15e985-9345-4f60-baf8-56e96be57c63',
            'ext': 'mp4',
            'title': 'Legends of Yesterday',
            'description': 'Oliver and Barry Allen take Kendra Saunders and Carter Hall to a remote location to keep them hidden from Vandal Savage while they figure out how to defeat him.',
            'duration': 2665,
            'series': 'Arrow',
            'season_number': 4,
            'season': '4',
            'episode_number': 8,
            'upload_date': '20151203',
            'timestamp': 1449122100,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'skip': 'redirect to http://cwtv.com/shows/arrow/',
    }, {
        'url': 'http://www.cwseed.com/shows/whose-line-is-it-anyway/jeff-davis-4/?play=24282b12-ead2-42f2-95ad-26770c2c6088',
        'info_dict': {
            'id': '24282b12-ead2-42f2-95ad-26770c2c6088',
            'ext': 'mp4',
            'title': 'Jeff Davis 4',
            'description': 'Jeff Davis is back to make you laugh.',
            'duration': 1263,
            'series': 'Whose Line Is It Anyway?',
            'season_number': 11,
            'episode_number': 20,
            'upload_date': '20151006',
            'timestamp': 1444107300,
            'age_limit': 14,
            'uploader': 'CWTV',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'http://cwtv.com/thecw/chroniclesofcisco/?play=8adebe35-f447-465f-ab52-e863506ff6d6',
        'only_matching': True,
    }, {
        'url': 'http://cwtvpr.com/the-cw/video?watch=9eee3f60-ef4e-440b-b3b2-49428ac9c54e',
        'only_matching': True,
    }, {
        'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?watch=6b15e985-9345-4f60-baf8-56e96be57c63',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        data = self._download_json(
            'http://images.cwtv.com/feed/mobileapp/video-meta/apiversion_8/guid_' + video_id,
            video_id)
        if data.get('result') != 'ok':
            raise ExtractorError(data['msg'], expected=True)
        video_data = data['video']
        title = video_data['title']
        mpx_url = video_data.get('mpx_url') or 'http://link.theplatform.com/s/cwtv/media/guid/2703454149/%s?formats=M3U' % video_id

        season = str_or_none(video_data.get('season'))
        episode = str_or_none(video_data.get('episode'))
        if episode and season:
            episode = episode[len(season):]

        return {
            '_type': 'url_transparent',
            'id': video_id,
            'title': title,
            'url': smuggle_url(mpx_url, {'force_smil_url': True}),
            'description': video_data.get('description_long'),
            'duration': int_or_none(video_data.get('duration_secs')),
            'series': video_data.get('series_name'),
            'season_number': int_or_none(season),
            'episode_number': int_or_none(episode),
            'timestamp': parse_iso8601(video_data.get('start_time')),
            'age_limit': parse_age_limit(video_data.get('rating')),
            'ie_key': 'ThePlatform',
        }


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

import re

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

class DailyMailIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?dailymail\.co\.uk/(?:video/[^/]+/video-|embed/video/)(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://www.dailymail.co.uk/video/tvshowbiz/video-1295863/The-Mountain-appears-sparkling-water-ad-Heavy-Bubbles.html',
        'md5': 'f6129624562251f628296c3a9ffde124',
        'info_dict': {
            'id': '1295863',
            'ext': 'mp4',
            'title': 'The Mountain appears in sparkling water ad for \'Heavy Bubbles\'',
            'description': 'md5:a93d74b6da172dd5dc4d973e0b766a84',
        }
    }, {
        'url': 'http://www.dailymail.co.uk/embed/video/1295863.html',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return re.findall(
            r'<iframe\b[^>]+\bsrc=["\'](?P<url>(?:https?:)?//(?:www\.)?dailymail\.co\.uk/embed/video/\d+\.html)',
            webpage)

    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"data-opts='({.+?})'", webpage, 'video data'), video_id)
        title = unescapeHTML(video_data['title'])

        sources_url = (try_get(
            video_data,
            (lambda x: x['plugins']['sources']['url'],
             lambda x: x['sources']['url']), compat_str)
            or 'http://www.dailymail.co.uk/api/player/%s/video-sources.json' % video_id)

        video_sources = self._download_json(sources_url, video_id)
        body = video_sources.get('body')
        if body:
            video_sources = body

        formats = []
        for rendition in video_sources['renditions']:
            rendition_url = rendition.get('url')
            if not rendition_url:
                continue
            tbr = int_or_none(rendition.get('encodingRate'), 1000)
            container = rendition.get('videoContainer')
            is_hls = container == 'M2TS'
            protocol = 'm3u8_native' if is_hls else determine_protocol({'url': rendition_url})
            formats.append({
                'format_id': ('hls' if is_hls else protocol) + ('-%d' % tbr if tbr else ''),
                'url': rendition_url,
                'width': int_or_none(rendition.get('frameWidth')),
                'height': int_or_none(rendition.get('frameHeight')),
                'tbr': tbr,
                'vcodec': rendition.get('videoCodec'),
                'container': container,
                'protocol': protocol,
                'ext': 'mp4' if is_hls else None,
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': unescapeHTML(video_data.get('descr')),
            'thumbnail': video_data.get('poster') or video_data.get('thumbnail'),
            'formats': formats,
        }