Chocobozzz/PeerTube

View on GitHub
packages/ffmpeg/src/shared/presets.ts

Summary

Maintainability
B
4 hrs
Test Coverage
import { pick } from '@peertube/peertube-core-utils'
import { FFmpegCommandWrapper } from '../ffmpeg-command-wrapper.js'
import { getScaleFilter, StreamType } from '../ffmpeg-utils.js'
import { ffprobePromise, getVideoStreamBitrate, getVideoStreamDimensionsInfo, hasAudioStream } from '../ffprobe.js'
import { addDefaultEncoderGlobalParams, addDefaultEncoderParams, applyEncoderOptions } from './encoder-options.js'

export async function presetVOD (options: {
  commandWrapper: FFmpegCommandWrapper

  videoInputPath: string
  separatedAudioInputPath?: string

  canCopyAudio: boolean
  canCopyVideo: boolean

  resolution: number
  fps: number

  videoStreamOnly: boolean

  scaleFilterValue?: string
}) {
  const { commandWrapper, videoInputPath, separatedAudioInputPath, resolution, fps, videoStreamOnly, scaleFilterValue } = options

  if (videoStreamOnly && !resolution) {
    throw new Error('Cannot generate video stream only without valid resolution')
  }

  const command = commandWrapper.getCommand()

  command.format('mp4')
    .outputOption('-movflags faststart')

  addDefaultEncoderGlobalParams(command)

  const videoProbe = await ffprobePromise(videoInputPath)
  const audioProbe = separatedAudioInputPath
    ? await ffprobePromise(separatedAudioInputPath)
    : videoProbe

  // Audio encoder
  const bitrate = await getVideoStreamBitrate(videoInputPath, videoProbe)
  const videoStreamDimensions = await getVideoStreamDimensionsInfo(videoInputPath, videoProbe)

  let streamsToProcess: StreamType[] = [ 'audio', 'video' ]

  if (videoStreamOnly || !await hasAudioStream(separatedAudioInputPath || videoInputPath, audioProbe)) {
    command.noAudio()
    streamsToProcess = [ 'video' ]
  } else if (!resolution) {
    command.noVideo()
    streamsToProcess = [ 'audio' ]
  }

  for (const streamType of streamsToProcess) {
    const input = streamType === 'video'
      ? videoInputPath
      : separatedAudioInputPath || videoInputPath

    const builderResult = await commandWrapper.getEncoderBuilderResult({
      ...pick(options, [ 'canCopyAudio', 'canCopyVideo' ]),

      input,
      inputProbe: streamType === 'video'
        ? videoProbe
        : audioProbe,

      inputBitrate: bitrate,
      inputRatio: videoStreamDimensions?.ratio || 0,

      resolution,
      fps,
      streamType,

      videoType: 'vod' as 'vod'
    })

    if (!builderResult) {
      throw new Error('No available encoder found for stream ' + streamType)
    }

    commandWrapper.debugLog(
      `Apply ffmpeg params from ${builderResult.encoder} for ${streamType} ` +
      `stream of input ${input} using ${commandWrapper.getProfile()} profile.`,
      { builderResult, resolution, fps }
    )

    if (streamType === 'video') {
      command.videoCodec(builderResult.encoder)

      if (scaleFilterValue) {
        command.outputOption(`-vf ${getScaleFilter(builderResult.result)}=${scaleFilterValue}`)
      }
    } else if (streamType === 'audio') {
      command.audioCodec(builderResult.encoder)
    }

    applyEncoderOptions(command, builderResult.result)
    addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps })
  }
}

export function presetCopy (commandWrapper: FFmpegCommandWrapper, options: {
  withAudio?: boolean // default true
  withVideo?: boolean // default true
} = {}) {
  const command = commandWrapper.getCommand()

  command.format('mp4')

  if (options.withAudio === false) command.noAudio()
  else command.audioCodec('copy')

  if (options.withVideo === false) command.noVideo()
  else command.videoCodec('copy')
}