1
0
Fork 0

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 { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils'
import { processImage } from './image-utils' import { processImage } from './image-utils'
import { logger } from './logger' import { logger } from './logger'
import { FilterSpecification } from 'fluent-ffmpeg'
/** /**
* *
@ -226,21 +227,14 @@ async function getLiveTranscodingCommand (options: {
const varStreamMap: string[] = [] const varStreamMap: string[] = []
command.complexFilter([ const complexFilter: FilterSpecification[] = [
{ {
inputs: '[v:0]', inputs: '[v:0]',
filter: 'split', filter: 'split',
options: resolutions.length, options: resolutions.length,
outputs: resolutions.map(r => `vtemp${r}`) 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('-preset superfast')
command.outputOption('-sc_threshold 0') command.outputOption('-sc_threshold 0')
@ -278,6 +272,13 @@ async function getLiveTranscodingCommand (options: {
command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`) command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`)
applyEncoderOptions(command, builderResult.result) 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}`) varStreamMap.push(`v:${i},a:${i}`)
} }
command.complexFilter(complexFilter)
addDefaultLiveHLSParams(command, outPath) addDefaultLiveHLSParams(command, outPath)
command.outputOption('-var_stream_map', varStreamMap.join(' ')) 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) let fps = await getVideoFileFPS(options.inputPath)
fps = computeFPS(fps, options.resolution) fps = computeFPS(fps, options.resolution)
command = await presetVideo(command, options.inputPath, options, fps) let scaleFilterValue: string
if (options.resolution !== undefined) { if (options.resolution !== undefined) {
// '?x720' or '720x?' for example scaleFilterValue = options.isPortraitMode === true
const size = options.isPortraitMode === true ? `${options.resolution}:-2`
? `${options.resolution}x?` : `-2:${options.resolution}`
: `?x${options.resolution}`
command = command.size(size)
} }
command = await presetVideo({ command, input: options.inputPath, transcodeOptions: options, fps, scaleFilterValue })
return command return command
} }
async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) { async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) {
command = command.loop(undefined) 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.outputOption('-preset:v veryfast')
command = command.input(options.audioPath) 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('-tune stillimage')
.outputOption('-shortest') .outputOption('-shortest')
@ -555,12 +558,15 @@ async function getEncoderBuilderResult (options: {
return null return null
} }
async function presetVideo ( async function presetVideo (options: {
command: ffmpeg.FfmpegCommand, command: ffmpeg.FfmpegCommand
input: string, input: string
transcodeOptions: TranscodeOptions, transcodeOptions: TranscodeOptions
fps?: number fps?: number
) { scaleFilterValue?: string
}) {
const { command, input, transcodeOptions, fps, scaleFilterValue } = options
let localCommand = command let localCommand = command
.format('mp4') .format('mp4')
.outputOption('-movflags faststart') .outputOption('-movflags faststart')
@ -601,9 +607,14 @@ async function presetVideo (
if (streamType === 'video') { if (streamType === 'video') {
localCommand.videoCodec(builderResult.encoder) localCommand.videoCodec(builderResult.encoder)
if (scaleFilterValue) {
localCommand.outputOption(`-vf ${getScaleFilter(builderResult.result)}=${scaleFilterValue}`)
}
} else if (streamType === 'audio') { } else if (streamType === 'audio') {
localCommand.audioCodec(builderResult.encoder) localCommand.audioCodec(builderResult.encoder)
} }
applyEncoderOptions(localCommand, builderResult.result) applyEncoderOptions(localCommand, builderResult.result)
addDefaultEncoderParams({ command: localCommand, encoder: builderResult.encoder, fps }) 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 { function applyEncoderOptions (command: ffmpeg.FfmpegCommand, options: EncoderOptions): ffmpeg.FfmpegCommand {
return command return command
.inputOptions(options.inputOptions ?? []) .inputOptions(options.inputOptions ?? [])
.videoFilters(options.videoFilters ?? [])
.outputOptions(options.outputOptions ?? []) .outputOptions(options.outputOptions ?? [])
} }
function getScaleFilter (options: EncoderOptions): string {
if (options.scaleFilter) return options.scaleFilter.name
return 'scale'
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Utils // Utils
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View File

@ -1,5 +1,7 @@
async function register ({ transcodingManager }) { async function register ({ transcodingManager }) {
// Output options
{
{ {
const builder = () => { const builder = () => {
return { return {
@ -13,17 +15,20 @@ async function register ({ transcodingManager }) {
} }
{ {
const builder = () => { const builder = (options) => {
return { return {
videoFilters: [ outputOptions: [
'fps=10' '-r:' + options.streamNum + ' 5'
] ]
} }
} }
transcodingManager.addVODProfile('libx264', 'video-filters-vod', builder) transcodingManager.addLiveProfile('libx264', 'low-live', builder)
}
} }
// Input options
{
{ {
const builder = () => { const builder = () => {
return { return {
@ -36,18 +41,6 @@ async function register ({ transcodingManager }) {
transcodingManager.addVODProfile('libx264', 'input-options-vod', builder) transcodingManager.addVODProfile('libx264', 'input-options-vod', builder)
} }
{
const builder = (options) => {
return {
outputOptions: [
'-r:' + options.streamNum + ' 5'
]
}
}
transcodingManager.addLiveProfile('libx264', 'low-live', builder)
}
{ {
const builder = () => { const builder = () => {
return { return {
@ -59,6 +52,34 @@ async function register ({ transcodingManager }) {
transcodingManager.addLiveProfile('libx264', 'input-options-live', builder) transcodingManager.addLiveProfile('libx264', 'input-options-live', builder)
} }
}
// Scale filters
{
{
const builder = () => {
return {
scaleFilter: {
name: 'Glomgold'
}
}
}
transcodingManager.addVODProfile('libx264', 'bad-scale-vod', builder)
}
{
const builder = () => {
return {
scaleFilter: {
name: 'Flintheart'
}
}
}
transcodingManager.addLiveProfile('libx264', 'bad-scale-live', builder)
}
}
} }

View File

@ -15,9 +15,11 @@ import {
sendRTMPStreamInVideo, sendRTMPStreamInVideo,
setAccessTokensToServers, setAccessTokensToServers,
setDefaultVideoChannel, setDefaultVideoChannel,
testFfmpegStreamError,
uninstallPlugin, uninstallPlugin,
updateCustomSubConfig, updateCustomSubConfig,
uploadVideoAndGetId, uploadVideoAndGetId,
waitFfmpegUntilError,
waitJobs, waitJobs,
waitUntilLivePublished waitUntilLivePublished
} from '../../../shared/extra-utils' } from '../../../shared/extra-utils'
@ -119,8 +121,8 @@ describe('Test transcoding plugins', function () {
const res = await getConfig(server.url) const res = await getConfig(server.url)
const config = res.body as ServerConfig const config = res.body as ServerConfig
expect(config.transcoding.availableProfiles).to.have.members([ 'default', 'low-vod', 'video-filters-vod', 'input-options-vod' ]) 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' ]) 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 () { 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) 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 () { it('Should apply input options in vod profile', async function () {
this.timeout(120000) this.timeout(120000)
@ -165,6 +156,22 @@ describe('Test transcoding plugins', function () {
await checkVideoFPS(videoUUID, 'below', 6) 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 () { it('Should not use the plugin profile if not chosen by the admin', async function () {
this.timeout(120000) this.timeout(120000)
@ -205,6 +212,17 @@ describe('Test transcoding plugins', function () {
await checkLiveFPS(liveVideoId, 'below', 6) 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 () { it('Should default to the default profile if the specified profile does not exist', async function () {
this.timeout(120000) this.timeout(120000)

View File

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

View File

@ -328,8 +328,6 @@ function register (...) {
Adding transcoding profiles allow admins to change ffmpeg encoding parameters and/or encoders. 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. 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 ```js
async function register ({ async function register ({
transcodingManager transcodingManager
@ -346,9 +344,6 @@ async function register ({
// All these options are optional and defaults to [] // All these options are optional and defaults to []
return { return {
inputOptions: [], inputOptions: [],
videoFilters: [
'vflip' // flip the video vertically
],
outputOptions: [ outputOptions: [
// Use a custom bitrate // Use a custom bitrate
'-b' + streamString + ' 10K' '-b' + streamString + ' 10K'
@ -364,7 +359,6 @@ async function register ({
// And/Or support this profile for live transcoding // And/Or support this profile for live transcoding
transcodingManager.addLiveProfile(encoder, profileName, builder) 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 = () => { const builder = () => {
return { return {
inputOptions: [], inputOptions: [],
videoFilters: [],
outputOptions: [] outputOptions: []
} }
} }