code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    js_to_json,
    mimetype2ext,
)

class ThreeQSDNIE(InfoExtractor):
    IE_NAME = '3qsdn'
    IE_DESC = '3Q SDN'
    _VALID_URL = r'https?://playout\.3qsdn\.com/(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
    _TESTS = [{
        # ondemand from http://www.philharmonie.tv/veranstaltung/26/
        'url': 'http://playout.3qsdn.com/0280d6b9-1215-11e6-b427-0cc47a188158?protocol=http',
        'md5': 'ab040e37bcfa2e0c079f92cb1dd7f6cd',
        'info_dict': {
            'id': '0280d6b9-1215-11e6-b427-0cc47a188158',
            'ext': 'mp4',
            'title': '0280d6b9-1215-11e6-b427-0cc47a188158',
            'is_live': False,
        },
        'expected_warnings': ['Failed to download MPD manifest', 'Failed to parse JSON'],
    }, {
        # live video stream
        'url': 'https://playout.3qsdn.com/d755d94b-4ab9-11e3-9162-0025907ad44f?js=true',
        'info_dict': {
            'id': 'd755d94b-4ab9-11e3-9162-0025907ad44f',
            'ext': 'mp4',
            'title': 're:^d755d94b-4ab9-11e3-9162-0025907ad44f [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'is_live': True,
        },
        'params': {
            'skip_download': True,  # m3u8 downloads
        },
        'expected_warnings': ['Failed to download MPD manifest'],
    }, {
        # live audio stream
        'url': 'http://playout.3qsdn.com/9edf36e0-6bf2-11e2-a16a-9acf09e2db48',
        'only_matching': True,
    }, {
        # live audio stream with some 404 URLs
        'url': 'http://playout.3qsdn.com/ac5c3186-777a-11e2-9c30-9acf09e2db48',
        'only_matching': True,
    }, {
        # geo restricted with 'This content is not available in your country'
        'url': 'http://playout.3qsdn.com/d63a3ffe-75e8-11e2-9c30-9acf09e2db48',
        'only_matching': True,
    }, {
        # geo restricted with 'playout.3qsdn.com/forbidden'
        'url': 'http://playout.3qsdn.com/8e330f26-6ae2-11e2-a16a-9acf09e2db48',
        'only_matching': True,
    }, {
        # live video with rtmp link
        'url': 'https://playout.3qsdn.com/6092bb9e-8f72-11e4-a173-002590c750be',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_url(webpage):
        mobj = re.search(
            r'<iframe[^>]+\b(?:data-)?src=(["\'])(?P<url>%s.*?)\1' % ThreeQSDNIE._VALID_URL, webpage)
        if mobj:
            return mobj.group('url')

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

        js = self._download_webpage(
            'http://playout.3qsdn.com/%s' % video_id, video_id,
            query={'js': 'true'})

        if any(p in js for p in (
                '>This content is not available in your country',
                'playout.3qsdn.com/forbidden')):
            self.raise_geo_restricted()

        stream_content = self._search_regex(
            r'streamContent\s*:\s*(["\'])(?P<content>.+?)\1', js,
            'stream content', default='demand', group='content')

        live = stream_content == 'live'

        stream_type = self._search_regex(
            r'streamType\s*:\s*(["\'])(?P<type>audio|video)\1', js,
            'stream type', default='video', group='type')

        formats = []
        urls = set()

        def extract_formats(item_url, item={}):
            if not item_url or item_url in urls:
                return
            urls.add(item_url)
            ext = mimetype2ext(item.get('type')) or determine_ext(item_url, default_ext=None)
            if ext == 'mpd':
                formats.extend(self._extract_mpd_formats(
                    item_url, video_id, mpd_id='mpd', fatal=False))
            elif ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    item_url, video_id, 'mp4',
                    entry_protocol='m3u8' if live else 'm3u8_native',
                    m3u8_id='hls', fatal=False))
            elif ext == 'f4m':
                formats.extend(self._extract_f4m_formats(
                    item_url, video_id, f4m_id='hds', fatal=False))
            else:
                if not self._is_valid_url(item_url, video_id):
                    return
                formats.append({
                    'url': item_url,
                    'format_id': item.get('quality'),
                    'ext': 'mp4' if item_url.startswith('rtsp') else ext,
                    'vcodec': 'none' if stream_type == 'audio' else None,
                })

        for item_js in re.findall(r'({[^{]*?\b(?:src|source)\s*:\s*["\'].+?})', js):
            f = self._parse_json(
                item_js, video_id, transform_source=js_to_json, fatal=False)
            if not f:
                continue
            extract_formats(f.get('src'), f)

        # More relaxed version to collect additional URLs and acting
        # as a future-proof fallback
        for _, src in re.findall(r'\b(?:src|source)\s*:\s*(["\'])((?:https?|rtsp)://.+?)\1', js):
            extract_formats(src)

        self._sort_formats(formats)

        title = self._live_title(video_id) if live else video_id

        return {
            'id': video_id,
            'title': title,
            'is_live': live,
            'formats': formats,
        }


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

from .common import InfoExtractor
from ..utils import (
    compat_str,
    ExtractorError,
    int_or_none,
    str_or_none,
    try_get,
    url_or_none,
)

class TikTokBaseIE(InfoExtractor):
    def _extract_aweme(self, data):
        video = data['video']
        description = str_or_none(try_get(data, lambda x: x['desc']))
        width = int_or_none(try_get(data, lambda x: video['width']))
        height = int_or_none(try_get(data, lambda x: video['height']))

        format_urls = set()
        formats = []
        for format_id in (
                'play_addr_lowbr', 'play_addr', 'play_addr_h264',
                'download_addr'):
            for format in try_get(
                    video, lambda x: x[format_id]['url_list'], list) or []:
                format_url = url_or_none(format)
                if not format_url:
                    continue
                if format_url in format_urls:
                    continue
                format_urls.add(format_url)
                formats.append({
                    'url': format_url,
                    'ext': 'mp4',
                    'height': height,
                    'width': width,
                })
        self._sort_formats(formats)

        thumbnail = url_or_none(try_get(
            video, lambda x: x['cover']['url_list'][0], compat_str))
        uploader = try_get(data, lambda x: x['author']['nickname'], compat_str)
        timestamp = int_or_none(data.get('create_time'))
        comment_count = int_or_none(data.get('comment_count')) or int_or_none(
            try_get(data, lambda x: x['statistics']['comment_count']))
        repost_count = int_or_none(try_get(
            data, lambda x: x['statistics']['share_count']))

        aweme_id = data['aweme_id']

        return {
            'id': aweme_id,
            'title': uploader or aweme_id,
            'description': description,
            'thumbnail': thumbnail,
            'uploader': uploader,
            'timestamp': timestamp,
            'comment_count': comment_count,
            'repost_count': repost_count,
            'formats': formats,
        }

class TikTokIE(TikTokBaseIE):
    _VALID_URL = r'''(?x)
                        https?://
                            (?:
                                (?:m\.)?tiktok\.com/v|
                                (?:www\.)?tiktok\.com/share/video
                            )
                            /(?P<id>\d+)
                    '''
    _TESTS = [{
        'url': 'https://m.tiktok.com/v/6606727368545406213.html',
        'md5': 'd584b572e92fcd48888051f238022420',
        'info_dict': {
            'id': '6606727368545406213',
            'ext': 'mp4',
            'title': 'Zureeal',
            'description': '#bowsette#mario#cosplay#uk#lgbt#gaming#asian#bowsettecosplay',
            'thumbnail': r're:^https?://.*~noop.image',
            'uploader': 'Zureeal',
            'timestamp': 1538248586,
            'upload_date': '20180929',
            'comment_count': int,
            'repost_count': int,
        }
    }, {
        'url': 'https://www.tiktok.com/share/video/6606727368545406213',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(
            'https://m.tiktok.com/v/%s.html' % video_id, video_id)
        data = self._parse_json(self._search_regex(
            r'\bdata\s*=\s*({.+?})\s*;', webpage, 'data'), video_id)
        return self._extract_aweme(data)

class TikTokUserIE(TikTokBaseIE):
    _VALID_URL = r'''(?x)
                        https?://
                            (?:
                                (?:m\.)?tiktok\.com/h5/share/usr|
                                (?:www\.)?tiktok\.com/share/user
                            )
                            /(?P<id>\d+)
                    '''
    _TESTS = [{
        'url': 'https://m.tiktok.com/h5/share/usr/188294915489964032.html',
        'info_dict': {
            'id': '188294915489964032',
        },
        'playlist_mincount': 24,
    }, {
        'url': 'https://www.tiktok.com/share/user/188294915489964032',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        user_id = self._match_id(url)
        data = self._download_json(
            'https://m.tiktok.com/h5/share/usr/list/%s/' % user_id, user_id,
            query={'_signature': '_'})
        entries = []
        for aweme in data['aweme_list']:
            try:
                entry = self._extract_aweme(aweme)
            except ExtractorError:
                continue
            entry['extractor_key'] = TikTokIE.ie_key()
            entries.append(entry)
        return self.playlist_result(entries, user_id)

repo_name: rrosajp/youtube


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..utils import ExtractorError

class TinyPicIE(InfoExtractor):
    IE_NAME = 'tinypic'
    IE_DESC = 'tinypic.com videos'
    _VALID_URL = r'https?://(?:.+?\.)?tinypic\.com/player\.php\?v=(?P<id>[^&]+)&s=\d+'

    _TESTS = [
        {
            'url': 'http://tinypic.com/player.php?v=6xw7tc%3E&s=5#.UtqZmbRFCM8',
            'md5': '609b74432465364e72727ebc6203f044',
            'info_dict': {
                'id': '6xw7tc',
                'ext': 'flv',
                'title': 'shadow phenomenon weird',
            },
        },
        {
            'url': 'http://de.tinypic.com/player.php?v=dy90yh&s=8',
            'only_matching': True,
        }
    ]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id')

        webpage = self._download_webpage(url, video_id, 'Downloading page')

        mobj = re.search(r'(?m)fo\.addVariable\("file",\s"(?P<fileid>[\da-z]+)"\);\n'
                         r'\s+fo\.addVariable\("s",\s"(?P<serverid>\d+)"\);', webpage)
        if mobj is None:
            raise ExtractorError('Video %s does not exist' % video_id, expected=True)

        file_id = mobj.group('fileid')
        server_id = mobj.group('serverid')

        KEYWORDS_SUFFIX = ', Video, images, photos, videos, myspace, ebay, video hosting, photo hosting'
        keywords = self._html_search_meta('keywords', webpage, 'title')
        title = keywords[:-len(KEYWORDS_SUFFIX)] if keywords.endswith(KEYWORDS_SUFFIX) else ''

        video_url = 'http://v%s.tinypic.com/%s.flv' % (server_id, file_id)
        thumbnail = 'http://v%s.tinypic.com/%s_th.jpg' % (server_id, file_id)

        return {
            'id': file_id,
            'url': video_url,
            'thumbnail': thumbnail,
            'title': title
        }


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

from .common import InfoExtractor

class TMZIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?tmz\.com/videos/(?P<id>[^/?#]+)'
    _TESTS = [{
        'url': 'http://www.tmz.com/videos/0_okj015ty/',
        'md5': '4d22a51ef205b6c06395d8394f72d560',
        'info_dict': {
            'id': '0_okj015ty',
            'ext': 'mp4',
            'title': 'Kim Kardashian\'s Boobs Unlock a Mystery!',
            'description': 'Did Kim Kardasain try to one-up Khloe by one-upping Kylie???  Or is she just showing off her amazing boobs?',
            'timestamp': 1394747163,
            'uploader_id': 'batchUser',
            'upload_date': '20140313',
        }
    }, {
        'url': 'http://www.tmz.com/videos/0-cegprt2p/',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url).replace('-', '_')
        return self.url_result('kaltura:591531:%s' % video_id, 'Kaltura', video_id)

class TMZArticleIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?tmz\.com/\d{4}/\d{2}/\d{2}/(?P<id>[^/]+)/?'
    _TEST = {
        'url': 'http://www.tmz.com/2015/04/19/bobby-brown-bobbi-kristina-awake-video-concert',
        'md5': '3316ff838ae5bb7f642537825e1e90d2',
        'info_dict': {
            'id': '0_6snoelag',
            'ext': 'mov',
            'title': 'Bobby Brown Tells Crowd ... Bobbi Kristina is Awake',
            'description': 'Bobby Brown stunned his audience during a concert Saturday night, when he told the crowd, "Bobbi is awake.  She\'s watching me."',
            'timestamp': 1429467813,
            'upload_date': '20150419',
            'uploader_id': 'batchUser',
        }
    }

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

        webpage = self._download_webpage(url, video_id)
        embedded_video_info = self._parse_json(self._html_search_regex(
            r'tmzVideoEmbed\(({.+?})\);', webpage, 'embedded video info'),
            video_id)

        return self.url_result(
            'http://www.tmz.com/videos/%s/' % embedded_video_info['id'])


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    fix_xml_ampersands,
    float_or_none,
    int_or_none,
    parse_duration,
    str_to_int,
    unescapeHTML,
    xpath_text,
)

class TNAFlixNetworkBaseIE(InfoExtractor):
    # May be overridden in descendants if necessary
    _CONFIG_REGEX = [
        r'flashvars\.config\s*=\s*escape\("(?P<url>[^"]+)"',
        r'<input[^>]+name="config\d?" value="(?P<url>[^"]+)"',
        r'config\s*=\s*(["\'])(?P<url>(?:https?:)?//(?:(?!\1).)+)\1',
    ]
    _HOST = 'tna'
    _VKEY_SUFFIX = ''
    _TITLE_REGEX = r'<input[^>]+name="title" value="([^"]+)"'
    _DESCRIPTION_REGEX = r'<input[^>]+name="description" value="([^"]+)"'
    _UPLOADER_REGEX = r'<input[^>]+name="username" value="([^"]+)"'
    _VIEW_COUNT_REGEX = None
    _COMMENT_COUNT_REGEX = None
    _AVERAGE_RATING_REGEX = None
    _CATEGORIES_REGEX = r'
]*>\s*]+class="infoTitle"[^>]*>Categories:\s*]+class="listView"[^>]*>(.+?)\s*
'

    def _extract_thumbnails(self, flix_xml):

        def get_child(elem, names):
            for name in names:
                child = elem.find(name)
                if child is not None:
                    return child

        timeline = get_child(flix_xml, ['timeline', 'rolloverBarImage'])
        if timeline is None:
            return

        pattern_el = get_child(timeline, ['imagePattern', 'pattern'])
        if pattern_el is None or not pattern_el.text:
            return

        first_el = get_child(timeline, ['imageFirst', 'first'])
        last_el = get_child(timeline, ['imageLast', 'last'])
        if first_el is None or last_el is None:
            return

        first_text = first_el.text
        last_text = last_el.text
        if not first_text.isdigit() or not last_text.isdigit():
            return

        first = int(first_text)
        last = int(last_text)
        if first > last:
            return

        width = int_or_none(xpath_text(timeline, './imageWidth', 'thumbnail width'))
        height = int_or_none(xpath_text(timeline, './imageHeight', 'thumbnail height'))

        return [{
            'url': self._proto_relative_url(pattern_el.text.replace('#', compat_str(i)), 'http:'),
            'width': width,
            'height': height,
        } for i in range(first, last + 1)]

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id')
        for display_id_key in ('display_id', 'display_id_2'):
            if display_id_key in mobj.groupdict():
                display_id = mobj.group(display_id_key)
                if display_id:
                    break
        else:
            display_id = video_id

        webpage = self._download_webpage(url, display_id)

        cfg_url = self._proto_relative_url(self._html_search_regex(
            self._CONFIG_REGEX, webpage, 'flashvars.config', default=None,
            group='url'), 'http:')

        if not cfg_url:
            inputs = self._hidden_inputs(webpage)
            cfg_url = ('https://cdn-fck.%sflix.com/%sflix/%s%s.fid?key=%s&VID=%s&premium=1&vip=1&alpha'
                       % (self._HOST, self._HOST, inputs['vkey'], self._VKEY_SUFFIX, inputs['nkey'], video_id))

        cfg_xml = self._download_xml(
            cfg_url, display_id, 'Downloading metadata',
            transform_source=fix_xml_ampersands, headers={'Referer': url})

        formats = []

        def extract_video_url(vl):
            # Any URL modification now results in HTTP Error 403: Forbidden
            return unescapeHTML(vl.text)

        video_link = cfg_xml.find('./videoLink')
        if video_link is not None:
            formats.append({
                'url': extract_video_url(video_link),
                'ext': xpath_text(cfg_xml, './videoConfig/type', 'type', default='flv'),
            })

        for item in cfg_xml.findall('./quality/item'):
            video_link = item.find('./videoLink')
            if video_link is None:
                continue
            res = item.find('res')
            format_id = None if res is None else res.text
            height = int_or_none(self._search_regex(
                r'^(\d+)[pP]', format_id, 'height', default=None))
            formats.append({
                'url': self._proto_relative_url(extract_video_url(video_link), 'http:'),
                'format_id': format_id,
                'height': height,
            })

        self._sort_formats(formats)

        thumbnail = self._proto_relative_url(
            xpath_text(cfg_xml, './startThumb', 'thumbnail'), 'http:')
        thumbnails = self._extract_thumbnails(cfg_xml)

        title = None
        if self._TITLE_REGEX:
            title = self._html_search_regex(
                self._TITLE_REGEX, webpage, 'title', default=None)
        if not title:
            title = self._og_search_title(webpage)

        age_limit = self._rta_search(webpage) or 18

        duration = parse_duration(self._html_search_meta(
            'duration', webpage, 'duration', default=None))

        def extract_field(pattern, name):
            return self._html_search_regex(pattern, webpage, name, default=None) if pattern else None

        description = extract_field(self._DESCRIPTION_REGEX, 'description')
        uploader = extract_field(self._UPLOADER_REGEX, 'uploader')
        view_count = str_to_int(extract_field(self._VIEW_COUNT_REGEX, 'view count'))
        comment_count = str_to_int(extract_field(self._COMMENT_COUNT_REGEX, 'comment count'))
        average_rating = float_or_none(extract_field(self._AVERAGE_RATING_REGEX, 'average rating'))

        categories_str = extract_field(self._CATEGORIES_REGEX, 'categories')
        categories = [c.strip() for c in categories_str.split(',')] if categories_str is not None else []

        return {
            'id': video_id,
            'display_id': display_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'thumbnails': thumbnails,
            'duration': duration,
            'age_limit': age_limit,
            'uploader': uploader,
            'view_count': view_count,
            'comment_count': comment_count,
            'average_rating': average_rating,
            'categories': categories,
            'formats': formats,
        }

class TNAFlixNetworkEmbedIE(TNAFlixNetworkBaseIE):
    _VALID_URL = r'https?://player\.(?:tna|emp)flix\.com/video/(?P<id>\d+)'

    _TITLE_REGEX = r'<title>([^<]+)</title>'

    _TESTS = [{
        'url': 'https://player.tnaflix.com/video/6538',
        'info_dict': {
            'id': '6538',
            'display_id': '6538',
            'ext': 'mp4',
            'title': 'Educational xxx video',
            'thumbnail': r're:https?://.*\.jpg$',
            'age_limit': 18,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://player.empflix.com/video/33051',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return [url for _, url in re.findall(
            r']+?src=(["\'])(?P<url>(?:https?:)?//player\.(?:tna|emp)flix\.com/video/\d+)\1',
            webpage)]

class TNAEMPFlixBaseIE(TNAFlixNetworkBaseIE):
    _DESCRIPTION_REGEX = r'(?s)>Description:</[^>]+>(.+?)<'
    _UPLOADER_REGEX = r'by\s*]+\bhref=["\']/profile/[^>]+>([^<]+)<'
    _CATEGORIES_REGEX = r'(?s)]*>Categories:(.+?)
'

class TNAFlixIE(TNAEMPFlixBaseIE):
    _VALID_URL = r'https?://(?:www\.)?tnaflix\.com/[^/]+/(?P<display_id>[^/]+)/video(?P<id>\d+)'

    _TITLE_REGEX = r'<title>(.+?) - (?:TNAFlix Porn Videos|TNAFlix\.com)</title>'

    _TESTS = [{
        # anonymous uploader, no categories
        'url': 'http://www.tnaflix.com/porn-stars/Carmella-Decesare-striptease/video553878',
        'md5': '7e569419fe6d69543d01e6be22f5f7c4',
        'info_dict': {
            'id': '553878',
            'display_id': 'Carmella-Decesare-striptease',
            'ext': 'mp4',
            'title': 'Carmella Decesare - striptease',
            'thumbnail': r're:https?://.*\.jpg$',
            'duration': 91,
            'age_limit': 18,
            'categories': ['Porn Stars'],
        }
    }, {
        # non-anonymous uploader, categories
        'url': 'https://www.tnaflix.com/teen-porn/Educational-xxx-video/video6538',
        'md5': '0f5d4d490dbfd117b8607054248a07c0',
        'info_dict': {
            'id': '6538',
            'display_id': 'Educational-xxx-video',
            'ext': 'mp4',
            'title': 'Educational xxx video',
            'description': 'md5:b4fab8f88a8621c8fabd361a173fe5b8',
            'thumbnail': r're:https?://.*\.jpg$',
            'duration': 164,
            'age_limit': 18,
            'uploader': 'bobwhite39',
            'categories': list,
        }
    }, {
        'url': 'https://www.tnaflix.com/amateur-porn/bunzHD-Ms.Donk/video358632',
        'only_matching': True,
    }]

class EMPFlixIE(TNAEMPFlixBaseIE):
    _VALID_URL = r'https?://(?:www\.)?empflix\.com/(?:videos/(?P<display_id>.+?)-|[^/]+/(?P<display_id_2>[^/]+)/video)(?P<id>[0-9]+)'

    _HOST = 'emp'
    _VKEY_SUFFIX = '-1'

    _TESTS = [{
        'url': 'http://www.empflix.com/videos/Amateur-Finger-Fuck-33051.html',
        'md5': 'bc30d48b91a7179448a0bda465114676',
        'info_dict': {
            'id': '33051',
            'display_id': 'Amateur-Finger-Fuck',
            'ext': 'mp4',
            'title': 'Amateur Finger Fuck',
            'description': 'Amateur solo finger fucking.',
            'thumbnail': r're:https?://.*\.jpg$',
            'duration': 83,
            'age_limit': 18,
            'uploader': 'cwbike',
            'categories': ['Amateur', 'Anal', 'Fisting', 'Home made', 'Solo'],
        }
    }, {
        'url': 'http://www.empflix.com/videos/[AROMA][ARMD-718]-Aoi-Yoshino-Sawa-25826.html',
        'only_matching': True,
    }, {
        'url': 'https://www.empflix.com/amateur-porn/Amateur-Finger-Fuck/video33051',
        'only_matching': True,
    }]

class MovieFapIE(TNAFlixNetworkBaseIE):
    _VALID_URL = r'https?://(?:www\.)?moviefap\.com/videos/(?P<id>[0-9a-f]+)/(?P<display_id>[^/]+)\.html'

    _VIEW_COUNT_REGEX = r'
Views\s*([\d,.]+)'
    _COMMENT_COUNT_REGEX = r']+id="comCount"[^>]*>([\d,.]+)'
    _AVERAGE_RATING_REGEX = r'Current Rating\s*
\s*([\d.]+)'
    _CATEGORIES_REGEX = r'(?s)
]+id="vid_info"[^>]*>\s*
]*>.+?
(.*?)
'

    _TESTS = [{
        # normal, multi-format video
        'url': 'http://www.moviefap.com/videos/be9867c9416c19f54a4a/experienced-milf-amazing-handjob.html',
        'md5': '26624b4e2523051b550067d547615906',
        'info_dict': {
            'id': 'be9867c9416c19f54a4a',
            'display_id': 'experienced-milf-amazing-handjob',
            'ext': 'mp4',
            'title': 'Experienced MILF Amazing Handjob',
            'description': 'Experienced MILF giving an Amazing Handjob',
            'thumbnail': r're:https?://.*\.jpg$',
            'age_limit': 18,
            'uploader': 'darvinfred06',
            'view_count': int,
            'comment_count': int,
            'average_rating': float,
            'categories': ['Amateur', 'Masturbation', 'Mature', 'Flashing'],
        }
    }, {
        # quirky single-format case where the extension is given as fid, but the video is really an flv
        'url': 'http://www.moviefap.com/videos/e5da0d3edce5404418f5/jeune-couple-russe.html',
        'md5': 'fa56683e291fc80635907168a743c9ad',
        'info_dict': {
            'id': 'e5da0d3edce5404418f5',
            'display_id': 'jeune-couple-russe',
            'ext': 'flv',
            'title': 'Jeune Couple Russe',
            'description': 'Amateur',
            'thumbnail': r're:https?://.*\.jpg$',
            'age_limit': 18,
            'uploader': 'whiskeyjar',
            'view_count': int,
            'comment_count': int,
            'average_rating': float,
            'categories': ['Amateur', 'Teen'],
        }
    }]


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

import json
import re

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    ExtractorError,
    float_or_none,
    int_or_none,
    parse_iso8601,
    sanitized_Request,
)

class ToggleIE(InfoExtractor):
    IE_NAME = 'toggle'
    _VALID_URL = r'https?://(?:(?:www\.)?mewatch|video\.toggle)\.sg/(?:en|zh)/(?:[^/]+/){2,}(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://www.mewatch.sg/en/series/lion-moms-tif/trailers/lion-moms-premier/343115',
        'info_dict': {
            'id': '343115',
            'ext': 'mp4',
            'title': 'Lion Moms Premiere',
            'description': 'md5:aea1149404bff4d7f7b6da11fafd8e6b',
            'upload_date': '20150910',
            'timestamp': 1441858274,
        },
        'params': {
            'skip_download': 'm3u8 download',
        }
    }, {
        'note': 'DRM-protected video',
        'url': 'http://www.mewatch.sg/en/movies/dug-s-special-mission/341413',
        'info_dict': {
            'id': '341413',
            'ext': 'wvm',
            'title': 'Dug\'s Special Mission',
            'description': 'md5:e86c6f4458214905c1772398fabc93e0',
            'upload_date': '20150827',
            'timestamp': 1440644006,
        },
        'params': {
            'skip_download': 'DRM-protected wvm download',
        }
    }, {
        # this also tests correct video id extraction
        'note': 'm3u8 links are geo-restricted, but Android/mp4 is okay',
        'url': 'http://www.mewatch.sg/en/series/28th-sea-games-5-show/28th-sea-games-5-show-ep11/332861',
        'info_dict': {
            'id': '332861',
            'ext': 'mp4',
            'title': '28th SEA Games (5 Show) -  Episode  11',
            'description': 'md5:3cd4f5f56c7c3b1340c50a863f896faa',
            'upload_date': '20150605',
            'timestamp': 1433480166,
        },
        'params': {
            'skip_download': 'DRM-protected wvm download',
        },
        'skip': 'm3u8 links are geo-restricted'
    }, {
        'url': 'http://video.toggle.sg/en/clips/seraph-sun-aloysius-will-suddenly-sing-some-old-songs-in-high-pitch-on-set/343331',
        'only_matching': True,
    }, {
        'url': 'http://www.mewatch.sg/en/clips/seraph-sun-aloysius-will-suddenly-sing-some-old-songs-in-high-pitch-on-set/343331',
        'only_matching': True,
    }, {
        'url': 'http://www.mewatch.sg/zh/series/zero-calling-s2-hd/ep13/336367',
        'only_matching': True,
    }, {
        'url': 'http://www.mewatch.sg/en/series/vetri-s2/webisodes/jeeva-is-an-orphan-vetri-s2-webisode-7/342302',
        'only_matching': True,
    }, {
        'url': 'http://www.mewatch.sg/en/movies/seven-days/321936',
        'only_matching': True,
    }, {
        'url': 'https://www.mewatch.sg/en/tv-show/news/may-2017-cna-singapore-tonight/fri-19-may-2017/512456',
        'only_matching': True,
    }, {
        'url': 'http://www.mewatch.sg/en/channels/eleven-plus/401585',
        'only_matching': True,
    }]

    _FORMAT_PREFERENCES = {
        'wvm-STBMain': -10,
        'wvm-iPadMain': -20,
        'wvm-iPhoneMain': -30,
        'wvm-Android': -40,
    }
    _API_USER = 'tvpapi_147'
    _API_PASS = '11111'

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

        webpage = self._download_webpage(
            url, video_id, note='Downloading video page')

        api_user = self._search_regex(
            r'apiUser\s*:\s*(["\'])(?P<user>.+?)\1', webpage, 'apiUser',
            default=self._API_USER, group='user')
        api_pass = self._search_regex(
            r'apiPass\s*:\s*(["\'])(?P<pass>.+?)\1', webpage, 'apiPass',
            default=self._API_PASS, group='pass')

        params = {
            'initObj': {
                'Locale': {
                    'LocaleLanguage': '',
                    'LocaleCountry': '',
                    'LocaleDevice': '',
                    'LocaleUserState': 0
                },
                'Platform': 0,
                'SiteGuid': 0,
                'DomainID': '0',
                'UDID': '',
                'ApiUser': api_user,
                'ApiPass': api_pass
            },
            'MediaID': video_id,
            'mediaType': 0,
        }

        req = sanitized_Request(
            'http://tvpapi.as.tvinci.com/v2_9/gateways/jsonpostgw.aspx?m=GetMediaInfo',
            json.dumps(params).encode('utf-8'))
        info = self._download_json(req, video_id, 'Downloading video info json')

        title = info['MediaName']

        formats = []
        for video_file in info.get('Files', []):
            video_url, vid_format = video_file.get('URL'), video_file.get('Format')
            if not video_url or video_url == 'NA' or not vid_format:
                continue
            ext = determine_ext(video_url)
            vid_format = vid_format.replace(' ', '')
            # if geo-restricted, m3u8 is inaccessible, but mp4 is okay
            if ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    video_url, video_id, ext='mp4', m3u8_id=vid_format,
                    note='Downloading %s m3u8 information' % vid_format,
                    errnote='Failed to download %s m3u8 information' % vid_format,
                    fatal=False))
            elif ext == 'mpd':
                formats.extend(self._extract_mpd_formats(
                    video_url, video_id, mpd_id=vid_format,
                    note='Downloading %s MPD manifest' % vid_format,
                    errnote='Failed to download %s MPD manifest' % vid_format,
                    fatal=False))
            elif ext == 'ism':
                formats.extend(self._extract_ism_formats(
                    video_url, video_id, ism_id=vid_format,
                    note='Downloading %s ISM manifest' % vid_format,
                    errnote='Failed to download %s ISM manifest' % vid_format,
                    fatal=False))
            elif ext in ('mp4', 'wvm'):
                # wvm are drm-protected files
                formats.append({
                    'ext': ext,
                    'url': video_url,
                    'format_id': vid_format,
                    'preference': self._FORMAT_PREFERENCES.get(ext + '-' + vid_format) or -1,
                    'format_note': 'DRM-protected video' if ext == 'wvm' else None
                })
        if not formats:
            # Most likely because geo-blocked
            raise ExtractorError('No downloadable videos found', expected=True)
        self._sort_formats(formats)

        duration = int_or_none(info.get('Duration'))
        description = info.get('Description')
        created_at = parse_iso8601(info.get('CreationDate') or None)

        average_rating = float_or_none(info.get('Rating'))
        view_count = int_or_none(info.get('ViewCounter') or info.get('view_counter'))
        like_count = int_or_none(info.get('LikeCounter') or info.get('like_counter'))

        thumbnails = []
        for picture in info.get('Pictures', []):
            if not isinstance(picture, dict):
                continue
            pic_url = picture.get('URL')
            if not pic_url:
                continue
            thumbnail = {
                'url': pic_url,
            }
            pic_size = picture.get('PicSize', '')
            m = re.search(r'(?P<width>\d+)[xX](?P<height>\d+)', pic_size)
            if m:
                thumbnail.update({
                    'width': int(m.group('width')),
                    'height': int(m.group('height')),
                })
            thumbnails.append(thumbnail)

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'duration': duration,
            'timestamp': created_at,
            'average_rating': average_rating,
            'view_count': view_count,
            'like_count': like_count,
            'thumbnails': thumbnails,
            'formats': formats,
        }


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

from .common import InfoExtractor
from ..utils import int_or_none

class TOnlineIE(InfoExtractor):
    IE_NAME = 't-online.de'
    _VALID_URL = r'https?://(?:www\.)?t-online\.de/tv/(?:[^/]+/)*id_(?P<id>\d+)'
    _TEST = {
        'url': 'http://www.t-online.de/tv/sport/fussball/id_79166266/drittes-remis-zidane-es-muss-etwas-passieren-.html',
        'md5': '7d94dbdde5f9d77c5accc73c39632c29',
        'info_dict': {
            'id': '79166266',
            'ext': 'mp4',
            'title': 'Drittes Remis! Zidane: "Es muss etwas passieren"',
            'description': 'Es läuft nicht rund bei Real Madrid. Das 1:1 gegen den SD Eibar war das dritte Unentschieden in Folge in der Liga.',
        }
    }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_data = self._download_json(
            'http://www.t-online.de/tv/id_%s/tid_json_video' % video_id, video_id)
        title = video_data['subtitle']

        formats = []
        for asset in video_data.get('assets', []):
            asset_source = asset.get('source') or asset.get('source2')
            if not asset_source:
                continue
            formats_id = []
            for field_key in ('type', 'profile'):
                field_value = asset.get(field_key)
                if field_value:
                    formats_id.append(field_value)
            formats.append({
                'format_id': '-'.join(formats_id),
                'url': asset_source,
            })

        thumbnails = []
        for image in video_data.get('images', []):
            image_source = image.get('source')
            if not image_source:
                continue
            thumbnails.append({
                'url': image_source,
            })

        return {
            'id': video_id,
            'title': title,
            'description': video_data.get('description'),
            'duration': int_or_none(video_data.get('duration')),
            'thumbnails': thumbnails,
            'formats': formats,
        }


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

import re

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

class ToonGogglesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?toongoggles\.com/shows/(?P<show_id>\d+)(?:/[^/]+/episodes/(?P<episode_id>\d+))?'
    _TESTS = [{
        'url': 'http://www.toongoggles.com/shows/217143/bernard-season-2/episodes/217147/football',
        'md5': '18289fc2b951eff6b953a9d8f01e6831',
        'info_dict': {
            'id': '217147',
            'ext': 'mp4',
            'title': 'Football',
            'uploader_id': '1',
            'description': 'Bernard decides to play football in order to be better than Lloyd and tries to beat him no matter how, he even cheats.',
            'upload_date': '20160718',
            'timestamp': 1468879330,
        }
    }, {
        'url': 'http://www.toongoggles.com/shows/227759/om-nom-stories-around-the-world',
        'info_dict': {
            'id': '227759',
            'title': 'Om Nom Stories Around The World',
        },
        'playlist_mincount': 11,
    }]

    def _call_api(self, action, page_id, query):
        query.update({
            'for_ng': 1,
            'for_web': 1,
            'show_meta': 1,
            'version': 7.0,
        })
        return self._download_json('http://api.toongoggles.com/' + action, page_id, query=query)

    def _parse_episode_data(self, episode_data):
        title = episode_data['episode_name']

        return {
            '_type': 'url_transparent',
            'id': episode_data['episode_id'],
            'title': title,
            'url': 'kaltura:513551:' + episode_data['entry_id'],
            'thumbnail': episode_data.get('thumbnail_url'),
            'description': episode_data.get('description'),
            'duration': parse_duration(episode_data.get('hms')),
            'series': episode_data.get('show_name'),
            'season_number': int_or_none(episode_data.get('season_num')),
            'episode_id': episode_data.get('episode_id'),
            'episode': title,
            'episode_number': int_or_none(episode_data.get('episode_num')),
            'categories': episode_data.get('categories'),
            'ie_key': 'Kaltura',
        }

    def _real_extract(self, url):
        show_id, episode_id = re.match(self._VALID_URL, url).groups()
        if episode_id:
            episode_data = self._call_api('search', episode_id, {
                'filter': 'episode',
                'id': episode_id,
            })['objects'][0]
            return self._parse_episode_data(episode_data)
        else:
            show_data = self._call_api('getepisodesbyshow', show_id, {
                'max': 1000000000,
                'showid': show_id,
            })
            entries = []
            for episode_data in show_data.get('objects', []):
                entries.append(self._parse_episode_data(episode_data))
            return self.playlist_result(entries, show_id, show_data.get('show_name'))


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

import json

from .radiocanada import RadioCanadaIE
from ..compat import compat_HTTPError
from ..utils import (
    ExtractorError,
    int_or_none,
    merge_dicts,
)

class TouTvIE(RadioCanadaIE):
    _NETRC_MACHINE = 'toutv'
    IE_NAME = 'tou.tv'
    _VALID_URL = r'https?://ici\.tou\.tv/(?P<id>[a-zA-Z0-9_-]+(?:/S[0-9]+[EC][0-9]+)?)'

    _TESTS = [{
        'url': 'http://ici.tou.tv/garfield-tout-court/S2015E17',
        'info_dict': {
            'id': '122017',
            'ext': 'mp4',
            'title': 'Saison 2015 Épisode 17',
            'description': 'La photo de famille 2',
            'upload_date': '20100717',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
        'skip': '404 Not Found',
    }, {
        'url': 'http://ici.tou.tv/hackers',
        'only_matching': True,
    }, {
        'url': 'https://ici.tou.tv/l-age-adulte/S01C501',
        'only_matching': True,
    }]
    _CLIENT_KEY = '90505c8d-9c34-4f34-8da1-3a85bdc6d4f4'

    def _real_initialize(self):
        email, password = self._get_login_info()
        if email is None:
            return
        try:
            self._access_token = self._download_json(
                'https://services.radio-canada.ca/toutv/profiling/accounts/login',
                None, 'Logging in', data=json.dumps({
                    'ClientId': self._CLIENT_KEY,
                    'ClientSecret': '34026772-244b-49b6-8b06-317b30ac9a20',
                    'Email': email,
                    'Password': password,
                    'Scope': 'id.write media-validation.read',
                }).encode(), headers={
                    'Authorization': 'client-key ' + self._CLIENT_KEY,
                    'Content-Type': 'application/json;charset=utf-8',
                })['access_token']
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                error = self._parse_json(e.cause.read().decode(), None)['Message']
                raise ExtractorError(error, expected=True)
            raise
        self._claims = self._call_api('validation/v2/getClaims')['claims']

    def _real_extract(self, url):
        path = self._match_id(url)
        metadata = self._download_json(
            'https://services.radio-canada.ca/toutv/presentation/%s' % path, path, query={
                'client_key': self._CLIENT_KEY,
                'device': 'web',
                'version': 4,
            })
        # IsDrm does not necessarily mean the video is DRM protected (see
        # https://github.com/ytdl-org/youtube-dl/issues/13994).
        if metadata.get('IsDrm'):
            self.report_warning('This video is probably DRM protected.', path)
        video_id = metadata['IdMedia']
        details = metadata['Details']

        return merge_dicts({
            'id': video_id,
            'title': details.get('OriginalTitle'),
            'description': details.get('Description'),
            'thumbnail': details.get('ImageUrl'),
            'duration': int_or_none(details.get('LengthInSeconds')),
            'series': metadata.get('ProgramTitle'),
            'season_number': int_or_none(metadata.get('SeasonNumber')),
            'season': metadata.get('SeasonTitle'),
            'episode_number': int_or_none(metadata.get('EpisodeNumber')),
            'episode': metadata.get('EpisodeTitle'),
        }, self._extract_info(metadata.get('AppCode', 'toutv'), video_id))


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

from .common import InfoExtractor
import re

class ToypicsIE(InfoExtractor):
    IE_DESC = 'Toypics video'
    _VALID_URL = r'https?://videos\.toypics\.net/view/(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://videos.toypics.net/view/514/chancebulged,-2-1/',
        'md5': '16e806ad6d6f58079d210fe30985e08b',
        'info_dict': {
            'id': '514',
            'ext': 'mp4',
            'title': "Chance-Bulge'd, 2",
            'age_limit': 18,
            'uploader': 'kidsune',
        }
    }

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

        webpage = self._download_webpage(url, video_id)

        formats = self._parse_html5_media_entries(
            url, webpage, video_id)[0]['formats']
        title = self._html_search_regex([
            r'
]+class=["\']view-video-title[^>]+>([^<]+)</h',
            r'<title>([^<]+) - Toypics</title>',
        ], webpage, 'title')

        uploader = self._html_search_regex(
            r'More videos from ([^<]+)', webpage, 'uploader',
            fatal=False)

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'uploader': uploader,
            'age_limit': 18,
        }

class ToypicsUserIE(InfoExtractor):
    IE_DESC = 'Toypics user profile'
    _VALID_URL = r'https?://videos\.toypics\.net/(?!view)(?P<id>[^/?#&]+)'
    _TEST = {
        'url': 'http://videos.toypics.net/Mikey',
        'info_dict': {
            'id': 'Mikey',
        },
        'playlist_mincount': 19,
    }

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

        profile_page = self._download_webpage(
            url, username, note='Retrieving profile page')

        video_count = int(self._search_regex(
            r'public/">Public Videos \(([0-9]+)\)
', profile_page,
            'video count'))

        PAGE_SIZE = 8
        urls = []
        page_count = (video_count + PAGE_SIZE + 1) // PAGE_SIZE
        for n in range(1, page_count + 1):
            lpage_url = url + '/public/%d' % n
            lpage = self._download_webpage(
                lpage_url, username,
                note='Downloading page %d/%d' % (n, page_count))
            urls.extend(
                re.findall(
                    r'
]+class=["\']preview[^>]+>\s*]+href="(https?://videos\.toypics\.net/view/[^"]+)"',
                    lpage))

        return {
            '_type': 'playlist',
            'id': username,
            'entries': [{
                '_type': 'url',
                'url': eurl,
                'ie_key': 'Toypics',
            } for eurl in urls]
        }


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor

class TrailerAddictIE(InfoExtractor):
    _WORKING = False
    _VALID_URL = r'(?:https?://)?(?:www\.)?traileraddict\.com/(?:trailer|clip)/(?P<movie>.+?)/(?P<trailer_name>.+)'
    _TEST = {
        'url': 'http://www.traileraddict.com/trailer/prince-avalanche/trailer',
        'md5': '41365557f3c8c397d091da510e73ceb4',
        'info_dict': {
            'id': '76184',
            'ext': 'mp4',
            'title': 'Prince Avalanche Trailer',
            'description': 'Trailer for Prince Avalanche.\n\nTwo highway road workers spend the summer of 1988 away from their city lives. The isolated landscape becomes a place of misadventure as the men find themselves at odds with each other and the women they left behind.',
        }
    }

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        name = mobj.group('movie') + '/' + mobj.group('trailer_name')
        webpage = self._download_webpage(url, name)

        title = self._search_regex(r'<title>(.+?)</title>',
                                   webpage, 'video title').replace(' - Trailer Addict', '')
        view_count_str = self._search_regex(
            r'([0-9,.]+)',
            webpage, 'view count', fatal=False)
        view_count = (
            None if view_count_str is None
            else int(view_count_str.replace(',', '')))
        video_id = self._search_regex(
            r'<param\s+name="movie"\s+value="/emb/([0-9]+)"\s*/>',
            webpage, 'video id')

        # Presence of (no)watchplus function indicates HD quality is available
        if re.search(r'function (no)?watchplus()', webpage):
            fvar = 'fvarhd'
        else:
            fvar = 'fvar'

        info_url = 'http://www.traileraddict.com/%s.php?tid=%s' % (fvar, str(video_id))
        info_webpage = self._download_webpage(info_url, video_id, 'Downloading the info webpage')

        final_url = self._search_regex(r'&fileurl=(.+)',
                                       info_webpage, 'Download url').replace('%3F', '?')
        thumbnail_url = self._search_regex(r'&image=(.+?)&',
                                           info_webpage, 'thumbnail url')

        description = self._html_search_regex(
            r'(?s)
.*?
]*>(.*?)
',
            webpage, 'description', fatal=False)

        return {
            'id': video_id,
            'url': final_url,
            'title': title,
            'thumbnail': thumbnail_url,
            'description': description,
            'view_count': view_count,
        }


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

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

class TriluliluIE(InfoExtractor):
    _VALID_URL = r'https?://(?:(?:www|m)\.)?trilulilu\.ro/(?:[^/]+/)?(?P<id>[^/#\?]+)'
    _TESTS = [{
        'url': 'http://www.trilulilu.ro/big-buck-bunny-1',
        'md5': '68da087b676a6196a413549212f60cc6',
        'info_dict': {
            'id': 'ae2899e124140b',
            'ext': 'mp4',
            'title': 'Big Buck Bunny',
            'description': ':) pentru copilul din noi',
            'uploader_id': 'chipy',
            'upload_date': '20120304',
            'timestamp': 1330830647,
            'uploader': 'chipy',
            'view_count': int,
            'like_count': int,
            'comment_count': int,
        },
    }, {
        'url': 'http://www.trilulilu.ro/adena-ft-morreti-inocenta',
        'md5': '929dfb8729dc71750463af88bbbbf4a4',
        'info_dict': {
            'id': 'f299710e3c91c5',
            'ext': 'mp4',
            'title': 'Adena ft. Morreti - Inocenta',
            'description': 'pop music',
            'uploader_id': 'VEVOmixt',
            'upload_date': '20151204',
            'uploader': 'VEVOmixt',
            'timestamp': 1449187937,
            'view_count': int,
            'like_count': int,
            'comment_count': int,
        },
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        media_info = self._download_json('http://m.trilulilu.ro/%s?format=json' % display_id, display_id)

        age_limit = 0
        errors = media_info.get('errors', {})
        if errors.get('friends'):
            raise ExtractorError('This video is private.', expected=True)
        elif errors.get('geoblock'):
            raise ExtractorError('This video is not available in your country.', expected=True)
        elif errors.get('xxx_unlogged'):
            age_limit = 18

        media_class = media_info.get('class')
        if media_class not in ('video', 'audio'):
            raise ExtractorError('not a video or an audio')

        user = media_info.get('user', {})

        thumbnail = media_info.get('cover_url')
        if thumbnail:
            thumbnail.format(width='1600', height='1200')

        # TODO: get correct ext for audio files
        stream_type = media_info.get('stream_type')
        formats = [{
            'url': media_info['href'],
            'ext': stream_type,
        }]
        if media_info.get('is_hd'):
            formats.append({
                'format_id': 'hd',
                'url': media_info['hrefhd'],
                'ext': stream_type,
            })
        if media_class == 'audio':
            formats[0]['vcodec'] = 'none'
        else:
            formats[0]['format_id'] = 'sd'

        return {
            'id': media_info['identifier'].split('|')[1],
            'display_id': display_id,
            'formats': formats,
            'title': media_info['title'],
            'description': media_info.get('description'),
            'thumbnail': thumbnail,
            'uploader_id': user.get('username'),
            'uploader': user.get('fullname'),
            'timestamp': parse_iso8601(media_info.get('published'), ' '),
            'duration': int_or_none(media_info.get('duration')),
            'view_count': int_or_none(media_info.get('count_views')),
            'like_count': int_or_none(media_info.get('count_likes')),
            'comment_count': int_or_none(media_info.get('count_comments')),
            'age_limit': age_limit,
        }


code:
from __future__ import unicode_literals

from .common import InfoExtractor

class TruNewsIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?trunews\.com/stream/(?P<id>[^/?#&]+)'
    _TEST = {
        'url': 'https://www.trunews.com/stream/will-democrats-stage-a-circus-during-president-trump-s-state-of-the-union-speech',
        'info_dict': {
            'id': '5c5a21e65d3c196e1c0020cc',
            'display_id': 'will-democrats-stage-a-circus-during-president-trump-s-state-of-the-union-speech',
            'ext': 'mp4',
            'title': "Will Democrats Stage a Circus During President Trump's State of the Union Speech?",
            'description': 'md5:c583b72147cc92cf21f56a31aff7a670',
            'duration': 3685,
            'timestamp': 1549411440,
            'upload_date': '20190206',
        },
        'add_ie': ['Zype'],
    }
    _ZYPE_TEMPL = 'https://player.zype.com/embed/%s.js?api_key=X5XnahkjCwJrT_l5zUqypnaLEObotyvtUKJWWlONxDoHVjP8vqxlArLV8llxMbyt'

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

        zype_id = self._download_json(
            'https://api.zype.com/videos', display_id, query={
                'app_key': 'PUVKp9WgGUb3-JUw6EqafLx8tFVP6VKZTWbUOR-HOm__g4fNDt1bCsm_LgYf_k9H',
                'per_page': 1,
                'active': 'true',
                'friendly_title': display_id,
            })['response'][0]['_id']
        return self.url_result(self._ZYPE_TEMPL % zype_id, 'Zype', zype_id)


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

import re

from .turner import TurnerBaseIE
from ..utils import (
    int_or_none,
    parse_iso8601,
)

class TruTVIE(TurnerBaseIE):
    _VALID_URL = r'https?://(?:www\.)?trutv\.com/(?:shows|full-episodes)/(?P<series_slug>[0-9A-Za-z-]+)/(?:videos/(?P<clip_slug>[0-9A-Za-z-]+)|(?P<id>\d+))'
    _TEST = {
        'url': 'https://www.trutv.com/shows/the-carbonaro-effect/videos/sunlight-activated-flower.html',
        'info_dict': {
            'id': 'f16c03beec1e84cd7d1a51f11d8fcc29124cc7f1',
            'ext': 'mp4',
            'title': 'Sunlight-Activated Flower',
            'description': "A customer is stunned when he sees Michael's sunlight-activated flower.",
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }

    def _real_extract(self, url):
        series_slug, clip_slug, video_id = re.match(self._VALID_URL, url).groups()

        if video_id:
            path = 'episode'
            display_id = video_id
        else:
            path = 'series/clip'
            display_id = clip_slug

        data = self._download_json(
            'https://api.trutv.com/v2/web/%s/%s/%s' % (path, series_slug, display_id),
            display_id)
        video_data = data['episode'] if video_id else data['info']
        media_id = video_data['mediaId']
        title = video_data['title'].strip()

        info = self._extract_ngtv_info(
            media_id, {}, {
                'url': url,
                'site_name': 'truTV',
                'auth_required': video_data.get('isAuthRequired'),
            })

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

        info.update({
            'id': media_id,
            'display_id': display_id,
            'title': title,
            'description': video_data.get('description'),
            'thumbnails': thumbnails,
            'timestamp': parse_iso8601(video_data.get('publicationDate')),
            'series': video_data.get('showTitle'),
            'season_number': int_or_none(video_data.get('seasonNum')),
            'episode_number': int_or_none(video_data.get('episodeNum')),
        })
        return info


code:
from __future__ import unicode_literals

import re

from ..utils import (
    int_or_none,
    str_to_int,
)
from .keezmovies import KeezMoviesIE

class Tube8IE(KeezMoviesIE):
    _VALID_URL = r'https?://(?:www\.)?tube8\.com/(?:[^/]+/)+(?P<display_id>[^/]+)/(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://www.tube8.com/teen/kasia-music-video/229795/',
        'md5': '65e20c48e6abff62ed0c3965fff13a39',
        'info_dict': {
            'id': '229795',
            'display_id': 'kasia-music-video',
            'ext': 'mp4',
            'description': 'hot teen Kasia grinding',
            'uploader': 'unknown',
            'title': 'Kasia music video',
            'age_limit': 18,
            'duration': 230,
            'categories': ['Teen'],
            'tags': ['dancing'],
        },
    }, {
        'url': 'http://www.tube8.com/shemale/teen/blonde-cd-gets-kidnapped-by-two-blacks-and-punished-for-being-a-slutty-girl/19569151/',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return re.findall(
            r']+\bsrc=["\']((?:https?:)?//(?:www\.)?tube8\.com/embed/(?:[^/]+/)+\d+)',
            webpage)

    def _real_extract(self, url):
        webpage, info = self._extract_info(url)

        if not info['title']:
            info['title'] = self._html_search_regex(
                r'videoTitle\s*=\s*"([^"]+)', webpage, 'title')

        description = self._html_search_regex(
            r'(?s)Description:
\s*
(.+?)
', webpage, 'description', fatal=False)
        uploader = self._html_search_regex(
            r'\s*(.+?)\s*<',
            webpage, 'uploader', fatal=False)

        like_count = int_or_none(self._search_regex(
            r'rupVar\s*=\s*"(\d+)"', webpage, 'like count', fatal=False))
        dislike_count = int_or_none(self._search_regex(
            r'rdownVar\s*=\s*"(\d+)"', webpage, 'dislike count', fatal=False))
        view_count = str_to_int(self._search_regex(
            r'Views:\s*
\s*
([\d,\.]+)',
            webpage, 'view count', fatal=False))
        comment_count = str_to_int(self._search_regex(
            r'(\d+)',
            webpage, 'comment count', fatal=False))

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

        tags_str = self._search_regex(
            r'(?s)Tags:\s*
\s*
(.+?)</(?!a)',
            webpage, 'tags', fatal=False)
        tags = [t for t in re.findall(
            r']+href=[^>]+>([^<]+)', tags_str)] if tags_str else None

        info.update({
            'description': description,
            'uploader': uploader,
            'view_count': view_count,
            'like_count': like_count,
            'dislike_count': dislike_count,
            'comment_count': comment_count,
            'categories': categories,
            'tags': tags,
        })

        return info


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

import re

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

class TubiTvIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?tubitv\.com/(?:video|movies|tv-shows)/(?P<id>[0-9]+)'
    _LOGIN_URL = 'http://tubitv.com/login'
    _NETRC_MACHINE = 'tubitv'
    _GEO_COUNTRIES = ['US']
    _TESTS = [{
        'url': 'http://tubitv.com/video/283829/the_comedian_at_the_friday',
        'md5': '43ac06be9326f41912dc64ccf7a80320',
        'info_dict': {
            'id': '283829',
            'ext': 'mp4',
            'title': 'The Comedian at The Friday',
            'description': 'A stand up comedian is forced to look at the decisions in his life while on a one week trip to the west coast.',
            'uploader_id': 'bc168bee0d18dd1cb3b86c68706ab434',
        },
    }, {
        'url': 'http://tubitv.com/tv-shows/321886/s01_e01_on_nom_stories',
        'only_matching': True,
    }, {
        'url': 'http://tubitv.com/movies/383676/tracker',
        'only_matching': True,
    }]

    def _login(self):
        username, password = self._get_login_info()
        if username is None:
            return
        self.report_login()
        form_data = {
            'username': username,
            'password': password,
        }
        payload = urlencode_postdata(form_data)
        request = sanitized_Request(self._LOGIN_URL, payload)
        request.add_header('Content-Type', 'application/x-www-form-urlencoded')
        login_page = self._download_webpage(
            request, None, False, 'Wrong login info')
        if not re.search(r'id="tubi-logout"', login_page):
            raise ExtractorError(
                'Login failed (invalid username/password)', expected=True)

    def _real_initialize(self):
        self._login()

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_data = self._download_json(
            'http://tubitv.com/oz/videos/%s/content' % video_id, video_id)
        title = video_data['title']

        formats = self._extract_m3u8_formats(
            self._proto_relative_url(video_data['url']),
            video_id, 'mp4', 'm3u8_native')
        self._sort_formats(formats)

        thumbnails = []
        for thumbnail_url in video_data.get('thumbnails', []):
            if not thumbnail_url:
                continue
            thumbnails.append({
                'url': self._proto_relative_url(thumbnail_url),
            })

        subtitles = {}
        for sub in video_data.get('subtitles', []):
            sub_url = sub.get('url')
            if not sub_url:
                continue
            subtitles.setdefault(sub.get('lang', 'English'), []).append({
                'url': self._proto_relative_url(sub_url),
            })

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'subtitles': subtitles,
            'thumbnails': thumbnails,
            'description': video_data.get('description'),
            'duration': int_or_none(video_data.get('duration')),
            'uploader_id': video_data.get('publisher_id'),
        }


code:
# coding: utf-8

from __future__ import unicode_literals

from .common import InfoExtractor

class TudouPlaylistIE(InfoExtractor):
    IE_NAME = 'tudou:playlist'
    _VALID_URL = r'https?://(?:www\.)?tudou\.com/listplay/(?P<id>[\w-]{11})\.html'
    _TESTS = [{
        'url': 'http://www.tudou.com/listplay/zzdE77v6Mmo.html',
        'info_dict': {
            'id': 'zzdE77v6Mmo',
        },
        'playlist_mincount': 209,
    }]

    def _real_extract(self, url):
        playlist_id = self._match_id(url)
        playlist_data = self._download_json(
            'http://www.tudou.com/tvp/plist.action?lcode=%s' % playlist_id, playlist_id)
        entries = [self.url_result(
            'http://www.tudou.com/programs/view/%s' % item['icode'],
            'Tudou', item['icode'],
            item['kw']) for item in playlist_data['items']]
        return self.playlist_result(entries, playlist_id)

class TudouAlbumIE(InfoExtractor):
    IE_NAME = 'tudou:album'
    _VALID_URL = r'https?://(?:www\.)?tudou\.com/album(?:cover|play)/(?P<id>[\w-]{11})'
    _TESTS = [{
        'url': 'http://www.tudou.com/albumplay/v5qckFJvNJg.html',
        'info_dict': {
            'id': 'v5qckFJvNJg',
        },
        'playlist_mincount': 45,
    }]

    def _real_extract(self, url):
        album_id = self._match_id(url)
        album_data = self._download_json(
            'http://www.tudou.com/tvp/alist.action?acode=%s' % album_id, album_id)
        entries = [self.url_result(
            'http://www.tudou.com/programs/view/%s' % item['icode'],
            'Tudou', item['icode'],
            item['kw']) for item in album_data['items']]
        return self.playlist_result(entries, album_id)


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

import re

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

class TumblrIE(InfoExtractor):
    _VALID_URL = r'https?://(?P<blog_name>[^/?#&]+)\.tumblr\.com/(?:post|video)/(?P<id>[0-9]+)(?:$|[/?#])'
    _NETRC_MACHINE = 'tumblr'
    _LOGIN_URL = 'https://www.tumblr.com/login'
    _TESTS = [{
        'url': 'http://tatianamaslanydaily.tumblr.com/post/54196191430/orphan-black-dvd-extra-behind-the-scenes',
        'md5': '479bb068e5b16462f5176a6828829767',
        'info_dict': {
            'id': '54196191430',
            'ext': 'mp4',
            'title': 'tatiana maslany news, Orphan Black || DVD extra - behind the scenes ↳...',
            'description': 'md5:37db8211e40b50c7c44e95da14f630b7',
            'thumbnail': r're:http://.*\.jpg',
        }
    }, {
        'url': 'http://5sostrum.tumblr.com/post/90208453769/yall-forgetting-the-greatest-keek-of-them-all',
        'md5': 'bf348ef8c0ef84fbf1cbd6fa6e000359',
        'info_dict': {
            'id': '90208453769',
            'ext': 'mp4',
            'title': '5SOS STRUM ;]',
            'description': 'md5:dba62ac8639482759c8eb10ce474586a',
            'thumbnail': r're:http://.*\.jpg',
        }
    }, {
        'url': 'http://hdvideotest.tumblr.com/post/130323439814/test-description-for-my-hd-video',
        'md5': '7ae503065ad150122dc3089f8cf1546c',
        'info_dict': {
            'id': '130323439814',
            'ext': 'mp4',
            'title': 'HD Video Testing \u2014 Test description for my HD video',
            'description': 'md5:97cc3ab5fcd27ee4af6356701541319c',
            'thumbnail': r're:http://.*\.jpg',
        },
        'params': {
            'format': 'hd',
        },
    }, {
        'url': 'http://naked-yogi.tumblr.com/post/118312946248/naked-smoking-stretching',
        'md5': 'de07e5211d60d4f3a2c3df757ea9f6ab',
        'info_dict': {
            'id': 'Wmur',
            'ext': 'mp4',
            'title': 'naked smoking & stretching',
            'upload_date': '20150506',
            'timestamp': 1430931613,
            'age_limit': 18,
            'uploader_id': '1638622',
            'uploader': 'naked-yogi',
        },
        'add_ie': ['Vidme'],
    }, {
        'url': 'http://camdamage.tumblr.com/post/98846056295/',
        'md5': 'a9e0c8371ea1ca306d6554e3fecf50b6',
        'info_dict': {
            'id': '105463834',
            'ext': 'mp4',
            'title': 'Cam Damage-HD 720p',
            'uploader': 'John Moyer',
            'uploader_id': 'user32021558',
        },
        'add_ie': ['Vimeo'],
    }, {
        'url': 'http://sutiblr.tumblr.com/post/139638707273',
        'md5': '2dd184b3669e049ba40563a7d423f95c',
        'info_dict': {
            'id': 'ir7qBEIKqvq',
            'ext': 'mp4',
            'title': 'Vine by sutiblr',
            'alt_title': 'Vine by sutiblr',
            'uploader': 'sutiblr',
            'uploader_id': '1198993975374495744',
            'upload_date': '20160220',
            'like_count': int,
            'comment_count': int,
            'repost_count': int,
        },
        'add_ie': ['Vine'],
    }, {
        'url': 'http://vitasidorkina.tumblr.com/post/134652425014/joskriver-victoriassecret-invisibility-or',
        'md5': '01c12ceb82cbf6b2fe0703aa56b3ad72',
        'info_dict': {
            'id': '-7LnUPGlSo',
            'ext': 'mp4',
            'title': 'Video by victoriassecret',
            'description': 'Invisibility or flight…which superpower would YOU choose? #VSFashionShow #ThisOrThat',
            'uploader_id': 'victoriassecret',
            'thumbnail': r're:^https?://.*\.jpg'
        },
        'add_ie': ['Instagram'],
    }]

    def _real_initialize(self):
        self._login()

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

        login_form = self._hidden_inputs(login_page)
        login_form.update({
            'user[email]': username,
            'user[password]': password
        })

        response, urlh = self._download_webpage_handle(
            self._LOGIN_URL, None, 'Logging in',
            data=urlencode_postdata(login_form), headers={
                'Content-Type': 'application/x-www-form-urlencoded',
                'Referer': self._LOGIN_URL,
            })

        # Successful login
        if '/dashboard' in urlh.geturl():
            return

        login_errors = self._parse_json(
            self._search_regex(
                r'RegistrationForm\.errors\s*=\s*(\[.+?\])\s*;', response,
                'login errors', default='[]'),
            None, fatal=False)
        if login_errors:
            raise ExtractorError(
                'Unable to login: %s' % login_errors[0], expected=True)

        self.report_warning('Login has probably failed')

    def _real_extract(self, url):
        m_url = re.match(self._VALID_URL, url)
        video_id = m_url.group('id')
        blog = m_url.group('blog_name')

        url = 'http://%s.tumblr.com/post/%s/' % (blog, video_id)
        webpage, urlh = self._download_webpage_handle(url, video_id)

        redirect_url = urlh.geturl()
        if 'tumblr.com/safe-mode' in redirect_url or redirect_url.startswith('/safe-mode'):
            raise ExtractorError(
                'This Tumblr may contain sensitive media. '
                'Disable safe mode in your account settings '
                'at https://www.tumblr.com/settings/account#safe_mode',
                expected=True)

        iframe_url = self._search_regex(
            r'src=\'(https?://www\.tumblr\.com/video/[^\']+)\'',
            webpage, 'iframe url', default=None)
        if iframe_url is None:
            return self.url_result(redirect_url, 'Generic')

        iframe = self._download_webpage(iframe_url, video_id, 'Downloading iframe page')

        duration = None
        sources = []

        sd_url = self._search_regex(
            r'<source[^>]+src=(["\'])(?P<url>.+?)\1', iframe,
            'sd video url', default=None, group='url')
        if sd_url:
            sources.append((sd_url, 'sd'))

        options = self._parse_json(
            self._search_regex(
                r'data-crt-options=(["\'])(?P<options>.+?)\1', iframe,
                'hd video url', default='', group='options'),
            video_id, fatal=False)
        if options:
            duration = int_or_none(options.get('duration'))
            hd_url = options.get('hdUrl')
            if hd_url:
                sources.append((hd_url, 'hd'))

        formats = [{
            'url': video_url,
            'ext': 'mp4',
            'format_id': format_id,
            'height': int_or_none(self._search_regex(
                r'/(\d{3,4})$', video_url, 'height', default=None)),
            'quality': quality,
        } for quality, (video_url, format_id) in enumerate(sources)]

        self._sort_formats(formats)

        # The only place where you can get a title, it's not complete,
        # but searching in other places doesn't work for all videos
        video_title = self._html_search_regex(
            r'(?s)<title>(?P<title>.*?)(?: \| Tumblr)?</title>',
            webpage, 'title')

        return {
            'id': video_id,
            'title': video_title,
            'description': self._og_search_description(webpage, default=None),
            'thumbnail': self._og_search_thumbnail(webpage, default=None),
            'duration': duration,
            'formats': formats,
        }


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

import re

from .common import InfoExtractor
from ..utils import ExtractorError
from ..compat import compat_urlparse

class TuneInBaseIE(InfoExtractor):
    _API_BASE_URL = 'http://tunein.com/tuner/tune/'

    @staticmethod
    def _extract_urls(webpage):
        return re.findall(
            r'<iframe[^>]+src=["\'](?P<url>(?:https?://)?tunein\.com/embed/player/[pst]\d+)',
            webpage)

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

        content_info = self._download_json(
            self._API_BASE_URL + self._API_URL_QUERY % content_id,
            content_id, note='Downloading JSON metadata')

        title = content_info['Title']
        thumbnail = content_info.get('Logo')
        location = content_info.get('Location')
        streams_url = content_info.get('StreamUrl')
        if not streams_url:
            raise ExtractorError('No downloadable streams found', expected=True)
        if not streams_url.startswith('http://'):
            streams_url = compat_urlparse.urljoin(url, streams_url)

        streams = self._download_json(
            streams_url, content_id, note='Downloading stream data',
            transform_source=lambda s: re.sub(r'^\s*\((.*)\);\s*$', r'\1', s))['Streams']

        is_live = None
        formats = []
        for stream in streams:
            if stream.get('Type') == 'Live':
                is_live = True
            reliability = stream.get('Reliability')
            format_note = (
                'Reliability: %d%%' % reliability
                if reliability is not None else None)
            formats.append({
                'preference': (
                    0 if reliability is None or reliability > 90
                    else 1),
                'abr': stream.get('Bandwidth'),
                'ext': stream.get('MediaType').lower(),
                'acodec': stream.get('MediaType'),
                'vcodec': 'none',
                'url': stream.get('Url'),
                'source_preference': reliability,
                'format_note': format_note,
            })
        self._sort_formats(formats)

        return {
            'id': content_id,
            'title': self._live_title(title) if is_live else title,
            'formats': formats,
            'thumbnail': thumbnail,
            'location': location,
            'is_live': is_live,
        }

class TuneInClipIE(TuneInBaseIE):
    IE_NAME = 'tunein:clip'
    _VALID_URL = r'https?://(?:www\.)?tunein\.com/station/.*?audioClipId\=(?P<id>\d+)'
    _API_URL_QUERY = '?tuneType=AudioClip&audioclipId=%s'

    _TESTS = [{
        'url': 'http://tunein.com/station/?stationId=246119&audioClipId=816',
        'md5': '99f00d772db70efc804385c6b47f4e77',
        'info_dict': {
            'id': '816',
            'title': '32m',
            'ext': 'mp3',
        },
    }]

class TuneInStationIE(TuneInBaseIE):
    IE_NAME = 'tunein:station'
    _VALID_URL = r'https?://(?:www\.)?tunein\.com/(?:radio/.*?-s|station/.*?StationId=|embed/player/s)(?P<id>\d+)'
    _API_URL_QUERY = '?tuneType=Station&stationId=%s'

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

    _TESTS = [{
        'url': 'http://tunein.com/radio/Jazz24-885-s34682/',
        'info_dict': {
            'id': '34682',
            'title': 'Jazz 24 on 88.5 Jazz24 - KPLU-HD2',
            'ext': 'mp3',
            'location': 'Tacoma, WA',
        },
        'params': {
            'skip_download': True,  # live stream
        },
    }, {
        'url': 'http://tunein.com/embed/player/s6404/',
        'only_matching': True,
    }]

class TuneInProgramIE(TuneInBaseIE):
    IE_NAME = 'tunein:program'
    _VALID_URL = r'https?://(?:www\.)?tunein\.com/(?:radio/.*?-p|program/.*?ProgramId=|embed/player/p)(?P<id>\d+)'
    _API_URL_QUERY = '?tuneType=Program&programId=%s'

    _TESTS = [{
        'url': 'http://tunein.com/radio/Jazz-24-p2506/',
        'info_dict': {
            'id': '2506',
            'title': 'Jazz 24 on 91.3 WUKY-HD3',
            'ext': 'mp3',
            'location': 'Lexington, KY',
        },
        'params': {
            'skip_download': True,  # live stream
        },
    }, {
        'url': 'http://tunein.com/embed/player/p191660/',
        'only_matching': True,
    }]

class TuneInTopicIE(TuneInBaseIE):
    IE_NAME = 'tunein:topic'
    _VALID_URL = r'https?://(?:www\.)?tunein\.com/(?:topic/.*?TopicId=|embed/player/t)(?P<id>\d+)'
    _API_URL_QUERY = '?tuneType=Topic&topicId=%s'

    _TESTS = [{
        'url': 'http://tunein.com/topic/?TopicId=101830576',
        'md5': 'c31a39e6f988d188252eae7af0ef09c9',
        'info_dict': {
            'id': '101830576',
            'title': 'Votez pour moi du 29 octobre 2015 (29/10/15)',
            'ext': 'mp3',
            'location': 'Belgium',
        },
    }, {
        'url': 'http://tunein.com/embed/player/t101830576/',
        'only_matching': True,
    }]

class TuneInShortenerIE(InfoExtractor):
    IE_NAME = 'tunein:shortener'
    IE_DESC = False  # Do not list
    _VALID_URL = r'https?://tun\.in/(?P<id>[A-Za-z0-9]+)'

    _TEST = {
        # test redirection
        'url': 'http://tun.in/ser7s',
        'info_dict': {
            'id': '34682',
            'title': 'Jazz 24 on 88.5 Jazz24 - KPLU-HD2',
            'ext': 'mp3',
            'location': 'Tacoma, WA',
        },
        'params': {
            'skip_download': True,  # live stream
        },
    }

    def _real_extract(self, url):
        redirect_id = self._match_id(url)
        # The server doesn't support HEAD requests
        urlh = self._request_webpage(
            url, redirect_id, note='Downloading redirect page')
        url = urlh.geturl()
        self.to_screen('Following redirect: %s' % url)
        return self.url_result(url)


code:
from __future__ import unicode_literals

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

class TunePkIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            (?:www\.)?tune\.pk/(?:video/|player/embed_player.php?.*?\bvid=)|
                            embed\.tune\.pk/play/
                        )
                        (?P<id>\d+)
                    '''
    _TESTS = [{
        'url': 'https://tune.pk/video/6919541/maudie-2017-international-trailer-1-ft-ethan-hawke-sally-hawkins',
        'md5': '0c537163b7f6f97da3c5dd1e3ef6dd55',
        'info_dict': {
            'id': '6919541',
            'ext': 'mp4',
            'title': 'Maudie (2017) | International Trailer # 1 ft Ethan Hawke, Sally Hawkins',
            'description': 'md5:eb5a04114fafef5cec90799a93a2d09c',
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1487327564,
            'upload_date': '20170217',
            'uploader': 'Movie Trailers',
            'duration': 107,
            'view_count': int,
        }
    }, {
        'url': 'https://tune.pk/player/embed_player.php?vid=6919541&folder=2017/02/17/&width=600&height=350&autoplay=no',
        'only_matching': True,
    }, {
        'url': 'https://embed.tune.pk/play/6919541?autoplay=no&ssl=yes&inline=true',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(
            'https://tune.pk/video/%s' % video_id, video_id)

        details = self._parse_json(
            self._search_regex(
                r'new\s+TunePlayer\(({.+?})\)\s*;\s*\n', webpage, 'tune player'),
            video_id)['details']

        video = details['video']
        title = video.get('title') or self._og_search_title(
            webpage, default=None) or self._html_search_meta(
            'title', webpage, 'title', fatal=True)

        formats = self._parse_jwplayer_formats(
            details['player']['sources'], video_id)
        self._sort_formats(formats)

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

        thumbnail = video.get('thumb') or self._og_search_thumbnail(
            webpage, default=None) or self._html_search_meta(
            'thumbnail', webpage, 'thumbnail')

        timestamp = unified_timestamp(video.get('date_added'))
        uploader = try_get(
            video, lambda x: x['uploader']['name'],
            compat_str) or self._html_search_meta('author', webpage, 'author')

        duration = int_or_none(video.get('duration'))
        view_count = int_or_none(video.get('views'))

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'timestamp': timestamp,
            'uploader': uploader,
            'duration': duration,
            'view_count': view_count,
            'formats': formats,
        }


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

import re

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

class TurboIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?turbo\.fr/videos-voiture/(?P<id>[0-9]+)-'
    _API_URL = 'http://www.turbo.fr/api/tv/xml.php?player_generique=player_generique&id={0:}'
    _TEST = {
        'url': 'http://www.turbo.fr/videos-voiture/454443-turbo-du-07-09-2014-renault-twingo-3-bentley-continental-gt-speed-ces-guide-achat-dacia.html',
        'md5': '33f4b91099b36b5d5a91f84b5bcba600',
        'info_dict': {
            'id': '454443',
            'ext': 'mp4',
            'duration': 3715,
            'title': 'Turbo du 07/09/2014 : Renault Twingo 3, Bentley Continental GT Speed, CES, Guide Achat Dacia... ',
            'description': 'Turbo du 07/09/2014 : Renault Twingo 3, Bentley Continental GT Speed, CES, Guide Achat Dacia...',
            'thumbnail': r're:^https?://.*\.jpg$',
        }
    }

    def _real_extract(self, url):
        mobj = re.match(self._VALID_URL, url)
        video_id = mobj.group('id')

        webpage = self._download_webpage(url, video_id)

        playlist = self._download_xml(self._API_URL.format(video_id), video_id)
        item = playlist.find('./channel/item')
        if item is None:
            raise ExtractorError('Playlist item was not found', expected=True)

        title = xpath_text(item, './title', 'title')
        duration = int_or_none(xpath_text(item, './durate', 'duration'))
        thumbnail = xpath_text(item, './visuel_clip', 'thumbnail')
        description = self._html_search_meta('description', webpage)

        formats = []
        get_quality = qualities(['3g', 'sd', 'hq'])
        for child in item:
            m = re.search(r'url_video_(?P<quality>.+)', child.tag)
            if m:
                quality = compat_str(m.group('quality'))
                formats.append({
                    'format_id': quality,
                    'url': child.text,
                    'quality': get_quality(quality),
                })
        self._sort_formats(formats)

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


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

import re

from .adobepass import AdobePassIE
from ..compat import compat_str
from ..utils import (
    xpath_text,
    int_or_none,
    determine_ext,
    float_or_none,
    parse_duration,
    xpath_attr,
    update_url_query,
    ExtractorError,
    strip_or_none,
    url_or_none,
)

class TurnerBaseIE(AdobePassIE):
    _AKAMAI_SPE_TOKEN_CACHE = {}

    def _extract_timestamp(self, video_data):
        return int_or_none(xpath_attr(video_data, 'dateCreated', 'uts'))

    def _add_akamai_spe_token(self, tokenizer_src, video_url, content_id, ap_data, custom_tokenizer_query=None):
        secure_path = self._search_regex(r'https?://[^/]+(.+/)', video_url, 'secure path') + '*'
        token = self._AKAMAI_SPE_TOKEN_CACHE.get(secure_path)
        if not token:
            query = {
                'path': secure_path,
            }
            if custom_tokenizer_query:
                query.update(custom_tokenizer_query)
            else:
                query['videoId'] = content_id
            if ap_data.get('auth_required'):
                query['accessToken'] = self._extract_mvpd_auth(ap_data['url'], content_id, ap_data['site_name'], ap_data['site_name'])
            auth = self._download_xml(
                tokenizer_src, content_id, query=query)
            error_msg = xpath_text(auth, 'error/msg')
            if error_msg:
                raise ExtractorError(error_msg, expected=True)
            token = xpath_text(auth, 'token')
            if not token:
                return video_url
            self._AKAMAI_SPE_TOKEN_CACHE[secure_path] = token
        return video_url + '?hdnea=' + token

    def _extract_cvp_info(self, data_src, video_id, path_data={}, ap_data={}):
        video_data = self._download_xml(data_src, video_id)
        video_id = video_data.attrib['id']
        title = xpath_text(video_data, 'headline', fatal=True)
        content_id = xpath_text(video_data, 'contentId') or video_id
        # rtmp_src = xpath_text(video_data, 'akamai/src')
        # if rtmp_src:
        #     splited_rtmp_src = rtmp_src.split(',')
        #     if len(splited_rtmp_src) == 2:
        #         rtmp_src = splited_rtmp_src[1]
        # aifp = xpath_text(video_data, 'akamai/aifp', default='')

        urls = []
        formats = []
        rex = re.compile(
            r'(?P<width>[0-9]+)x(?P<height>[0-9]+)(?:_(?P<bitrate>[0-9]+))?')
        # Possible formats locations: files/file, files/groupFiles/files
        # and maybe others
        for video_file in video_data.findall('.//file'):
            video_url = video_file.text.strip()
            if not video_url:
                continue
            ext = determine_ext(video_url)
            if video_url.startswith('/mp4:protected/'):
                continue
                # TODO Correct extraction for these files
                # protected_path_data = path_data.get('protected')
                # if not protected_path_data or not rtmp_src:
                #     continue
                # protected_path = self._search_regex(
                #     r'/mp4:(.+)\.[a-z0-9]', video_url, 'secure path')
                # auth = self._download_webpage(
                #     protected_path_data['tokenizer_src'], query={
                #         'path': protected_path,
                #         'videoId': content_id,
                #         'aifp': aifp,
                #     })
                # token = xpath_text(auth, 'token')
                # if not token:
                #     continue
                # video_url = rtmp_src + video_url + '?' + token
            elif video_url.startswith('/secure/'):
                secure_path_data = path_data.get('secure')
                if not secure_path_data:
                    continue
                video_url = self._add_akamai_spe_token(
                    secure_path_data['tokenizer_src'],
                    secure_path_data['media_src'] + video_url,
                    content_id, ap_data)
            elif not re.match('https?://', video_url):
                base_path_data = path_data.get(ext, path_data.get('default', {}))
                media_src = base_path_data.get('media_src')
                if not media_src:
                    continue
                video_url = media_src + video_url
            if video_url in urls:
                continue
            urls.append(video_url)
            format_id = video_file.get('bitrate')
            if ext == 'smil':
                formats.extend(self._extract_smil_formats(
                    video_url, video_id, fatal=False))
            elif ext == 'm3u8':
                m3u8_formats = self._extract_m3u8_formats(
                    video_url, video_id, 'mp4',
                    m3u8_id=format_id or 'hls', fatal=False)
                if '/secure/' in video_url and '?hdnea=' in video_url:
                    for f in m3u8_formats:
                        f['_seekable'] = False
                formats.extend(m3u8_formats)
            elif ext == 'f4m':
                formats.extend(self._extract_f4m_formats(
                    update_url_query(video_url, {'hdcore': '3.7.0'}),
                    video_id, f4m_id=format_id or 'hds', fatal=False))
            else:
                f = {
                    'format_id': format_id,
                    'url': video_url,
                    'ext': ext,
                }
                mobj = rex.search(format_id + video_url)
                if mobj:
                    f.update({
                        'width': int(mobj.group('width')),
                        'height': int(mobj.group('height')),
                        'tbr': int_or_none(mobj.group('bitrate')),
                    })
                elif isinstance(format_id, compat_str):
                    if format_id.isdigit():
                        f['tbr'] = int(format_id)
                    else:
                        mobj = re.match(r'ios_(audio|[0-9]+)$', format_id)
                        if mobj:
                            if mobj.group(1) == 'audio':
                                f.update({
                                    'vcodec': 'none',
                                    'ext': 'm4a',
                                })
                            else:
                                f['tbr'] = int(mobj.group(1))
                formats.append(f)
        self._sort_formats(formats)

        subtitles = {}
        for source in video_data.findall('closedCaptions/source'):
            for track in source.findall('track'):
                track_url = url_or_none(track.get('url'))
                if not track_url or track_url.endswith('/big'):
                    continue
                lang = track.get('lang') or track.get('label') or 'en'
                subtitles.setdefault(lang, []).append({
                    'url': track_url,
                    'ext': {
                        'scc': 'scc',
                        'webvtt': 'vtt',
                        'smptett': 'tt',
                    }.get(source.get('format'))
                })

        thumbnails = [{
            'id': image.get('cut'),
            'url': image.text,
            'width': int_or_none(image.get('width')),
            'height': int_or_none(image.get('height')),
        } for image in video_data.findall('images/image')]

        is_live = xpath_text(video_data, 'isLive') == 'true'

        return {
            'id': video_id,
            'title': self._live_title(title) if is_live else title,
            'formats': formats,
            'subtitles': subtitles,
            'thumbnails': thumbnails,
            'thumbnail': xpath_text(video_data, 'poster'),
            'description': strip_or_none(xpath_text(video_data, 'description')),
            'duration': parse_duration(xpath_text(video_data, 'length') or xpath_text(video_data, 'trt')),
            'timestamp': self._extract_timestamp(video_data),
            'upload_date': xpath_attr(video_data, 'metas', 'version'),
            'series': xpath_text(video_data, 'showTitle'),
            'season_number': int_or_none(xpath_text(video_data, 'seasonNumber')),
            'episode_number': int_or_none(xpath_text(video_data, 'episodeNumber')),
            'is_live': is_live,
        }

    def _extract_ngtv_info(self, media_id, tokenizer_query, ap_data=None):
        streams_data = self._download_json(
            'http://medium.ngtv.io/media/%s/tv' % media_id,
            media_id)['media']['tv']
        duration = None
        chapters = []
        formats = []
        for supported_type in ('unprotected', 'bulkaes'):
            stream_data = streams_data.get(supported_type, {})
            m3u8_url = stream_data.get('secureUrl') or stream_data.get('url')
            if not m3u8_url:
                continue
            if stream_data.get('playlistProtection') == 'spe':
                m3u8_url = self._add_akamai_spe_token(
                    'http://token.ngtv.io/token/token_spe',
                    m3u8_url, media_id, ap_data or {}, tokenizer_query)
            formats.extend(self._extract_m3u8_formats(
                m3u8_url, media_id, 'mp4', m3u8_id='hls', fatal=False))

            duration = float_or_none(stream_data.get('totalRuntime'))

            if not chapters:
                for chapter in stream_data.get('contentSegments', []):
                    start_time = float_or_none(chapter.get('start'))
                    chapter_duration = float_or_none(chapter.get('duration'))
                    if start_time is None or chapter_duration is None:
                        continue
                    chapters.append({
                        'start_time': start_time,
                        'end_time': start_time + chapter_duration,
                    })
        self._sort_formats(formats)

        return {
            'formats': formats,
            'chapters': chapters,
            'duration': duration,
        }