Chocobozzz/PeerTube

View on GitHub
apps/peertube-cli/src/peertube-upload.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { Command } from '@commander-js/extra-typings'
import { VideoCommentPolicy, VideoPrivacy, VideoPrivacyType } from '@peertube/peertube-models'
import { PeerTubeServer } from '@peertube/peertube-server-commands'
import { access, constants } from 'fs/promises'
import { isAbsolute } from 'path'
import { inspect } from 'util'
import { assignToken, buildServer, getServerCredentials, listOptions } from './shared/index.js'

type UploadOptions = {
  url?: string
  username?: string
  password?: string
  thumbnail?: string
  preview?: string
  file?: string
  videoName?: string
  category?: number
  licence?: number
  language?: string
  tags?: string[]
  nsfw?: true
  videoDescription?: string
  privacy?: VideoPrivacyType
  channelName?: string
  noCommentsEnabled?: true
  support?: string
  noWaitTranscoding?: true
  noDownloadEnabled?: true
}

export function defineUploadProgram () {
  const program = new Command('upload')
    .description('Upload a video on a PeerTube instance')
    .alias('up')

  program
    .option('-u, --url <url>', 'Server url')
    .option('-U, --username <username>', 'Username')
    .option('-p, --password <token>', 'Password')
    .option('-b, --thumbnail <thumbnailPath>', 'Thumbnail path')
    .option('--preview <previewPath>', 'Preview path')
    .option('-f, --file <file>', 'Video absolute file path')
    .option('-n, --video-name <name>', 'Video name')
    .option('-c, --category <category_number>', 'Category number', parseInt)
    .option('-l, --licence <licence_number>', 'Licence number', parseInt)
    .option('-L, --language <language_code>', 'Language ISO 639 code (fr or en...)')
    .option('-t, --tags <tags>', 'Video tags', listOptions)
    .option('-N, --nsfw', 'Video is Not Safe For Work')
    .option('-d, --video-description <description>', 'Video description')
    .option('-P, --privacy <privacy_number>', 'Privacy', v => parseInt(v) as VideoPrivacyType)
    .option('-C, --channel-name <channel_name>', 'Channel name')
    .option('--no-comments-enabled', 'Disable video comments')
    .option('-s, --support <support>', 'Video support text')
    .option('--no-wait-transcoding', 'Do not wait transcoding before publishing the video')
    .option('--no-download-enabled', 'Disable video download')
    .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info')
    .action(async options => {
      try {
        const { url, username, password } = await getServerCredentials(options)

        if (!options.videoName || !options.file) {
          if (!options.videoName) console.error('--video-name is required.')
          if (!options.file) console.error('--file is required.')

          process.exit(-1)
        }

        if (isAbsolute(options.file) === false) {
          console.error('File path should be absolute.')
          process.exit(-1)
        }

        await run({ ...options, url, username, password })
      } catch (err) {
        console.error('Cannot upload video: ' + err.message)
        process.exit(-1)
      }
    })

  return program
}

// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------

async function run (options: UploadOptions) {
  const { url, username, password } = options

  const server = buildServer(url)
  await assignToken(server, username, password)

  await access(options.file, constants.F_OK)

  console.log('Uploading %s video...', options.videoName)

  const baseAttributes = await buildVideoAttributesFromCommander(server, options)

  const attributes = {
    ...baseAttributes,

    fixture: options.file,
    thumbnailfile: options.thumbnail,
    previewfile: options.preview
  }

  try {
    await server.videos.upload({ attributes })
    console.log(`Video ${options.videoName} uploaded.`)
    process.exit(0)
  } catch (err) {
    const message = err.message || ''
    if (message.includes('413')) {
      console.error('Aborted: user quota is exceeded or video file is too big for this PeerTube instance.')
    } else {
      console.error(inspect(err))
    }

    process.exit(-1)
  }
}

async function buildVideoAttributesFromCommander (server: PeerTubeServer, options: UploadOptions) {
  const defaultBooleanAttributes = {
    nsfw: false,
    downloadEnabled: true,
    waitTranscoding: true
  }

  const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {}

  for (const key of Object.keys(defaultBooleanAttributes)) {
    if (options[key] !== undefined) {
      booleanAttributes[key] = options[key]
    } else {
      booleanAttributes[key] = defaultBooleanAttributes[key]
    }
  }

  const videoAttributes = {
    name: options.videoName,
    category: options.category || undefined,
    licence: options.licence || undefined,
    language: options.language || undefined,
    privacy: options.privacy || VideoPrivacy.PUBLIC,
    support: options.support || undefined,
    description: options.videoDescription || undefined,
    tags: options.tags || undefined,

    commentsPolicy: options.noCommentsEnabled !== undefined
      ? options.noCommentsEnabled === true
        ? VideoCommentPolicy.DISABLED
        : VideoCommentPolicy.ENABLED
      : undefined,

    ...booleanAttributes
  }

  if (options.channelName) {
    const videoChannel = await server.channels.get({ channelName: options.channelName })

    Object.assign(videoAttributes, { channelId: videoChannel.id })

    if (!videoAttributes.support && videoChannel.support) {
      Object.assign(videoAttributes, { support: videoChannel.support })
    }
  }

  return videoAttributes
}