peertube-upload.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import { access, constants } from 'fs/promises'
  2. import { isAbsolute } from 'path'
  3. import { inspect } from 'util'
  4. import { Command } from '@commander-js/extra-typings'
  5. import { VideoPrivacy } from '@peertube/peertube-models'
  6. import { PeerTubeServer } from '@peertube/peertube-server-commands'
  7. import { assignToken, buildServer, getServerCredentials, listOptions } from './shared/index.js'
  8. type UploadOptions = {
  9. url?: string
  10. username?: string
  11. password?: string
  12. thumbnail?: string
  13. preview?: string
  14. file?: string
  15. videoName?: string
  16. category?: string
  17. licence?: string
  18. language?: string
  19. tags?: string
  20. nsfw?: true
  21. videoDescription?: string
  22. privacy?: number
  23. channelName?: string
  24. noCommentsEnabled?: true
  25. support?: string
  26. noWaitTranscoding?: true
  27. noDownloadEnabled?: true
  28. }
  29. export function defineUploadProgram () {
  30. const program = new Command('upload')
  31. .description('Upload a video on a PeerTube instance')
  32. .alias('up')
  33. program
  34. .option('-u, --url <url>', 'Server url')
  35. .option('-U, --username <username>', 'Username')
  36. .option('-p, --password <token>', 'Password')
  37. .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
  38. .option('-v, --preview <previewPath>', 'Preview path')
  39. .option('-f, --file <file>', 'Video absolute file path')
  40. .option('-n, --video-name <name>', 'Video name')
  41. .option('-c, --category <category_number>', 'Category number')
  42. .option('-l, --licence <licence_number>', 'Licence number')
  43. .option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)')
  44. .option('-t, --tags <tags>', 'Video tags', listOptions)
  45. .option('-N, --nsfw', 'Video is Not Safe For Work')
  46. .option('-d, --video-description <description>', 'Video description')
  47. .option('-P, --privacy <privacy_number>', 'Privacy', parseInt)
  48. .option('-C, --channel-name <channel_name>', 'Channel name')
  49. .option('--no-comments-enabled', 'Disable video comments')
  50. .option('-s, --support <support>', 'Video support text')
  51. .option('--no-wait-transcoding', 'Do not wait transcoding before publishing the video')
  52. .option('--no-download-enabled', 'Disable video download')
  53. .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info')
  54. .action(async options => {
  55. try {
  56. const { url, username, password } = await getServerCredentials(options)
  57. if (!options.videoName || !options.file) {
  58. if (!options.videoName) console.error('--video-name is required.')
  59. if (!options.file) console.error('--file is required.')
  60. process.exit(-1)
  61. }
  62. if (isAbsolute(options.file) === false) {
  63. console.error('File path should be absolute.')
  64. process.exit(-1)
  65. }
  66. await run({ ...options, url, username, password })
  67. } catch (err) {
  68. console.error('Cannot upload video: ' + err.message)
  69. process.exit(-1)
  70. }
  71. })
  72. return program
  73. }
  74. // ---------------------------------------------------------------------------
  75. // Private
  76. // ---------------------------------------------------------------------------
  77. async function run (options: UploadOptions) {
  78. const { url, username, password } = options
  79. const server = buildServer(url)
  80. await assignToken(server, username, password)
  81. await access(options.file, constants.F_OK)
  82. console.log('Uploading %s video...', options.videoName)
  83. const baseAttributes = await buildVideoAttributesFromCommander(server, options)
  84. const attributes = {
  85. ...baseAttributes,
  86. fixture: options.file,
  87. thumbnailfile: options.thumbnail,
  88. previewfile: options.preview
  89. }
  90. try {
  91. await server.videos.upload({ attributes })
  92. console.log(`Video ${options.videoName} uploaded.`)
  93. process.exit(0)
  94. } catch (err) {
  95. const message = err.message || ''
  96. if (message.includes('413')) {
  97. console.error('Aborted: user quota is exceeded or video file is too big for this PeerTube instance.')
  98. } else {
  99. console.error(inspect(err))
  100. }
  101. process.exit(-1)
  102. }
  103. }
  104. async function buildVideoAttributesFromCommander (server: PeerTubeServer, options: UploadOptions, defaultAttributes: any = {}) {
  105. const defaultBooleanAttributes = {
  106. nsfw: false,
  107. commentsEnabled: true,
  108. downloadEnabled: true,
  109. waitTranscoding: true
  110. }
  111. const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}
  112. for (const key of Object.keys(defaultBooleanAttributes)) {
  113. if (options[key] !== undefined) {
  114. booleanAttributes[key] = options[key]
  115. } else if (defaultAttributes[key] !== undefined) {
  116. booleanAttributes[key] = defaultAttributes[key]
  117. } else {
  118. booleanAttributes[key] = defaultBooleanAttributes[key]
  119. }
  120. }
  121. const videoAttributes = {
  122. name: options.videoName || defaultAttributes.name,
  123. category: options.category || defaultAttributes.category || undefined,
  124. licence: options.licence || defaultAttributes.licence || undefined,
  125. language: options.language || defaultAttributes.language || undefined,
  126. privacy: options.privacy || defaultAttributes.privacy || VideoPrivacy.PUBLIC,
  127. support: options.support || defaultAttributes.support || undefined,
  128. description: options.videoDescription || defaultAttributes.description || undefined,
  129. tags: options.tags || defaultAttributes.tags || undefined
  130. }
  131. Object.assign(videoAttributes, booleanAttributes)
  132. if (options.channelName) {
  133. const videoChannel = await server.channels.get({ channelName: options.channelName })
  134. Object.assign(videoAttributes, { channelId: videoChannel.id })
  135. if (!videoAttributes.support && videoChannel.support) {
  136. Object.assign(videoAttributes, { support: videoChannel.support })
  137. }
  138. }
  139. return videoAttributes
  140. }