import { access, constants } from 'fs/promises' import { isAbsolute } from 'path' import { inspect } from 'util' import { Command } from '@commander-js/extra-typings' import { VideoPrivacy } from '@peertube/peertube-models' import { PeerTubeServer } from '@peertube/peertube-server-commands' 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?: string licence?: string language?: string tags?: string nsfw?: true videoDescription?: string privacy?: number 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 ', 'Server url') .option('-U, --username ', 'Username') .option('-p, --password ', 'Password') .option('-b, --thumbnail ', 'Thumbnail path') .option('-v, --preview ', 'Preview path') .option('-f, --file ', 'Video absolute file path') .option('-n, --video-name ', 'Video name') .option('-c, --category ', 'Category number') .option('-l, --licence ', 'Licence number') .option('-L, --language ', 'Language ISO 639 code (fr or en...)') .option('-t, --tags ', 'Video tags', listOptions) .option('-N, --nsfw', 'Video is Not Safe For Work') .option('-d, --video-description ', 'Video description') .option('-P, --privacy ', 'Privacy', parseInt) .option('-C, --channel-name ', 'Channel name') .option('--no-comments-enabled', 'Disable video comments') .option('-s, --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 ', '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, defaultAttributes: any = {}) { const defaultBooleanAttributes = { nsfw: false, commentsEnabled: true, 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 if (defaultAttributes[key] !== undefined) { booleanAttributes[key] = defaultAttributes[key] } else { booleanAttributes[key] = defaultBooleanAttributes[key] } } const videoAttributes = { name: options.videoName || defaultAttributes.name, category: options.category || defaultAttributes.category || undefined, licence: options.licence || defaultAttributes.licence || undefined, language: options.language || defaultAttributes.language || undefined, privacy: options.privacy || defaultAttributes.privacy || VideoPrivacy.PUBLIC, support: options.support || defaultAttributes.support || undefined, description: options.videoDescription || defaultAttributes.description || undefined, tags: options.tags || defaultAttributes.tags || undefined } Object.assign(videoAttributes, 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 }