@ -3,16 +3,13 @@
import pluginInterfaces as pi
import pluginInterfaces as pi
import utils
import utils
import video as vid
import os
import mimetypes
import mimetypes
import json
import json
import logging
import logging
import sys
import datetime
import datetime
import pytz
import pytz
from os.path import splitext , basename , abspath # TODO: remove me, we already import `os` or at least choose one
from os.path import splitext , basename , abspath
from tzlocal import get_localzone
from tzlocal import get_localzone
from configparser import RawConfigParser
from configparser import RawConfigParser
@ -20,16 +17,19 @@ from requests_oauthlib import OAuth2Session
from requests_toolbelt import MultipartEncoder , MultipartEncoderMonitor
from requests_toolbelt import MultipartEncoder , MultipartEncoderMonitor
from oauthlib.oauth2 import LegacyApplicationClient
from oauthlib.oauth2 import LegacyApplicationClient
from clint.textui.progress import Bar as ProgressBar
from clint.textui.progress import Bar as ProgressBar
from yapsy.PluginManager import PluginManagerSingleton
logger = logging . getLogger ( ' Prismedia ' )
logger = logging . getLogger ( ' Prismedia ' )
class Peertube ( pi . IPlatformPlugin ) :
class Peertube ( pi . IPlatformPlugin ) :
"""
"""
Plugin to upload to the Peertube platform .
Plugin to upload to the Peertube platform .
The connetions files should be set as # TODO: EXPLAIN HOW TO SETUP THE SECRET FILES
The connec tions files should be set as # TODO: EXPLAIN HOW TO SETUP THE SECRET FILES
- `publish-at-peertube=DATE` : overrides the default `publish-at=DATE` for this platform . # TODO: Maybe we will use a [<plugin_name>] section on the config fire, explain that.
- `publish-at-peertube=DATE` : overrides the default `publish-at=DATE` for this platform . # TODO: Maybe we will use a [<plugin_name>] section on the config fire, explain that.
"""
"""
SECRETS_FILE = ' peertube_secret '
NAME = " peertube " # TODO: find if it is possible to get the plugin’s name from inside the plugin
SECRETS_FILE = " peertube_secret "
PRIVACY = {
PRIVACY = {
" public " : 1 ,
" public " : 1 ,
" unlisted " : 2 ,
" unlisted " : 2 ,
@ -74,14 +74,17 @@ class Peertube(pi.IPlatformPlugin):
def __init__ ( self ) :
def __init__ ( self ) :
self . channelCreate = False
self . channelCreate = False
self . name = " peertube " # TODO: find if it is possible to get the plugin’s name from inside the plugin
self . oauth = { }
self . oauth = { }
self . secret = { }
self . secret = { }
def prepare_options ( self , video , options ) :
def prepare_options ( self , video , options ) :
pluginManager = PluginManagerSingleton . get ( )
# TODO: get the `publish-at-peertube=DATE` option
# TODO: get the `publish-at-peertube=DATE` option
# TODO: get the `channel` and `channel-create` options
# TODO: get the `channel` and `channel-create` options
video . platform [ self . name ] . channel = " "
pluginManager . registerOptionFromPlugin ( " Platform " , self . NAME , " publish-at " , " 2034-05-07T19:00:00 " )
pluginManager . registerOptionFromPlugin ( " Platform " , self . NAME , " channel " , " toto " )
pluginManager . registerOptionFromPlugin ( " Platform " , self . NAME , " channel-create " , False )
video . platform [ self . NAME ] . channel = " "
self . secret = RawConfigParser ( )
self . secret = RawConfigParser ( )
self . secret . read ( self . SECRETS_FILE )
self . secret . read ( self . SECRETS_FILE )
@ -105,32 +108,28 @@ class Peertube(pi.IPlatformPlugin):
client_secret = str ( self . secret . get ( ' peertube ' , ' client_secret ' ) )
client_secret = str ( self . secret . get ( ' peertube ' , ' client_secret ' ) )
)
)
def convert_peertube_date ( self , date ) :
def convert_peertube_date ( self , date ) :
date = datetime . datetime . strptime ( date , ' % Y- % m- %d T % H: % M: % S ' )
date = datetime . datetime . strptime ( date , ' % Y- % m- %d T % H: % M: % S ' )
tz = get_localzone ( )
tz = get_localzone ( )
tz = pytz . timezone ( str ( tz ) )
tz = pytz . timezone ( str ( tz ) )
return tz . localize ( date ) . isoformat ( )
return tz . localize ( date ) . isoformat ( )
def get_default_channel ( self , user_info ) :
def get_default_channel ( self , user_info ) :
return user_info [ ' videoChannels ' ] [ 0 ] [ ' id ' ]
return user_info [ ' videoChannels ' ] [ 0 ] [ ' id ' ]
def get_channel_by_name ( self , user_info , video ) :
def get_channel_by_name ( self , user_info , video ) :
for channel in user_info [ " videoChannels " ] :
for channel in user_info [ " videoChannels " ] :
if channel [ ' displayName ' ] == video . platform [ self . name ] . channel :
if channel [ ' displayName ' ] == video . platform [ self . NAME ] . channel :
return channel [ ' id ' ]
return channel [ ' id ' ]
def create_channel ( self , instance_url , video ) :
def create_channel ( self , instance_url , video ) :
template = ( ' Peertube: Channel %s does not exist, creating it. ' )
template = ( ' Peertube: Channel %s does not exist, creating it. ' )
logger . info ( template % ( video . platform [ self . name ] . channel ) )
channel_name = utils . cleanString ( video . platform [ self . name ] . channel )
logger . info ( template % ( video . platform [ self . NAME ] . channel ) )
channel_name = utils . cleanString ( video . platform [ self . NAME ] . channel )
# Peertube allows 20 chars max for channel name
# Peertube allows 20 chars max for channel name
channel_name = channel_name [ : 19 ]
channel_name = channel_name [ : 19 ]
data = ' { " name " : " ' + channel_name + ' " , \
data = ' { " name " : " ' + channel_name + ' " , \
" displayName " : " ' + video.platform[self.name ].channel + ' " , \
" displayName " : " ' + video.platform[self.NAME ].channel + ' " , \
" description " : null , \
" description " : null , \
" support " : null } '
" support " : null } '
@ -139,8 +138,8 @@ class Peertube(pi.IPlatformPlugin):
}
}
try :
try :
response = self . oauth . post ( instance_url + " /api/v1/video-channels/ " ,
response = self . oauth . post ( instance_url + " /api/v1/video-channels/ " ,
data = data . encode ( ' utf-8 ' ) ,
headers = headers )
data = data . encode ( ' utf-8 ' ) ,
headers = headers )
except Exception as e :
except Exception as e :
logger . error ( " Peertube: " + utils . get_exception_string ( e ) )
logger . error ( " Peertube: " + utils . get_exception_string ( e ) )
@ -151,25 +150,24 @@ class Peertube(pi.IPlatformPlugin):
return jresponse [ ' id ' ]
return jresponse [ ' id ' ]
if response . status_code == 409 :
if response . status_code == 409 :
logger . critical ( ' Peertube: It seems there is a conflict with an existing channel named '
logger . critical ( ' Peertube: It seems there is a conflict with an existing channel named '
+ channel_name + ' . '
' Please beware Peertube internal name is compiled from 20 firsts characters of channel name. '
' Also note that channel name are not case sensitive (no uppercase nor accent) '
' Please check your channel name and retry. ' )
+ channel_name + ' . '
' Please beware Peertube internal name is compiled from 20 firsts characters of channel name. '
' Also note that channel name are not case sensitive (no uppercase nor accent) '
' Please check your channel name and retry. ' )
exit ( 1 )
exit ( 1 )
else :
else :
logger . critical ( ( ' Peertube: Creating channel failed with an unexpected response: '
logger . critical ( ( ' Peertube: Creating channel failed with an unexpected response: '
' %s ' ) % response )
' %s ' ) % response )
exit ( 1 )
exit ( 1 )
def get_default_playlist ( user_info ) :
def get_default_playlist ( self , user_info ) :
return user_info [ ' videoChannels ' ] [ 0 ] [ ' id ' ]
return user_info [ ' videoChannels ' ] [ 0 ] [ ' id ' ]
def get_playlist_by_name ( instance_url , username , video ) :
def get_playlist_by_name ( self , instance_url , username , video ) :
start = 0
start = 0
user_playlists = json . loads ( self . oauth . get (
user_playlists = json . loads ( self . oauth . get (
instance_url + " /api/v1/accounts/ " + username + " /video-playlists?start= " + str ( start ) + " &count=100 " ) . content )
instance_url + " /api/v1/accounts/ " + username + " /video-playlists?start= " + str (
start ) + " &count=100 " ) . content )
total = user_playlists [ " total " ]
total = user_playlists [ " total " ]
data = user_playlists [ " data " ]
data = user_playlists [ " data " ]
# We need to iterate on pagination as peertube returns max 100 playlists (see #41)
# We need to iterate on pagination as peertube returns max 100 playlists (see #41)
@ -179,11 +177,11 @@ class Peertube(pi.IPlatformPlugin):
return playlist [ ' id ' ]
return playlist [ ' id ' ]
start = start + 100
start = start + 100
user_playlists = json . loads ( self . oauth . get (
user_playlists = json . loads ( self . oauth . get (
instance_url + " /api/v1/accounts/ " + username + " /video-playlists?start= " + str ( start ) + " &count=100 " ) . content )
instance_url + " /api/v1/accounts/ " + username + " /video-playlists?start= " + str (
start ) + " &count=100 " ) . content )
data = user_playlists [ " data " ]
data = user_playlists [ " data " ]
def create_playlist ( instance_url , video , channel ) :
def create_playlist ( self , instance_url , video , channel ) :
template = ( ' Peertube: Playlist %s does not exist, creating it. ' )
template = ( ' Peertube: Playlist %s does not exist, creating it. ' )
logger . info ( template % ( str ( video . playlistName ) ) )
logger . info ( template % ( str ( video . playlistName ) ) )
# We use files for form-data Content
# We use files for form-data Content
@ -197,7 +195,7 @@ class Peertube(pi.IPlatformPlugin):
try :
try :
response = self . oauth . post ( instance_url + " /api/v1/video-playlists/ " ,
response = self . oauth . post ( instance_url + " /api/v1/video-playlists/ " ,
files = files )
files = files )
except Exception as e :
except Exception as e :
logger . error ( " Peertube: " + utils . get_exception_string ( e ) )
logger . error ( " Peertube: " + utils . get_exception_string ( e ) )
@ -208,11 +206,10 @@ class Peertube(pi.IPlatformPlugin):
return jresponse [ ' id ' ]
return jresponse [ ' id ' ]
else :
else :
logger . critical ( ( ' Peertube: Creating the playlist failed with an unexpected response: '
logger . critical ( ( ' Peertube: Creating the playlist failed with an unexpected response: '
' %s ' ) % response )
' %s ' ) % response )
exit ( 1 )
exit ( 1 )
def set_playlist ( instance_url , video_id , playlist_id ) :
def set_playlist ( self , instance_url , video_id , playlist_id ) :
logger . info ( ' Peertube: add video to playlist. ' )
logger . info ( ' Peertube: add video to playlist. ' )
data = ' { " videoId " : " ' + str ( video_id ) + ' " } '
data = ' { " videoId " : " ' + str ( video_id ) + ' " } '
@ -221,9 +218,9 @@ class Peertube(pi.IPlatformPlugin):
}
}
try :
try :
response = self . oauth . post ( instance_url + " /api/v1/video-playlists/ " + str ( playlist_id ) + " /videos " ,
data = data ,
headers = headers )
response = self . oauth . post ( instance_url + " /api/v1/video-playlists/ " + str ( playlist_id ) + " /videos " ,
data = data ,
headers = headers )
except Exception as e :
except Exception as e :
logger . error ( " Peertube: " + utils . get_exception_string ( e ) )
logger . error ( " Peertube: " + utils . get_exception_string ( e ) )
@ -232,19 +229,18 @@ class Peertube(pi.IPlatformPlugin):
logger . info ( ' Peertube: Video is successfully added to the playlist. ' )
logger . info ( ' Peertube: Video is successfully added to the playlist. ' )
else :
else :
logger . critical ( ( ' Peertube: Configuring the playlist failed with an unexpected response: '
logger . critical ( ( ' Peertube: Configuring the playlist failed with an unexpected response: '
' %s ' ) % response )
' %s ' ) % response )
exit ( 1 )
exit ( 1 )
def upload_video ( self , video , options ) :
def upload_video ( self , video , options ) :
def get_userinfo ( instanc e_url) :
return json . loads ( self . oauth . get ( instanc e_url + " /api/v1/users/me " ) . content )
def get_userinfo ( bas e_url) :
return json . loads ( self . oauth . get ( bas e_url + " /api/v1/users/me " ) . content )
def get_file ( path ) :
def get_file ( video_ path) :
mimetypes . init ( )
mimetypes . init ( )
return ( basename ( path ) , open ( abspath ( path ) , ' rb ' ) ,
mimetypes . types_map [ splitext ( path ) [ 1 ] ] )
return ( basename ( video_ path) , open ( abspath ( video_ path) , ' rb ' ) ,
mimetypes . types_map [ splitext ( video_ path) [ 1 ] ] )
path = video . path
path = video . path
instance_url = str ( self . secret . get ( ' peertube ' , ' peertube_url ' ) ) . rstrip ( ' / ' )
instance_url = str ( self . secret . get ( ' peertube ' , ' peertube_url ' ) ) . rstrip ( ' / ' )
@ -257,7 +253,7 @@ class Peertube(pi.IPlatformPlugin):
# https://github.com/requests/toolbelt/issues/205
# https://github.com/requests/toolbelt/issues/205
fields = [
fields = [
( " name " , video . name ) ,
( " name " , video . name ) ,
( " licence " , " 1 " ) , # TODO: get licence from video object
( " licence " , " 1 " ) , # TODO: get licence from video object
( " description " , video . description ) ,
( " description " , video . description ) ,
( " category " , str ( self . CATEGORY [ video . category ] ) ) ,
( " category " , str ( self . CATEGORY [ video . category ] ) ) ,
( " language " , str ( self . LANGUAGE [ video . language ] ) ) ,
( " language " , str ( self . LANGUAGE [ video . language ] ) ) ,
@ -274,7 +270,8 @@ class Peertube(pi.IPlatformPlugin):
continue
continue
# Tag more than 30 chars crashes Peertube, so skip tags
# Tag more than 30 chars crashes Peertube, so skip tags
if len ( strtag ) > = 30 :
if len ( strtag ) > = 30 :
logger . warning ( " Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag )
logger . warning (
" Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag )
logger . warning ( " Peertube: Meanwhile, this tag will be skipped " )
logger . warning ( " Peertube: Meanwhile, this tag will be skipped " )
continue
continue
# Peertube supports only 5 tags at the moment
# Peertube supports only 5 tags at the moment
@ -285,8 +282,8 @@ class Peertube(pi.IPlatformPlugin):
fields . append ( ( " tags[] " , strtag ) )
fields . append ( ( " tags[] " , strtag ) )
# If peertubeAt exists, use instead of publishAt
# If peertubeAt exists, use instead of publishAt
if video . platform [ self . name ] . publishAt :
publishAt = video . platform [ self . name ] . publishAt
if video . platform [ self . NAME ] . publishAt :
publishAt = video . platform [ self . NAME ] . publishAt
elif video . publishAt :
elif video . publishAt :
publishAt = video . publishAt
publishAt = video . publishAt
@ -306,12 +303,13 @@ class Peertube(pi.IPlatformPlugin):
fields . append ( ( " thumbnailfile " , get_file ( video . thumbnail ) ) )
fields . append ( ( " thumbnailfile " , get_file ( video . thumbnail ) ) )
fields . append ( ( " previewfile " , get_file ( video . thumbnail ) ) )
fields . append ( ( " previewfile " , get_file ( video . thumbnail ) ) )
if hasattr ( video . platform [ self . name ] , " channel " ) : # TODO: Should always be present
if hasattr ( video . platform [ self . NAME ] , " channel " ) : # TODO: Should always be present
channel_id = self . get_channel_by_name ( user_info , video )
channel_id = self . get_channel_by_name ( user_info , video )
if not channel_id and self . channelCreate :
if not channel_id and self . channelCreate :
channel_id = self . create_channel ( instance_url , video )
channel_id = self . create_channel ( instance_url , video )
elif not channel_id :
elif not channel_id :
logger . warning ( " Peertube: Channel ` " + video . platform [ self . name ] . channel + " ` is unknown, using default channel. " ) # TODO: debate if we should have the same message and behavior than playlist : "does not exist, please set --channelCreate"
logger . warning ( " Peertube: Channel ` " + video . platform [
self . NAME ] . channel + " ` is unknown, using default channel. " ) # TODO: debate if we should have the same message and behavior than playlist: "does not exist, please set --channelCreate"
channel_id = self . get_default_channel ( user_info )
channel_id = self . get_default_channel ( user_info )
else :
else :
channel_id = self . get_default_channel ( user_info )
channel_id = self . get_default_channel ( user_info )
@ -323,8 +321,9 @@ class Peertube(pi.IPlatformPlugin):
if not playlist_id and video . playlistCreate :
if not playlist_id and video . playlistCreate :
playlist_id = create_playlist ( instance_url , video , channel_id )
playlist_id = create_playlist ( instance_url , video , channel_id )
elif not playlist_id :
elif not playlist_id :
logger . critical ( " Peertube: Playlist ` " + video . playlistName + " ` does not exist, please set --playlistCreate "
" if you want to create it " )
logger . critical (
" Peertube: Playlist ` " + video . playlistName + " ` does not exist, please set --playlistCreate "
" if you want to create it " )
exit ( 1 )
exit ( 1 )
encoder = MultipartEncoder ( fields )
encoder = MultipartEncoder ( fields )
@ -338,8 +337,8 @@ class Peertube(pi.IPlatformPlugin):
' Content-Type ' : multipart_data . content_type
' Content-Type ' : multipart_data . content_type
}
}
response = self . oauth . post ( instance_url + " /api/v1/videos/upload " ,
response = self . oauth . post ( instance_url + " /api/v1/videos/upload " ,
data = multipart_data ,
headers = headers )
data = multipart_data ,
headers = headers )
if response is not None :
if response is not None :
if response . status_code == 200 :
if response . status_code == 200 :
@ -350,17 +349,16 @@ class Peertube(pi.IPlatformPlugin):
logger . info ( " Peertube: Video was successfully uploaded. " )
logger . info ( " Peertube: Video was successfully uploaded. " )
template_url = " %s /videos/watch/ %s "
template_url = " %s /videos/watch/ %s "
video . platform [ self . name ] . url = template_url % ( instance_url , uuid )
logger . info ( " Peertube: Watch it at " + video . platform [ self . name ] . url + " . " )
video . platform [ self . NAME ] . url = template_url % ( instance_url , uuid )
logger . info ( " Peertube: Watch it at " + video . platform [ self . NAME ] . url + " . " )
# Upload is successful we may set playlist
# Upload is successful we may set playlist
if ' playlist_id ' in locals ( ) :
if ' playlist_id ' in locals ( ) :
set_playlist ( instance_url , video_id , playlist_id )
set_playlist ( instance_url , video_id , playlist_id )
else :
else :
logger . critical ( ( ' Peertube: The upload failed with an unexpected response: '
logger . critical ( ( ' Peertube: The upload failed with an unexpected response: '
' %s ' ) % response )
' %s ' ) % response )
exit ( 1 )
exit ( 1 )
# upload_finished = False
# upload_finished = False
# def create_callback(encoder, progress_type):
# def create_callback(encoder, progress_type):
# upload_size_MB = encoder.len * (1 / (1024 * 1024))
# upload_size_MB = encoder.len * (1 / (1024 * 1024))
@ -396,16 +394,14 @@ class Peertube(pi.IPlatformPlugin):
#
#
# return callback
# return callback
def hearthbeat ( self ) :
def heartbeat ( self ) :
"""
"""
If needed for your platform , use a bit of the api so the platform is aware the keys are still in use .
If needed for your platform , use a bit of the api so the platform is aware the keys are still in use .
"""
"""
print ( " Hearth beat for peertube (nothing to do)" )
print ( " heart beat for peertube (nothing to do)" )
pass
pass
def upload ( self , video , options ) :
# def run(options):
# def run(options):
def upload ( self , video , options ) :
logger . info ( ' Peertube: Uploading video... ' )
logger . info ( ' Peertube: Uploading video... ' )
self . upload_video ( video , options )
self . upload_video ( video , options )