From c729caf6cc34630877a0e5a1bda1719384cd0c8a Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 11 Feb 2022 10:51:33 +0100 Subject: Add basic video editor support --- server/helpers/ffmpeg/ffmpeg-commons.ts | 114 ++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 server/helpers/ffmpeg/ffmpeg-commons.ts (limited to 'server/helpers/ffmpeg/ffmpeg-commons.ts') diff --git a/server/helpers/ffmpeg/ffmpeg-commons.ts b/server/helpers/ffmpeg/ffmpeg-commons.ts new file mode 100644 index 000000000..ee338889c --- /dev/null +++ b/server/helpers/ffmpeg/ffmpeg-commons.ts @@ -0,0 +1,114 @@ +import { Job } from 'bull' +import ffmpeg, { FfmpegCommand } from 'fluent-ffmpeg' +import { execPromise } from '@server/helpers/core-utils' +import { logger, loggerTagsFactory } from '@server/helpers/logger' +import { CONFIG } from '@server/initializers/config' +import { FFMPEG_NICE } from '@server/initializers/constants' +import { EncoderOptions } from '@shared/models' + +const lTags = loggerTagsFactory('ffmpeg') + +type StreamType = 'audio' | 'video' + +function getFFmpeg (input: string, type: 'live' | 'vod') { + // We set cwd explicitly because ffmpeg appears to create temporary files when trancoding which fails in read-only file systems + const command = ffmpeg(input, { + niceness: type === 'live' ? FFMPEG_NICE.LIVE : FFMPEG_NICE.VOD, + cwd: CONFIG.STORAGE.TMP_DIR + }) + + const threads = type === 'live' + ? CONFIG.LIVE.TRANSCODING.THREADS + : CONFIG.TRANSCODING.THREADS + + if (threads > 0) { + // If we don't set any threads ffmpeg will chose automatically + command.outputOption('-threads ' + threads) + } + + return command +} + +function getFFmpegVersion () { + return new Promise((res, rej) => { + (ffmpeg() as any)._getFfmpegPath((err, ffmpegPath) => { + if (err) return rej(err) + if (!ffmpegPath) return rej(new Error('Could not find ffmpeg path')) + + return execPromise(`${ffmpegPath} -version`) + .then(stdout => { + const parsed = stdout.match(/ffmpeg version .?(\d+\.\d+(\.\d+)?)/) + if (!parsed || !parsed[1]) return rej(new Error(`Could not find ffmpeg version in ${stdout}`)) + + // Fix ffmpeg version that does not include patch version (4.4 for example) + let version = parsed[1] + if (version.match(/^\d+\.\d+$/)) { + version += '.0' + } + + return res(version) + }) + .catch(err => rej(err)) + }) + }) +} + +async function runCommand (options: { + command: FfmpegCommand + silent?: boolean // false by default + job?: Job +}) { + const { command, silent = false, job } = options + + return new Promise((res, rej) => { + let shellCommand: string + + command.on('start', cmdline => { shellCommand = cmdline }) + + command.on('error', (err, stdout, stderr) => { + if (silent !== true) logger.error('Error in ffmpeg.', { stdout, stderr, shellCommand, ...lTags() }) + + rej(err) + }) + + command.on('end', (stdout, stderr) => { + logger.debug('FFmpeg command ended.', { stdout, stderr, shellCommand, ...lTags() }) + + res() + }) + + if (job) { + command.on('progress', progress => { + if (!progress.percent) return + + job.progress(Math.round(progress.percent)) + .catch(err => logger.warn('Cannot set ffmpeg job progress.', { err, ...lTags() })) + }) + } + + command.run() + }) +} + +function buildStreamSuffix (base: string, streamNum?: number) { + if (streamNum !== undefined) { + return `${base}:${streamNum}` + } + + return base +} + +function getScaleFilter (options: EncoderOptions): string { + if (options.scaleFilter) return options.scaleFilter.name + + return 'scale' +} + +export { + getFFmpeg, + getFFmpegVersion, + runCommand, + StreamType, + buildStreamSuffix, + getScaleFilter +} -- cgit v1.2.3