code:
from .common import InfoExtractor

class PeekVidsIE(InfoExtractor):
    _VALID_URL = r'''(?x)
        https?://(?:www\.)?peekvids\.com/
        (?:(?:[^/?#]+/){2}|embed/?\?(?:[^#]*&)?v=)
        (?P<id>[^/?&#]*)
    '''
    _TESTS = [{
        'url': 'https://peekvids.com/pc/dane-jones-cute-redhead-with-perfect-tits-with-mini-vamp/BSyLMbN0YCd',
        'md5': 'a00940646c428e232407e3e62f0e8ef5',
        'info_dict': {
            'id': 'BSyLMbN0YCd',
            'title': ' Dane Jones - Cute redhead with perfect tits with Mini Vamp, SEXYhub',
            'ext': 'mp4',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'Watch  Dane Jones - Cute redhead with perfect tits with Mini Vamp (7 min), uploaded by SEXYhub.com',
            'timestamp': 1642579329,
            'upload_date': '20220119',
            'duration': 416,
            'view_count': int,
            'age_limit': 18,
        },
    }]
    _DOMAIN = 'www.peekvids.com'

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

        short_video_id = self._html_search_regex(r'<video [^>]*data-id="(.+?)"', webpage, 'short video ID')
        srcs = self._download_json(
            f'https://{self._DOMAIN}/v-alt/{short_video_id}', video_id,
            note='Downloading list of source files')
        formats = [{
            'url': url,
            'ext': 'mp4',
            'format_id': name[8:],
        } for name, url in srcs.items() if len(name) > 8 and name.startswith('data-src')]
        if not formats:
            formats = [{'url': url} for url in srcs.values()]
        self._sort_formats(formats)

        info = self._search_json_ld(webpage, video_id, expected_type='VideoObject')
        info.update({
            'id': video_id,
            'age_limit': 18,
            'formats': formats,
        })
        return info

class PlayVidsIE(PeekVidsIE):
    _VALID_URL = r'https?://(?:www\.)?playvids\.com/(?:embed/|[^/]{2}/)?(?P<id>[^/?#]*)'
    _TESTS = [{
        'url': 'https://www.playvids.com/U3pBrYhsjXM/pc/dane-jones-cute-redhead-with-perfect-tits-with-mini-vamp',
        'md5': 'cd7dfd8a2e815a45402369c76e3c1825',
        'info_dict': {
            'id': 'U3pBrYhsjXM',
            'title': ' Dane Jones - Cute redhead with perfect tits with Mini Vamp, SEXYhub',
            'ext': 'mp4',
            'thumbnail': r're:^https?://.*\.jpg$',
            'description': 'Watch  Dane Jones - Cute redhead with perfect tits with Mini Vamp video in HD, uploaded by SEXYhub.com',
            'timestamp': 1640435839,
            'upload_date': '20211225',
            'duration': 416,
            'view_count': int,
            'age_limit': 18,
        },
    }, {
        'url': 'https://www.playvids.com/es/U3pBrYhsjXM/pc/dane-jones-cute-redhead-with-perfect-tits-with-mini-vamp',
        'only_matching': True,
    }, {
        'url': 'https://www.playvids.com/embed/U3pBrYhsjXM',
        'only_matching': True,
    }]
    _DOMAIN = 'www.playvids.com'


code:
from __future__ import unicode_literals

from .peertube import PeerTubeIE, PeerTubePlaylistIE, PeerTubeChannelIE, PeerTubeAccountIE

__all__ = ['PeerTubeIE', 'PeerTubePlaylistIE', 'PeerTubeChannelIE', 'PeerTubeAccountIE']

repo_name: Lesmiscore/ytdl


code:
# coding: utf-8
# AUTOMATICALLY GENERATED FILE. DO NOT EDIT.
# Generated by ./devscripts/make_peertube_instance_list.py
from __future__ import unicode_literals

instances = {
    # list of instances here
    "0ch.tv",
    "40two.tube",
    "a.metube.ch",
    "acts17archive.org",
    "ai-tube.ch",
    "alttube.fr",
    "anarchy.tube",
    "ani.9tail.net",
    "apathy.tv",
    "aperi.tube",
    "apertatube.net",
    "archiwum.ts-tech.us",
    "artitube.artifaille.fr",
    "auf1.eu",
    "badaboom.rakka.tk",
    "battlepenguin.video",
    "beardedtek.net",
    "bee-tube.fr",
    "beetoons.tv",
    "bestgore.fun",
    "beta.swebbtv.se",
    "biblion.refchat.net",
    "bideoak.argia.eus",
    "birkeundnymphe.de",
    "bitcast.online",
    "bitcointv.com",
    "bittube.video",
    "breeze.tube",
    "bundang.tube.jeena.net",
    "canal.facil.services",
    "canard.tube",
    "casstream.nohost.me",
    "cdn01.tilvids.com",
    "cinema.yunohost.support",
    "clap.nerv-project.eu",
    "climatejustice.video",
    "cliptube.org",
    "comf.tube",
    "comic.bot.nu",
    "conf.tube",
    "conspiracydistillery.com",
    "crank.recoil.org",
    "creemoseducacioninclusiva.uma.es",
    "dalek.zone",
    "dalekzone.com",
    "darkvapor.nohost.me",
    "daschauher.aksel.rocks",
    "demo.lioncast.org",
    "demoarts.tv",
    "dev.dollarvigilante.tv",
    "digitalcourage.video",
    "diode.zone",
    "dnk.video",
    "docker.videos.lecygnenoir.info",
    "dreiecksnebel.alex-detsch.de",
    "dud-video.inf.tu-dresden.de",
    "dud175.inf.tu-dresden.de",
    "dumont.video",
    "dytube.com",
    "engelsburgtv.veezee.tube",
    "enum.run",
    "erotube.fr",
    "evangelisch.video",
    "exo.tube",
    "exode.me",
    "fair.tube",
    "fedimovie.com",
    "fediverse.tv",
    "fightforinfo.com",
    "film.k-prod.fr",
    "film.node9.org",
    "flim.txmn.tk",
    "flytandemeistring.space",
    "fontube.fr",
    "foss.tube",
    "fotogramas.politicaconciencia.org",
    "framatube.org",
    "free-streams.com",
    "freediverse.com",
    "frentube.com",
    "frentube.myyouniverse.observer",
    "ftsi.ru",
    "gade.o-k-i.net",
    "galileo.news",
    "gegenstimme.tv",
    "goredb.com",
    "gorf.tube",
    "greatview.video",
    "grypstube.uni-greifswald.de",
    "h3h3.club",
    "hitchtube.fr",
    "hpstube.fr",
    "hyperreal.tube",
    "iamazure.auton.id",
    "ias-peertube.iosb.fraunhofer.de",
    "indymotion.fr",
    "infothema.net",
    "invoice.peertube.biz",
    "irrsinn.video",
    "jahve.pl",
    "juggling.digital",
    "jupiter.tube",
    "jzqk.org",
    "kenfm.quadplay.tv",
    "kino.kompot.si",
    "kino.schuerz.at",
    "kinotu.be",
    "kinowolnosc.pl",
    "kirche.peertube-host.de",
    "kodcast.com",
    "kolektiva.media",
    "kraut.zone",
    "krdtube.org",
    "kumi.tube",
    "lastbreach.tv",
    "lawsplaining.peertube.biz",
    "leopard.tube",
    "lepetitmayennais.fr.nf",
    "lexx.impa.me",
    "lfbu.nl",
    "libra.syntazia.org",
    "libre.video",
    "libremedia.video",
    "libretube.ru",
    "light.tcsh.online",
    "linhtran.eu",
    "live.codinglab.ch",
    "live.libratoi.org",
    "live.nanao.moe",
    "live.oldskool.fi",
    "live.solari.com",
    "live2.codinglab.ch",
    "livegram.net",
    "lockdown-online-teknival.org",
    "lostpod.space",
    "lucarne.balsamine.be",
    "maindreieck-tv.de",
    "mani.tube",
    "media-test.interior.edu.uy",
    "media.assassinate-you.net",
    "media.econoalchemist.com",
    "media.fsfe.org",
    "media.gzevd.de",
    "media.interior.edu.uy",
    "media.krashboyz.org",
    "media.over-world.org",
    "media.privacyinternational.org",
    "media.theplattform.net",
    "media.undeadnetwork.de",
    "media.unifiedberets.com",
    "medias.debrouillonet.org",
    "medias.pingbase.net",
    "mediathek.rzgierskopp.de",
    "megatube.lilomoino.fr",
    "melsungen.peertube-host.de",
    "merci-la-police.fr",
    "mikeclips.net",
    "mirametube.fr",
    "mirrored.rocks",
    "mojotube.net",
    "monero.wf",
    "monplaisirtube.ddns.net",
    "mountaintown.video",
    "mov.clov.fr",
    "mp-tube.de",
    "mplayer.demouliere.eu",
    "mtube.mooo.com",
    "music.noho.st",
    "mytube.kn-cloud.de",
    "mytube.madzel.de",
    "nadajemy.com",
    "nanawel-peertube.dyndns.org",
    "nastub.cz",
    "nekofans.tv",
    "nettube.uc-netcorsoft.de",
    "nightshift.minnix.dev",
    "noagendatube.com",
    "notretube.asselma.eu",
    "nya.show",
    "ocfedtest.hosted.spacebear.ee",
    "offenes.tv",
    "oglej.ga",
    "old.tormentasolar.win",
    "open.movie",
    "open.tube",
    "openmedia.edunova.it",
    "ovaltube.codinglab.ch",
    "p.eertu.be",
    "p.lu",
    "p2ptv.ru",
    "pace.rip",
    "pcbu.nl",
    "peer.azurs.fr",
    "peer.galaxycrow.de",
    "peer.philoxweb.be",
    "peer.tube",
    "peertube.020.pl",
    "peertube.0x5e.eu",
    "peertube.1312.media",
    "peertube.1984.cz",
    "peertube.2i2l.net",
    "peertube.3dcandy.social",
    "peertube.adjutor.xyz",
    "peertube.alpharius.io",
    "peertube.am-networks.fr",
    "peertube.amicale.net",
    "peertube.anduin.net",
    "peertube.anon-kenkai.com",
    "peertube.antaresx.ch",
    "peertube.anzui.dev",
    "peertube.arch-linux.cz",
    "peertube.art3mis.de",
    "peertube.artica.center",
    "peertube.as62430.net",
    "peertube.askan.info",
    "peertube.atilla.org",
    "peertube.atsuchan.page",
    "peertube.aukfood.net",
    "peertube.autogestioncitoyenne.org",
    "peertube.aventer.biz",
    "peertube.based-jacob.xyz",
    "peertube.be",
    "peertube.beardedtek.com",
    "peertube.beeldengeluid.nl",
    "peertube.bgzashtita.es",
    "peertube.bilange.ca",
    "peertube.bittube.tv",
    "peertube.biz",
    "peertube.br0.fr",
    "peertube.bridaahost.ynh.fr",
    "peertube.bubbletea.dev",
    "peertube.bubuit.net",
    "peertube.buymymojo.net",
    "peertube.cabaal.net",
    "peertube.cf",
    "peertube.ch",
    "peertube.chantierlibre.org",
    "peertube.chatinbit.com",
    "peertube.chaunchy.com",
    "peertube.chtisurel.net",
    "peertube.cipherbliss.com",
    "peertube.cirkau.art",
    "peertube.cloomii.com",
    "peertube.cloud.nerdraum.de",
    "peertube.cloud.sans.pub",
    "peertube.co.uk",
    "peertube.coko.foundation",
    "peertube.communecter.org",
    "peertube.cpge-brizeux.fr",
    "peertube.cpy.re",
    "peertube.ctrl-c.liu.se",
    "peertube.ctseuro.com",
    "peertube.cuatrolibertades.org",
    "peertube.datagueule.tv",
    "peertube.davigge.com",
    "peertube.dc.pini.fr",
    "peertube.debian.social",
    "peertube.demonix.fr",
    "peertube.designersethiques.org",
    "peertube.desmu.fr",
    "peertube.devloprog.org",
    "peertube.devol.it",
    "peertube.dragonborn.app",
    "peertube.dsmouse.net",
    "peertube.dtmf.ca",
    "peertube.dynlinux.io",
    "peertube.eb8.org",
    "peertube.ecologie.bzh",
    "peertube.education-forum.com",
    "peertube.egroc.de",
    "peertube.elforcer.ru",
    "peertube.espace.si",
    "peertube.ethibox.fr",
    "peertube.eu.org",
    "peertube.european-pirates.eu",
    "peertube.eus",
    "peertube.euskarabildua.eus",
    "peertube.ev1.pw",
    "peertube.expi.studio",
    "peertube.f-si.org",
    "peertube.familleboisteau.fr",
    "peertube.fenarinarsa.com",
    "peertube.ffs2play.fr",
    "peertube.floss-marketing-school.com",
    "peertube.fomin.site",
    "peertube.fr",
    "peertube.freenet.ru",
    "peertube.frontmediatique.fr",
    "peertube.functional.cafe",
    "peertube.gaialabs.ch",
    "peertube.gardion.de",
    "peertube.gargantia.fr",
    "peertube.gcaillaut.fr",
    "peertube.gcfamily.fr",
    "peertube.gegeweb.eu",
    "peertube.gemlog.ca",
    "peertube.genma.fr",
    "peertube.get-racing.de",
    "peertube.gidikroon.eu",
    "peertube.grosist.fr",
    "peertube.gsugambit.com",
    "peertube.habets.house",
    "peertube.hackerfraternity.org",
    "peertube.hacknews.pmdcollab.org",
    "peertube.hainry.fr",
    "peertube.hellsite.net",
    "peertube.heraut.eu",
    "peertube.histoirescrepues.fr",
    "peertube.home.x0r.fr",
    "peertube.hosnet.fr",
    "peertube.ichigo.everydayimshuflin.com",
    "peertube.ignifi.me",
    "peertube.imaag.de",
    "peertube.inapurna.org",
    "peertube.informaction.info",
    "peertube.inubo.ch",
    "peertube.iriseden.eu",
    "peertube.it",
    "peertube.jackbot.fr",
    "peertube.jensdiemer.de",
    "peertube.kalua.im",
    "peertube.keazilla.net",
    "peertube.kenjiyon.xyz",
    "peertube.ketchup.noho.st",
    "peertube.klaewyss.fr",
    "peertube.kleph.eu",
    "peertube.kriom.net",
    "peertube.kx.studio",
    "peertube.laas.fr",
    "peertube.lagob.fr",
    "peertube.lagvoid.com",
    "peertube.lestutosdeprocessus.fr",
    "peertube.letoine.fr",
    "peertube.lhc.net.br",
    "peertube.librelabucm.org",
    "peertube.librenet.co.za",
    "peertube.librosphere.fr",
    "peertube.linuxrocks.online",
    "peertube.live",
    "peertube.livingutopia.org",
    "peertube.local-rouze.org",
    "peertube.logilab.fr",
    "peertube.louis.software",
    "peertube.louisematic.site",
    "peertube.luckow.org",
    "peertube.luga.at",
    "peertube.lyceeconnecte.fr",
    "peertube.madixam.xyz",
    "peertube.makotoworkshop.org",
    "peertube.manalejandro.com",
    "peertube.marud.fr",
    "peertube.maxweiss.io",
    "peertube.mi-site.net",
    "peertube.miguelcr.me",
    "peertube.mobilsicher.de",
    "peertube.monlycee.net",
    "peertube.musicstudio.pro",
    "peertube.mxinfo.fr",
    "peertube.mygaia.org",
    "peertube.myrasp.eu",
    "peertube.narrativerry.xyz",
    "peertube.netzbegruenung.de",
    "peertube.newsocial.tech",
    "peertube.nicolastissot.fr",
    "peertube.nodja.com",
    "peertube.nogafa.org",
    "peertube.nomagic.uk",
    "peertube.normalgamingcommunity.cz",
    "peertube.noussommes.org",
    "peertube.nz",
    "peertube.offerman.com",
    "peertube.ohioskates.com",
    "peertube.okko.io",
    "peertube.onionstorm.net",
    "peertube.opencloud.lu",
    "peertube.openstreetmap.fr",
    "peertube.otakufarms.com",
    "peertube.p2p-node.de",
    "peertube.paladyn.org",
    "peertube.parleur.net",
    "peertube.patapouf.xyz",
    "peertube.pcservice46.fr",
    "peertube.pi2.dev",
    "peertube.pl",
    "peertube.plataformess.org",
    "peertube.plop.gq",
    "peertube.public.cat",
    "peertube.qtg.fr",
    "peertube.r2.enst.fr",
    "peertube.r5c3.fr",
    "peertube.radres.xyz",
    "peertube.rainbowswingers.net",
    "peertube.red",
    "peertube.redpill-insight.com",
    "peertube.remerge.net",
    "peertube.revelin.fr",
    "peertube.rlp.schule",
    "peertube.roflcopter.fr",
    "peertube.rougevertbleu.tv",
    "peertube.rse43.com",
    "peertube.rural-it.org",
    "peertube.s2s.video",
    "peertube.satoshishop.de",
    "peertube.scic-tetris.org",
    "peertube.scyldings.com",
    "peertube.se",
    "peertube.sebu77.com",
    "peertube.securitymadein.lu",
    "peertube.semweb.pro",
    "peertube.sensin.eu",
    "peertube.serveur.slv-valbonne.fr",
    "peertube.simounet.net",
    "peertube.skorpil.cz",
    "peertube.sl-network.fr",
    "peertube.slat.org",
    "peertube.snargol.com",
    "peertube.social.my-wan.de",
    "peertube.solidev.net",
    "peertube.soykaf.org",
    "peertube.stefofficiel.me",
    "peertube.stemy.me",
    "peertube.stream",
    "peertube.su",
    "peertube.suranyami.com",
    "peertube.swarm.solvingmaz.es",
    "peertube.swrs.net",
    "peertube.takeko.cyou",
    "peertube.tangentfox.com",
    "peertube.taxinachtegel.de",
    "peertube.terranout.mine.nu",
    "peertube.thele.me",
    "peertube.therealblue.de",
    "peertube.ti-fr.com",
    "peertube.tiennot.net",
    "peertube.today",
    "peertube.togart.de",
    "peertube.touhoppai.moe",
    "peertube.travelpandas.eu",
    "peertube.treffler.cloud",
    "peertube.troback.com",
    "peertube.tspu.edu.ru",
    "peertube.tweb.tv",
    "peertube.ucy.de",
    "peertube.umeahackerspace.se",
    "peertube.underworld.fr",
    "peertube.uno",
    "peertube.vapronva.pw",
    "peertube.vip",
    "peertube.virtual-assembly.org",
    "peertube.vlaki.cz",
    "peertube.w.utnw.de",
    "peertube.we-keys.fr",
    "peertube.wiredentrypoint.xyz",
    "peertube.wivodaim.net",
    "peertube.xwiki.com",
    "peertube.zapashcanon.fr",
    "peertube.zergy.net",
    "peertube.zoz-serv.org",
    "peertube.zwindler.fr",
    "peertube1.zeteo.me",
    "peertube2.cpy.re",
    "peertube3.cpy.re",
    "peertuibe.fedsinfo.nohost.me",
    "peervideo.club",
    "peervideo.ru",
    "perceptiontravel.tv",
    "periscope.numenaute.org",
    "pete.warpnine.de",
    "petitlutinartube.fr",
    "pewflix.com",
    "phijkchu.com",
    "phoenixproject.group",
    "piercingtheveil.tv",
    "pierre.tube",
    "piraten.space",
    "pire.artisanlogiciel.net",
    "pirtube.calut.fr",
    "pityu.flaki.hu",
    "play.mittdata.se",
    "play.rosano.ca",
    "player.ojamajo.moe",
    "playroom.kitsunes.world",
    "plextube.nl",
    "poast.tv",
    "pocketnetpeertube1.nohost.me",
    "pocketnetpeertube10.nohost.me",
    "pocketnetpeertube11.nohost.me",
    "pocketnetpeertube2.nohost.me",
    "pocketnetpeertube4.nohost.me",
    "pocketnetpeertube5.nohost.me",
    "pocketnetpeertube6.nohost.me",
    "pocketnetpeertube7.nohost.me",
    "pocketnetpeertube9.nohost.me",
    "podlibre.video",
    "polskijutub.mkljczk.pl",
    "pony.tube",
    "pornteur.com",
    "portal.digilab.nfa.cz",
    "ppstube.portageps.org",
    "praxis.su",
    "private.fedimovie.com",
    "przej.me",
    "pt.bolapara.mywire.org",
    "pt.borgcube.eu",
    "pt.diaspodon.fr",
    "pt.freedomwolf.cc",
    "pt.gordons.gen.nz",
    "pt.ilyamikcoder.com",
    "pt.k2s.sk",
    "pt.m0r016.net",
    "pt.maciej.website",
    "pt.mezzo.moe",
    "pt.nix.uno",
    "pt.nospy.net",
    "pt.pube.tk",
    "pt.sfunk1x.com",
    "pt01.lehrerfortbildung-bw.de",
    "ptb.lunarviews.net",
    "ptmir1.inter21.net",
    "ptmir3.inter21.net",
    "ptmir4.inter21.net",
    "ptube.dox.tf",
    "ptube.horsentiers.fr",
    "ptube.ranranhome.info",
    "ptube.rousset.nom.fr",
    "puffy.tube",
    "quantube.win",
    "queermotion.org",
    "rankett.net",
    "raptube.antipub.org",
    "redundant2.peertube.support",
    "refuznik.video",
    "regarder.sans.pub",
    "renewtube.org",
    "replay.jres.org",
    "repro.video",
    "review.peertube.biz",
    "rtmp.steflaf.fr",
    "runtube.re",
    "ruraletv.ovh",
    "s.vnchich.net",
    "scitech.video",
    "scores.tube",
    "sdmtube.fr",
    "see.vtopia.live",
    "seka.pona.la",
    "sender-fm.veezee.tube",
    "serv1.wiki-tube.de",
    "serv2.wiki-tube.de",
    "serv3.wiki-tube.de",
    "share.tube",
    "sharetube.us",
    "shiotube.f5.si",
    "shotatube.xyz",
    "skeptikon.fr",
    "skeptube.fr",
    "soberania.tv",
    "socialwebtube.com",
    "socpeertube.ru",
    "sovran.video",
    "spacepub.space",
    "spectra.video",
    "staging.gegenstimme.tv",
    "stl1988.peertube-host.de",
    "stream.bungle.chat",
    "stream.conesphere.cloud",
    "stream.digikite.net",
    "stream.dynacloud.ynh.fr",
    "stream.elven.pw",
    "stream.jurnalfm.md",
    "stream.k-prod.fr",
    "stream.litera.tools",
    "stream.nuemedia.se",
    "stream.trm.md",
    "streamarchive.manicphase.me",
    "streamsource.video",
    "strzelmistrz.com",
    "studios.racer159.com",
    "swebbtube.se",
    "swebbtv.se",
    "syop.tv",
    "t.0x0.st",
    "teevee.rdam.tools",
    "testtube.florimond.eu",
    "testube.distrilab.fr",
    "the.jokertv.eu",
    "theater.ethernia.net",
    "thecool.tube",
    "themintfarm.ml",
    "tilvids.com",
    "tinsley.video",
    "toobnix.org",
    "torstenwitte.peertube-host.de",
    "troll.tv",
    "truetube.media",
    "trutube.online",
    "trutube.xyz",
    "truvitv.com",
    "tube-action-educative.apps.education.fr",
    "tube-aix-marseille.beta.education.fr",
    "tube-amiens.beta.education.fr",
    "tube-arts-lettres-sciences-humaines.apps.education.fr",
    "tube-besancon.beta.education.fr",
    "tube-bordeaux.beta.education.fr",
    "tube-clermont-ferrand.beta.education.fr",
    "tube-corse.beta.education.fr",
    "tube-creteil.beta.education.fr",
    "tube-cycle-2.apps.education.fr",
    "tube-cycle-3.apps.education.fr",
    "tube-dijon.beta.education.fr",
    "tube-education-physique-et-sportive.apps.education.fr",
    "tube-education.beta.education.fr",
    "tube-enseignement-professionnel.apps.education.fr",
    "tube-grenoble.beta.education.fr",
    "tube-institutionnel.apps.education.fr",
    "tube-langues-vivantes.apps.education.fr",
    "tube-lille.beta.education.fr",
    "tube-limoges.beta.education.fr",
    "tube-maternelle.apps.education.fr",
    "tube-montpellier.beta.education.fr",
    "tube-nancy.beta.education.fr",
    "tube-nantes.beta.education.fr",
    "tube-nice.beta.education.fr",
    "tube-normandie.beta.education.fr",
    "tube-numerique-educatif.apps.education.fr",
    "tube-orleans-tours.beta.education.fr",
    "tube-outremer.beta.education.fr",
    "tube-paris.beta.education.fr",
    "tube-poitiers.beta.education.fr",
    "tube-reims.beta.education.fr",
    "tube-sciences-technologies.apps.education.fr",
    "tube-strasbourg.beta.education.fr",
    "tube-test.apps.education.fr",
    "tube-toulouse.beta.education.fr",
    "tube-versailles.beta.education.fr",
    "tube.03281.fspfc.org",
    "tube.1o1.io",
    "tube.4aem.com",
    "tube.9minuti.it",
    "tube.abolivier.bzh",
    "tube.ac-amiens.fr",
    "tube.ac-lyon.fr",
    "tube.aerztefueraufklaerung.de",
    "tube.alado.space",
    "tube.amic37.fr",
    "tube.angst-frei.ch",
    "tube.anjara.eu",
    "tube.anufrij.de",
    "tube.apolut.net",
    "tube.aquilenet.fr",
    "tube.arthack.nz",
    "tube.avensio.de",
    "tube.azadehafzar.ir",
    "tube.azbyka.ru",
    "tube.azkware.net",
    "tube.bachaner.fr",
    "tube.benzo.online",
    "tube.bit-friends.de",
    "tube.bstly.de",
    "tube.calculate.social",
    "tube.chaoszone.tv",
    "tube.childrenshealthdefense.eu",
    "tube.chocoflan.net",
    "tube.cloud-libre.eu",
    "tube.cloud.rousselie.name",
    "tube.cms.garden",
    "tube.cnr.it",
    "tube.communia.org",
    "tube.conferences-gesticulees.net",
    "tube.crapaud-fou.org",
    "tube.cryptography.dog",
    "tube.cyano.at",
    "tube.cyberia.club",
    "tube.cybershock.life",
    "tube.danq.me",
    "tube.darknight-coffee.org",
    "tube.defenders.town",
    "tube.derlemue.com",
    "tube.dianaband.info",
    "tube.die-rote-front.de",
    "tube.digitalesozialearbeit.de",
    "tube.dirkomatik.de",
    "tube.distrilab.fr",
    "tube.ditatompel.com",
    "tube.dnet.one",
    "tube.doctors4covidethics.org",
    "tube.dragonpsi.xyz",
    "tube.dsocialize.net",
    "tube.ebin.club",
    "tube.elemac.fr",
    "tube.emy.plus",
    "tube.emy.world",
    "tube.erzbistum-hamburg.de",
    "tube.exozy.me",
    "tube.extinctionrebellion.fr",
    "tube.fait.ch",
    "tube.fdn.fr",
    "tube.fede.re",
    "tube.flokinet.is",
    "tube.florimond.eu",
    "tube.fofoca.eu",
    "tube.foxden.party",
    "tube.freepeople.fr",
    "tube.friloux.me",
    "tube.frischesicht.de",
    "tube.froth.zone",
    "tube.futuretic.fr",
    "tube.g1zm0.de",
    "tube.geekyboo.net",
    "tube.genb.de",
    "tube.ghk-academy.info",
    "tube.globalfrens.com",
    "tube.gnous.eu",
    "tube.govital.net",
    "tube.grap.coop",
    "tube.graz.social",
    "tube.grin.hu",
    "tube.hackerscop.org",
    "tube.hoga.fr",
    "tube.homecomputing.fr",
    "tube.hunterjozwiak.com",
    "tube.int5.net",
    "tube.interhacker.space",
    "tube.io18.top",
    "tube.itsg.host",
    "tube.jeena.net",
    "tube.kai-stuht.com",
    "tube.kdy.ch",
    "tube.kekmahub.com",
    "tube.kenfm.de",
    "tube.kher.nl",
    "tube.kicou.info",
    "tube.klardenken.tv",
    "tube.kockatoo.org",
    "tube.kotur.org",
    "tube.koweb.fr",
    "tube.koyax.org",
    "tube.ksl-bmx.de",
    "tube.lacaveatonton.ovh",
    "tube.lelaidier.net",
    "tube.linc.systems",
    "tube.lokad.com",
    "tube.lucie-philou.com",
    "tube.maiti.info",
    "tube.mediainformationcenter.de",
    "tube.melonbread.xyz",
    "tube.mfraters.net",
    "tube.midov.pl",
    "tube.miegl.cz",
    "tube.missbanal.net",
    "tube.misterbanal.net",
    "tube.motuhake.xyz",
    "tube.nah.re",
    "tube.nestor.coop",
    "tube.network.europa.eu",
    "tube.nicfab.it",
    "tube.nilsu.org",
    "tube.nocturlab.fr",
    "tube.nogafa.org",
    "tube.nowtech.io",
    "tube.nox-rhea.org",
    "tube.nuagelibre.fr",
    "tube.nuxnik.com",
    "tube.nx-pod.de",
    "tube.nx12.net",
    "tube.octaplex.net",
    "tube.oisux.org",
    "tube.okcinfo.news",
    "tube.oldkid.digital",
    "tube.onlinekirche.net",
    "tube.opportunis.me",
    "tube.oraclefilms.com",
    "tube.org.il",
    "tube.others.social",
    "tube.otter.sh",
    "tube.p2p.legal",
    "tube.pawelko.net",
    "tube.picasoft.net",
    "tube.pilgerweg-21.de",
    "tube.piweb.be",
    "tube.plaf.fr",
    "tube.plomlompom.com",
    "tube.pmj.rocks",
    "tube.polytech-reseau.org",
    "tube.ponsonaille.fr",
    "tube.port0.xyz",
    "tube.portes-imaginaire.org",
    "tube.postblue.info",
    "tube.probono.media",
    "tube.pyngu.com",
    "tube.querdenken-711.de",
    "tube.radiomercure.fr",
    "tube.rebellion.global",
    "tube.reseau-canope.fr",
    "tube.revertron.com",
    "tube.rfc1149.net",
    "tube.rhythms-of-resistance.org",
    "tube.rita.moe",
    "tube.rooty.fr",
    "tube.rsi.cnr.it",
    "tube.s1gm4.eu",
    "tube.saumon.io",
    "tube.schleuss.online",
    "tube.schule.social",
    "tube.seditio.fr",
    "tube.shanti.cafe",
    "tube.shela.nu",
    "tube.skrep.in",
    "tube.sleeping.town",
    "tube.sloth.network",
    "tube.source.news",
    "tube.sp-codes.de",
    "tube.spdns.org",
    "tube.sysctl.io",
    "tube.systerserver.net",
    "tube.systest.eu",
    "tube.taker.fr",
    "tube.tappret.fr",
    "tube.tchncs.de",
    "tube.thechangebook.org",
    "tube.thierrytalbert.fr",
    "tube.toldi.eu",
    "tube.toontoet.nl",
    "tube.tpshd.de",
    "tube.tr4sk.me",
    "tube.troopers.agency",
    "tube.tuxfriend.fr",
    "tube.tylerdavis.xyz",
    "tube.ullihome.de",
    "tube.ulne.be",
    "tube.undernet.uy",
    "tube.unif.app",
    "tube.valinor.fr",
    "tube.villejuif.fr",
    "tube.vrpnet.org",
    "tube.wehost.lgbt",
    "tube.wolfe.casa",
    "tube.xd0.de",
    "tube.xn--baw-joa.social",
    "tube.xsoftware.it",
    "tube.xy-space.de",
    "tube.yapbreak.fr",
    "tube.zee.li",
    "tube1.it.tuwien.ac.at",
    "tube2.apolut.net",
    "tube2.nous.media",
    "tube4.apolut.net",
    "tubedu.org",
    "tubes.jodh.us",
    "tubocatodico.bida.im",
    "tubulus.openlatin.org",
    "tuktube.com",
    "turkum.me",
    "tututu.tube",
    "tuvideo.encanarias.info",
    "tv.adn.life",
    "tv.atmx.ca",
    "tv.based.quest",
    "tv.catalpafestival.fr",
    "tv.datamol.org",
    "tv.filmfreedom.net",
    "tv.generallyrubbish.net.au",
    "tv.kirkmanskorner.com",
    "tv.lumbung.space",
    "tv.mattchristiansenmedia.com",
    "tv.neue.city",
    "tv.orion-serv.fr",
    "tv.pirateradio.social",
    "tv.pirati.cz",
    "tv.santic-zombie.ru",
    "tv.suwerenni.org",
    "tv.undersco.re",
    "tv1.gomntu.space",
    "tvox.ru",
    "twctube.twc-zone.eu",
    "ua.peertube.red",
    "ukva.li",
    "unfilter.tube",
    "v.basspistol.org",
    "v.bearvideo.win",
    "v.esd.cc",
    "v.kisombrella.top",
    "v.kretschmann.social",
    "v.lastorder.xyz",
    "v.lor.sh",
    "v.mkp.ca",
    "v.wolfskaempf.de",
    "v1.smartit.nu",
    "varis.tv",
    "vdp.veezee.tube",
    "vhs.absturztau.be",
    "vid.ancreport.com",
    "vid.lelux.fi",
    "vid.mkp.ca",
    "vid.ncrypt.at",
    "vid.plantplotting.co.uk",
    "vid.pretok.tv",
    "vid.prometheus.systems",
    "vid.samtripoli.com",
    "vid.twhtv.club",
    "vid.wildeboer.net",
    "vid.y-y.li",
    "vidcommons.org",
    "vide.oxel.me",
    "video-cave-v2.de",
    "video.076.ne.jp",
    "video.1146.nohost.me",
    "video.3cmr.fr",
    "video.9wd.eu",
    "video.administrieren.net",
    "video.ados.accoord.fr",
    "video.altertek.org",
    "video.amiga-ng.org",
    "video.anartist.org",
    "video.antopie.org",
    "video.aqr.af",
    "video.asgardius.company",
    "video.asulia.fr",
    "video.audiovisuel-participatif.org",
    "video.avant-le-strike.buzz",
    "video.barbed.fr",
    "video.barcelo.cf",
    "video.bards.online",
    "video.barkoczy.social",
    "video.benetou.fr",
    "video.bhscs2.club",
    "video.binarydad.com",
    "video.blast-info.fr",
    "video.blender.org",
    "video.blueline.mg",
    "video.cabane-libre.org",
    "video.campusgemeinde.de",
    "video.catgirl.biz",
    "video.cats-home.net",
    "video.cerclearistote.com",
    "video.chadwaltercummings.me",
    "video.chalec.org",
    "video.chbmeyer.de",
    "video.cigliola.com",
    "video.citizen4.eu",
    "video.clumsy.computer",
    "video.cm-en-transition.fr",
    "video.cnr.it",
    "video.cnt.social",
    "video.coales.co",
    "video.codefor.de",
    "video.codingfield.com",
    "video.colibris-outilslibres.org",
    "video.comptoir.net",
    "video.comun.al",
    "video.comune.trento.it",
    "video.coop.tools",
    "video.cpn.so",
    "video.csc49.fr",
    "video.cybre.town",
    "video.davduf.net",
    "video.discord-insoumis.fr",
    "video.dnfi.no",
    "video.dresden.network",
    "video.dudenas.lt",
    "video.ecole-89.com",
    "video.eientei.org",
    "video.electronicping.net",
    "video.ellijaymakerspace.org",
    "video.emergeheart.info",
    "video.eradicatinglove.xyz",
    "video.espr.moe",
    "video.eutychia.org",
    "video.exodus-privacy.eu.org",
    "video.extremelycorporate.ca",
    "video.fbxl.net",
    "video.fdlibre.eu",
    "video.fhtagn.org",
    "video.firehawk-systems.com",
    "video.fitchfamily.org",
    "video.fox-romka.ru",
    "video.freie-linke.de",
    "video.g3l.org",
    "video.genky.duckdns.org",
    "video.graine-pdl.org",
    "video.greenmycity.eu",
    "video.gresille.org",
    "video.guerredeclasse.fr",
    "video.gyt.is",
    "video.hackers.town",
    "video.hainry.fr",
    "video.hardlimit.com",
    "video.hostpath.de",
    "video.hostux.net",
    "video.igem.org",
    "video.internet-czas-dzialac.pl",
    "video.interru.io",
    "video.jacen.moe",
    "video.jigmedatse.com",
    "video.kicik.fr",
    "video.kuba-orlik.name",
    "video.lapineige.fr",
    "video.latavernedejohnjohn.fr",
    "video.lavolte.net",
    "video.lemediatv.fr",
    "video.lespoesiesdheloise.fr",
    "video.liberta.vip",
    "video.libreti.net",
    "video.licentia.net",
    "video.linc.systems",
    "video.lincolncyber.com",
    "video.linux.it",
    "video.linuxtrent.it",
    "video.livecchi.cloud",
    "video.liveitlive.show",
    "video.lono.space",
    "video.lqdn.fr",
    "video.lrose.de",
    "video.lunasqu.ee",
    "video.lundi.am",
    "video.lw1.at",
    "video.lycee-experimental.org",
    "video.manicphase.me",
    "video.marcorennmaus.de",
    "video.mass-trespass.uk",
    "video.matomocamp.org",
    "video.medienzentrum-harburg.de",
    "video.mentality.rip",
    "video.metaccount.de",
    "video.metaversum.wtf",
    "video.migennes.net",
    "video.monsieurbidouille.fr",
    "video.moonmen.us",
    "video.motoreitaliacarlonegri.it",
    "video.mttv.it",
    "video.mugoreve.fr",
    "video.mullet.social",
    "video.mundodesconocido.com",
    "video.mycrowd.ca",
    "video.nesven.eu",
    "video.netsyms.com",
    "video.niboe.info",
    "video.nobodyhasthe.biz",
    "video.nogafam.es",
    "video.nonewabnormal.com",
    "video.occm.cc",
    "video.odayacres.farm",
    "video.off-investigation.fr",
    "video.oh14.de",
    "video.okaris.de",
    "video.omniatv.com",
    "video.osvoj.ru",
    "video.ozgurkon.org",
    "video.p1ng0ut.social",
    "video.p3x.de",
    "video.paradigmthreat.net",
    "video.passageenseine.fr",
    "video.pcf.fr",
    "video.pe6.eu",
    "video.perivid.xyz",
    "video.ploud.fr",
    "video.ploud.jp",
    "video.pony.gallery",
    "video.pourpenser.pro",
    "video.progressiv.dev",
    "video.pthreat.co",
    "video.publicspaces.net",
    "video.pullopen.xyz",
    "video.qoto.org",
    "video.r3s.nrw",
    "video.rainevixen.com",
    "video.rastapuls.com",
    "video.resolutions.it",
    "video.retroedge.tech",
    "video.rhizome.org",
    "video.rs-einrich.de",
    "video.rubdos.be",
    "video.sadmin.io",
    "video.sbud.tk",
    "video.schnitzler.one",
    "video.sdm-tools.net",
    "video.selea.se",
    "video.sftblw.moe",
    "video.sgiath.dev",
    "video.shitposter.club",
    "video.simplex-software.ru",
    "video.slipfox.xyz",
    "video.snug.moe",
    "video.software-fuer-engagierte.de",
    "video.soi.ch",
    "video.sonet.ws",
    "video.stlzr.de",
    "video.subak.ovh",
    "video.taboulisme.com",
    "video.tedomum.net",
    "video.telemillevaches.net",
    "video.tferdinand.net",
    "video.thinkof.name",
    "video.tii.space",
    "video.toby3d.me",
    "video.toot.pt",
    "video.trankil.info",
    "video.travisshears.xyz",
    "video.triplea.fr",
    "video.tryptophonic.com",
    "video.turbo.chat",
    "video.typica.us",
    "video.up.edu.ph",
    "video.uriopss-pdl.fr",
    "video.ustim.ru",
    "video.ut0pia.org",
    "video.vaku.org.ua",
    "video.valme.io",
    "video.vanderwarker.photos",
    "video.vegafjord.me",
    "video.veloma.org",
    "video.violoncello.ch",
    "video.vny.fr",
    "video.wakkeren.nl",
    "video.wee.tf",
    "video.wilkie.how",
    "video.windfluechter.org",
    "video.writeas.org",
    "video.wsf2021.info",
    "video.wsforum.net",
    "video.wuatek.is",
    "video.xonumi.com",
    "video.zarchbox.ovh",
    "video4b.ru",
    "videomensoif.ynh.fr",
    "videopen.net",
    "videos-libr.es",
    "videos-passages.huma-num.fr",
    "videos.3d-wolf.com",
    "videos.aadtp.be",
    "videos.adhocmusic.com",
    "videos.ahp-numerique.fr",
    "videos.alamaisondulibre.org",
    "videos.alexandrebadalo.pt",
    "videos.alolise.org",
    "videos.archigny.net",
    "videos.arumoon.ru",
    "videos.avency.de",
    "videos.b4tech.org",
    "videos.benjaminbrady.ie",
    "videos.benpro.fr",
    "videos.bik.opencloud.lu",
    "videos.buceoluegoexisto.com",
    "videos.campdarling.com",
    "videos.capas.se",
    "videos.carsonchappel.xyz",
    "videos.cassidypunchmachine.com",
    "videos.cemea.org",
    "videos.cloudron.io",
    "videos.codingotaku.com",
    "videos.coletivos.org",
    "videos.danksquad.org",
    "videos.domainepublic.net",
    "videos.ephphatha.church",
    "videos.factsonthegroundshow.com",
    "videos.festivalparminous.org",
    "videos.fromouter.space",
    "videos.fsci.in",
    "videos.funkwhale.audio",
    "videos.globenet.org",
    "videos.grafo.zone",
    "videos.gunfreezone.net",
    "videos.hack2g2.fr",
    "videos.hauspie.fr",
    "videos.hush.is",
    "videos.hyphalfusion.network",
    "videos.im.allmendenetz.de",
    "videos.iut-orsay.fr",
    "videos.jackbenson.xyz",
    "videos.john-livingston.fr",
    "videos.keyedlimepie.org",
    "videos.koumoul.com",
    "videos.laernorsk.no",
    "videos.laliguepaysdelaloire.org",
    "videos.lemnoslife.com",
    "videos.lemouvementassociatif-pdl.org",
    "videos.lescommuns.org",
    "videos.leslionsfloorball.fr",
    "videos.librescrum.org",
    "videos.lukesmith.xyz",
    "videos.martyn.berlin",
    "videos.mastodont.cat",
    "videos.mattwilson.org",
    "videos.npo.city",
    "videos.openmandriva.org",
    "videos.optoutpod.com",
    "videos.pair2jeux.tube",
    "videos.pcorp.us",
    "videos.pueseso.club",
    "videos.pzelawski.xyz",
    "videos.rabbit-company.com",
    "videos.rampin.org",
    "videos.realnephestate.xyz",
    "videos.redeyes.site",
    "videos.rights.ninja",
    "videos.ritimo.org",
    "videos.sadx.moe",
    "videos.sarcasmstardust.com",
    "videos.scanlines.xyz",
    "videos.shmalls.pw",
    "videos.side-ways.net",
    "videos.slownewsdayshow.com",
    "videos.squat.net",
    "videos.stadtfabrikanten.org",
    "videos.supertuxkart.net",
    "videos.tankernn.eu",
    "videos.tcit.fr",
    "videos.testimonia.org",
    "videos.thinkerview.com",
    "videos.tormentasolar.win",
    "videos.trom.lt",
    "videos.trom.tf",
    "videos.ubuntu-paris.org",
    "videos.upr.fr",
    "videos.weblib.re",
    "videos.wirtube.de",
    "videos.yesil.club",
    "videos.yeswiki.net",
    "videotry.de",
    "vidid.pl",
    "vids.roshless.me",
    "vids.stary.pc.pl",
    "vids.tekdmn.me",
    "vidz.dou.bet",
    "vidz.julien.ovh",
    "viet69.ml",
    "views.southfox.me",
    "visionon.tv",
    "viste.pt",
    "vnchich.com",
    "vnhup.com",
    "vnop.org",
    "vod.ksite.de",
    "vtr.chikichiki.tube",
    "vulgarisation-informatique.fr",
    "wacha.punks.cc",
    "watch.44con.com",
    "watch.autonomous-zone.earth",
    "watch.breadtube.tv",
    "watch.deranalyst.ch",
    "watch.ignorance.eu",
    "watch.kcl.academy",
    "watch.libertaria.space",
    "watch.nuked.social",
    "watch.ocaml.org",
    "watch.rt4mn.org",
    "watch.snoot.tube",
    "watch.softinio.com",
    "watch.thelema.social",
    "watch.tubelab.video",
    "web-fellow.de",
    "webtv.vandoeuvre.net",
    "widemus.de",
    "wikileaks.video",
    "wirtube.de",
    "wiwi.video",
    "worldofvids.com",
    "wwtube.net",
    "www.aktion-nordost.tv",
    "www.birkeundnymphe.de",
    "www.free-streams.com",
    "www.kotikoff.net",
    "www.neptube.io",
    "www.orion-hub.fr",
    "www.rocaguinarda.tv",
    "www.videos-libr.es",
    "www.wiki-tube.de",
    "www.yiny.org",
    "www.zohup.net",
    "www4.mir.inter21.net",
    "xxivproduction.video",
    "xxx.noho.st",
    "xxxporn.co.uk",
    "y.melonia.se",
    "yhwh.tube",
    "yiny.org",
    "yoba.tv",
    "yt.x1337x.fr",
    "yunopeertube.myddns.me",
    "yutu.nl",
    "zapping.no-ip.info",
}

__all__ = ['instances']


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

import datetime
import re

from urllib.parse import urlencode

try:
    from .instances import instances
except ImportError:
    instances = ()

from ..common import SelfHostedInfoExtractor
from ...compat import functools
from ...utils import (
    OnDemandPagedList,
    get_first_group,
    int_or_none,
    parse_resolution,
    str_or_none,
    traverse_obj,
    try_get,
    unified_timestamp,
    url_or_none,
    urljoin,
    ExtractorError,
)

known_valid_instances = set()

class PeerTubeBaseIE(SelfHostedInfoExtractor):
    _UUID_RE = r'[\da-zA-Z]{22}|[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}'
    _SH_VALID_CONTENT_STRINGS = (
        '<title>PeerTube<',
        'There will be other non JS-based clients to access PeerTube',
        '>There are other non JS-based unofficial clients to access PeerTube',
        '>We are sorry but it seems that PeerTube is not compatible with your web browser.<',
        '<meta property="og:platform" content="PeerTube"',
    )
    _API_BASE = 'https://%s/api/v1/%s/%s/%s'
    _NETRC_MACHINE = 'peertube'
    _LOGIN_INFO = None
    _PAGE_SIZE = 25

    _HOSTNAME_GROUPS = ('host', 'host_2')
    _INSTANCE_LIST = instances
    _DYNAMIC_INSTANCE_LIST = known_valid_instances
    _NODEINFO_SOFTWARE = ('peertube', )
    _SOFTWARE_NAME = 'PeerTube'

    def _login(self):
        if self._LOGIN_INFO:
            ts = datetime.datetime.now().timestamp()
            if self._LOGIN_INFO['expires_on'] >= ts + 5:
                return True

        username, password = self._get_login_info()
        if not username:
            return None

        # the instance domain (the one where user has an account) must be separated from the user e-mail
        mobj = re.match(r'^(?P<username>[^@]+(?:@[^@]+)?)@(?P<instance>.+)$', username)
        if not mobj:
            self.report_warning(
                'Invalid login format - must be in format [username or email]@[instance]')
        username, instance = mobj.group('username', 'instance')

        oauth_keys = self._downloader.cache.load('peertube-oauth', instance)
        if not oauth_keys:
            oauth_keys = self._download_json(f'https://{instance}/api/v1/oauth-clients/local', instance, 'Downloading OAuth keys')
            self._downloader.cache.store('peertube-oauth', instance, oauth_keys)
        client_id, client_secret = oauth_keys['client_id'], oauth_keys['client_secret']

        auth_res = self._download_json(f'https://{instance}/api/v1/users/token', instance, 'Logging in', data=bytes(urlencode({
            'client_id': client_id,
            'client_secret': client_secret,
            'response_type': 'code',
            'grant_type': 'password',
            'scope': 'user',
            'username': username,
            'password': password,
        }).encode('utf-8')))

        ts = datetime.datetime.now().timestamp()
        auth_res['instance'] = instance
        auth_res['expires_on'] = ts + auth_res['expires_in']
        auth_res['refresh_token_expires_on'] = ts + auth_res['refresh_token_expires_in']
        # not using self to set the details to expose it to all peertube extractors
        PeerTubeBaseIE._LOGIN_INFO = auth_res

    @classmethod
    def _match_id_and_host(cls, url):
        mobj = cls._match_valid_url(url)
        host = get_first_group(mobj, 'host', 'host_2')
        video_id = mobj.group('id')
        return host, video_id

    def _call_api(self, host, resource, resource_id, path, note=None, errnote=None, fatal=True):
        return self._download_json(
            self._API_BASE % (host, resource, resource_id, path), resource_id,
            headers={
                'Authorization': f'Bearer {self._LOGIN_INFO["access_token"]}',
            } if self._LOGIN_INFO and self._LOGIN_INFO['instance'] == host else {},
            note=note, errnote=errnote, fatal=fatal)

    def _parse_video(self, video, url):
        host, display_id = self._match_id_and_host(url)
        info_dict = {}

        formats = []
        files = video.get('files') or []
        for playlist in (video.get('streamingPlaylists') or []):
            if not isinstance(playlist, dict):
                continue
            playlist_files = playlist.get('files')
            if not (playlist_files and isinstance(playlist_files, list)):
                continue
            files.extend(playlist_files)
        for file_ in files:
            if not isinstance(file_, dict):
                continue
            file_url = url_or_none(file_.get('fileUrl'))
            if not file_url:
                continue
            file_size = int_or_none(file_.get('size'))
            format_id = try_get(
                file_, lambda x: x['resolution']['label'], str)
            f = parse_resolution(format_id)
            f.update({
                'url': file_url,
                'format_id': format_id,
                'filesize': file_size,
            })
            if format_id == '0p':
                f['vcodec'] = 'none'
            else:
                f['fps'] = int_or_none(file_.get('fps'))
            formats.append(f)
            # ytdl-patched does not support BitTorrent for now
            # if file_.get('torrentDownloadUrl'):
            #     f = f.copy()
            #     f.update({
            #         'url': file_['torrentDownloadUrl'],
            #         'ext': determine_ext(file_url),
            #         'format_id': '%s-torrent' % format_id,
            #         'protocol': 'bittorrent',
            #     })
            #     formats.append(f)
        if files:
            self._sort_formats(formats)
            info_dict['formats'] = formats
        else:
            info_dict.update({
                '_type': 'url_transparent',
                'url': 'peertube:%s:%s' % (host, video['uuid']),
                'ie_key': 'PeerTube',
            })

        def data(section, field, type_):
            return traverse_obj(video, (section, field), expected_type=type_)

        def account_data(field, type_):
            return data('account', field, type_)

        def channel_data(field, type_):
            return data('channel', field, type_)

        category = data('category', 'label', str)
        categories = [category] if category else None

        age_limit = 18 if video.get('nsfw') else None

        webpage_url = 'https://%s/videos/watch/%s' % (host, display_id)

        info_dict.update({
            'id': video['uuid'],
            'title': video['name'],
            'description': video.get('description'),
            'thumbnail': urljoin(webpage_url, video.get('thumbnailPath')),
            'timestamp': unified_timestamp(video.get('publishedAt')),
            'uploader': account_data('displayName', str),
            'uploader_id': str_or_none(account_data('id', int)),
            'uploader_url': url_or_none(account_data('url', str)),
            'channel': channel_data('displayName', str),
            'channel_id': str_or_none(channel_data('id', int)),
            'channel_url': url_or_none(channel_data('url', str)),
            'language': data('language', 'id', str),
            'license': data('licence', 'label', str),
            'duration': int_or_none(video.get('duration')),
            'view_count': int_or_none(video.get('views')),
            'like_count': int_or_none(video.get('likes')),
            'dislike_count': int_or_none(video.get('dislikes')),
            'age_limit': age_limit,
            'tags': try_get(video, lambda x: x['tags'], list),
            'categories': categories,
        })
        return info_dict

class PeerTubeIE(PeerTubeBaseIE):
    _VALID_URL = r'''(?x)
        (?P<prefix>peertube:)?(?:
            (?P<host>[^:]+):|
            https?://(?P<host_2>[^/]+)/(?:videos/(?:watch|embed)|api/v\d/videos|w)/
        )
        (?P<id>%s)
    ''' % PeerTubeBaseIE._UUID_RE

    _EMBED_REGEX = [r'''(?x)<iframe[^>]+\bsrc=["\'](?P<url>(?:https?:)?//{_INSTANCES_RE}/videos/embed/{cls._UUID_RE})''']
    _TESTS = [{
        'url': 'https://framatube.org/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d',
        'md5': '8563064d245a4be5705bddb22bb00a28',
        'info_dict': {
            'id': '9c9de5e8-0a1e-484a-b099-e80766180a6d',
            'ext': 'mp4',
            'title': 'What is PeerTube?',
            'description': 'md5:3fefb8dde2b189186ce0719fda6f7b10',
            'thumbnail': r're:https?://.*\.(?:jpg|png)',
            'timestamp': 1538391166,
            'upload_date': '20181001',
            'uploader': 'Framasoft',
            'uploader_id': '3',
            'uploader_url': 'https://framatube.org/accounts/framasoft',
            'channel': 'A propos de PeerTube',
            'channel_id': '2215',
            'channel_url': 'https://framatube.org/video-channels/joinpeertube',
            'language': 'en',
            'license': 'Attribution - Share Alike',
            'duration': 113,
            'view_count': int,
            'like_count': int,
            'dislike_count': int,
            'tags': ['framasoft', 'peertube'],
            'categories': ['Science & Technology'],
        }
    }, {
        'url': 'https://peertube2.cpy.re/w/122d093a-1ede-43bd-bd34-59d2931ffc5e',
        'info_dict': {
            'id': '122d093a-1ede-43bd-bd34-59d2931ffc5e',
            'ext': 'mp4',
            'title': 'E2E tests',
            'uploader_id': '37855',
            'timestamp': 1589276219,
            'upload_date': '20200512',
            'uploader': 'chocobozzz',
        }
    }, {
        'url': 'https://peertube2.cpy.re/w/3fbif9S3WmtTP8gGsC5HBd',
        'info_dict': {
            'id': '3fbif9S3WmtTP8gGsC5HBd',
            'ext': 'mp4',
            'title': 'E2E tests',
            'uploader_id': '37855',
            'timestamp': 1589276219,
            'upload_date': '20200512',
            'uploader': 'chocobozzz',
        },
    }, {
        'url': 'https://peertube2.cpy.re/api/v1/videos/3fbif9S3WmtTP8gGsC5HBd',
        'info_dict': {
            'id': '3fbif9S3WmtTP8gGsC5HBd',
            'ext': 'mp4',
            'title': 'E2E tests',
            'uploader_id': '37855',
            'timestamp': 1589276219,
            'upload_date': '20200512',
            'uploader': 'chocobozzz',
        },
    }, {
        # Issue #26002
        'url': 'peertube:spacepub.space:d8943b2d-8280-497b-85ec-bc282ec2afdc',
        'info_dict': {
            'id': 'd8943b2d-8280-497b-85ec-bc282ec2afdc',
            'ext': 'mp4',
            'title': 'Dot matrix printer shell demo',
            'uploader_id': '3',
            'timestamp': 1587401293,
            'upload_date': '20200420',
            'uploader': 'Drew DeVault',
        }
    }, {
        # new url scheme since PeerTube 3.3
        'url': 'https://peertube2.cpy.re/w/3fbif9S3WmtTP8gGsC5HBd',
        'info_dict': {
            'id': '122d093a-1ede-43bd-bd34-59d2931ffc5e',
            'ext': 'mp4',
            'title': 'E2E tests',
            'uploader_id': '37855',
            'timestamp': 1589276219,
            'upload_date': '20200512',
            'uploader': 'chocobozzz',
        },
    }, {
        'url': 'https://peertube.debian.social/videos/watch/0b04f13d-1e18-4f1d-814e-4979aa7c9c44',
        'only_matching': True,
    }, {
        # nsfw
        'url': 'https://vod.ksite.de/videos/watch/9bb88cd3-9959-46d9-9ab9-33d2bb704c39',
        'only_matching': True,
    }, {
        'url': 'https://vod.ksite.de/videos/embed/fed67262-6edb-4d1c-833b-daa9085c71d7',
        'only_matching': True,
    }, {
        'url': 'peertube:framatube.org:b37a5b9f-e6b5-415c-b700-04a5cd6ec205',
        'only_matching': True,
    }, {
        'url': 'https://peertube2.cpy.re/w/122d093a-1ede-43bd-bd34-59d2931ffc5e',
        'only_matching': True,
    }, {
        'url': 'https://peertube2.cpy.re/api/v1/videos/3fbif9S3WmtTP8gGsC5HBd',
        'only_matching': True,
    }, {
        'url': 'peertube:peertube2.cpy.re:3fbif9S3WmtTP8gGsC5HBd',
        'only_matching': True,
    }, {
        'url': 'peertube:video.blender.org:b37a5b9f-e6b5-415c-b700-04a5cd6ec205',
        'only_matching': True,
    }]

    @staticmethod
    def _extract_peertube_url(webpage, source_url):
        mobj = re.match(
            r'https?://(?P<host>[^/]+)/(?:videos/(?:watch|embed)|w)/(?P<id>%s)'
            % PeerTubeIE._UUID_RE, source_url)
        if mobj and any(p in webpage for p in PeerTubeIE._SH_VALID_CONTENT_STRINGS):
            return 'peertube:%s:%s' % mobj.group('host', 'id')

    @classmethod
    def _extract_embed_urls(cls, url, webpage):
        embeds = tuple(super()._extract_embed_urls(url, webpage))
        if embeds:
            return embeds

        peertube_url = cls._extract_peertube_url(webpage, url)
        if peertube_url:
            return [peertube_url]

    def _get_subtitles(self, host, video_id):
        captions = self._call_api(
            host, video_id, 'captions', note='Downloading captions JSON',
            fatal=False)
        if not isinstance(captions, dict):
            return
        data = captions.get('data')
        if not isinstance(data, list):
            return
        subtitles = {}
        for e in data:
            language_id = try_get(e, lambda x: x['language']['id'], str)
            caption_url = urljoin('https://%s' % host, e.get('captionPath'))
            if not caption_url:
                continue
            subtitles.setdefault(language_id or 'en', []).append({
                'url': caption_url,
            })
        return subtitles

    def _real_extract(self, url):
        host, video_id = self._match_id_and_host(url)

        if self._LOGIN_INFO and self._LOGIN_INFO['instance'] != host:
            video_search = self._call_api(
                self._LOGIN_INFO['instance'], 'search', 'videos', '?' + urlencode({
                    'search': f'https://{host}/videos/watch/{video_id}',
                }), note='Searching for remote video')
            if len(video_search) == 0:
                raise ExtractorError('Remote video not found')
            host, video_id = self._LOGIN_INFO['instance'], video_search['data'][0]['uuid']

        video = self._call_api(
            host, 'videos', video_id, '', note='Downloading video JSON')

        info_dict = self._parse_video(video, url)

        info_dict['subtitles'] = self.extract_subtitles(host, video_id)

        description = None
        if url.startswith('http'):
            webpage = self._download_webpage(url, video_id, fatal=False) or None
            description = self._og_search_description(webpage, default=None)
        if not description:
            full_description = self._call_api(
                host, 'videos', video_id, 'description', note='Downloading description JSON',
                fatal=False)
            description = str_or_none(traverse_obj(full_description, 'description'))
        if not description:
            description = video.get('description')
        info_dict['description'] = description

        return info_dict

class PeerTubePlaylistIE(PeerTubeBaseIE):
    _VALID_URL = r'''(?x)
        (?P<prefix>peertube:playlist:)?(?:
            (?P<host>[^:]+):|
            https?://(?P<host_2>[^/]+)/(?:videos/(?:watch|embed)/playlist|api/v\d/video-playlists|w/p)/
        )
        (?P<id>%s)
    ''' % PeerTubeBaseIE._UUID_RE

    _TESTS = [{
        'url': 'https://peertube.debian.social/w/p/hFdJoTuyhNJVa1cDWd1d12',
        'info_dict': {
            'id': 'hFdJoTuyhNJVa1cDWd1d12',
            'description': 'Diversas palestras do Richard Stallman no Brasil.',
            'title': 'Richard Stallman no Brasil',
            'timestamp': 1599676222,
        },
        'playlist_mincount': 9,
    }, {
        'url': 'https://video.internet-czas-dzialac.pl/videos/watch/playlist/3c81b894-acde-4539-91a2-1748b208c14c?playlistPosition=1',
        'info_dict': {
            'id': '3c81b894-acde-4539-91a2-1748b208c14c',
            'title': 'Podcast Internet. Czas Działać!',
            'uploader_id': 3,
            'uploader': 'Internet. Czas działać!',
        },
        'playlist_mincount': 14,
    }, {
        'url': 'https://peertube2.cpy.re/w/p/hrAdcvjkMMkHJ28upnoN21',
        'only_matching': True,
    }]

    def _entries(self, url, host, display_id, page):
        videos = self._call_api(
            host, 'video-playlists', display_id,
            f'videos?start={page * self._PAGE_SIZE}&count={self._PAGE_SIZE}',
            note=f'Downloading playlist video list (page #{page})')
        yield from (self._parse_video(video, url) for video in videos['data'])

    def _real_extract(self, url):
        host, display_id = self._match_id_and_host(url)

        playlist_data = self._call_api(host, 'video-playlists', display_id, '', 'Downloading playlist metadata')
        entries = OnDemandPagedList(
            functools.partial(self._entries, url, host, display_id),
            self._PAGE_SIZE)

        return {
            '_type': 'playlist',
            'entries': entries,
            'id': playlist_data.get('uuid'),
            'title': playlist_data.get('displayName'),
            'description': playlist_data.get('description'),

            'channel': traverse_obj(playlist_data, ('videoChannel', 'displayName')),
            'channel_id': traverse_obj(playlist_data, ('videoChannel', 'id')),
            'channel_url': traverse_obj(playlist_data, ('videoChannel', 'url')),

            'uploader': traverse_obj(playlist_data, ('ownerAccount', 'displayName')),
            'uploader_id': traverse_obj(playlist_data, ('ownerAccount', 'displayName')),
            'uploader_url': traverse_obj(playlist_data, ('ownerAccount', 'displayName')),
        }

class PeerTubeChannelIE(PeerTubeBaseIE):
    _VALID_URL = r'''(?x)
        (?P<prefix>peertube:channel:)?(?:
            (?P<host>[^:]+):|
            https?://(?P<host_2>[^/]+)/(?:(?:api/v\d/)?video-channels|c)/
        )
        (?P<id>[^/?#]+)(?:/videos)?
    '''

    _TESTS = [{
        'url': 'https://video.internet-czas-dzialac.pl/video-channels/internet_czas_dzialac/videos',
        'info_dict': {
            'id': '2',
            'title': 'Internet. Czas działać!',
            'description': 'md5:ac35d70f6625b04b189e0b4b76e62e17',
            'uploader_id': 3,
            'uploader': 'Internet. Czas działać!',
        },
        'playlist_mincount': 14,
    }, {
        'url': 'https://framatube.org/c/bf54d359-cfad-4935-9d45-9d6be93f63e8/videos',
        'info_dict': {
            'id': 'bf54d359-cfad-4935-9d45-9d6be93f63e8',
            'timestamp': 1519917377,
            'title': 'Les vidéos de Framasoft',
        },
        'playlist_mincount': 345,
    }, {
        'url': 'https://peertube2.cpy.re/c/blender_open_movies@video.blender.org/videos',
        'info_dict': {
            'id': 'blender_open_movies@video.blender.org',
            'timestamp': 1542287810,
            'title': 'Official Blender Open Movies',
        },
        'playlist_mincount': 11,
    }, {
        'url': 'https://video.internet-czas-dzialac.pl/c/internet_czas_dzialac',
        'only_matching': True,
    }]

    def _entries(self, url, host, display_id, page):
        videos = self._call_api(
            host, 'video-channels', display_id,
            f'videos?start={page * self._PAGE_SIZE}&count={self._PAGE_SIZE}&sort=publishedAt',
            note=f'Downloading channel video list (page #{page})')
        yield from (self._parse_video(video, url) for video in videos['data'])

    def _real_extract(self, url):
        host, display_id = self._match_id_and_host(url)

        channel_data = self._call_api(host, 'video-channels', display_id, '', 'Downloading channel metadata')
        entries = OnDemandPagedList(
            functools.partial(self._entries, url, host, display_id),
            self._PAGE_SIZE)

        return {
            '_type': 'playlist',
            'entries': entries,
            'id': str_or_none(channel_data.get('id')),
            'title': channel_data.get('displayName'),
            'display_id': channel_data.get('name'),
            'description': channel_data.get('description'),

            'channel': channel_data.get('displayName'),
            'channel_id': channel_data.get('id'),
            'channel_url': channel_data.get('url'),

            'uploader': traverse_obj(channel_data, ('ownerAccount', 'displayName')),
            'uploader_id': traverse_obj(channel_data, ('ownerAccount', 'displayName')),
            'uploader_url': traverse_obj(channel_data, ('ownerAccount', 'displayName')),
        }

class PeerTubeAccountIE(PeerTubeBaseIE):
    _VALID_URL = r'''(?x)
        (?P<prefix>peertube:account:)?(?:
            (?P<host>[^:]+):|
            https?://(?P<host_2>[^/]+)/(?:(?:api/v\d/)?accounts|a)/
        )
        (?P<id>[^/?#]+)(?:/video(?:s|-channels))?
    '''

    _TESTS = [{
        'url': 'https://video.internet-czas-dzialac.pl/accounts/icd/video-channels',
        'info_dict': {
            'id': '3',
            'description': 'md5:ac35d70f6625b04b189e0b4b76e62e17',
            'uploader': 'Internet. Czas działać!',
            'title': 'Internet. Czas działać!',
            'uploader_id': 3,
        },
        'playlist_mincount': 14,
    }, {
        'url': 'https://peertube2.cpy.re/a/chocobozzz/videos',
        'info_dict': {
            'id': 'chocobozzz',
            'timestamp': 1553874564,
            'title': 'chocobozzz',
        },
        'playlist_mincount': 2,
    }, {
        'url': 'https://video.internet-czas-dzialac.pl/a/icd',
        'only_matching': True,
    }]

    def _entries(self, url, host, display_id, page):
        videos = self._call_api(
            host, 'accounts', display_id,
            f'videos?start={page * self._PAGE_SIZE}&count={self._PAGE_SIZE}&sort=publishedAt',
            note=f'Downloading account video list (page #{page})')
        yield from (self._parse_video(video, url) for video in videos['data'])

    def _real_extract(self, url):
        host, display_id = self._match_id_and_host(url)

        account_data = self._call_api(host, 'accounts', display_id, '', 'Downloading account metadata')
        entries = OnDemandPagedList(
            functools.partial(self._entries, url, host, display_id),
            self._PAGE_SIZE)

        return {
            '_type': 'playlist',
            'entries': entries,
            'id': str_or_none(account_data.get('id')),
            'title': account_data.get('displayName'),
            'display_id': account_data.get('name'),
            'description': account_data.get('description'),
            'uploader': account_data.get('displayName'),
            'uploader_id': account_data.get('id'),
            'uploader_url': account_data.get('url'),
        }


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

class PeerTVIE(InfoExtractor):
    IE_NAME = 'peer.tv'
    _VALID_URL = r'https?://(?:www\.)?peer\.tv/(?:de|it|en)/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.peer.tv/de/841',
        'info_dict': {
            'id': '841',
            'ext': 'mp4',
            'title': 'Die Brunnenburg',
            'description': 'md5:4395f6142b090338340ab88a3aae24ed',
        },
    }, {
        'url': 'https://www.peer.tv/it/404',
        'info_dict': {
            'id': '404',
            'ext': 'mp4',
            'title': 'Cascate di ghiaccio in Val Gardena',
            'description': 'md5:e8e5907f236171842674e8090e3577b8',
        },
    }]

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

        video_key = self._html_search_regex(r'player\.peer\.tv/js/([a-zA-Z0-9]+)', webpage, 'video key')

        js = self._download_webpage(f'https://player.peer.tv/js/{video_key}/', video_id,
                                    headers={'Referer': 'https://www.peer.tv/'}, note='Downloading session id')

        session_id = self._search_regex(r'["\']session_id["\']:\s*["\']([a-zA-Z0-9]+)["\']', js, 'session id')

        player_webpage = self._download_webpage(
            f'https://player.peer.tv/jsc/{video_key}/{session_id}?jsr=aHR0cHM6Ly93d3cucGVlci50di9kZS84NDE=&cs=UTF-8&mq=2&ua=0&webm=p&mp4=p&hls=1',
            video_id, note='Downloading player webpage')

        m3u8_url = self._search_regex(r'["\']playlist_url["\']:\s*(["\'][^"\']+["\'])', player_webpage, 'm3u8 url')
        m3u8_url = self._parse_json(m3u8_url, video_id, transform_source=js_to_json)

        formats = self._extract_m3u8_formats(m3u8_url, video_id, m3u8_id='hls')

        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': self._html_search_regex(r'<h1>(.+?)</h1>', webpage, 'title').replace('\xa0', ' '),
            'formats': formats,
            'description': self._html_search_meta(('og:description', 'description'), webpage),
            'thumbnail': self._html_search_meta(('og:image', 'image'), webpage)
        }


code:
import json
import re
import urllib.parse

from .common import InfoExtractor
from ..compat import compat_HTTPError
from ..utils import (
    ExtractorError,
    float_or_none,
    str_or_none,
    traverse_obj,
    url_or_none,
)

class PelotonIE(InfoExtractor):
    IE_NAME = 'peloton'
    _NETRC_MACHINE = 'peloton'
    _VALID_URL = r'https?://members\.onepeloton\.com/classes/player/(?P<id>[a-f0-9]+)'
    _TESTS = [{
        'url': 'https://members.onepeloton.com/classes/player/0e9653eb53544eeb881298c8d7a87b86',
        'info_dict': {
            'id': '0e9653eb53544eeb881298c8d7a87b86',
            'title': '20 min Chest & Back Strength',
            'ext': 'mp4',
            'thumbnail': r're:^https?://.+\.jpg',
            'description': 'md5:fcd5be9b9eda0194b470e13219050a66',
            'creator': 'Chase Tucker',
            'release_timestamp': 1556141400,
            'timestamp': 1556141400,
            'upload_date': '20190424',
            'duration': 1389,
            'categories': ['Strength'],
            'tags': ['Workout Mat', 'Light Weights', 'Medium Weights'],
            'is_live': False,
            'chapters': 'count:1',
            'subtitles': {'en': [{
                'url': r're:^https?://.+',
                'ext': 'vtt'
            }]},
        }, 'params': {
            'skip_download': 'm3u8',
        },
        '_skip': 'Account needed'
    }, {
        'url': 'https://members.onepeloton.com/classes/player/26603d53d6bb4de1b340514864a6a6a8',
        'info_dict': {
            'id': '26603d53d6bb4de1b340514864a6a6a8',
            'title': '30 min Earth Day Run',
            'ext': 'm4a',
            'thumbnail': r're:https://.+\.jpg',
            'description': 'md5:adc065a073934d7ee0475d217afe0c3d',
            'creator': 'Selena Samuela',
            'release_timestamp': 1587567600,
            'timestamp': 1587567600,
            'upload_date': '20200422',
            'duration': 1802,
            'categories': ['Running'],
            'is_live': False,
            'chapters': 'count:3'
        }, 'params': {
            'skip_download': 'm3u8',
        },
        '_skip': 'Account needed'
    }]

    _MANIFEST_URL_TEMPLATE = '%s?hdnea=%s'

    def _start_session(self, video_id):
        self._download_webpage('https://api.onepeloton.com/api/started_client_session', video_id, note='Starting session')

    def _login(self, video_id):
        username, password = self._get_login_info()
        if not (username and password):
            self.raise_login_required()
        try:
            self._download_json(
                'https://api.onepeloton.com/auth/login', video_id, note='Logging in',
                data=json.dumps({
                    'username_or_email': username,
                    'password': password,
                    'with_pubsub': False
                }).encode(),
                headers={'Content-Type': 'application/json', 'User-Agent': 'web'})
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                json_string = self._webpage_read_content(e.cause, None, video_id)
                res = self._parse_json(json_string, video_id)
                raise ExtractorError(res['message'], expected=res['message'] == 'Login failed')
            else:
                raise

    def _get_token(self, video_id):
        try:
            subscription = self._download_json(
                'https://api.onepeloton.com/api/subscription/stream', video_id, note='Downloading token',
                data=json.dumps({}).encode(), headers={'Content-Type': 'application/json'})
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
                json_string = self._webpage_read_content(e.cause, None, video_id)
                res = self._parse_json(json_string, video_id)
                raise ExtractorError(res['message'], expected=res['message'] == 'Stream limit reached')
            else:
                raise
        return subscription['token']

    def _real_extract(self, url):
        video_id = self._match_id(url)
        try:
            self._start_session(video_id)
        except ExtractorError as e:
            if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
                self._login(video_id)
                self._start_session(video_id)
            else:
                raise

        metadata = self._download_json('https://api.onepeloton.com/api/ride/%s/details?stream_source=multichannel' % video_id, video_id)
        ride_data = metadata.get('ride')
        if not ride_data:
            raise ExtractorError('Missing stream metadata')
        token = self._get_token(video_id)

        is_live = False
        if ride_data.get('content_format') == 'audio':
            url = self._MANIFEST_URL_TEMPLATE % (ride_data.get('vod_stream_url'), urllib.parse.quote(token))
            formats = [{
                'url': url,
                'ext': 'm4a',
                'format_id': 'audio',
                'vcodec': 'none',
            }]
            subtitles = {}
        else:
            if ride_data.get('vod_stream_url'):
                url = 'https://members.onepeloton.com/.netlify/functions/m3u8-proxy?displayLanguage=en&acceptedSubtitles=%s&url=%s?hdnea=%s' % (
                    ','.join([re.sub('^([a-z]+)-([A-Z]+)$', r'\1', caption) for caption in ride_data['captions']]),
                    ride_data['vod_stream_url'],
                    urllib.parse.quote(urllib.parse.quote(token)))
            elif ride_data.get('live_stream_url'):
                url = self._MANIFEST_URL_TEMPLATE % (ride_data.get('live_stream_url'), urllib.parse.quote(token))
                is_live = True
            else:
                raise ExtractorError('Missing video URL')
            formats, subtitles = self._extract_m3u8_formats_and_subtitles(url, video_id, 'mp4')

        if metadata.get('instructor_cues'):
            subtitles['cues'] = [{
                'data': json.dumps(metadata.get('instructor_cues')),
                'ext': 'json'
            }]

        category = ride_data.get('fitness_discipline_display_name')
        chapters = [{
            'start_time': segment.get('start_time_offset'),
            'end_time': segment.get('start_time_offset') + segment.get('length'),
            'title': segment.get('name')
        } for segment in traverse_obj(metadata, ('segments', 'segment_list'))]

        self._sort_formats(formats)
        return {
            'id': video_id,
            'title': ride_data.get('title'),
            'formats': formats,
            'thumbnail': url_or_none(ride_data.get('image_url')),
            'description': str_or_none(ride_data.get('description')),
            'creator': traverse_obj(ride_data, ('instructor', 'name')),
            'release_timestamp': ride_data.get('original_air_time'),
            'timestamp': ride_data.get('original_air_time'),
            'subtitles': subtitles,
            'duration': float_or_none(ride_data.get('length')),
            'categories': [category] if category else None,
            'tags': traverse_obj(ride_data, ('equipment_tags', ..., 'name')),
            'is_live': is_live,
            'chapters': chapters
        }

class PelotonLiveIE(InfoExtractor):
    IE_NAME = 'peloton:live'
    IE_DESC = 'Peloton Live'
    _VALID_URL = r'https?://members\.onepeloton\.com/player/live/(?P<id>[a-f0-9]+)'
    _TEST = {
        'url': 'https://members.onepeloton.com/player/live/eedee2d19f804a9788f53aa8bd38eb1b',
        'info_dict': {
            'id': '32edc92d28044be5bf6c7b6f1f8d1cbc',
            'title': '30 min HIIT Ride: Live from Home',
            'ext': 'mp4',
            'thumbnail': r're:^https?://.+\.png',
            'description': 'md5:f0d7d8ed3f901b7ee3f62c1671c15817',
            'creator': 'Alex Toussaint',
            'release_timestamp': 1587736620,
            'timestamp': 1587736620,
            'upload_date': '20200424',
            'duration': 2014,
            'categories': ['Cycling'],
            'is_live': False,
            'chapters': 'count:3'
        },
        'params': {
            'skip_download': 'm3u8',
        },
        '_skip': 'Account needed'
    }

    def _real_extract(self, url):
        workout_id = self._match_id(url)
        peloton = self._download_json(f'https://api.onepeloton.com/api/peloton/{workout_id}', workout_id)

        if peloton.get('ride_id'):
            if not peloton.get('is_live') or peloton.get('is_encore') or peloton.get('status') != 'PRE_START':
                return self.url_result('https://members.onepeloton.com/classes/player/%s' % peloton['ride_id'])
            else:
                raise ExtractorError('Ride has not started', expected=True)
        else:
            raise ExtractorError('Missing video ID')


code:
from .common import InfoExtractor

class PeopleIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?people\.com/people/videos/0,,(?P<id>\d+),00\.html'

    _TEST = {
        'url': 'http://www.people.com/people/videos/0,,20995451,00.html',
        'info_dict': {
            'id': 'ref:20995451',
            'ext': 'mp4',
            'title': 'Astronaut Love Triangle Victim Speaks Out: “The Crime in 2007 Hasn’t Defined Us”',
            'description': 'Colleen Shipman speaks to PEOPLE for the first time about life after the attack',
            'thumbnail': r're:^https?://.*\.jpg',
            'duration': 246.318,
            'timestamp': 1458720585,
            'upload_date': '20160323',
            'uploader_id': '416418724',
        },
        'params': {
            'skip_download': True,
        },
        'add_ie': ['BrightcoveNew'],
    }

    def _real_extract(self, url):
        return self.url_result(
            'http://players.brightcove.net/416418724/default_default/index.html?videoId=ref:%s'
            % self._match_id(url), 'BrightcoveNew')


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

class PerformGroupIE(InfoExtractor):
    _VALID_URL = r'https?://player\.performgroup\.com/eplayer(?:/eplayer\.html|\.js)#/?(?P<id>[0-9a-f]{26})\.(?P<auth_token>[0-9a-z]{26})'
    _TESTS = [{
        # http://www.faz.net/aktuell/sport/fussball/wm-2018-playoffs-schweiz-besiegt-nordirland-1-0-15286104.html
        'url': 'http://player.performgroup.com/eplayer/eplayer.html#d478c41c5d192f56b9aa859de8.1w4crrej5w14e1ed4s1ce4ykab',
        'md5': '259cb03d142e2e52471e8837ecacb29f',
        'info_dict': {
            'id': 'xgrwobuzumes1lwjxtcdpwgxd',
            'ext': 'mp4',
            'title': 'Liga MX: Keine Einsicht nach Horrorfoul',
            'description': 'md5:7cd3b459c82725b021e046ab10bf1c5b',
            'timestamp': 1511533477,
            'upload_date': '20171124',
        }
    }]

    def _call_api(self, service, auth_token, content_id, referer_url):
        return self._download_json(
            'http://ep3.performfeeds.com/ep%s/%s/%s/' % (service, auth_token, content_id),
            content_id, headers={
                'Referer': referer_url,
                'Origin': 'http://player.performgroup.com',
            }, query={
                '_fmt': 'json',
            })

    def _real_extract(self, url):
        player_id, auth_token = self._match_valid_url(url).groups()
        bootstrap = self._call_api('bootstrap', auth_token, player_id, url)
        video = bootstrap['config']['dataSource']['sourceItems'][0]['videos'][0]
        video_id = video['uuid']
        vod = self._call_api('vod', auth_token, video_id, url)
        media = vod['videos']['video'][0]['media']

        formats = []
        hls_url = media.get('hls', {}).get('url')
        if hls_url:
            formats.extend(self._extract_m3u8_formats(hls_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))

        hds_url = media.get('hds', {}).get('url')
        if hds_url:
            formats.extend(self._extract_f4m_formats(hds_url + '?hdcore', video_id, f4m_id='hds', fatal=False))

        for c in media.get('content', []):
            c_url = c.get('url')
            if not c_url:
                continue
            tbr = int_or_none(c.get('bitrate'), 1000)
            format_id = 'http'
            if tbr:
                format_id += '-%d' % tbr
            formats.append({
                'format_id': format_id,
                'url': c_url,
                'tbr': tbr,
                'width': int_or_none(c.get('width')),
                'height': int_or_none(c.get('height')),
                'filesize': int_or_none(c.get('fileSize')),
                'vcodec': c.get('type'),
                'fps': int_or_none(c.get('videoFrameRate')),
                'vbr': int_or_none(c.get('videoRate'), 1000),
                'abr': int_or_none(c.get('audioRate'), 1000),
            })
        self._sort_formats(formats)

        return {
            'id': video_id,
            'title': video['title'],
            'description': video.get('description'),
            'thumbnail': video.get('poster'),
            'duration': int_or_none(video.get('duration')),
            'timestamp': int_or_none(video.get('publishedTime'), 1000),
            'formats': formats,
        }


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

class PeriscopeBaseIE(InfoExtractor):
    _M3U8_HEADERS = {
        'Referer': 'https://www.periscope.tv/'
    }

    def _call_api(self, method, query, item_id):
        return self._download_json(
            'https://api.periscope.tv/api/v2/%s' % method,
            item_id, query=query)

    def _parse_broadcast_data(self, broadcast, video_id):
        title = broadcast.get('status') or 'Periscope Broadcast'
        uploader = broadcast.get('user_display_name') or broadcast.get('username')
        title = '%s - %s' % (uploader, title) if uploader else title
        is_live = broadcast.get('state').lower() == 'running'

        thumbnails = [{
            'url': broadcast[image],
        } for image in ('image_url', 'image_url_small') if broadcast.get(image)]

        return {
            'id': broadcast.get('id') or video_id,
            'title': title,
            'timestamp': parse_iso8601(broadcast.get('created_at')),
            'uploader': uploader,
            'uploader_id': broadcast.get('user_id') or broadcast.get('username'),
            'thumbnails': thumbnails,
            'view_count': int_or_none(broadcast.get('total_watched')),
            'tags': broadcast.get('tags'),
            'is_live': is_live,
        }

    @staticmethod
    def _extract_common_format_info(broadcast):
        return broadcast.get('state').lower(), int_or_none(broadcast.get('width')), int_or_none(broadcast.get('height'))

    @staticmethod
    def _add_width_and_height(f, width, height):
        for key, val in (('width', width), ('height', height)):
            if not f.get(key):
                f[key] = val

    def _extract_pscp_m3u8_formats(self, m3u8_url, video_id, format_id, state, width, height, fatal=True):
        m3u8_formats = self._extract_m3u8_formats(
            m3u8_url, video_id, 'mp4',
            entry_protocol='m3u8_native'
            if state in ('ended', 'timed_out') else 'm3u8',
            m3u8_id=format_id, fatal=fatal, headers=self._M3U8_HEADERS)
        if len(m3u8_formats) == 1:
            self._add_width_and_height(m3u8_formats[0], width, height)
        for f in m3u8_formats:
            f.setdefault('http_headers', {}).update(self._M3U8_HEADERS)
        return m3u8_formats

class PeriscopeIE(PeriscopeBaseIE):
    IE_DESC = 'Periscope'
    IE_NAME = 'periscope'
    _VALID_URL = r'https?://(?:www\.)?(?:periscope|pscp)\.tv/[^/]+/(?P<id>[^/?#]+)'
    _EMBED_REGEX = [r'<iframe[^>]+src=([\'"])(?P<url>(?:https?:)?//(?:www\.)?(?:periscope|pscp)\.tv/(?:(?!\1).)+)\1']
    # Alive example URLs can be found here https://www.periscope.tv/
    _TESTS = [{
        'url': 'https://www.periscope.tv/w/aJUQnjY3MjA3ODF8NTYxMDIyMDl2zCg2pECBgwTqRpQuQD352EMPTKQjT4uqlM3cgWFA-g==',
        'md5': '65b57957972e503fcbbaeed8f4fa04ca',
        'info_dict': {
            'id': '56102209',
            'ext': 'mp4',
            'title': 'Bec Boop - 🚠✈🇬🇧 Fly above #London in Emirates Air Line cable car at night 🇬🇧✈🚠 #BoopScope 🎀💗',
            'timestamp': 1438978559,
            'upload_date': '20150807',
            'uploader': 'Bec Boop',
            'uploader_id': '1465763',
        },
        'skip': 'Expires in 24 hours',
    }, {
        'url': 'https://www.periscope.tv/w/1ZkKzPbMVggJv',
        'only_matching': True,
    }, {
        'url': 'https://www.periscope.tv/bastaakanoggano/1OdKrlkZZjOJX',
        'only_matching': True,
    }, {
        'url': 'https://www.periscope.tv/w/1ZkKzPbMVggJv',
        'only_matching': True,
    }]

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

        stream = self._call_api(
            'accessVideoPublic', {'broadcast_id': token}, token)

        broadcast = stream['broadcast']
        info = self._parse_broadcast_data(broadcast, token)

        state = broadcast.get('state').lower()
        width = int_or_none(broadcast.get('width'))
        height = int_or_none(broadcast.get('height'))

        def add_width_and_height(f):
            for key, val in (('width', width), ('height', height)):
                if not f.get(key):
                    f[key] = val

        video_urls = set()
        formats = []
        for format_id in ('replay', 'rtmp', 'hls', 'https_hls', 'lhls', 'lhlsweb'):
            video_url = stream.get(format_id + '_url')
            if not video_url or video_url in video_urls:
                continue
            video_urls.add(video_url)
            if format_id != 'rtmp':
                m3u8_formats = self._extract_pscp_m3u8_formats(
                    video_url, token, format_id, state, width, height, False)
                formats.extend(m3u8_formats)
                continue
            rtmp_format = {
                'url': video_url,
                'ext': 'flv' if format_id == 'rtmp' else 'mp4',
            }
            self._add_width_and_height(rtmp_format)
            formats.append(rtmp_format)
        self._sort_formats(formats)

        info['formats'] = formats
        return info

class PeriscopeUserIE(PeriscopeBaseIE):
    _VALID_URL = r'https?://(?:www\.)?(?:periscope|pscp)\.tv/(?P<id>[^/]+)/?$'
    IE_DESC = 'Periscope user videos'
    IE_NAME = 'periscope:user'

    _TEST = {
        'url': 'https://www.periscope.tv/LularoeHusbandMike/',
        'info_dict': {
            'id': 'LularoeHusbandMike',
            'title': 'LULAROE HUSBAND MIKE',
            'description': 'md5:6cf4ec8047768098da58e446e82c82f0',
        },
        # Periscope only shows videos in the last 24 hours, so it's possible to
        # get 0 videos
        'playlist_mincount': 0,
    }

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

        webpage = self._download_webpage(url, user_name)

        data_store = self._parse_json(
            unescapeHTML(self._search_regex(
                r'data-store=(["\'])(?P<data>.+?)\1',
                webpage, 'data store', default='{}', group='data')),
            user_name)

        user = list(data_store['UserCache']['users'].values())[0]['user']
        user_id = user['id']
        session_id = data_store['SessionToken']['public']['broadcastHistory']['token']['session_id']

        broadcasts = self._call_api(
            'getUserBroadcastsPublic',
            {'user_id': user_id, 'session_id': session_id},
            user_name)['broadcasts']

        broadcast_ids = [
            broadcast['id'] for broadcast in broadcasts if broadcast.get('id')]

        title = user.get('display_name') or user.get('username') or user_name
        description = user.get('description')

        entries = [
            self.url_result(
                'https://www.periscope.tv/%s/%s' % (user_name, broadcast_id))
            for broadcast_id in broadcast_ids]

        return self.playlist_result(entries, user_id, title, description)


code:
from .common import InfoExtractor
from ..compat import compat_str
from ..utils import try_get

class PhilharmonieDeParisIE(InfoExtractor):
    IE_DESC = 'Philharmonie de Paris'
    _VALID_URL = r'''(?x)
                    https?://
                        (?:
                            live\.philharmoniedeparis\.fr/(?:[Cc]oncert/|embed(?:app)?/|misc/Playlist\.ashx\?id=)|
                            pad\.philharmoniedeparis\.fr/(?:doc/CIMU/|player\.aspx\?id=)|
                            philharmoniedeparis\.fr/fr/live/concert/|
                            otoplayer\.philharmoniedeparis\.fr/fr/embed/
                        )
                        (?P<id>\d+)
                    '''
    _TESTS = [{
        'url': 'https://philharmoniedeparis.fr/fr/live/concert/1129666-danses-symphoniques',
        'md5': '24bdb7e86c200c107680e1f7770330ae',
        'info_dict': {
            'id': '1129666',
            'ext': 'mp4',
            'title': 'Danses symphoniques. Orchestre symphonique Divertimento - Zahia Ziouani. Bizet, de Falla, Stravinski, Moussorgski, Saint-Saëns',
        },
    }, {
        'url': 'https://philharmoniedeparis.fr/fr/live/concert/1032066-akademie-fur-alte-musik-berlin-rias-kammerchor-rene-jacobs-passion-selon-saint-jean-de-johann',
        'info_dict': {
            'id': '1032066',
            'title': 'Akademie für alte Musik Berlin, Rias Kammerchor, René Jacobs : Passion selon saint Jean de Johann Sebastian Bach',
        },
        'playlist_mincount': 2,
    }, {
        'url': 'https://philharmoniedeparis.fr/fr/live/concert/1030324-orchestre-philharmonique-de-radio-france-myung-whun-chung-renaud-capucon-pascal-dusapin-johannes',
        'only_matching': True,
    }, {
        'url': 'http://live.philharmoniedeparis.fr/misc/Playlist.ashx?id=1030324&track=&lang=fr',
        'only_matching': True,
    }, {
        'url': 'https://live.philharmoniedeparis.fr/embedapp/1098406/berlioz-fantastique-lelio-les-siecles-national-youth-choir-of.html?lang=fr-FR',
        'only_matching': True,
    }, {
        'url': 'https://otoplayer.philharmoniedeparis.fr/fr/embed/1098406?lang=fr-FR',
        'only_matching': True,
    }]

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

        config = self._download_json(
            'https://otoplayer.philharmoniedeparis.fr/fr/config/%s.json' % video_id, video_id, query={
                'id': video_id,
                'lang': 'fr-FR',
            })

        def extract_entry(source):
            if not isinstance(source, dict):
                return
            title = source.get('title')
            if not title:
                return
            files = source.get('files')
            if not isinstance(files, dict):
                return
            format_urls = set()
            formats = []
            for format_id in ('mobile', 'desktop'):
                format_url = try_get(
                    files, lambda x: x[format_id]['file'], compat_str)
                if not format_url or format_url in format_urls:
                    continue
                format_urls.add(format_url)
                formats.extend(self._extract_m3u8_formats(
                    format_url, video_id, 'mp4', entry_protocol='m3u8_native',
                    m3u8_id='hls', fatal=False))
            if not formats and not self.get_param('ignore_no_formats'):
                return
            self._sort_formats(formats)
            return {
                'title': title,
                'formats': formats,
                'thumbnail': files.get('thumbnail'),
            }
        info = extract_entry(config)
        if info:
            info.update({
                'id': video_id,
            })
            return info
        entries = []
        for num, chapter in enumerate(config['chapters'], start=1):
            entry = extract_entry(chapter)
            if entry is None:
                continue
            entry['id'] = '%s-%d' % (video_id, num)
            entries.append(entry)

        return self.playlist_result(entries, video_id, config.get('title'))


code:
import re

from .youtube import YoutubeIE
from .zdf import ZDFBaseIE
from ..compat import compat_str
from ..utils import (
    int_or_none,
    merge_dicts,
    try_get,
    unified_timestamp,
    urljoin,
)

class PhoenixIE(ZDFBaseIE):
    IE_NAME = 'phoenix.de'
    _VALID_URL = r'https?://(?:www\.)?phoenix\.de/(?:[^/]+/)*[^/?#&]*-a-(?P<id>\d+)\.html'
    _TESTS = [{
        # Same as https://www.zdf.de/politik/phoenix-sendungen/wohin-fuehrt-der-protest-in-der-pandemie-100.html
        'url': 'https://www.phoenix.de/sendungen/ereignisse/corona-nachgehakt/wohin-fuehrt-der-protest-in-der-pandemie-a-2050630.html',
        'md5': '34ec321e7eb34231fd88616c65c92db0',
        'info_dict': {
            'id': '210222_phx_nachgehakt_corona_protest',
            'ext': 'mp4',
            'title': 'Wohin führt der Protest in der Pandemie?',
            'description': 'md5:7d643fe7f565e53a24aac036b2122fbd',
            'duration': 1691,
            'timestamp': 1613902500,
            'upload_date': '20210221',
            'uploader': 'Phoenix',
            'series': 'corona nachgehakt',
            'episode': 'Wohin führt der Protest in der Pandemie?',
        },
    }, {
        # Youtube embed
        'url': 'https://www.phoenix.de/sendungen/gespraeche/phoenix-streitgut-brennglas-corona-a-1965505.html',
        'info_dict': {
            'id': 'hMQtqFYjomk',
            'ext': 'mp4',
            'title': 'phoenix streitgut: Brennglas Corona - Wie gerecht ist unsere Gesellschaft?',
            'description': 'md5:ac7a02e2eb3cb17600bc372e4ab28fdd',
            'duration': 3509,
            'upload_date': '20201219',
            'uploader': 'phoenix',
            'uploader_id': 'phoenix',
        },
        'params': {
            'skip_download': True,
        },
    }, {
        'url': 'https://www.phoenix.de/entwicklungen-in-russland-a-2044720.html',
        'only_matching': True,
    }, {
        # no media
        'url': 'https://www.phoenix.de/sendungen/dokumentationen/mit-dem-jumbo-durch-die-nacht-a-89625.html',
        'only_matching': True,
    }, {
        # Same as https://www.zdf.de/politik/phoenix-sendungen/die-gesten-der-maechtigen-100.html
        'url': 'https://www.phoenix.de/sendungen/dokumentationen/gesten-der-maechtigen-i-a-89468.html?ref=suche',
        'only_matching': True,
    }]

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

        article = self._download_json(
            'https://www.phoenix.de/response/id/%s' % article_id, article_id,
            'Downloading article JSON')

        video = article['absaetze'][0]
        title = video.get('titel') or article.get('subtitel')

        if video.get('typ') == 'video-youtube':
            video_id = video['id']
            return self.url_result(
                video_id, ie=YoutubeIE.ie_key(), video_id=video_id,
                video_title=title)

        video_id = compat_str(video.get('basename') or video.get('content'))

        details = self._download_json(
            'https://www.phoenix.de/php/mediaplayer/data/beitrags_details.php',
            video_id, 'Downloading details JSON', query={
                'ak': 'web',
                'ptmd': 'true',
                'id': video_id,
                'profile': 'player2',
            })

        title = title or details['title']
        content_id = details['tracking']['nielsen']['content']['assetid']

        info = self._extract_ptmd(
            'https://tmd.phoenix.de/tmd/2/ngplayer_2_3/vod/ptmd/phoenix/%s' % content_id,
            content_id, None, url)

        duration = int_or_none(try_get(
            details, lambda x: x['tracking']['nielsen']['content']['length']))
        timestamp = unified_timestamp(details.get('editorialDate'))
        series = try_get(
            details, lambda x: x['tracking']['nielsen']['content']['program'],
            compat_str)
        episode = title if details.get('contentType') == 'episode' else None

        thumbnails = []
        teaser_images = try_get(details, lambda x: x['teaserImageRef']['layouts'], dict) or {}
        for thumbnail_key, thumbnail_url in teaser_images.items():
            thumbnail_url = urljoin(url, thumbnail_url)
            if not thumbnail_url:
                continue
            thumbnail = {
                'url': thumbnail_url,
            }
            m = re.match('^([0-9]+)x([0-9]+)$', thumbnail_key)
            if m:
                thumbnail['width'] = int(m.group(1))
                thumbnail['height'] = int(m.group(2))
            thumbnails.append(thumbnail)

        return merge_dicts(info, {
            'id': content_id,
            'title': title,
            'description': details.get('leadParagraph'),
            'duration': duration,
            'thumbnails': thumbnails,
            'timestamp': timestamp,
            'uploader': details.get('tvService'),
            'series': series,
            'episode': episode,
        })


code:
import json

from .common import InfoExtractor
from ..compat import compat_urllib_parse_unquote

class PhotobucketIE(InfoExtractor):
    _VALID_URL = r'https?://(?:[a-z0-9]+\.)?photobucket\.com/.*(([\?\&]current=)|_)(?P<id>.*)\.(?P<ext>(flv)|(mp4))'
    _TEST = {
        'url': 'http://media.photobucket.com/user/rachaneronas/media/TiredofLinkBuildingTryBacklinkMyDomaincom_zpsc0c3b9fa.mp4.html?filters[term]=search&filters[primary]=videos&filters[secondary]=images&sort=1&o=0',
        'md5': '7dabfb92b0a31f6c16cebc0f8e60ff99',
        'info_dict': {
            'id': 'zpsc0c3b9fa',
            'ext': 'mp4',
            'timestamp': 1367669341,
            'upload_date': '20130504',
            'uploader': 'rachaneronas',
            'title': 'Tired of Link Building? Try BacklinkMyDomain.com!',
        }
    }

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

        webpage = self._download_webpage(url, video_id)

        # Extract URL, uploader, and title from webpage
        self.report_extraction(video_id)
        info_json = self._search_regex(r'Pb\.Data\.Shared\.put\(Pb\.Data\.Shared\.MEDIA, (.*?)\);',
                                       webpage, 'info json')
        info = json.loads(info_json)
        url = compat_urllib_parse_unquote(self._html_search_regex(r'file=(.+\.mp4)', info['linkcodes']['html'], 'url'))
        return {
            'id': video_id,
            'url': url,
            'uploader': info['username'],
            'timestamp': info['creationDate'],
            'title': info['title'],
            'ext': video_extension,
            'thumbnail': info['thumbUrl'],
        }


code:
import urllib.parse

from .common import InfoExtractor
from ..utils import (
    ExtractorError,
    parse_duration,
    parse_filesize,
    str_to_int,
    unified_timestamp,
    urlencode_postdata,
)

class PiaproIE(InfoExtractor):
    _NETRC_MACHINE = 'piapro'
    _VALID_URL = r'https?://piapro\.jp/t/(?P<id>\w+)/?'
    _TESTS = [{
        'url': 'https://piapro.jp/t/NXYR',
        'md5': 'a9d52f27d13bafab7ee34116a7dcfa77',
        'info_dict': {
            'id': 'NXYR',
            'ext': 'mp3',
            'uploader': 'wowaka',
            'uploader_id': 'wowaka',
            'title': '裏表ラバーズ',
            'thumbnail': r're:^https?://.*\.jpg$',
        }
    }, {
        'note': 'There are break lines in description, mandating (?s) flag',
        'url': 'https://piapro.jp/t/9cSd',
        'md5': '952bb6d1e8de95050206408a87790676',
        'info_dict': {
            'id': '9cSd',
            'ext': 'mp3',
            'title': '青に溶けた風船 / 初音ミク',
            'description': 'md5:d395a9bd151447631a5a1460bc7f9132',
            'uploader': 'シアン・キノ',
            'uploader_id': 'cyankino',
        }
    }]

    _login_status = False

    def _perform_login(self, username, password):
        login_ok = True
        login_form_strs = {
            '_username': username,
            '_password': password,
            '_remember_me': 'on',
            'login': 'ログイン'
        }
        self._request_webpage('https://piapro.jp/login/', None)
        urlh = self._request_webpage(
            'https://piapro.jp/login/exe', None,
            note='Logging in', errnote='Unable to log in',
            data=urlencode_postdata(login_form_strs))
        if urlh is False:
            login_ok = False
        else:
            parts = urllib.parse.urlparse(urlh.geturl())
            if parts.path != '/':
                login_ok = False
        if not login_ok:
            self.report_warning(
                'unable to log in: bad username or password')
        self._login_status = login_ok

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

        category_id = self._search_regex(r'categoryId=(.+)">', webpage, 'category ID')
        if category_id not in ('1', '2', '21', '22', '23', '24', '25'):
            raise ExtractorError('The URL does not contain audio.', expected=True)

        str_duration, str_filesize = self._search_regex(
            r'サイズ：(.+?)/\(([0-9,]+?[KMG]?B)）', webpage, 'duration and size',
            group=(1, 2), default=(None, None))
        str_viewcount = self._search_regex(r'閲覧数：([0-9,]+)\s+', webpage, 'view count', fatal=False)

        uploader_id, uploader = self._search_regex(
            r'([^<]+)さん<', webpage, 'uploader',
            group=(1, 2), default=(None, None))
        content_id = self._search_regex(r'contentId\:\'(.+)\'', webpage, 'content ID')
        create_date = self._search_regex(r'createDate\:\'(.+)\'', webpage, 'timestamp')

        player_webpage = self._download_webpage(
            f'https://piapro.jp/html5_player_popup/?id={content_id}&cdate={create_date}',
            video_id, note='Downloading player webpage')

        return {
            'id': video_id,
            'title': self._html_search_regex(r'
(.+?)
', webpage, 'title', fatal=False),
            'description': self._html_search_regex(r'(?s)
(.+?)
\s*<div', webpage, 'description', fatal=False),
            'uploader': uploader,
            'uploader_id': uploader_id,
            'timestamp': unified_timestamp(create_date, False),
            'duration': parse_duration(str_duration),
            'view_count': str_to_int(str_viewcount),
            'thumbnail': self._html_search_meta('twitter:image', webpage),

            'filesize_approx': parse_filesize(str_filesize.replace(',', '')),
            'url': self._search_regex(r'mp3:\s*\'(.*?)\'\}', player_webpage, 'url'),
            'ext': 'mp3',
            'vcodec': 'none',
        }