PeerTube/server/helpers/ffmpeg/ffmpeg-presets.ts

157 lines
4.5 KiB
TypeScript
Raw Normal View History

2022-02-11 10:51:33 +01:00
import { FfmpegCommand } from 'fluent-ffmpeg'
import { pick } from 'lodash'
import { logger, loggerTagsFactory } from '@server/helpers/logger'
import { AvailableEncoders, EncoderOptions } from '@shared/models'
import { buildStreamSuffix, getScaleFilter, StreamType } from './ffmpeg-commons'
import { getEncoderBuilderResult } from './ffmpeg-encoders'
import { ffprobePromise, getVideoStreamBitrate, getVideoStreamDimensionsInfo, hasAudioStream } from './ffprobe-utils'
const lTags = loggerTagsFactory('ffmpeg')
// ---------------------------------------------------------------------------
function addDefaultEncoderGlobalParams (command: FfmpegCommand) {
// avoid issues when transcoding some files: https://trac.ffmpeg.org/ticket/6375
command.outputOption('-max_muxing_queue_size 1024')
// strip all metadata
.outputOption('-map_metadata -1')
// allows import of source material with incompatible pixel formats (e.g. MJPEG video)
.outputOption('-pix_fmt yuv420p')
}
function addDefaultEncoderParams (options: {
command: FfmpegCommand
encoder: 'libx264' | string
fps: number
streamNum?: number
}) {
const { command, encoder, fps, streamNum } = options
if (encoder === 'libx264') {
// 3.1 is the minimal resource allocation for our highest supported resolution
command.outputOption(buildStreamSuffix('-level:v', streamNum) + ' 3.1')
if (fps) {
// Keyframe interval of 2 seconds for faster seeking and resolution switching.
// https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html
// https://superuser.com/a/908325
command.outputOption(buildStreamSuffix('-g:v', streamNum) + ' ' + (fps * 2))
}
}
}
// ---------------------------------------------------------------------------
async function presetVOD (options: {
command: FfmpegCommand
input: string
availableEncoders: AvailableEncoders
profile: string
canCopyAudio: boolean
canCopyVideo: boolean
resolution: number
fps: number
scaleFilterValue?: string
}) {
const { command, input, profile, resolution, fps, scaleFilterValue } = options
let localCommand = command
.format('mp4')
.outputOption('-movflags faststart')
addDefaultEncoderGlobalParams(command)
const probe = await ffprobePromise(input)
// Audio encoder
const bitrate = await getVideoStreamBitrate(input, probe)
const videoStreamDimensions = await getVideoStreamDimensionsInfo(input, probe)
let streamsToProcess: StreamType[] = [ 'audio', 'video' ]
if (!await hasAudioStream(input, probe)) {
localCommand = localCommand.noAudio()
streamsToProcess = [ 'video' ]
}
for (const streamType of streamsToProcess) {
const builderResult = await getEncoderBuilderResult({
...pick(options, [ 'availableEncoders', 'canCopyAudio', 'canCopyVideo' ]),
input,
inputBitrate: bitrate,
inputRatio: videoStreamDimensions?.ratio || 0,
profile,
resolution,
fps,
streamType,
videoType: 'vod' as 'vod'
})
if (!builderResult) {
throw new Error('No available encoder found for stream ' + streamType)
}
logger.debug(
'Apply ffmpeg params from %s for %s stream of input %s using %s profile.',
builderResult.encoder, streamType, input, profile,
{ builderResult, resolution, fps, ...lTags() }
)
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 })
}
return localCommand
}
function presetCopy (command: FfmpegCommand): FfmpegCommand {
return command
.format('mp4')
.videoCodec('copy')
.audioCodec('copy')
}
function presetOnlyAudio (command: FfmpegCommand): FfmpegCommand {
return command
.format('mp4')
.audioCodec('copy')
.noVideo()
}
function applyEncoderOptions (command: FfmpegCommand, options: EncoderOptions): FfmpegCommand {
return command
.inputOptions(options.inputOptions ?? [])
.outputOptions(options.outputOptions ?? [])
}
// ---------------------------------------------------------------------------
export {
presetVOD,
presetCopy,
presetOnlyAudio,
addDefaultEncoderGlobalParams,
addDefaultEncoderParams,
applyEncoderOptions
}