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.

254 lines
13 KiB

  1. #!/usr/bin/python
  2. # coding: utf-8
  3. # NOTE: Since we use config file to set some defaults values, it is not possible to use the standard syntax with brackets, we use parenthesis instead.
  4. # If we were to use them we would override configuration file values with default values of cli.
  5. # TODO: change `youtube-at` and `peertube-at` that are not easily expendable as options in my opinion
  6. # TODO: remove `--url-only` and `--batch`
  7. """
  8. prismedia - tool to upload videos to different platforms (historically Peertube and Youtube)
  9. Usage:
  10. prismedia [cli] [options] --file=<file>
  11. prismedia <interface> [<parameters>...]
  12. prismedia --heartbeat
  13. prismedia --help | -h | --version | -V
  14. Options:
  15. -f, --file=STRING Path to the video file to upload in mp4. This is the only mandatory option except if you provide the name of a plugin interface (see <interface>).
  16. --thumbnail=STRING Path to a file to use as a thumbnail for the video.
  17. --name=NAME Name of the video to upload. (default to video filename)
  18. -d, --description=STRING Description of the video. (default: default description)
  19. -t, --tag=STRING Tags for the video. comma separated.
  20. WARN: tags with punctuation (!, ', ", ?, ...)
  21. are not supported by Mastodon to be published from Peertube
  22. -c, --category=STRING Category for the videos, see below. (default: Films)
  23. --licence=STRING Creative Common licence tag (for example: CC-BY-SA) (default: proprietary)
  24. -p, --privacy=STRING Choose between public, unlisted or private. (default: private)
  25. --disable-comments Disable comments (Peertube only as YT API does not support) (default: comments are enabled)
  26. --nsfw Set the video as No Safe For Work (Peertube only as YT API does not support) (default: video is safe)
  27. --nfo=STRING Configure a specific nfo file to set options for the video.
  28. By default Prismedia search a .txt based on the video name and will
  29. decode the file as UTF-8 (so make sure your nfo file is UTF-8 encoded)
  30. See nfo_example.txt for more details
  31. --language=STRING Specify the default language for video. See below for supported language. (default is English)
  32. --publish-at=DATE Publish the video at the given DATE using local server timezone.
  33. DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00
  34. DATE should be in the future
  35. --peertube-at=DATE Override publish-at for the corresponding platform. Allow to create preview on specific platform
  36. --youtube-at=DATE Override publish-at for the corresponding platform. Allow to create preview on specific platform
  37. --original-date=DATE Configure the video as initially recorded at DATE
  38. DATE should be on the form YYYY-MM-DDThh:mm:ss eg: 2018-03-12T19:00:00
  39. DATE should be in the past
  40. --auto-original-date Automatically use the file modification time as original date
  41. Supported types are jpg and jpeg.
  42. By default, prismedia search for an image based on video name followed by .jpg or .jpeg
  43. --channel=STRING Set the channel to use for the video (Peertube only)
  44. If the channel is not found, spawn an error except if --channelCreate is set.
  45. --channel-create Create the channel if not exists. (Peertube only, default do not create)
  46. Only relevant if --channel is set.
  47. --playlist=STRING Set the playlist to use for the video.
  48. If the playlist is not found, spawn an error except if --playlistCreate is set.
  49. --playlist-create Create the playlist if not exists. (default do not create)
  50. Only relevant if --playlist is set.
  51. --progress=STRING Set the progress bar view, one of percentage, bigFile, accurate. [default: percentage]
  52. --heartbeat Use some credits to show some activity for you apikey so the platform know it is used and would not inactivate your keys.
  53. -h, --help Show this help. Note that calling `help` without the `--` calls a plugin showing a different help for the plugins.
  54. -V, --version Show the version.
  55. Plugins options:
  56. <interface> Interface plugin to use to provide the video to upload. Select the interface you want to use. If `--file` is provided instead the interface will be the command line.
  57. --platform=STRING Platforms plugins to use. Usually one platform plugin upload to one platform website (comma separated list) (default: all)
  58. --consumer=STRING Consumers plugins to use. They are executed after an upload has been done (comma separated list) (default: all)
  59. Logging options:
  60. --log=STRING Log level, between debug, info, warning, error, critical. Ignored if --quiet is set (default to info)
  61. -q, --quiet Suppress any log except Critical (alias for --log=critical).
  62. -u, --url-only Display generated URL after upload directly on stdout, implies --quiet
  63. --batch Display generated URL after upload with platform information for easier parsing. Implies --quiet
  64. Be careful --batch and --url-only are mutually exclusives.
  65. Strict options:
  66. Strict options allow you to force some option to be present when uploading a video. It's useful to be sure you do not
  67. forget something when uploading a video, for example if you use multiples NFO. You may force the presence of description,
  68. tags, thumbnail, ...
  69. All strict option are optionals and are provided only to avoid errors when uploading :-)
  70. All strict options can be specified in NFO directly, the only strict option mandatory on cli is --withNFO
  71. All strict options are off by default
  72. --with-NFO Prevent the upload without a NFO, either specified via cli or found in the directory
  73. --with-thumbnail Prevent the upload without a thumbnail
  74. --with-name Prevent the upload if no name are found
  75. --with-description Prevent the upload without description
  76. --with-tag Prevent the upload without tags
  77. --with-playlist Prevent the upload if no playlist
  78. --with-publish-at Prevent the upload if no schedule
  79. --with-original-date Prevent the upload if no original date configured
  80. --with-platform Prevent the upload if at least one platform is not specified
  81. --with-category Prevent the upload if no category
  82. --with-language Prevent upload if no language
  83. --with-channel Prevent upload if no channel
  84. Categories:
  85. Category is the type of video you upload. Default is films.
  86. Here are available categories from Peertube and Youtube:
  87. music, films, vehicles, sports, travels, gaming, people,
  88. comedy, entertainment, news, how to, education, activism,
  89. science & technology, science, technology, animals
  90. Languages:
  91. Language of the video (audio track), choose one. Default is English
  92. Here are available languages from Peertube and Youtube:
  93. Arabic, English, French, German, Hindi, Italian, Japanese,
  94. Korean, Mandarin, Portuguese, Punjabi, Russian, Spanish
  95. """
  96. import os
  97. import logging
  98. import pluginInterfaces as pi
  99. import configuration
  100. import utils
  101. import video as vid
  102. from docopt import docopt
  103. from yapsy.PluginManager import PluginManagerSingleton
  104. # logging.basicConfig(level=logging.DEBUG)
  105. VERSION = "prismedia v1.0.0-plugins-alpha"
  106. def loadPlugins():
  107. from yapsy.ConfigurablePluginManager import ConfigurablePluginManager
  108. config = configuration.configuration_instance
  109. basePluginsPath = [config.root_path + "/plugins"]
  110. # TODO: check if AutoInstallPluginManager can help install new plugins or if it is already easy enough to download
  111. # and unzip a file.
  112. PluginManagerSingleton.setBehaviour([ConfigurablePluginManager])
  113. pluginManager = PluginManagerSingleton.get()
  114. pluginManager.setPluginPlaces(directories_list=basePluginsPath)
  115. pluginManager.setPluginInfoExtension("prismedia-plugin")
  116. pluginManager.setConfigParser(config.config_parser, pluginManager.config_has_changed)
  117. # Define the various categories corresponding to the different
  118. # kinds of plugins you have defined
  119. pluginManager.setCategoriesFilter({
  120. pi.PluginTypes.ALL: pi.IPrismediaBasePlugin,
  121. pi.PluginTypes.INTERFACE: pi.IInterfacePlugin,
  122. pi.PluginTypes.PLATFORM: pi.IPlatformPlugin,
  123. pi.PluginTypes.CONSUMER: pi.IConsumerPlugin,
  124. })
  125. pluginManager.collectPlugins()
  126. return pluginManager
  127. # TODO: cut this function into smaller ones
  128. def main():
  129. logger = logging.getLogger('Prismedia')
  130. # TODO: Check: Maybe this does not work good when installed via pip.
  131. pluginManager = loadPlugins()
  132. # TODO: add the arguments’s verification (copy/adapt the Schema table)
  133. options = docopt(__doc__, version=VERSION)
  134. # Helper functionalities help the user but do not upload anything
  135. if not utils.helperFunctionalities(options):
  136. exit(os.EX_OK)
  137. # Get all arguments needed by core only before calling any plugin
  138. listPlatforms = utils.getOption(options, "--platform")
  139. listConsumers = utils.getOption(options, "--consumer")
  140. if options["<interface>"]:
  141. interface_name = utils.getOption("<interface>")
  142. else:
  143. interface_name = "cli"
  144. interface = pluginManager.getPluginByName(interface_name, pi.PluginTypes.INTERFACE)
  145. video = vid.Video()
  146. try:
  147. if not interface.plugin_object.prepare_options(video, options):
  148. # The plugin asked to stop execution.
  149. exit(os.EX_OK)
  150. except Exception as e:
  151. logger.critical(utils.get_exception_string(e))
  152. exit(os.EX_CONFIG)
  153. if listPlatforms:
  154. platforms = pluginManager.getPluginsOf(categories=pi.PluginTypes.PLATFORM, name=[listPlatforms.split(",")])
  155. else:
  156. platforms = pluginManager.getPluginsOfCategory(pi.PluginTypes.PLATFORM)
  157. if listConsumers:
  158. consumers = pluginManager.getPluginsOf(categories=pi.PluginTypes.CONSUMER, name=[listConsumers.split(",")])
  159. else:
  160. consumers = pluginManager.getPluginsOfCategory(pi.PluginTypes.CONSUMER)
  161. # Let each plugin check its options before starting any upload
  162. # We cannot merge this loop with the one from interface since the interface can change which plugin to use
  163. # We need to create each platform object in video, so we cannot merge this loop with the following one
  164. for plugin in platforms:
  165. # TODO: Check this is needed or not: in case of no plugin or wrong name maybe the list is empty instead of there being a None value
  166. if plugin is None:
  167. # TODO: log instead to error? critical?
  168. print("No plugin installed name `" + plugin.name + "`.")
  169. exit(os.EX_USAGE)
  170. try:
  171. video.platform[plugin.name] = vid.Platform()
  172. if not plugin.plugin_object.prepare_options(video, options):
  173. # A plugin found ill formed options, it should have logged the precises info
  174. print(plugin.name + " found a malformed option.")
  175. exit(os.EX_CONFIG)
  176. except Exception as e:
  177. logger.critical("Error while preparing plugin `" + plugin.name + "`: " + utils.get_exception_string(e))
  178. exit(os.EX_CONFIG)
  179. for plugin in consumers:
  180. # TODO: Check this is needed or not: in case of no plugin or wrong name maybe the list is empty instead of there being a None value
  181. if plugin is None:
  182. # TODO: log instead to error? critical?
  183. print("No plugin installed name `" + plugin.name + "`.")
  184. exit(os.EX_USAGE)
  185. try:
  186. if not plugin.plugin_object.prepare_options(video, options):
  187. # A plugin found ill formed options, it should have logged the precises info
  188. print(plugin.name + " found a malformed option.")
  189. exit(os.EX_CONFIG)
  190. except Exception as e:
  191. logger.critical(utils.get_exception_string(e))
  192. exit(os.EX_CONFIG)
  193. if video.path == "":
  194. # TODO: log instead to error? critical?
  195. print("No valid path to a video file has been provided.")
  196. exit(os.EX_USAGE)
  197. print("All options validated, starting uploads onto platforms")
  198. for platform in platforms:
  199. print("Uploading to: " + platform.name)
  200. try:
  201. platform.plugin_object.upload(video, options)
  202. except Exception as e: # TODO: Maybe not catch every Exception?
  203. logger.critical(utils.get_exception_string(e))
  204. video.platform[platform.name].error = e
  205. video.platform[platform.name].publishAt = None
  206. video.platform[platform.name].url = None
  207. print("All uploads have been done, calling consumers plugins")
  208. for consumer in consumers:
  209. print("Calling consumer: " + consumer.name)
  210. consumer.plugin_object.finished(video, options)
  211. main()