1 import { FfmpegCommand } from 'fluent-ffmpeg'
2 import { pick } from 'lodash'
3 import { logger, loggerTagsFactory } from '@server/helpers/logger'
4 import { AvailableEncoders, EncoderOptions } from '@shared/models'
5 import { buildStreamSuffix, getScaleFilter, StreamType } from './ffmpeg-commons'
6 import { getEncoderBuilderResult } from './ffmpeg-encoders'
7 import { ffprobePromise, getVideoStreamBitrate, getVideoStreamDimensionsInfo, hasAudioStream } from './ffprobe-utils'
9 const lTags = loggerTagsFactory('ffmpeg')
11 // ---------------------------------------------------------------------------
13 function addDefaultEncoderGlobalParams (command: FfmpegCommand) {
14 // avoid issues when transcoding some files: https://trac.ffmpeg.org/ticket/6375
15 command.outputOption('-max_muxing_queue_size 1024')
17 .outputOption('-map_metadata -1')
18 // allows import of source material with incompatible pixel formats (e.g. MJPEG video)
19 .outputOption('-pix_fmt yuv420p')
22 function addDefaultEncoderParams (options: {
23 command: FfmpegCommand
24 encoder: 'libx264' | string
29 const { command, encoder, fps, streamNum } = options
31 if (encoder === 'libx264') {
32 // 3.1 is the minimal resource allocation for our highest supported resolution
33 command.outputOption(buildStreamSuffix('-level:v', streamNum) + ' 3.1')
36 // Keyframe interval of 2 seconds for faster seeking and resolution switching.
37 // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html
38 // https://superuser.com/a/908325
39 command.outputOption(buildStreamSuffix('-g:v', streamNum) + ' ' + (fps * 2))
44 // ---------------------------------------------------------------------------
46 async function presetVOD (options: {
47 command: FfmpegCommand
50 availableEncoders: AvailableEncoders
59 scaleFilterValue?: string
61 const { command, input, profile, resolution, fps, scaleFilterValue } = options
63 let localCommand = command
65 .outputOption('-movflags faststart')
67 addDefaultEncoderGlobalParams(command)
69 const probe = await ffprobePromise(input)
72 const bitrate = await getVideoStreamBitrate(input, probe)
73 const videoStreamDimensions = await getVideoStreamDimensionsInfo(input, probe)
75 let streamsToProcess: StreamType[] = [ 'audio', 'video' ]
77 if (!await hasAudioStream(input, probe)) {
78 localCommand = localCommand.noAudio()
79 streamsToProcess = [ 'video' ]
82 for (const streamType of streamsToProcess) {
83 const builderResult = await getEncoderBuilderResult({
84 ...pick(options, [ 'availableEncoders', 'canCopyAudio', 'canCopyVideo' ]),
87 inputBitrate: bitrate,
88 inputRatio: videoStreamDimensions?.ratio || 0,
95 videoType: 'vod' as 'vod'
99 throw new Error('No available encoder found for stream ' + streamType)
103 'Apply ffmpeg params from %s for %s stream of input %s using %s profile.',
104 builderResult.encoder, streamType, input, profile,
105 { builderResult, resolution, fps, ...lTags() }
108 if (streamType === 'video') {
109 localCommand.videoCodec(builderResult.encoder)
111 if (scaleFilterValue) {
112 localCommand.outputOption(`-vf ${getScaleFilter(builderResult.result)}=${scaleFilterValue}`)
114 } else if (streamType === 'audio') {
115 localCommand.audioCodec(builderResult.encoder)
118 applyEncoderOptions(localCommand, builderResult.result)
119 addDefaultEncoderParams({ command: localCommand, encoder: builderResult.encoder, fps })
125 function presetCopy (command: FfmpegCommand): FfmpegCommand {
132 function presetOnlyAudio (command: FfmpegCommand): FfmpegCommand {
139 function applyEncoderOptions (command: FfmpegCommand, options: EncoderOptions): FfmpegCommand {
141 .inputOptions(options.inputOptions ?? [])
142 .outputOptions(options.outputOptions ?? [])
145 // ---------------------------------------------------------------------------
152 addDefaultEncoderGlobalParams,
153 addDefaultEncoderParams,