mirror of https://github.com/Chocobozzz/PeerTube
				
				
				
			
		
			
				
	
	
		
			157 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
| 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
 | |
| }
 |