mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2024-11-28 03:23:57 -06:00
Add ability for plugins to specify scale filter
This commit is contained in:
parent
d2351bcfd4
commit
3e03b961b8
@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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[]
|
||||
}
|
||||
|
||||
|
@ -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: []
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user