1 import { FfmpegCommand, FilterSpecification } from 'fluent-ffmpeg'
2 import { join } from 'path'
3 import { VIDEO_LIVE } from '@server/initializers/constants'
4 import { AvailableEncoders, LiveVideoLatencyMode } from '@shared/models'
5 import { logger, loggerTagsFactory } from '../logger'
6 import { buildStreamSuffix, getFFmpeg, getScaleFilter, StreamType } from './ffmpeg-commons'
7 import { getEncoderBuilderResult } from './ffmpeg-encoders'
8 import { addDefaultEncoderGlobalParams, addDefaultEncoderParams, applyEncoderOptions } from './ffmpeg-presets'
9 import { computeFPS } from './ffprobe-utils'
11 const lTags = loggerTagsFactory('ffmpeg')
13 async function getLiveTranscodingCommand (options: {
17 masterPlaylistName: string
18 latencyMode: LiveVideoLatencyMode
28 availableEncoders: AvailableEncoders
45 const command = getFFmpeg(inputUrl, 'live')
47 const varStreamMap: string[] = []
49 const complexFilter: FilterSpecification[] = [
53 options: resolutions.length,
54 outputs: resolutions.map(r => `vtemp${r}`)
58 command.outputOption('-sc_threshold 0')
60 addDefaultEncoderGlobalParams(command)
62 for (let i = 0; i < resolutions.length; i++) {
63 const streamMap: string[] = []
64 const resolution = resolutions[i]
65 const resolutionFPS = computeFPS(fps, resolution)
67 const baseEncoderBuilderParams = {
76 inputBitrate: bitrate,
83 videoType: 'live' as 'live'
87 const streamType: StreamType = 'video'
88 const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
90 throw new Error('No available live video encoder found')
93 command.outputOption(`-map [vout${resolution}]`)
95 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
98 'Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile,
99 { builderResult, fps: resolutionFPS, resolution, ...lTags() }
102 command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`)
103 applyEncoderOptions(command, builderResult.result)
106 inputs: `vtemp${resolution}`,
107 filter: getScaleFilter(builderResult.result),
108 options: `w=-2:h=${resolution}`,
109 outputs: `vout${resolution}`
112 streamMap.push(`v:${i}`)
116 const streamType: StreamType = 'audio'
117 const builderResult = await getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
118 if (!builderResult) {
119 throw new Error('No available live audio encoder found')
122 command.outputOption('-map a:0')
124 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
127 'Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile,
128 { builderResult, fps: resolutionFPS, resolution, ...lTags() }
131 command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`)
132 applyEncoderOptions(command, builderResult.result)
134 streamMap.push(`a:${i}`)
137 varStreamMap.push(streamMap.join(','))
140 command.complexFilter(complexFilter)
142 addDefaultLiveHLSParams({ command, outPath, masterPlaylistName, latencyMode })
144 command.outputOption('-var_stream_map', varStreamMap.join(' '))
149 function getLiveMuxingCommand (options: {
152 masterPlaylistName: string
153 latencyMode: LiveVideoLatencyMode
155 const { inputUrl, outPath, masterPlaylistName, latencyMode } = options
157 const command = getFFmpeg(inputUrl, 'live')
159 command.outputOption('-c:v copy')
160 command.outputOption('-c:a copy')
161 command.outputOption('-map 0:a?')
162 command.outputOption('-map 0:v?')
164 addDefaultLiveHLSParams({ command, outPath, masterPlaylistName, latencyMode })
169 function getLiveSegmentTime (latencyMode: LiveVideoLatencyMode) {
170 if (latencyMode === LiveVideoLatencyMode.SMALL_LATENCY) {
171 return VIDEO_LIVE.SEGMENT_TIME_SECONDS.SMALL_LATENCY
174 return VIDEO_LIVE.SEGMENT_TIME_SECONDS.DEFAULT_LATENCY
177 // ---------------------------------------------------------------------------
182 getLiveTranscodingCommand,
186 // ---------------------------------------------------------------------------
188 function addDefaultLiveHLSParams (options: {
189 command: FfmpegCommand
191 masterPlaylistName: string
192 latencyMode: LiveVideoLatencyMode
194 const { command, outPath, masterPlaylistName, latencyMode } = options
196 command.outputOption('-hls_time ' + getLiveSegmentTime(latencyMode))
197 command.outputOption('-hls_list_size ' + VIDEO_LIVE.SEGMENTS_LIST_SIZE)
198 command.outputOption('-hls_flags delete_segments+independent_segments+program_date_time')
199 command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%06d.ts')}`)
200 command.outputOption('-master_pl_name ' + masterPlaylistName)
201 command.outputOption(`-f hls`)
203 command.output(join(outPath, '%v.m3u8'))