mirror of https://github.com/Chocobozzz/PeerTube
				
				
				
			
		
			
				
	
	
		
			205 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			5.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
import { FfmpegCommand, FilterSpecification } from 'fluent-ffmpeg'
 | 
						|
import { join } from 'path'
 | 
						|
import { VIDEO_LIVE } from '@server/initializers/constants'
 | 
						|
import { AvailableEncoders, LiveVideoLatencyMode } from '@shared/models'
 | 
						|
import { logger, loggerTagsFactory } from '../logger'
 | 
						|
import { buildStreamSuffix, getFFmpeg, getScaleFilter, StreamType } from './ffmpeg-commons'
 | 
						|
import { getEncoderBuilderResult } from './ffmpeg-encoders'
 | 
						|
import { addDefaultEncoderGlobalParams, addDefaultEncoderParams, applyEncoderOptions } from './ffmpeg-presets'
 | 
						|
import { computeFPS } from './ffprobe-utils'
 | 
						|
 | 
						|
const lTags = loggerTagsFactory('ffmpeg')
 | 
						|
 | 
						|
async function getLiveTranscodingCommand (options: {
 | 
						|
  inputUrl: string
 | 
						|
 | 
						|
  outPath: string
 | 
						|
  masterPlaylistName: string
 | 
						|
  latencyMode: LiveVideoLatencyMode
 | 
						|
 | 
						|
  resolutions: number[]
 | 
						|
 | 
						|
  // Input information
 | 
						|
  fps: number
 | 
						|
  bitrate: number
 | 
						|
  ratio: number
 | 
						|
  hasAudio: boolean
 | 
						|
 | 
						|
  availableEncoders: AvailableEncoders
 | 
						|
  profile: string
 | 
						|
}) {
 | 
						|
  const {
 | 
						|
    inputUrl,
 | 
						|
    outPath,
 | 
						|
    resolutions,
 | 
						|
    fps,
 | 
						|
    bitrate,
 | 
						|
    availableEncoders,
 | 
						|
    profile,
 | 
						|
    masterPlaylistName,
 | 
						|
    ratio,
 | 
						|
    latencyMode,
 | 
						|
    hasAudio
 | 
						|
  } = options
 | 
						|
 | 
						|
  const command = getFFmpeg(inputUrl, 'live')
 | 
						|
 | 
						|
  const varStreamMap: string[] = []
 | 
						|
 | 
						|
  const complexFilter: FilterSpecification[] = [
 | 
						|
    {
 | 
						|
      inputs: '[v:0]',
 | 
						|
      filter: 'split',
 | 
						|
      options: resolutions.length,
 | 
						|
      outputs: resolutions.map(r => `vtemp${r}`)
 | 
						|
    }
 | 
						|
  ]
 | 
						|
 | 
						|
  command.outputOption('-sc_threshold 0')
 | 
						|
 | 
						|
  addDefaultEncoderGlobalParams(command)
 | 
						|
 | 
						|
  for (let i = 0; i < resolutions.length; i++) {
 | 
						|
    const streamMap: string[] = []
 | 
						|
    const resolution = resolutions[i]
 | 
						|
    const resolutionFPS = computeFPS(fps, resolution)
 | 
						|
 | 
						|
    const baseEncoderBuilderParams = {
 | 
						|
      input: inputUrl,
 | 
						|
 | 
						|
      availableEncoders,
 | 
						|
      profile,
 | 
						|
 | 
						|
      canCopyAudio: true,
 | 
						|
      canCopyVideo: true,
 | 
						|
 | 
						|
      inputBitrate: bitrate,
 | 
						|
      inputRatio: ratio,
 | 
						|
 | 
						|
      resolution,
 | 
						|
      fps: resolutionFPS,
 | 
						|
 | 
						|
      streamNum: i,
 | 
						|
      videoType: 'live' as 'live'
 | 
						|
    }
 | 
						|
 | 
						|
    {
 | 
						|
      const streamType: StreamType = 'video'
 | 
						|
      const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
 | 
						|
      if (!builderResult) {
 | 
						|
        throw new Error('No available live video encoder found')
 | 
						|
      }
 | 
						|
 | 
						|
      command.outputOption(`-map [vout${resolution}]`)
 | 
						|
 | 
						|
      addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
 | 
						|
 | 
						|
      logger.debug(
 | 
						|
        'Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile,
 | 
						|
        { builderResult, fps: resolutionFPS, resolution, ...lTags() }
 | 
						|
      )
 | 
						|
 | 
						|
      command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`)
 | 
						|
      applyEncoderOptions(command, builderResult.result)
 | 
						|
 | 
						|
      complexFilter.push({
 | 
						|
        inputs: `vtemp${resolution}`,
 | 
						|
        filter: getScaleFilter(builderResult.result),
 | 
						|
        options: `w=-2:h=${resolution}`,
 | 
						|
        outputs: `vout${resolution}`
 | 
						|
      })
 | 
						|
 | 
						|
      streamMap.push(`v:${i}`)
 | 
						|
    }
 | 
						|
 | 
						|
    if (hasAudio) {
 | 
						|
      const streamType: StreamType = 'audio'
 | 
						|
      const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
 | 
						|
      if (!builderResult) {
 | 
						|
        throw new Error('No available live audio encoder found')
 | 
						|
      }
 | 
						|
 | 
						|
      command.outputOption('-map a:0')
 | 
						|
 | 
						|
      addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
 | 
						|
 | 
						|
      logger.debug(
 | 
						|
        'Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile,
 | 
						|
        { builderResult, fps: resolutionFPS, resolution, ...lTags() }
 | 
						|
      )
 | 
						|
 | 
						|
      command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`)
 | 
						|
      applyEncoderOptions(command, builderResult.result)
 | 
						|
 | 
						|
      streamMap.push(`a:${i}`)
 | 
						|
    }
 | 
						|
 | 
						|
    varStreamMap.push(streamMap.join(','))
 | 
						|
  }
 | 
						|
 | 
						|
  command.complexFilter(complexFilter)
 | 
						|
 | 
						|
  addDefaultLiveHLSParams({ command, outPath, masterPlaylistName, latencyMode })
 | 
						|
 | 
						|
  command.outputOption('-var_stream_map', varStreamMap.join(' '))
 | 
						|
 | 
						|
  return command
 | 
						|
}
 | 
						|
 | 
						|
function getLiveMuxingCommand (options: {
 | 
						|
  inputUrl: string
 | 
						|
  outPath: string
 | 
						|
  masterPlaylistName: string
 | 
						|
  latencyMode: LiveVideoLatencyMode
 | 
						|
}) {
 | 
						|
  const { inputUrl, outPath, masterPlaylistName, latencyMode } = options
 | 
						|
 | 
						|
  const command = getFFmpeg(inputUrl, 'live')
 | 
						|
 | 
						|
  command.outputOption('-c:v copy')
 | 
						|
  command.outputOption('-c:a copy')
 | 
						|
  command.outputOption('-map 0:a?')
 | 
						|
  command.outputOption('-map 0:v?')
 | 
						|
 | 
						|
  addDefaultLiveHLSParams({ command, outPath, masterPlaylistName, latencyMode })
 | 
						|
 | 
						|
  return command
 | 
						|
}
 | 
						|
 | 
						|
function getLiveSegmentTime (latencyMode: LiveVideoLatencyMode) {
 | 
						|
  if (latencyMode === LiveVideoLatencyMode.SMALL_LATENCY) {
 | 
						|
    return VIDEO_LIVE.SEGMENT_TIME_SECONDS.SMALL_LATENCY
 | 
						|
  }
 | 
						|
 | 
						|
  return VIDEO_LIVE.SEGMENT_TIME_SECONDS.DEFAULT_LATENCY
 | 
						|
}
 | 
						|
 | 
						|
// ---------------------------------------------------------------------------
 | 
						|
 | 
						|
export {
 | 
						|
  getLiveSegmentTime,
 | 
						|
 | 
						|
  getLiveTranscodingCommand,
 | 
						|
  getLiveMuxingCommand
 | 
						|
}
 | 
						|
 | 
						|
// ---------------------------------------------------------------------------
 | 
						|
 | 
						|
function addDefaultLiveHLSParams (options: {
 | 
						|
  command: FfmpegCommand
 | 
						|
  outPath: string
 | 
						|
  masterPlaylistName: string
 | 
						|
  latencyMode: LiveVideoLatencyMode
 | 
						|
}) {
 | 
						|
  const { command, outPath, masterPlaylistName, latencyMode } = options
 | 
						|
 | 
						|
  command.outputOption('-hls_time ' + getLiveSegmentTime(latencyMode))
 | 
						|
  command.outputOption('-hls_list_size ' + VIDEO_LIVE.SEGMENTS_LIST_SIZE)
 | 
						|
  command.outputOption('-hls_flags delete_segments+independent_segments+program_date_time')
 | 
						|
  command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%06d.ts')}`)
 | 
						|
  command.outputOption('-master_pl_name ' + masterPlaylistName)
 | 
						|
  command.outputOption(`-f hls`)
 | 
						|
 | 
						|
  command.output(join(outPath, '%v.m3u8'))
 | 
						|
}
 |