code:
import itertools
import json
import math

from .common import InfoExtractor
from ..compat import compat_urllib_parse_unquote
from ..utils import (
    determine_ext,
    format_field,
    int_or_none,
    str_or_none,
    traverse_obj,
    try_get
)

class GameJoltBaseIE(InfoExtractor):
    _API_BASE = 'https://gamejolt.com/site-api/'

    def _call_api(self, endpoint, *args, **kwargs):
        kwargs.setdefault('headers', {}).update({'Accept': 'image/webp,*/*'})
        return self._download_json(self._API_BASE + endpoint, *args, **kwargs)['payload']

    def _parse_content_as_text(self, content):
        outer_contents, joined_contents = content.get('content') or [], []
        for outer_content in outer_contents:
            if outer_content.get('type') != 'paragraph':
                joined_contents.append(self._parse_content_as_text(outer_content))
                continue
            inner_contents, inner_content_text = outer_content.get('content') or [], ''
            for inner_content in inner_contents:
                if inner_content.get('text'):
                    inner_content_text += inner_content['text']
                elif inner_content.get('type') == 'hardBreak':
                    inner_content_text += '\n'
            joined_contents.append(inner_content_text)

        return '\n'.join(joined_contents)

    def _get_comments(self, post_num_id, post_hash_id):
        sort_by, scroll_id = self._configuration_arg('comment_sort', ['hot'], ie_key=GameJoltIE.ie_key())[0], -1
        is_scrolled = sort_by in ('new', 'you')
        for page in itertools.count(1):
            comments_data = self._call_api(
                'comments/Fireside_Post/%s/%s?%s=%d' % (
                    post_num_id, sort_by,
                    'scroll_id' if is_scrolled else 'page', scroll_id if is_scrolled else page),
                post_hash_id, note='Downloading comments list page %d' % page)
            if not comments_data.get('comments'):
                break
            for comment in traverse_obj(comments_data, (('comments', 'childComments'), ...), expected_type=dict, default=[]):
                yield {
                    'id': comment['id'],
                    'text': self._parse_content_as_text(
                        self._parse_json(comment['comment_content'], post_hash_id)),
                    'timestamp': int_or_none(comment.get('posted_on'), scale=1000),
                    'like_count': comment.get('votes'),
                    'author': traverse_obj(comment, ('user', ('display_name', 'name')), expected_type=str_or_none, get_all=False),
                    'author_id': traverse_obj(comment, ('user', 'username'), expected_type=str_or_none),
                    'author_thumbnail': traverse_obj(comment, ('user', 'image_avatar'), expected_type=str_or_none),
                    'parent': comment.get('parent_id') or None,
                }
            scroll_id = int_or_none(comments_data['comments'][-1].get('posted_on'))

    def _parse_post(self, post_data):
        post_id = post_data['hash']
        lead_content = self._parse_json(post_data.get('lead_content') or '{}', post_id, fatal=False) or {}
        description, full_description = post_data.get('leadStr') or self._parse_content_as_text(
            self._parse_json(post_data.get('lead_content'), post_id)), None
        if post_data.get('has_article'):
            article_content = self._parse_json(
                post_data.get('article_content')
                or self._call_api(f'web/posts/article/{post_data.get("id", post_id)}', post_id,
                                  note='Downloading article metadata', errnote='Unable to download article metadata', fatal=False).get('article'),
                post_id, fatal=False)
            full_description = self._parse_content_as_text(article_content)

        user_data = post_data.get('user') or {}
        info_dict = {
            'extractor_key': GameJoltIE.ie_key(),
            'extractor': 'GameJolt',
            'webpage_url': str_or_none(post_data.get('url')) or f'https://gamejolt.com/p/{post_id}',
            'id': post_id,
            'title': description,
            'description': full_description or description,
            'display_id': post_data.get('slug'),
            'uploader': user_data.get('display_name') or user_data.get('name'),
            'uploader_id': user_data.get('username'),
            'uploader_url': format_field(user_data, 'url', 'https://gamejolt.com%s'),
            'categories': [try_get(category, lambda x: '%s - %s' % (x['community']['name'], x['channel'].get('display_title') or x['channel']['title']))
                           for category in post_data.get('communities' or [])],
            'tags': traverse_obj(
                lead_content, ('content', ..., 'content', ..., 'marks', ..., 'attrs', 'tag'), expected_type=str_or_none),
            'like_count': int_or_none(post_data.get('like_count')),
            'comment_count': int_or_none(post_data.get('comment_count'), default=0),
            'timestamp': int_or_none(post_data.get('added_on'), scale=1000),
            'release_timestamp': int_or_none(post_data.get('published_on'), scale=1000),
            '__post_extractor': self.extract_comments(post_data.get('id'), post_id)
        }

        # TODO: Handle multiple videos/embeds?
        video_data = traverse_obj(post_data, ('videos', ...), expected_type=dict, get_all=False) or {}
        formats, subtitles, thumbnails = [], {}, []
        for media in video_data.get('media') or []:
            media_url, mimetype, ext, media_id = media['img_url'], media.get('filetype', ''), determine_ext(media['img_url']), media.get('type')
            if mimetype == 'application/vnd.apple.mpegurl' or ext == 'm3u8':
                hls_formats, hls_subs = self._extract_m3u8_formats_and_subtitles(media_url, post_id, 'mp4', m3u8_id=media_id)
                formats.extend(hls_formats)
                subtitles.update(hls_subs)
            elif mimetype == 'application/dash+xml' or ext == 'mpd':
                dash_formats, dash_subs = self._extract_mpd_formats_and_subtitles(media_url, post_id, mpd_id=media_id)
                formats.extend(dash_formats)
                subtitles.update(dash_subs)
            elif 'image' in mimetype:
                thumbnails.append({
                    'id': media_id,
                    'url': media_url,
                    'width': media.get('width'),
                    'height': media.get('height'),
                    'filesize': media.get('filesize'),
                })
            else:
                formats.append({
                    'format_id': media_id,
                    'url': media_url,
                    'width': media.get('width'),
                    'height': media.get('height'),
                    'filesize': media.get('filesize'),
                    'acodec': 'none' if 'video-card' in media_url else None,
                })

        if formats:
            return {
                **info_dict,
                'formats': formats,
                'subtitles': subtitles,
                'thumbnails': thumbnails,
                'view_count': int_or_none(video_data.get('view_count')),
            }

        gif_entries = []
        for media in post_data.get('media', []):
            if determine_ext(media['img_url']) != 'gif' or 'gif' not in media.get('filetype', ''):
                continue
            gif_entries.append({
                'id': media['hash'],
                'title': media['filename'].split('.')[0],
                'formats': [{
                    'format_id': url_key,
                    'url': media[url_key],
                    'width': media.get('width') if url_key == 'img_url' else None,
                    'height': media.get('height') if url_key == 'img_url' else None,
                    'filesize': media.get('filesize') if url_key == 'img_url' else None,
                    'acodec': 'none',
                } for url_key in ('img_url', 'mediaserver_url', 'mediaserver_url_mp4', 'mediaserver_url_webm') if media.get(url_key)]
            })
        if gif_entries:
            return {
                '_type': 'playlist',
                **info_dict,
                'entries': gif_entries,
            }

        embed_url = traverse_obj(post_data, ('embeds', ..., 'url'), expected_type=str_or_none, get_all=False)
        if embed_url:
            return self.url_result(embed_url)
        return info_dict

class GameJoltIE(GameJoltBaseIE):
    _VALID_URL = r'https?://(?:www\.)?gamejolt\.com/p/(?:[\w-]*-)?(?P<id>\w{8})'
    _TESTS = [{
        # No audio
        'url': 'https://gamejolt.com/p/introducing-ramses-jackson-some-fnf-himbo-i-ve-been-animating-fo-c6achnzu',
        'md5': 'cd5f733258f6678b0ce500dd88166d86',
        'info_dict': {
            'id': 'c6achnzu',
            'ext': 'mp4',
            'display_id': 'introducing-ramses-jackson-some-fnf-himbo-i-ve-been-animating-fo-c6achnzu',
            'title': 'Introducing Ramses Jackson, some FNF himbo I’ve been animating for the past few days, hehe.\n#fnfmod #fridaynightfunkin',
            'description': 'Introducing Ramses Jackson, some FNF himbo I’ve been animating for the past few days, hehe.\n#fnfmod #fridaynightfunkin',
            'uploader': 'Jakeneutron',
            'uploader_id': 'Jakeneutron',
            'uploader_url': 'https://gamejolt.com/@Jakeneutron',
            'categories': ['Friday Night Funkin\' - Videos'],
            'tags': ['fnfmod', 'fridaynightfunkin'],
            'timestamp': 1633499590,
            'upload_date': '20211006',
            'release_timestamp': 1633499655,
            'release_date': '20211006',
            'thumbnail': 're:^https?://.+wgch9mhq.png$',
            'like_count': int,
            'comment_count': int,
            'view_count': int,
        }
    }, {
        # YouTube embed
        'url': 'https://gamejolt.com/p/hey-hey-if-there-s-anyone-who-s-looking-to-get-into-learning-a-n6g4jzpq',
        'md5': '79a931ff500a5c783ef6c3bda3272e32',
        'info_dict': {
            'id': 'XsNA_mzC0q4',
            'title': 'Adobe Animate CC 2021 Tutorial || Part 1 - The Basics',
            'description': 'md5:9d1ab9e2625b3fe1f42b2a44c67fdd13',
            'uploader': 'Jakeneutron',
            'uploader_id': 'Jakeneutron',
            'uploader_url': 'http://www.youtube.com/user/Jakeneutron',
            'ext': 'mp4',
            'duration': 1749,
            'tags': ['Adobe Animate CC', 'Tutorial', 'Animation', 'The Basics', 'For Beginners'],
            'like_count': int,
            'playable_in_embed': True,
            'categories': ['Education'],
            'availability': 'public',
            'thumbnail': 'https://i.ytimg.com/vi_webp/XsNA_mzC0q4/maxresdefault.webp',
            'age_limit': 0,
            'live_status': 'not_live',
            'channel_url': 'https://www.youtube.com/channel/UC6_L7fnczNalFZyBthUE9oA',
            'channel': 'Jakeneutron',
            'channel_id': 'UC6_L7fnczNalFZyBthUE9oA',
            'upload_date': '20211015',
            'view_count': int,
            'chapters': 'count:18',
        }
    }, {
        # Article
        'url': 'https://gamejolt.com/p/i-fuckin-broke-chaos-d56h3eue',
        'md5': '786c1ccf98fde02c03a2768acb4258d0',
        'info_dict': {
            'id': 'd56h3eue',
            'ext': 'mp4',
            'display_id': 'i-fuckin-broke-chaos-d56h3eue',
            'title': 'I fuckin broke Chaos.',
            'description': 'I moved my tab durning the cutscene so now it\'s stuck like this.',
            'uploader': 'Jeff____________',
            'uploader_id': 'The_Nyesh_Man',
            'uploader_url': 'https://gamejolt.com/@The_Nyesh_Man',
            'categories': ['Friday Night Funkin\' - Videos'],
            'timestamp': 1639800264,
            'upload_date': '20211218',
            'release_timestamp': 1639800330,
            'release_date': '20211218',
            'thumbnail': 're:^https?://.+euksy8bd.png$',
            'like_count': int,
            'comment_count': int,
            'view_count': int,
        }
    }, {
        # Single GIF
        'url': 'https://gamejolt.com/p/hello-everyone-i-m-developing-a-pixel-art-style-mod-for-fnf-and-i-vs4gdrd8',
        'info_dict': {
            'id': 'vs4gdrd8',
            'display_id': 'hello-everyone-i-m-developing-a-pixel-art-style-mod-for-fnf-and-i-vs4gdrd8',
            'title': 'md5:cc3d8b031d9bc7ec2ec5a9ffc707e1f9',
            'description': 'md5:cc3d8b031d9bc7ec2ec5a9ffc707e1f9',
            'uploader': 'Quesoguy',
            'uploader_id': 'CheeseguyDev',
            'uploader_url': 'https://gamejolt.com/@CheeseguyDev',
            'categories': ['Game Dev - General', 'Arts n\' Crafts - Creations', 'Pixel Art - showcase',
                           'Friday Night Funkin\' - Mods', 'Newgrounds - Friday Night Funkin (13+)'],
            'timestamp': 1639517122,
            'release_timestamp': 1639519966,
            'like_count': int,
            'comment_count': int,
        },
        'playlist': [{
            'info_dict': {
                'id': 'dszyjnwi',
                'ext': 'webm',
                'title': 'gif-presentacion-mejorado-dszyjnwi',
                'n_entries': 1,
            }
        }]
    }, {
        # Multiple GIFs
        'url': 'https://gamejolt.com/p/gif-yhsqkumq',
        'playlist_count': 35,
        'info_dict': {
            'id': 'yhsqkumq',
            'display_id': 'gif-yhsqkumq',
            'title': 'GIF',
            'description': 'GIF',
            'uploader': 'DaniilTvman',
            'uploader_id': 'DaniilTvman',
            'uploader_url': 'https://gamejolt.com/@DaniilTvman',
            'categories': ['Five Nights At The AGK Studio Comunity - NEWS game'],
            'timestamp': 1638721559,
            'release_timestamp': 1638722276,
            'like_count': int,
            'comment_count': int,
        },
    }]

    def _real_extract(self, url):
        post_id = self._match_id(url)
        post_data = self._call_api(
            f'web/posts/view/{post_id}', post_id)['post']
        return self._parse_post(post_data)

class GameJoltPostListBaseIE(GameJoltBaseIE):
    def _entries(self, endpoint, list_id, note='Downloading post list', errnote='Unable to download post list', initial_items=[]):
        page_num, scroll_id = 1, None
        items = initial_items or self._call_api(endpoint, list_id, note=note, errnote=errnote)['items']
        while items:
            for item in items:
                yield self._parse_post(item['action_resource_model'])
            scroll_id = items[-1]['scroll_id']
            page_num += 1
            items = self._call_api(
                endpoint, list_id, note=f'{note} page {page_num}', errnote=errnote, data=json.dumps({
                    'scrollDirection': 'from',
                    'scrollId': scroll_id,
                }).encode('utf-8')).get('items')

class GameJoltUserIE(GameJoltPostListBaseIE):
    _VALID_URL = r'https?://(?:www\.)?gamejolt\.com/@(?P<id>[\w-]+)'
    _TESTS = [{
        'url': 'https://gamejolt.com/@BlazikenSuperStar',
        'playlist_mincount': 1,
        'info_dict': {
            'id': '6116784',
            'title': 'S. Blaze',
            'description': 'md5:5ba7fbbb549e8ea2545aafbfe22eb03a',
        },
        'params': {
            'ignore_no_formats_error': True,
        },
        'expected_warnings': ['skipping format', 'No video formats found', 'Requested format is not available'],
    }]

    def _real_extract(self, url):
        user_id = self._match_id(url)
        user_data = self._call_api(
            f'web/profile/@{user_id}', user_id, note='Downloading user info', errnote='Unable to download user info')['user']
        bio = self._parse_content_as_text(
            self._parse_json(user_data.get('bio_content', '{}'), user_id, fatal=False) or {})
        return self.playlist_result(
            self._entries(f'web/posts/fetch/user/@{user_id}?tab=active', user_id, 'Downloading user posts', 'Unable to download user posts'),
            str_or_none(user_data.get('id')), user_data.get('display_name') or user_data.get('name'), bio)

class GameJoltGameIE(GameJoltPostListBaseIE):
    _VALID_URL = r'https?://(?:www\.)?gamejolt\.com/games/[\w-]+/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://gamejolt.com/games/Friday4Fun/655124',
        'playlist_mincount': 2,
        'info_dict': {
            'id': '655124',
            'title': 'Friday Night Funkin\': Friday 4 Fun',
            'description': 'md5:576a7dd87912a2dcf33c50d2bd3966d3'
        },
        'params': {
            'ignore_no_formats_error': True,
        },
        'expected_warnings': ['skipping format', 'No video formats found', 'Requested format is not available'],
    }]

    def _real_extract(self, url):
        game_id = self._match_id(url)
        game_data = self._call_api(
            f'web/discover/games/{game_id}', game_id, note='Downloading game info', errnote='Unable to download game info')['game']
        description = self._parse_content_as_text(
            self._parse_json(game_data.get('description_content', '{}'), game_id, fatal=False) or {})
        return self.playlist_result(
            self._entries(f'web/posts/fetch/game/{game_id}', game_id, 'Downloading game posts', 'Unable to download game posts'),
            game_id, game_data.get('title'), description)

class GameJoltGameSoundtrackIE(GameJoltBaseIE):
    _VALID_URL = r'https?://(?:www\.)?gamejolt\.com/get/soundtrack(?:\?|\#!?)(?:.*?[&;])??game=(?P<id>(?:\d+)+)'
    _TESTS = [{
        'url': 'https://gamejolt.com/get/soundtrack?foo=bar&game=657899',
        'info_dict': {
            'id': '657899',
            'title': 'Friday Night Funkin\': Vs Oswald',
            'n_entries': None,
        },
        'playlist': [{
            'info_dict': {
                'id': '184434',
                'ext': 'mp3',
                'title': 'Gettin\' Lucky (Menu Music)',
                'url': r're:^https://.+vs-oswald-menu-music\.mp3$',
                'release_timestamp': 1635190816,
                'release_date': '20211025',
                'n_entries': 3,
            }
        }, {
            'info_dict': {
                'id': '184435',
                'ext': 'mp3',
                'title': 'Rabbit\'s Luck (Extended Version)',
                'url': r're:^https://.+rabbit-s-luck--full-version-\.mp3$',
                'release_timestamp': 1635190841,
                'release_date': '20211025',
                'n_entries': 3,
            }
        }, {
            'info_dict': {
                'id': '185228',
                'ext': 'mp3',
                'title': 'Last Straw',
                'url': r're:^https://.+last-straw\.mp3$',
                'release_timestamp': 1635881104,
                'release_date': '20211102',
                'n_entries': 3,
            }
        }]
    }]

    def _real_extract(self, url):
        game_id = self._match_id(url)
        game_overview = self._call_api(
            f'web/discover/games/overview/{game_id}', game_id, note='Downloading soundtrack info', errnote='Unable to download soundtrack info')
        return self.playlist_result([{
            'id': str_or_none(song.get('id')),
            'title': str_or_none(song.get('title')),
            'url': str_or_none(song.get('url')),
            'release_timestamp': int_or_none(song.get('posted_on'), scale=1000),
        } for song in game_overview.get('songs') or []], game_id, traverse_obj(
            game_overview, ('microdata', 'name'), (('twitter', 'fb'), 'title'), expected_type=str_or_none, get_all=False))

class GameJoltCommunityIE(GameJoltPostListBaseIE):
    _VALID_URL = r'https?://(?:www\.)?gamejolt\.com/c/(?P<id>(?P<community>[\w-]+)(?:/(?P<channel>[\w-]+))?)(?:(?:\?|\#!?)(?:.*?[&;])??sort=(?P<sort>\w+))?'
    _TESTS = [{
        'url': 'https://gamejolt.com/c/fnf/videos',
        'playlist_mincount': 50,
        'info_dict': {
            'id': 'fnf/videos',
            'title': 'Friday Night Funkin\' - Videos',
            'description': 'md5:6d8c06f27460f7d35c1554757ffe53c8'
        },
        'params': {
            'playlistend': 50,
            'ignore_no_formats_error': True,
        },
        'expected_warnings': ['skipping format', 'No video formats found', 'Requested format is not available'],
    }, {
        'url': 'https://gamejolt.com/c/youtubers',
        'playlist_mincount': 50,
        'info_dict': {
            'id': 'youtubers/featured',
            'title': 'Youtubers - featured',
            'description': 'md5:53e5582c93dcc467ab597bfca4db17d4'
        },
        'params': {
            'playlistend': 50,
            'ignore_no_formats_error': True,
        },
        'expected_warnings': ['skipping format', 'No video formats found', 'Requested format is not available'],
    }]

    def _real_extract(self, url):
        display_id, community_id, channel_id, sort_by = self._match_valid_url(url).group('id', 'community', 'channel', 'sort')
        channel_id, sort_by = channel_id or 'featured', sort_by or 'new'

        community_data = self._call_api(
            f'web/communities/view/{community_id}', display_id,
            note='Downloading community info', errnote='Unable to download community info')['community']
        channel_data = traverse_obj(self._call_api(
            f'web/communities/view-channel/{community_id}/{channel_id}', display_id,
            note='Downloading channel info', errnote='Unable to download channel info', fatal=False), 'channel') or {}

        title = f'{community_data.get("name") or community_id} - {channel_data.get("display_title") or channel_id}'
        description = self._parse_content_as_text(
            self._parse_json(community_data.get('description_content') or '{}', display_id, fatal=False) or {})
        return self.playlist_result(
            self._entries(
                f'web/posts/fetch/community/{community_id}?channels[]={sort_by}&channels[]={channel_id}',
                display_id, 'Downloading community posts', 'Unable to download community posts'),
            f'{community_id}/{channel_id}', title, description)

class GameJoltSearchIE(GameJoltPostListBaseIE):
    _VALID_URL = r'https?://(?:www\.)?gamejolt\.com/search(?:/(?P<filter>communities|users|games))?(?:\?|\#!?)(?:.*?[&;])??q=(?P<id>(?:[^&#]+)+)'
    _URL_FORMATS = {
        'users': 'https://gamejolt.com/@{username}',
        'communities': 'https://gamejolt.com/c/{path}',
        'games': 'https://gamejolt.com/games/{slug}/{id}',
    }
    _TESTS = [{
        'url': 'https://gamejolt.com/search?foo=bar&q=%23fnf',
        'playlist_mincount': 50,
        'info_dict': {
            'id': '#fnf',
            'title': '#fnf',
        },
        'params': {
            'playlistend': 50,
            'ignore_no_formats_error': True,
        },
        'expected_warnings': ['skipping format', 'No video formats found', 'Requested format is not available'],
    }, {
        'url': 'https://gamejolt.com/search/communities?q=cookie%20run',
        'playlist_mincount': 10,
        'info_dict': {
            'id': 'cookie run',
            'title': 'cookie run',
        },
    }, {
        'url': 'https://gamejolt.com/search/users?q=mlp',
        'playlist_mincount': 278,
        'info_dict': {
            'id': 'mlp',
            'title': 'mlp',
        },
    }, {
        'url': 'https://gamejolt.com/search/games?q=roblox',
        'playlist_mincount': 688,
        'info_dict': {
            'id': 'roblox',
            'title': 'roblox',
        },
    }]

    def _search_entries(self, query, filter_mode, display_query):
        initial_search_data = self._call_api(
            f'web/search/{filter_mode}?q={query}', display_query,
            note=f'Downloading {filter_mode} list', errnote=f'Unable to download {filter_mode} list')
        entries_num = traverse_obj(initial_search_data, 'count', f'{filter_mode}Count')
        if not entries_num:
            return
        for page in range(1, math.ceil(entries_num / initial_search_data['perPage']) + 1):
            search_results = self._call_api(
                f'web/search/{filter_mode}?q={query}&page={page}', display_query,
                note=f'Downloading {filter_mode} list page {page}', errnote=f'Unable to download {filter_mode} list')
            for result in search_results[filter_mode]:
                yield self.url_result(self._URL_FORMATS[filter_mode].format(**result))

    def _real_extract(self, url):
        filter_mode, query = self._match_valid_url(url).group('filter', 'id')
        display_query = compat_urllib_parse_unquote(query)
        return self.playlist_result(
            self._search_entries(query, filter_mode, display_query) if filter_mode else self._entries(
                f'web/posts/fetch/search/{query}', display_query, initial_items=self._call_api(
                    f'web/search?q={query}', display_query,
                    note='Downloading initial post list', errnote='Unable to download initial post list')['posts']),
            display_query, display_query)


code:
from .once import OnceIE
from ..compat import compat_urllib_parse_unquote

class GameSpotIE(OnceIE):
    _VALID_URL = r'https?://(?:www\.)?gamespot\.com/(?:video|article|review)s/(?:[^/]+/\d+-|embed/)(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://www.gamespot.com/videos/arma-3-community-guide-sitrep-i/2300-6410818/',
        'md5': 'b2a30deaa8654fcccd43713a6b6a4825',
        'info_dict': {
            'id': 'gs-2300-6410818',
            'ext': 'mp4',
            'title': 'Arma 3 - Community Guide: SITREP I',
            'description': 'Check out this video where some of the basics of Arma 3 is explained.',
        },
        'skip': 'manifest URL give HTTP Error 404: Not Found',
    }, {
        'url': 'http://www.gamespot.com/videos/the-witcher-3-wild-hunt-xbox-one-now-playing/2300-6424837/',
        'md5': '173ea87ad762cf5d3bf6163dceb255a6',
        'info_dict': {
            'id': 'gs-2300-6424837',
            'ext': 'mp4',
            'title': 'Now Playing - The Witcher 3: Wild Hunt',
            'description': 'Join us as we take a look at the early hours of The Witcher 3: Wild Hunt and more.',
        },
    }, {
        'url': 'https://www.gamespot.com/videos/embed/6439218/',
        'only_matching': True,
    }, {
        'url': 'https://www.gamespot.com/articles/the-last-of-us-2-receives-new-ps4-trailer/1100-6454469/',
        'only_matching': True,
    }, {
        'url': 'https://www.gamespot.com/reviews/gears-of-war-review/1900-6161188/',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        page_id = self._match_id(url)
        webpage = self._download_webpage(url, page_id)
        data_video = self._parse_json(self._html_search_regex(
            r'data-video=(["\'])({.*?})\1', webpage,
            'video data', group=2), page_id)
        title = compat_urllib_parse_unquote(data_video['title'])
        streams = data_video['videoStreams']
        formats = []

        m3u8_url = streams.get('adaptive_stream')
        if m3u8_url:
            m3u8_formats = self._extract_m3u8_formats(
                m3u8_url, page_id, 'mp4', 'm3u8_native',
                m3u8_id='hls', fatal=False)
            for f in m3u8_formats:
                formats.append(f)
                http_f = f.copy()
                del http_f['manifest_url']
                http_f.update({
                    'format_id': f['format_id'].replace('hls-', 'http-'),
                    'protocol': 'http',
                    'url': f['url'].replace('.m3u8', '.mp4'),
                })
                formats.append(http_f)

        mpd_url = streams.get('adaptive_dash')
        if mpd_url:
            formats.extend(self._extract_mpd_formats(
                mpd_url, page_id, mpd_id='dash', fatal=False))

        self._sort_formats(formats)

        return {
            'id': data_video.get('guid') or page_id,
            'display_id': page_id,
            'title': title,
            'formats': formats,
            'description': self._html_search_meta('description', webpage),
            'thumbnail': self._og_search_thumbnail(webpage),
        }

repo_name: Lesmiscore/ytdl


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

class GameStarIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?game(?P<site>pro|star)\.de/videos/.*,(?P<id>[0-9]+)\.html'
    _TESTS = [{
        'url': 'http://www.gamestar.de/videos/trailer,3/hobbit-3-die-schlacht-der-fuenf-heere,76110.html',
        'md5': 'ee782f1f8050448c95c5cacd63bc851c',
        'info_dict': {
            'id': '76110',
            'ext': 'mp4',
            'title': 'Hobbit 3: Die Schlacht der Fünf Heere - Teaser-Trailer zum dritten Teil',
            'description': 'Der Teaser-Trailer zu Hobbit 3: Die Schlacht der Fünf Heere zeigt einige Szenen aus dem dritten Teil der Saga und kündigt den...',
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1406542380,
            'upload_date': '20140728',
            'duration': 17,
        }
    }, {
        'url': 'http://www.gamepro.de/videos/top-10-indie-spiele-fuer-nintendo-switch-video-tolle-nindies-games-zum-download,95316.html',
        'only_matching': True,
    }, {
        'url': 'http://www.gamestar.de/videos/top-10-indie-spiele-fuer-nintendo-switch-video-tolle-nindies-games-zum-download,95316.html',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, video_id)

        # TODO: there are multiple ld+json objects in the webpage,
        # while _search_json_ld finds only the first one
        json_ld = self._parse_json(self._search_regex(
            r'(?s)',
            webpage, 'JSON-LD', group='json_ld'), video_id)
        info_dict = self._json_ld(json_ld, video_id)
        info_dict['title'] = remove_end(
            info_dict['title'], ' - Game%s' % site.title())

        view_count = int_or_none(json_ld.get('interactionCount'))
        comment_count = int_or_none(self._html_search_regex(
            r'Kommentare\s*]+class=["\']count[^>]+>\s*\(\s*([0-9]+)',
            webpage, 'comment count', fatal=False))

        info_dict.update({
            'id': video_id,
            'url': 'http://gamestar.de/_misc/videos/portal/getVideoUrl.cfm?premium=0&videoId=' + video_id,
            'ext': 'mp4',
            'view_count': view_count,
            'comment_count': comment_count
        })

        return info_dict


code:
import re
from .common import InfoExtractor
from ..utils import (
    float_or_none,
    int_or_none,
    unified_strdate,
)

class GaskrankIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?gaskrank\.tv/tv/(?P<categories>[^/]+)/(?P<id>[^/]+)\.htm'
    _TESTS = [{
        'url': 'http://www.gaskrank.tv/tv/motorrad-fun/strike-einparken-durch-anfaenger-crash-mit-groesserem-flurschaden.htm',
        'md5': '1ae88dbac97887d85ebd1157a95fc4f9',
        'info_dict': {
            'id': '201601/26955',
            'ext': 'mp4',
            'title': 'Strike! Einparken können nur Männer - Flurschaden hält sich in Grenzen *lol*',
            'thumbnail': r're:^https?://.*\.jpg$',
            'categories': ['motorrad-fun'],
            'display_id': 'strike-einparken-durch-anfaenger-crash-mit-groesserem-flurschaden',
            'uploader_id': 'Bikefun',
            'upload_date': '20170110',
            'uploader_url': None,
        }
    }, {
        'url': 'http://www.gaskrank.tv/tv/racing/isle-of-man-tt-2011-michael-du-15920.htm',
        'md5': 'c33ee32c711bc6c8224bfcbe62b23095',
        'info_dict': {
            'id': '201106/15920',
            'ext': 'mp4',
            'title': 'Isle of Man - Michael Dunlop vs Guy Martin - schwindelig kucken',
            'thumbnail': r're:^https?://.*\.jpg$',
            'categories': ['racing'],
            'display_id': 'isle-of-man-tt-2011-michael-du-15920',
            'uploader_id': 'IOM',
            'upload_date': '20170523',
            'uploader_url': 'www.iomtt.com',
        }
    }]

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

        webpage = self._download_webpage(url, display_id)

        title = self._og_search_title(
            webpage, default=None) or self._html_search_meta(
            'title', webpage, fatal=True)

        categories = [self._match_valid_url(url).group('categories')]

        mobj = re.search(
            r'Video von:\s*(?P<uploader_id>[^|]*?)\s*\|\s*vom:\s*(?P<upload_date>[0-9][0-9]\.[0-9][0-9]\.[0-9][0-9][0-9][0-9])',
            webpage)
        if mobj is not None:
            uploader_id = mobj.groupdict().get('uploader_id')
            upload_date = unified_strdate(mobj.groupdict().get('upload_date'))

        uploader_url = self._search_regex(
            r'Homepage:\s*<[^>]*>(?P<uploader_url>[^<]*)',
            webpage, 'uploader_url', default=None)
        tags = re.findall(
            r'/tv/tags/[^/]+/"\s*>(?P<tag>[^<]*?)<',
            webpage)

        view_count = self._search_regex(
            r'class\s*=\s*"gkRight"(?:[^>]*>\s*<[^>]*)*icon-eye-open(?:[^>]*>\s*<[^>]*)*>\s*(?P<view_count>[0-9\.]*)',
            webpage, 'view_count', default=None)
        if view_count:
            view_count = int_or_none(view_count.replace('.', ''))

        average_rating = self._search_regex(
            r'itemprop\s*=\s*"ratingValue"[^>]*>\s*(?P<average_rating>[0-9,]+)',
            webpage, 'average_rating')
        if average_rating:
            average_rating = float_or_none(average_rating.replace(',', '.'))

        video_id = self._search_regex(
            r'https?://movies\.gaskrank\.tv/([^-]*?)(-[^\.]*)?\.mp4',
            webpage, 'video id', default=display_id)

        entry = self._parse_html5_media_entries(url, webpage, video_id)[0]
        entry.update({
            'id': video_id,
            'title': title,
            'categories': categories,
            'display_id': display_id,
            'uploader_id': uploader_id,
            'upload_date': upload_date,
            'uploader_url': uploader_url,
            'tags': tags,
            'view_count': view_count,
            'average_rating': average_rating,
        })
        self._sort_formats(entry['formats'])

        return entry


code:
from .common import InfoExtractor

class GazetaIE(InfoExtractor):
    _VALID_URL = r'(?P<url>https?://(?:www\.)?gazeta\.ru/(?:[^/]+/)?video/(?:main/)*(?:\d{4}/\d{2}/\d{2}/)?(?P<id>[A-Za-z0-9-_.]+)\.s?html)'
    _TESTS = [{
        'url': 'http://www.gazeta.ru/video/main/zadaite_vopros_vladislavu_yurevichu.shtml',
        'md5': 'd49c9bdc6e5a7888f27475dc215ee789',
        'info_dict': {
            'id': '205566',
            'ext': 'mp4',
            'title': '«70–80 процентов гражданских в Донецке на грани голода»',
            'description': 'md5:38617526050bd17b234728e7f9620a71',
            'thumbnail': r're:^https?://.*\.jpg',
        },
        'skip': 'video not found',
    }, {
        'url': 'http://www.gazeta.ru/lifestyle/video/2015/03/08/master-klass_krasivoi_byt._delaem_vesennii_makiyazh.shtml',
        'only_matching': True,
    }, {
        'url': 'http://www.gazeta.ru/video/main/main/2015/06/22/platit_ili_ne_platit_po_isku_yukosa.shtml',
        'md5': '37f19f78355eb2f4256ee1688359f24c',
        'info_dict': {
            'id': '252048',
            'ext': 'mp4',
            'title': '"Если по иску ЮКОСа придется платить, это будет большой удар по бюджету"',
        },
        'add_ie': ['EaglePlatform'],
    }]

    def _real_extract(self, url):
        mobj = self._match_valid_url(url)

        display_id = mobj.group('id')
        embed_url = '%s?p=embed' % mobj.group('url')
        embed_page = self._download_webpage(
            embed_url, display_id, 'Downloading embed page')

        video_id = self._search_regex(
            r'<div[^>]*?class="eagleplayer"[^>]*?data-id="([^"]+)"', embed_page, 'video id')

        return self.url_result(
            'eagleplatform:gazeta.media.eagleplatform.com:%s' % video_id, 'EaglePlatform')


code:
import re

from .common import InfoExtractor
from .kaltura import KalturaIE
from ..utils import (
    HEADRequest,
    remove_start,
    sanitized_Request,
    smuggle_url,
    urlencode_postdata,
)

class GDCVaultIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?gdcvault\.com/play/(?P<id>\d+)(?:/(?P<name>[\w-]+))?'
    _NETRC_MACHINE = 'gdcvault'
    _TESTS = [
        {
            'url': 'http://www.gdcvault.com/play/1019721/Doki-Doki-Universe-Sweet-Simple',
            'md5': '7ce8388f544c88b7ac11c7ab1b593704',
            'info_dict': {
                'id': '201311826596_AWNY',
                'display_id': 'Doki-Doki-Universe-Sweet-Simple',
                'ext': 'mp4',
                'title': 'Doki-Doki Universe: Sweet, Simple and Genuine (GDC Next 10)'
            }
        },
        {
            'url': 'http://www.gdcvault.com/play/1015683/Embracing-the-Dark-Art-of',
            'info_dict': {
                'id': '201203272_1330951438328RSXR',
                'display_id': 'Embracing-the-Dark-Art-of',
                'ext': 'flv',
                'title': 'Embracing the Dark Art of Mathematical Modeling in AI'
            },
            'params': {
                'skip_download': True,  # Requires rtmpdump
            }
        },
        {
            'url': 'http://www.gdcvault.com/play/1015301/Thexder-Meets-Windows-95-or',
            'md5': 'a5eb77996ef82118afbbe8e48731b98e',
            'info_dict': {
                'id': '1015301',
                'display_id': 'Thexder-Meets-Windows-95-or',
                'ext': 'flv',
                'title': 'Thexder Meets Windows 95, or Writing Great Games in the Windows 95 Environment',
            },
            'skip': 'Requires login',
        },
        {
            'url': 'http://gdcvault.com/play/1020791/',
            'only_matching': True,
        },
        {
            # Hard-coded hostname
            'url': 'http://gdcvault.com/play/1023460/Tenacious-Design-and-The-Interface',
            'md5': 'a8efb6c31ed06ca8739294960b2dbabd',
            'info_dict': {
                'id': '840376_BQRC',
                'ext': 'mp4',
                'display_id': 'Tenacious-Design-and-The-Interface',
                'title': 'Tenacious Design and The Interface of \'Destiny\'',
            },
        },
        {
            # Multiple audios
            'url': 'http://www.gdcvault.com/play/1014631/Classic-Game-Postmortem-PAC',
            'info_dict': {
                'id': '12396_1299111843500GMPX',
                'ext': 'mp4',
                'title': 'How to Create a Good Game - From My Experience of Designing Pac-Man',
            },
            # 'params': {
            #     'skip_download': True,  # Requires rtmpdump
            #     'format': 'jp',  # The japanese audio
            # }
        },
        {
            # gdc-player.html
            'url': 'http://www.gdcvault.com/play/1435/An-American-engine-in-Tokyo',
            'info_dict': {
                'id': '9350_1238021887562UHXB',
                'display_id': 'An-American-engine-in-Tokyo',
                'ext': 'mp4',
                'title': 'An American Engine in Tokyo:/nThe collaboration of Epic Games and Square Enix/nFor THE LAST REMINANT',
            },
        },
        {
            # Kaltura Embed
            'url': 'https://www.gdcvault.com/play/1026180/Mastering-the-Apex-of-Scaling',
            'info_dict': {
                'id': '0_h1fg8j3p',
                'ext': 'mp4',
                'title': 'Mastering the Apex of Scaling Game Servers (Presented by Multiplay)',
                'timestamp': 1554401811,
                'upload_date': '20190404',
                'uploader_id': 'joe@blazestreaming.com',
            },
            'params': {
                'format': 'mp4-408',
            },
        },
        {
            # Kaltura embed, whitespace between quote and embedded URL in iframe's src
            'url': 'https://www.gdcvault.com/play/1025699',
            'info_dict': {
                'id': '0_zagynv0a',
                'ext': 'mp4',
                'title': 'Tech Toolbox',
                'upload_date': '20190408',
                'uploader_id': 'joe@blazestreaming.com',
                'timestamp': 1554764629,
            },
            'params': {
                'skip_download': True,
            },
        },
        {
            # HTML5 video
            'url': 'http://www.gdcvault.com/play/1014846/Conference-Keynote-Shigeru',
            'only_matching': True,
        },
    ]

    def _login(self, webpage_url, display_id):
        username, password = self._get_login_info()
        if username is None or password is None:
            self.report_warning('It looks like ' + webpage_url + ' requires a login. Try specifying a username and password and try again.')
            return None

        mobj = re.match(r'(?P<root_url>https?://.*?/).*', webpage_url)
        login_url = mobj.group('root_url') + 'api/login.php'
        logout_url = mobj.group('root_url') + 'logout'

        login_form = {
            'email': username,
            'password': password,
        }

        request = sanitized_Request(login_url, urlencode_postdata(login_form))
        request.add_header('Content-Type', 'application/x-www-form-urlencoded')
        self._download_webpage(request, display_id, 'Logging in')
        start_page = self._download_webpage(webpage_url, display_id, 'Getting authenticated video page')
        self._download_webpage(logout_url, display_id, 'Logging out')

        return start_page

    def _real_extract(self, url):
        video_id, name = self._match_valid_url(url).groups()
        display_id = name or video_id

        webpage_url = 'http://www.gdcvault.com/play/' + video_id
        start_page = self._download_webpage(webpage_url, display_id)

        direct_url = self._search_regex(
            r's1\.addVariable\("file",\s*encodeURIComponent\("(/[^"]+)"\)\);',
            start_page, 'url', default=None)
        if direct_url:
            title = self._html_search_regex(
                r'Session Name:?	\s*(.*?)	',
                start_page, 'title')
            video_url = 'http://www.gdcvault.com' + direct_url
            # resolve the url so that we can detect the correct extension
            video_url = self._request_webpage(
                HEADRequest(video_url), video_id).geturl()

            return {
                'id': video_id,
                'display_id': display_id,
                'url': video_url,
                'title': title,
            }

        embed_url = KalturaIE._extract_url(start_page)
        if embed_url:
            embed_url = smuggle_url(embed_url, {'source_url': url})
            ie_key = 'Kaltura'
        else:
            PLAYER_REGEX = r'<iframe src="(?P<xml_root>.+?)/(?:gdc-)?player.*?\.html.*?".*?'

            xml_root = self._html_search_regex(
                PLAYER_REGEX, start_page, 'xml root', default=None)
            if xml_root is None:
                # Probably need to authenticate
                login_res = self._login(webpage_url, display_id)
                if login_res is None:
                    self.report_warning('Could not login.')
                else:
                    start_page = login_res
                    # Grab the url from the authenticated page
                    xml_root = self._html_search_regex(
                        PLAYER_REGEX, start_page, 'xml root')

            xml_name = self._html_search_regex(
                r'<iframe src=".*?\?xml(?:=|URL=xml/)(.+?\.xml).*?".*?',
                start_page, 'xml filename', default=None)
            if not xml_name:
                info = self._parse_html5_media_entries(url, start_page, video_id)[0]
                info.update({
                    'title': remove_start(self._search_regex(
                        r'>Session Name:\s*<.*?>\s*(.+?)	', start_page,
                        'title', default=None) or self._og_search_title(
                        start_page, default=None), 'GDC Vault - '),
                    'id': video_id,
                    'display_id': display_id,
                })
                return info
            embed_url = '%s/xml/%s' % (xml_root, xml_name)
            ie_key = 'DigitallySpeaking'

        return {
            '_type': 'url_transparent',
            'id': video_id,
            'display_id': display_id,
            'url': embed_url,
            'ie_key': ie_key,
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    base_url,
    determine_ext,
    int_or_none,
    url_basename,
    urljoin,
)

class GediDigitalIE(InfoExtractor):
    _VALID_URL = r'''(?x:(?P<base_url>(?:https?:)//video\.
        (?:
            (?:
                (?:espresso\.)?repubblica
                |lastampa
                |ilsecoloxix
                |huffingtonpost
            )|
            (?:
                iltirreno
                |messaggeroveneto
                |ilpiccolo
                |gazzettadimantova
                |mattinopadova
                |laprovinciapavese
                |tribunatreviso
                |nuovavenezia
                |gazzettadimodena
                |lanuovaferrara
                |corrierealpi
                |lasentinella
            )\.gelocal
        )\.it(?:/[^/]+){2,4}/(?P<id>\d+))(?:$|[?&].*))'''
    _EMBED_REGEX = [rf'''(?x)
            (?:
                data-frame-src=|
                <iframe[^\n]+src=
            )
            (["'])(?P<url>{_VALID_URL})\1''']
    _TESTS = [{
        'url': 'https://video.lastampa.it/politica/il-paradosso-delle-regionali-la-lega-vince-ma-sembra-aver-perso/121559/121683',
        'md5': '84658d7fb9e55a6e57ecc77b73137494',
        'info_dict': {
            'id': '121683',
            'ext': 'mp4',
            'title': 'Il paradosso delle Regionali: ecco perché la Lega vince ma sembra aver perso',
            'description': 'md5:de7f4d6eaaaf36c153b599b10f8ce7ca',
            'thumbnail': r're:^https://www\.repstatic\.it/video/photo/.+?-thumb-full-.+?\.jpg$',
            'duration': 125,
        },
    }, {
        'url': 'https://video.huffingtonpost.it/embed/politica/cotticelli-non-so-cosa-mi-sia-successo-sto-cercando-di-capire-se-ho-avuto-un-malore/29312/29276?responsive=true&el=video971040871621586700',
        'only_matching': True,
    }, {
        'url': 'https://video.espresso.repubblica.it/embed/tutti-i-video/01-ted-villa/14772/14870&width=640&height=360',
        'only_matching': True,
    }, {
        'url': 'https://video.repubblica.it/motori/record-della-pista-a-spa-francorchamps-la-pagani-huayra-roadster-bc-stupisce/367415/367963',
        'only_matching': True,
    }, {
        'url': 'https://video.ilsecoloxix.it/sport/cassani-e-i-brividi-azzurri-ai-mondiali-di-imola-qui-mi-sono-innamorato-del-ciclismo-da-ragazzino-incredibile-tornarci-da-ct/66184/66267',
        'only_matching': True,
    }, {
        'url': 'https://video.iltirreno.gelocal.it/sport/dentro-la-notizia-ferrari-cosa-succede-a-maranello/141059/142723',
        'only_matching': True,
    }, {
        'url': 'https://video.messaggeroveneto.gelocal.it/locale/maria-giovanna-elmi-covid-vaccino/138155/139268',
        'only_matching': True,
    }, {
        'url': 'https://video.ilpiccolo.gelocal.it/dossier/big-john/dinosauro-big-john-al-via-le-visite-guidate-a-trieste/135226/135751',
        'only_matching': True,
    }, {
        'url': 'https://video.gazzettadimantova.gelocal.it/locale/dal-ponte-visconteo-di-valeggio-l-and-8217sos-dei-ristoratori-aprire-anche-a-cena/137310/137818',
        'only_matching': True,
    }, {
        'url': 'https://video.mattinopadova.gelocal.it/dossier/coronavirus-in-veneto/covid-a-vo-un-anno-dopo-un-cuore-tricolore-per-non-dimenticare/138402/138964',
        'only_matching': True,
    }, {
        'url': 'https://video.laprovinciapavese.gelocal.it/locale/mede-zona-rossa-via-alle-vaccinazioni-per-gli-over-80/137545/138120',
        'only_matching': True,
    }, {
        'url': 'https://video.tribunatreviso.gelocal.it/dossier/coronavirus-in-veneto/ecco-le-prima-vaccinazioni-di-massa-nella-marca/134485/135024',
        'only_matching': True,
    }, {
        'url': 'https://video.nuovavenezia.gelocal.it/locale/camion-troppo-alto-per-il-ponte-ferroviario-perde-il-carico/135734/136266',
        'only_matching': True,
    }, {
        'url': 'https://video.gazzettadimodena.gelocal.it/locale/modena-scoperta-la-proteina-che-predice-il-livello-di-gravita-del-covid/139109/139796',
        'only_matching': True,
    }, {
        'url': 'https://video.lanuovaferrara.gelocal.it/locale/due-bombole-di-gpl-aperte-e-abbandonate-i-vigili-bruciano-il-gas/134391/134957',
        'only_matching': True,
    }, {
        'url': 'https://video.corrierealpi.gelocal.it/dossier/cortina-2021-i-mondiali-di-sci-alpino/mondiali-di-sci-il-timelapse-sulla-splendida-olympia/133760/134331',
        'only_matching': True,
    }, {
        'url': 'https://video.lasentinella.gelocal.it/locale/vestigne-centra-un-auto-e-si-ribalta/138931/139466',
        'only_matching': True,
    }, {
        'url': 'https://video.espresso.repubblica.it/tutti-i-video/01-ted-villa/14772',
        'only_matching': True,
    }]

    @staticmethod
    def _sanitize_urls(urls):
        # add protocol if missing
        for i, e in enumerate(urls):
            if e.startswith('//'):
                urls[i] = 'https:%s' % e
        # clean iframes urls
        for i, e in enumerate(urls):
            urls[i] = urljoin(base_url(e), url_basename(e))
        return urls

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        return cls._sanitize_urls(tuple(super()._extract_embed_urls(url, webpage)))

    @staticmethod
    def _clean_formats(formats):
        format_urls = set()
        clean_formats = []
        for f in formats:
            if f['url'] not in format_urls:
                if f.get('audio_ext') != 'none' and not f.get('acodec'):
                    continue
                format_urls.add(f['url'])
                clean_formats.append(f)
        formats[:] = clean_formats

    def _real_extract(self, url):
        video_id, url = self._match_valid_url(url).group('id', 'base_url')
        webpage = self._download_webpage(url, video_id)
        title = self._html_search_meta(
            ['twitter:title', 'og:title'], webpage, fatal=True)
        player_data = re.findall(
            r"PlayerFactory\.setParam\('(?P<type>format|param)',\s*'(?P<name>[^']+)',\s*'(?P<val>[^']+)'\);",
            webpage)

        formats = []
        duration = thumb = None
        for t, n, v in player_data:
            if t == 'format':
                if n in ('video-hds-vod-ec', 'video-hls-vod-ec', 'video-viralize', 'video-youtube-pfp'):
                    continue
                elif n.endswith('-vod-ak'):
                    formats.extend(self._extract_akamai_formats(
                        v, video_id, {'http': 'media.gedidigital.it'}))
                else:
                    ext = determine_ext(v)
                    if ext == 'm3u8':
                        formats.extend(self._extract_m3u8_formats(
                            v, video_id, 'mp4', 'm3u8_native', m3u8_id=n, fatal=False))
                        continue
                    f = {
                        'format_id': n,
                        'url': v,
                    }
                    if ext == 'mp3':
                        abr = int_or_none(self._search_regex(
                            r'-mp3-audio-(\d+)', v, 'abr', default=None))
                        f.update({
                            'abr': abr,
                            'tbr': abr,
                            'acodec': ext,
                            'vcodec': 'none'
                        })
                    else:
                        mobj = re.match(r'^video-rrtv-(\d+)(?:-(\d+))?$', n)
                        if mobj:
                            f.update({
                                'height': int(mobj.group(1)),
                                'vbr': int_or_none(mobj.group(2)),
                            })
                        if not f.get('vbr'):
                            f['vbr'] = int_or_none(self._search_regex(
                                r'-video-rrtv-(\d+)', v, 'abr', default=None))
                    formats.append(f)
            elif t == 'param':
                if n in ['image_full', 'image']:
                    thumb = v
                elif n == 'videoDuration':
                    duration = int_or_none(v)

        self._clean_formats(formats)
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': title,
            'description': self._html_search_meta(
                ['twitter:description', 'og:description', 'description'], webpage),
            'thumbnail': thumb or self._og_search_thumbnail(webpage),
            'formats': formats,
            'duration': duration,
        }