mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-05 10:49:28 +02:00
Add basic video editor support
This commit is contained in:
parent
a24bf4dc65
commit
c729caf6cc
130 changed files with 3969 additions and 1353 deletions
156
server/helpers/ffmpeg/ffmpeg-presets.ts
Normal file
156
server/helpers/ffmpeg/ffmpeg-presets.ts
Normal file
|
@ -0,0 +1,156 @@
|
|||
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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue