import * as ffmpeg from 'fluent-ffmpeg'
import { readFile, remove, writeFile } from 'fs-extra'
import { dirname, join } from 'path'
-import { FFMPEG_NICE, VIDEO_LIVE, VIDEO_TRANSCODING_ENCODERS, VIDEO_TRANSCODING_FPS } from '@server/initializers/constants'
+import { FFMPEG_NICE, VIDEO_LIVE, VIDEO_TRANSCODING_ENCODERS } from '@server/initializers/constants'
import { VideoResolution } from '../../shared/models/videos'
import { checkFFmpegEncoders } from '../initializers/checker-before-init'
import { CONFIG } from '../initializers/config'
-import { getAudioStream, getClosestFramerateStandard, getVideoFileFPS } from './ffprobe-utils'
+import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils'
import { processImage } from './image-utils'
import { logger } from './logger'
command.outputOption('-preset superfast')
+ addDefaultEncoderGlobalParams({ command })
+
for (let i = 0; i < resolutions.length; i++) {
const resolution = resolutions[i]
- const baseEncoderBuilderParams = { input, availableEncoders, profile, fps, resolution, streamNum: i, videoType: 'live' as 'live' }
+ const resolutionFPS = computeFPS(fps, resolution)
+
+ const baseEncoderBuilderParams = {
+ input,
+ availableEncoders,
+ profile,
+ fps: resolutionFPS,
+ resolution,
+ streamNum: i,
+ videoType: 'live' as 'live'
+ }
{
const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType: 'VIDEO' }))
command.outputOption(`-map [vout${resolution}]`)
- addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps, streamNum: i })
+ addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
logger.debug('Apply ffmpeg live video params from %s.', builderResult.encoder, builderResult)
command.outputOption('-map a:0')
- addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps, streamNum: i })
+ addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
logger.debug('Apply ffmpeg live audio params from %s.', builderResult.encoder, builderResult)
// Default options
// ---------------------------------------------------------------------------
+function addDefaultEncoderGlobalParams (options: {
+ command: ffmpeg.FfmpegCommand
+}) {
+ const { command } = options
+
+ // 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')
+ // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it
+ .outputOption('-b_strategy 1')
+ // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16
+ .outputOption('-bf 16')
+ // allows import of source material with incompatible pixel formats (e.g. MJPEG video)
+ .outputOption('-pix_fmt yuv420p')
+}
+
function addDefaultEncoderParams (options: {
command: ffmpeg.FfmpegCommand
encoder: 'libx264' | string
if (encoder === 'libx264') {
// 3.1 is the minimal resource allocation for our highest supported resolution
- command.outputOption('-level 3.1')
- // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it
- .outputOption('-b_strategy 1')
- // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16
- .outputOption('-bf 16')
- // allows import of source material with incompatible pixel formats (e.g. MJPEG video)
- .outputOption(buildStreamSuffix('-pix_fmt', streamNum) + ' yuv420p')
- // strip all metadata
- .outputOption('-map_metadata -1')
- // avoid issues when transcoding some files: https://trac.ffmpeg.org/ticket/6375
- .outputOption(buildStreamSuffix('-max_muxing_queue_size', streamNum) + ' 1024')
+ 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('-g ' + (fps * 2))
+ command.outputOption(buildStreamSuffix('-g:v', streamNum) + ' ' + (fps * 2))
}
}
}
async function buildx264VODCommand (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) {
let fps = await getVideoFileFPS(options.inputPath)
- if (
- // On small/medium resolutions, limit FPS
- options.resolution !== undefined &&
- options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
- fps > VIDEO_TRANSCODING_FPS.AVERAGE
- ) {
- // Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value
- fps = getClosestFramerateStandard(fps, 'STANDARD')
- }
+ fps = computeFPS(fps, options.resolution)
command = await presetVideo(command, options.inputPath, options, fps)
command = command.size(size)
}
- // Hard FPS limits
- if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD')
- else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN
-
- command = command.withFPS(fps)
-
return command
}
command = await presetVideo(command, options.audioPath, options)
- /*
- MAIN reference: https://slhck.info/video/2017/03/01/rate-control.html
- Our target situation is closer to a livestream than a stream,
- since we want to reduce as much a possible the encoding burden,
- although not to the point of a livestream where there is a hard
- constraint on the frames per second to be encoded.
- */
command.outputOption('-preset:v veryfast')
command = command.input(options.audioPath)
.format('mp4')
.outputOption('-movflags faststart')
+ addDefaultEncoderGlobalParams({ command })
+
// Audio encoder
const parsedAudio = await getAudioStream(input)