From 3e03b961b8ab897500dfea626f808c009f64e551 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 9 Apr 2021 10:36:21 +0200 Subject: Add ability for plugins to specify scale filter --- server/helpers/ffmpeg-utils.ts | 66 +++++++++------ .../peertube-plugin-test-transcoding-one/main.js | 97 +++++++++++++--------- server/tests/plugins/plugin-transcoding.ts | 34 ++++++-- 3 files changed, 126 insertions(+), 71 deletions(-) (limited to 'server') diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 685a35886..aa4223cda 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -9,6 +9,7 @@ import { execPromise, promisify0 } from './core-utils' import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils' import { processImage } from './image-utils' import { logger } from './logger' +import { FilterSpecification } from 'fluent-ffmpeg' /** * @@ -226,21 +227,14 @@ async function getLiveTranscodingCommand (options: { const varStreamMap: string[] = [] - command.complexFilter([ + const complexFilter: FilterSpecification[] = [ { inputs: '[v:0]', filter: 'split', options: resolutions.length, outputs: resolutions.map(r => `vtemp${r}`) - }, - - ...resolutions.map(r => ({ - inputs: `vtemp${r}`, - filter: 'scale', - options: `w=-2:h=${r}`, - outputs: `vout${r}` - })) - ]) + } + ] command.outputOption('-preset superfast') command.outputOption('-sc_threshold 0') @@ -278,6 +272,13 @@ async function getLiveTranscodingCommand (options: { command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`) applyEncoderOptions(command, builderResult.result) + + complexFilter.push({ + inputs: `vtemp${resolution}`, + filter: getScaleFilter(builderResult.result), + options: `w=-2:h=${resolution}`, + outputs: `vout${resolution}` + }) } { @@ -300,6 +301,8 @@ async function getLiveTranscodingCommand (options: { varStreamMap.push(`v:${i},a:${i}`) } + command.complexFilter(complexFilter) + addDefaultLiveHLSParams(command, outPath) command.outputOption('-var_stream_map', varStreamMap.join(' ')) @@ -389,29 +392,29 @@ async function buildx264VODCommand (command: ffmpeg.FfmpegCommand, options: Tran let fps = await getVideoFileFPS(options.inputPath) fps = computeFPS(fps, options.resolution) - command = await presetVideo(command, options.inputPath, options, fps) + let scaleFilterValue: string if (options.resolution !== undefined) { - // '?x720' or '720x?' for example - const size = options.isPortraitMode === true - ? `${options.resolution}x?` - : `?x${options.resolution}` - - command = command.size(size) + scaleFilterValue = options.isPortraitMode === true + ? `${options.resolution}:-2` + : `-2:${options.resolution}` } + command = await presetVideo({ command, input: options.inputPath, transcodeOptions: options, fps, scaleFilterValue }) + return command } async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) { command = command.loop(undefined) - command = await presetVideo(command, options.audioPath, options) + // Avoid "height not divisible by 2" error + const scaleFilterValue = 'trunc(iw/2)*2:trunc(ih/2)*2' + command = await presetVideo({ command, input: options.audioPath, transcodeOptions: options, scaleFilterValue }) command.outputOption('-preset:v veryfast') command = command.input(options.audioPath) - .videoFilter('scale=trunc(iw/2)*2:trunc(ih/2)*2') // Avoid "height not divisible by 2" error .outputOption('-tune stillimage') .outputOption('-shortest') @@ -555,12 +558,15 @@ async function getEncoderBuilderResult (options: { return null } -async function presetVideo ( - command: ffmpeg.FfmpegCommand, - input: string, - transcodeOptions: TranscodeOptions, +async function presetVideo (options: { + command: ffmpeg.FfmpegCommand + input: string + transcodeOptions: TranscodeOptions fps?: number -) { + scaleFilterValue?: string +}) { + const { command, input, transcodeOptions, fps, scaleFilterValue } = options + let localCommand = command .format('mp4') .outputOption('-movflags faststart') @@ -601,9 +607,14 @@ async function presetVideo ( if (streamType === 'video') { localCommand.videoCodec(builderResult.encoder) + + if (scaleFilterValue) { + localCommand.outputOption(`-vf ${getScaleFilter(builderResult.result)}=${scaleFilterValue}`) + } } else if (streamType === 'audio') { localCommand.audioCodec(builderResult.encoder) } + applyEncoderOptions(localCommand, builderResult.result) addDefaultEncoderParams({ command: localCommand, encoder: builderResult.encoder, fps }) } @@ -628,10 +639,15 @@ function presetOnlyAudio (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand { function applyEncoderOptions (command: ffmpeg.FfmpegCommand, options: EncoderOptions): ffmpeg.FfmpegCommand { return command .inputOptions(options.inputOptions ?? []) - .videoFilters(options.videoFilters ?? []) .outputOptions(options.outputOptions ?? []) } +function getScaleFilter (options: EncoderOptions): string { + if (options.scaleFilter) return options.scaleFilter.name + + return 'scale' +} + // --------------------------------------------------------------------------- // Utils // --------------------------------------------------------------------------- diff --git a/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js b/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js index 366b827a9..59b136947 100644 --- a/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js +++ b/server/tests/fixtures/peertube-plugin-test-transcoding-one/main.js @@ -1,63 +1,84 @@ async function register ({ transcodingManager }) { + // Output options { - const builder = () => { - return { - outputOptions: [ - '-r 10' - ] + { + const builder = () => { + return { + outputOptions: [ + '-r 10' + ] + } } - } - transcodingManager.addVODProfile('libx264', 'low-vod', builder) - } + transcodingManager.addVODProfile('libx264', 'low-vod', builder) + } - { - const builder = () => { - return { - videoFilters: [ - 'fps=10' - ] + { + const builder = (options) => { + return { + outputOptions: [ + '-r:' + options.streamNum + ' 5' + ] + } } - } - transcodingManager.addVODProfile('libx264', 'video-filters-vod', builder) + transcodingManager.addLiveProfile('libx264', 'low-live', builder) + } } + // Input options { - const builder = () => { - return { - inputOptions: [ - '-r 5' - ] + { + const builder = () => { + return { + inputOptions: [ + '-r 5' + ] + } } - } - transcodingManager.addVODProfile('libx264', 'input-options-vod', builder) - } + transcodingManager.addVODProfile('libx264', 'input-options-vod', builder) + } - { - const builder = (options) => { - return { - outputOptions: [ - '-r:' + options.streamNum + ' 5' - ] + { + const builder = () => { + return { + inputOptions: [ + '-r 5' + ] + } } - } - transcodingManager.addLiveProfile('libx264', 'low-live', builder) + transcodingManager.addLiveProfile('libx264', 'input-options-live', builder) + } } + // Scale filters { - const builder = () => { - return { - inputOptions: [ - '-r 5' - ] + { + const builder = () => { + return { + scaleFilter: { + name: 'Glomgold' + } + } } + + transcodingManager.addVODProfile('libx264', 'bad-scale-vod', builder) } - transcodingManager.addLiveProfile('libx264', 'input-options-live', builder) + { + const builder = () => { + return { + scaleFilter: { + name: 'Flintheart' + } + } + } + + transcodingManager.addLiveProfile('libx264', 'bad-scale-live', builder) + } } } diff --git a/server/tests/plugins/plugin-transcoding.ts b/server/tests/plugins/plugin-transcoding.ts index 415705ca1..b6dff930e 100644 --- a/server/tests/plugins/plugin-transcoding.ts +++ b/server/tests/plugins/plugin-transcoding.ts @@ -15,9 +15,11 @@ import { sendRTMPStreamInVideo, setAccessTokensToServers, setDefaultVideoChannel, + testFfmpegStreamError, uninstallPlugin, updateCustomSubConfig, uploadVideoAndGetId, + waitFfmpegUntilError, waitJobs, waitUntilLivePublished } from '../../../shared/extra-utils' @@ -119,8 +121,8 @@ describe('Test transcoding plugins', function () { const res = await getConfig(server.url) const config = res.body as ServerConfig - expect(config.transcoding.availableProfiles).to.have.members([ 'default', 'low-vod', 'video-filters-vod', 'input-options-vod' ]) - expect(config.live.transcoding.availableProfiles).to.have.members([ 'default', 'low-live', 'input-options-live' ]) + expect(config.transcoding.availableProfiles).to.have.members([ 'default', 'low-vod', 'input-options-vod', 'bad-scale-vod' ]) + expect(config.live.transcoding.availableProfiles).to.have.members([ 'default', 'low-live', 'input-options-live', 'bad-scale-live' ]) }) it('Should not use the plugin profile if not chosen by the admin', async function () { @@ -143,26 +145,31 @@ describe('Test transcoding plugins', function () { await checkVideoFPS(videoUUID, 'below', 12) }) - it('Should apply video filters in vod profile', async function () { + it('Should apply input options in vod profile', async function () { this.timeout(120000) - await updateConf(server, 'video-filters-vod', 'default') + await updateConf(server, 'input-options-vod', 'default') const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid await waitJobs([ server ]) - await checkVideoFPS(videoUUID, 'below', 12) + await checkVideoFPS(videoUUID, 'below', 6) }) - it('Should apply input options in vod profile', async function () { + it('Should apply the scale filter in vod profile', async function () { this.timeout(120000) - await updateConf(server, 'input-options-vod', 'default') + await updateConf(server, 'bad-scale-vod', 'default') const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid await waitJobs([ server ]) - await checkVideoFPS(videoUUID, 'below', 6) + // Transcoding failed + const res = await getVideo(server.url, videoUUID) + const video: VideoDetails = res.body + + expect(video.files).to.have.lengthOf(1) + expect(video.streamingPlaylists).to.have.lengthOf(0) }) it('Should not use the plugin profile if not chosen by the admin', async function () { @@ -205,6 +212,17 @@ describe('Test transcoding plugins', function () { await checkLiveFPS(liveVideoId, 'below', 6) }) + it('Should apply the scale filter name on live profile', async function () { + this.timeout(120000) + + await updateConf(server, 'low-vod', 'bad-scale-live') + + const liveVideoId = await createLiveWrapper(server) + + const command = await sendRTMPStreamInVideo(server.url, server.accessToken, liveVideoId, 'video_short2.webm') + await testFfmpegStreamError(command, true) + }) + it('Should default to the default profile if the specified profile does not exist', async function () { this.timeout(120000) -- cgit v1.2.3