Scripting way to upload videos to peertube and youtube
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
12 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. #!/usr/bin/env python
  2. # coding: utf-8
  3. import os
  4. import mimetypes
  5. import json
  6. import logging
  7. import sys
  8. import datetime
  9. import pytz
  10. from os.path import splitext, basename, abspath
  11. from tzlocal import get_localzone
  12. from configparser import RawConfigParser
  13. from requests_oauthlib import OAuth2Session
  14. from oauthlib.oauth2 import LegacyApplicationClient
  15. from requests_toolbelt.multipart.encoder import MultipartEncoder
  16. from . import utils
  17. logger = logging.getLogger('Prismedia')
  18. PEERTUBE_SECRETS_FILE = 'peertube_secret'
  19. PEERTUBE_PRIVACY = {
  20. "public": 1,
  21. "unlisted": 2,
  22. "private": 3
  23. }
  24. def get_authenticated_service(secret):
  25. peertube_url = str(secret.get('peertube', 'peertube_url')).rstrip("/")
  26. oauth_client = LegacyApplicationClient(
  27. client_id=str(secret.get('peertube', 'client_id'))
  28. )
  29. try:
  30. oauth = OAuth2Session(client=oauth_client)
  31. oauth.fetch_token(
  32. token_url=str(peertube_url + '/api/v1/users/token'),
  33. # lower as peertube does not store uppercase for pseudo
  34. username=str(secret.get('peertube', 'username').lower()),
  35. password=str(secret.get('peertube', 'password')),
  36. client_id=str(secret.get('peertube', 'client_id')),
  37. client_secret=str(secret.get('peertube', 'client_secret'))
  38. )
  39. except Exception as e:
  40. if hasattr(e, 'message'):
  41. logger.critical("Peertube: " + str(e.message))
  42. exit(1)
  43. else:
  44. logger.critical("Peertube: " + str(e))
  45. exit(1)
  46. return oauth
  47. def get_default_channel(user_info):
  48. return user_info['videoChannels'][0]['id']
  49. def get_channel_by_name(user_info, options):
  50. for channel in user_info["videoChannels"]:
  51. if channel['displayName'] == options.get('--channel'):
  52. return channel['id']
  53. def create_channel(oauth, url, options):
  54. template = ('Peertube: Channel %s does not exist, creating it.')
  55. logger.info(template % (str(options.get('--channel'))))
  56. channel_name = utils.cleanString(str(options.get('--channel')))
  57. # Peertube allows 20 chars max for channel name
  58. channel_name = channel_name[:19]
  59. data = '{"name":"' + channel_name + '", \
  60. "displayName":"' + options.get('--channel') + '", \
  61. "description":null, \
  62. "support":null}'
  63. headers = {
  64. 'Content-Type': "application/json; charset=UTF-8"
  65. }
  66. try:
  67. response = oauth.post(url + "/api/v1/video-channels/",
  68. data=data.encode('utf-8'),
  69. headers=headers)
  70. except Exception as e:
  71. if hasattr(e, 'message'):
  72. logger.error("Peertube: " + str(e.message))
  73. else:
  74. logger.error("Peertube: " + str(e))
  75. if response is not None:
  76. if response.status_code == 200:
  77. jresponse = response.json()
  78. jresponse = jresponse['videoChannel']
  79. return jresponse['id']
  80. if response.status_code == 409:
  81. logger.critical('Peertube: It seems there is a conflict with an existing channel named '
  82. + channel_name + '.'
  83. ' Please beware Peertube internal name is compiled from 20 firsts characters of channel name.'
  84. ' Also note that channel name are not case sensitive (no uppercase nor accent)'
  85. ' Please check your channel name and retry.')
  86. exit(1)
  87. else:
  88. logger.critical(('Peertube: Creating channel failed with an unexpected response: '
  89. '%s') % response)
  90. exit(1)
  91. def get_default_playlist(user_info):
  92. return user_info['videoChannels'][0]['id']
  93. def get_playlist_by_name(user_playlists, options):
  94. for playlist in user_playlists["data"]:
  95. if playlist['displayName'] == options.get('--playlist'):
  96. return playlist['id']
  97. def create_playlist(oauth, url, options, channel):
  98. template = ('Peertube: Playlist %s does not exist, creating it.')
  99. logger.info(template % (str(options.get('--playlist'))))
  100. # We use files for form-data Content
  101. # see https://requests.readthedocs.io/en/latest/user/quickstart/#post-a-multipart-encoded-file
  102. # None is used to mute "filename" field
  103. files = {'displayName': (None, str(options.get('--playlist'))),
  104. 'privacy': (None, "1"),
  105. 'description': (None, "null"),
  106. 'videoChannelId': (None, str(channel)),
  107. 'thumbnailfile': (None, "null")}
  108. try:
  109. response = oauth.post(url + "/api/v1/video-playlists/",
  110. files=files)
  111. except Exception as e:
  112. if hasattr(e, 'message'):
  113. logger.error("Peertube: " + str(e.message))
  114. else:
  115. logger.error("Peertube: " + str(e))
  116. if response is not None:
  117. if response.status_code == 200:
  118. jresponse = response.json()
  119. jresponse = jresponse['videoPlaylist']
  120. return jresponse['id']
  121. else:
  122. logger.critical(('Peertube: Creating the playlist failed with an unexpected response: '
  123. '%s') % response)
  124. exit(1)
  125. def set_playlist(oauth, url, video_id, playlist_id):
  126. logger.info('Peertube: add video to playlist.')
  127. data = '{"videoId":"' + str(video_id) + '"}'
  128. headers = {
  129. 'Content-Type': "application/json"
  130. }
  131. try:
  132. response = oauth.post(url + "/api/v1/video-playlists/"+str(playlist_id)+"/videos",
  133. data=data,
  134. headers=headers)
  135. except Exception as e:
  136. if hasattr(e, 'message'):
  137. logger.error("Peertube: " + str(e.message))
  138. else:
  139. logger.error("Peertube: " + str(e))
  140. if response is not None:
  141. if response.status_code == 200:
  142. logger.info('Peertube: Video is successfully added to the playlist.')
  143. else:
  144. logger.critical(('Peertube: Configuring the playlist failed with an unexpected response: '
  145. '%s') % response)
  146. exit(1)
  147. def upload_video(oauth, secret, options):
  148. def get_userinfo():
  149. return json.loads(oauth.get(url+"/api/v1/users/me").content)
  150. def get_file(path):
  151. mimetypes.init()
  152. return (basename(path), open(abspath(path), 'rb'),
  153. mimetypes.types_map[splitext(path)[1]])
  154. def get_playlist(username):
  155. return json.loads(oauth.get(url+"/api/v1/accounts/"+username+"/video-playlists").content)
  156. path = options.get('--file')
  157. url = str(secret.get('peertube', 'peertube_url')).rstrip('/')
  158. user_info = get_userinfo()
  159. user_playlists = get_playlist(str(secret.get('peertube', 'username').lower()))
  160. # We need to transform fields into tuple to deal with tags as
  161. # MultipartEncoder does not support list refer
  162. # https://github.com/requests/toolbelt/issues/190 and
  163. # https://github.com/requests/toolbelt/issues/205
  164. fields = [
  165. ("name", options.get('--name') or splitext(basename(options.get('--file')))[0]),
  166. ("licence", "1"),
  167. ("description", options.get('--description') or "default description"),
  168. ("nsfw", str(int(options.get('--nsfw')) or "0")),
  169. ("videofile", get_file(path))
  170. ]
  171. if options.get('--tags'):
  172. tags = options.get('--tags').split(',')
  173. for strtag in tags:
  174. # Empty tag crashes Peertube, so skip them
  175. if strtag == "":
  176. continue
  177. # Tag more than 30 chars crashes Peertube, so exit and check tags
  178. if len(strtag) >= 30:
  179. logger.error("Peertube: Sorry, Peertube does not support tag with more than 30 characters, please reduce tag: " + strtag)
  180. logger.error("Peertube: Meanwhile, this tag will be skipped")
  181. continue
  182. fields.append(("tags[]", strtag))
  183. if options.get('--category'):
  184. fields.append(("category", str(utils.getCategory(options.get('--category'), 'peertube'))))
  185. else:
  186. # if no category, set default to 2 (Films)
  187. fields.append(("category", "2"))
  188. if options.get('--language'):
  189. fields.append(("language", str(utils.getLanguage(options.get('--language'), "peertube"))))
  190. else:
  191. # if no language, set default to 1 (English)
  192. fields.append(("language", "en"))
  193. if options.get('--disable-comments'):
  194. fields.append(("commentsEnabled", "0"))
  195. else:
  196. fields.append(("commentsEnabled", "1"))
  197. privacy = None
  198. if options.get('--privacy'):
  199. privacy = options.get('--privacy').lower()
  200. # If peertubeAt exists, use instead of publishAt
  201. if options.get('--peertubeAt'):
  202. publishAt = options.get('--peertubeAt')
  203. elif options.get('--publishAt'):
  204. publishAt = options.get('--publishAt')
  205. if 'publishAt' in locals():
  206. publishAt = datetime.datetime.strptime(publishAt, '%Y-%m-%dT%H:%M:%S')
  207. tz = get_localzone()
  208. tz = pytz.timezone(str(tz))
  209. publishAt = tz.localize(publishAt).isoformat()
  210. fields.append(("scheduleUpdate[updateAt]", publishAt))
  211. fields.append(("scheduleUpdate[privacy]", str(PEERTUBE_PRIVACY["public"])))
  212. fields.append(("privacy", str(PEERTUBE_PRIVACY["private"])))
  213. else:
  214. fields.append(("privacy", str(PEERTUBE_PRIVACY[privacy or "private"])))
  215. if options.get('--thumbnail'):
  216. fields.append(("thumbnailfile", get_file(options.get('--thumbnail'))))
  217. fields.append(("previewfile", get_file(options.get('--thumbnail'))))
  218. if options.get('--channel'):
  219. channel_id = get_channel_by_name(user_info, options)
  220. if not channel_id and options.get('--channelCreate'):
  221. channel_id = create_channel(oauth, url, options)
  222. elif not channel_id:
  223. logger.warning("Peertube: Channel `" + options.get('--channel') + "` is unknown, using default channel.")
  224. channel_id = get_default_channel(user_info)
  225. else:
  226. channel_id = get_default_channel(user_info)
  227. fields.append(("channelId", str(channel_id)))
  228. if options.get('--playlist'):
  229. playlist_id = get_playlist_by_name(user_playlists, options)
  230. if not playlist_id and options.get('--playlistCreate'):
  231. playlist_id = create_playlist(oauth, url, options, channel_id)
  232. elif not playlist_id:
  233. logger.critical("Peertube: Playlist `" + options.get('--playlist') + "` does not exist, please set --playlistCreate"
  234. " if you want to create it")
  235. exit(1)
  236. logger_stdout = None
  237. if options.get('--url-only') or options.get('--batch'):
  238. logger_stdout = logging.getLogger('stdoutlogs')
  239. multipart_data = MultipartEncoder(fields)
  240. headers = {
  241. 'Content-Type': multipart_data.content_type
  242. }
  243. response = oauth.post(url + "/api/v1/videos/upload",
  244. data=multipart_data,
  245. headers=headers)
  246. if response is not None:
  247. if response.status_code == 200:
  248. jresponse = response.json()
  249. jresponse = jresponse['video']
  250. uuid = jresponse['uuid']
  251. video_id = str(jresponse['id'])
  252. logger.info('Peertube : Video was successfully uploaded.')
  253. template = 'Peertube: Watch it at %s/videos/watch/%s.'
  254. logger.info(template % (url, uuid))
  255. template_stdout = '%s/videos/watch/%s'
  256. if options.get('--url-only'):
  257. logger_stdout.info(template_stdout % (url, uuid))
  258. elif options.get('--batch'):
  259. logger_stdout.info("Peertube: " + template_stdout % (url, uuid))
  260. # Upload is successful we may set playlist
  261. if options.get('--playlist'):
  262. set_playlist(oauth, url, video_id, playlist_id)
  263. else:
  264. logger.critical(('Peertube: The upload failed with an unexpected response: '
  265. '%s') % response)
  266. exit(1)
  267. def run(options):
  268. secret = RawConfigParser()
  269. try:
  270. secret.read(PEERTUBE_SECRETS_FILE)
  271. except Exception as e:
  272. logger.critical("Peertube: Error loading " + str(PEERTUBE_SECRETS_FILE) + ": " + str(e))
  273. exit(1)
  274. insecure_transport = secret.get('peertube', 'OAUTHLIB_INSECURE_TRANSPORT')
  275. os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = insecure_transport
  276. oauth = get_authenticated_service(secret)
  277. try:
  278. logger.info('Peertube: Uploading video...')
  279. upload_video(oauth, secret, options)
  280. except Exception as e:
  281. if hasattr(e, 'message'):
  282. logger.error("Peertube: " + str(e.message))
  283. else:
  284. logger.error("Peertube: " + str(e))