code:
import base64

from .common import InfoExtractor
from ..compat import (
    compat_urllib_parse_urlencode,
    compat_str,
)
from ..utils import (
    format_field,
    int_or_none,
    parse_iso8601,
    smuggle_url,
    unsmuggle_url,
    urlencode_postdata,
)

class AWAANIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?show/(?P<show_id>\d+)/[^/]+(?:/(?P<id>\d+)/(?P<season_id>\d+))?'

    def _real_extract(self, url):
        show_id, video_id, season_id = self._match_valid_url(url).groups()
        if video_id and int(video_id) > 0:
            return self.url_result(
                'http://awaan.ae/media/%s' % video_id, 'AWAANVideo')
        elif season_id and int(season_id) > 0:
            return self.url_result(smuggle_url(
                'http://awaan.ae/program/season/%s' % season_id,
                {'show_id': show_id}), 'AWAANSeason')
        else:
            return self.url_result(
                'http://awaan.ae/program/%s' % show_id, 'AWAANSeason')

class AWAANBaseIE(InfoExtractor):
    def _parse_video_data(self, video_data, video_id, is_live):
        title = video_data.get('title_en') or video_data['title_ar']
        img = video_data.get('img')

        return {
            'id': video_id,
            'title': title,
            'description': video_data.get('description_en') or video_data.get('description_ar'),
            'thumbnail': format_field(img, None, 'http://admin.mangomolo.com/analytics/%s'),
            'duration': int_or_none(video_data.get('duration')),
            'timestamp': parse_iso8601(video_data.get('create_time'), ' '),
            'is_live': is_live,
            'uploader_id': video_data.get('user_id'),
        }

class AWAANVideoIE(AWAANBaseIE):
    IE_NAME = 'awaan:video'
    _VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?(?:video(?:/[^/]+)?|media|catchup/[^/]+/[^/]+)/(?P<id>\d+)'
    _TESTS = [{
        'url': 'http://www.dcndigital.ae/#/video/%D8%B1%D8%AD%D9%84%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D8%B1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/17375',
        'md5': '5f61c33bfc7794315c671a62d43116aa',
        'info_dict':
        {
            'id': '17375',
            'ext': 'mp4',
            'title': 'رحلة العمر : الحلقة 1',
            'description': 'md5:0156e935d870acb8ef0a66d24070c6d6',
            'duration': 2041,
            'timestamp': 1227504126,
            'upload_date': '20081124',
            'uploader_id': '71',
        },
    }, {
        'url': 'http://awaan.ae/video/26723981/%D8%AF%D8%A7%D8%B1-%D8%A7%D9%84%D8%B3%D9%84%D8%A7%D9%85:-%D8%AE%D9%8A%D8%B1-%D8%AF%D9%88%D8%B1-%D8%A7%D9%84%D8%A3%D9%86%D8%B5%D8%A7%D8%B1',
        'only_matching': True,
    }]

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

        video_data = self._download_json(
            'http://admin.mangomolo.com/analytics/index.php/plus/video?id=%s' % video_id,
            video_id, headers={'Origin': 'http://awaan.ae'})
        info = self._parse_video_data(video_data, video_id, False)

        embed_url = 'http://admin.mangomolo.com/analytics/index.php/customers/embed/video?' + compat_urllib_parse_urlencode({
            'id': video_data['id'],
            'user_id': video_data['user_id'],
            'signature': video_data['signature'],
            'countries': 'Q0M=',
            'filter': 'DENY',
        })
        info.update({
            '_type': 'url_transparent',
            'url': embed_url,
            'ie_key': 'MangomoloVideo',
        })
        return info

class AWAANLiveIE(AWAANBaseIE):
    IE_NAME = 'awaan:live'
    _VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?live/(?P<id>\d+)'
    _TEST = {
        'url': 'http://awaan.ae/live/6/dubai-tv',
        'info_dict': {
            'id': '6',
            'ext': 'mp4',
            'title': 're:Dubai Al Oula [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
            'upload_date': '20150107',
            'timestamp': 1420588800,
            'uploader_id': '71',
        },
        'params': {
            # m3u8 download
            'skip_download': True,
        },
    }

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

        channel_data = self._download_json(
            'http://admin.mangomolo.com/analytics/index.php/plus/getchanneldetails?channel_id=%s' % channel_id,
            channel_id, headers={'Origin': 'http://awaan.ae'})
        info = self._parse_video_data(channel_data, channel_id, True)

        embed_url = 'http://admin.mangomolo.com/analytics/index.php/customers/embed/index?' + compat_urllib_parse_urlencode({
            'id': base64.b64encode(channel_data['user_id'].encode()).decode(),
            'channelid': base64.b64encode(channel_data['id'].encode()).decode(),
            'signature': channel_data['signature'],
            'countries': 'Q0M=',
            'filter': 'DENY',
        })
        info.update({
            '_type': 'url_transparent',
            'url': embed_url,
            'ie_key': 'MangomoloLive',
        })
        return info

class AWAANSeasonIE(InfoExtractor):
    IE_NAME = 'awaan:season'
    _VALID_URL = r'https?://(?:www\.)?(?:awaan|dcndigital)\.ae/(?:#/)?program/(?:(?P<show_id>\d+)|season/(?P<season_id>\d+))'
    _TEST = {
        'url': 'http://dcndigital.ae/#/program/205024/%D9%85%D8%AD%D8%A7%D8%B6%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D9%8A%D8%AE-%D8%A7%D9%84%D8%B4%D8%B9%D8%B1%D8%A7%D9%88%D9%8A',
        'info_dict':
        {
            'id': '7910',
            'title': 'محاضرات الشيخ الشعراوي',
        },
        'playlist_mincount': 27,
    }

    def _real_extract(self, url):
        url, smuggled_data = unsmuggle_url(url, {})
        show_id, season_id = self._match_valid_url(url).groups()

        data = {}
        if season_id:
            data['season'] = season_id
            show_id = smuggled_data.get('show_id')
            if show_id is None:
                season = self._download_json(
                    'http://admin.mangomolo.com/analytics/index.php/plus/season_info?id=%s' % season_id,
                    season_id, headers={'Origin': 'http://awaan.ae'})
                show_id = season['id']
        data['show_id'] = show_id
        show = self._download_json(
            'http://admin.mangomolo.com/analytics/index.php/plus/show',
            show_id, data=urlencode_postdata(data), headers={
                'Origin': 'http://awaan.ae',
                'Content-Type': 'application/x-www-form-urlencoded'
            })
        if not season_id:
            season_id = show['default_season']
        for season in show['seasons']:
            if season['id'] == season_id:
                title = season.get('title_en') or season['title_ar']

                entries = []
                for video in show['videos']:
                    video_id = compat_str(video['id'])
                    entries.append(self.url_result(
                        'http://awaan.ae/media/%s' % video_id, 'AWAANVideo', video_id))

                return self.playlist_result(entries, season_id, title)


code:
import datetime
import hashlib
import hmac

from .common import InfoExtractor
from ..compat import compat_urllib_parse_urlencode

class AWSIE(InfoExtractor):
    _AWS_ALGORITHM = 'AWS4-HMAC-SHA256'
    _AWS_REGION = 'us-east-1'

    def _aws_execute_api(self, aws_dict, video_id, query=None):
        query = query or {}
        amz_date = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
        date = amz_date[:8]
        headers = {
            'Accept': 'application/json',
            'Host': self._AWS_PROXY_HOST,
            'X-Amz-Date': amz_date,
            'X-Api-Key': self._AWS_API_KEY
        }
        session_token = aws_dict.get('session_token')
        if session_token:
            headers['X-Amz-Security-Token'] = session_token

        def aws_hash(s):
            return hashlib.sha256(s.encode('utf-8')).hexdigest()

        # Task 1: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
        canonical_querystring = compat_urllib_parse_urlencode(query)
        canonical_headers = ''
        for header_name, header_value in sorted(headers.items()):
            canonical_headers += '%s:%s\n' % (header_name.lower(), header_value)
        signed_headers = ';'.join([header.lower() for header in sorted(headers.keys())])
        canonical_request = '\n'.join([
            'GET',
            aws_dict['uri'],
            canonical_querystring,
            canonical_headers,
            signed_headers,
            aws_hash('')
        ])

        # Task 2: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
        credential_scope_list = [date, self._AWS_REGION, 'execute-api', 'aws4_request']
        credential_scope = '/'.join(credential_scope_list)
        string_to_sign = '\n'.join([self._AWS_ALGORITHM, amz_date, credential_scope, aws_hash(canonical_request)])

        # Task 3: http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
        def aws_hmac(key, msg):
            return hmac.new(key, msg.encode('utf-8'), hashlib.sha256)

        def aws_hmac_digest(key, msg):
            return aws_hmac(key, msg).digest()

        def aws_hmac_hexdigest(key, msg):
            return aws_hmac(key, msg).hexdigest()

        k_signing = ('AWS4' + aws_dict['secret_key']).encode('utf-8')
        for value in credential_scope_list:
            k_signing = aws_hmac_digest(k_signing, value)

        signature = aws_hmac_hexdigest(k_signing, string_to_sign)

        # Task 4: http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
        headers['Authorization'] = ', '.join([
            '%s Credential=%s/%s' % (self._AWS_ALGORITHM, aws_dict['access_key'], credential_scope),
            'SignedHeaders=%s' % signed_headers,
            'Signature=%s' % signature,
        ])

        return self._download_json(
            'https://%s%s%s' % (self._AWS_PROXY_HOST, aws_dict['uri'], '?' + canonical_querystring if canonical_querystring else ''),
            video_id, headers=headers)

repo_name: Lesmiscore/ytdl


code:
import json

from .common import InfoExtractor
from .kaltura import KalturaIE

class AZMedienIE(InfoExtractor):
    IE_DESC = 'AZ Medien videos'
    _VALID_URL = r'''(?x)
                    https?://
                        (?:www\.|tv\.)?
                        (?P<host>
                            telezueri\.ch|
                            telebaern\.tv|
                            telem1\.ch|
                            tvo-online\.ch
                        )/
                        [^/]+/
                        (?P<id>
                            [^/]+-(?P<article_id>\d+)
                        )
                        (?:
                            \#video=
                            (?P<kaltura_id>
                                [_0-9a-z]+
                            )
                        )?
                    '''

    _TESTS = [{
        'url': 'https://tv.telezueri.ch/sonntalk/bundesrats-vakanzen-eu-rahmenabkommen-133214569',
        'info_dict': {
            'id': '1_anruz3wy',
            'ext': 'mp4',
            'title': 'Bundesrats-Vakanzen / EU-Rahmenabkommen',
            'uploader_id': 'TVOnline',
            'upload_date': '20180930',
            'timestamp': 1538328802,
            'view_count': int,
            'thumbnail': 'http://cfvod.kaltura.com/p/1719221/sp/171922100/thumbnail/entry_id/1_anruz3wy/version/100031',
            'duration': 1930
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.telebaern.tv/telebaern-news/montag-1-oktober-2018-ganze-sendung-133531189#video=0_7xjo9lf1',
        'only_matching': True
    }]
    _API_TEMPL = 'https://www.%s/api/pub/gql/%s/NewsArticleTeaser/a4016f65fe62b81dc6664dd9f4910e4ab40383be'
    _PARTNER_ID = '1719221'

    def _real_extract(self, url):
        host, display_id, article_id, entry_id = self._match_valid_url(url).groups()

        if not entry_id:
            entry_id = self._download_json(
                self._API_TEMPL % (host, host.split('.')[0]), display_id, query={
                    'variables': json.dumps({
                        'contextId': 'NewsArticle:' + article_id,
                    }),
                })['data']['context']['mainAsset']['video']['kaltura']['kalturaId']

        return self.url_result(
            'kaltura:%s:%s' % (self._PARTNER_ID, entry_id),
            ie=KalturaIE.ie_key(), video_id=entry_id)


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

class BaiduVideoIE(InfoExtractor):
    IE_DESC = '百度视频'
    _VALID_URL = r'https?://v\.baidu\.com/(?P<type>[a-z]+)/(?P<id>\d+)\.htm'
    _TESTS = [{
        'url': 'http://v.baidu.com/comic/1069.htm?frp=bdbrand&q=%E4%B8%AD%E5%8D%8E%E5%B0%8F%E5%BD%93%E5%AE%B6',
        'info_dict': {
            'id': '1069',
            'title': '中华小当家 TV版国语',
            'description': 'md5:51be07afe461cf99fa61231421b5397c',
        },
        'playlist_count': 52,
    }, {
        'url': 'http://v.baidu.com/show/11595.htm?frp=bdbrand',
        'info_dict': {
            'id': '11595',
            'title': 're:^奔跑吧兄弟',
            'description': 'md5:1bf88bad6d850930f542d51547c089b8',
        },
        'playlist_mincount': 12,
    }]

    def _call_api(self, path, category, playlist_id, note):
        return self._download_json('http://app.video.baidu.com/%s/?worktype=adnative%s&id=%s' % (
            path, category, playlist_id), playlist_id, note)

    def _real_extract(self, url):
        category, playlist_id = self._match_valid_url(url).groups()
        if category == 'show':
            category = 'tvshow'
        if category == 'tv':
            category = 'tvplay'

        playlist_detail = self._call_api(
            'xqinfo', category, playlist_id, 'Download playlist JSON metadata')

        playlist_title = playlist_detail['title']
        playlist_description = unescapeHTML(playlist_detail.get('intro'))

        episodes_detail = self._call_api(
            'xqsingle', category, playlist_id, 'Download episodes JSON metadata')

        entries = [self.url_result(
            episode['url'], video_title=episode['title']
        ) for episode in episodes_detail['videos']]

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


code:
import math

from .common import InfoExtractor
from ..compat import (
    compat_urllib_parse_urlparse,
    compat_parse_qs,
)
from ..utils import (
    format_field,
    InAdvancePagedList,
    traverse_obj,
    unified_timestamp,
)

class BanByeBaseIE(InfoExtractor):
    _API_BASE = 'https://api.banbye.com'
    _CDN_BASE = 'https://cdn.banbye.com'
    _VIDEO_BASE = 'https://banbye.com/watch'

    @staticmethod
    def _extract_playlist_id(url, param='playlist'):
        return compat_parse_qs(
            compat_urllib_parse_urlparse(url).query).get(param, [None])[0]

    def _extract_playlist(self, playlist_id):
        data = self._download_json(f'{self._API_BASE}/playlists/{playlist_id}', playlist_id)
        return self.playlist_result([
            self.url_result(f'{self._VIDEO_BASE}/{video_id}', BanByeIE)
            for video_id in data['videoIds']], playlist_id, data.get('name'))

class BanByeIE(BanByeBaseIE):
    _VALID_URL = r'https?://(?:www\.)?banbye.com/(?:en/)?watch/(?P<id>\w+)'
    _TESTS = [{
        'url': 'https://banbye.com/watch/v_ytfmvkVYLE8T',
        'md5': '2f4ea15c5ca259a73d909b2cfd558eb5',
        'info_dict': {
            'id': 'v_ytfmvkVYLE8T',
            'ext': 'mp4',
            'title': 'md5:5ec098f88a0d796f987648de6322ba0f',
            'description': 'md5:4d94836e73396bc18ef1fa0f43e5a63a',
            'uploader': 'wRealu24',
            'channel_id': 'ch_wrealu24',
            'channel_url': 'https://banbye.com/channel/ch_wrealu24',
            'timestamp': 1647604800,
            'upload_date': '20220318',
            'duration': 1931,
            'thumbnail': r're:https?://.*\.webp',
            'tags': 'count:5',
            'like_count': int,
            'dislike_count': int,
            'view_count': int,
            'comment_count': int,
        },
    }, {
        'url': 'https://banbye.com/watch/v_2JjQtqjKUE_F?playlistId=p_Ld82N6gBw_OJ',
        'info_dict': {
            'title': 'Krzysztof Karoń',
            'id': 'p_Ld82N6gBw_OJ',
        },
        'playlist_count': 9,
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        playlist_id = self._extract_playlist_id(url, 'playlistId')

        if self._yes_playlist(playlist_id, video_id):
            return self._extract_playlist(playlist_id)

        data = self._download_json(f'{self._API_BASE}/videos/{video_id}', video_id)
        thumbnails = [{
            'id': f'{quality}p',
            'url': f'{self._CDN_BASE}/video/{video_id}/{quality}.webp',
        } for quality in [48, 96, 144, 240, 512, 1080]]
        formats = [{
            'format_id': f'http-{quality}p',
            'quality': quality,
            'url': f'{self._CDN_BASE}/video/{video_id}/{quality}.mp4',
        } for quality in data['quality']]

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': data.get('title'),
            'description': data.get('desc'),
            'uploader': traverse_obj(data, ('channel', 'name')),
            'channel_id': data.get('channelId'),
            'channel_url': format_field(data, 'channelId', 'https://banbye.com/channel/%s'),
            'timestamp': unified_timestamp(data.get('publishedAt')),
            'duration': data.get('duration'),
            'tags': data.get('tags'),
            'formats': formats,
            'thumbnails': thumbnails,
            'like_count': data.get('likes'),
            'dislike_count': data.get('dislikes'),
            'view_count': data.get('views'),
            'comment_count': data.get('commentCount'),
        }

class BanByeChannelIE(BanByeBaseIE):
    _VALID_URL = r'https?://(?:www\.)?banbye.com/(?:en/)?channel/(?P<id>\w+)'
    _TESTS = [{
        'url': 'https://banbye.com/channel/ch_wrealu24',
        'info_dict': {
            'title': 'wRealu24',
            'id': 'ch_wrealu24',
            'description': 'md5:da54e48416b74dfdde20a04867c0c2f6',
        },
        'playlist_mincount': 791,
    }, {
        'url': 'https://banbye.com/channel/ch_wrealu24?playlist=p_Ld82N6gBw_OJ',
        'info_dict': {
            'title': 'Krzysztof Karoń',
            'id': 'p_Ld82N6gBw_OJ',
        },
        'playlist_count': 9,
    }]
    _PAGE_SIZE = 100

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

        if playlist_id:
            return self._extract_playlist(playlist_id)

        def page_func(page_num):
            data = self._download_json(f'{self._API_BASE}/videos', channel_id, query={
                'channelId': channel_id,
                'sort': 'new',
                'limit': self._PAGE_SIZE,
                'offset': page_num * self._PAGE_SIZE,
            }, note=f'Downloading page {page_num+1}')
            return [
                self.url_result(f"{self._VIDEO_BASE}/{video['_id']}", BanByeIE)
                for video in data['items']
            ]

        channel_data = self._download_json(f'{self._API_BASE}/channels/{channel_id}', channel_id)
        entries = InAdvancePagedList(
            page_func,
            math.ceil(channel_data['videoCount'] / self._PAGE_SIZE),
            self._PAGE_SIZE)

        return self.playlist_result(
            entries, channel_id, channel_data.get('name'), channel_data.get('description'))


code:
from .brightcove import BrightcoveNewIE
from ..utils import extract_attributes

class BandaiChannelIE(BrightcoveNewIE):
    IE_NAME = 'bandaichannel'
    _VALID_URL = r'https?://(?:www\.)?b-ch\.com/titles/(?P<id>\d+/\d+)'
    _TESTS = [{
        'url': 'https://www.b-ch.com/titles/514/001',
        'md5': 'a0f2d787baa5729bed71108257f613a4',
        'info_dict': {
            'id': '6128044564001',
            'ext': 'mp4',
            'title': 'メタルファイターMIKU 第1話',
            'timestamp': 1580354056,
            'uploader_id': '5797077852001',
            'upload_date': '20200130',
            'duration': 1387.733,
        },
        'params': {
            'skip_download': True,
        },
    }]

    def _real_extract(self, url):
        video_id = self._match_id(url)
        webpage = self._download_webpage(url, video_id)
        attrs = extract_attributes(self._search_regex(
            r'(<video-js[^>]+\bid="bcplayer"[^>]*>)', webpage, 'player'))
        bc = self._download_json(
            'https://pbifcd.b-ch.com/v1/playbackinfo/ST/70/' + attrs['data-info'],
            video_id, headers={'X-API-KEY': attrs['data-auth'].strip()})['bc']
        return self._parse_brightcove_metadata(bc, bc['id'])


code:
import random
import re
import time

from .common import InfoExtractor
from ..compat import compat_str
from ..utils import (
    ExtractorError,
    float_or_none,
    int_or_none,
    KNOWN_EXTENSIONS,
    parse_filesize,
    str_or_none,
    try_get,
    update_url_query,
    unified_strdate,
    unified_timestamp,
    url_or_none,
    urljoin,
)

class BandcampIE(InfoExtractor):
    _VALID_URL = r'https?://(?P<uploader>[^/]+)\.bandcamp\.com/track/(?P<id>[^/?#&]+)'
    _EMBED_REGEX = [r'<meta property="og:url"[^>]*?content="(?P<url>.*?bandcamp\.com.*?)"']
    _TESTS = [{
        'url': 'http://youtube-dl.bandcamp.com/track/youtube-dl-test-song',
        'md5': 'c557841d5e50261777a6585648adf439',
        'info_dict': {
            'id': '1812978515',
            'ext': 'mp3',
            'title': "youtube-dl  \"'/\\ä↭ - youtube-dl  \"'/\\ä↭ - youtube-dl test song \"'/\\ä↭",
            'duration': 9.8485,
            'uploader': 'youtube-dl  "\'/\\ä↭',
            'upload_date': '20121129',
            'timestamp': 1354224127,
        },
        '_skip': 'There is a limit of 200 free downloads / month for the test song'
    }, {
        # free download
        'url': 'http://benprunty.bandcamp.com/track/lanius-battle',
        'info_dict': {
            'id': '2650410135',
            'ext': 'aiff',
            'title': 'Ben Prunty - Lanius (Battle)',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Ben Prunty',
            'timestamp': 1396508491,
            'upload_date': '20140403',
            'release_timestamp': 1396483200,
            'release_date': '20140403',
            'duration': 260.877,
            'track': 'Lanius (Battle)',
            'track_number': 1,
            'track_id': '2650410135',
            'artist': 'Ben Prunty',
            'album': 'FTL: Advanced Edition Soundtrack',
        },
    }, {
        # no free download, mp3 128
        'url': 'https://relapsealumni.bandcamp.com/track/hail-to-fire',
        'md5': 'fec12ff55e804bb7f7ebeb77a800c8b7',
        'info_dict': {
            'id': '2584466013',
            'ext': 'mp3',
            'title': 'Mastodon - Hail to Fire',
            'thumbnail': r're:^https?://.*\.jpg$',
            'uploader': 'Mastodon',
            'timestamp': 1322005399,
            'upload_date': '20111122',
            'release_timestamp': 1076112000,
            'release_date': '20040207',
            'duration': 120.79,
            'track': 'Hail to Fire',
            'track_number': 5,
            'track_id': '2584466013',
            'artist': 'Mastodon',
            'album': 'Call of the Mastodon',
        },
    }]

    def _extract_data_attr(self, webpage, video_id, attr='tralbum', fatal=True):
        return self._parse_json(self._html_search_regex(
            r'data-%s=(["\'])({.+?})\1' % attr, webpage,
            attr + ' data', group=2), video_id, fatal=fatal)

    def _real_extract(self, url):
        title, uploader = self._match_valid_url(url).group('id', 'uploader')
        webpage = self._download_webpage(url, title)
        tralbum = self._extract_data_attr(webpage, title)
        thumbnail = self._og_search_thumbnail(webpage)

        track_id = None
        track = None
        track_number = None
        duration = None

        formats = []
        track_info = try_get(tralbum, lambda x: x['trackinfo'][0], dict)
        if track_info:
            file_ = track_info.get('file')
            if isinstance(file_, dict):
                for format_id, format_url in file_.items():
                    if not url_or_none(format_url):
                        continue
                    ext, abr_str = format_id.split('-', 1)
                    formats.append({
                        'format_id': format_id,
                        'url': self._proto_relative_url(format_url, 'http:'),
                        'ext': ext,
                        'vcodec': 'none',
                        'acodec': ext,
                        'abr': int_or_none(abr_str),
                    })
            track = track_info.get('title')
            track_id = str_or_none(
                track_info.get('track_id') or track_info.get('id'))
            track_number = int_or_none(track_info.get('track_num'))
            duration = float_or_none(track_info.get('duration'))

        embed = self._extract_data_attr(webpage, title, 'embed', False)
        current = tralbum.get('current') or {}
        artist = embed.get('artist') or current.get('artist') or tralbum.get('artist')
        timestamp = unified_timestamp(
            current.get('publish_date') or tralbum.get('album_publish_date'))

        download_link = tralbum.get('freeDownloadPage')
        if download_link:
            track_id = compat_str(tralbum['id'])

            download_webpage = self._download_webpage(
                download_link, track_id, 'Downloading free downloads page')

            blob = self._extract_data_attr(download_webpage, track_id, 'blob')

            info = try_get(
                blob, (lambda x: x['digital_items'][0],
                       lambda x: x['download_items'][0]), dict)
            if info:
                downloads = info.get('downloads')
                if isinstance(downloads, dict):
                    if not track:
                        track = info.get('title')
                    if not artist:
                        artist = info.get('artist')
                    if not thumbnail:
                        thumbnail = info.get('thumb_url')

                    download_formats = {}
                    download_formats_list = blob.get('download_formats')
                    if isinstance(download_formats_list, list):
                        for f in blob['download_formats']:
                            name, ext = f.get('name'), f.get('file_extension')
                            if all(isinstance(x, compat_str) for x in (name, ext)):
                                download_formats[name] = ext.strip('.')

                    for format_id, f in downloads.items():
                        format_url = f.get('url')
                        if not format_url:
                            continue
                        # Stat URL generation algorithm is reverse engineered from
                        # download_*_bundle_*.js
                        stat_url = update_url_query(
                            format_url.replace('/download/', '/statdownload/'), {
                                '.rand': int(time.time() * 1000 * random.random()),
                            })
                        format_id = f.get('encoding_name') or format_id
                        stat = self._download_json(
                            stat_url, track_id, 'Downloading %s JSON' % format_id,
                            transform_source=lambda s: s[s.index('{'):s.rindex('}') + 1],
                            fatal=False)
                        if not stat:
                            continue
                        retry_url = url_or_none(stat.get('retry_url'))
                        if not retry_url:
                            continue
                        formats.append({
                            'url': self._proto_relative_url(retry_url, 'http:'),
                            'ext': download_formats.get(format_id),
                            'format_id': format_id,
                            'format_note': f.get('description'),
                            'filesize': parse_filesize(f.get('size_mb')),
                            'vcodec': 'none',
                            'acodec': format_id.split('-')[0],
                        })

        self._sort_formats(formats)

        title = '%s - %s' % (artist, track) if artist else track

        if not duration:
            duration = float_or_none(self._html_search_meta(
                'duration', webpage, default=None))

        return {
            'id': track_id,
            'title': title,
            'thumbnail': thumbnail,
            'uploader': artist,
            'uploader_id': uploader,
            'uploader_url': f'https://{uploader}.bandcamp.com',
            'timestamp': timestamp,
            'release_timestamp': unified_timestamp(tralbum.get('album_release_date')),
            'duration': duration,
            'track': track,
            'track_number': track_number,
            'track_id': track_id,
            'artist': artist,
            'album': embed.get('album_title'),
            'formats': formats,
        }

class BandcampAlbumIE(BandcampIE):
    IE_NAME = 'Bandcamp:album'
    _VALID_URL = r'https?://(?:(?P<subdomain>[^.]+)\.)?bandcamp\.com/album/(?P<id>[^/?#&]+)'

    _TESTS = [{
        'url': 'http://blazo.bandcamp.com/album/jazz-format-mixtape-vol-1',
        'playlist': [
            {
                'md5': '39bc1eded3476e927c724321ddf116cf',
                'info_dict': {
                    'id': '1353101989',
                    'ext': 'mp3',
                    'title': 'Blazo - Intro',
                    'timestamp': 1311756226,
                    'upload_date': '20110727',
                    'uploader': 'Blazo',
                }
            },
            {
                'md5': '1a2c32e2691474643e912cc6cd4bffaa',
                'info_dict': {
                    'id': '38097443',
                    'ext': 'mp3',
                    'title': 'Blazo - Kero One - Keep It Alive (Blazo remix)',
                    'timestamp': 1311757238,
                    'upload_date': '20110727',
                    'uploader': 'Blazo',
                }
            },
        ],
        'info_dict': {
            'title': 'Jazz Format Mixtape vol.1',
            'id': 'jazz-format-mixtape-vol-1',
            'uploader_id': 'blazo',
        },
        'params': {
            'playlistend': 2
        },
        'skip': 'Bandcamp imposes download limits.'
    }, {
        'url': 'http://nightbringer.bandcamp.com/album/hierophany-of-the-open-grave',
        'info_dict': {
            'title': 'Hierophany of the Open Grave',
            'uploader_id': 'nightbringer',
            'id': 'hierophany-of-the-open-grave',
        },
        'playlist_mincount': 9,
    }, {
        # with escaped quote in title
        'url': 'https://jstrecords.bandcamp.com/album/entropy-ep',
        'info_dict': {
            'title': '"Entropy" EP',
            'uploader_id': 'jstrecords',
            'id': 'entropy-ep',
            'description': 'md5:0ff22959c943622972596062f2f366a5',
        },
        'playlist_mincount': 3,
    }, {
        # not all tracks have songs
        'url': 'https://insulters.bandcamp.com/album/we-are-the-plague',
        'info_dict': {
            'id': 'we-are-the-plague',
            'title': 'WE ARE THE PLAGUE',
            'uploader_id': 'insulters',
            'description': 'md5:b3cf845ee41b2b1141dc7bde9237255f',
        },
        'playlist_count': 2,
    }]

    @classmethod
    def suitable(cls, url):
        return (False
                if BandcampWeeklyIE.suitable(url) or BandcampIE.suitable(url)
                else super(BandcampAlbumIE, cls).suitable(url))

    def _real_extract(self, url):
        uploader_id, album_id = self._match_valid_url(url).groups()
        playlist_id = album_id or uploader_id
        webpage = self._download_webpage(url, playlist_id)
        tralbum = self._extract_data_attr(webpage, playlist_id)
        track_info = tralbum.get('trackinfo')
        if not track_info:
            raise ExtractorError('The page doesn\'t contain any tracks')
        # Only tracks with duration info have songs
        entries = [
            self.url_result(
                urljoin(url, t['title_link']), BandcampIE.ie_key(),
                str_or_none(t.get('track_id') or t.get('id')), t.get('title'))
            for t in track_info
            if t.get('duration')]

        current = tralbum.get('current') or {}

        return {
            '_type': 'playlist',
            'uploader_id': uploader_id,
            'id': playlist_id,
            'title': current.get('title'),
            'description': current.get('about'),
            'entries': entries,
        }

class BandcampWeeklyIE(BandcampIE):
    IE_NAME = 'Bandcamp:weekly'
    _VALID_URL = r'https?://(?:www\.)?bandcamp\.com/?\?(?:.*?&)?show=(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://bandcamp.com/?show=224',
        'md5': 'b00df799c733cf7e0c567ed187dea0fd',
        'info_dict': {
            'id': '224',
            'ext': 'opus',
            'title': 'BC Weekly April 4th 2017 - Magic Moments',
            'description': 'md5:5d48150916e8e02d030623a48512c874',
            'duration': 5829.77,
            'release_date': '20170404',
            'series': 'Bandcamp Weekly',
            'episode': 'Magic Moments',
            'episode_id': '224',
        },
        'params': {
            'format': 'opus-lo',
        },
    }, {
        'url': 'https://bandcamp.com/?blah/blah@&show=228',
        'only_matching': True
    }]

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

        blob = self._extract_data_attr(webpage, show_id, 'blob')

        show = blob['bcw_data'][show_id]

        formats = []
        for format_id, format_url in show['audio_stream'].items():
            if not url_or_none(format_url):
                continue
            for known_ext in KNOWN_EXTENSIONS:
                if known_ext in format_id:
                    ext = known_ext
                    break
            else:
                ext = None
            formats.append({
                'format_id': format_id,
                'url': format_url,
                'ext': ext,
                'vcodec': 'none',
            })
        self._sort_formats(formats)

        title = show.get('audio_title') or 'Bandcamp Weekly'
        subtitle = show.get('subtitle')
        if subtitle:
            title += ' - %s' % subtitle

        return {
            'id': show_id,
            'title': title,
            'description': show.get('desc') or show.get('short_desc'),
            'duration': float_or_none(show.get('audio_duration')),
            'is_live': False,
            'release_date': unified_strdate(show.get('published_date')),
            'series': 'Bandcamp Weekly',
            'episode': show.get('subtitle'),
            'episode_id': show_id,
            'formats': formats
        }

class BandcampUserIE(InfoExtractor):
    IE_NAME = 'Bandcamp:user'
    _VALID_URL = r'https?://(?!www\.)(?P<id>[^.]+)\.bandcamp\.com(?:/music)?/?(?:[#?]|$)'

    _TESTS = [{
        # Type 1 Bandcamp user page.
        'url': 'https://adrianvonziegler.bandcamp.com',
        'info_dict': {
            'id': 'adrianvonziegler',
            'title': 'Discography of adrianvonziegler',
        },
        'playlist_mincount': 23,
    }, {
        # Bandcamp user page with only one album
        'url': 'http://dotscale.bandcamp.com',
        'info_dict': {
            'id': 'dotscale',
            'title': 'Discography of dotscale'
        },
        'playlist_count': 1,
    }, {
        # Type 2 Bandcamp user page.
        'url': 'https://nightcallofficial.bandcamp.com',
        'info_dict': {
            'id': 'nightcallofficial',
            'title': 'Discography of nightcallofficial',
        },
        'playlist_count': 4,
    }, {
        'url': 'https://steviasphere.bandcamp.com/music',
        'playlist_mincount': 47,
        'info_dict': {
            'id': 'steviasphere',
            'title': 'Discography of steviasphere',
        },
    }, {
        'url': 'https://coldworldofficial.bandcamp.com/music',
        'playlist_mincount': 10,
        'info_dict': {
            'id': 'coldworldofficial',
            'title': 'Discography of coldworldofficial',
        },
    }, {
        'url': 'https://nuclearwarnowproductions.bandcamp.com/music',
        'playlist_mincount': 399,
        'info_dict': {
            'id': 'nuclearwarnowproductions',
            'title': 'Discography of nuclearwarnowproductions',
        },
    }]

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

        discography_data = (re.findall(r'
]+>\s*<a href=["\'](?![^"\'/]*?/merch)([^"\']+)', webpage)
                            or re.findall(r'
]+trackTitle["\'][^"\']+["\']([^"\']+)', webpage))

        return self.playlist_from_matches(
            discography_data, uploader, f'Discography of {uploader}', getter=lambda x: urljoin(url, x))


code:
import json

from .common import InfoExtractor
from ..utils import (
    try_get,
    int_or_none,
    url_or_none,
    float_or_none,
    unified_timestamp,
)

class BannedVideoIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?banned\.video/watch\?id=(?P<id>[0-f]{24})'
    _TESTS = [{
        'url': 'https://banned.video/watch?id=5e7a859644e02200c6ef5f11',
        'md5': '14b6e81d41beaaee2215cd75c6ed56e4',
        'info_dict': {
            'id': '5e7a859644e02200c6ef5f11',
            'ext': 'mp4',
            'title': 'China Discovers Origin of Corona Virus: Issues Emergency Statement',
            'thumbnail': r're:^https?://(?:www\.)?assets\.infowarsmedia.com/images/',
            'description': 'md5:560d96f02abbebe6c6b78b47465f6b28',
            'upload_date': '20200324',
            'timestamp': 1585087895,
        }
    }]

    _GRAPHQL_GETMETADATA_QUERY = '''
query GetVideoAndComments($id: String!) {
    getVideo(id: $id) {
        streamUrl
        directUrl
        unlisted
        live
        tags {
            name
        }
        title
        summary
        playCount
        largeImage
        videoDuration
        channel {
            _id
            title
        }
        createdAt
    }
    getVideoComments(id: $id, limit: 999999, offset: 0) {
        _id
        content
        user {
            _id
            username
        }
        voteCount {
            positive
        }
        createdAt
        replyCount
    }
}'''

    _GRAPHQL_GETCOMMENTSREPLIES_QUERY = '''
query GetCommentReplies($id: String!) {
    getCommentReplies(id: $id, limit: 999999, offset: 0) {
        _id
        content
        user {
            _id
            username
        }
        voteCount {
            positive
        }
        createdAt
        replyCount
    }
}'''

    _GRAPHQL_QUERIES = {
        'GetVideoAndComments': _GRAPHQL_GETMETADATA_QUERY,
        'GetCommentReplies': _GRAPHQL_GETCOMMENTSREPLIES_QUERY,
    }

    def _call_api(self, video_id, id, operation, note):
        return self._download_json(
            'https://api.infowarsmedia.com/graphql', video_id, note=note,
            headers={
                'Content-Type': 'application/json; charset=utf-8'
            }, data=json.dumps({
                'variables': {'id': id},
                'operationName': operation,
                'query': self._GRAPHQL_QUERIES[operation]
            }).encode('utf8')).get('data')

    def _get_comments(self, video_id, comments, comment_data):
        yield from comments
        for comment in comment_data.copy():
            comment_id = comment.get('_id')
            if comment.get('replyCount') > 0:
                reply_json = self._call_api(
                    video_id, comment_id, 'GetCommentReplies',
                    f'Downloading replies for comment {comment_id}')
                for reply in reply_json.get('getCommentReplies'):
                    yield self._parse_comment(reply, comment_id)

    @staticmethod
    def _parse_comment(comment_data, parent):
        return {
            'id': comment_data.get('_id'),
            'text': comment_data.get('content'),
            'author': try_get(comment_data, lambda x: x['user']['username']),
            'author_id': try_get(comment_data, lambda x: x['user']['_id']),
            'timestamp': unified_timestamp(comment_data.get('createdAt')),
            'parent': parent,
            'like_count': try_get(comment_data, lambda x: x['voteCount']['positive']),
        }

    def _real_extract(self, url):
        video_id = self._match_id(url)
        video_json = self._call_api(video_id, video_id, 'GetVideoAndComments', 'Downloading video metadata')
        video_info = video_json['getVideo']
        is_live = video_info.get('live')
        comments = [self._parse_comment(comment, 'root') for comment in video_json.get('getVideoComments')]

        formats = [{
            'format_id': 'direct',
            'quality': 1,
            'url': video_info.get('directUrl'),
            'ext': 'mp4',
        }] if url_or_none(video_info.get('directUrl')) else []
        if video_info.get('streamUrl'):
            formats.extend(self._extract_m3u8_formats(
                video_info.get('streamUrl'), video_id, 'mp4',
                entry_protocol='m3u8_native', m3u8_id='hls', live=True))
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video_info.get('title')[:-1],
            'formats': formats,
            'is_live': is_live,
            'description': video_info.get('summary'),
            'channel': try_get(video_info, lambda x: x['channel']['title']),
            'channel_id': try_get(video_info, lambda x: x['channel']['_id']),
            'view_count': int_or_none(video_info.get('playCount')),
            'thumbnail': url_or_none(video_info.get('largeImage')),
            'duration': float_or_none(video_info.get('videoDuration')),
            'timestamp': unified_timestamp(video_info.get('createdAt')),
            'tags': [tag.get('name') for tag in video_info.get('tags')],
            'availability': self._availability(is_unlisted=video_info.get('unlisted')),
            'comments': comments,
            '__post_extractor': self.extract_comments(video_id, comments, video_json.get('getVideoComments'))
        }