code:
from __future__ import unicode_literals

import itertools
import re

from .common import InfoExtractor
from ..utils import (
    int_or_none,
    js_to_json,
    orderedSet,
    parse_duration,
    sanitized_Request,
    str_to_int,
)

class XTubeIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                        (?:
                            xtube:|
                            https?://(?:www\.)?xtube\.com/(?:watch\.php\?.*\bv=|video-watch/(?:embedded/)?(?P<display_id>[^/]+)-)
                        )
                        (?P<id>[^/?&#]+)
                    '''

    _TESTS = [{
        # old URL schema
        'url': 'http://www.xtube.com/watch.php?v=kVTUy_G222_',
        'md5': '092fbdd3cbe292c920ef6fc6a8a9cdab',
        'info_dict': {
            'id': 'kVTUy_G222_',
            'ext': 'mp4',
            'title': 'strange erotica',
            'description': 'contains:an ET kind of thing',
            'uploader': 'greenshowers',
            'duration': 450,
            'view_count': int,
            'comment_count': int,
            'age_limit': 18,
        }
    }, {
        # FLV videos with duplicated formats
        'url': 'http://www.xtube.com/video-watch/A-Super-Run-Part-1-YT-9299752',
        'md5': 'a406963eb349dd43692ec54631efd88b',
        'info_dict': {
            'id': '9299752',
            'display_id': 'A-Super-Run-Part-1-YT',
            'ext': 'flv',
            'title': 'A Super Run - Part 1 (YT)',
            'description': 'md5:4cc3af1aa1b0413289babc88f0d4f616',
            'uploader': 'tshirtguy59',
            'duration': 579,
            'view_count': int,
            'comment_count': int,
            'age_limit': 18,
        },
    }, {
        # new URL schema
        'url': 'http://www.xtube.com/video-watch/strange-erotica-625837',
        'only_matching': True,
    }, {
        'url': 'xtube:625837',
        'only_matching': True,
    }, {
        'url': 'xtube:kVTUy_G222_',
        'only_matching': True,
    }, {
        'url': 'https://www.xtube.com/video-watch/embedded/milf-tara-and-teen-shared-and-cum-covered-extreme-bukkake-32203482?embedsize=big',
        'only_matching': True,
    }]

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

        if not display_id:
            display_id = video_id

        if video_id.isdigit() and len(video_id) < 11:
            url_pattern = 'http://www.xtube.com/video-watch/-%s'
        else:
            url_pattern = 'http://www.xtube.com/watch.php?v=%s'

        webpage = self._download_webpage(
            url_pattern % video_id, display_id, headers={
                'Cookie': 'age_verified=1; cookiesAccepted=1',
            })

        title, thumbnail, duration = [None] * 3

        config = self._parse_json(self._search_regex(
            r'playerConf\s*=\s*({.+?})\s*,\s*\n', webpage, 'config',
            default='{}'), video_id, transform_source=js_to_json, fatal=False)
        if config:
            config = config.get('mainRoll')
            if isinstance(config, dict):
                title = config.get('title')
                thumbnail = config.get('poster')
                duration = int_or_none(config.get('duration'))
                sources = config.get('sources') or config.get('format')

        if not isinstance(sources, dict):
            sources = self._parse_json(self._search_regex(
                r'(["\'])?sources\1?\s*:\s*(?P<sources>{.+?}),',
                webpage, 'sources', group='sources'), video_id,
                transform_source=js_to_json)

        formats = []
        for format_id, format_url in sources.items():
            formats.append({
                'url': format_url,
                'format_id': format_id,
                'height': int_or_none(format_id),
            })
        self._remove_duplicate_formats(formats)
        self._sort_formats(formats)

        if not title:
            title = self._search_regex(
                (r'
\s*(?P<title>[^<]+?)\s*
', r'videoTitle\s*:\s*(["\'])(?P<title>.+?)\1'),
                webpage, 'title', group='title')
        description = self._og_search_description(
            webpage, default=None) or self._html_search_meta(
            'twitter:description', webpage, default=None) or self._search_regex(
            r'
\s*
([^<]+)', webpage, 'description', fatal=False)
        uploader = self._search_regex(
            (r'<input[^>]+name="contentOwnerId"[^>]+value="([^"]+)"',
             r']+class="nickname"[^>]*>([^<]+)'),
            webpage, 'uploader', fatal=False)
        if not duration:
            duration = parse_duration(self._search_regex(
                r'
Runtime:?
\s*
([^<]+)
',
                webpage, 'duration', fatal=False))
        view_count = str_to_int(self._search_regex(
            (r'["\']viewsCount["\'][^>]*>(\d+)\s+views',
             r'
Views:?
\s*
([\d,\.]+)
'),
            webpage, 'view count', fatal=False))
        comment_count = str_to_int(self._html_search_regex(
            r'>Comments? \(([\d,\.]+)\)<',
            webpage, 'comment count', fatal=False))

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

class XTubeUserIE(InfoExtractor):
    IE_DESC = 'XTube user profile'
    _VALID_URL = r'https?://(?:www\.)?xtube\.com/profile/(?P<id>[^/]+-\d+)'
    _TEST = {
        'url': 'http://www.xtube.com/profile/greenshowers-4056496',
        'info_dict': {
            'id': 'greenshowers-4056496',
            'age_limit': 18,
        },
        'playlist_mincount': 154,
    }

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

        entries = []
        for pagenum in itertools.count(1):
            request = sanitized_Request(
                'http://www.xtube.com/profile/%s/videos/%d' % (user_id, pagenum),
                headers={
                    'Cookie': 'popunder=4',
                    'X-Requested-With': 'XMLHttpRequest',
                    'Referer': url,
                })

            page = self._download_json(
                request, user_id, 'Downloading videos JSON page %d' % pagenum)

            html = page.get('html')
            if not html:
                break

            for video_id in orderedSet([video_id for _, video_id in re.findall(
                    r'data-plid=(["\'])(.+?)\1', html)]):
                entries.append(self.url_result('xtube:%s' % video_id, XTubeIE.ie_key()))

            page_count = int_or_none(page.get('pageCount'))
            if not page_count or pagenum == page_count:
                break

        playlist = self.playlist_result(entries, user_id)
        playlist['age_limit'] = 18
        return playlist


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

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    float_or_none,
    get_element_by_attribute,
    parse_iso8601,
    remove_end,
)

class XuiteIE(InfoExtractor):
    IE_DESC = '隨意窩Xuite影音'
    _REGEX_BASE64 = r'(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?'
    _VALID_URL = r'https?://vlog\.xuite\.net/(?:play|embed)/(?P<id>%s)' % _REGEX_BASE64
    _TESTS = [{
        # Audio
        'url': 'http://vlog.xuite.net/play/RGkzc1ZULTM4NjA5MTQuZmx2',
        'md5': 'e79284c87b371424885448d11f6398c8',
        'info_dict': {
            'id': '3860914',
            'ext': 'mp3',
            'title': '孤單南半球-歐德陽',
            'description': '孤單南半球-歐德陽',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 247.246,
            'timestamp': 1314932940,
            'upload_date': '20110902',
            'uploader': '阿能',
            'uploader_id': '15973816',
            'categories': ['個人短片'],
        },
    }, {
        # Video with only one format
        'url': 'http://vlog.xuite.net/play/WUxxR2xCLTI1OTI1MDk5LmZsdg==',
        'md5': '21f7b39c009b5a4615b4463df6eb7a46',
        'info_dict': {
            'id': '25925099',
            'ext': 'mp4',
            'title': 'BigBuckBunny_320x180',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 596.458,
            'timestamp': 1454242500,
            'upload_date': '20160131',
            'uploader': '屁姥',
            'uploader_id': '12158353',
            'categories': ['個人短片'],
            'description': 'http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4',
        },
    }, {
        # Video with two formats
        'url': 'http://vlog.xuite.net/play/bWo1N1pLLTIxMzAxMTcwLmZsdg==',
        'md5': '1166e0f461efe55b62e26a2d2a68e6de',
        'info_dict': {
            'id': '21301170',
            'ext': 'mp4',
            'title': '暗殺教室 02',
            'description': '字幕:【極影字幕社】',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 1384.907,
            'timestamp': 1421481240,
            'upload_date': '20150117',
            'uploader': '我只是想認真點',
            'uploader_id': '242127761',
            'categories': ['電玩動漫'],
        },
        'skip': 'Video removed',
    }, {
        # Video with encoded media id
        # from http://forgetfulbc.blogspot.com/2016/06/date.html
        'url': 'http://vlog.xuite.net/embed/cE1xbENoLTI3NDQ3MzM2LmZsdg==?ar=0&as=0',
        'info_dict': {
            'id': '27447336',
            'ext': 'mp4',
            'title': '男女平權只是口號？專家解釋約會時男生是否該幫女生付錢 (中字)',
            'description': 'md5:1223810fa123b179083a3aed53574706',
            'timestamp': 1466160960,
            'upload_date': '20160617',
            'uploader': 'B.C. & Lowy',
            'uploader_id': '232279340',
        },
    }, {
        'url': 'http://vlog.xuite.net/play/S1dDUjdyLTMyOTc3NjcuZmx2/%E5%AD%AB%E7%87%95%E5%A7%BF-%E7%9C%BC%E6%B7%9A%E6%88%90%E8%A9%A9',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        # /play/ URLs provide embedded video URL and more metadata
        url = url.replace('/embed/', '/play/')
        video_id = self._match_id(url)

        webpage = self._download_webpage(url, video_id)

        error_msg = self._search_regex(
            r'<div id="error-message-content">([^<]+)',
            webpage, 'error message', default=None)
        if error_msg:
            raise ExtractorError(
                '%s returned error: %s' % (self.IE_NAME, error_msg),
                expected=True)

        media_info = self._parse_json(self._search_regex(
            r'var\s+mediaInfo\s*=\s*({.*});', webpage, 'media info'), video_id)

        video_id = media_info['MEDIA_ID']

        formats = []
        for key in ('html5Url', 'html5HQUrl'):
            video_url = media_info.get(key)
            if not video_url:
                continue
            format_id = self._search_regex(
                r'\bq=(.+?)\b', video_url, 'format id', default=None)
            formats.append({
                'url': video_url,
                'ext': 'mp4' if format_id.isnumeric() else format_id,
                'format_id': format_id,
                'height': int(format_id) if format_id.isnumeric() else None,
            })
        self._sort_formats(formats)

        timestamp = media_info.get('PUBLISH_DATETIME')
        if timestamp:
            timestamp = parse_iso8601(timestamp + ' +0800', ' ')

        category = media_info.get('catName')
        categories = [category] if category else []

        uploader = media_info.get('NICKNAME')
        uploader_url = None

        author_div = get_element_by_attribute('itemprop', 'author', webpage)
        if author_div:
            uploader = uploader or self._html_search_meta('name', author_div)
            uploader_url = self._html_search_regex(
                r'<link[^>]+itemprop="url"[^>]+href="([^"]+)"', author_div,
                'uploader URL', fatal=False)

        return {
            'id': video_id,
            'title': media_info['TITLE'],
            'description': remove_end(media_info.get('metaDesc'), ' (Xuite 影音)'),
            'thumbnail': media_info.get('ogImageUrl'),
            'timestamp': timestamp,
            'uploader': uploader,
            'uploader_id': media_info.get('MEMBER_ID'),
            'uploader_url': uploader_url,
            'duration': float_or_none(media_info.get('MEDIA_DURATION'), 1000000),
            'categories': categories,
            'formats': formats,
        }

repo_name: rrosajp/youtube


code:
from __future__ import unicode_literals

import re

from .common import InfoExtractor
from ..compat import compat_urllib_parse_unquote
from ..utils import (
    clean_html,
    determine_ext,
    ExtractorError,
    int_or_none,
    parse_duration,
)

class XVideosIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            (?:[^/]+\.)?xvideos2?\.com/video|
                            (?:www\.)?xvideos\.es/video|
                            flashservice\.xvideos\.com/embedframe/|
                            static-hw\.xvideos\.com/swf/xv-player\.swf\?.*?\bid_video=
                        )
                        (?P<id>[0-9]+)
                    '''
    _TESTS = [{
        'url': 'http://www.xvideos.com/video4588838/biker_takes_his_girl',
        'md5': '14cea69fcb84db54293b1e971466c2e1',
        'info_dict': {
            'id': '4588838',
            'ext': 'mp4',
            'title': 'Biker Takes his Girl',
            'duration': 108,
            'age_limit': 18,
        }
    }, {
        'url': 'https://flashservice.xvideos.com/embedframe/4588838',
        'only_matching': True,
    }, {
        'url': 'http://static-hw.xvideos.com/swf/xv-player.swf?id_video=4588838',
        'only_matching': True,
    }, {
        'url': 'http://xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'https://xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'https://xvideos.es/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'https://www.xvideos.es/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'http://xvideos.es/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'http://www.xvideos.es/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'http://fr.xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'https://fr.xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'http://it.xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'https://it.xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'http://de.xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }, {
        'url': 'https://de.xvideos.com/video4588838/biker_takes_his_girl',
        'only_matching': True
    }]

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

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

        mobj = re.search(r'
(.+?)
', webpage)
        if mobj:
            raise ExtractorError('%s said: %s' % (self.IE_NAME, clean_html(mobj.group(1))), expected=True)

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

        thumbnails = []
        for preference, thumbnail in enumerate(('', '169')):
            thumbnail_url = self._search_regex(
                r'setThumbUrl%s\(\s*(["\'])(?P<thumbnail>(?:(?!\1).)+)\1' % thumbnail,
                webpage, 'thumbnail', default=None, group='thumbnail')
            if thumbnail_url:
                thumbnails.append({
                    'url': thumbnail_url,
                    'preference': preference,
                })

        duration = int_or_none(self._og_search_property(
            'duration', webpage, default=None)) or parse_duration(
            self._search_regex(
                r']+class=["\']duration["\'][^>]*>.*?(\d[^<]+)',
                webpage, 'duration', fatal=False))

        formats = []

        video_url = compat_urllib_parse_unquote(self._search_regex(
            r'flv_url=(.+?)&', webpage, 'video URL', default=''))
        if video_url:
            formats.append({
                'url': video_url,
                'format_id': 'flv',
            })

        for kind, _, format_url in re.findall(
                r'setVideo([^(]+)\((["\'])(http.+?)\2\)', webpage):
            format_id = kind.lower()
            if format_id == 'hls':
                formats.extend(self._extract_m3u8_formats(
                    format_url, video_id, 'mp4',
                    entry_protocol='m3u8_native', m3u8_id='hls', fatal=False))
            elif format_id in ('urllow', 'urlhigh'):
                formats.append({
                    'url': format_url,
                    'format_id': '%s-%s' % (determine_ext(format_url, 'mp4'), format_id[3:]),
                    'quality': -2 if format_id.endswith('low') else None,
                })

        self._sort_formats(formats)

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


code:
from __future__ import unicode_literals

import re

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

class XXXYMoviesIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?xxxymovies\.com/videos/(?P<id>\d+)/(?P<display_id>[^/]+)'
    _TEST = {
        'url': 'http://xxxymovies.com/videos/138669/ecstatic-orgasm-sofcore/',
        'md5': '810b1bdbbffff89dd13bdb369fe7be4b',
        'info_dict': {
            'id': '138669',
            'display_id': 'ecstatic-orgasm-sofcore',
            'ext': 'mp4',
            'title': 'Ecstatic Orgasm Sofcore',
            'duration': 931,
            'categories': list,
            'view_count': int,
            'like_count': int,
            'dislike_count': int,
            'age_limit': 18,
        }
    }

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

        webpage = self._download_webpage(url, display_id)

        video_url = self._search_regex(
            r"video_url\s*:\s*'([^']+)'", webpage, 'video URL')

        title = self._html_search_regex(
            [r'
]+\bclass="block_header"[^>]*>\s*
([^<]+)<',
             r'<title>(.*?)\s*-\s*(?:XXXYMovies\.com|XXX\s+Movies)</title>'],
            webpage, 'title')

        thumbnail = self._search_regex(
            r"preview_url\s*:\s*'([^']+)'",
            webpage, 'thumbnail', fatal=False)

        categories = self._html_search_meta(
            'keywords', webpage, 'categories', default='').split(',')

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

        view_count = int_or_none(self._html_search_regex(
            r'
\s*(\d+)',
            webpage, 'view count', fatal=False))
        like_count = int_or_none(self._search_regex(
            r'>\s*Likes? \((\d+)\)',
            webpage, 'like count', fatal=False))
        dislike_count = int_or_none(self._search_regex(
            r'>\s*Dislike \((\d+)\)',
            webpage, 'dislike count', fatal=False))

        age_limit = self._rta_search(webpage)

        return {
            'id': video_id,
            'display_id': display_id,
            'url': video_url,
            'title': title,
            'thumbnail': thumbnail,
            'categories': categories,
            'duration': duration,
            'view_count': view_count,
            'like_count': like_count,
            'dislike_count': dislike_count,
            'age_limit': age_limit,
        }


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

import hashlib
import itertools
import re

from .common import InfoExtractor, SearchInfoExtractor
from ..compat import (
    compat_str,
    compat_urllib_parse,
)
from ..utils import (
    clean_html,
    ExtractorError,
    int_or_none,
    mimetype2ext,
    parse_iso8601,
    smuggle_url,
    try_get,
    url_or_none,
)

from .brightcove import BrightcoveNewIE

class YahooIE(InfoExtractor):
    IE_DESC = 'Yahoo screen and movies'
    _VALID_URL = r'(?P<url>https?://(?:(?P<country>[a-zA-Z]{2}(?:-[a-zA-Z]{2})?|malaysia)\.)?(?:[\da-zA-Z_-]+\.)?yahoo\.com/(?:[^/]+/)*(?P<id>[^?&#]*-[0-9]+(?:-[a-z]+)?)\.html)'
    _TESTS = [{
        'url': 'http://screen.yahoo.com/julian-smith-travis-legg-watch-214727115.html',
        'info_dict': {
            'id': '2d25e626-2378-391f-ada0-ddaf1417e588',
            'ext': 'mp4',
            'title': 'Julian Smith & Travis Legg Watch Julian Smith',
            'description': 'Julian and Travis watch Julian Smith',
            'duration': 6863,
            'timestamp': 1369812016,
            'upload_date': '20130529',
        },
    }, {
        'url': 'https://screen.yahoo.com/community/community-sizzle-reel-203225340.html?format=embed',
        'md5': '7993e572fac98e044588d0b5260f4352',
        'info_dict': {
            'id': '4fe78544-8d48-39d8-97cd-13f205d9fcdb',
            'ext': 'mp4',
            'title': "Yahoo Saves 'Community'",
            'description': 'md5:4d4145af2fd3de00cbb6c1d664105053',
            'duration': 170,
            'timestamp': 1406838636,
            'upload_date': '20140731',
        },
    }, {
        'url': 'https://uk.screen.yahoo.com/editor-picks/cute-raccoon-freed-drain-using-091756545.html',
        'md5': '71298482f7c64cbb7fa064e4553ff1c1',
        'info_dict': {
            'id': 'b3affa53-2e14-3590-852b-0e0db6cd1a58',
            'ext': 'webm',
            'title': 'Cute Raccoon Freed From Drain\u00a0Using Angle Grinder',
            'description': 'md5:f66c890e1490f4910a9953c941dee944',
            'duration': 97,
            'timestamp': 1414489862,
            'upload_date': '20141028',
        }
    }, {
        'url': 'http://news.yahoo.com/video/china-moses-crazy-blues-104538833.html',
        'md5': '88e209b417f173d86186bef6e4d1f160',
        'info_dict': {
            'id': 'f885cf7f-43d4-3450-9fac-46ac30ece521',
            'ext': 'mp4',
            'title': 'China Moses Is Crazy About the Blues',
            'description': 'md5:9900ab8cd5808175c7b3fe55b979bed0',
            'duration': 128,
            'timestamp': 1385722202,
            'upload_date': '20131129',
        }
    }, {
        'url': 'https://www.yahoo.com/movies/v/true-story-trailer-173000497.html',
        'md5': '2a9752f74cb898af5d1083ea9f661b58',
        'info_dict': {
            'id': '071c4013-ce30-3a93-a5b2-e0413cd4a9d1',
            'ext': 'mp4',
            'title': '\'True Story\' Trailer',
            'description': 'True Story',
            'duration': 150,
            'timestamp': 1418919206,
            'upload_date': '20141218',
        },
    }, {
        'url': 'https://gma.yahoo.com/pizza-delivery-man-surprised-huge-tip-college-kids-195200785.html',
        'only_matching': True,
    }, {
        'note': 'NBC Sports embeds',
        'url': 'http://sports.yahoo.com/blogs/ncaab-the-dagger/tyler-kalinoski-s-buzzer-beater-caps-davidson-s-comeback-win-185609842.html?guid=nbc_cbk_davidsonbuzzerbeater_150313',
        'info_dict': {
            'id': '9CsDKds0kvHI',
            'ext': 'flv',
            'description': 'md5:df390f70a9ba7c95ff1daace988f0d8d',
            'title': 'Tyler Kalinoski hits buzzer-beater to lift Davidson',
            'upload_date': '20150313',
            'uploader': 'NBCU-SPORTS',
            'timestamp': 1426270238,
        },
    }, {
        'url': 'https://tw.news.yahoo.com/-100120367.html',
        'only_matching': True,
    }, {
        # Query result is embedded in webpage, but explicit request to video API fails with geo restriction
        'url': 'https://screen.yahoo.com/community/communitary-community-episode-1-ladders-154501237.html',
        'md5': '4fbafb9c9b6f07aa8f870629f6671b35',
        'info_dict': {
            'id': '1f32853c-a271-3eef-8cb6-f6d6872cb504',
            'ext': 'mp4',
            'title': 'Communitary - Community Episode 1: Ladders',
            'description': 'md5:8fc39608213295748e1e289807838c97',
            'duration': 1646,
            'timestamp': 1440436550,
            'upload_date': '20150824',
            'series': 'Communitary',
            'season_number': 6,
            'episode_number': 1,
        },
    }, {
        # ytwnews://cavideo/
        'url': 'https://tw.video.yahoo.com/movie-tw/單車天使-中文版預-092316541.html',
        'info_dict': {
            'id': 'ba133ff2-0793-3510-b636-59dfe9ff6cff',
            'ext': 'mp4',
            'title': '單車天使 - 中文版預',
            'description': '中文版預',
            'timestamp': 1476696196,
            'upload_date': '20161017',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # Contains both a Yahoo hosted video and multiple Youtube embeds
        'url': 'https://www.yahoo.com/entertainment/gwen-stefani-reveals-the-pop-hit-she-passed-on-assigns-it-to-her-voice-contestant-instead-033045672.html',
        'info_dict': {
            'id': '46c5d95a-528f-3d03-b732-732fcadd51de',
            'title': 'Gwen Stefani reveals the pop hit she passed on, assigns it to her \'Voice\' contestant instead',
            'description': 'Gwen decided not to record this hit herself, but she decided it was the perfect fit for Kyndall Inskeep.',
        },
        'playlist': [{
            'info_dict': {
                'id': '966d4262-4fd1-3aaa-b45b-049ca6e38ba6',
                'ext': 'mp4',
                'title': 'Gwen Stefani reveals she turned down one of Sia\'s best songs',
                'description': 'On "The Voice" Tuesday, Gwen Stefani told Taylor Swift which Sia hit was almost hers.',
                'timestamp': 1572406500,
                'upload_date': '20191030',
            },
        }, {
            'info_dict': {
                'id': '352CFDOQrKg',
                'ext': 'mp4',
                'title': 'Kyndal Inskeep "Performs the Hell Out of" Sia\'s "Elastic Heart" - The Voice Knockouts 2019',
                'description': 'md5:35b61e94c2ae214bc965ff4245f80d11',
                'uploader': 'The Voice',
                'uploader_id': 'NBCTheVoice',
                'upload_date': '20191029',
            },
        }],
        'params': {
            'playlistend': 2,
        },
        'expected_warnings': ['HTTP Error 404'],
    }, {
        'url': 'https://malaysia.news.yahoo.com/video/bystanders-help-ontario-policeman-bust-190932818.html',
        'only_matching': True,
    }, {
        'url': 'https://es-us.noticias.yahoo.com/es-la-puerta-irrompible-que-110539379.html',
        'only_matching': True,
    }, {
        'url': 'https://www.yahoo.com/entertainment/v/longtime-cbs-news-60-minutes-032036500-cbs.html',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        url, country, display_id = re.match(self._VALID_URL, url).groups()
        if not country:
            country = 'us'
        else:
            country = country.split('-')[0]
        api_base = 'https://%s.yahoo.com/_td/api/resource/' % country

        for i, uuid in enumerate(['url=' + url, 'ymedia-alias=' + display_id]):
            content = self._download_json(
                api_base + 'content;getDetailView=true;uuids=["%s"]' % uuid,
                display_id, 'Downloading content JSON metadata', fatal=i == 1)
            if content:
                item = content['items'][0]
                break

        if item.get('type') != 'video':
            entries = []

            cover = item.get('cover') or {}
            if cover.get('type') == 'yvideo':
                cover_url = cover.get('url')
                if cover_url:
                    entries.append(self.url_result(
                        cover_url, 'Yahoo', cover.get('uuid')))

            for e in item.get('body', []):
                if e.get('type') == 'videoIframe':
                    iframe_url = e.get('url')
                    if not iframe_url:
                        continue
                    entries.append(self.url_result(iframe_url))

            return self.playlist_result(
                entries, item.get('uuid'),
                item.get('title'), item.get('summary'))

        video_id = item['uuid']
        video = self._download_json(
            api_base + 'VideoService.videos;view=full;video_ids=["%s"]' % video_id,
            video_id, 'Downloading video JSON metadata')[0]
        title = video['title']

        if country == 'malaysia':
            country = 'my'

        is_live = video.get('live_state') == 'live'
        fmts = ('m3u8',) if is_live else ('webm', 'mp4')

        urls = []
        formats = []
        subtitles = {}
        for fmt in fmts:
            media_obj = self._download_json(
                'https://video-api.yql.yahoo.com/v1/video/sapi/streams/' + video_id,
                video_id, 'Downloading %s JSON metadata' % fmt,
                headers=self.geo_verification_headers(), query={
                    'format': fmt,
                    'region': country.upper(),
                })['query']['results']['mediaObj'][0]
            msg = media_obj.get('status', {}).get('msg')

            for s in media_obj.get('streams', []):
                host = s.get('host')
                path = s.get('path')
                if not host or not path:
                    continue
                s_url = host + path
                if s.get('format') == 'm3u8':
                    formats.extend(self._extract_m3u8_formats(
                        s_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
                    continue
                tbr = int_or_none(s.get('bitrate'))
                formats.append({
                    'url': s_url,
                    'format_id': fmt + ('-%d' % tbr if tbr else ''),
                    'width': int_or_none(s.get('width')),
                    'height': int_or_none(s.get('height')),
                    'tbr': tbr,
                    'fps': int_or_none(s.get('framerate')),
                })

            for cc in media_obj.get('closedcaptions', []):
                cc_url = cc.get('url')
                if not cc_url or cc_url in urls:
                    continue
                urls.append(cc_url)
                subtitles.setdefault(cc.get('lang') or 'en-US', []).append({
                    'url': cc_url,
                    'ext': mimetype2ext(cc.get('content_type')),
                })

        streaming_url = video.get('streaming_url')
        if streaming_url and not is_live:
            formats.extend(self._extract_m3u8_formats(
                streaming_url, video_id, 'mp4',
                'm3u8_native', m3u8_id='hls', fatal=False))

        if not formats and msg == 'geo restricted':
            self.raise_geo_restricted()

        self._sort_formats(formats)

        thumbnails = []
        for thumb in video.get('thumbnails', []):
            thumb_url = thumb.get('url')
            if not thumb_url:
                continue
            thumbnails.append({
                'id': thumb.get('tag'),
                'url': thumb.get('url'),
                'width': int_or_none(thumb.get('width')),
                'height': int_or_none(thumb.get('height')),
            })

        series_info = video.get('series_info') or {}

        return {
            'id': video_id,
            'title': self._live_title(title) if is_live else title,
            'formats': formats,
            'display_id': display_id,
            'thumbnails': thumbnails,
            'description': clean_html(video.get('description')),
            'timestamp': parse_iso8601(video.get('publish_time')),
            'subtitles': subtitles,
            'duration': int_or_none(video.get('duration')),
            'view_count': int_or_none(video.get('view_count')),
            'is_live': is_live,
            'series': video.get('show_name'),
            'season_number': int_or_none(series_info.get('season_number')),
            'episode_number': int_or_none(series_info.get('episode_number')),
        }

class YahooSearchIE(SearchInfoExtractor):
    IE_DESC = 'Yahoo screen search'
    _MAX_RESULTS = 1000
    IE_NAME = 'screen.yahoo:search'
    _SEARCH_KEY = 'yvsearch'

    def _get_n_results(self, query, n):
        """Get a specified number of results for a query"""
        entries = []
        for pagenum in itertools.count(0):
            result_url = 'http://video.search.yahoo.com/search/?p=%s&fr=screen&o=js&gs=0&b=%d' % (compat_urllib_parse.quote_plus(query), pagenum * 30)
            info = self._download_json(result_url, query,
                                       note='Downloading results page ' + str(pagenum + 1))
            m = info['m']
            results = info['results']

            for (i, r) in enumerate(results):
                if (pagenum * 30) + i >= n:
                    break
                mobj = re.search(r'(?P<url>screen\.yahoo\.com/.*?-\d*?\.html)"', r)
                e = self.url_result('http://' + mobj.group('url'), 'Yahoo')
                entries.append(e)
            if (pagenum * 30 + i >= n) or (m['last'] >= (m['total'] - 1)):
                break

        return {
            '_type': 'playlist',
            'id': query,
            'entries': entries,
        }

class YahooGyaOPlayerIE(InfoExtractor):
    IE_NAME = 'yahoo:gyao:player'
    _VALID_URL = r'https?://(?:gyao\.yahoo\.co\.jp/(?:player|episode/[^/]+)|streaming\.yahoo\.co\.jp/c/y)/(?P<id>\d+/v\d+/v\d+|[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
    _TESTS = [{
        'url': 'https://gyao.yahoo.co.jp/player/00998/v00818/v0000000000000008564/',
        'info_dict': {
            'id': '5993125228001',
            'ext': 'mp4',
            'title': 'フューリー　【字幕版】',
            'description': 'md5:21e691c798a15330eda4db17a8fe45a5',
            'uploader_id': '4235717419001',
            'upload_date': '20190124',
            'timestamp': 1548294365,
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }, {
        'url': 'https://streaming.yahoo.co.jp/c/y/01034/v00133/v0000000000000000706/',
        'only_matching': True,
    }, {
        'url': 'https://gyao.yahoo.co.jp/episode/%E3%81%8D%E3%81%AE%E3%81%86%E4%BD%95%E9%A3%9F%E3%81%B9%E3%81%9F%EF%BC%9F%20%E7%AC%AC2%E8%A9%B1%202019%2F4%2F12%E6%94%BE%E9%80%81%E5%88%86/5cb02352-b725-409e-9f8d-88f947a9f682',
        'only_matching': True,
    }]
    _GEO_BYPASS = False

    def _real_extract(self, url):
        video_id = self._match_id(url).replace('/', ':')
        headers = self.geo_verification_headers()
        headers['Accept'] = 'application/json'
        resp = self._download_json(
            'https://gyao.yahoo.co.jp/apis/playback/graphql', video_id, query={
                'appId': 'dj00aiZpPUNJeDh2cU1RazU3UCZzPWNvbnN1bWVyc2VjcmV0Jng9NTk-',
                'query': '''{
  content(parameter: {contentId: "%s", logicaAgent: PC_WEB}) {
    video {
      delivery {
        id
      }
      title
    }
  }
}''' % video_id,
            }, headers=headers)
        content = resp['data']['content']
        if not content:
            msg = resp['errors'][0]['message']
            if msg == 'not in japan':
                self.raise_geo_restricted(countries=['JP'])
            raise ExtractorError(msg)
        video = content['video']
        return {
            '_type': 'url_transparent',
            'id': video_id,
            'title': video['title'],
            'url': smuggle_url(
                'http://players.brightcove.net/4235717419001/SyG5P0gjb_default/index.html?videoId=' + video['delivery']['id'],
                {'geo_countries': ['JP']}),
            'ie_key': BrightcoveNewIE.ie_key(),
        }

class YahooGyaOIE(InfoExtractor):
    IE_NAME = 'yahoo:gyao'
    _VALID_URL = r'https?://(?:gyao\.yahoo\.co\.jp/(?:p|title(?:/[^/]+)?)|streaming\.yahoo\.co\.jp/p/y)/(?P<id>\d+/v\d+|[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})'
    _TESTS = [{
        'url': 'https://gyao.yahoo.co.jp/p/00449/v03102/',
        'info_dict': {
            'id': '00449:v03102',
        },
        'playlist_count': 2,
    }, {
        'url': 'https://streaming.yahoo.co.jp/p/y/01034/v00133/',
        'only_matching': True,
    }, {
        'url': 'https://gyao.yahoo.co.jp/title/%E3%81%97%E3%82%83%E3%81%B9%E3%81%8F%E3%82%8A007/5b025a49-b2e5-4dc7-945c-09c6634afacf',
        'only_matching': True,
    }, {
        'url': 'https://gyao.yahoo.co.jp/title/5b025a49-b2e5-4dc7-945c-09c6634afacf',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        program_id = self._match_id(url).replace('/', ':')
        videos = self._download_json(
            'https://gyao.yahoo.co.jp/api/programs/%s/videos' % program_id, program_id)['videos']
        entries = []
        for video in videos:
            video_id = video.get('id')
            if not video_id:
                continue
            entries.append(self.url_result(
                'https://gyao.yahoo.co.jp/player/%s/' % video_id.replace(':', '/'),
                YahooGyaOPlayerIE.ie_key(), video_id))
        return self.playlist_result(entries, program_id)

class YahooJapanNewsIE(InfoExtractor):
    IE_NAME = 'yahoo:japannews'
    IE_DESC = 'Yahoo! Japan News'
    _VALID_URL = r'https?://(?P<host>(?:news|headlines)\.yahoo\.co\.jp)[^\d]*(?P<id>\d[\d-]*\d)?'
    _GEO_COUNTRIES = ['JP']
    _TESTS = [{
        'url': 'https://headlines.yahoo.co.jp/videonews/ann?a=20190716-00000071-ann-int',
        'info_dict': {
            'id': '1736242',
            'ext': 'mp4',
            'title': 'ムン大統領が対日批判を強化“現金化”効果は？（テレビ朝日系（ANN）） - Yahoo!ニュース',
            'description': '韓国の元徴用工らを巡る裁判の原告が弁護士が差し押さえた三菱重工業の資産を売却して - Yahoo!ニュース(テレビ朝日系（ANN）)',
            'thumbnail': r're:^https?://.*\.[a-zA-Z\d]{3,4}$',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        # geo restricted
        'url': 'https://headlines.yahoo.co.jp/hl?a=20190721-00000001-oxv-l04',
        'only_matching': True,
    }, {
        'url': 'https://headlines.yahoo.co.jp/videonews/',
        'only_matching': True,
    }, {
        'url': 'https://news.yahoo.co.jp',
        'only_matching': True,
    }, {
        'url': 'https://news.yahoo.co.jp/byline/hashimotojunji/20190628-00131977/',
        'only_matching': True,
    }, {
        'url': 'https://news.yahoo.co.jp/feature/1356',
        'only_matching': True
    }]

    def _extract_formats(self, json_data, content_id):
        formats = []

        video_data = try_get(
            json_data,
            lambda x: x['ResultSet']['Result'][0]['VideoUrlSet']['VideoUrl'],
            list)
        for vid in video_data or []:
            delivery = vid.get('delivery')
            url = url_or_none(vid.get('Url'))
            if not delivery or not url:
                continue
            elif delivery == 'hls':
                formats.extend(
                    self._extract_m3u8_formats(
                        url, content_id, 'mp4', 'm3u8_native',
                        m3u8_id='hls', fatal=False))
            else:
                formats.append({
                    'url': url,
                    'format_id': 'http-%s' % compat_str(vid.get('bitrate', '')),
                    'height': int_or_none(vid.get('height')),
                    'width': int_or_none(vid.get('width')),
                    'tbr': int_or_none(vid.get('bitrate')),
                })
        self._remove_duplicate_formats(formats)
        self._sort_formats(formats)

        return formats

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

        webpage = self._download_webpage(url, display_id)

        title = self._html_search_meta(
            ['og:title', 'twitter:title'], webpage, 'title', default=None
        ) or self._html_search_regex('<title>([^<]+)</title>', webpage, 'title')

        if display_id == host:
            # Headline page (w/ multiple BC playlists) ('news.yahoo.co.jp', 'headlines.yahoo.co.jp/videonews/', ...)
            stream_plists = re.findall(r'plist=(\d+)', webpage) or re.findall(r'plist["\']:\s*["\']([^"\']+)', webpage)
            entries = [
                self.url_result(
                    smuggle_url(
                        'http://players.brightcove.net/5690807595001/HyZNerRl7_default/index.html?playlistId=%s' % plist_id,
                        {'geo_countries': ['JP']}),
                    ie='BrightcoveNew', video_id=plist_id)
                for plist_id in stream_plists]
            return self.playlist_result(entries, playlist_title=title)

        # Article page
        description = self._html_search_meta(
            ['og:description', 'description', 'twitter:description'],
            webpage, 'description', default=None)
        thumbnail = self._og_search_thumbnail(
            webpage, default=None) or self._html_search_meta(
            'twitter:image', webpage, 'thumbnail', default=None)
        space_id = self._search_regex([
            r'<script[^>]+class=["\']yvpub-player["\'][^>]+spaceid=([^&"\']+)',
            r'YAHOO\.JP\.srch\.\w+link\.onLoad[^;]+spaceID["\' ]*:["\' ]+([^"\']+)',
            r'<!--\s+SpaceID=(\d+)'
        ], webpage, 'spaceid')

        content_id = self._search_regex(
            r'<script[^>]+class=["\']yvpub-player["\'][^>]+contentid=(?P<contentid>[^&"\']+)',
            webpage, 'contentid', group='contentid')

        json_data = self._download_json(
            'https://feapi-yvpub.yahooapis.jp/v1/content/%s' % content_id,
            content_id,
            query={
                'appid': 'dj0zaiZpPVZMTVFJR0FwZWpiMyZzPWNvbnN1bWVyc2VjcmV0Jng9YjU-',
                'output': 'json',
                'space_id': space_id,
                'domain': host,
                'ak': hashlib.md5('_'.join((space_id, host)).encode()).hexdigest(),
                'device_type': '1100',
            })
        formats = self._extract_formats(json_data, content_id)

        return {
            'id': content_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'formats': formats,
        }


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

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    determine_ext,
    float_or_none,
    int_or_none,
    try_get,
    urlencode_postdata,
)

class YandexDiskIE(InfoExtractor):
    _VALID_URL = r'https?://yadi\.sk/[di]/(?P<id>[^/?#&]+)'

    _TESTS = [{
        'url': 'https://yadi.sk/i/VdOeDou8eZs6Y',
        'md5': '33955d7ae052f15853dc41f35f17581c',
        'info_dict': {
            'id': 'VdOeDou8eZs6Y',
            'ext': 'mp4',
            'title': '4.mp4',
            'duration': 168.6,
            'uploader': 'y.botova',
            'uploader_id': '300043621',
            'view_count': int,
        },
    }, {
        'url': 'https://yadi.sk/d/h3WAXvDS3Li3Ce',
        'only_matching': True,
    }]

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

        status = self._download_webpage(
            'https://disk.yandex.com/auth/status', video_id, query={
                'urlOrigin': url,
                'source': 'public',
                'md5': 'false',
            })

        sk = self._search_regex(
            r'(["\'])sk(?:External)?\1\s*:\s*(["\'])(?P<value>(?:(?!\2).)+)\2',
            status, 'sk', group='value')

        webpage = self._download_webpage(url, video_id)

        models = self._parse_json(
            self._search_regex(
                r'<script[^>]+id=["\']models-client[^>]+>\s*(\[.+?\])\s*</script',
                webpage, 'video JSON'),
            video_id)

        data = next(
            model['data'] for model in models
            if model.get('model') == 'resource')

        video_hash = data['id']
        title = data['name']

        models = self._download_json(
            'https://disk.yandex.com/models/', video_id,
            data=urlencode_postdata({
                '_model.0': 'videoInfo',
                'id.0': video_hash,
                '_model.1': 'do-get-resource-url',
                'id.1': video_hash,
                'version': '13.6',
                'sk': sk,
            }), query={'_m': 'videoInfo'})['models']

        videos = try_get(models, lambda x: x[0]['data']['videos'], list) or []
        source_url = try_get(
            models, lambda x: x[1]['data']['file'], compat_str)

        formats = []
        if source_url:
            formats.append({
                'url': source_url,
                'format_id': 'source',
                'ext': determine_ext(title, 'mp4'),
                'quality': 1,
            })
        for video in videos:
            format_url = video.get('url')
            if not format_url:
                continue
            if determine_ext(format_url) == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    format_url, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            else:
                formats.append({
                    'url': format_url,
                })
        self._sort_formats(formats)

        duration = float_or_none(try_get(
            models, lambda x: x[0]['data']['duration']), 1000)
        uploader = try_get(
            data, lambda x: x['user']['display_name'], compat_str)
        uploader_id = try_get(
            data, lambda x: x['user']['uid'], compat_str)
        view_count = int_or_none(try_get(
            data, lambda x: x['meta']['views_counter']))

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


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

import re
import hashlib

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

class YandexMusicBaseIE(InfoExtractor):
    @staticmethod
    def _handle_error(response):
        if isinstance(response, dict):
            error = response.get('error')
            if error:
                raise ExtractorError(error, expected=True)
            if response.get('type') == 'captcha' or 'captcha' in response:
                YandexMusicBaseIE._raise_captcha()

    @staticmethod
    def _raise_captcha():
        raise ExtractorError(
            'YandexMusic has considered youtube-dl requests automated and '
            'asks you to solve a CAPTCHA. You can either wait for some '
            'time until unblocked and optionally use --sleep-interval '
            'in future or alternatively you can go to https://music.yandex.ru/ '
            'solve CAPTCHA, then export cookies and pass cookie file to '
            'youtube-dl with --cookies',
            expected=True)

    def _download_webpage_handle(self, *args, **kwargs):
        webpage = super(YandexMusicBaseIE, self)._download_webpage_handle(*args, **kwargs)
        if 'Нам очень жаль, но запросы, поступившие с вашего IP-адреса, похожи на автоматические.' in webpage:
            self._raise_captcha()
        return webpage

    def _download_json(self, *args, **kwargs):
        response = super(YandexMusicBaseIE, self)._download_json(*args, **kwargs)
        self._handle_error(response)
        return response

class YandexMusicTrackIE(YandexMusicBaseIE):
    IE_NAME = 'yandexmusic:track'
    IE_DESC = 'Яндекс.Музыка - Трек'
    _VALID_URL = r'https?://music\.yandex\.(?:ru|kz|ua|by)/album/(?P<album_id>\d+)/track/(?P<id>\d+)'

    _TESTS = [{
        'url': 'http://music.yandex.ru/album/540508/track/4878838',
        'md5': 'f496818aa2f60b6c0062980d2e00dc20',
        'info_dict': {
            'id': '4878838',
            'ext': 'mp3',
            'title': 'Carlo Ambrosio & Fabio Di Bari - Gypsy Eyes 1',
            'filesize': 4628061,
            'duration': 193.04,
            'track': 'Gypsy Eyes 1',
            'album': 'Gypsy Soul',
            'album_artist': 'Carlo Ambrosio',
            'artist': 'Carlo Ambrosio & Fabio Di Bari',
            'release_year': 2009,
        },
        'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        # multiple disks
        'url': 'http://music.yandex.ru/album/3840501/track/705105',
        'md5': 'ebe7b4e2ac7ac03fe11c19727ca6153e',
        'info_dict': {
            'id': '705105',
            'ext': 'mp3',
            'title': 'Hooverphonic - Sometimes',
            'filesize': 5743386,
            'duration': 239.27,
            'track': 'Sometimes',
            'album': 'The Best of Hooverphonic',
            'album_artist': 'Hooverphonic',
            'artist': 'Hooverphonic',
            'release_year': 2016,
            'genre': 'pop',
            'disc_number': 2,
            'track_number': 9,
        },
        'skip': 'Travis CI servers blocked by YandexMusic',
    }]

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

        track = self._download_json(
            'http://music.yandex.ru/handlers/track.jsx?track=%s:%s' % (track_id, album_id),
            track_id, 'Downloading track JSON')['track']
        track_title = track['title']

        download_data = self._download_json(
            'https://music.yandex.ru/api/v2.1/handlers/track/%s:%s/web-album_track-track-track-main/download/m' % (track_id, album_id),
            track_id, 'Downloading track location url JSON',
            headers={'X-Retpath-Y': url})

        fd_data = self._download_json(
            download_data['src'], track_id,
            'Downloading track location JSON',
            query={'format': 'json'})
        key = hashlib.md5(('XGRlBW9FXlekgbPrRHuSiA' + fd_data['path'][1:] + fd_data['s']).encode('utf-8')).hexdigest()
        storage = track['storageDir'].split('.')
        f_url = 'http://%s/get-mp3/%s/%s?track-id=%s ' % (fd_data['host'], key, fd_data['ts'] + fd_data['path'], storage[1])

        thumbnail = None
        cover_uri = track.get('albums', [{}])[0].get('coverUri')
        if cover_uri:
            thumbnail = cover_uri.replace('%%', 'orig')
            if not thumbnail.startswith('http'):
                thumbnail = 'http://' + thumbnail

        track_info = {
            'id': track_id,
            'ext': 'mp3',
            'url': f_url,
            'filesize': int_or_none(track.get('fileSize')),
            'duration': float_or_none(track.get('durationMs'), 1000),
            'thumbnail': thumbnail,
            'track': track_title,
            'acodec': download_data.get('codec'),
            'abr': int_or_none(download_data.get('bitrate')),
        }

        def extract_artist_name(artist):
            decomposed = artist.get('decomposed')
            if not isinstance(decomposed, list):
                return artist['name']
            parts = [artist['name']]
            for element in decomposed:
                if isinstance(element, dict) and element.get('name'):
                    parts.append(element['name'])
                elif isinstance(element, compat_str):
                    parts.append(element)
            return ''.join(parts)

        def extract_artist(artist_list):
            if artist_list and isinstance(artist_list, list):
                artists_names = [extract_artist_name(a) for a in artist_list if a.get('name')]
                if artists_names:
                    return ', '.join(artists_names)

        albums = track.get('albums')
        if albums and isinstance(albums, list):
            album = albums[0]
            if isinstance(album, dict):
                year = album.get('year')
                disc_number = int_or_none(try_get(
                    album, lambda x: x['trackPosition']['volume']))
                track_number = int_or_none(try_get(
                    album, lambda x: x['trackPosition']['index']))
                track_info.update({
                    'album': album.get('title'),
                    'album_artist': extract_artist(album.get('artists')),
                    'release_year': int_or_none(year),
                    'genre': album.get('genre'),
                    'disc_number': disc_number,
                    'track_number': track_number,
                })

        track_artist = extract_artist(track.get('artists'))
        if track_artist:
            track_info.update({
                'artist': track_artist,
                'title': '%s - %s' % (track_artist, track_title),
            })
        else:
            track_info['title'] = track_title

        return track_info

class YandexMusicPlaylistBaseIE(YandexMusicBaseIE):
    def _build_playlist(self, tracks):
        return [
            self.url_result(
                'http://music.yandex.ru/album/%s/track/%s' % (track['albums'][0]['id'], track['id']))
            for track in tracks if track.get('albums') and isinstance(track.get('albums'), list)]

class YandexMusicAlbumIE(YandexMusicPlaylistBaseIE):
    IE_NAME = 'yandexmusic:album'
    IE_DESC = 'Яндекс.Музыка - Альбом'
    _VALID_URL = r'https?://music\.yandex\.(?:ru|kz|ua|by)/album/(?P<id>\d+)/?(\?|$)'

    _TESTS = [{
        'url': 'http://music.yandex.ru/album/540508',
        'info_dict': {
            'id': '540508',
            'title': 'Carlo Ambrosio - Gypsy Soul (2009)',
        },
        'playlist_count': 50,
        'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        'url': 'https://music.yandex.ru/album/3840501',
        'info_dict': {
            'id': '3840501',
            'title': 'Hooverphonic - The Best of Hooverphonic (2016)',
        },
        'playlist_count': 33,
        'skip': 'Travis CI servers blocked by YandexMusic',
    }]

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

        album = self._download_json(
            'http://music.yandex.ru/handlers/album.jsx?album=%s' % album_id,
            album_id, 'Downloading album JSON')

        entries = self._build_playlist([track for volume in album['volumes'] for track in volume])

        title = '%s - %s' % (album['artists'][0]['name'], album['title'])
        year = album.get('year')
        if year:
            title += ' (%s)' % year

        return self.playlist_result(entries, compat_str(album['id']), title)

class YandexMusicPlaylistIE(YandexMusicPlaylistBaseIE):
    IE_NAME = 'yandexmusic:playlist'
    IE_DESC = 'Яндекс.Музыка - Плейлист'
    _VALID_URL = r'https?://music\.yandex\.(?P<tld>ru|kz|ua|by)/users/(?P<user>[^/]+)/playlists/(?P<id>\d+)'

    _TESTS = [{
        'url': 'http://music.yandex.ru/users/music.partners/playlists/1245',
        'info_dict': {
            'id': '1245',
            'title': 'Что слушают Enter Shikari',
            'description': 'md5:3b9f27b0efbe53f2ee1e844d07155cc9',
        },
        'playlist_count': 6,
        'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        # playlist exceeding the limit of 150 tracks shipped with webpage (see
        # https://github.com/ytdl-org/youtube-dl/issues/6666)
        'url': 'https://music.yandex.ru/users/ya.playlist/playlists/1036',
        'info_dict': {
            'id': '1036',
            'title': 'Музыка 90-х',
        },
        'playlist_mincount': 300,
        'skip': 'Travis CI servers blocked by YandexMusic',
    }]

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

        playlist = self._download_json(
            'https://music.yandex.%s/handlers/playlist.jsx' % tld,
            playlist_id, 'Downloading missing tracks JSON',
            fatal=False,
            headers={
                'Referer': url,
                'X-Requested-With': 'XMLHttpRequest',
                'X-Retpath-Y': url,
            },
            query={
                'owner': user,
                'kinds': playlist_id,
                'light': 'true',
                'lang': tld,
                'external-domain': 'music.yandex.%s' % tld,
                'overembed': 'false',
            })['playlist']

        tracks = playlist['tracks']
        track_ids = [compat_str(track_id) for track_id in playlist['trackIds']]

        # tracks dictionary shipped with playlist.jsx API is limited to 150 tracks,
        # missing tracks should be retrieved manually.
        if len(tracks) < len(track_ids):
            present_track_ids = set([
                compat_str(track['id'])
                for track in tracks if track.get('id')])
            missing_track_ids = [
                track_id for track_id in track_ids
                if track_id not in present_track_ids]
            missing_tracks = self._download_json(
                'https://music.yandex.%s/handlers/track-entries.jsx' % tld,
                playlist_id, 'Downloading missing tracks JSON',
                fatal=False,
                headers={
                    'Referer': url,
                    'X-Requested-With': 'XMLHttpRequest',
                },
                query={
                    'entries': ','.join(missing_track_ids),
                    'lang': tld,
                    'external-domain': 'music.yandex.%s' % tld,
                    'overembed': 'false',
                    'strict': 'true',
                })
            if missing_tracks:
                tracks.extend(missing_tracks)

        return self.playlist_result(
            self._build_playlist(tracks),
            compat_str(playlist_id),
            playlist.get('title'), playlist.get('description'))


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

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

class YandexVideoIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            yandex\.ru(?:/portal/(?:video|efir))?/?\?.*?stream_id=|
                            frontend\.vh\.yandex\.ru/player/
                        )
                        (?P<id>[\da-f]+)
                    '''
    _TESTS = [{
        'url': 'https://yandex.ru/portal/video?stream_id=4dbb262b4fe5cf15a215de4f34eee34d',
        'md5': '33955d7ae052f15853dc41f35f17581c',
        'info_dict': {
            'id': '4dbb262b4fe5cf15a215de4f34eee34d',
            'ext': 'mp4',
            'title': 'В Нью-Йорке баржи и теплоход оторвались от причала и расплылись по Гудзону',
            'description': '',
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 0,
            'duration': 30,
            'age_limit': 18,
        },
    }, {
        'url': 'https://yandex.ru/portal/efir?stream_id=4dbb36ec4e0526d58f9f2dc8f0ecf374&from=morda',
        'only_matching': True,
    }, {
        'url': 'https://yandex.ru/?stream_id=4dbb262b4fe5cf15a215de4f34eee34d',
        'only_matching': True,
    }, {
        'url': 'https://frontend.vh.yandex.ru/player/4dbb262b4fe5cf15a215de4f34eee34d?from=morda',
        'only_matching': True,
    }, {
        # vod-episode, series episode
        'url': 'https://yandex.ru/portal/video?stream_id=45b11db6e4b68797919c93751a938cee',
        'only_matching': True,
    }, {
        # episode, sports
        'url': 'https://yandex.ru/?stream_channel=1538487871&stream_id=4132a07f71fb0396be93d74b3477131d',
        'only_matching': True,
    }, {
        # DASH with DRM
        'url': 'https://yandex.ru/portal/video?from=morda&stream_id=485a92d94518d73a9d0ff778e13505f8',
        'only_matching': True,
    }]

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

        content = self._download_json(
            'https://frontend.vh.yandex.ru/v22/player/%s.json' % video_id,
            video_id, query={
                'stream_options': 'hires',
                'disable_trackings': 1,
            })['content']

        content_url = url_or_none(content.get('content_url')) or url_or_none(
            content['streams'][0]['url'])
        title = content.get('title') or content.get('computed_title')

        ext = determine_ext(content_url)

        if ext == 'm3u8':
            formats = self._extract_m3u8_formats(
                content_url, video_id, 'mp4', entry_protocol='m3u8_native',
                m3u8_id='hls')
        elif ext == 'mpd':
            formats = self._extract_mpd_formats(
                content_url, video_id, mpd_id='dash')
        else:
            formats = [{'url': content_url}]

        self._sort_formats(formats)

        description = content.get('description')
        thumbnail = content.get('thumbnail')
        timestamp = (int_or_none(content.get('release_date'))
                     or int_or_none(content.get('release_date_ut'))
                     or int_or_none(content.get('start_time')))
        duration = int_or_none(content.get('duration'))
        series = content.get('program_title')
        age_limit = int_or_none(content.get('restriction_age'))

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


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

import re

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

class YapFilesIE(InfoExtractor):
    _YAPFILES_URL = r'//(?:(?:www|api)\.)?yapfiles\.ru/get_player/*\?.*?\bv=(?P<id>\w+)'
    _VALID_URL = r'https?:%s' % _YAPFILES_URL
    _TESTS = [{
        # with hd
        'url': 'http://www.yapfiles.ru/get_player/?v=vMDE1NjcyNDUt0413',
        'md5': '2db19e2bfa2450568868548a1aa1956c',
        'info_dict': {
            'id': 'vMDE1NjcyNDUt0413',
            'ext': 'mp4',
            'title': 'Самый худший пароль WIFI',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 72,
        },
    }, {
        # without hd
        'url': 'https://api.yapfiles.ru/get_player/?uid=video_player_1872528&plroll=1&adv=1&v=vMDE4NzI1Mjgt690b',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_urls(webpage):
        return [unescapeHTML(mobj.group('url')) for mobj in re.finditer(
            r'<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?%s.*?)\1'
            % YapFilesIE._YAPFILES_URL, webpage)]

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

        webpage = self._download_webpage(url, video_id, fatal=False)

        player_url = None
        query = {}
        if webpage:
            player_url = self._search_regex(
                r'player\.init\s*\(\s*(["\'])(?P<url>(?:(?!\1).)+)\1', webpage,
                'player url', default=None, group='url')

        if not player_url:
            player_url = 'http://api.yapfiles.ru/load/%s/' % video_id
            query = {
                'md5': 'ded5f369be61b8ae5f88e2eeb2f3caff',
                'type': 'json',
                'ref': url,
            }

        player = self._download_json(
            player_url, video_id, query=query)['player']

        playlist_url = player['playlist']
        title = player['title']
        thumbnail = player.get('poster')

        if title == 'Ролик удален' or 'deleted.jpg' in (thumbnail or ''):
            raise ExtractorError(
                'Video %s has been removed' % video_id, expected=True)

        playlist = self._download_json(
            playlist_url, video_id)['player']['main']

        hd_height = int_or_none(player.get('hd'))

        QUALITIES = ('sd', 'hd')
        quality_key = qualities(QUALITIES)
        formats = []
        for format_id in QUALITIES:
            is_hd = format_id == 'hd'
            format_url = url_or_none(playlist.get(
                'file%s' % ('_hd' if is_hd else '')))
            if not format_url:
                continue
            formats.append({
                'url': format_url,
                'format_id': format_id,
                'quality': quality_key(format_id),
                'height': hd_height if is_hd else None,
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'thumbnail': thumbnail,
            'duration': int_or_none(player.get('length')),
            'formats': formats,
        }


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

from .common import InfoExtractor
from ..utils import (
    HEADRequest,
    get_element_by_attribute,
    parse_iso8601,
)

class YesJapanIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?yesjapan\.com/video/(?P<slug>[A-Za-z0-9\-]*)_(?P<id>[A-Za-z0-9]+)\.html'
    _TEST = {
        'url': 'http://www.yesjapan.com/video/japanese-in-5-20-wa-and-ga-particle-usages_726497834.html',
        'md5': 'f0be416314e5be21a12b499b330c21cf',
        'info_dict': {
            'id': '726497834',
            'title': 'Japanese in 5! #20 - WA And GA Particle Usages',
            'description': 'This should clear up some issues most students of Japanese encounter with WA and GA....',
            'ext': 'mp4',
            'timestamp': 1416391590,
            'upload_date': '20141119',
            'thumbnail': r're:^https?://.*\.jpg$',
        }
    }

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

        webpage = self._download_webpage(url, video_id)
        title = self._og_search_title(webpage)
        video_url = self._og_search_video_url(webpage)
        description = self._og_search_description(webpage)
        thumbnail = self._og_search_thumbnail(webpage)

        timestamp = None
        submit_info = get_element_by_attribute('class', 'pm-submit-data', webpage)
        if submit_info:
            timestamp = parse_iso8601(self._search_regex(
                r'datetime="([^"]+)"', submit_info, 'upload date', fatal=False, default=None))

        # attempt to resolve the final URL in order to get a proper extension
        redirect_req = HEADRequest(video_url)
        req = self._request_webpage(
            redirect_req, video_id, note='Resolving final URL', errnote='Could not resolve final URL', fatal=False)
        if req:
            video_url = req.geturl()

        formats = [{
            'format_id': 'sd',
            'url': video_url,
        }]

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


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

from .common import InfoExtractor
from ..utils import ExtractorError

class YinYueTaiIE(InfoExtractor):
    IE_NAME = 'yinyuetai:video'
    IE_DESC = '音悦Tai'
    _VALID_URL = r'https?://v\.yinyuetai\.com/video(?:/h5)?/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://v.yinyuetai.com/video/2322376',
        'md5': '6e3abe28d38e3a54b591f9f040595ce0',
        'info_dict': {
            'id': '2322376',
            'ext': 'mp4',
            'title': '少女时代_PARTY_Music Video Teaser',
            'creator': '少女时代',
            'duration': 25,
            'thumbnail': r're:^https?://.*\.jpg$',
        },
    }, {
        'url': 'http://v.yinyuetai.com/video/h5/2322376',
        'only_matching': True,
    }]

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

        info = self._download_json(
            'http://ext.yinyuetai.com/main/get-h-mv-info?json=true&videoId=%s' % video_id, video_id,
            'Downloading mv info')['videoInfo']['coreVideoInfo']

        if info['error']:
            raise ExtractorError(info['errorMsg'], expected=True)

        formats = [{
            'url': format_info['videoUrl'],
            'format_id': format_info['qualityLevel'],
            'format': format_info.get('qualityLevelName'),
            'filesize': format_info.get('fileSize'),
            # though URLs ends with .flv, the downloaded files are in fact mp4
            'ext': 'mp4',
            'tbr': format_info.get('bitrate'),
        } for format_info in info['videoUrlModels']]
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': info['videoName'],
            'thumbnail': info.get('bigHeadImage'),
            'creator': info.get('artistNames'),
            'duration': info.get('duration'),
            'formats': formats,
        }


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

import re
import json

from .common import InfoExtractor
from ..compat import compat_urllib_parse_unquote_plus

class YnetIE(InfoExtractor):
    _VALID_URL = r'https?://(?:.+?\.)?ynet\.co\.il/(?:.+?/)?0,7340,(?P<id>L(?:-[0-9]+)+),00\.html'
    _TESTS = [
        {
            'url': 'http://hot.ynet.co.il/home/0,7340,L-11659-99244,00.html',
            'info_dict': {
                'id': 'L-11659-99244',
                'ext': 'flv',
                'title': 'איש לא יודע מאיפה באנו',
                'thumbnail': r're:^https?://.*\.jpg',
            }
        }, {
            'url': 'http://hot.ynet.co.il/home/0,7340,L-8859-84418,00.html',
            'info_dict': {
                'id': 'L-8859-84418',
                'ext': 'flv',
                'title': "צפו: הנשיקה הלוהטת של תורגי' ויוליה פלוטקין",
                'thumbnail': r're:^https?://.*\.jpg',
            }
        }
    ]

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

        content = compat_urllib_parse_unquote_plus(self._og_search_video_url(webpage))
        config = json.loads(self._search_regex(r'config=({.+?})$', content, 'video config'))
        f4m_url = config['clip']['url']
        title = self._og_search_title(webpage)
        m = re.search(r'ynet - HOT -- (["\']+)(?P<title>.+?)\1', title)
        if m:
            title = m.group('title')
        formats = self._extract_f4m_formats(f4m_url, video_id)
        self._sort_formats(formats)

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


code:
from __future__ import unicode_literals

import re

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

class YouJizzIE(InfoExtractor):
    _VALID_URL = r'https?://(?:\w+\.)?youjizz\.com/videos/(?:[^/#?]*-(?P<id>\d+)\.html|embed/(?P<embed_id>\d+))'
    _TESTS = [{
        'url': 'http://www.youjizz.com/videos/zeichentrick-1-2189178.html',
        'md5': 'b1e1dfaa8bb9537d8b84eeda9cf4acf4',
        'info_dict': {
            'id': '2189178',
            'ext': 'mp4',
            'title': 'Zeichentrick 1',
            'age_limit': 18,
            'duration': 2874,
        }
    }, {
        'url': 'http://www.youjizz.com/videos/-2189178.html',
        'only_matching': True,
    }, {
        'url': 'https://www.youjizz.com/videos/embed/31991001',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        title = self._html_search_regex(
            r'<title>(.+?)</title>', webpage, 'title')

        formats = []

        encodings = self._parse_json(
            self._search_regex(
                r'[Ee]ncodings\s*=\s*(\[.+?\]);\n', webpage, 'encodings',
                default='[]'),
            video_id, fatal=False)
        for encoding in encodings:
            if not isinstance(encoding, dict):
                continue
            format_url = url_or_none(encoding.get('filename'))
            if not format_url:
                continue
            if determine_ext(format_url) == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    format_url, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            else:
                format_id = encoding.get('name') or encoding.get('quality')
                height = int_or_none(self._search_regex(
                    r'^(\d+)[pP]', format_id, 'height', default=None))
                formats.append({
                    'url': format_url,
                    'format_id': format_id,
                    'height': height,
                })

        if formats:
            info_dict = {
                'formats': formats,
            }
        else:
            # YouJizz's HTML5 player has invalid HTML
            webpage = webpage.replace('"controls', '" controls')
            info_dict = self._parse_html5_media_entries(
                url, webpage, video_id)[0]

        duration = parse_duration(self._search_regex(
            r'Runtime:([^<]+)', webpage, 'duration',
            default=None))
        uploader = self._search_regex(
            r'Uploaded By:.*?]*>([^<]+)', webpage, 'uploader',
            default=None)

        info_dict.update({
            'id': video_id,
            'title': title,
            'age_limit': self._rta_search(webpage),
            'duration': duration,
            'uploader': uploader,
        })

        return info_dict


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

import random
import re
import string
import time

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    get_element_by_class,
    js_to_json,
    str_or_none,
    strip_jsonp,
)

class YoukuIE(InfoExtractor):
    IE_NAME = 'youku'
    IE_DESC = '优酷'
    _VALID_URL = r'''(?x)
        (?:
            https?://(
                (?:v|player)\.youku\.com/(?:v_show/id_|player\.php/sid/)|
                video\.tudou\.com/v/)|
            youku:)
        (?P<id>[A-Za-z0-9]+)(?:\.html|/v\.swf|)
    '''

    _TESTS = [{
        # MD5 is unstable
        'url': 'http://v.youku.com/v_show/id_XMTc1ODE5Njcy.html',
        'info_dict': {
            'id': 'XMTc1ODE5Njcy',
            'title': '★Smile﹗♡ Git Fresh -Booty Music舞蹈.',
            'ext': 'mp4',
            'duration': 74.73,
            'thumbnail': r're:^https?://.*',
            'uploader': '。躲猫猫、',
            'uploader_id': '36017967',
            'uploader_url': 'http://i.youku.com/u/UMTQ0MDcxODY4',
            'tags': list,
        }
    }, {
        'url': 'http://player.youku.com/player.php/sid/XNDgyMDQ2NTQw/v.swf',
        'only_matching': True,
    }, {
        'url': 'http://v.youku.com/v_show/id_XODgxNjg1Mzk2_ev_1.html',
        'info_dict': {
            'id': 'XODgxNjg1Mzk2',
            'ext': 'mp4',
            'title': '武媚娘传奇 85',
            'duration': 1999.61,
            'thumbnail': r're:^https?://.*',
            'uploader': '疯狂豆花',
            'uploader_id': '62583473',
            'uploader_url': 'http://i.youku.com/u/UMjUwMzMzODky',
            'tags': list,
        },
    }, {
        'url': 'http://v.youku.com/v_show/id_XMTI1OTczNDM5Mg==.html',
        'info_dict': {
            'id': 'XMTI1OTczNDM5Mg',
            'ext': 'mp4',
            'title': '花千骨 04',
            'duration': 2363,
            'thumbnail': r're:^https?://.*',
            'uploader': '放剧场-花千骨',
            'uploader_id': '772849359',
            'uploader_url': 'http://i.youku.com/u/UMzA5MTM5NzQzNg==',
            'tags': list,
        },
    }, {
        'url': 'http://v.youku.com/v_show/id_XNjA1NzA2Njgw.html',
        'note': 'Video protected with password',
        'info_dict': {
            'id': 'XNjA1NzA2Njgw',
            'ext': 'mp4',
            'title': '邢義田复旦讲座之想象中的胡人—从“左衽孔子”说起',
            'duration': 7264.5,
            'thumbnail': r're:^https?://.*',
            'uploader': 'FoxJin1006',
            'uploader_id': '322014285',
            'uploader_url': 'http://i.youku.com/u/UMTI4ODA1NzE0MA==',
            'tags': list,
        },
        'params': {
            'videopassword': '100600',
        },
    }, {
        # /play/get.json contains streams with "channel_type":"tail"
        'url': 'http://v.youku.com/v_show/id_XOTUxMzg4NDMy.html',
        'info_dict': {
            'id': 'XOTUxMzg4NDMy',
            'ext': 'mp4',
            'title': '我的世界☆明月庄主☆车震猎杀☆杀人艺术Minecraft',
            'duration': 702.08,
            'thumbnail': r're:^https?://.*',
            'uploader': '明月庄主moon',
            'uploader_id': '38465621',
            'uploader_url': 'http://i.youku.com/u/UMTUzODYyNDg0',
            'tags': list,
        },
    }, {
        'url': 'http://video.tudou.com/v/XMjIyNzAzMTQ4NA==.html?f=46177805',
        'info_dict': {
            'id': 'XMjIyNzAzMTQ4NA',
            'ext': 'mp4',
            'title': '卡马乔国足开大脚长传冲吊集锦',
            'duration': 289,
            'thumbnail': r're:^https?://.*',
            'uploader': '阿卜杜拉之星',
            'uploader_id': '2382249',
            'uploader_url': 'http://i.youku.com/u/UOTUyODk5Ng==',
            'tags': list,
        },
    }, {
        'url': 'http://video.tudou.com/v/XMjE4ODI3OTg2MA==.html',
        'only_matching': True,
    }]

    @staticmethod
    def get_ysuid():
        return '%d%s' % (int(time.time()), ''.join([
            random.choice(string.ascii_letters) for i in range(3)]))

    def get_format_name(self, fm):
        _dict = {
            '3gp': 'h6',
            '3gphd': 'h5',
            'flv': 'h4',
            'flvhd': 'h4',
            'mp4': 'h3',
            'mp4hd': 'h3',
            'mp4hd2': 'h4',
            'mp4hd3': 'h4',
            'hd2': 'h2',
            'hd3': 'h1',
        }
        return _dict.get(fm)

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

        self._set_cookie('youku.com', '__ysuid', self.get_ysuid())
        self._set_cookie('youku.com', 'xreferrer', 'http://www.youku.com')

        _, urlh = self._download_webpage_handle(
            'https://log.mmstat.com/eg.js', video_id, 'Retrieving cna info')
        # The etag header is '"foobar"'; let's remove the double quotes
        cna = urlh.headers['etag'][1:-1]

        # request basic data
        basic_data_params = {
            'vid': video_id,
            'ccode': '0590',
            'client_ip': '192.168.1.1',
            'utid': cna,
            'client_ts': time.time() / 1000,
        }

        video_password = self._downloader.params.get('videopassword')
        if video_password:
            basic_data_params['password'] = video_password

        headers = {
            'Referer': url,
        }
        headers.update(self.geo_verification_headers())
        data = self._download_json(
            'https://ups.youku.com/ups/get.json', video_id,
            'Downloading JSON metadata',
            query=basic_data_params, headers=headers)['data']

        error = data.get('error')
        if error:
            error_note = error.get('note')
            if error_note is not None and '因版权原因无法观看此视频' in error_note:
                raise ExtractorError(
                    'Youku said: Sorry, this video is available in China only', expected=True)
            elif error_note and '该视频被设为私密' in error_note:
                raise ExtractorError(
                    'Youku said: Sorry, this video is private', expected=True)
            else:
                msg = 'Youku server reported error %i' % error.get('code')
                if error_note is not None:
                    msg += ': ' + error_note
                raise ExtractorError(msg)

        # get video title
        video_data = data['video']
        title = video_data['title']

        formats = [{
            'url': stream['m3u8_url'],
            'format_id': self.get_format_name(stream.get('stream_type')),
            'ext': 'mp4',
            'protocol': 'm3u8_native',
            'filesize': int(stream.get('size')),
            'width': stream.get('width'),
            'height': stream.get('height'),
        } for stream in data['stream'] if stream.get('channel_type') != 'tail']
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'duration': video_data.get('seconds'),
            'thumbnail': video_data.get('logo'),
            'uploader': video_data.get('username'),
            'uploader_id': str_or_none(video_data.get('userid')),
            'uploader_url': data.get('uploader', {}).get('homepage'),
            'tags': video_data.get('tags'),
        }

class YoukuShowIE(InfoExtractor):
    _VALID_URL = r'https?://list\.youku\.com/show/id_(?P<id>[0-9a-z]+)\.html'
    IE_NAME = 'youku:show'

    _TESTS = [{
        'url': 'http://list.youku.com/show/id_zc7c670be07ff11e48b3f.html',
        'info_dict': {
            'id': 'zc7c670be07ff11e48b3f',
            'title': '花千骨 DVD版',
            'description': 'md5:a1ae6f5618571bbeb5c9821f9c81b558',
        },
        'playlist_count': 50,
    }, {
        # Episode number not starting from 1
        'url': 'http://list.youku.com/show/id_zefbfbd70efbfbd780bef.html',
        'info_dict': {
            'id': 'zefbfbd70efbfbd780bef',
            'title': '超级飞侠3',
            'description': 'md5:275715156abebe5ccc2a1992e9d56b98',
        },
        'playlist_count': 24,
    }, {
        # Ongoing playlist. The initial page is the last one
        'url': 'http://list.youku.com/show/id_za7c275ecd7b411e1a19e.html',
        'only_matching': True,
    }, {
        #  No data-id value.
        'url': 'http://list.youku.com/show/id_zefbfbd61237fefbfbdef.html',
        'only_matching': True,
    }, {
        #  Wrong number of reload_id.
        'url': 'http://list.youku.com/show/id_z20eb4acaf5c211e3b2ad.html',
        'only_matching': True,
    }]

    def _extract_entries(self, playlist_data_url, show_id, note, query):
        query['callback'] = 'cb'
        playlist_data = self._download_json(
            playlist_data_url, show_id, query=query, note=note,
            transform_source=lambda s: js_to_json(strip_jsonp(s))).get('html')
        if playlist_data is None:
            return [None, None]
        drama_list = (get_element_by_class('p-drama-grid', playlist_data)
                      or get_element_by_class('p-drama-half-row', playlist_data))
        if drama_list is None:
            raise ExtractorError('No episodes found')
        video_urls = re.findall(r'<a[^>]+href="([^"]+)"', drama_list)
        return playlist_data, [
            self.url_result(self._proto_relative_url(video_url, 'http:'), YoukuIE.ie_key())
            for video_url in video_urls]

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

        entries = []
        page_config = self._parse_json(self._search_regex(
            r'var\s+PageConfig\s*=\s*({.+});', webpage, 'page config'),
            show_id, transform_source=js_to_json)
        first_page, initial_entries = self._extract_entries(
            'http://list.youku.com/show/module', show_id,
            note='Downloading initial playlist data page',
            query={
                'id': page_config['showid'],
                'tab': 'showInfo',
            })
        first_page_reload_id = self._html_search_regex(
            r'<div[^>]+id="(reload_\d+)', first_page, 'first page reload id')
        # The first reload_id has the same items as first_page
        reload_ids = re.findall('<li[^>]+data-id="([^"]+)">', first_page)
        entries.extend(initial_entries)
        for idx, reload_id in enumerate(reload_ids):
            if reload_id == first_page_reload_id:
                continue
            _, new_entries = self._extract_entries(
                'http://list.youku.com/show/episode', show_id,
                note='Downloading playlist data page %d' % (idx + 1),
                query={
                    'id': page_config['showid'],
                    'stage': reload_id,
                })
            if new_entries is not None:
                entries.extend(new_entries)
        desc = self._html_search_meta('description', webpage, fatal=False)
        playlist_title = desc.split(',')[0] if desc else None
        detail_li = get_element_by_class('p-intro', webpage)
        playlist_description = get_element_by_class(
            'intro-more', detail_li) if detail_li else None

        return self.playlist_result(
            entries, show_id, playlist_title, playlist_description)


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

import itertools

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

CDN_API_BASE = 'https://cdn.younow.com/php/api'
MOMENT_URL_FORMAT = '%s/moment/fetch/id=%%s' % CDN_API_BASE

class YouNowLiveIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?younow\.com/(?P<id>[^/?#&]+)'
    _TEST = {
        'url': 'https://www.younow.com/AmandaPadeezy',
        'info_dict': {
            'id': 'AmandaPadeezy',
            'ext': 'mp4',
            'is_live': True,
            'title': 'March 26, 2017',
            'thumbnail': r're:^https?://.*\.jpg$',
            'tags': ['girls'],
            'categories': ['girls'],
            'uploader': 'AmandaPadeezy',
            'uploader_id': '6716501',
            'uploader_url': 'https://www.younow.com/AmandaPadeezy',
            'creator': 'AmandaPadeezy',
        },
        'skip': True,
    }

    @classmethod
    def suitable(cls, url):
        return (False
                if YouNowChannelIE.suitable(url) or YouNowMomentIE.suitable(url)
                else super(YouNowLiveIE, cls).suitable(url))

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

        data = self._download_json(
            'https://api.younow.com/php/api/broadcast/info/curId=0/user=%s'
            % username, username)

        if data.get('errorCode') != 0:
            raise ExtractorError(data['errorMsg'], expected=True)

        uploader = try_get(
            data, lambda x: x['user']['profileUrlString'],
            compat_str) or username

        return {
            'id': uploader,
            'is_live': True,
            'title': self._live_title(uploader),
            'thumbnail': data.get('awsUrl'),
            'tags': data.get('tags'),
            'categories': data.get('tags'),
            'uploader': uploader,
            'uploader_id': data.get('userId'),
            'uploader_url': 'https://www.younow.com/%s' % username,
            'creator': uploader,
            'view_count': int_or_none(data.get('viewers')),
            'like_count': int_or_none(data.get('likes')),
            'formats': [{
                'url': '%s/broadcast/videoPath/hls=1/broadcastId=%s/channelId=%s'
                       % (CDN_API_BASE, data['broadcastId'], data['userId']),
                'ext': 'mp4',
                'protocol': 'm3u8',
            }],
        }

def _extract_moment(item, fatal=True):
    moment_id = item.get('momentId')
    if not moment_id:
        if not fatal:
            return
        raise ExtractorError('Unable to extract moment id')

    moment_id = compat_str(moment_id)

    title = item.get('text')
    if not title:
        title = 'YouNow %s' % (
            item.get('momentType') or item.get('titleType') or 'moment')

    uploader = try_get(item, lambda x: x['owner']['name'], compat_str)
    uploader_id = try_get(item, lambda x: x['owner']['userId'])
    uploader_url = 'https://www.younow.com/%s' % uploader if uploader else None

    entry = {
        'extractor_key': 'YouNowMoment',
        'id': moment_id,
        'title': title,
        'view_count': int_or_none(item.get('views')),
        'like_count': int_or_none(item.get('likes')),
        'timestamp': int_or_none(item.get('created')),
        'creator': uploader,
        'uploader': uploader,
        'uploader_id': uploader_id,
        'uploader_url': uploader_url,
        'formats': [{
            'url': 'https://hls.younow.com/momentsplaylists/live/%s/%s.m3u8'
                   % (moment_id, moment_id),
            'ext': 'mp4',
            'protocol': 'm3u8_native',
        }],
    }

    return entry

class YouNowChannelIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?younow\.com/(?P<id>[^/]+)/channel'
    _TEST = {
        'url': 'https://www.younow.com/its_Kateee_/channel',
        'info_dict': {
            'id': '14629760',
            'title': 'its_Kateee_ moments'
        },
        'playlist_mincount': 8,
    }

    def _entries(self, username, channel_id):
        created_before = 0
        for page_num in itertools.count(1):
            if created_before is None:
                break
            info = self._download_json(
                '%s/moment/profile/channelId=%s/createdBefore=%d/records=20'
                % (CDN_API_BASE, channel_id, created_before), username,
                note='Downloading moments page %d' % page_num)
            items = info.get('items')
            if not items or not isinstance(items, list):
                break
            for item in items:
                if not isinstance(item, dict):
                    continue
                item_type = item.get('type')
                if item_type == 'moment':
                    entry = _extract_moment(item, fatal=False)
                    if entry:
                        yield entry
                elif item_type == 'collection':
                    moments = item.get('momentsIds')
                    if isinstance(moments, list):
                        for moment_id in moments:
                            m = self._download_json(
                                MOMENT_URL_FORMAT % moment_id, username,
                                note='Downloading %s moment JSON' % moment_id,
                                fatal=False)
                            if m and isinstance(m, dict) and m.get('item'):
                                entry = _extract_moment(m['item'])
                                if entry:
                                    yield entry
                created_before = int_or_none(item.get('created'))

    def _real_extract(self, url):
        username = self._match_id(url)
        channel_id = compat_str(self._download_json(
            'https://api.younow.com/php/api/broadcast/info/curId=0/user=%s'
            % username, username, note='Downloading user information')['userId'])
        return self.playlist_result(
            self._entries(username, channel_id), channel_id,
            '%s moments' % username)

class YouNowMomentIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?younow\.com/[^/]+/(?P<id>[^/?#&]+)'
    _TEST = {
        'url': 'https://www.younow.com/GABO.../20712117/36319236/3b316doc/m',
        'md5': 'a30c70eadb9fb39a1aa3c8c0d22a0807',
        'info_dict': {
            'id': '20712117',
            'ext': 'mp4',
            'title': 'YouNow capture',
            'view_count': int,
            'like_count': int,
            'timestamp': 1490432040,
            'upload_date': '20170325',
            'uploader': 'GABO...',
            'uploader_id': 35917228,
        },
    }

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

    def _real_extract(self, url):
        video_id = self._match_id(url)
        item = self._download_json(MOMENT_URL_FORMAT % video_id, video_id)
        return _extract_moment(item['item'])