code:
import itertools
import json
import random
import string
import time

from .common import InfoExtractor
from ..compat import compat_urllib_parse_unquote, compat_urllib_parse_urlparse
from ..utils import (
    ExtractorError,
    HEADRequest,
    LazyList,
    UnsupportedError,
    get_element_by_id,
    get_first,
    int_or_none,
    join_nonempty,
    qualities,
    srt_subtitles_timecode,
    str_or_none,
    traverse_obj,
    try_get,
    url_or_none,
)

class TikTokBaseIE(InfoExtractor):
    _APP_VERSIONS = [('26.1.3', '260103'), ('26.1.2', '260102'), ('26.1.1', '260101'), ('25.6.2', '250602')]
    _WORKING_APP_VERSION = None
    _APP_NAME = 'trill'
    _AID = 1180
    _API_HOSTNAME = 'api-h2.tiktokv.com'
    _UPLOADER_URL_FORMAT = 'https://www.tiktok.com/@%s'
    _WEBPAGE_HOST = 'https://www.tiktok.com/'
    QUALITIES = ('360p', '540p', '720p', '1080p')

    @staticmethod
    def _create_url(user_id, video_id):
        return f'https://www.tiktok.com/@{user_id or "_"}/video/{video_id}'

    def _get_sigi_state(self, webpage, display_id):
        return self._parse_json(get_element_by_id(
            'SIGI_STATE|sigi-persisted-data', webpage, escape_value=False), display_id)

    def _call_api_impl(self, ep, query, manifest_app_version, video_id, fatal=True,
                       note='Downloading API JSON', errnote='Unable to download API page'):
        self._set_cookie(self._API_HOSTNAME, 'odin_tt', ''.join(random.choice('0123456789abcdef') for _ in range(160)))
        webpage_cookies = self._get_cookies(self._WEBPAGE_HOST)
        if webpage_cookies.get('sid_tt'):
            self._set_cookie(self._API_HOSTNAME, 'sid_tt', webpage_cookies['sid_tt'].value)
        return self._download_json(
            'https://%s/aweme/v1/%s/' % (self._API_HOSTNAME, ep), video_id=video_id,
            fatal=fatal, note=note, errnote=errnote, headers={
                'User-Agent': f'com.ss.android.ugc.trill/{manifest_app_version} (Linux; U; Android 10; en_US; Pixel 4; Build/QQ3A.200805.001; Cronet/58.0.2991.0)',
                'Accept': 'application/json',
            }, query=query)

    def _build_api_query(self, query, app_version, manifest_app_version):
        return {
            **query,
            'version_name': app_version,
            'version_code': manifest_app_version,
            'build_number': app_version,
            'manifest_version_code': manifest_app_version,
            'update_version_code': manifest_app_version,
            'openudid': ''.join(random.choice('0123456789abcdef') for _ in range(16)),
            'uuid': ''.join([random.choice(string.digits) for _ in range(16)]),
            '_rticket': int(time.time() * 1000),
            'ts': int(time.time()),
            'device_brand': 'Google',
            'device_type': 'Pixel 4',
            'device_platform': 'android',
            'resolution': '1080*1920',
            'dpi': 420,
            'os_version': '10',
            'os_api': '29',
            'carrier_region': 'US',
            'sys_region': 'US',
            'region': 'US',
            'app_name': self._APP_NAME,
            'app_language': 'en',
            'language': 'en',
            'timezone_name': 'America/New_York',
            'timezone_offset': '-14400',
            'channel': 'googleplay',
            'ac': 'wifi',
            'mcc_mnc': '310260',
            'is_my_cn': 0,
            'aid': self._AID,
            'ssmix': 'a',
            'as': 'a1qwert123',
            'cp': 'cbfhckdckkde1',
        }

    def _call_api(self, ep, query, video_id, fatal=True,
                  note='Downloading API JSON', errnote='Unable to download API page'):
        if not self._WORKING_APP_VERSION:
            app_version = self._configuration_arg('app_version', [''], ie_key=TikTokIE.ie_key())[0]
            manifest_app_version = self._configuration_arg('manifest_app_version', [''], ie_key=TikTokIE.ie_key())[0]
            if app_version and manifest_app_version:
                self._WORKING_APP_VERSION = (app_version, manifest_app_version)
                self.write_debug('Imported app version combo from extractor arguments')
            elif app_version or manifest_app_version:
                self.report_warning('Only one of the two required version params are passed as extractor arguments', only_once=True)

        if self._WORKING_APP_VERSION:
            app_version, manifest_app_version = self._WORKING_APP_VERSION
            real_query = self._build_api_query(query, app_version, manifest_app_version)
            return self._call_api_impl(ep, real_query, manifest_app_version, video_id, fatal, note, errnote)

        for count, (app_version, manifest_app_version) in enumerate(self._APP_VERSIONS, start=1):
            real_query = self._build_api_query(query, app_version, manifest_app_version)
            try:
                res = self._call_api_impl(ep, real_query, manifest_app_version, video_id, fatal, note, errnote)
                self._WORKING_APP_VERSION = (app_version, manifest_app_version)
                return res
            except ExtractorError as e:
                if isinstance(e.cause, json.JSONDecodeError) and e.cause.pos == 0:
                    if count == len(self._APP_VERSIONS):
                        if fatal:
                            raise e
                        else:
                            self.report_warning(str(e.cause or e.msg))
                            return
                    self.report_warning('%s. Retrying... (attempt %s of %s)' % (str(e.cause or e.msg), count, len(self._APP_VERSIONS)))
                    continue
                raise e

    def _get_subtitles(self, aweme_detail, aweme_id):
        # TODO: Extract text positioning info
        subtitles = {}
        captions_info = traverse_obj(
            aweme_detail, ('interaction_stickers', ..., 'auto_video_caption_info', 'auto_captions', ...), expected_type=dict, default=[])
        for caption in captions_info:
            caption_url = traverse_obj(caption, ('url', 'url_list', ...), expected_type=url_or_none, get_all=False)
            if not caption_url:
                continue
            caption_json = self._download_json(
                caption_url, aweme_id, note='Downloading captions', errnote='Unable to download captions', fatal=False)
            if not caption_json:
                continue
            subtitles.setdefault(caption.get('language', 'en'), []).append({
                'ext': 'srt',
                'data': '\n\n'.join(
                    f'{i + 1}\n{srt_subtitles_timecode(line["start_time"] / 1000)} --> {srt_subtitles_timecode(line["end_time"] / 1000)}\n{line["text"]}'
                    for i, line in enumerate(caption_json['utterances']) if line.get('text'))
            })
        return subtitles

    def _parse_aweme_video_app(self, aweme_detail):
        aweme_id = aweme_detail['aweme_id']
        video_info = aweme_detail['video']

        def parse_url_key(url_key):
            format_id, codec, res, bitrate = self._search_regex(
                r'v[^_]+_(?P<id>(?P<codec>[^_]+)_(?P<res>\d+p)_(?P<bitrate>\d+))', url_key,
                'url key', default=(None, None, None, None), group=('id', 'codec', 'res', 'bitrate'))
            if not format_id:
                return {}, None
            return {
                'format_id': format_id,
                'vcodec': 'h265' if codec == 'bytevc1' else codec,
                'tbr': int_or_none(bitrate, scale=1000) or None,
                'quality': qualities(self.QUALITIES)(res),
            }, res

        known_resolutions = {}

        def extract_addr(addr, add_meta={}):
            parsed_meta, res = parse_url_key(addr.get('url_key', ''))
            if res:
                known_resolutions.setdefault(res, {}).setdefault('height', add_meta.get('height'))
                known_resolutions[res].setdefault('width', add_meta.get('width'))
                parsed_meta.update(known_resolutions.get(res, {}))
                add_meta.setdefault('height', int_or_none(res[:-1]))
            return [{
                'url': url,
                'filesize': int_or_none(addr.get('data_size')),
                'ext': 'mp4',
                'acodec': 'aac',
                'source_preference': -2 if 'aweme/v1' in url else -1,  # Downloads from API might get blocked
                **add_meta, **parsed_meta,
                'format_note': join_nonempty(
                    add_meta.get('format_note'), '(API)' if 'aweme/v1' in url else None, delim=' ')
            } for url in addr.get('url_list') or []]

        # Hack: Add direct video links first to prioritize them when removing duplicate formats
        formats = []
        if video_info.get('play_addr'):
            formats.extend(extract_addr(video_info['play_addr'], {
                'format_id': 'play_addr',
                'format_note': 'Direct video',
                'vcodec': 'h265' if traverse_obj(
                    video_info, 'is_bytevc1', 'is_h265') else 'h264',  # TODO: Check for "direct iOS" videos, like https://www.tiktok.com/@cookierun_dev/video/7039716639834656002
                'width': video_info.get('width'),
                'height': video_info.get('height'),
            }))
        if video_info.get('download_addr'):
            formats.extend(extract_addr(video_info['download_addr'], {
                'format_id': 'download_addr',
                'format_note': 'Download video%s' % (', watermarked' if video_info.get('has_watermark') else ''),
                'vcodec': 'h264',
                'width': video_info.get('width'),
                'height': video_info.get('height'),
                'preference': -2 if video_info.get('has_watermark') else -1,
            }))
        if video_info.get('play_addr_h264'):
            formats.extend(extract_addr(video_info['play_addr_h264'], {
                'format_id': 'play_addr_h264',
                'format_note': 'Direct video',
                'vcodec': 'h264',
            }))
        if video_info.get('play_addr_bytevc1'):
            formats.extend(extract_addr(video_info['play_addr_bytevc1'], {
                'format_id': 'play_addr_bytevc1',
                'format_note': 'Direct video',
                'vcodec': 'h265',
            }))

        for bitrate in video_info.get('bit_rate', []):
            if bitrate.get('play_addr'):
                formats.extend(extract_addr(bitrate['play_addr'], {
                    'format_id': bitrate.get('gear_name'),
                    'format_note': 'Playback video',
                    'tbr': try_get(bitrate, lambda x: x['bit_rate'] / 1000),
                    'vcodec': 'h265' if traverse_obj(
                        bitrate, 'is_bytevc1', 'is_h265') else 'h264',
                    'fps': bitrate.get('FPS'),
                }))

        self._remove_duplicate_formats(formats)
        auth_cookie = self._get_cookies(self._WEBPAGE_HOST).get('sid_tt')
        if auth_cookie:
            for f in formats:
                self._set_cookie(compat_urllib_parse_urlparse(f['url']).hostname, 'sid_tt', auth_cookie.value)
        self._sort_formats(formats, ('quality', 'codec', 'size', 'br'))

        thumbnails = []
        for cover_id in ('cover', 'ai_dynamic_cover', 'animated_cover', 'ai_dynamic_cover_bak',
                         'origin_cover', 'dynamic_cover'):
            cover = video_info.get(cover_id)
            if cover:
                for cover_url in cover['url_list']:
                    thumbnails.append({
                        'id': cover_id,
                        'url': cover_url,
                    })

        stats_info = aweme_detail.get('statistics', {})
        author_info = aweme_detail.get('author', {})
        music_info = aweme_detail.get('music', {})
        user_url = self._UPLOADER_URL_FORMAT % (traverse_obj(author_info,
                                                             'sec_uid', 'id', 'uid', 'unique_id',
                                                             expected_type=str_or_none, get_all=False))
        labels = traverse_obj(aweme_detail, ('hybrid_label', ..., 'text'), expected_type=str, default=[])

        contained_music_track = traverse_obj(
            music_info, ('matched_song', 'title'), ('matched_pgc_sound', 'title'), expected_type=str)
        contained_music_author = traverse_obj(
            music_info, ('matched_song', 'author'), ('matched_pgc_sound', 'author'), 'author', expected_type=str)

        is_generic_og_trackname = music_info.get('is_original_sound') and music_info.get('title') == 'original sound - %s' % music_info.get('owner_handle')
        if is_generic_og_trackname:
            music_track, music_author = contained_music_track or 'original sound', contained_music_author
        else:
            music_track, music_author = music_info.get('title'), music_info.get('author')

        return {
            'id': aweme_id,
            'extractor_key': TikTokIE.ie_key(),
            'extractor': TikTokIE.IE_NAME,
            'webpage_url': self._create_url(author_info.get('uid'), aweme_id),
            'title': aweme_detail.get('desc'),
            'description': aweme_detail.get('desc'),
            'view_count': int_or_none(stats_info.get('play_count')),
            'like_count': int_or_none(stats_info.get('digg_count')),
            'repost_count': int_or_none(stats_info.get('share_count')),
            'comment_count': int_or_none(stats_info.get('comment_count')),
            'uploader': str_or_none(author_info.get('unique_id')),
            'creator': str_or_none(author_info.get('nickname')),
            'uploader_id': str_or_none(author_info.get('uid')),
            'uploader_url': user_url,
            'track': music_track,
            'album': str_or_none(music_info.get('album')) or None,
            'artist': music_author or None,
            'timestamp': int_or_none(aweme_detail.get('create_time')),
            'formats': formats,
            'subtitles': self.extract_subtitles(aweme_detail, aweme_id),
            'thumbnails': thumbnails,
            'duration': int_or_none(traverse_obj(video_info, 'duration', ('download_addr', 'duration')), scale=1000),
            'availability': self._availability(
                is_private='Private' in labels,
                needs_subscription='Friends only' in labels,
                is_unlisted='Followers only' in labels)
        }

    def _parse_aweme_video_web(self, aweme_detail, webpage_url):
        video_info = aweme_detail['video']
        author_info = traverse_obj(aweme_detail, 'authorInfo', 'author', expected_type=dict, default={})
        music_info = aweme_detail.get('music') or {}
        stats_info = aweme_detail.get('stats') or {}
        user_url = self._UPLOADER_URL_FORMAT % (traverse_obj(author_info,
                                                             'secUid', 'id', 'uid', 'uniqueId',
                                                             expected_type=str_or_none, get_all=False)
                                                or aweme_detail.get('authorSecId'))

        formats = []
        play_url = video_info.get('playAddr')
        width = video_info.get('width')
        height = video_info.get('height')
        if isinstance(play_url, str):
            formats = [{
                'url': self._proto_relative_url(play_url),
                'ext': 'mp4',
                'width': width,
                'height': height,
            }]
        elif isinstance(play_url, list):
            formats = [{
                'url': self._proto_relative_url(url),
                'ext': 'mp4',
                'width': width,
                'height': height,
            } for url in traverse_obj(play_url, (..., 'src'), expected_type=url_or_none, default=[]) if url]

        download_url = url_or_none(video_info.get('downloadAddr')) or traverse_obj(video_info, ('download', 'url'), expected_type=url_or_none)
        if download_url:
            formats.append({
                'format_id': 'download',
                'url': self._proto_relative_url(download_url),
                'ext': 'mp4',
                'width': width,
                'height': height,
            })
        self._remove_duplicate_formats(formats)
        self._sort_formats(formats)

        thumbnails = []
        for thumbnail_name in ('thumbnail', 'cover', 'dynamicCover', 'originCover'):
            if aweme_detail.get(thumbnail_name):
                thumbnails = [{
                    'url': self._proto_relative_url(aweme_detail[thumbnail_name]),
                    'width': width,
                    'height': height
                }]

        return {
            'id': traverse_obj(aweme_detail, 'id', 'awemeId', expected_type=str_or_none),
            'title': aweme_detail.get('desc'),
            'duration': try_get(aweme_detail, lambda x: x['video']['duration'], int),
            'view_count': int_or_none(stats_info.get('playCount')),
            'like_count': int_or_none(stats_info.get('diggCount')),
            'repost_count': int_or_none(stats_info.get('shareCount')),
            'comment_count': int_or_none(stats_info.get('commentCount')),
            'timestamp': int_or_none(aweme_detail.get('createTime')),
            'creator': str_or_none(author_info.get('nickname')),
            'uploader': str_or_none(author_info.get('uniqueId') or aweme_detail.get('author')),
            'uploader_id': str_or_none(author_info.get('id') or aweme_detail.get('authorId')),
            'uploader_url': user_url,
            'track': str_or_none(music_info.get('title')),
            'album': str_or_none(music_info.get('album')) or None,
            'artist': str_or_none(music_info.get('authorName')),
            'formats': formats,
            'thumbnails': thumbnails,
            'description': str_or_none(aweme_detail.get('desc')),
            'http_headers': {
                'Referer': webpage_url
            }
        }

class TikTokIE(TikTokBaseIE):
    _VALID_URL = r'https?://www\.tiktok\.com/(?:embed|@(?P<user_id>[\w\.-]+)/video)/(?P<id>\d+)'
    _EMBED_REGEX = [rf'<(?:script|iframe)[^>]+\bsrc=(["\'])(?P<url>{_VALID_URL})']

    _TESTS = [{
        'url': 'https://www.tiktok.com/@leenabhushan/video/6748451240264420610',
        'md5': '736bb7a466c6f0a6afeb597da1e6f5b7',
        'info_dict': {
            'id': '6748451240264420610',
            'ext': 'mp4',
            'title': '#jassmanak #lehanga #leenabhushan',
            'description': '#jassmanak #lehanga #leenabhushan',
            'duration': 13,
            'height': 1024,
            'width': 576,
            'uploader': 'leenabhushan',
            'uploader_id': '6691488002098119685',
            'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAA_Eb4t1vodM1IuTy_cvp9CY22RAb59xqrO0Xtz9CYQJvgXaDvZxYnZYRzDWhhgJmy',
            'creator': 'facestoriesbyleenabh',
            'thumbnail': r're:^https?://[\w\/\.\-]+(~[\w\-]+\.image)?',
            'upload_date': '20191016',
            'timestamp': 1571246252,
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
            'artist': 'Ysrbeats',
            'album': 'Lehanga',
            'track': 'Lehanga',
        }
    }, {
        'url': 'https://www.tiktok.com/@patroxofficial/video/6742501081818877190?langCountry=en',
        'md5': '6f3cf8cdd9b28cb8363fe0a9a160695b',
        'info_dict': {
            'id': '6742501081818877190',
            'ext': 'mp4',
            'title': 'md5:5e2a23877420bb85ce6521dbee39ba94',
            'description': 'md5:5e2a23877420bb85ce6521dbee39ba94',
            'duration': 27,
            'height': 960,
            'width': 540,
            'uploader': 'patrox',
            'uploader_id': '18702747',
            'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAAiFnldaILebi5heDoVU6bn4jBWWycX6-9U3xuNPqZ8Ws',
            'creator': 'patroX',
            'thumbnail': r're:^https?://[\w\/\.\-]+(~[\w\-]+\.image)?',
            'upload_date': '20190930',
            'timestamp': 1569860870,
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
            'artist': 'Evan Todd, Jessica Keenan Wynn, Alice Lee, Barrett Wilbert Weed & Jon Eidson',
            'track': 'Big Fun',
        }
    }, {
        # Banned audio, only available on the app
        'url': 'https://www.tiktok.com/@barudakhb_/video/6984138651336838402',
        'info_dict': {
            'id': '6984138651336838402',
            'ext': 'mp4',
            'title': 'Balas @yolaaftwsr hayu yu ? #SquadRandom_ 🔥',
            'description': 'Balas @yolaaftwsr hayu yu ? #SquadRandom_ 🔥',
            'uploader': 'barudakhb_',
            'creator': 'md5:29f238c49bc0c176cb3cef1a9cea9fa6',
            'uploader_id': '6974687867511718913',
            'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAAbhBwQC-R1iKoix6jDFsF-vBdfx2ABoDjaZrM9fX6arU3w71q3cOWgWuTXn1soZ7d',
            'track': 'Boka Dance',
            'artist': 'md5:29f238c49bc0c176cb3cef1a9cea9fa6',
            'timestamp': 1626121503,
            'duration': 18,
            'thumbnail': r're:^https?://[\w\/\.\-]+(~[\w\-]+\.image)?',
            'upload_date': '20210712',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        }
    }, {
        # Sponsored video, only available with feed workaround
        'url': 'https://www.tiktok.com/@MS4wLjABAAAATh8Vewkn0LYM7Fo03iec3qKdeCUOcBIouRk1mkiag6h3o_pQu_dUXvZ2EZlGST7_/video/7042692929109986561',
        'info_dict': {
            'id': '7042692929109986561',
            'ext': 'mp4',
            'title': 'Slap and Run!',
            'description': 'Slap and Run!',
            'uploader': 'user440922249',
            'creator': 'Slap And Run',
            'uploader_id': '7036055384943690754',
            'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAATh8Vewkn0LYM7Fo03iec3qKdeCUOcBIouRk1mkiag6h3o_pQu_dUXvZ2EZlGST7_',
            'track': 'Promoted Music',
            'timestamp': 1639754738,
            'duration': 30,
            'thumbnail': r're:^https?://[\w\/\.\-]+(~[\w\-]+\.image)?',
            'upload_date': '20211217',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        },
        'expected_warnings': ['trying with webpage', 'Unable to find video in feed']
    }, {
        # Video without title and description
        'url': 'https://www.tiktok.com/@pokemonlife22/video/7059698374567611694',
        'info_dict': {
            'id': '7059698374567611694',
            'ext': 'mp4',
            'title': 'TikTok video #7059698374567611694',
            'description': '',
            'uploader': 'pokemonlife22',
            'creator': 'Pokemon',
            'uploader_id': '6820838815978423302',
            'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAA0tF1nBwQVVMyrGu3CqttkNgM68Do1OXUFuCY0CRQk8fEtSVDj89HqoqvbSTmUP2W',
            'track': 'original sound',
            'timestamp': 1643714123,
            'duration': 6,
            'thumbnail': r're:^https?://[\w\/\.\-]+(~[\w\-]+\.image)?',
            'upload_date': '20220201',
            'artist': 'Pokemon',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        },
    }, {
        # hydration JSON is sent in a <script> element
        'url': 'https://www.tiktok.com/@denidil6/video/7065799023130643713',
        'info_dict': {
            'id': '7065799023130643713',
            'ext': 'mp4',
            'title': '#denidil#денидил',
            'description': '#denidil#денидил',
            'uploader': 'denidil6',
            'uploader_id': '7046664115636405250',
            'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAAsvMSzFdQ4ikl3uR2TEJwMBbB2yZh2Zxwhx-WCo3rbDpAharE3GQCrFuJArI3C8QJ',
            'artist': 'Holocron Music',
            'album': 'Wolf Sounds (1 Hour) Enjoy the Company of the Animal That Is the Majestic King of the Night',
            'track': 'Wolf Sounds (1 Hour) Enjoy the Company of the Animal That Is the Majestic King of the Night',
            'timestamp': 1645134536,
            'duration': 26,
            'upload_date': '20220217',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        },
        'skip': 'This video is unavailable',
    }, {
        # Auto-captions available
        'url': 'https://www.tiktok.com/@hankgreen1/video/7047596209028074758',
        'only_matching': True
    }]

    def _extract_aweme_app(self, aweme_id):
        feed_list = self._call_api('feed', {'aweme_id': aweme_id}, aweme_id,
                                   note='Downloading video feed', errnote='Unable to download video feed').get('aweme_list') or []
        aweme_detail = next((aweme for aweme in feed_list if str(aweme.get('aweme_id')) == aweme_id), None)
        if not aweme_detail:
            raise ExtractorError('Unable to find video in feed', video_id=aweme_id)
        return self._parse_aweme_video_app(aweme_detail)

    def _real_extract(self, url):
        video_id, user_id = self._match_valid_url(url).group('id', 'user_id')
        try:
            return self._extract_aweme_app(video_id)
        except ExtractorError as e:
            self.report_warning(f'{e}; trying with webpage')

        url = self._create_url(user_id, video_id)
        webpage = self._download_webpage(url, video_id, headers={'User-Agent': 'User-Agent:Mozilla/5.0'})
        next_data = self._search_nextjs_data(webpage, video_id, default='{}')
        if next_data:
            status = traverse_obj(next_data, ('props', 'pageProps', 'statusCode'), expected_type=int) or 0
            video_data = traverse_obj(next_data, ('props', 'pageProps', 'itemInfo', 'itemStruct'), expected_type=dict)
        else:
            sigi_data = self._get_sigi_state(webpage, video_id)
            status = traverse_obj(sigi_data, ('VideoPage', 'statusCode'), expected_type=int) or 0
            video_data = traverse_obj(sigi_data, ('ItemModule', video_id), expected_type=dict)

        if status == 0:
            return self._parse_aweme_video_web(video_data, url)
        elif status == 10216:
            raise ExtractorError('This video is private', expected=True)
        raise ExtractorError('Video not available', video_id=video_id)

class TikTokUserIE(TikTokBaseIE):
    IE_NAME = 'tiktok:user'
    _VALID_URL = r'https?://(?:www\.)?tiktok\.com/@(?P<id>[\w\.-]+)/?(?:$|[#?])'
    _WORKING = False
    _TESTS = [{
        'url': 'https://tiktok.com/@corgibobaa?lang=en',
        'playlist_mincount': 45,
        'info_dict': {
            'id': '6935371178089399301',
            'title': 'corgibobaa',
            'thumbnail': r're:https://.+_1080x1080\.webp'
        },
        'expected_warnings': ['Retrying']
    }, {
        'url': 'https://www.tiktok.com/@6820838815978423302',
        'playlist_mincount': 5,
        'info_dict': {
            'id': '6820838815978423302',
            'title': '6820838815978423302',
            'thumbnail': r're:https://.+_1080x1080\.webp'
        },
        'expected_warnings': ['Retrying']
    }, {
        'url': 'https://www.tiktok.com/@meme',
        'playlist_mincount': 593,
        'info_dict': {
            'id': '79005827461758976',
            'title': 'meme',
            'thumbnail': r're:https://.+_1080x1080\.webp'
        },
        'expected_warnings': ['Retrying']
    }]

    r'''  # TODO: Fix by adding _signature to api_url
    def _entries(self, webpage, user_id, username):
        secuid = self._search_regex(r'\"secUid\":\"(?P<secUid>[^\"]+)', webpage, username)
        verifyfp_cookie = self._get_cookies('https://www.tiktok.com').get('s_v_web_id')
        if not verifyfp_cookie:
            raise ExtractorError('Improper cookies (missing s_v_web_id).', expected=True)
        api_url = f'https://m.tiktok.com/api/post/item_list/?aid=1988&cookie_enabled=true&count=30&verifyFp={verifyfp_cookie.value}&secUid={secuid}&cursor='
        cursor = '0'
        for page in itertools.count():
            data_json = self._download_json(api_url + cursor, username, note='Downloading Page %d' % page)
            for video in data_json.get('itemList', []):
                video_id = video['id']
                video_url = f'https://www.tiktok.com/@{user_id}/video/{video_id}'
                yield self._url_result(video_url, 'TikTok', video_id, str_or_none(video.get('desc')))
            if not data_json.get('hasMore'):
                break
            cursor = data_json['cursor']
    '''

    def _video_entries_api(self, webpage, user_id, username):
        query = {
            'user_id': user_id,
            'count': 21,
            'max_cursor': 0,
            'min_cursor': 0,
            'retry_type': 'no_retry',
            'device_id': ''.join(random.choice(string.digits) for _ in range(19)),  # Some endpoints don't like randomized device_id, so it isn't directly set in _call_api.
        }

        for page in itertools.count(1):
            for retry in self.RetryManager():
                try:
                    post_list = self._call_api(
                        'aweme/post', query, username, note=f'Downloading user video list page {page}',
                        errnote='Unable to download user video list')
                except ExtractorError as e:
                    if isinstance(e.cause, json.JSONDecodeError) and e.cause.pos == 0:
                        retry.error = e
                        continue
                    raise
            yield from post_list.get('aweme_list', [])
            if not post_list.get('has_more'):
                break
            query['max_cursor'] = post_list['max_cursor']

    def _entries_api(self, user_id, videos):
        for video in videos:
            yield {
                **self._parse_aweme_video_app(video),
                'extractor_key': TikTokIE.ie_key(),
                'extractor': 'TikTok',
                'webpage_url': f'https://tiktok.com/@{user_id}/video/{video["aweme_id"]}',
            }

    def _real_extract(self, url):
        user_name = self._match_id(url)
        webpage = self._download_webpage(url, user_name, headers={
            'User-Agent': 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)'
        })
        user_id = self._html_search_regex(r'snssdk\d*://user/profile/(\d+)', webpage, 'user ID', default=None) or user_name

        videos = LazyList(self._video_entries_api(webpage, user_id, user_name))
        thumbnail = traverse_obj(videos, (0, 'author', 'avatar_larger', 'url_list', 0))

        return self.playlist_result(self._entries_api(user_id, videos), user_id, user_name, thumbnail=thumbnail)

class TikTokBaseListIE(TikTokBaseIE):
    def _entries(self, list_id, display_id):
        query = {
            self._QUERY_NAME: list_id,
            'cursor': 0,
            'count': 20,
            'type': 5,
            'device_id': ''.join(random.choice(string.digits) for i in range(19))
        }

        for page in itertools.count(1):
            for retry in self.RetryManager():
                try:
                    post_list = self._call_api(
                        self._API_ENDPOINT, query, display_id, note=f'Downloading video list page {page}',
                        errnote='Unable to download video list')
                except ExtractorError as e:
                    if isinstance(e.cause, json.JSONDecodeError) and e.cause.pos == 0:
                        retry.error = e
                        continue
                    raise
            for video in post_list.get('aweme_list', []):
                yield {
                    **self._parse_aweme_video_app(video),
                    'extractor_key': TikTokIE.ie_key(),
                    'extractor': 'TikTok',
                    'webpage_url': f'https://tiktok.com/@_/video/{video["aweme_id"]}',
                }
            if not post_list.get('has_more'):
                break
            query['cursor'] = post_list['cursor']

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

class TikTokSoundIE(TikTokBaseListIE):
    IE_NAME = 'tiktok:sound'
    _VALID_URL = r'https?://(?:www\.)?tiktok\.com/music/[\w\.-]+-(?P<id>[\d]+)[/?#&]?'
    _WORKING = False
    _QUERY_NAME = 'music_id'
    _API_ENDPOINT = 'music/aweme'
    _TESTS = [{
        'url': 'https://www.tiktok.com/music/Build-a-Btch-6956990112127585029?lang=en',
        'playlist_mincount': 100,
        'info_dict': {
            'id': '6956990112127585029'
        },
        'expected_warnings': ['Retrying']
    }, {
        # Actual entries are less than listed video count
        'url': 'https://www.tiktok.com/music/jiefei-soap-remix-7036843036118469381',
        'playlist_mincount': 2182,
        'info_dict': {
            'id': '7036843036118469381'
        },
        'expected_warnings': ['Retrying']
    }]

class TikTokEffectIE(TikTokBaseListIE):
    IE_NAME = 'tiktok:effect'
    _VALID_URL = r'https?://(?:www\.)?tiktok\.com/sticker/[\w\.-]+-(?P<id>[\d]+)[/?#&]?'
    _WORKING = False
    _QUERY_NAME = 'sticker_id'
    _API_ENDPOINT = 'sticker/aweme'
    _TESTS = [{
        'url': 'https://www.tiktok.com/sticker/MATERIAL-GWOOORL-1258156',
        'playlist_mincount': 100,
        'info_dict': {
            'id': '1258156',
        },
        'expected_warnings': ['Retrying']
    }, {
        # Different entries between mobile and web, depending on region
        'url': 'https://www.tiktok.com/sticker/Elf-Friend-479565',
        'only_matching': True
    }]

class TikTokTagIE(TikTokBaseListIE):
    IE_NAME = 'tiktok:tag'
    _VALID_URL = r'https?://(?:www\.)?tiktok\.com/tag/(?P<id>[^/?#&]+)'
    _WORKING = False
    _QUERY_NAME = 'ch_id'
    _API_ENDPOINT = 'challenge/aweme'
    _TESTS = [{
        'url': 'https://tiktok.com/tag/hello2018',
        'playlist_mincount': 39,
        'info_dict': {
            'id': '46294678',
            'title': 'hello2018',
        },
        'expected_warnings': ['Retrying']
    }, {
        'url': 'https://tiktok.com/tag/fypシ?is_copy_url=0&is_from_webapp=v1',
        'only_matching': True
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id, headers={
            'User-Agent': 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)'
        })
        tag_id = self._html_search_regex(r'snssdk\d*://challenge/detail/(\d+)', webpage, 'tag ID')
        return self.playlist_result(self._entries(tag_id, display_id), tag_id, display_id)

class DouyinIE(TikTokIE):
    _VALID_URL = r'https?://(?:www\.)?douyin\.com/video/(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.douyin.com/video/6961737553342991651',
        'md5': '10523312c8b8100f353620ac9dc8f067',
        'info_dict': {
            'id': '6961737553342991651',
            'ext': 'mp4',
            'title': '#杨超越  小小水手带你去远航❤',
            'uploader': '杨超越',
            'upload_date': '20210513',
            'timestamp': 1620905839,
            'uploader_id': '110403406559',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        }
    }, {
        'url': 'https://www.douyin.com/video/6982497745948921092',
        'md5': 'd78408c984b9b5102904cf6b6bc2d712',
        'info_dict': {
            'id': '6982497745948921092',
            'ext': 'mp4',
            'title': '这个夏日和小羊@杨超越 一起遇见白色幻想',
            'uploader': '杨超越工作室',
            'upload_date': '20210708',
            'timestamp': 1625739481,
            'uploader_id': '408654318141572',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        }
    }, {
        'url': 'https://www.douyin.com/video/6953975910773099811',
        'md5': '72e882e24f75064c218b76c8b713c185',
        'info_dict': {
            'id': '6953975910773099811',
            'ext': 'mp4',
            'title': '#一起看海  出现在你的夏日里',
            'uploader': '杨超越',
            'upload_date': '20210422',
            'timestamp': 1619098692,
            'uploader_id': '110403406559',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        }
    }, {
        'url': 'https://www.douyin.com/video/6950251282489675042',
        'md5': 'b4db86aec367ef810ddd38b1737d2fed',
        'info_dict': {
            'id': '6950251282489675042',
            'ext': 'mp4',
            'title': '哈哈哈，成功了哈哈哈哈哈哈',
            'uploader': '杨超越',
            'upload_date': '20210412',
            'timestamp': 1618231483,
            'uploader_id': '110403406559',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        }
    }, {
        'url': 'https://www.douyin.com/video/6963263655114722595',
        'md5': '1abe1c477d05ee62efb40bf2329957cf',
        'info_dict': {
            'id': '6963263655114722595',
            'ext': 'mp4',
            'title': '#哪个爱豆的105度最甜 换个角度看看我哈哈',
            'uploader': '杨超越',
            'upload_date': '20210517',
            'timestamp': 1621261163,
            'uploader_id': '110403406559',
            'view_count': int,
            'like_count': int,
            'repost_count': int,
            'comment_count': int,
        }
    }]
    _APP_VERSIONS = [('9.6.0', '960')]
    _APP_NAME = 'aweme'
    _AID = 1128
    _API_HOSTNAME = 'aweme.snssdk.com'
    _UPLOADER_URL_FORMAT = 'https://www.douyin.com/user/%s'
    _WEBPAGE_HOST = 'https://www.douyin.com/'

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

        try:
            return self._extract_aweme_app(video_id)
        except ExtractorError as e:
            self.report_warning(f'{e}; trying with webpage')

        webpage = self._download_webpage(url, video_id)
        render_data_json = self._search_regex(
            r'<script [^>]*\bid=[\'"]RENDER_DATA[\'"][^>]*>(%7B.+%7D)</script>',
            webpage, 'render data', default=None)
        if not render_data_json:
            # TODO: Run verification challenge code to generate signature cookies
            raise ExtractorError('Fresh cookies (not necessarily logged in) are needed')

        render_data = self._parse_json(
            render_data_json, video_id, transform_source=compat_urllib_parse_unquote)
        return self._parse_aweme_video_web(get_first(render_data, ('aweme', 'detail')), url)

class TikTokVMIE(InfoExtractor):
    _VALID_URL = r'https?://(?:vm|vt)\.tiktok\.com/(?P<id>\w+)'
    IE_NAME = 'vm.tiktok'

    _TESTS = [{
        'url': 'https://vm.tiktok.com/ZSe4FqkKd',
        'info_dict': {
            'id': '7023491746608712966',
            'ext': 'mp4',
            'title': 'md5:5607564db90271abbbf8294cca77eddd',
            'description': 'md5:5607564db90271abbbf8294cca77eddd',
            'duration': 11,
            'upload_date': '20211026',
            'uploader_id': '7007385080558846981',
            'creator': 'Memes',
            'artist': 'Memes',
            'track': 'original sound',
            'uploader': 'susmandem',
            'timestamp': 1635284105,
            'thumbnail': r're:https://.+\.webp.*',
            'like_count': int,
            'view_count': int,
            'comment_count': int,
            'repost_count': int,
            'uploader_url': 'https://www.tiktok.com/@MS4wLjABAAAAXcNoOEOxVyBzuII_E--T0MeCrLP0ay1Sm6x_n3dluiWEoWZD0VlQOytwad4W0i0n',
        }
    }, {
        'url': 'https://vt.tiktok.com/ZSe4FqkKd',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        new_url = self._request_webpage(
            HEADRequest(url), self._match_id(url), headers={'User-Agent': 'facebookexternalhit/1.1'}).geturl()
        if self.suitable(new_url):  # Prevent infinite loop in case redirect fails
            raise UnsupportedError(new_url)
        return self.url_result(new_url)


code:
import re

from .common import InfoExtractor
from ..utils import ExtractorError

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

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

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

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

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

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

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

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

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

repo_name: Lesmiscore/ytdl


code:
import re

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    get_element_by_attribute,
)

class TMZIE(InfoExtractor):
    _VALID_URL = r"https?://(?:www\.)?tmz\.com/.*"
    _TESTS = [
        {
            "url": "http://www.tmz.com/videos/0-cegprt2p/",
            "info_dict": {
                "id": "http://www.tmz.com/videos/0-cegprt2p/",
                "ext": "mp4",
                "title": "No Charges Against Hillary Clinton? Harvey Says It Ain't Over Yet",
                "description": "Harvey talks about Director Comey’s decision not to prosecute Hillary Clinton.",
                "timestamp": 1467831837,
                "uploader": "TMZ Staff",
                "upload_date": "20160706",
                "thumbnail": "https://imagez.tmz.com/image/5e/4by3/2016/07/06/5eea7dc01baa5c2e83eb06930c170e46_xl.jpg",
                "duration": 772.0,
            },
        },
        {
            "url": "https://www.tmz.com/videos/071119-chris-morgan-women-4590005-0-zcsejvcr/",
            "info_dict": {
                "id": "https://www.tmz.com/videos/071119-chris-morgan-women-4590005-0-zcsejvcr/",
                "ext": "mp4",
                "title": "Angry Bagel Shop Guy Says He Doesn't Trust Women",
                "description": "The enraged man who went viral for ranting about women on dating sites before getting ragdolled in a bagel shop is defending his misogyny ... he says it's women's fault in the first place.",
                "timestamp": 1562889485,
                "uploader": "TMZ Staff",
                "upload_date": "20190711",
                "thumbnail": "https://imagez.tmz.com/image/a8/4by3/2019/07/12/a85480d27b2f50a7bfea2322151d67a5_xl.jpg",
                "duration": 123.0,
            },
        },
        {
            "url": "http://www.tmz.com/2015/04/19/bobby-brown-bobbi-kristina-awake-video-concert",
            "md5": "5429c85db8bde39a473a56ca8c4c5602",
            "info_dict": {
                "id": "http://www.tmz.com/2015/04/19/bobby-brown-bobbi-kristina-awake-video-concert",
                "ext": "mp4",
                "title": "Bobby Brown Tells Crowd ... Bobbi Kristina is Awake",
                "description": 'Bobby Brown stunned his audience during a concert Saturday night, when he told the crowd, "Bobbi is awake.  She\'s watching me."',
                "timestamp": 1429467813,
                "uploader": "TMZ Staff",
                "upload_date": "20150419",
                "duration": 29.0,
                "thumbnail": "https://imagez.tmz.com/image/15/4by3/2015/04/20/1539c7ae136359fc979236fa6a9449dd_xl.jpg",
            },
        },
        {
            "url": "http://www.tmz.com/2015/09/19/patti-labelle-concert-fan-stripping-kicked-out-nicki-minaj/",
            "info_dict": {
                "id": "http://www.tmz.com/2015/09/19/patti-labelle-concert-fan-stripping-kicked-out-nicki-minaj/",
                "ext": "mp4",
                "title": "Patti LaBelle -- Goes Nuclear On Stripping Fan",
                "description": "Patti LaBelle made it known loud and clear last night ... NO "
                "ONE gets on her stage and strips down.",
                "timestamp": 1442683746,
                "uploader": "TMZ Staff",
                "upload_date": "20150919",
                "duration": 104.0,
                "thumbnail": "https://imagez.tmz.com/image/5e/4by3/2015/09/20/5e57d7575062528082994e18ac3f0f48_xl.jpg",
            },
        },
        {
            "url": "http://www.tmz.com/2016/01/28/adam-silver-sting-drake-blake-griffin/",
            "info_dict": {
                "id": "http://www.tmz.com/2016/01/28/adam-silver-sting-drake-blake-griffin/",
                "ext": "mp4",
                "title": "NBA's Adam Silver -- Blake Griffin's a Great Guy ... He'll Learn from This",
                "description": "Two pretty parts of this video with NBA Commish Adam Silver.",
                "timestamp": 1454010989,
                "uploader": "TMZ Staff",
                "upload_date": "20160128",
                "duration": 59.0,
                "thumbnail": "https://imagez.tmz.com/image/38/4by3/2016/01/29/3856e83e0beb57059ec412122b842fb1_xl.jpg",
            },
        },
        {
            "url": "http://www.tmz.com/2016/10/27/donald-trump-star-vandal-arrested-james-otis/",
            "info_dict": {
                "id": "http://www.tmz.com/2016/10/27/donald-trump-star-vandal-arrested-james-otis/",
                "ext": "mp4",
                "title": "Trump Star Vandal -- I'm Not Afraid of Donald or the Cops!",
                "description": "James Otis is the the guy who took a pickaxe to Donald Trump's star on the Walk of Fame, and he tells TMZ .. he's ready and willing to go to jail for the crime.",
                "timestamp": 1477500095,
                "uploader": "TMZ Staff",
                "upload_date": "20161026",
                "thumbnail": "https://imagez.tmz.com/image/0d/4by3/2016/10/27/0d904814d4a75dcf9cc3b8cfd1edc1a3_xl.jpg",
                "duration": 128.0,
            },
        },
        {
            "url": "https://www.tmz.com/videos/2020-10-31-103120-beverly-hills-protest-4878209/",
            "info_dict": {
                "id": "https://www.tmz.com/videos/2020-10-31-103120-beverly-hills-protest-4878209/",
                "ext": "mp4",
                "title": "Cops Use Billy Clubs Against Pro-Trump and Anti-Fascist "
                "Demonstrators",
                "description": "Beverly Hills may be an omen of what's coming next week, "
                "because things got crazy on the streets and cops started "
                "swinging their billy clubs at both Anti-Fascist and Pro-Trump "
                "demonstrators.",
                "timestamp": 1604182772,
                "uploader": "TMZ Staff",
                "upload_date": "20201031",
                "duration": 96.0,
                "thumbnail": "https://imagez.tmz.com/image/f3/4by3/2020/10/31/f37bd5a8aef84497866f425130c58be3_xl.jpg",
            },
        },
        {
            "url": "https://www.tmz.com/2020/11/05/gervonta-davis-car-crash-hit-and-run-police/",
            "info_dict": {
                "id": "Dddb6IGe-ws",
                "ext": "mp4",
                "title": "SICK LAMBO GERVONTA DAVIS IN HIS NEW RIDE RIGHT AFTER KO AFTER LEO  EsNews Boxing",
                "uploader": "ESNEWS",
                "description": "md5:49675bc58883ccf80474b8aa701e1064",
                "upload_date": "20201102",
                "uploader_id": "ESNEWS",
                "uploader_url": "http://www.youtube.com/user/ESNEWS",
                "like_count": int,
                "channel_id": "UCI-Oq7oFGakzSzHFlTtsUsQ",
                "channel": "ESNEWS",
                "view_count": int,
                "duration": 225,
                "live_status": "not_live",
                "thumbnail": "https://i.ytimg.com/vi_webp/Dddb6IGe-ws/maxresdefault.webp",
                "channel_url": "https://www.youtube.com/channel/UCI-Oq7oFGakzSzHFlTtsUsQ",
                "channel_follower_count": int,
                "playable_in_embed": True,
                "categories": ["Sports"],
                "age_limit": 0,
                "tags": "count:10",
                "availability": "public",
            },
        },
        {
            "url": "https://www.tmz.com/2020/11/19/conor-mcgregor-dustin-poirier-contract-fight-ufc-257-fight-island/",
            "info_dict": {
                "id": "1329450007125225473",
                "ext": "mp4",
                "title": "The Mac Life - BREAKING: Conor McGregor (@thenotoriousmma) has signed his bout agreement for his rematch with Dustin Poirier for January 23.",
                "uploader": "The Mac Life",
                "description": "md5:56e6009bbc3d12498e10d08a8e1f1c69",
                "upload_date": "20201119",
                "uploader_id": "TheMacLife",
                "timestamp": 1605800556,
                "thumbnail": "https://pbs.twimg.com/media/EnMmfT8XYAExgxJ.jpg?name=small",
                "like_count": int,
                "duration": 11.812,
                "uploader_url": "https://twitter.com/TheMacLife",
                "age_limit": 0,
                "repost_count": int,
                "tags": [],
                "comment_count": int,
            },
        },
    ]

    def _real_extract(self, url):
        webpage = self._download_webpage(url, url)
        jsonld = self._search_json_ld(webpage, url)
        if not jsonld or "url" not in jsonld:
            # try to extract from YouTube Player API
            # see https://developers.google.com/youtube/iframe_api_reference#Video_Queueing_Functions
            match_obj = re.search(r'\.cueVideoById\(\s*(?P<quote>[\'"])(?P<id>.*?)(?P=quote)', webpage)
            if match_obj:
                res = self.url_result(match_obj.group("id"))
                return res
            # try to extract from twitter
            blockquote_el = get_element_by_attribute("class", "twitter-tweet", webpage)
            if blockquote_el:
                matches = re.findall(
                    r'<a[^>]+href=\s*(?P<quote>[\'"])(?P<link>.*?)(?P=quote)',
                    blockquote_el)
                if matches:
                    for _, match in matches:
                        if "/status/" in match:
                            res = self.url_result(match)
                            return res
            raise ExtractorError("No video found!")
        if id not in jsonld:
            jsonld["id"] = url
        return jsonld


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

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

    def _extract_thumbnails(self, flix_xml):

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

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

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

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

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

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

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

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

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

        webpage = self._download_webpage(url, display_id)

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

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

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

        formats = []

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

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

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

        self._sort_formats(formats)

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

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

        age_limit = self._rta_search(webpage) or 18

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

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

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

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

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

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

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

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

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

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

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

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

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

    _HOST = 'emp'
    _VKEY_SUFFIX = '-1'

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

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

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

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


code:
import json
import re

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

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

    _API_USER = 'tvpapi_147'
    _API_PASS = '11111'

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

        params = {
            'initObj': {
                'Locale': {
                    'LocaleLanguage': '',
                    'LocaleCountry': '',
                    'LocaleDevice': '',
                    'LocaleUserState': 0
                },
                'Platform': 0,
                'SiteGuid': 0,
                'DomainID': '0',
                'UDID': '',
                'ApiUser': self._API_USER,
                'ApiPass': self._API_PASS
            },
            'MediaID': video_id,
            'mediaType': 0,
        }

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

        title = info['MediaName']

        formats = []
        for video_file in info.get('Files', []):
            video_url, vid_format = video_file.get('URL'), video_file.get('Format')
            if not video_url or video_url == 'NA' or not vid_format:
                continue
            ext = determine_ext(video_url)
            vid_format = vid_format.replace(' ', '')
            # if geo-restricted, m3u8 is inaccessible, but mp4 is okay
            if ext == 'm3u8':
                m3u8_formats = self._extract_m3u8_formats(
                    video_url, video_id, ext='mp4', m3u8_id=vid_format,
                    note='Downloading %s m3u8 information' % vid_format,
                    errnote='Failed to download %s m3u8 information' % vid_format,
                    fatal=False)
                for f in m3u8_formats:
                    # Apple FairPlay Streaming
                    if '/fpshls/' in f['url']:
                        continue
                    formats.append(f)
            elif ext == 'mpd':
                formats.extend(self._extract_mpd_formats(
                    video_url, video_id, mpd_id=vid_format,
                    note='Downloading %s MPD manifest' % vid_format,
                    errnote='Failed to download %s MPD manifest' % vid_format,
                    fatal=False))
            elif ext == 'ism':
                formats.extend(self._extract_ism_formats(
                    video_url, video_id, ism_id=vid_format,
                    note='Downloading %s ISM manifest' % vid_format,
                    errnote='Failed to download %s ISM manifest' % vid_format,
                    fatal=False))
            elif ext == 'mp4':
                formats.append({
                    'ext': ext,
                    'url': video_url,
                    'format_id': vid_format,
                })
        if not formats:
            for meta in (info.get('Metas') or []):
                if (not self.get_param('allow_unplayable_formats')
                        and meta.get('Key') == 'Encryption' and meta.get('Value') == '1'):
                    self.report_drm(video_id)
            # Most likely because geo-blocked if no formats and no DRM
        self._sort_formats(formats)

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

        def counter(prefix):
            return int_or_none(
                info.get(prefix + 'Counter') or info.get(prefix.lower() + '_counter'))

        return {
            'id': video_id,
            'title': title,
            'description': strip_or_none(info.get('Description')),
            'duration': int_or_none(info.get('Duration')),
            'timestamp': parse_iso8601(info.get('CreationDate') or None),
            'average_rating': float_or_none(info.get('Rating')),
            'view_count': counter('View'),
            'like_count': counter('Like'),
            'thumbnails': thumbnails,
            'formats': formats,
        }

class MeWatchIE(InfoExtractor):
    IE_NAME = 'mewatch'
    _VALID_URL = r'https?://(?:(?:www|live)\.)?mewatch\.sg/watch/[^/?#&]+-(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'https://www.mewatch.sg/watch/Recipe-Of-Life-E1-179371',
        'info_dict': {
            'id': '1008625',
            'ext': 'mp4',
            'title': 'Recipe Of Life 味之道',
            'timestamp': 1603306526,
            'description': 'md5:6e88cde8af2068444fc8e1bc3ebf257c',
            'upload_date': '20201021',
        },
        'params': {
            'skip_download': 'm3u8 download',
        },
    }, {
        'url': 'https://www.mewatch.sg/watch/Little-Red-Dot-Detectives-S2-搜密。打卡。小红点-S2-E1-176232',
        'only_matching': True,
    }, {
        'url': 'https://www.mewatch.sg/watch/Little-Red-Dot-Detectives-S2-%E6%90%9C%E5%AF%86%E3%80%82%E6%89%93%E5%8D%A1%E3%80%82%E5%B0%8F%E7%BA%A2%E7%82%B9-S2-E1-176232',
        'only_matching': True,
    }, {
        'url': 'https://live.mewatch.sg/watch/Recipe-Of-Life-E41-189759',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        item_id = self._match_id(url)
        custom_id = self._download_json(
            'https://cdn.mewatch.sg/api/items/' + item_id,
            item_id, query={'segments': 'all'})['customId']
        return self.url_result(
            'toggle:' + custom_id, ToggleIE.ie_key(), custom_id)


code:
from .common import InfoExtractor
from ..utils import int_or_none, parse_qs

class ToggoIE(InfoExtractor):
    IE_NAME = 'toggo'
    _VALID_URL = r'https?://(?:www\.)?toggo\.de/(?:toggolino/)?[^/?#]+/(?:folge|video)/(?P<id>[^/?#]+)'
    _TESTS = [{
        'url': 'https://www.toggo.de/weihnachtsmann--co-kg/folge/ein-geschenk-fuer-zwei',
        'info_dict': {
            'id': 'VEP2977',
            'ext': 'mp4',
            'title': 'Ein Geschenk für zwei',
            'display_id': 'ein-geschenk-fuer-zwei',
            'thumbnail': r're:^https?://.*\.(?:jpg|png)',
            'description': 'md5:b7715915bfa47824b4e4ad33fb5962f8',
            'release_timestamp': 1637259179,
            'series': 'Weihnachtsmann & Co. KG',
            'season': 'Weihnachtsmann & Co. KG',
            'season_number': 1,
            'season_id': 'VST118',
            'episode': 'Ein Geschenk für zwei',
            'episode_number': 7,
            'episode_id': 'VEP2977',
            'timestamp': 1581935960,
            'uploader_id': '6057955896001',
            'upload_date': '20200217',
        },
        'params': {'skip_download': True},
    }, {
        'url': 'https://www.toggo.de/grizzy--die-lemminge/folge/ab-durch-die-wand-vogelfrei-rock\'n\'lemming',
        'only_matching': True,
    }, {
        'url': 'https://www.toggo.de/toggolino/paw-patrol/folge/der-wetter-zeppelin-der-chili-kochwettbewerb',
        'only_matching': True,
    }, {
        'url': 'https://www.toggo.de/toggolino/paw-patrol/video/paw-patrol-rettung-im-anflug',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        data = self._download_json(
            f'https://production-n.toggo.de/api/assetstore/vod/asset/{display_id}', display_id)['data']

        brightcove_id = next(
            x['value'] for x in data['custom_fields'] if x.get('key') == 'video-cloud-id')
        info = self._downloader.get_info_extractor('BrightcoveNew').extract(
            f'http://players.brightcove.net/6057955896001/default_default/index.html?videoId={brightcove_id}')

        for f in info['formats']:
            if '/dash/live/cenc/' in f.get('fragment_base_url', ''):
                # Get hidden non-DRM format
                f['fragment_base_url'] = f['fragment_base_url'].replace('/cenc/', '/clear/')
                f['has_drm'] = False

            if '/fairplay/' in f.get('manifest_url', ''):
                f['has_drm'] = True

        thumbnails = [{
            'id': name,
            'url': url,
            'width': int_or_none(next(iter(parse_qs(url).get('width', [])), None)),
        } for name, url in (data.get('images') or {}).items()]

        return {
            **info,
            'id': data.get('id'),
            'display_id': display_id,
            'title': data.get('title'),
            'language': data.get('language'),
            'thumbnails': thumbnails,
            'description': data.get('description'),
            'release_timestamp': data.get('earliest_start_date'),
            'series': data.get('series_title'),
            'season': data.get('season_title'),
            'season_number': data.get('season_no'),
            'season_id': data.get('season_id'),
            'episode': data.get('title'),
            'episode_number': data.get('episode_no'),
            'episode_id': data.get('id'),
        }


code:
import functools
import re

from .common import InfoExtractor
from ..utils import (
    clean_html,
    get_element_by_class,
    parse_count,
    remove_end,
    unified_strdate,
    js_to_json,
    OnDemandPagedList,
)

class TokentubeIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?tokentube\.net/(?:view\?[vl]=|[vl]/)(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://tokentube.net/l/3236632011/Praise-A-Thon-Pastori-Chrisin-ja-Pastori-Bennyn-kanssa-27-8-2021',
        'info_dict': {
            'id': '3236632011',
            'ext': 'mp4',
            'title': 'Praise-A-Thon Pastori Chrisin ja Pastori Bennyn kanssa 27.8.2021',
            'description': '',
            'uploader': 'Pastori Chris - Rapsodia.fi',
            'upload_date': '20210827',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://tokentube.net/v/3950239124/Linux-Ubuntu-Studio-perus-k%C3%A4ytt%C3%B6',
        'md5': '0e1f00421f501f5eada9890d38fcfb56',
        'info_dict': {
            'id': '3950239124',
            'ext': 'mp4',
            'title': 'Linux Ubuntu Studio perus käyttö',
            'description': 'md5:46077d0daaba1974f2dc381257f9d64c',
            'uploader': 'jyrilehtonen',
            'upload_date': '20210825',
        },
    }, {
        'url': 'https://tokentube.net/view?v=3582463289',
        'info_dict': {
            'id': '3582463289',
            'ext': 'mp4',
            'title': 'Police for Freedom - toiminta aloitetaan Suomessa ❤??',
            'description': 'md5:37ebf1cb44264e0bf23ed98b337ee63e',
            'uploader': 'Voitontie',
            'upload_date': '20210428',
        }
    }]

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

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

        data_json = self._html_search_regex(r'({["\']html5["\'].+?}}}+)', webpage, 'data json')
        data_json = self._parse_json(js_to_json(data_json), video_id, fatal=False)

        sources = data_json.get('sources') or self._parse_json(
            self._html_search_regex(r'updateSrc\(([^\)]+)\)', webpage, 'sources'),
            video_id, transform_source=js_to_json)

        formats = [{
            'url': format.get('src'),
            'format_id': format.get('label'),
            'height': format.get('res'),
        } for format in sources]

        view_count = parse_count(self._html_search_regex(
            r'
\s*([\d\.,]+)\s*views?
',
            webpage, 'view_count', fatal=False))

        like_count = parse_count(self._html_search_regex(
            r'
\s*(\d+)\s*
',
            webpage, 'like count', fatal=False))

        dislike_count = parse_count(self._html_search_regex(
            r'
\s*(\d+)\s*
',
            webpage, 'dislike count', fatal=False))

        upload_date = unified_strdate(self._html_search_regex(
            r'Published\s*on\s+([^<]+)',
            webpage, 'upload date', fatal=False))

        uploader = self._html_search_regex(
            r']+>(.+?)',
            webpage, 'uploader', fatal=False)

        description = (clean_html(get_element_by_class('p-d-txt', webpage))
                       or self._html_search_meta(('og:description', 'description', 'twitter:description'), webpage))

        description = remove_end(description, 'Category')

        self._sort_formats(formats)

        return {
            'id': video_id,
            'formats': formats,
            'title': title,
            'view_count': view_count,
            'like_count': like_count,
            'dislike_count': dislike_count,
            'upload_date': upload_date,
            'description': description,
            'uploader': uploader,
        }

class TokentubeChannelIE(InfoExtractor):
    _PAGE_SIZE = 20
    IE_NAME = 'Tokentube:channel'
    _VALID_URL = r'https?://(?:www\.)?tokentube\.net/channel/(?P<id>\d+)/[^/]+(?:/videos)?'
    _TESTS = [{
        'url': 'https://tokentube.net/channel/3697658904/TokenTube',
        'info_dict': {
            'id': '3697658904',
        },
        'playlist_mincount': 7,
    }, {
        'url': 'https://tokentube.net/channel/3353234420/Linux/videos',
        'info_dict': {
            'id': '3353234420',
        },
        'playlist_mincount': 20,
    }, {
        'url': 'https://tokentube.net/channel/3475834195/Voitontie',
        'info_dict': {
            'id': '3475834195',
        },
        'playlist_mincount': 150,
    }]

    def _fetch_page(self, channel_id, page):
        page += 1
        videos_info = self._download_webpage(
            f'https://tokentube.net/videos?p=0&m=1&sort=recent&u={channel_id}&page={page}',
            channel_id, headers={'X-Requested-With': 'XMLHttpRequest'},
            note=f'Downloading page {page}', fatal=False)
        if ' Sorry, no results were found.' not in videos_info:
            for path, media_id in re.findall(
                    r']+\bhref=["\']([^"\']+/[lv]/(\d+)/\S+)["\'][^>]+>',
                    videos_info):
                yield self.url_result(path, ie=TokentubeIE.ie_key(), video_id=media_id)

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

        entries = OnDemandPagedList(functools.partial(
            self._fetch_page, channel_id), self._PAGE_SIZE)

        return self.playlist_result(entries, channel_id)


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

import re
import functools
import urllib.parse

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    OnDemandPagedList,
    try_get,
)

class TokyoMotionBaseIE(InfoExtractor):
    _COMMON_HEADERS = {
        'Accept-Encoding': '*',
    }

    @staticmethod
    def _extract_video_urls(variant, webpage):
        return ('https://www.%smotion.net%s' % (variant, urllib.parse.quote(frg.group()))
                for frg in re.finditer(r'/video/(?P<id>\d+)/[^#?&"\']+', webpage))

    def _do_paging(self, variant, user_id, index):
        index += 1
        newurl = self._PAGING_BASE_TEMPLATE % (variant, user_id, index)
        webpage = self._download_webpage(newurl, user_id, headers=self._COMMON_HEADERS, note='Downloading page %d' % index)

        yield from [self.url_result(url) for url in self._extract_video_urls(variant, webpage)][::2]

class TokyoMotionPlaylistBaseIE(TokyoMotionBaseIE):
    def _real_extract(self, url):
        user_id = self._match_id(url)
        matches = OnDemandPagedList(functools.partial(self._do_paging, self._VARIANT, user_id), 18)
        return self.playlist_result(matches, user_id, self._TITLE_TEMPLATE % user_id)

class UnvariantedMotionIE(TokyoMotionBaseIE):
    IE_NAME = '%smotion'
    _VALID_URL = r'https?://(?:www\.)?%smotion\.net/video/(?P<id>\d+)(?P<excess>/[^#\?]+)?'

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)
        url, video_id, excess = mobj.group(0, 'id', 'excess')
        if not excess:
            # fix URL silently
            url = url.split('#')[0]
            if not url.endswith('/'):
                url += '/'
            url += 'a'

        webpage = self._download_webpage(url, video_id, headers=self._COMMON_HEADERS)
        title = self._og_search_title(webpage, default=None)

        entry = try_get(
            webpage,
            lambda x: self._parse_html5_media_entries(url, x, video_id, m3u8_id='hls')[0],
            dict)
        if not entry:
            raise ExtractorError('This is a private video.', expected=True)

        for fmt in entry['formats']:
            # TODO: maybe Range can be used again
            fmt['external_downloader'] = 'ffmpeg'
            fmt['preference'] = 1 if fmt['format_id'] == 'HD' else -1

        self._sort_formats(entry['formats'], ('preference', ))
        entry.update({
            'id': video_id,
            'title': title,
            'age_limit': 18,
        })
        return entry

class UnvariantedMotionUserIE(TokyoMotionPlaylistBaseIE):
    IE_NAME = '%smotion:user'
    _VALID_URL = r'https?://(?:www\.)?%smotion\.net/user/(?P<id>[^/]+)(?:/videos)?'
    _PAGING_BASE_TEMPLATE = 'https://www.%smotion.net/user/%s/videos?page=%d'
    _TITLE_TEMPLATE = 'Uploads from %s'

class UnvariantedMotionUserFavsIE(TokyoMotionPlaylistBaseIE):
    IE_NAME = '%smotion:user:favs'
    _VALID_URL = r'https?://(?:www\.)?%smotion\.net/user/(?P<id>[^/]+)/favorite/videos'
    _PAGING_BASE_TEMPLATE = 'https://www.%smotion.net/user/%s/favorite/videos?page=%d'
    _TITLE_TEMPLATE = 'Favorites from %s'

class UnvariantedMotionSearchesIE(TokyoMotionPlaylistBaseIE):
    IE_NAME = '%smotion:searches'
    _VALID_URL = r'https?://(?:www\.)?%smotion\.net/search\?search_query=(?P<id>[^/&]+)(?:&search_type=videos)?(?:&page=\d+)?'
    _PAGING_BASE_TEMPLATE = 'https://www.%smotion.net/search?search_query=%s&search_type=videos&page=%d'
    _TITLE_TEMPLATE = 'Search results for %s'

class TokyoMotionIE(UnvariantedMotionIE):
    _VARIANT = 'tokyo'

class TokyoMotionUserIE(UnvariantedMotionUserIE):
    _VARIANT = 'tokyo'

class TokyoMotionUserFavsIE(UnvariantedMotionUserFavsIE):
    _VARIANT = 'tokyo'

class TokyoMotionSearchesIE(UnvariantedMotionSearchesIE):
    _VARIANT = 'tokyo'

class OsakaMotionIE(UnvariantedMotionIE):
    _VARIANT = 'osaka'

class OsakaMotionUserIE(UnvariantedMotionUserIE):
    _VARIANT = 'osaka'

class OsakaMotionUserFavsIE(UnvariantedMotionUserFavsIE):
    _VARIANT = 'osaka'

class OsakaMotionSearchesIE(UnvariantedMotionSearchesIE):
    _VARIANT = 'osaka'

class TokyoMotionScannerIE(TokyoMotionBaseIE):
    IE_DESC = False  # Do not list
    IE_NAME = '%smotion:scanner'
    _VARIANT = 'tokyo'
    _VALID_URL = r'tmscan:https?://(?:www\.)?%smotion\.net/(?P<id>.*)'
    _TESTS = []

    def _real_extract(self, url):
        user_id = self._match_valid_url(url).groups()
        webpage = self._download_webpage(url[7:], user_id, headers=self._COMMON_HEADERS)
        matches = self._extract_video_urls(self._VARIANT, webpage)
        return self.playlist_result(
            (self.url_result(url) for url in matches),
            user_id, 'Scanned results for %s' % url)

for k, v in list(locals().items()):
    if not isinstance(v, type):
        continue
    if not getattr(v, '_VARIANT', None):
        continue
    v.IE_NAME = v.IE_NAME % v._VARIANT
    v._VALID_URL = v._VALID_URL % v._VARIANT


code:
from .common import InfoExtractor
from ..utils import int_or_none, join_nonempty

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

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

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

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

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


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

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

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

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

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

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


code:
import json

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

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

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

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

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

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


code:
from .common import InfoExtractor
import re

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

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

        webpage = self._download_webpage(url, video_id)

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

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

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

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

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

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

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

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

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


code:
import re

from .common import InfoExtractor

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

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

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

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

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

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

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

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