Add ability for plugins to specify scale filter

This commit is contained in:
Chocobozzz 2021-04-09 10:36:21 +02:00 committed by Chocobozzz
parent d2351bcfd4
commit 3e03b961b8
5 changed files with 137 additions and 86 deletions

View File

@ -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
// ---------------------------------------------------------------------------

View File

@ -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 = (options) => {
return {
outputOptions: [
'-r:' + options.streamNum + ' 5'
]
}
}
transcodingManager.addLiveProfile('libx264', 'low-live', builder)
}
}
// Input options
{
const builder = () => {
return {
videoFilters: [
'fps=10'
]
{
const builder = () => {
return {
inputOptions: [
'-r 5'
]
}
}
transcodingManager.addVODProfile('libx264', 'input-options-vod', builder)
}
transcodingManager.addVODProfile('libx264', 'video-filters-vod', builder)
{
const builder = () => {
return {
inputOptions: [
'-r 5'
]
}
}
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.addVODProfile('libx264', 'input-options-vod', builder)
}
{
const builder = (options) => {
return {
outputOptions: [
'-r:' + options.streamNum + ' 5'
]
{
const builder = () => {
return {
scaleFilter: {
name: 'Flintheart'
}
}
}
transcodingManager.addLiveProfile('libx264', 'bad-scale-live', builder)
}
transcodingManager.addLiveProfile('libx264', 'low-live', builder)
}
{
const builder = () => {
return {
inputOptions: [
'-r 5'
]
}
}
transcodingManager.addLiveProfile('libx264', 'input-options-live', builder)
}
}

View File

@ -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,17 +145,6 @@ describe('Test transcoding plugins', function () {
await checkVideoFPS(videoUUID, 'below', 12)
})
it('Should apply video filters in vod profile', async function () {
this.timeout(120000)
await updateConf(server, 'video-filters-vod', 'default')
const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid
await waitJobs([ server ])
await checkVideoFPS(videoUUID, 'below', 12)
})
it('Should apply input options in vod profile', async function () {
this.timeout(120000)
@ -165,6 +156,22 @@ describe('Test transcoding plugins', function () {
await checkVideoFPS(videoUUID, 'below', 6)
})
it('Should apply the scale filter in vod profile', async function () {
this.timeout(120000)
await updateConf(server, 'bad-scale-vod', 'default')
const videoUUID = (await uploadVideoAndGetId({ server, videoName: 'video' })).uuid
await waitJobs([ server ])
// 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 () {
this.timeout(120000)
@ -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)

View File

@ -12,8 +12,11 @@ export type EncoderOptionsBuilder = (params: {
export interface EncoderOptions {
copy?: boolean // Copy stream? Default to false
scaleFilter?: {
name: string
}
inputOptions?: string[]
videoFilters?: string[]
outputOptions?: string[]
}

View File

@ -328,8 +328,6 @@ function register (...) {
Adding transcoding profiles allow admins to change ffmpeg encoding parameters and/or encoders.
A transcoding profile has to be chosen by the admin of the instance using the admin configuration.
Transcoding profiles used for live transcoding must not provide any `videoFilters`.
```js
async function register ({
transcodingManager
@ -346,9 +344,6 @@ async function register ({
// All these options are optional and defaults to []
return {
inputOptions: [],
videoFilters: [
'vflip' // flip the video vertically
],
outputOptions: [
// Use a custom bitrate
'-b' + streamString + ' 10K'
@ -364,7 +359,6 @@ async function register ({
// And/Or support this profile for live transcoding
transcodingManager.addLiveProfile(encoder, profileName, builder)
// Note: this profile will fail for live transcode because it specifies videoFilters
}
{
@ -401,7 +395,6 @@ async function register ({
const builder = () => {
return {
inputOptions: [],
videoFilters: [],
outputOptions: []
}
}