code:
import itertools
import re
from base64 import b64encode
from datetime import datetime
from hashlib import md5

from .common import InfoExtractor
from ..compat import compat_str, compat_urllib_parse_urlencode
from ..utils import float_or_none, sanitized_Request

class NetEaseMusicBaseIE(InfoExtractor):
    _FORMATS = ['bMusic', 'mMusic', 'hMusic']
    _NETEASE_SALT = '3go8&$8*3*3h0k(2)2'
    _API_BASE = 'http://music.163.com/api/'

    @classmethod
    def _encrypt(cls, dfsid):
        salt_bytes = bytearray(cls._NETEASE_SALT.encode('utf-8'))
        string_bytes = bytearray(compat_str(dfsid).encode('ascii'))
        salt_len = len(salt_bytes)
        for i in range(len(string_bytes)):
            string_bytes[i] = string_bytes[i] ^ salt_bytes[i % salt_len]
        m = md5()
        m.update(bytes(string_bytes))
        result = b64encode(m.digest()).decode('ascii')
        return result.replace('/', '_').replace('+', '-')

    def extract_formats(self, info):
        formats = []
        for song_format in self._FORMATS:
            details = info.get(song_format)
            if not details:
                continue
            song_file_path = '/%s/%s.%s' % (
                self._encrypt(details['dfsId']), details['dfsId'], details['extension'])

            # 203.130.59.9, 124.40.233.182, 115.231.74.139, etc is a reverse proxy-like feature
            # from NetEase's CDN provider that can be used if m5.music.126.net does not
            # work, especially for users outside of Mainland China
            # via: https://github.com/JixunMoe/unblock-163/issues/3#issuecomment-163115880
            for host in ('http://m5.music.126.net', 'http://115.231.74.139/m1.music.126.net',
                         'http://124.40.233.182/m1.music.126.net', 'http://203.130.59.9/m1.music.126.net'):
                song_url = host + song_file_path
                if self._is_valid_url(song_url, info['id'], 'song'):
                    formats.append({
                        'url': song_url,
                        'ext': details.get('extension'),
                        'abr': float_or_none(details.get('bitrate'), scale=1000),
                        'format_id': song_format,
                        'filesize': details.get('size'),
                        'asr': details.get('sr')
                    })
                    break
        return formats

    @classmethod
    def convert_milliseconds(cls, ms):
        return int(round(ms / 1000.0))

    def query_api(self, endpoint, video_id, note):
        req = sanitized_Request('%s%s' % (self._API_BASE, endpoint))
        req.add_header('Referer', self._API_BASE)
        return self._download_json(req, video_id, note)

class NetEaseMusicIE(NetEaseMusicBaseIE):
    IE_NAME = 'netease:song'
    IE_DESC = '网易云音乐'
    _VALID_URL = r'https?://music\.163\.com/(#/)?song\?id=(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://music.163.com/#/song?id=32102397',
        'md5': 'f2e97280e6345c74ba9d5677dd5dcb45',
        'info_dict': {
            'id': '32102397',
            'ext': 'mp3',
            'title': 'Bad Blood (feat. Kendrick Lamar)',
            'creator': 'Taylor Swift / Kendrick Lamar',
            'upload_date': '20150517',
            'timestamp': 1431878400,
            'description': 'md5:a10a54589c2860300d02e1de821eb2ef',
        },
        'skip': 'Blocked outside Mainland China',
    }, {
        'note': 'No lyrics translation.',
        'url': 'http://music.163.com/#/song?id=29822014',
        'info_dict': {
            'id': '29822014',
            'ext': 'mp3',
            'title': '听见下雨的声音',
            'creator': '周杰伦',
            'upload_date': '20141225',
            'timestamp': 1419523200,
            'description': 'md5:a4d8d89f44656af206b7b2555c0bce6c',
        },
        'skip': 'Blocked outside Mainland China',
    }, {
        'note': 'No lyrics.',
        'url': 'http://music.163.com/song?id=17241424',
        'info_dict': {
            'id': '17241424',
            'ext': 'mp3',
            'title': 'Opus 28',
            'creator': 'Dustin O\'Halloran',
            'upload_date': '20080211',
            'timestamp': 1202745600,
        },
        'skip': 'Blocked outside Mainland China',
    }, {
        'note': 'Has translated name.',
        'url': 'http://music.163.com/#/song?id=22735043',
        'info_dict': {
            'id': '22735043',
            'ext': 'mp3',
            'title': '소원을 말해봐 (Genie)',
            'creator': '少女时代',
            'description': 'md5:79d99cc560e4ca97e0c4d86800ee4184',
            'upload_date': '20100127',
            'timestamp': 1264608000,
            'alt_title': '说出愿望吧(Genie)',
        },
        'skip': 'Blocked outside Mainland China',
    }]

    def _process_lyrics(self, lyrics_info):
        original = lyrics_info.get('lrc', {}).get('lyric')
        translated = lyrics_info.get('tlyric', {}).get('lyric')

        if not translated:
            return original

        lyrics_expr = r'(\[[0-9]{2}:[0-9]{2}\.[0-9]{2,}\])([^\n]+)'
        original_ts_texts = re.findall(lyrics_expr, original)
        translation_ts_dict = dict(
            (time_stamp, text) for time_stamp, text in re.findall(lyrics_expr, translated)
        )
        lyrics = '\n'.join([
            '%s%s / %s' % (time_stamp, text, translation_ts_dict.get(time_stamp, ''))
            for time_stamp, text in original_ts_texts
        ])
        return lyrics

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

        params = {
            'id': song_id,
            'ids': '[%s]' % song_id
        }
        info = self.query_api(
            'song/detail?' + compat_urllib_parse_urlencode(params),
            song_id, 'Downloading song info')['songs'][0]

        formats = self.extract_formats(info)
        self._sort_formats(formats)

        lyrics_info = self.query_api(
            'song/lyric?id=%s&lv=-1&tv=-1' % song_id,
            song_id, 'Downloading lyrics data')
        lyrics = self._process_lyrics(lyrics_info)

        alt_title = None
        if info.get('transNames'):
            alt_title = '/'.join(info.get('transNames'))

        return {
            'id': song_id,
            'title': info['name'],
            'alt_title': alt_title,
            'creator': ' / '.join([artist['name'] for artist in info.get('artists', [])]),
            'timestamp': self.convert_milliseconds(info.get('album', {}).get('publishTime')),
            'thumbnail': info.get('album', {}).get('picUrl'),
            'duration': self.convert_milliseconds(info.get('duration', 0)),
            'description': lyrics,
            'formats': formats,
        }

class NetEaseMusicAlbumIE(NetEaseMusicBaseIE):
    IE_NAME = 'netease:album'
    IE_DESC = '网易云音乐 - 专辑'
    _VALID_URL = r'https?://music\.163\.com/(#/)?album\?id=(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://music.163.com/#/album?id=220780',
        'info_dict': {
            'id': '220780',
            'title': 'B\'day',
        },
        'playlist_count': 23,
        'skip': 'Blocked outside Mainland China',
    }

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

        info = self.query_api(
            'album/%s?id=%s' % (album_id, album_id),
            album_id, 'Downloading album data')['album']

        name = info['name']
        desc = info.get('description')
        entries = [
            self.url_result('http://music.163.com/#/song?id=%s' % song['id'],
                            'NetEaseMusic', song['id'])
            for song in info['songs']
        ]
        return self.playlist_result(entries, album_id, name, desc)

class NetEaseMusicSingerIE(NetEaseMusicBaseIE):
    IE_NAME = 'netease:singer'
    IE_DESC = '网易云音乐 - 歌手'
    _VALID_URL = r'https?://music\.163\.com/(#/)?artist\?id=(?P<id>[0-9]+)'
    _TESTS = [{
        'note': 'Singer has aliases.',
        'url': 'http://music.163.com/#/artist?id=10559',
        'info_dict': {
            'id': '10559',
            'title': '张惠妹 - aMEI;阿密特',
        },
        'playlist_count': 50,
        'skip': 'Blocked outside Mainland China',
    }, {
        'note': 'Singer has translated name.',
        'url': 'http://music.163.com/#/artist?id=124098',
        'info_dict': {
            'id': '124098',
            'title': '李昇基 - 이승기',
        },
        'playlist_count': 50,
        'skip': 'Blocked outside Mainland China',
    }]

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

        info = self.query_api(
            'artist/%s?id=%s' % (singer_id, singer_id),
            singer_id, 'Downloading singer data')

        name = info['artist']['name']
        if info['artist']['trans']:
            name = '%s - %s' % (name, info['artist']['trans'])
        if info['artist']['alias']:
            name = '%s - %s' % (name, ';'.join(info['artist']['alias']))

        entries = [
            self.url_result('http://music.163.com/#/song?id=%s' % song['id'],
                            'NetEaseMusic', song['id'])
            for song in info['hotSongs']
        ]
        return self.playlist_result(entries, singer_id, name)

class NetEaseMusicListIE(NetEaseMusicBaseIE):
    IE_NAME = 'netease:playlist'
    IE_DESC = '网易云音乐 - 歌单'
    _VALID_URL = r'https?://music\.163\.com/(#/)?(playlist|discover/toplist)\?id=(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://music.163.com/#/playlist?id=79177352',
        'info_dict': {
            'id': '79177352',
            'title': 'Billboard 2007 Top 100',
            'description': 'md5:12fd0819cab2965b9583ace0f8b7b022'
        },
        'playlist_count': 99,
        'skip': 'Blocked outside Mainland China',
    }, {
        'note': 'Toplist/Charts sample',
        'url': 'http://music.163.com/#/discover/toplist?id=3733003',
        'info_dict': {
            'id': '3733003',
            'title': 're:韩国Melon排行榜周榜 [0-9]{4}-[0-9]{2}-[0-9]{2}',
            'description': 'md5:73ec782a612711cadc7872d9c1e134fc',
        },
        'playlist_count': 50,
        'skip': 'Blocked outside Mainland China',
    }]

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

        info = self.query_api(
            'playlist/detail?id=%s&lv=-1&tv=-1' % list_id,
            list_id, 'Downloading playlist data')['result']

        name = info['name']
        desc = info.get('description')

        if info.get('specialType') == 10:  # is a chart/toplist
            datestamp = datetime.fromtimestamp(
                self.convert_milliseconds(info['updateTime'])).strftime('%Y-%m-%d')
            name = '%s %s' % (name, datestamp)

        entries = [
            self.url_result('http://music.163.com/#/song?id=%s' % song['id'],
                            'NetEaseMusic', song['id'])
            for song in info['tracks']
        ]
        return self.playlist_result(entries, list_id, name, desc)

class NetEaseMusicMvIE(NetEaseMusicBaseIE):
    IE_NAME = 'netease:mv'
    IE_DESC = '网易云音乐 - MV'
    _VALID_URL = r'https?://music\.163\.com/(#/)?mv\?id=(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://music.163.com/#/mv?id=415350',
        'info_dict': {
            'id': '415350',
            'ext': 'mp4',
            'title': '이럴거면 그러지말지',
            'description': '白雅言自作曲唱甜蜜爱情',
            'creator': '白雅言',
            'upload_date': '20150520',
        },
        'skip': 'Blocked outside Mainland China',
    }

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

        info = self.query_api(
            'mv/detail?id=%s&type=mp4' % mv_id,
            mv_id, 'Downloading mv info')['data']

        formats = [
            {'url': mv_url, 'ext': 'mp4', 'format_id': '%sp' % brs, 'height': int(brs)}
            for brs, mv_url in info['brs'].items()
        ]
        self._sort_formats(formats)

        return {
            'id': mv_id,
            'title': info['name'],
            'description': info.get('desc') or info.get('briefDesc'),
            'creator': info['artistName'],
            'upload_date': info['publishTime'].replace('-', ''),
            'formats': formats,
            'thumbnail': info.get('cover'),
            'duration': self.convert_milliseconds(info.get('duration', 0)),
        }

class NetEaseMusicProgramIE(NetEaseMusicBaseIE):
    IE_NAME = 'netease:program'
    IE_DESC = '网易云音乐 - 电台节目'
    _VALID_URL = r'https?://music\.163\.com/(#/?)program\?id=(?P<id>[0-9]+)'
    _TESTS = [{
        'url': 'http://music.163.com/#/program?id=10109055',
        'info_dict': {
            'id': '10109055',
            'ext': 'mp3',
            'title': '不丹足球背后的故事',
            'description': '喜马拉雅人的足球梦 ...',
            'creator': '大话西藏',
            'timestamp': 1434179342,
            'upload_date': '20150613',
            'duration': 900,
        },
        'skip': 'Blocked outside Mainland China',
    }, {
        'note': 'This program has accompanying songs.',
        'url': 'http://music.163.com/#/program?id=10141022',
        'info_dict': {
            'id': '10141022',
            'title': '25岁，你是自在如风的少年<27°C>',
            'description': 'md5:8d594db46cc3e6509107ede70a4aaa3b',
        },
        'playlist_count': 4,
        'skip': 'Blocked outside Mainland China',
    }, {
        'note': 'This program has accompanying songs.',
        'url': 'http://music.163.com/#/program?id=10141022',
        'info_dict': {
            'id': '10141022',
            'ext': 'mp3',
            'title': '25岁，你是自在如风的少年<27°C>',
            'description': 'md5:8d594db46cc3e6509107ede70a4aaa3b',
            'timestamp': 1434450841,
            'upload_date': '20150616',
        },
        'params': {
            'noplaylist': True
        },
        'skip': 'Blocked outside Mainland China',
    }]

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

        info = self.query_api(
            'dj/program/detail?id=%s' % program_id,
            program_id, 'Downloading program info')['program']

        name = info['name']
        description = info['description']

        if not self._yes_playlist(info['songs'] and program_id, info['mainSong']['id']):
            formats = self.extract_formats(info['mainSong'])
            self._sort_formats(formats)

            return {
                'id': info['mainSong']['id'],
                'title': name,
                'description': description,
                'creator': info['dj']['brand'],
                'timestamp': self.convert_milliseconds(info['createTime']),
                'thumbnail': info['coverUrl'],
                'duration': self.convert_milliseconds(info.get('duration', 0)),
                'formats': formats,
            }

        song_ids = [info['mainSong']['id']]
        song_ids.extend([song['id'] for song in info['songs']])
        entries = [
            self.url_result('http://music.163.com/#/song?id=%s' % song_id,
                            'NetEaseMusic', song_id)
            for song_id in song_ids
        ]
        return self.playlist_result(entries, program_id, name, description)

class NetEaseMusicDjRadioIE(NetEaseMusicBaseIE):
    IE_NAME = 'netease:djradio'
    IE_DESC = '网易云音乐 - 电台'
    _VALID_URL = r'https?://music\.163\.com/(#/)?djradio\?id=(?P<id>[0-9]+)'
    _TEST = {
        'url': 'http://music.163.com/#/djradio?id=42',
        'info_dict': {
            'id': '42',
            'title': '声音蔓延',
            'description': 'md5:766220985cbd16fdd552f64c578a6b15'
        },
        'playlist_mincount': 40,
        'skip': 'Blocked outside Mainland China',
    }
    _PAGE_SIZE = 1000

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

        name = None
        desc = None
        entries = []
        for offset in itertools.count(start=0, step=self._PAGE_SIZE):
            info = self.query_api(
                'dj/program/byradio?asc=false&limit=%d&radioId=%s&offset=%d'
                % (self._PAGE_SIZE, dj_id, offset),
                dj_id, 'Downloading dj programs - %d' % offset)

            entries.extend([
                self.url_result(
                    'http://music.163.com/#/program?id=%s' % program['id'],
                    'NetEaseMusicProgram', program['id'])
                for program in info['programs']
            ])

            if name is None:
                radio = info['programs'][0]['radio']
                name = radio['name']
                desc = radio['desc']

            if not info['more']:
                break

        return self.playlist_result(entries, dj_id, name, desc)


code:
from .common import InfoExtractor
from .dailymotion import DailymotionIE
from ..utils import smuggle_url, traverse_obj

class NetverseBaseIE(InfoExtractor):
    _ENDPOINTS = {
        'watch': 'watchvideo',
        'video': 'watchvideo',
        'webseries': 'webseries',
        'season': 'webseason_videos',
    }

    def _call_api(self, slug, endpoint, query={}, season_id='', display_id=None):
        return self._download_json(
            f'https://api.netverse.id/medias/api/v2/{self._ENDPOINTS[endpoint]}/{slug}/{season_id}',
            display_id or slug, query=query)

class NetverseIE(NetverseBaseIE):
    _VALID_URL = r'https?://(?:\w+\.)?netverse\.id/(?P<type>watch|video)/(?P<display_id>[^/?#&]+)'
    _TESTS = [{
        # Watch video
        'url': 'https://www.netverse.id/watch/waktu-indonesia-bercanda-edisi-spesial-lebaran-2016',
        'info_dict': {
            'id': 'k4yhqUwINAGtmHx3NkL',
            'title': 'Waktu Indonesia Bercanda - Edisi Spesial Lebaran 2016',
            'ext': 'mp4',
            'season': 'Season 2016',
            'description': 'md5:d41d8cd98f00b204e9800998ecf8427e',
            'thumbnail': r're:https?://s\d+\.dmcdn\.net/v/T7aV31Y0eGRWBbwkK/x1080',
            'episode_number': 22,
            'episode': 'Episode 22',
            'uploader_id': 'x2ir3vq',
            'age_limit': 0,
            'tags': [],
            'view_count': int,
            'display_id': 'waktu-indonesia-bercanda-edisi-spesial-lebaran-2016',
            'duration': 2990,
            'upload_date': '20210722',
            'timestamp': 1626919804,
            'like_count': int,
            'uploader': 'Net Prime',
        }
    }, {
        # series
        'url': 'https://www.netverse.id/watch/jadoo-seorang-model',
        'info_dict': {
            'id': 'x88izwc',
            'title': 'Jadoo Seorang Model',
            'ext': 'mp4',
            'season': 'Season 2',
            'description': 'md5:8a74f70812cca267e19ee0635f0af835',
            'thumbnail': r're:https?://s\d+\.dmcdn\.net/v/Thwuy1YURicFmGu0v/x1080',
            'episode_number': 2,
            'episode': 'Episode 2',
            'view_count': int,
            'like_count': int,
            'display_id': 'jadoo-seorang-model',
            'uploader_id': 'x2ir3vq',
            'duration': 635,
            'timestamp': 1646372927,
            'tags': ['PG069497-hellojadooseason2eps2'],
            'upload_date': '20220304',
            'uploader': 'Net Prime',
            'age_limit': 0,
        },
        'skip': 'video get Geo-blocked for some country'
    }, {
        # non www host
        'url': 'https://netverse.id/watch/tetangga-baru',
        'info_dict': {
            'id': 'k4CNGz7V0HJ7vfwZbXy',
            'ext': 'mp4',
            'title': 'Tetangga Baru',
            'season': 'Season 1',
            'description': 'md5:23fcf70e97d461d3029d25d59b2ccfb9',
            'thumbnail': r're:https?://s\d+\.dmcdn\.net/v/T3Ogm1YEnnyjVKAFF/x1080',
            'episode_number': 1,
            'episode': 'Episode 1',
            'timestamp': 1624538169,
            'view_count': int,
            'upload_date': '20210624',
            'age_limit': 0,
            'uploader_id': 'x2ir3vq',
            'like_count': int,
            'uploader': 'Net Prime',
            'tags': ['PG008534', 'tetangga', 'Baru'],
            'display_id': 'tetangga-baru',
            'duration': 1406,
        },
    }, {
        # /video url
        'url': 'https://www.netverse.id/video/pg067482-hellojadoo-season1',
        'title': 'Namaku Choi Jadoo',
        'info_dict': {
            'id': 'x887jzz',
            'ext': 'mp4',
            'thumbnail': r're:https?://s\d+\.dmcdn\.net/v/TfuZ_1Y6PboJ5An_s/x1080',
            'season': 'Season 1',
            'episode_number': 1,
            'description': 'md5:d4f627b3e7a3f9acdc55f6cdd5ea41d5',
            'title': 'Namaku Choi Jadoo',
            'episode': 'Episode 1',
            'age_limit': 0,
            'like_count': int,
            'view_count': int,
            'tags': ['PG067482', 'PG067482-HelloJadoo-season1'],
            'duration': 780,
            'display_id': 'pg067482-hellojadoo-season1',
            'uploader_id': 'x2ir3vq',
            'uploader': 'Net Prime',
            'timestamp': 1645764984,
            'upload_date': '20220225',
        },
        'skip': 'This video get Geo-blocked for some country'
    }]

    def _real_extract(self, url):
        display_id, sites_type = self._match_valid_url(url).group('display_id', 'type')
        program_json = self._call_api(display_id, sites_type)
        videos = program_json['response']['videos']

        return {
            '_type': 'url_transparent',
            'ie_key': DailymotionIE.ie_key(),
            'url': smuggle_url(videos['dailymotion_url'], {'query': {'embedder': 'https://www.netverse.id'}}),
            'display_id': display_id,
            'title': videos.get('title'),
            'season': videos.get('season_name'),
            'thumbnail': traverse_obj(videos, ('program_detail', 'thumbnail_image')),
            'description': traverse_obj(videos, ('program_detail', 'description')),
            'episode_number': videos.get('episode_order'),
        }

class NetversePlaylistIE(NetverseBaseIE):
    _VALID_URL = r'https?://(?:\w+\.)?netverse\.id/(?P<type>webseries)/(?P<display_id>[^/?#&]+)'
    _TESTS = [{
        # multiple season
        'url': 'https://netverse.id/webseries/tetangga-masa-gitu',
        'info_dict': {
            'id': 'tetangga-masa-gitu',
            'title': 'Tetangga Masa Gitu',
        },
        'playlist_count': 519,
    }, {
        # single season
        'url': 'https://netverse.id/webseries/kelas-internasional',
        'info_dict': {
            'id': 'kelas-internasional',
            'title': 'Kelas Internasional',
        },
        'playlist_count': 203,
    }]

    def parse_playlist(self, json_data, playlist_id):
        slug_sample = traverse_obj(json_data, ('related', 'data', ..., 'slug'))[0]
        for season in traverse_obj(json_data, ('seasons', ..., 'id')):
            playlist_json = self._call_api(
                slug_sample, 'season', display_id=playlist_id, season_id=season)

            for current_page in range(playlist_json['response']['season_list']['last_page']):
                playlist_json = self._call_api(slug_sample, 'season', query={'page': current_page + 1},
                                               season_id=season, display_id=playlist_id)
                for slug in traverse_obj(playlist_json, ('response', ..., 'data', ..., 'slug')):
                    yield self.url_result(f'https://www.netverse.id/video/{slug}', NetverseIE)

    def _real_extract(self, url):
        playlist_id, sites_type = self._match_valid_url(url).group('display_id', 'type')
        playlist_data = self._call_api(playlist_id, sites_type)

        return self.playlist_result(
            self.parse_playlist(playlist_data['response'], playlist_id),
            traverse_obj(playlist_data, ('response', 'webseries_info', 'slug')),
            traverse_obj(playlist_data, ('response', 'webseries_info', 'title')))

repo_name: Lesmiscore/ytdl


code:
from .common import InfoExtractor
from ..utils import (
    clean_html,
    int_or_none,
    js_to_json,
    parse_iso8601,
)

class NetzkinoIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?netzkino\.de/\#!/[^/]+/(?P<id>[^/]+)'

    _TESTS = [{
        'url': 'https://www.netzkino.de/#!/scifikino/rakete-zum-mond',
        'md5': '92a3f8b76f8d7220acce5377ea5d4873',
        'info_dict': {
            'id': 'rakete-zum-mond',
            'ext': 'mp4',
            'title': 'Rakete zum Mond \u2013 Jules Verne',
            'description': 'md5:f0a8024479618ddbfa450ff48ffa6c60',
            'upload_date': '20120813',
            'thumbnail': r're:https?://.*\.jpg$',
            'timestamp': 1344858571,
            'age_limit': 12,
        },
        'params': {
            'skip_download': 'Download only works from Germany',
        }
    }, {
        'url': 'https://www.netzkino.de/#!/filme/dr-jekyll-mrs-hyde-2',
        'md5': 'c7728b2dadd04ff6727814847a51ef03',
        'info_dict': {
            'id': 'dr-jekyll-mrs-hyde-2',
            'ext': 'mp4',
            'title': 'Dr. Jekyll & Mrs. Hyde 2',
            'description': 'md5:c2e9626ebd02de0a794b95407045d186',
            'upload_date': '20190130',
            'thumbnail': r're:https?://.*\.jpg$',
            'timestamp': 1548849437,
            'age_limit': 18,
        },
        'params': {
            'skip_download': 'Download only works from Germany',
        }
    }]

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

        api_url = 'https://api.netzkino.de.simplecache.net/capi-2.0a/movies/%s.json?d=www' % video_id
        info = self._download_json(api_url, video_id)
        custom_fields = info['custom_fields']

        production_js = self._download_webpage(
            'http://www.netzkino.de/beta/dist/production.min.js', video_id,
            note='Downloading player code')
        avo_js = self._search_regex(
            r'var urlTemplate=(\{.*?"\})',
            production_js, 'URL templates')
        templates = self._parse_json(
            avo_js, video_id, transform_source=js_to_json)

        suffix = {
            'hds': '.mp4/manifest.f4m',
            'hls': '.mp4/master.m3u8',
            'pmd': '.mp4',
        }
        film_fn = custom_fields['Streaming'][0]
        formats = [{
            'format_id': key,
            'ext': 'mp4',
            'url': tpl.replace('{}', film_fn) + suffix[key],
        } for key, tpl in templates.items()]
        self._sort_formats(formats)

        return {
            'id': video_id,
            'formats': formats,
            'title': info['title'],
            'age_limit': int_or_none(custom_fields.get('FSK')[0]),
            'timestamp': parse_iso8601(info.get('date'), delimiter=' '),
            'description': clean_html(info.get('content')),
            'thumbnail': info.get('thumbnail'),
        }


code:
import functools
import re

from .common import InfoExtractor
from ..utils import (
    clean_html,
    extract_attributes,
    get_element_by_id,
    int_or_none,
    parse_count,
    parse_duration,
    unified_timestamp,
    OnDemandPagedList,
    try_get,
)

class NewgroundsIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?newgrounds\.com/(?:audio/listen|portal/view)/(?P<id>\d+)(?:/format/flash)?'
    _TESTS = [{
        'url': 'https://www.newgrounds.com/audio/listen/549479',
        'md5': 'fe6033d297591288fa1c1f780386f07a',
        'info_dict': {
            'id': '549479',
            'ext': 'mp3',
            'title': 'B7 - BusMode',
            'uploader': 'Burn7',
            'timestamp': 1378878540,
            'upload_date': '20130911',
            'duration': 143,
            'view_count': int,
            'description': 'md5:b8b3c2958875189f07d8e313462e8c4f',
        },
    }, {
        'url': 'https://www.newgrounds.com/portal/view/1',
        'md5': 'fbfb40e2dc765a7e830cb251d370d981',
        'info_dict': {
            'id': '1',
            'ext': 'mp4',
            'title': 'Scrotum 1',
            'uploader': 'Brian-Beaton',
            'timestamp': 955064100,
            'upload_date': '20000406',
            'view_count': int,
            'description': 'Scrotum plays "catch."',
            'age_limit': 17,
        },
    }, {
        # source format unavailable, additional mp4 formats
        'url': 'http://www.newgrounds.com/portal/view/689400',
        'info_dict': {
            'id': '689400',
            'ext': 'mp4',
            'title': 'ZTV News Episode 8',
            'uploader': 'ZONE-SAMA',
            'timestamp': 1487965140,
            'upload_date': '20170224',
            'view_count': int,
            'description': 'md5:aff9b330ec2e78ed93b1ad6d017accc6',
            'age_limit': 17,
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.newgrounds.com/portal/view/297383',
        'md5': '2c11f5fd8cb6b433a63c89ba3141436c',
        'info_dict': {
            'id': '297383',
            'ext': 'mp4',
            'title': 'Metal Gear Awesome',
            'uploader': 'Egoraptor',
            'timestamp': 1140663240,
            'upload_date': '20060223',
            'view_count': int,
            'description': 'md5:9246c181614e23754571995104da92e0',
            'age_limit': 13,
        }
    }, {
        'url': 'https://www.newgrounds.com/portal/view/297383/format/flash',
        'md5': '5d05585a9a0caca059f5abfbd3865524',
        'info_dict': {
            'id': '297383',
            'ext': 'swf',
            'title': 'Metal Gear Awesome',
            'description': 'Metal Gear Awesome',
            'uploader': 'Egoraptor',
            'upload_date': '20060223',
            'timestamp': 1140663240,
            'age_limit': 13,
        }
    }]
    _AGE_LIMIT = {
        'e': 0,
        't': 13,
        'm': 17,
        'a': 18,
    }

    def _real_extract(self, url):
        media_id = self._match_id(url)
        formats = []
        uploader = None
        webpage = self._download_webpage(url, media_id)

        title = self._html_extract_title(webpage)

        media_url_string = self._search_regex(
            r'"url"\s*:\s*("[^"]+"),', webpage, 'media url', default=None)

        if media_url_string:
            media_url = self._parse_json(media_url_string, media_id)
            formats = [{
                'url': media_url,
                'format_id': 'source',
                'quality': 1,
            }]
        else:
            json_video = self._download_json('https://www.newgrounds.com/portal/video/' + media_id, media_id, headers={
                'Accept': 'application/json',
                'Referer': url,
                'X-Requested-With': 'XMLHttpRequest'
            })

            uploader = json_video.get('author')
            media_formats = json_video.get('sources', [])
            for media_format in media_formats:
                media_sources = media_formats[media_format]
                for source in media_sources:
                    formats.append({
                        'format_id': media_format,
                        'quality': int_or_none(media_format[:-1]),
                        'url': source.get('src')
                    })

        if not uploader:
            uploader = self._html_search_regex(
                (r'(?s)
]*>(.+?)
.*?\s*(?:Author|Artist)\s*',
                 r'(?:Author|Writer)\s*]+>([^<]+)'), webpage, 'uploader',
                fatal=False)

        age_limit = self._html_search_regex(
            r'
]+>', webpage, 'age_limit', default='e')
        age_limit = self._AGE_LIMIT.get(age_limit)

        timestamp = unified_timestamp(self._html_search_regex(
            (r'
\s*Uploaded\s*
\s*
([^<]+
\s*
[^<]+)',
             r'
\s*Uploaded\s*
\s*
([^<]+)'), webpage, 'timestamp',
            default=None))

        duration = parse_duration(self._html_search_regex(
            r'"duration"\s*:\s*["\']?(\d+)["\']?', webpage,
            'duration', default=None))

        description = clean_html(get_element_by_id('author_comments', webpage)) or self._og_search_description(webpage)

        view_count = parse_count(self._html_search_regex(
            r'(?s)
\s*(?:Views|Listens)\s*
\s*
([\d\.,]+)
', webpage,
            'view count', default=None))

        filesize = int_or_none(self._html_search_regex(
            r'"filesize"\s*:\s*["\']?([\d]+)["\']?,', webpage, 'filesize',
            default=None))

        video_type_description = self._html_search_regex(
            r'"description"\s*:\s*["\']?([^"\']+)["\']?,', webpage, 'filesize',
            default=None)

        if len(formats) == 1:
            formats[0]['filesize'] = filesize

        if video_type_description == 'Audio File':
            formats[0]['vcodec'] = 'none'
        self._check_formats(formats, media_id)
        self._sort_formats(formats)

        return {
            'id': media_id,
            'title': title,
            'uploader': uploader,
            'timestamp': timestamp,
            'duration': duration,
            'formats': formats,
            'thumbnail': self._og_search_thumbnail(webpage),
            'description': description,
            'age_limit': age_limit,
            'view_count': view_count,
        }

class NewgroundsPlaylistIE(InfoExtractor):
    IE_NAME = 'Newgrounds:playlist'
    _VALID_URL = r'https?://(?:www\.)?newgrounds\.com/(?:collection|[^/]+/search/[^/]+)/(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.newgrounds.com/collection/cats',
        'info_dict': {
            'id': 'cats',
            'title': 'Cats',
        },
        'playlist_mincount': 45,
    }, {
        'url': 'https://www.newgrounds.com/collection/dogs',
        'info_dict': {
            'id': 'dogs',
            'title': 'Dogs',
        },
        'playlist_mincount': 26,
    }, {
        'url': 'http://www.newgrounds.com/audio/search/title/cats',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, playlist_id)

        title = self._html_extract_title(webpage, default=None)

        # cut left menu
        webpage = self._search_regex(
            r'(?s)
]+\bclass=["\']column wide(.+)',
            webpage, 'wide column', default=webpage)

        entries = []
        for a, path, media_id in re.findall(
                r'(]+\bhref=["\'][^"\']+((?:portal/view|audio/listen)/(\d+))[^>]+>)',
                webpage):
            a_class = extract_attributes(a).get('class')
            if a_class not in ('item-portalsubmission', 'item-audiosubmission'):
                continue
            entries.append(
                self.url_result(
                    f'https://www.newgrounds.com/{path}',
                    ie=NewgroundsIE.ie_key(), video_id=media_id))

        return self.playlist_result(entries, playlist_id, title)

class NewgroundsUserIE(InfoExtractor):
    IE_NAME = 'Newgrounds:user'
    _VALID_URL = r'https?://(?P<id>[^\.]+)\.newgrounds\.com/(?:movies|audio)/?(?:[#?]|$)'
    _TESTS = [{
        'url': 'https://burn7.newgrounds.com/audio',
        'info_dict': {
            'id': 'burn7',
        },
        'playlist_mincount': 150,
    }, {
        'url': 'https://burn7.newgrounds.com/movies',
        'info_dict': {
            'id': 'burn7',
        },
        'playlist_mincount': 2,
    }, {
        'url': 'https://brian-beaton.newgrounds.com/movies',
        'info_dict': {
            'id': 'brian-beaton',
        },
        'playlist_mincount': 10,
    }]
    _PAGE_SIZE = 30

    def _fetch_page(self, channel_id, url, page):
        page += 1
        posts_info = self._download_json(
            f'{url}/page/{page}', channel_id,
            note=f'Downloading page {page}', headers={
                'Accept': 'application/json, text/javascript, */*; q = 0.01',
                'X-Requested-With': 'XMLHttpRequest',
            })
        sequence = posts_info.get('sequence', [])
        for year in sequence:
            posts = try_get(posts_info, lambda x: x['years'][str(year)]['items'])
            for post in posts:
                path, media_id = self._search_regex(
                    r']+\bhref=["\'][^"\']+((?:portal/view|audio/listen)/(\d+))[^>]+>',
                    post, 'url', group=(1, 2))
                yield self.url_result(f'https://www.newgrounds.com/{path}', NewgroundsIE.ie_key(), media_id)

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

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

        return self.playlist_result(entries, channel_id)


code:
import re

from .common import InfoExtractor
from ..utils import ExtractorError

class NewsPicksIE(InfoExtractor):
    _VALID_URL = r'https://newspicks\.com/movie-series/(?P<channel_id>\d+)\?movieId=(?P<id>\d+)'

    _TESTS = [{
        'url': 'https://newspicks.com/movie-series/11?movieId=1813',
        'info_dict': {
            'id': '1813',
            'title': '日本の課題を破壊せよ【ゲスト：成田悠輔】',
            'description': 'md5:09397aad46d6ded6487ff13f138acadf',
            'channel': 'HORIE ONE',
            'channel_id': '11',
            'release_date': '20220117',
            'thumbnail': r're:https://.+jpg',
            'ext': 'mp4',
        },
    }]

    def _real_extract(self, url):
        video_id, channel_id = self._match_valid_url(url).group('id', 'channel_id')
        webpage = self._download_webpage(url, video_id)
        entries = self._parse_html5_media_entries(
            url, webpage.replace('movie-for-pc', 'movie'), video_id, 'hls')
        if not entries:
            raise ExtractorError('No HTML5 media elements found')
        info = entries[0]
        self._sort_formats(info['formats'])

        title = self._html_search_meta('og:title', webpage, fatal=False)
        description = self._html_search_meta(
            ('og:description', 'twitter:title'), webpage, fatal=False)
        channel = self._html_search_regex(
            r'value="11".+?<div\s+class="title">(.+?)</div', webpage, 'channel name', fatal=False)
        if not title or not channel:
            title, channel = re.split(r'\s*|\s*', self._html_extract_title(webpage))

        release_date = self._search_regex(
            r'<span\s+class="on-air-date">\s*(\d+)年(\d+)月(\d+)日\s*</span>',
            webpage, 'release date', fatal=False, group=(1, 2, 3))

        info.update({
            'id': video_id,
            'title': title,
            'description': description,
            'channel': channel,
            'channel_id': channel_id,
            'release_date': ('%04d%02d%02d' % tuple(map(int, release_date))) if release_date else None,
        })
        return info


code:
import base64
import hashlib

from .common import InfoExtractor
from ..aes import aes_cbc_decrypt_bytes, unpad_pkcs7
from ..utils import (
    int_or_none,
    parse_codecs,
    parse_duration,
)

class NewstubeIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?newstube\.ru/media/(?P<id>.+)'
    _TEST = {
        'url': 'http://www.newstube.ru/media/telekanal-cnn-peremestil-gorod-slavyansk-v-krym',
        'md5': '9d10320ad473444352f72f746ccb8b8c',
        'info_dict': {
            'id': '728e0ef2-e187-4012-bac0-5a081fdcb1f6',
            'ext': 'mp4',
            'title': 'Телеканал CNN переместил город Славянск в Крым',
            'description': 'md5:419a8c9f03442bc0b0a794d689360335',
            'duration': 31.05,
        },
    }

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

        page = self._download_webpage(url, video_id)
        title = self._html_search_meta(['og:title', 'twitter:title'], page, fatal=True)

        video_guid = self._html_search_regex(
            r'<meta\s+property="og:video(?::(?:(?:secure_)?url|iframe))?"\s+content="https?://(?:www\.)?newstube\.ru/embed/(?P<guid>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})',
            page, 'video GUID')

        enc_data = base64.b64decode(self._download_webpage(
            'https://www.newstube.ru/embed/api/player/getsources2',
            video_guid, query={
                'guid': video_guid,
                'ff': 3,
            }))
        key = hashlib.pbkdf2_hmac(
            'sha1', video_guid.replace('-', '').encode(), enc_data[:16], 1)[:16]
        dec_data = unpad_pkcs7(aes_cbc_decrypt_bytes(enc_data[32:], key, enc_data[16:32]))
        sources = self._parse_json(dec_data, video_guid)

        formats = []
        for source in sources:
            source_url = source.get('Src')
            if not source_url:
                continue
            height = int_or_none(source.get('Height'))
            f = {
                'format_id': 'http' + ('-%dp' % height if height else ''),
                'url': source_url,
                'width': int_or_none(source.get('Width')),
                'height': height,
            }
            source_type = source.get('Type')
            if source_type:
                f.update(parse_codecs(self._search_regex(
                    r'codecs="([^"]+)"', source_type, 'codecs', fatal=False)))
            formats.append(f)

        self._check_formats(formats, video_guid)
        self._sort_formats(formats)

        return {
            'id': video_guid,
            'title': title,
            'description': self._html_search_meta(['description', 'og:description'], page),
            'thumbnail': self._html_search_meta(['og:image:secure_url', 'og:image', 'twitter:image'], page),
            'duration': parse_duration(self._html_search_meta('duration', page)),
            'formats': formats,
        }


code:
from .common import InfoExtractor
from ..utils import (
    js_to_json,
    merge_dicts,
)

class NewsyIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?newsy\.com/stories/(?P<id>[^/?#$&]+)'

    _TESTS = [{
        'url': 'https://www.newsy.com/stories/nft-trend-leads-to-fraudulent-art-auctions/',
        'info_dict': {
            'id': '609d65125b086c24fb529312',
            'ext': 'mp4',
            'title': 'NFT Art Auctions Have A Piracy Problem',
            'description': 'md5:971e52ab8bc97e50305475cde8284c83',
            'display_id': 'nft-trend-leads-to-fraudulent-art-auctions',
            'timestamp': 1621339200,
            'duration': 339630,
            'thumbnail': 'https://cdn.newsy.com/images/videos/x/1620927824_xyrrP4.jpg',
            'upload_date': '20210518'
        },
        'params': {'skip_download': True}
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        data_json = self._parse_json(self._html_search_regex(
            r'data-video-player\s?=\s?"({[^"]+})">', webpage, 'data'), display_id, js_to_json)
        ld_json = self._search_json_ld(webpage, display_id, fatal=False)

        formats, subtitles = [], {}
        if data_json.get('stream'):
            fmts, subs = self._extract_m3u8_formats_and_subtitles(data_json['stream'], display_id)
            formats.extend(fmts)
            subtitles = self._merge_subtitles(subtitles, subs)
        self._sort_formats(formats)
        return merge_dicts(ld_json, {
            'id': data_json['id'],
            'display_id': display_id,
            'title': data_json.get('headline'),
            'duration': data_json.get('duration'),
            'thumbnail': data_json.get('image'),
            'formats': formats,
            'subtitles': subtitles,
        })


code:
from .common import InfoExtractor
from ..compat import compat_urlparse
from ..utils import (
    clean_html,
    get_element_by_class,
    int_or_none,
    parse_iso8601,
    remove_start,
    unified_timestamp,
)

class NextMediaIE(InfoExtractor):
    IE_DESC = '蘋果日報'
    _VALID_URL = r'https?://hk\.apple\.nextmedia\.com/[^/]+/[^/]+/(?P<date>\d+)/(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://hk.apple.nextmedia.com/realtime/news/20141108/53109199',
        'md5': 'dff9fad7009311c421176d1ac90bfe4f',
        'info_dict': {
            'id': '53109199',
            'ext': 'mp4',
            'title': '【佔領金鐘】50外國領事議員撐場 讚學生勇敢香港有希望',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:28222b9912b6665a21011b034c70fcc7',
            'timestamp': 1415456273,
            'upload_date': '20141108',
        }
    }]

    _URL_PATTERN = r'\{ url: \'(.+)\' \}'

    def _real_extract(self, url):
        news_id = self._match_id(url)
        page = self._download_webpage(url, news_id)
        return self._extract_from_nextmedia_page(news_id, url, page)

    def _extract_from_nextmedia_page(self, news_id, url, page):
        redirection_url = self._search_regex(
            r'window\.location\.href\s*=\s*([\'"])(?P<url>(?!\1).+)\1',
            page, 'redirection URL', default=None, group='url')
        if redirection_url:
            return self.url_result(compat_urlparse.urljoin(url, redirection_url))

        title = self._fetch_title(page)
        video_url = self._search_regex(self._URL_PATTERN, page, 'video url')

        attrs = {
            'id': news_id,
            'title': title,
            'url': video_url,  # ext can be inferred from url
            'thumbnail': self._fetch_thumbnail(page),
            'description': self._fetch_description(page),
        }

        timestamp = self._fetch_timestamp(page)
        if timestamp:
            attrs['timestamp'] = timestamp
        else:
            attrs['upload_date'] = self._fetch_upload_date(url)

        return attrs

    def _fetch_title(self, page):
        return self._og_search_title(page)

    def _fetch_thumbnail(self, page):
        return self._og_search_thumbnail(page)

    def _fetch_timestamp(self, page):
        dateCreated = self._search_regex('"dateCreated":"([^"]+)"', page, 'created time')
        return parse_iso8601(dateCreated)

    def _fetch_upload_date(self, url):
        return self._search_regex(self._VALID_URL, url, 'upload date', group='date')

    def _fetch_description(self, page):
        return self._og_search_property('description', page)

class NextMediaActionNewsIE(NextMediaIE):
    IE_DESC = '蘋果日報 - 動新聞'
    _VALID_URL = r'https?://hk\.dv\.nextmedia\.com/actionnews/[^/]+/(?P<date>\d+)/(?P<id>\d+)/\d+'
    _TESTS = [{
        'url': 'http://hk.dv.nextmedia.com/actionnews/hit/20150121/19009428/20061460',
        'md5': '05fce8ffeed7a5e00665d4b7cf0f9201',
        'info_dict': {
            'id': '19009428',
            'ext': 'mp4',
            'title': '【壹週刊】細10年男友偷食　50歲邵美琪再失戀',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:cd802fad1f40fd9ea178c1e2af02d659',
            'timestamp': 1421791200,
            'upload_date': '20150120',
        }
    }]

    def _real_extract(self, url):
        news_id = self._match_id(url)
        actionnews_page = self._download_webpage(url, news_id)
        article_url = self._og_search_url(actionnews_page)
        article_page = self._download_webpage(article_url, news_id)
        return self._extract_from_nextmedia_page(news_id, url, article_page)

class AppleDailyIE(NextMediaIE):
    IE_DESC = '臺灣蘋果日報'
    _VALID_URL = r'https?://(www|ent)\.appledaily\.com\.tw/[^/]+/[^/]+/[^/]+/(?P<date>\d+)/(?P<id>\d+)(/.*)?'
    _TESTS = [{
        'url': 'http://ent.appledaily.com.tw/enews/article/entertainment/20150128/36354694',
        'md5': 'a843ab23d150977cc55ef94f1e2c1e4d',
        'info_dict': {
            'id': '36354694',
            'ext': 'mp4',
            'title': '周亭羽走過摩鐵陰霾2男陪吃 九把刀孤寒看醫生',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:2acd430e59956dc47cd7f67cb3c003f4',
            'upload_date': '20150128',
        }
    }, {
        'url': 'http://www.appledaily.com.tw/realtimenews/article/strange/20150128/550549/%E4%B8%8D%E6%BB%BF%E8%A2%AB%E8%B8%A9%E8%85%B3%E3%80%80%E5%B1%B1%E6%9D%B1%E5%85%A9%E5%A4%A7%E5%AA%BD%E4%B8%80%E8%B7%AF%E6%89%93%E4%B8%8B%E8%BB%8A',
        'md5': '86b4e9132d158279c7883822d94ccc49',
        'info_dict': {
            'id': '550549',
            'ext': 'mp4',
            'title': '不滿被踩腳　山東兩大媽一路打下車',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:175b4260c1d7c085993474217e4ab1b4',
            'upload_date': '20150128',
        }
    }, {
        'url': 'http://www.appledaily.com.tw/animation/realtimenews/new/20150128/5003671',
        'md5': '03df296d95dedc2d5886debbb80cb43f',
        'info_dict': {
            'id': '5003671',
            'ext': 'mp4',
            'title': '20正妹熱舞　《刀龍傳說Online》火辣上市',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:23c0aac567dc08c9c16a3161a2c2e3cd',
            'upload_date': '20150128',
        },
        'skip': 'redirect to http://www.appledaily.com.tw/animation/',
    }, {
        # No thumbnail
        'url': 'http://www.appledaily.com.tw/animation/realtimenews/new/20150128/5003673/',
        'md5': 'b06182cd386ea7bc6115ec7ff0f72aeb',
        'info_dict': {
            'id': '5003673',
            'ext': 'mp4',
            'title': '半夜尿尿　好像會看到___',
            'description': 'md5:61d2da7fe117fede148706cdb85ac066',
            'upload_date': '20150128',
        },
        'expected_warnings': [
            'video thumbnail',
        ],
        'skip': 'redirect to http://www.appledaily.com.tw/animation/',
    }, {
        'url': 'http://www.appledaily.com.tw/appledaily/article/supplement/20140417/35770334/',
        'md5': 'eaa20e6b9df418c912d7f5dec2ba734d',
        'info_dict': {
            'id': '35770334',
            'ext': 'mp4',
            'title': '咖啡占卜測 XU裝熟指數',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'md5:7b859991a6a4fedbdf3dd3b66545c748',
            'upload_date': '20140417',
        },
    }, {
        'url': 'http://www.appledaily.com.tw/actionnews/appledaily/7/20161003/960588/',
        'only_matching': True,
    }, {
        # Redirected from http://ent.appledaily.com.tw/enews/article/entertainment/20150128/36354694
        'url': 'http://ent.appledaily.com.tw/section/article/headline/20150128/36354694',
        'only_matching': True,
    }]

    _URL_PATTERN = r'\{url: \'(.+)\'\}'

    def _fetch_title(self, page):
        return (self._html_search_regex(r'
([^<>]+)
', page, 'news title', default=None)
                or self._html_search_meta('description', page, 'news title'))

    def _fetch_thumbnail(self, page):
        return self._html_search_regex(r"setInitialImage\(\'([^']+)'\)", page, 'video thumbnail', fatal=False)

    def _fetch_timestamp(self, page):
        return None

    def _fetch_description(self, page):
        return self._html_search_meta('description', page, 'news description')

class NextTVIE(InfoExtractor):
    IE_DESC = '壹電視'
    _VALID_URL = r'https?://(?:www\.)?nexttv\.com\.tw/(?:[^/]+/)+(?P<id>\d+)'

    _TEST = {
        'url': 'http://www.nexttv.com.tw/news/realtime/politics/11779671',
        'info_dict': {
            'id': '11779671',
            'ext': 'mp4',
            'title': '「超收稅」近4千億！　藍議員籲發消費券',
            'thumbnail': r're:^https?://.*\.jpg$',
            'timestamp': 1484825400,
            'upload_date': '20170119',
            'view_count': int,
        },
    }

    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 = self._hidden_inputs(webpage)

        video_url = data['ntt-vod-src-detailview']

        date_str = get_element_by_class('date', webpage)
        timestamp = unified_timestamp(date_str + '+0800') if date_str else None

        view_count = int_or_none(remove_start(
            clean_html(get_element_by_class('click', webpage)), '點閱：'))

        return {
            'id': video_id,
            'title': title,
            'url': video_url,
            'thumbnail': data.get('ntt-vod-img-src'),
            'timestamp': timestamp,
            'view_count': view_count,
        }


code:
import hashlib
import random
import re
import time

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    ExtractorError,
    int_or_none,
    parse_duration,
    srt_subtitles_timecode,
    traverse_obj,
    try_get,
    urlencode_postdata,
)

class NexxIE(InfoExtractor):
    _VALID_URL = r'''(?x)
                        (?:
                            https?://api\.nexx(?:\.cloud|cdn\.com)/v3(?:\.\d)?/(?P<domain_id>\d+)/videos/byid/|
                            nexx:(?:(?P<domain_id_s>\d+):)?|
                            https?://arc\.nexx\.cloud/api/video/
                        )
                        (?P<id>\d+)
                    '''
    _TESTS = [{
        # movie
        'url': 'https://api.nexx.cloud/v3/748/videos/byid/128907',
        'md5': '31899fd683de49ad46f4ee67e53e83fe',
        'info_dict': {
            'id': '128907',
            'ext': 'mp4',
            'title': 'Stiftung Warentest',
            'alt_title': 'Wie ein Test abläuft',
            'description': 'md5:d1ddb1ef63de721132abd38639cc2fd2',
            'creator': 'SPIEGEL TV',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 2509,
            'timestamp': 1384264416,
            'upload_date': '20131112',
        },
        'skip': 'Spiegel nexx CDNs are now disabled'
    }, {
        # episode with captions
        'url': 'https://api.nexx.cloud/v3.1/741/videos/byid/1701834',
        'info_dict': {
            'id': '1701834',
            'ext': 'mp4',
            'title': 'Mein Leben mit \'nem TikTok E-Boy 😤',
            'alt_title': 'Mein Leben mit \'nem TikTok E-Boy 😤',
            'description': 'md5:f84f395a881fd143f952c892deab528d',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 770,
            'timestamp': 1595600027,
            'upload_date': '20200724',
            'episode_number': 2,
            'season_number': 2,
            'episode': 'Episode 2',
            'season': 'Season 2',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'nexx:741:1269984',
        'md5': 'd5f14e14b592501e51addd5abef95a7f',
        'info_dict': {
            'id': '1269984',
            'ext': 'mp4',
            'title': '1 TAG ohne KLO... wortwörtlich! ?',
            'alt_title': '1 TAG ohne KLO... wortwörtlich! ?',
            'description': 'md5:2016393a31991a900946432ccdd09a6f',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 607,
            'timestamp': 1518614955,
            'upload_date': '20180214',
        },
    }, {
        # free cdn from http://www.spiegel.de/video/eifel-zoo-aufregung-um-ausgebrochene-raubtiere-video-99018031.html
        'url': 'nexx:747:1533779',
        'md5': '6bf6883912b82b7069fb86c2297e9893',
        'info_dict': {
            'id': '1533779',
            'ext': 'mp4',
            'title': 'Aufregung um ausgebrochene Raubtiere',
            'alt_title': 'Eifel-Zoo',
            'description': 'md5:f21375c91c74ad741dcb164c427999d2',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 111,
            'timestamp': 1527874460,
            'upload_date': '20180601',
        },
        'skip': 'Spiegel nexx CDNs are now disabled'
    }, {
        'url': 'https://api.nexxcdn.com/v3/748/videos/byid/128907',
        'only_matching': True,
    }, {
        'url': 'nexx:748:128907',
        'only_matching': True,
    }, {
        'url': 'nexx:128907',
        'only_matching': True,
    }, {
        'url': 'https://arc.nexx.cloud/api/video/128907.json',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_domain_id(webpage):
        mobj = re.search(
            r'<script\b[^>]+\bsrc=["\'](?:https?:)?//(?:require|arc)\.nexx(?:\.cloud|cdn\.com)/(?:sdk/)?(?P<id>\d+)',
            webpage)
        return mobj.group('id') if mobj else None

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        # Reference:
        # 1. https://nx-s.akamaized.net/files/201510/44.pdf

        entries = []

        # JavaScript Integration
        domain_id = NexxIE._extract_domain_id(webpage)
        if domain_id:
            for video_id in re.findall(
                    r'(?is)onPLAYReady.+?_play\.(?:init|(?:control\.)?addPlayer)\s*\(.+?\s*,\s*["\']?(\d+)',
                    webpage):
                entries.append(
                    'https://api.nexx.cloud/v3/%s/videos/byid/%s'
                    % (domain_id, video_id))

        # TODO: support more embed formats

        return entries

    def _handle_error(self, response):
        if traverse_obj(response, ('metadata', 'notice'), expected_type=str):
            self.report_warning('%s said: %s' % (self.IE_NAME, response['metadata']['notice']))
        status = int_or_none(try_get(
            response, lambda x: x['metadata']['status']) or 200)
        if 200 <= status < 300:
            return
        raise ExtractorError(
            '%s said: %s' % (self.IE_NAME, response['metadata']['errorhint']),
            expected=True)

    def _call_api(self, domain_id, path, video_id, data=None, headers={}):
        headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
        result = self._download_json(
            'https://api.nexx.cloud/v3/%s/%s' % (domain_id, path), video_id,
            'Downloading %s JSON' % path, data=urlencode_postdata(data),
            headers=headers)
        self._handle_error(result)
        return result['result']

    def _extract_free_formats(self, video, video_id):
        stream_data = video['streamdata']
        cdn = stream_data['cdnType']
        assert cdn == 'free'

        hash = video['general']['hash']

        ps = compat_str(stream_data['originalDomain'])
        if stream_data['applyFolderHierarchy'] == 1:
            s = ('%04d' % int(video_id))[::-1]
            ps += '/%s/%s' % (s[0:2], s[2:4])
        ps += '/%s/%s_' % (video_id, hash)

        t = 'http://%s' + ps
        fd = stream_data['azureFileDistribution'].split(',')
        cdn_provider = stream_data['cdnProvider']

        def p0(p):
            return '_%s' % p if stream_data['applyAzureStructure'] == 1 else ''

        formats = []
        if cdn_provider == 'ak':
            t += ','
            for i in fd:
                p = i.split(':')
                t += p[1] + p0(int(p[0])) + ','
            t += '.mp4.csmil/master.%s'
        elif cdn_provider == 'ce':
            k = t.split('/')
            h = k.pop()
            http_base = t = '/'.join(k)
            http_base = http_base % stream_data['cdnPathHTTP']
            t += '/asset.ism/manifest.%s?dcp_ver=aos4&videostream='
            for i in fd:
                p = i.split(':')
                tbr = int(p[0])
                filename = '%s%s%s.mp4' % (h, p[1], p0(tbr))
                f = {
                    'url': http_base + '/' + filename,
                    'format_id': '%s-http-%d' % (cdn, tbr),
                    'tbr': tbr,
                }
                width_height = p[1].split('x')
                if len(width_height) == 2:
                    f.update({
                        'width': int_or_none(width_height[0]),
                        'height': int_or_none(width_height[1]),
                    })
                formats.append(f)
                a = filename + ':%s' % (tbr * 1000)
                t += a + ','
            t = t[:-1] + '&audiostream=' + a.split(':')[0]
        else:
            assert False

        if cdn_provider == 'ce':
            formats.extend(self._extract_mpd_formats(
                t % (stream_data['cdnPathDASH'], 'mpd'), video_id,
                mpd_id='%s-dash' % cdn, fatal=False))
        formats.extend(self._extract_m3u8_formats(
            t % (stream_data['cdnPathHLS'], 'm3u8'), video_id, 'mp4',
            entry_protocol='m3u8_native', m3u8_id='%s-hls' % cdn, fatal=False))

        return formats

    def _extract_3q_formats(self, video, video_id):
        stream_data = video['streamdata']
        cdn = stream_data['cdnType']
        assert cdn == '3q'

        q_acc, q_prefix, q_locator, q_hash = stream_data['qAccount'], stream_data['qPrefix'], stream_data['qLocator'], stream_data['qHash']
        protection_key = traverse_obj(
            video, ('protectiondata', 'key'), expected_type=str)

        def get_cdn_shield_base(shield_type=''):
            for secure in ('', 's'):
                cdn_shield = stream_data.get('cdnShield%sHTTP%s' % (shield_type, secure.upper()))
                if cdn_shield:
                    return 'http%s://%s' % (secure, cdn_shield)
            return f'http://sdn-global-{"prog" if shield_type.lower() == "prog" else "streaming"}-cache.3qsdn.com/' + (f's/{protection_key}/' if protection_key else '')

        stream_base = get_cdn_shield_base()

        formats = []
        formats.extend(self._extract_m3u8_formats(
            f'{stream_base}{q_acc}/files/{q_prefix}/{q_locator}/{q_acc}-{stream_data.get("qHEVCHash") or q_hash}.ism/manifest.m3u8',
            video_id, 'mp4', m3u8_id=f'{cdn}-hls', fatal=False))
        formats.extend(self._extract_mpd_formats(
            f'{stream_base}{q_acc}/files/{q_prefix}/{q_locator}/{q_acc}-{q_hash}.ism/manifest.mpd',
            video_id, mpd_id=f'{cdn}-dash', fatal=False))

        progressive_base = get_cdn_shield_base('Prog')
        q_references = stream_data.get('qReferences') or ''
        fds = q_references.split(',')
        for fd in fds:
            ss = fd.split(':')
            if len(ss) != 3:
                continue
            tbr = int_or_none(ss[1], scale=1000)
            formats.append({
                'url': f'{progressive_base}{q_acc}/uploads/{q_acc}-{ss[2]}.webm',
                'format_id': f'{cdn}-{ss[0]}{"-%s" % tbr if tbr else ""}',
                'tbr': tbr,
            })

        azure_file_distribution = stream_data.get('azureFileDistribution') or ''
        fds = azure_file_distribution.split(',')
        for fd in fds:
            ss = fd.split(':')
            if len(ss) != 3:
                continue
            tbr = int_or_none(ss[0])
            width, height = ss[1].split('x') if len(ss[1].split('x')) == 2 else (None, None)
            f = {
                'url': f'{progressive_base}{q_acc}/files/{q_prefix}/{q_locator}/{ss[2]}.mp4',
                'format_id': f'{cdn}-http-{"-%s" % tbr if tbr else ""}',
                'tbr': tbr,
                'width': int_or_none(width),
                'height': int_or_none(height),
            }
            formats.append(f)

        return formats

    def _extract_azure_formats(self, video, video_id):
        stream_data = video['streamdata']
        cdn = stream_data['cdnType']
        assert cdn == 'azure'

        azure_locator = stream_data['azureLocator']

        def get_cdn_shield_base(shield_type='', static=False):
            for secure in ('', 's'):
                cdn_shield = stream_data.get('cdnShield%sHTTP%s' % (shield_type, secure.upper()))
                if cdn_shield:
                    return 'http%s://%s' % (secure, cdn_shield)
            else:
                if 'fb' in stream_data['azureAccount']:
                    prefix = 'df' if static else 'f'
                else:
                    prefix = 'd' if static else 'p'
                account = int(stream_data['azureAccount'].replace('nexxplayplus', '').replace('nexxplayfb', ''))
                return 'http://nx-%s%02d.akamaized.net/' % (prefix, account)

        language = video['general'].get('language_raw') or ''

        azure_stream_base = get_cdn_shield_base()
        is_ml = ',' in language
        azure_manifest_url = '%s%s/%s_src%s.ism/Manifest' % (
            azure_stream_base, azure_locator, video_id, ('_manifest' if is_ml else '')) + '%s'

        protection_token = try_get(
            video, lambda x: x['protectiondata']['token'], compat_str)
        if protection_token:
            azure_manifest_url += '?hdnts=%s' % protection_token

        formats = self._extract_m3u8_formats(
            azure_manifest_url % '(format=m3u8-aapl)',
            video_id, 'mp4', 'm3u8_native',
            m3u8_id='%s-hls' % cdn, fatal=False)
        formats.extend(self._extract_mpd_formats(
            azure_manifest_url % '(format=mpd-time-csf)',
            video_id, mpd_id='%s-dash' % cdn, fatal=False))
        formats.extend(self._extract_ism_formats(
            azure_manifest_url % '', video_id, ism_id='%s-mss' % cdn, fatal=False))

        azure_progressive_base = get_cdn_shield_base('Prog', True)
        azure_file_distribution = stream_data.get('azureFileDistribution')
        if azure_file_distribution:
            fds = azure_file_distribution.split(',')
            if fds:
                for fd in fds:
                    ss = fd.split(':')
                    if len(ss) == 2:
                        tbr = int_or_none(ss[0])
                        if tbr:
                            f = {
                                'url': '%s%s/%s_src_%s_%d.mp4' % (
                                    azure_progressive_base, azure_locator, video_id, ss[1], tbr),
                                'format_id': '%s-http-%d' % (cdn, tbr),
                                'tbr': tbr,
                            }
                            width_height = ss[1].split('x')
                            if len(width_height) == 2:
                                f.update({
                                    'width': int_or_none(width_height[0]),
                                    'height': int_or_none(width_height[1]),
                                })
                            formats.append(f)

        return formats

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

        video = None

        def find_video(result):
            if isinstance(result, dict):
                return result
            elif isinstance(result, list):
                vid = int(video_id)
                for v in result:
                    if try_get(v, lambda x: x['general']['ID'], int) == vid:
                        return v
            return None

        response = self._download_json(
            'https://arc.nexx.cloud/api/video/%s.json' % video_id,
            video_id, fatal=False)
        if response and isinstance(response, dict):
            result = response.get('result')
            if result:
                video = find_video(result)

        # not all videos work via arc, e.g. nexx:741:1269984
        if not video:
            # Reverse engineered from JS code (see getDeviceID function)
            device_id = '%d:%d:%d%d' % (
                random.randint(1, 4), int(time.time()),
                random.randint(1e4, 99999), random.randint(1, 9))

            result = self._call_api(domain_id, 'session/init', video_id, data={
                'nxp_devh': device_id,
                'nxp_userh': '',
                'precid': '0',
                'playlicense': '0',
                'screenx': '1920',
                'screeny': '1080',
                'playerversion': '6.0.00',
                'gateway': 'html5',
                'adGateway': '',
                'explicitlanguage': 'en-US',
                'addTextTemplates': '1',
                'addDomainData': '1',
                'addAdModel': '1',
            }, headers={
                'X-Request-Enable-Auth-Fallback': '1',
            })

            cid = result['general']['cid']

            # As described in [1] X-Request-Token generation algorithm is
            # as follows:
            #   md5( operation + domain_id + domain_secret )
            # where domain_secret is a static value that will be given by nexx.tv
            # as per [1]. Here is how this "secret" is generated (reversed
            # from _play._factory.data.getDomainData function, search for
            # domaintoken or enableAPIAccess). So it's actually not static
            # and not that much of a secret.
            # 1. https://nexxtvstorage.blob.core.windows.net/files/201610/27.pdf
            secret = result['device']['domaintoken'][int(device_id[0]):]
            secret = secret[0:len(secret) - int(device_id[-1])]

            op = 'byid'

            # Reversed from JS code for _play.api.call function (search for
            # X-Request-Token)
            request_token = hashlib.md5(
                ''.join((op, domain_id, secret)).encode('utf-8')).hexdigest()

            result = self._call_api(
                domain_id, 'videos/%s/%s' % (op, video_id), video_id, data={
                    'additionalfields': 'language,channel,format,licenseby,slug,fileversion,episode,season',
                    'addInteractionOptions': '1',
                    'addStatusDetails': '1',
                    'addStreamDetails': '1',
                    'addFeatures': '1',
                    # Caption format selection doesn't seem to be enforced?
                    'addCaptions': 'vtt',
                    'addScenes': '1',
                    'addChapters': '1',
                    'addHotSpots': '1',
                    'addConnectedMedia': 'persons',
                    'addBumpers': '1',
                }, headers={
                    'X-Request-CID': cid,
                    'X-Request-Token': request_token,
                })
            video = find_video(result)

        general = video['general']
        title = general['title']

        cdn = video['streamdata']['cdnType']

        if cdn == 'azure':
            formats = self._extract_azure_formats(video, video_id)
        elif cdn == 'free':
            formats = self._extract_free_formats(video, video_id)
        elif cdn == '3q':
            formats = self._extract_3q_formats(video, video_id)
        else:
            self.raise_no_formats(f'{cdn} formats are currently not supported', video_id)

        self._sort_formats(formats)

        subtitles = {}
        for sub in video.get('captiondata') or []:
            if sub.get('data'):
                subtitles.setdefault(sub.get('language', 'en'), []).append({
                    'ext': 'srt',
                    'data': '\n\n'.join(
                        f'{i + 1}\n{srt_subtitles_timecode(line["fromms"] / 1000)} --> {srt_subtitles_timecode(line["toms"] / 1000)}\n{line["caption"]}'
                        for i, line in enumerate(sub['data'])),
                    'name': sub.get('language_long') or sub.get('title')
                })
            elif sub.get('url'):
                subtitles.setdefault(sub.get('language', 'en'), []).append({
                    'url': sub['url'],
                    'ext': sub.get('format'),
                    'name': sub.get('language_long') or sub.get('title')
                })

        return {
            'id': video_id,
            'title': title,
            'alt_title': general.get('subtitle'),
            'description': general.get('description'),
            'release_year': int_or_none(general.get('year')),
            'creator': general.get('studio') or general.get('studio_adref') or None,
            'thumbnail': try_get(
                video, lambda x: x['imagedata']['thumb'], compat_str),
            'duration': parse_duration(general.get('runtime')),
            'timestamp': int_or_none(general.get('uploaded')),
            'episode_number': traverse_obj(
                video, (('episodedata', 'general'), 'episode'), expected_type=int, get_all=False),
            'season_number': traverse_obj(
                video, (('episodedata', 'general'), 'season'), expected_type=int, get_all=False),
            'cast': traverse_obj(video, ('connectedmedia', ..., 'title'), expected_type=str),
            'formats': formats,
            'subtitles': subtitles,
        }

class NexxEmbedIE(InfoExtractor):
    _VALID_URL = r'https?://embed\.nexx(?:\.cloud|cdn\.com)/\d+/(?:video/)?(?P<id>[^/?#&]+)'
    # Reference. https://nx-s.akamaized.net/files/201510/44.pdf
    _EMBED_REGEX = [r'<iframe[^>]+\bsrc=(["\'])(?P<url>(?:https?:)?//embed\.nexx(?:\.cloud|cdn\.com)/\d+/(?:(?!\1).)+)\1']
    _TESTS = [{
        'url': 'http://embed.nexx.cloud/748/KC1614647Z27Y7T?autoplay=1',
        'md5': '16746bfc28c42049492385c989b26c4a',
        'info_dict': {
            'id': '161464',
            'ext': 'mp4',
            'title': 'Nervenkitzel Achterbahn',
            'alt_title': 'Karussellbauer in Deutschland',
            'description': 'md5:ffe7b1cc59a01f585e0569949aef73cc',
            'creator': 'SPIEGEL TV',
            'thumbnail': r're:^https?://.*\.jpg$',
            'duration': 2761,
            'timestamp': 1394021479,
            'upload_date': '20140305',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://embed.nexx.cloud/11888/video/DSRTO7UVOX06S7',
        'only_matching': True,
    }]

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

        webpage = self._download_webpage(url, embed_id)

        return self.url_result(NexxIE._extract_url(webpage), ie=NexxIE.ie_key())


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

class NFBIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?nfb\.ca/film/(?P<id>[^/?#&]+)'
    _TESTS = [{
        'url': 'https://www.nfb.ca/film/trafficopter/',
        'info_dict': {
            'id': 'trafficopter',
            'ext': 'mp4',
            'title': 'Trafficopter',
            'description': 'md5:060228455eb85cf88785c41656776bc0',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Barrie Howells',
            'release_year': 1972,
        },
    }]

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

        webpage = self._download_webpage('https://www.nfb.ca/film/%s/' % video_id, video_id)

        iframe = self._html_search_regex(
            r'<[^>]+\bid=["\']player-iframe["\'][^>]*src=["\']([^"\']+)',
            webpage, 'iframe', default=None, fatal=True)
        if iframe.startswith('/'):
            iframe = f'https://www.nfb.ca{iframe}'

        player = self._download_webpage(iframe, video_id)

        source = self._html_search_regex(
            r'source:\s*\'([^\']+)',
            player, 'source', default=None, fatal=True)

        formats, subtitles = self._extract_m3u8_formats_and_subtitles(source, video_id, ext='mp4')
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': self._html_search_regex(
                r'<[^>]+\bid=["\']titleHeader["\'][^>]*>\s*<h1[^>]*>\s*([^<]+?)\s*</h1>',
                webpage, 'title', default=None),
            'description': self._html_search_regex(
                r'<[^>]+\bid=["\']tabSynopsis["\'][^>]*>\s*<p[^>]*>\s*([^<]+)',
                webpage, 'description', default=None),
            'thumbnail': self._html_search_regex(
                r'poster:\s*\'([^\']+)',
                player, 'thumbnail', default=None),
            'uploader': self._html_search_regex(
                r'<[^>]+\bitemprop=["\']name["\'][^>]*>([^<]+)',
                webpage, 'uploader', default=None),
            'release_year': int_or_none(self._html_search_regex(
                r'<[^>]+\bitemprop=["\']datePublished["\'][^>]*>([^<]+)',
                webpage, 'release_year', default=None)),
            'formats': formats,
            'subtitles': subtitles,
        }


code:
from .common import InfoExtractor

from ..utils import (
    try_get,
    unified_strdate,
    unified_timestamp
)

class NFHSNetworkIE(InfoExtractor):
    IE_NAME = 'NFHSNetwork'
    _VALID_URL = r'https?://(?:www\.)?nfhsnetwork\.com/events/[\w-]+/(?P<id>(?:gam|evt|dd|)?[\w\d]{0,10})'
    _TESTS = [{
        # Auto-generated two-team sport (pixellot)
        'url': 'https://www.nfhsnetwork.com/events/rockford-high-school-rockford-mi/gamcf7e54cfbc',
        'info_dict': {
            'id': 'gamcf7e54cfbc',
            'ext': 'mp4',
            'title': 'Rockford vs Spring Lake - Girls Varsity Lacrosse 03/27/2021',
            'uploader': 'MHSAA - Michigan: Rockford High School, Rockford, MI',
            'uploader_id': 'cd2622cf76',
            'uploader_url': 'https://www.nfhsnetwork.com/schools/rockford-high-school-rockford-mi',
            'location': 'Rockford, Michigan',
            'timestamp': 1616859000,
            'upload_date': '20210327'
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }, {
        # Non-sport activity with description
        'url': 'https://www.nfhsnetwork.com/events/limon-high-school-limon-co/evt4a30e3726c',
        'info_dict': {
            'id': 'evt4a30e3726c',
            'ext': 'mp4',
            'title': 'Drama Performance Limon High School vs. Limon High School - 12/13/2020',
            'description': 'Join the broadcast of the Limon High School Musical Performance at 2 PM.',
            'uploader': 'CHSAA: Limon High School, Limon, CO',
            'uploader_id': '7d2d121332',
            'uploader_url': 'https://www.nfhsnetwork.com/schools/limon-high-school-limon-co',
            'location': 'Limon, Colorado',
            'timestamp': 1607893200,
            'upload_date': '20201213'
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }, {
        # Postseason game
        'url': 'https://www.nfhsnetwork.com/events/nfhs-network-special-events/dd8de71d45',
        'info_dict': {
            'id': 'dd8de71d45',
            'ext': 'mp4',
            'title': '2015 UA Holiday Classic Tournament: National Division  - 12/26/2015',
            'uploader': 'SoCal Sports Productions',
            'uploader_id': '063dba0150',
            'uploader_url': 'https://www.nfhsnetwork.com/affiliates/socal-sports-productions',
            'location': 'San Diego, California',
            'timestamp': 1451187000,
            'upload_date': '20151226'
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }, {
        # Video with no broadcasts object
        'url': 'https://www.nfhsnetwork.com/events/wiaa-wi/9aa2f92f82',
        'info_dict': {
            'id': '9aa2f92f82',
            'ext': 'mp4',
            'title': 'Competitive Equity  - 01/21/2015',
            'description': 'Committee members discuss points of their research regarding a competitive equity plan',
            'uploader': 'WIAA - Wisconsin: Wisconsin Interscholastic Athletic Association',
            'uploader_id': 'a49f7d1002',
            'uploader_url': 'https://www.nfhsnetwork.com/associations/wiaa-wi',
            'location': 'Stevens Point, Wisconsin',
            'timestamp': 1421856000,
            'upload_date': '20150121'
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        }
    }
    ]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        data = self._download_json(
            'https://cfunity.nfhsnetwork.com/v2/game_or_event/' + video_id,
            video_id)
        publisher = data.get('publishers')[0]  # always exists
        broadcast = (publisher.get('broadcasts') or publisher.get('vods'))[0]  # some (older) videos don't have a broadcasts object
        uploader = publisher.get('formatted_name') or publisher.get('name')
        uploaderID = publisher.get('publisher_key')
        pubType = publisher.get('type')
        uploaderPrefix = (
            "schools" if pubType == "school"
            else "associations" if "association" in pubType
            else "affiliates" if (pubType == "publisher" or pubType == "affiliate")
            else "schools")
        uploaderPage = 'https://www.nfhsnetwork.com/%s/%s' % (uploaderPrefix, publisher.get('slug'))
        location = '%s, %s' % (data.get('city'), data.get('state_name'))
        description = broadcast.get('description')
        isLive = broadcast.get('on_air') or broadcast.get('status') == 'on_air' or False

        timestamp = unified_timestamp(data.get('local_start_time'))
        upload_date = unified_strdate(data.get('local_start_time'))

        title = (
            self._og_search_title(webpage)
            or self._html_search_regex(r'
(.*?)
', webpage, 'title'))
        title = title.split('|')[0].strip()

        video_type = 'broadcasts' if isLive else 'vods'
        key = broadcast.get('key') if isLive else try_get(publisher, lambda x: x['vods'][0]['key'])
        m3u8_url = self._download_json(
            'https://cfunity.nfhsnetwork.com/v2/%s/%s/url' % (video_type, key),
            video_id).get('video_url')

        formats = self._extract_m3u8_formats(m3u8_url, video_id, 'mp4', live=isLive)
        self._sort_formats(formats, ['res', 'tbr'])

        return {
            'id': video_id,
            'title': title,
            'formats': formats,
            'description': description,
            'timestamp': timestamp,
            'uploader': uploader,
            'uploader_id': uploaderID,
            'uploader_url': uploaderPage,
            'location': location,
            'upload_date': upload_date,
            'is_live': isLive
        }


code:
import re

from .common import InfoExtractor
from ..utils import (
    clean_html,
    determine_ext,
    get_element_by_class,
)

class NFLBaseIE(InfoExtractor):
    _VALID_URL_BASE = r'''(?x)
                    https?://
                        (?P<host>
                            (?:www\.)?
                            (?:
                                (?:
                                    nfl|
                                    buffalobills|
                                    miamidolphins|
                                    patriots|
                                    newyorkjets|
                                    baltimoreravens|
                                    bengals|
                                    clevelandbrowns|
                                    steelers|
                                    houstontexans|
                                    colts|
                                    jaguars|
                                    (?:titansonline|tennesseetitans)|
                                    denverbroncos|
                                    (?:kc)?chiefs|
                                    raiders|
                                    chargers|
                                    dallascowboys|
                                    giants|
                                    philadelphiaeagles|
                                    (?:redskins|washingtonfootball)|
                                    chicagobears|
                                    detroitlions|
                                    packers|
                                    vikings|
                                    atlantafalcons|
                                    panthers|
                                    neworleanssaints|
                                    buccaneers|
                                    azcardinals|
                                    (?:stlouis|the)rams|
                                    49ers|
                                    seahawks
                                )\.com|
                                .+?\.clubs\.nfl\.com
                            )
                        )/
                    '''
    _VIDEO_CONFIG_REGEX = r'<script[^>]+id="[^"]*video-config-[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}[^"]*"[^>]*>\s*({.+})'
    _WORKING = False

    def _parse_video_config(self, video_config, display_id):
        video_config = self._parse_json(video_config, display_id)
        item = video_config['playlist'][0]
        mcp_id = item.get('mcpID')
        if mcp_id:
            info = self.url_result(
                'anvato:GXvEgwyJeWem8KCYXfeoHWknwP48Mboj:' + mcp_id,
                'Anvato', mcp_id)
        else:
            media_id = item.get('id') or item['entityId']
            title = item['title']
            item_url = item['url']
            info = {'id': media_id}
            ext = determine_ext(item_url)
            if ext == 'm3u8':
                info['formats'] = self._extract_m3u8_formats(item_url, media_id, 'mp4')
                self._sort_formats(info['formats'])
            else:
                info['url'] = item_url
                if item.get('audio') is True:
                    info['vcodec'] = 'none'
            is_live = video_config.get('live') is True
            thumbnails = None
            image_url = item.get(item.get('imageSrc')) or item.get(item.get('posterImage'))
            if image_url:
                thumbnails = [{
                    'url': image_url,
                    'ext': determine_ext(image_url, 'jpg'),
                }]
            info.update({
                'title': title,
                'is_live': is_live,
                'description': clean_html(item.get('description')),
                'thumbnails': thumbnails,
            })
        return info

class NFLIE(NFLBaseIE):
    IE_NAME = 'nfl.com'
    _VALID_URL = NFLBaseIE._VALID_URL_BASE + r'(?:videos?|listen|audio)/(?P<id>[^/#?&]+)'
    _TESTS = [{
        'url': 'https://www.nfl.com/videos/baker-mayfield-s-game-changing-plays-from-3-td-game-week-14',
        'info_dict': {
            'id': '899441',
            'ext': 'mp4',
            'title': "Baker Mayfield's game-changing plays from 3-TD game Week 14",
            'description': 'md5:85e05a3cc163f8c344340f220521136d',
            'upload_date': '20201215',
            'timestamp': 1608009755,
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'NFL',
        }
    }, {
        'url': 'https://www.chiefs.com/listen/patrick-mahomes-travis-kelce-react-to-win-over-dolphins-the-breakdown',
        'md5': '6886b32c24b463038c760ceb55a34566',
        'info_dict': {
            'id': 'd87e8790-3e14-11eb-8ceb-ff05c2867f99',
            'ext': 'mp3',
            'title': 'Patrick Mahomes, Travis Kelce React to Win Over Dolphins | The Breakdown',
            'description': 'md5:12ada8ee70e6762658c30e223e095075',
        }
    }, {
        'url': 'https://www.buffalobills.com/video/buffalo-bills-military-recognition-week-14',
        'only_matching': True,
    }, {
        'url': 'https://www.raiders.com/audio/instant-reactions-raiders-week-14-loss-to-indianapolis-colts-espn-jason-fitz',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        return self._parse_video_config(self._search_regex(
            self._VIDEO_CONFIG_REGEX, webpage, 'video config'), display_id)

class NFLArticleIE(NFLBaseIE):
    IE_NAME = 'nfl.com:article'
    _VALID_URL = NFLBaseIE._VALID_URL_BASE + r'news/(?P<id>[^/#?&]+)'
    _TEST = {
        'url': 'https://www.buffalobills.com/news/the-only-thing-we-ve-earned-is-the-noise-bills-coaches-discuss-handling-rising-e',
        'info_dict': {
            'id': 'the-only-thing-we-ve-earned-is-the-noise-bills-coaches-discuss-handling-rising-e',
            'title': "'The only thing we've earned is the noise' | Bills coaches discuss handling rising expectations",
        },
        'playlist_count': 4,
    }

    def _real_extract(self, url):
        display_id = self._match_id(url)
        webpage = self._download_webpage(url, display_id)
        entries = []
        for video_config in re.findall(self._VIDEO_CONFIG_REGEX, webpage):
            entries.append(self._parse_video_config(video_config, display_id))
        title = clean_html(get_element_by_class(
            'nfl-c-article__title', webpage)) or self._html_search_meta(
            ['og:title', 'twitter:title'], webpage)
        return self.playlist_result(entries, display_id, title)