2018-05-26 00:09:15 +07:00
# coding: utf-8
from __future__ import unicode_literals
2018-05-26 00:28:30 +07:00
import re
2018-05-26 00:09:15 +07:00
from . common import InfoExtractor
from . . compat import compat_str
from . . utils import (
int_or_none ,
parse_resolution ,
try_get ,
unified_timestamp ,
2018-07-21 19:08:28 +07:00
url_or_none ,
2018-05-26 00:09:15 +07:00
urljoin ,
)
class PeerTubeIE ( InfoExtractor ) :
2019-05-29 13:27:37 +02:00
# generated with (on the fish shell, with jq installed):
# echo (echo -n "(?:" ; curl -s "https://instances.joinpeertube.org/api/v1/instances?start=0&count=5000" | jq | string match -r "\"host\": \"(.*)\"" | string match -vr "\"host\": \"" | string replace -a . "\." | string join "|"; echo -n "|tube\.22decembre\.eu)") > instancelist
_INSTANCES_RE = r ''' (?:tube \ .openalgeria \ .org|peertube \ .mastodon \ .host|armstube \ .com|tube \ .moxaik \ .de|peertube \ .gnumeria \ .fr|video \ .minzord \ .eu \ .org|video \ .cohan \ .io|peertube \ .s2s \ .video|peertube \ .lol|tube \ .open-plug \ .eu|videos \ .mikedilger \ .com|open \ .tube|peertube \ .ch|peertube \ .normandie-libre \ .fr|pt \ .a-trappes-terre \ .fr|peertube \ .slat \ .org|video \ .lacaveatonton \ .ovh|peertube \ .uno|peertube \ .servebeer \ .com|lexx \ .impa \ .me|peertube \ .fedi \ .quebec|tube \ .h3z \ .jp|tube \ .plus200 \ .com|peertube \ .eric \ .ovh|tube \ .metadocs \ .cc|tube \ .unmondemeilleur \ .eu|dissidents \ .tv|anartube \ .zapto \ .org|gouttedeau \ .space|video \ .antirep \ .net|peertube \ .odat \ .xyz|peertube \ .davidpeach \ .co \ .uk|nrop \ .cant \ .at|peertube \ .hugolecourt \ .fr|v \ .mbius \ .io|tube \ .tr4sk \ .me|peertube \ .osureplayviewer \ .xyz|video \ .bruitbruit \ .com|video \ .abga \ .be|tube \ .dodsorf \ .as|tube \ .ksl-bmx \ .de|tube \ .plaf \ .fr|tube \ .tchncs \ .de|peertube \ .azkware \ .net|video \ .devinberg \ .com|video \ .hdys \ .band|hitchtube \ .fr|video \ .glassbeadcollective \ .org|nsfw \ .wetube \ .moe|peertube \ .kosebamse \ .com|medias \ .libox \ .fr|tube \ .mux \ .re|peertube \ .travelpandas \ .eu|yunopeertube \ .myddns \ .me|eggz \ .tv|peertube \ .varney \ .fr|peertube \ .anon-kenkai \ .com|peertube \ .bimwiki \ .fr|video \ .atlanti \ .se|tube \ .maiti \ .info|vid \ .lubar \ .me|tubee \ .fr|videos \ .dinofly \ .com|tube \ .ipfixe \ .info|toobnix \ .org|videotape \ .me|voca \ .tube|peertube \ .com \ .au|peerwatch \ .xyz|video \ .heromuster \ .com|video \ .lemediatv \ .fr|video \ .up \ .edu \ .ph|balafon \ .video|video \ .ivel \ .fr|thickrips \ .cloud|pt \ .laurentkruger \ .fr|peertube \ .m2 \ .nz|peertube \ .esadhar \ .net|peertube \ .geismar \ .paris|video \ .monarch-pass \ .net|peertube \ .artica \ .center|tube \ .nx-pod \ .de|video \ .vny \ .fr|video \ .alternanet \ .fr|indymotion \ .fr|fanvid \ .stopthatimp \ .net|cine \ .nashe \ .be|video \ .farci \ .org|v \ .lesterpig \ .com|video \ .okaris \ .de|tube \ .pawelko \ .net|peertube \ .mablr \ .org|tube \ .fede \ .re|pytu \ .be|evertron \ .tv|peertube \ .wivodaim \ .com|devtube \ .dev-wiki \ .de|raptube \ .antipub \ .org|video \ .selea \ .se|peertube \ .mygaia \ .org|video \ .oh14 \ .de|tube \ .fabrigli \ .fr|peertube \ .livingutopia \ .org|peertube \ .the-penguin \ .de|tube \ .thechangebook \ .org|peertube \ .montecsys \ .fr|tube \ .anjara \ .eu|pt \ .pube \ .tk|video \ .samedi \ .pm|mplayer \ .demouliere \ .eu|peertube \ .dans-ma-bulle \ .life|widemus \ .de|peertube \ .me|luttube \ .tk|video \ .depucelage \ .xyz|peertube \ .zapashcanon \ .fr|mhtube \ .de|video \ .latavernedejohnjohn \ .fr|peertube \ .pcservice46 \ .fr|peertube \ .mazzonetto \ .eu|tube \ .linc \ .systems|video \ .irem \ .univ-paris-diderot \ .fr|video \ .livecchi \ .cloud|alttube \ .fr|video \ .coop \ .tools|peertube \ .pretex \ .space|video \ .cabane-libre \ .org|peertube \ .openstreetmap \ .fr|peertube \ .nipponalba \ .scot|videos \ .alolise \ .org|vis \ .ion \ .ovh|csictv \ .csic \ .es|peertube \ .gaah \ .duckdns \ .org|0ch \ .in|irrsinn \ .video|video \ .antopie \ .org|scitech \ .video|tube2 \ .nemsia \ .org|video \ .amic37 \ .fr|peertube \ .freeforge \ .eu|video \ .arbitrarion \ .com|peertube \ .joel-smolski \ .com|video \ .datsemultimedia \ .com|stoptrackingus \ .tv|peertube \ .ricostrongxxx \ .com|tubercul \ .es|peertube \ .mindpalace \ .io|docker \ .videos \ .lecygnenoir \ .info|peertube \ .terranout \ .mine \ .nu|peertube \ .togart \ .de|tube \ .postblue \ .info|videos \ .domainepublic \ .net|peertube \ .cyber-tribal \ .com|video \ .gresille \ .org|peertube \ .dsmouse \ .net|peertube \ .david \ .durieux \ .family|cinema \ .yunohost \ .support|tube \ .theocevaer \ .fr|repro \ .video|tube \ .4aem \ .com|opera42 \ .com|quaziinc \ .com|peertube \ .metawurst \ .space|videos \ .wakapo \ .com|video \ .ploud \ .fr|video \ .freeradical \ .zone|tube \ .valinor \ .fr|refuznik \ .video|rhoads \ .com|tube \ .64 \ .re|peertube \ .xoddark \ .com|peertube \ .subak \ .ovh|pt \ .kircheneuenburg \ .de|peertube \ .asrun \ .eu|peertube \ .lagob \ .fr|videos \ .side-ways \ .net|videos \ .upr \ .fr|peervideo \ .net|peertube \ .dynlinux \ .io|91video \ .online|video \ .valme \ .io|video \ .taboulisme \ .com|peertube \ .la-famille-muller \ .fr|videos-libr \ .es|wetube \ .moe|video \ .monsieurbidouille \ .fr|tv \ .mooh \ .fr|nuage \ .acostey \ .fr|video \ .monsieur-a \ .fr|peertube \ .librelois \ .fr|tube \ .radiomercure \ .fr|videos \ .pair2jeux \ .tube|videos \ .pueseso \ .club|peer \ .mathdacloud \ .ovh|media \ .assassinate-you \ .net|vidcommons \ .org|ptube \ .rousset \ .nom \ .fr|tube \ .cyano \ .at|videos \ .squat \ .net|video \ .iphodase \ .fr|peertube \ .makotoworks
'''
2018-06-19 23:00:36 +07:00
_UUID_RE = r ' [ \ da-fA-F] {8} -[ \ da-fA-F] {4} -[ \ da-fA-F] {4} -[ \ da-fA-F] {4} -[ \ da-fA-F] {12} '
2018-05-26 00:28:30 +07:00
_VALID_URL = r ''' (?x)
2018-06-19 23:00:36 +07:00
( ? :
peertube : ( ? P < host > [ ^ : ] + ) : |
https ? : / / ( ? P < host_2 > % s ) / ( ? : videos / ( ? : watch | embed ) | api / v \d / videos ) /
)
( ? P < id > % s )
''' % (_INSTANCES_RE, _UUID_RE)
2018-05-26 00:09:15 +07:00
_TESTS = [ {
2019-05-29 13:27:37 +02:00
' url ' : " https://framatube.org/videos/watch/9c9de5e8-0a1e-484a-b099-e80766180a6d " ,
' md5 ' : ' 9bed8c0137913e17b86334e5885aacff ' ,
2018-05-26 00:09:15 +07:00
' info_dict ' : {
2019-05-29 13:27:37 +02:00
' id ' : ' 9c9de5e8-0a1e-484a-b099-e80766180a6d ' ,
2018-05-26 00:09:15 +07:00
' ext ' : ' mp4 ' ,
2019-05-29 13:27:37 +02:00
' title ' : ' What is PeerTube? ' ,
' description ' : """ **[Want to help to translate this video?](https://trad.framasoft.org/iteration/view/what-is-peertube/master)** (documentation [here](https://trad.framasoft.org))! \r \n \r \n **Take back the control of your videos! [#JoinPeertube](https://joinpeertube.org)** \r \n *A decentralized video hosting network, based on free/libre software!* \r \n \r \n **Animation Produced by:** [LILA](https://libreart.info) - [ZeMarmot Team](https://film.zemarmot.net) \r \n *Directed by* Aryeom \r \n *Assistant* Jehan \r \n **Licence**: [CC-By-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) \r \n \r \n **Sponsored by** [Framasoft](https://framasoft.org) \r \n \r \n **Music**: [Red Step Forward](http://play.dogmazic.net/song.php?song_id=52491) - CC-By Ken Bushima \r \n \r \n **Movie Clip**: [Caminades 3: Llamigos](http://www.caminandes.com/) CC-By Blender Institute """ ,
' timestamp ' : 1538391166 ,
' upload_date ' : ' 20181001 ' ,
' uploader ' : ' Framasoft ' ,
' uploader_id ' : ' 0a635cad-dcdb-443f-b600-6c38ffaffe1f ' ,
' uploader_url ' : ' https://framatube.org/accounts/framasoft ' ,
' license ' : ' Attribution - Share Alike ' ,
' duration ' : 113 ,
2018-05-26 00:09:15 +07:00
' view_count ' : int ,
' like_count ' : int ,
' dislike_count ' : int ,
' tags ' : list ,
' categories ' : list ,
}
} , {
' url ' : ' https://peertube.tamanoir.foucry.net/videos/watch/0b04f13d-1e18-4f1d-814e-4979aa7c9c44 ' ,
' only_matching ' : True ,
} , {
# nsfw
' url ' : ' https://tube.22decembre.eu/videos/watch/9bb88cd3-9959-46d9-9ab9-33d2bb704c39 ' ,
' only_matching ' : True ,
2018-05-26 00:15:38 +07:00
} , {
' url ' : ' https://tube.22decembre.eu/videos/embed/fed67262-6edb-4d1c-833b-daa9085c71d7 ' ,
' only_matching ' : True ,
} , {
' url ' : ' https://tube.openalgeria.org/api/v1/videos/c1875674-97d0-4c94-a058-3f7e64c962e8 ' ,
' only_matching ' : True ,
2018-06-19 23:00:36 +07:00
} , {
' url ' : ' peertube:video.blender.org:b37a5b9f-e6b5-415c-b700-04a5cd6ec205 ' ,
' only_matching ' : True ,
2018-05-26 00:09:15 +07:00
} ]
2018-05-26 00:28:30 +07:00
@staticmethod
2018-06-19 23:00:36 +07:00
def _extract_peertube_url ( webpage , source_url ) :
mobj = re . match (
r ' https?://(?P<host>[^/]+)/videos/watch/(?P<id> %s ) '
% PeerTubeIE . _UUID_RE , source_url )
if mobj and any ( p in webpage for p in (
' <title>PeerTube< ' ,
' There will be other non JS-based clients to access PeerTube ' ,
' >We are sorry but it seems that PeerTube is not compatible with your web browser.< ' ) ) :
return ' peertube: %s : %s ' % mobj . group ( ' host ' , ' id ' )
@staticmethod
def _extract_urls ( webpage , source_url ) :
entries = re . findall (
r ''' (?x)<iframe[^>]+ \ bsrc=[ " \ ' ](?P<url>(?:https?:)?// %s /videos/embed/ %s ) '''
% ( PeerTubeIE . _INSTANCES_RE , PeerTubeIE . _UUID_RE ) , webpage )
if not entries :
peertube_url = PeerTubeIE . _extract_peertube_url ( webpage , source_url )
if peertube_url :
entries = [ peertube_url ]
return entries
2018-05-26 00:28:30 +07:00
2018-05-26 00:09:15 +07:00
def _real_extract ( self , url ) :
2018-06-19 23:00:36 +07:00
mobj = re . match ( self . _VALID_URL , url )
host = mobj . group ( ' host ' ) or mobj . group ( ' host_2 ' )
video_id = mobj . group ( ' id ' )
2018-05-26 00:09:15 +07:00
video = self . _download_json (
2018-06-19 23:00:36 +07:00
' https:// %s /api/v1/videos/ %s ' % ( host , video_id ) , video_id )
2018-05-26 00:09:15 +07:00
title = video [ ' name ' ]
formats = [ ]
for file_ in video [ ' files ' ] :
if not isinstance ( file_ , dict ) :
continue
2018-07-21 19:08:28 +07:00
file_url = url_or_none ( file_ . get ( ' fileUrl ' ) )
if not file_url :
2018-05-26 00:09:15 +07:00
continue
file_size = int_or_none ( file_ . get ( ' size ' ) )
format_id = try_get (
file_ , lambda x : x [ ' resolution ' ] [ ' label ' ] , compat_str )
f = parse_resolution ( format_id )
f . update ( {
' url ' : file_url ,
' format_id ' : format_id ,
' filesize ' : file_size ,
} )
formats . append ( f )
self . _sort_formats ( formats )
def account_data ( field ) :
return try_get ( video , lambda x : x [ ' account ' ] [ field ] , compat_str )
category = try_get ( video , lambda x : x [ ' category ' ] [ ' label ' ] , compat_str )
categories = [ category ] if category else None
nsfw = video . get ( ' nsfw ' )
if nsfw is bool :
age_limit = 18 if nsfw else 0
else :
age_limit = None
2019-05-29 13:27:37 +02:00
try :
description = self . _download_json (
' https:// %s /api/v1/videos/ %s /description ' % ( host , video_id ) , video_id , note = " Downloading description " , fatal = False ) . get ( " description " )
except BaseException :
description = description . get ( " description " )
2018-05-26 00:09:15 +07:00
return {
' id ' : video_id ,
' title ' : title ,
2019-05-29 13:27:37 +02:00
' description ' : description ,
2018-05-26 00:09:15 +07:00
' thumbnail ' : urljoin ( url , video . get ( ' thumbnailPath ' ) ) ,
' timestamp ' : unified_timestamp ( video . get ( ' publishedAt ' ) ) ,
' uploader ' : account_data ( ' displayName ' ) ,
' uploader_id ' : account_data ( ' uuid ' ) ,
2019-05-29 13:27:37 +02:00
' uploader_url ' : account_data ( ' url ' ) ,
2018-05-26 00:09:15 +07:00
' license ' : try_get (
video , lambda x : x [ ' licence ' ] [ ' label ' ] , compat_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 ,
' formats ' : formats ,
}
2019-05-29 13:27:37 +02:00
class PeerTubeChannelIE ( PeerTubeIE ) :
_CHANNEL_RE = r ' [a-zA-Z0-9-_@ \ .]+ '
_VALID_URL = r ''' (?x)
( ? :
https ? : / / ( ? P < host > % s ) / ( api / v \d / ) ? accounts / ( ? P < channel > % s ) / videos
)
''' % (PeerTubeIE._INSTANCES_RE, _CHANNEL_RE)
_TESTS = [ {
' url ' : ' https://peertube.cpy.re/accounts/chocobozzz/videos ' ,
' info_dict ' : {
' id ' : ' 1fccb6e4-863c-4538-a4db-770c464f06a1 ' ,
' title ' : ' Default chocobozzz channel ' ,
} ,
' playlist_mincount ' : 6 ,
} , {
' url ' : ' https://videos.pair2jeux.tube/accounts/lecygnenoir/videos ' ,
' info_dict ' : {
' title ' : ' Default lecygnenoir channel ' ,
' id ' : ' 8a1ea992-9adf-4c3e-90b2-972d5b299be9 ' ,
} ,
' playlist_mincount ' : 880 ,
} , {
' url ' : ' https://peertube.mastodon.host/accounts/ecoleduchatnoir@sikke.fi/videos ' ,
' only_matching ' : True
} ]
def _real_extract ( self , url ) :
mobj = re . match ( self . _VALID_URL , url )
host = mobj . group ( ' host ' )
channelId = mobj . group ( ' channel ' )
videos = self . _download_json (
' https:// %s /api/v1/accounts/ %s /videos?count=50 ' % ( host , channelId ) , channelId )
channel = None
if len ( videos . get ( ' data ' ) ) > 0 :
channel = videos . get ( ' data ' ) [ 0 ] . get ( ' channel ' )
videoCount = videos . get ( " total " ) or 0
videoListed = 0
entries = [ ]
while videoListed < videoCount :
for video in videos . get ( ' data ' ) :
entries . append ( {
' url ' : " peertube: %s " % video . get ( " uuid " ) ,
' id ' : video . get ( ' uuid ' ) ,
' title ' : video . get ( " name " ) ,
' description ' : video . get ( ' description ' ) ,
' license ' : try_get (
video , lambda x : x [ ' licence ' ] [ ' label ' ] , compat_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 ' ) ) ,
' tags ' : try_get ( video , lambda x : x [ ' tags ' ] , list ) ,
} )
videoListed + = 1
if videoListed != videoCount :
videos = self . _download_json (
' https:// %s /api/v1/accounts/ %s /videos?count=50&start= %s ' % ( host , channelId , str ( videoListed ) ) , channel , note = " Downloading page from the video %s / %s " % ( str ( videoListed ) , str ( videoCount ) ) )
if channel is None :
return self . playlist_result ( entries )
else :
return self . playlist_result ( entries , channel . get ( ' uuid ' ) , channel . get ( ' displayName ' ) )