code:
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,
        }


code:
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|
                            (?:www|flashservice)\.xvideos\.com/embedframe/|
                            static-hw\.xvideos\.com/swf/xv-player\.swf\?.*?\bid_video=
                        )
                        (?P<id>[0-9]+)
                    '''
    _TESTS = [{
        'url': 'https://www.xvideos.com/video4588838/motorcycle_guy_cucks_influencer_steals_his_gf',
        'md5': '14cea69fcb84db54293b1e971466c2e1',
        'info_dict': {
            'id': '4588838',
            'ext': 'mp4',
            'title': 'Motorcycle Guy Cucks Influencer, Steals his GF',
            'duration': 108,
            'age_limit': 18,
            'thumbnail': r're:^https://img-hw.xvideos-cdn.com/.+\.jpg',
        }
    }, {
        # Broken HLS formats
        'url': 'https://www.xvideos.com/video65982001/what_s_her_name',
        'md5': 'b82d7d7ef7d65a84b1fa6965f81f95a5',
        'info_dict': {
            'id': '65982001',
            'ext': 'mp4',
            'title': 'what\'s her name?',
            'duration': 120,
            'age_limit': 18,
            'thumbnail': r're:^https://img-hw.xvideos-cdn.com/.+\.jpg',
        }
    }, {
        'url': 'https://flashservice.xvideos.com/embedframe/4588838',
        'only_matching': True,
    }, {
        'url': 'https://www.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(url, 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':
                hls_formats = self._extract_m3u8_formats(
                    format_url, video_id, 'mp4',
                    entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)
                self._check_formats(hls_formats, video_id)
                formats.extend(hls_formats)
            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,
        }

repo_name: Lesmiscore/ytdl


code:
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 = self._match_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:
import hashlib
import itertools
import urllib.parse

from .brightcove import BrightcoveNewIE
from .common import InfoExtractor, SearchInfoExtractor
from .youtube import YoutubeIE
from ..utils import (
    ExtractorError,
    clean_html,
    int_or_none,
    mimetype2ext,
    parse_iso8601,
    smuggle_url,
    traverse_obj,
    try_get,
    url_or_none,
)

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)'
    _EMBED_REGEX = [r'<iframe[^>]+?src=(["\'])(?P<url>https?://(?:screen|movies)\.yahoo\.com/.+?\.html\?format=embed)\1']

    _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',
        },
        'skip': 'No longer exists',
    }, {
        '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',
        },
        'skip': 'Unfortunately, this video is not available in your region',
    }, {
        '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',
        },
        'skip': 'No longer exists',
    }, {
        '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,
        },
        'skip': 'No longer exists',
    }, {
        # 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:7fe8e3d5806f96002e55f190d1d94479',
                'uploader': 'The Voice',
                'uploader_id': 'NBCTheVoice',
                'upload_date': '20191029',
            },
        }],
        'params': {
            'playlistend': 2,
        },
        'expected_warnings': ['HTTP Error 404', 'Ignoring subtitle tracks'],
    }, {
        '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 _extract_yahoo_video(self, video_id, country):
        video = self._download_json(
            'https://%s.yahoo.com/_td/api/resource/VideoService.videos;view=full;video_ids=["%s"]' % (country, 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(metadata_available=True)

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

    def _real_extract(self, url):
        url, country, display_id = self._match_valid_url(url).groups()
        if not country:
            country = 'us'
        else:
            country = country.split('-')[0]

        items = self._download_json(
            'https://%s.yahoo.com/caas/content/article' % country, display_id,
            'Downloading content JSON metadata', query={
                'url': url
            })['items'][0]

        item = items['data']['partnerData']
        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') or []):
                if e.get('type') == 'videoIframe':
                    iframe_url = e.get('url')
                    if iframe_url:
                        entries.append(self.url_result(iframe_url))

            if item.get('type') == 'storywithleadvideo':
                iframe_url = try_get(item, lambda x: x['meta']['player']['url'])
                if iframe_url:
                    entries.append(self.url_result(iframe_url))
                else:
                    self.report_warning("Yahoo didn't provide an iframe url for this storywithleadvideo")

            if items.get('markup'):
                entries.extend(
                    self.url_result(yt_url) for yt_url in YoutubeIE._extract_embed_urls(url, items['markup']))

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

        info = self._extract_yahoo_video(item['uuid'], country)
        info['display_id'] = display_id
        return info

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

    def _search_results(self, query):
        for pagenum in itertools.count(0):
            result_url = 'http://video.search.yahoo.com/search/?p=%s&fr=screen&o=js&gs=0&b=%d' % (urllib.parse.quote_plus(query), pagenum * 30)
            info = self._download_json(result_url, query,
                                       note='Downloading results page ' + str(pagenum + 1))
            yield from (self.url_result(result['rurl']) for result in info['results'])
            if info['m']['last'] >= info['m']['total'] - 1:
                break

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,
    }, {
        'url': 'https://gyao.yahoo.co.jp/episode/5fa1226c-ef8d-4e93-af7a-fd92f4e30597',
        '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/title/%E3%82%BF%E3%82%A4%E3%83%A0%E3%83%9C%E3%82%AB%E3%83%B3%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA%20%E3%83%A4%E3%83%83%E3%82%BF%E3%83%BC%E3%83%9E%E3%83%B3/5f60ceb3-6e5e-40ef-ba40-d68b598d067f',
        'info_dict': {
            'id': '5f60ceb3-6e5e-40ef-ba40-d68b598d067f',
        },
        'playlist_mincount': 80,
    }, {
        'url': 'https://gyao.yahoo.co.jp/p/00449/v03102/',
        'only_matching': True,
    }, {
        '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 _entries(self, program_id):
        page = 1
        while True:
            playlist = self._download_json(
                f'https://gyao.yahoo.co.jp/api/programs/{program_id}/videos?page={page}&serviceId=gy', program_id,
                note=f'Downloading JSON metadata page {page}')
            if not playlist:
                break
            for video in playlist['videos']:
                video_id = video.get('id')
                if not video_id:
                    continue
                if video.get('streamingAvailability') == 'notYet':
                    continue
                yield self.url_result(
                    'https://gyao.yahoo.co.jp/player/%s/' % video_id.replace(':', '/'),
                    YahooGyaOPlayerIE.ie_key(), video_id)
            if playlist.get('ended'):
                break
            page += 1

    def _real_extract(self, url):
        program_id = self._match_id(url).replace('/', ':')
        return self.playlist_result(self._entries(program_id), program_id)

class YahooJapanNewsIE(InfoExtractor):
    IE_NAME = 'yahoo:japannews'
    IE_DESC = 'Yahoo! Japan News'
    _VALID_URL = r'https?://news\.yahoo\.co\.jp/(?:articles|feature)/(?P<id>[a-zA-Z0-9]+)'
    _GEO_COUNTRIES = ['JP']
    _TESTS = [{
        'url': 'https://news.yahoo.co.jp/articles/a70fe3a064f1cfec937e2252c7fc6c1ba3201c0e',
        'info_dict': {
            'id': 'a70fe3a064f1cfec937e2252c7fc6c1ba3201c0e',
            'ext': 'mp4',
            'title': '【独自】安倍元総理「国葬」中止求め“脅迫メール”…「子ども誘拐」“送信者”を追跡',
            'description': 'md5:1c06974575f930f692d8696fbcfdc546',
            'thumbnail': r're:https://.+',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://news.yahoo.co.jp/feature/1356',
        'only_matching': True
    }]

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

        for vid in traverse_obj(json_data, ('ResultSet', 'Result', ..., 'VideoUrlSet', 'VideoUrl', ...)) 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': f'http-{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):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        preloaded_state = self._search_json(r'__PRELOADED_STATE__\s*=', webpage, 'preloaded state', video_id)

        content_id = traverse_obj(
            preloaded_state, ('articleDetail', 'paragraphs', ..., 'objectItems', ..., 'video', 'vid'),
            get_all=False, expected_type=int)
        if content_id is None:
            raise ExtractorError('This article does not contain a video', expected=True)

        HOST = 'news.yahoo.co.jp'
        space_id = traverse_obj(preloaded_state, ('pageData', 'spaceId'), expected_type=str)
        json_data = self._download_json(
            f'https://feapi-yvpub.yahooapis.jp/v1/content/{content_id}',
            video_id, query={
                'appid': 'dj0zaiZpPVZMTVFJR0FwZWpiMyZzPWNvbnN1bWVyc2VjcmV0Jng9YjU-',
                'output': 'json',
                'domain': HOST,
                'ak': hashlib.md5('_'.join((space_id, HOST)).encode()).hexdigest() if space_id else '',
                'device_type': '1100',
            })

        title = (
            traverse_obj(preloaded_state,
                         ('articleDetail', 'headline'), ('pageData', 'pageParam', 'title'),
                         expected_type=str)
            or self._html_search_meta(('og:title', 'twitter:title'), webpage, 'title', default=None)
            or self._html_extract_title(webpage))
        description = (
            traverse_obj(preloaded_state, ('pageData', 'description'), expected_type=str)
            or self._html_search_meta(
                ('og:description', 'description', 'twitter:description'),
                webpage, 'description', default=None))
        thumbnail = (
            traverse_obj(preloaded_state, ('pageData', 'ogpImage'), expected_type=str)
            or self._og_search_thumbnail(webpage, default=None)
            or self._html_search_meta('twitter:image', webpage, 'thumbnail', default=None))

        return {
            'id': video_id,
            'title': title,
            'description': description,
            'thumbnail': thumbnail,
            'formats': self._extract_formats(json_data, video_id),
        }


code:
import json

from .common import InfoExtractor
from ..utils import (
    determine_ext,
    float_or_none,
    int_or_none,
    mimetype2ext,
    try_get,
    urljoin,
)

class YandexDiskIE(InfoExtractor):
    _VALID_URL = r'''(?x)https?://
        (?P<domain>
            yadi\.sk|
            disk\.yandex\.
                (?:
                    az|
                    by|
                    co(?:m(?:\.(?:am|ge|tr))?|\.il)|
                    ee|
                    fr|
                    k[gz]|
                    l[tv]|
                    md|
                    t[jm]|
                    u[az]|
                    ru
                )
        )/(?:[di]/|public.*?\bhash=)(?P<id>[^/?#&]+)'''

    _TESTS = [{
        'url': 'https://yadi.sk/i/VdOeDou8eZs6Y',
        'md5': 'a4a8d52958c8fddcf9845935070402ae',
        'info_dict': {
            'id': 'VdOeDou8eZs6Y',
            'ext': 'mp4',
            'title': '4.mp4',
            'duration': 168.6,
            'uploader': 'y.botova',
            'uploader_id': '300043621',
            'view_count': int,
        },
        'expected_warnings': ['Unable to download JSON metadata'],
    }, {
        'url': 'https://yadi.sk/d/h3WAXvDS3Li3Ce',
        'only_matching': True,
    }, {
        'url': 'https://yadi.sk/public?hash=5DZ296JK9GWCLp02f6jrObjnctjRxMs8L6%2B%2FuhNqk38%3D',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)
        store = self._parse_json(self._search_regex(
            r'<script[^>]+id="store-prefetch"[^>]*>\s*({.+?})\s*</script>',
            webpage, 'store'), video_id)
        resource = store['resources'][store['rootResourceId']]

        title = resource['name']
        meta = resource.get('meta') or {}

        public_url = meta.get('short_url')
        if public_url:
            video_id = self._match_id(public_url)

        source_url = (self._download_json(
            'https://cloud-api.yandex.net/v1/disk/public/resources/download',
            video_id, query={'public_key': url}, fatal=False) or {}).get('href')
        video_streams = resource.get('videoStreams') or {}
        video_hash = resource.get('hash') or url
        environment = store.get('environment') or {}
        sk = environment.get('sk')
        yandexuid = environment.get('yandexuid')
        if sk and yandexuid and not (source_url and video_streams):
            self._set_cookie(domain, 'yandexuid', yandexuid)

            def call_api(action):
                return (self._download_json(
                    urljoin(url, '/public/api/') + action, video_id, data=json.dumps({
                        'hash': video_hash,
                        'sk': sk,
                    }).encode(), headers={
                        'Content-Type': 'text/plain',
                    }, fatal=False) or {}).get('data') or {}
            if not source_url:
                # TODO: figure out how to detect if download limit has
                # been reached and then avoid unnecessary source format
                # extraction requests
                source_url = call_api('download-url').get('url')
            if not video_streams:
                video_streams = call_api('get-video-streams')

        formats = []
        if source_url:
            formats.append({
                'url': source_url,
                'format_id': 'source',
                'ext': determine_ext(title, meta.get('ext') or mimetype2ext(meta.get('mime_type')) or 'mp4'),
                'quality': 1,
                'filesize': int_or_none(meta.get('size'))
            })

        for video in (video_streams.get('videos') or []):
            format_url = video.get('url')
            if not format_url:
                continue
            if video.get('dimension') == 'adaptive':
                formats.extend(self._extract_m3u8_formats(
                    format_url, video_id, 'mp4', 'm3u8_native',
                    m3u8_id='hls', fatal=False))
            else:
                size = video.get('size') or {}
                height = int_or_none(size.get('height'))
                format_id = 'hls'
                if height:
                    format_id += '-%dp' % height
                formats.append({
                    'ext': 'mp4',
                    'format_id': format_id,
                    'height': height,
                    'protocol': 'm3u8_native',
                    'url': format_url,
                    'width': int_or_none(size.get('width')),
                })
        self._sort_formats(formats)

        uid = resource.get('uid')
        display_name = try_get(store, lambda x: x['users'][uid]['displayName'])

        return {
            'id': video_id,
            'title': title,
            'duration': float_or_none(video_streams.get('duration'), 1000),
            'uploader': display_name,
            'uploader_id': uid,
            'view_count': int_or_none(meta.get('views_counter')),
            'formats': formats,
        }


code:
import hashlib
import itertools

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

class YandexMusicBaseIE(InfoExtractor):
    _VALID_URL_BASE = r'https?://music\.yandex\.(?P<tld>ru|kz|ua|by|com)'

    @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 yt-dlp 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 '
            'yt-dlp 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

    def _call_api(self, ep, tld, url, item_id, note, query):
        return self._download_json(
            'https://music.yandex.%s/handlers/%s.jsx' % (tld, ep),
            item_id, note,
            fatal=False,
            headers={
                'Referer': url,
                'X-Requested-With': 'XMLHttpRequest',
                'X-Retpath-Y': url,
            },
            query=query)

class YandexMusicTrackIE(YandexMusicBaseIE):
    IE_NAME = 'yandexmusic:track'
    IE_DESC = 'Яндекс.Музыка - Трек'
    _VALID_URL = r'%s/album/(?P<album_id>\d+)/track/(?P<id>\d+)' % YandexMusicBaseIE._VALID_URL_BASE

    _TESTS = [{
        'url': 'http://music.yandex.ru/album/540508/track/4878838',
        'md5': 'dec8b661f12027ceaba33318787fff76',
        'info_dict': {
            'id': '4878838',
            'ext': 'mp3',
            'title': 'md5:c63e19341fdbe84e43425a30bc777856',
            'filesize': int,
            'duration': 193.04,
            'track': 'md5:210508c6ffdfd67a493a6c378f22c3ff',
            'album': 'md5:cd04fb13c4efeafdfa0a6a6aca36d01a',
            'album_artist': 'md5:5f54c35462c07952df33d97cfb5fc200',
            'artist': 'md5:e6fd86621825f14dc0b25db3acd68160',
            'release_year': 2009,
        },
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        # multiple disks
        'url': 'http://music.yandex.ru/album/3840501/track/705105',
        'md5': '82a54e9e787301dd45aba093cf6e58c0',
        'info_dict': {
            'id': '705105',
            'ext': 'mp3',
            'title': 'md5:f86d4a9188279860a83000277024c1a6',
            'filesize': int,
            'duration': 239.27,
            'track': 'md5:40f887f0666ba1aa10b835aca44807d1',
            'album': 'md5:624f5224b14f5c88a8e812fd7fbf1873',
            'album_artist': 'md5:dd35f2af4e8927100cbe6f5e62e1fb12',
            'artist': 'md5:dd35f2af4e8927100cbe6f5e62e1fb12',
            'release_year': 2016,
            'genre': 'pop',
            'disc_number': 2,
            'track_number': 9,
        },
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        'url': 'http://music.yandex.com/album/540508/track/4878838',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        tld, album_id, track_id = mobj.group('tld'), mobj.group('album_id'), mobj.group('id')

        track = self._call_api(
            'track', tld, url, track_id, 'Downloading track JSON',
            {'track': '%s:%s' % (track_id, album_id)})['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', query={'hq': 1}, 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()
        f_url = 'http://%s/get-mp3/%s/%s?track-id=%s ' % (fd_data['host'], key, fd_data['ts'] + fd_data['path'], track['id'])

        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 _extract_tracks(self, source, item_id, url, tld):
        tracks = source['tracks']
        track_ids = [compat_str(track_id) for track_id in source['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]
            # Request missing tracks in chunks to avoid exceeding max HTTP header size,
            # see https://github.com/ytdl-org/youtube-dl/issues/27355
            _TRACKS_PER_CHUNK = 250
            for chunk_num in itertools.count(0):
                start = chunk_num * _TRACKS_PER_CHUNK
                end = start + _TRACKS_PER_CHUNK
                missing_track_ids_req = missing_track_ids[start:end]
                assert missing_track_ids_req
                missing_tracks = self._call_api(
                    'track-entries', tld, url, item_id,
                    'Downloading missing tracks JSON chunk %d' % (chunk_num + 1), {
                        'entries': ','.join(missing_track_ids_req),
                        'lang': tld,
                        'external-domain': 'music.yandex.%s' % tld,
                        'overembed': 'false',
                        'strict': 'true',
                    })
                if missing_tracks:
                    tracks.extend(missing_tracks)
                if end >= len(missing_track_ids):
                    break

        return tracks

    def _build_playlist(self, tracks):
        entries = []
        for track in tracks:
            track_id = track.get('id') or track.get('realId')
            if not track_id:
                continue
            albums = track.get('albums')
            if not albums or not isinstance(albums, list):
                continue
            album = albums[0]
            if not isinstance(album, dict):
                continue
            album_id = album.get('id')
            if not album_id:
                continue
            entries.append(self.url_result(
                'http://music.yandex.ru/album/%s/track/%s' % (album_id, track_id),
                ie=YandexMusicTrackIE.ie_key(), video_id=track_id))
        return entries

class YandexMusicAlbumIE(YandexMusicPlaylistBaseIE):
    IE_NAME = 'yandexmusic:album'
    IE_DESC = 'Яндекс.Музыка - Альбом'
    _VALID_URL = r'%s/album/(?P<id>\d+)' % YandexMusicBaseIE._VALID_URL_BASE

    _TESTS = [{
        'url': 'http://music.yandex.ru/album/540508',
        'info_dict': {
            'id': '540508',
            'title': 'md5:7ed1c3567f28d14be9f61179116f5571',
        },
        'playlist_count': 50,
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        'url': 'https://music.yandex.ru/album/3840501',
        'info_dict': {
            'id': '3840501',
            'title': 'md5:36733472cdaa7dcb1fd9473f7da8e50f',
        },
        'playlist_count': 33,
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        # empty artists
        'url': 'https://music.yandex.ru/album/9091882',
        'info_dict': {
            'id': '9091882',
            'title': 'ТЕД на русском',
        },
        'playlist_count': 187,
    }]

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

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

        album = self._call_api(
            'album', tld, url, album_id, 'Downloading album JSON',
            {'album': album_id})

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

        title = album['title']
        artist = try_get(album, lambda x: x['artists'][0]['name'], compat_str)
        if artist:
            title = '%s - %s' % (artist, 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'%s/users/(?P<user>[^/]+)/playlists/(?P<id>\d+)' % YandexMusicBaseIE._VALID_URL_BASE

    _TESTS = [{
        'url': 'http://music.yandex.ru/users/music.partners/playlists/1245',
        'info_dict': {
            'id': '1245',
            'title': 'md5:841559b3fe2b998eca88d0d2e22a3097',
            'description': 'md5:3b9f27b0efbe53f2ee1e844d07155cc9',
        },
        'playlist_count': 5,
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }, {
        'url': 'https://music.yandex.ru/users/ya.playlist/playlists/1036',
        'only_matching': True,
    }, {
        # playlist exceeding the limit of 150 tracks (see
        # https://github.com/ytdl-org/youtube-dl/issues/6666)
        'url': 'https://music.yandex.ru/users/mesiaz/playlists/1364',
        'info_dict': {
            'id': '1364',
            'title': 'md5:b3b400f997d3f878a13ae0699653f7db',
        },
        'playlist_mincount': 437,
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }]

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

        playlist = self._call_api(
            'playlist', tld, url, playlist_id, 'Downloading playlist JSON', {
                'owner': user,
                'kinds': playlist_id,
                'light': 'true',
                'lang': tld,
                'external-domain': 'music.yandex.%s' % tld,
                'overembed': 'false',
            })['playlist']

        tracks = self._extract_tracks(playlist, playlist_id, url, tld)

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

class YandexMusicArtistBaseIE(YandexMusicPlaylistBaseIE):
    def _call_artist(self, tld, url, artist_id):
        return self._call_api(
            'artist', tld, url, artist_id,
            'Downloading artist %s JSON' % self._ARTIST_WHAT, {
                'artist': artist_id,
                'what': self._ARTIST_WHAT,
                'sort': self._ARTIST_SORT or '',
                'dir': '',
                'period': '',
                'lang': tld,
                'external-domain': 'music.yandex.%s' % tld,
                'overembed': 'false',
            })

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        tld = mobj.group('tld')
        artist_id = mobj.group('id')
        data = self._call_artist(tld, url, artist_id)
        tracks = self._extract_tracks(data, artist_id, url, tld)
        title = try_get(data, lambda x: x['artist']['name'], compat_str)
        return self.playlist_result(
            self._build_playlist(tracks), artist_id, title)

class YandexMusicArtistTracksIE(YandexMusicArtistBaseIE):
    IE_NAME = 'yandexmusic:artist:tracks'
    IE_DESC = 'Яндекс.Музыка - Артист - Треки'
    _VALID_URL = r'%s/artist/(?P<id>\d+)/tracks' % YandexMusicBaseIE._VALID_URL_BASE

    _TESTS = [{
        'url': 'https://music.yandex.ru/artist/617526/tracks',
        'info_dict': {
            'id': '617526',
            'title': 'md5:131aef29d45fd5a965ca613e708c040b',
        },
        'playlist_count': 507,
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }]

    _ARTIST_SORT = ''
    _ARTIST_WHAT = 'tracks'

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        tld = mobj.group('tld')
        artist_id = mobj.group('id')
        data = self._call_artist(tld, url, artist_id)
        tracks = self._extract_tracks(data, artist_id, url, tld)
        artist = try_get(data, lambda x: x['artist']['name'], compat_str)
        title = '%s - %s' % (artist or artist_id, 'Треки')
        return self.playlist_result(
            self._build_playlist(tracks), artist_id, title)

class YandexMusicArtistAlbumsIE(YandexMusicArtistBaseIE):
    IE_NAME = 'yandexmusic:artist:albums'
    IE_DESC = 'Яндекс.Музыка - Артист - Альбомы'
    _VALID_URL = r'%s/artist/(?P<id>\d+)/albums' % YandexMusicBaseIE._VALID_URL_BASE

    _TESTS = [{
        'url': 'https://music.yandex.ru/artist/617526/albums',
        'info_dict': {
            'id': '617526',
            'title': 'md5:55dc58d5c85699b7fb41ee926700236c',
        },
        'playlist_count': 8,
        # 'skip': 'Travis CI servers blocked by YandexMusic',
    }]

    _ARTIST_SORT = 'year'
    _ARTIST_WHAT = 'albums'

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        tld = mobj.group('tld')
        artist_id = mobj.group('id')
        data = self._call_artist(tld, url, artist_id)
        entries = []
        for album in data['albums']:
            if not isinstance(album, dict):
                continue
            album_id = album.get('id')
            if not album_id:
                continue
            entries.append(self.url_result(
                'http://music.yandex.ru/album/%s' % album_id,
                ie=YandexMusicAlbumIE.ie_key(), video_id=album_id))
        artist = try_get(data, lambda x: x['artist']['name'], compat_str)
        title = '%s - %s' % (artist or artist_id, 'Альбомы')
        return self.playlist_result(entries, artist_id, title)


code:
import itertools
import re

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

class YandexVideoIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            yandex\.ru(?:/(?:portal/(?:video|efir)|efir))?/?\?.*?stream_id=|
                            frontend\.vh\.yandex\.ru/player/
                        )
                        (?P<id>(?:[\da-f]{32}|[\w-]{12}))
                    '''
    _TESTS = [{
        'url': 'https://yandex.ru/portal/video?stream_id=4dbb36ec4e0526d58f9f2dc8f0ecf374',
        'md5': 'e02a05bfaf0d9615ef07ae3a10f4faf4',
        'info_dict': {
            'id': '4dbb36ec4e0526d58f9f2dc8f0ecf374',
            'ext': 'mp4',
            'title': 'Русский Вудсток - главный рок-фест в истории СССР / вДудь',
            'description': 'md5:7d6b8d4bc4a3b9a56499916c1ea5b5fa',
            'thumbnail': r're:^https?://',
            'timestamp': 1549972939,
            'duration': 5575,
            'age_limit': 18,
            'upload_date': '20190212',
            'view_count': int,
            'like_count': int,
            'dislike_count': int,
        },
    }, {
        'url': 'https://yandex.ru/portal/efir?stream_id=4dbb262b4fe5cf15a215de4f34eee34d&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,
    }, {
        'url': 'https://yandex.ru/efir?stream_active=watching&stream_id=v7a2dZ-v5mSI&from_block=efir_newtab',
        'only_matching': True,
    }]

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

        player = try_get((self._download_json(
            'https://frontend.vh.yandex.ru/graphql', video_id, data=('''{
  player(content_id: "%s") {
    computed_title
    content_url
    description
    dislikes
    duration
    likes
    program_title
    release_date
    release_date_ut
    release_year
    restriction_age
    season
    start_time
    streams
    thumbnail
    title
    views_count
  }
}''' % video_id).encode(), fatal=False)), lambda x: x['player']['content'])
        if not player or player.get('error'):
            player = self._download_json(
                'https://frontend.vh.yandex.ru/v23/player/%s.json' % video_id,
                video_id, query={
                    'stream_options': 'hires',
                    'disable_trackings': 1,
                })
        content = player['content']

        title = content.get('title') or content['computed_title']

        formats = []
        streams = content.get('streams') or []
        streams.append({'url': content.get('content_url')})
        for stream in streams:
            content_url = url_or_none(stream.get('url'))
            if not content_url:
                continue
            ext = determine_ext(content_url)
            if ext == 'ismc':
                continue
            elif ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(
                    content_url, video_id, 'mp4',
                    'm3u8_native', m3u8_id='hls', fatal=False))
            elif ext == 'mpd':
                formats.extend(self._extract_mpd_formats(
                    content_url, video_id, mpd_id='dash', fatal=False))
            else:
                formats.append({'url': content_url})

        self._sort_formats(formats)

        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')))
        season = content.get('season') or {}

        return {
            'id': video_id,
            'title': title,
            'description': content.get('description'),
            'thumbnail': content.get('thumbnail'),
            'timestamp': timestamp,
            'duration': int_or_none(content.get('duration')),
            'series': content.get('program_title'),
            'age_limit': int_or_none(content.get('restriction_age')),
            'view_count': int_or_none(content.get('views_count')),
            'like_count': int_or_none(content.get('likes')),
            'dislike_count': int_or_none(content.get('dislikes')),
            'season_number': int_or_none(season.get('season_number')),
            'season_id': season.get('id'),
            'release_year': int_or_none(content.get('release_year')),
            'formats': formats,
        }

class YandexVideoPreviewIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?yandex\.ru/video/preview(?:/?\?.*?filmId=|/)(?P<id>\d+)'
    _TESTS = [{  # Odnoklassniki
        'url': 'https://yandex.ru/video/preview/?filmId=10682852472978372885&text=summer',
        'info_dict': {
            'id': '1352565459459',
            'ext': 'mp4',
            'like_count': int,
            'upload_date': '20191202',
            'age_limit': 0,
            'duration': 196,
            'thumbnail': 'https://i.mycdn.me/videoPreview?id=544866765315&type=37&idx=13&tkn=TY5qjLYZHxpmcnK8U2LgzYkgmaU&fn=external_8',
            'uploader_id': '481054701571',
            'title': 'LOFT - summer, summer, summer HD',
            'uploader': 'АРТЁМ КУДРОВ',
        },
    }, {  # youtube
        'url': 'https://yandex.ru/video/preview/?filmId=4479424425337895262&source=main_redirect&text=видео&utm_source=main_stripe_big',
        'only_matching': True,
    }, {  # YandexVideo
        'url': 'https://yandex.ru/video/preview/5275069442094787341',
        'only_matching': True,
    }, {  # youtube
        'url': 'https://yandex.ru/video/preview/?filmId=16658118429797832897&from=tabbar&p=1&text=%D0%BF%D1%80%D0%BE%D1%81%D0%BC%D0%BE%D1%82%D1%80+%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82%D0%B0+%D0%BC%D0%B0%D0%BB%D0%B5%D0%BD%D1%8C%D0%BA%D0%B8%D0%B9+%D0%BF%D1%80%D0%B8%D0%BD%D1%86+%D0%BC%D1%8B+%D0%B2+%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%B5+%D0%B7%D0%B0+%D1%82%D0%B5%D1%85+%D0%BA%D0%BE%D0%B3%D0%BE+%D0%BF%D1%80%D0%B8%D1%80%D1%83%D1%87%D0%B8%D0%BB%D0%B8',
        'only_matching': True,
    }, {  # Odnoklassniki
        'url': 'https://yandex.ru/video/preview/?text=Francis%20Lai%20-%20Le%20Bon%20Et%20Les%20MC)chants&path=wizard&parent-reqid=1643208087979310-1481782809207673478-sas3-0931-2f9-sas-l7-balancer-8080-BAL-9380&wiz_type=vital&filmId=12508152936505397283',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        webpage = self._download_webpage(url, id)
        data_raw = self._search_regex(r'window.Ya.__inline_params__\s*=\s*JSON.parse\(\'([^"]+?\\u0022video\\u0022:[^"]+?})\'\);', webpage, 'data_raw')
        data_json = self._parse_json(data_raw, id, transform_source=lowercase_escape)
        return self.url_result(data_json['video']['url'])

class ZenYandexIE(InfoExtractor):
    _VALID_URL = r'https?://zen\.yandex\.ru(?:/video)?/(media|watch)/(?:(?:id/[^/]+/|[^/]+/)(?:[a-z0-9-]+)-)?(?P<id>[a-z0-9-]+)'
    _TESTS = [{
        'url': 'https://zen.yandex.ru/media/popmech/izverjenie-vulkana-iz-spichek-zreliscnyi-opyt-6002240ff8b1af50bb2da5e3',
        'info_dict': {
            'id': '6002240ff8b1af50bb2da5e3',
            'ext': 'mp4',
            'title': 'Извержение вулкана из спичек: зрелищный опыт',
            'description': 'md5:053ad3c61b5596d510c9a199dc8ee633',
            'thumbnail': 're:^https://avatars.mds.yandex.net/',
            'uploader': 'Популярная механика',
        },
        'params': {
            'skip_download': 'm3u8',
        },
    }, {
        'url': 'https://zen.yandex.ru/media/id/606fd806cc13cb3c58c05cf5/vot-eto-focus-dedy-morozy-na-gidrociklah-60c7c443da18892ebfe85ed7',
        'info_dict': {
            'id': '60c7c443da18892ebfe85ed7',
            'ext': 'mp4',
            'title': 'ВОТ ЭТО Focus. Деды Морозы на гидроциклах',
            'description': 'md5:f3db3d995763b9bbb7b56d4ccdedea89',
            'thumbnail': 're:^https://avatars.mds.yandex.net/',
            'uploader': 'AcademeG DailyStream'
        },
        'params': {
            'skip_download': 'm3u8',
            'format': 'bestvideo',
        },
    }, {
        'url': 'https://zen.yandex.ru/video/watch/6002240ff8b1af50bb2da5e3',
        'info_dict': {
            'id': '6002240ff8b1af50bb2da5e3',
            'ext': 'mp4',
            'title': 'Извержение вулкана из спичек: зрелищный опыт',
            'description': 'md5:053ad3c61b5596d510c9a199dc8ee633',
            'uploader': 'Популярная механика',
        },
        'params': {
            'skip_download': 'm3u8',
        },
    }, {
        'url': 'https://zen.yandex.ru/media/id/606fd806cc13cb3c58c05cf5/novyi-samsung-fold-3-moskvich-barahlit-612f93b7f8d48e7e945792a2?from=channel&rid=2286618386.482.1630817595976.42360',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        id = self._match_id(url)
        webpage = self._download_webpage(url, id)
        data_json = self._parse_json(
            self._search_regex(r'data\s*=\s*({["\']_*serverState_*video.+?});', webpage, 'metadata'), id)
        serverstate = self._search_regex(r'(_+serverState_+video-site_[^_]+_+)',
                                         webpage, 'server state').replace('State', 'Settings')
        uploader = self._search_regex(r'(<a\s*class=["\']card-channel-link[^"\']+["\'][^>]+>)',
                                      webpage, 'uploader', default='<a>')
        uploader_name = extract_attributes(uploader).get('aria-label')
        video_json = try_get(data_json, lambda x: x[serverstate]['exportData']['video'], dict)
        stream_urls = try_get(video_json, lambda x: x['video']['streams'])
        formats = []
        for s_url in stream_urls:
            ext = determine_ext(s_url)
            if ext == 'mpd':
                formats.extend(self._extract_mpd_formats(s_url, id, mpd_id='dash'))
            elif ext == 'm3u8':
                formats.extend(self._extract_m3u8_formats(s_url, id, 'mp4'))
        self._sort_formats(formats)
        return {
            'id': id,
            'title': video_json.get('title') or self._og_search_title(webpage),
            'formats': formats,
            'duration': int_or_none(video_json.get('duration')),
            'view_count': int_or_none(video_json.get('views')),
            'uploader': uploader_name or data_json.get('authorName') or try_get(data_json, lambda x: x['publisher']['name']),
            'description': self._og_search_description(webpage) or try_get(data_json, lambda x: x['og']['description']),
            'thumbnail': self._og_search_thumbnail(webpage) or try_get(data_json, lambda x: x['og']['imageUrl']),
        }

class ZenYandexChannelIE(InfoExtractor):
    _VALID_URL = r'https?://zen\.yandex\.ru/(?!media|video)(?:id/)?(?P<id>[a-z0-9-_]+)'
    _TESTS = [{
        'url': 'https://zen.yandex.ru/tok_media',
        'info_dict': {
            'id': 'tok_media',
        },
        'playlist_mincount': 169,
    }, {
        'url': 'https://zen.yandex.ru/id/606fd806cc13cb3c58c05cf5',
        'info_dict': {
            'id': '606fd806cc13cb3c58c05cf5',
        },
        'playlist_mincount': 657,
    }]

    def _entries(self, id, url):
        webpage = self._download_webpage(url, id)
        data_json = self._parse_json(re.findall(r'var\s?data\s?=\s?({.+?})\s?;', webpage)[-1], id)
        for key in data_json.keys():
            if key.startswith('__serverState__'):
                data_json = data_json[key]
        items = list(try_get(data_json, lambda x: x['feed']['items'], dict).values())
        more = try_get(data_json, lambda x: x['links']['more']) or None
        for page in itertools.count(1):
            for item in items:
                video_id = item.get('publication_id') or item.get('publicationId')
                video_url = item.get('link')
                yield self.url_result(video_url, ie=ZenYandexIE.ie_key(), video_id=video_id.split(':')[-1])
            if not more:
                break
            data_json = self._download_json(more, id, note='Downloading Page %d' % page)
            items = data_json.get('items', [])
            more = try_get(data_json, lambda x: x['more']['link']) or None

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


code:
from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    int_or_none,
    qualities,
    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
    _EMBED_REGEX = [rf'<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?{_YAPFILES_URL}.*?)\1']
    _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,
    }]

    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:
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:
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:
import json
import re
import urllib.parse

from .common import InfoExtractor

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 = 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 .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 = self._match_valid_url(url)
        video_id = mobj.group('id') or mobj.group('embed_id')

        webpage = self._download_webpage(url, video_id)

        title = self._html_extract_title(webpage)

        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:
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': '0532',
            'client_ip': '192.168.1.1',
            'utid': cna,
            'client_ts': time.time() / 1000,
        }

        video_password = self.get_param('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)