1 import { FilterSpecification } from 'fluent-ffmpeg'
2 import { join } from 'path'
3 import { pick } from '@shared/core-utils'
4 import { FFmpegCommandWrapper, FFmpegCommandWrapperOptions } from './ffmpeg-command-wrapper'
5 import { buildStreamSuffix, getScaleFilter, StreamType } from './ffmpeg-utils'
6 import { addDefaultEncoderGlobalParams, addDefaultEncoderParams, applyEncoderOptions } from './shared'
8 export class FFmpegLive {
9 private readonly commandWrapper: FFmpegCommandWrapper
11 constructor (options: FFmpegCommandWrapperOptions) {
12 this.commandWrapper = new FFmpegCommandWrapper(options)
15 async getLiveTranscodingCommand (options: {
19 masterPlaylistName: string
31 segmentListSize: number
32 segmentDuration: number
43 const command = this.commandWrapper.buildCommand(inputUrl)
45 const varStreamMap: string[] = []
47 const complexFilter: FilterSpecification[] = [
51 options: toTranscode.length,
52 outputs: toTranscode.map(t => `vtemp${t.resolution}`)
56 command.outputOption('-sc_threshold 0')
58 addDefaultEncoderGlobalParams(command)
60 for (let i = 0; i < toTranscode.length; i++) {
61 const streamMap: string[] = []
62 const { resolution, fps } = toTranscode[i]
64 const baseEncoderBuilderParams = {
70 inputBitrate: bitrate,
77 videoType: 'live' as 'live'
81 const streamType: StreamType = 'video'
82 const builderResult = await this.commandWrapper.getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
84 throw new Error('No available live video encoder found')
87 command.outputOption(`-map [vout${resolution}]`)
89 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps, streamNum: i })
91 this.commandWrapper.debugLog(
92 `Apply ffmpeg live video params from ${builderResult.encoder} using ${this.commandWrapper.getProfile()} profile.`,
93 { builderResult, fps, toTranscode }
96 command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`)
97 applyEncoderOptions(command, builderResult.result)
100 inputs: `vtemp${resolution}`,
101 filter: getScaleFilter(builderResult.result),
102 options: `w=-2:h=${resolution}`,
103 outputs: `vout${resolution}`
106 streamMap.push(`v:${i}`)
110 const streamType: StreamType = 'audio'
111 const builderResult = await this.commandWrapper.getEncoderBuilderResult({ ...baseEncoderBuilderParams, streamType })
112 if (!builderResult) {
113 throw new Error('No available live audio encoder found')
116 command.outputOption('-map a:0')
118 addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps, streamNum: i })
120 this.commandWrapper.debugLog(
121 `Apply ffmpeg live audio params from ${builderResult.encoder} using ${this.commandWrapper.getProfile()} profile.`,
122 { builderResult, fps, resolution }
125 command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`)
126 applyEncoderOptions(command, builderResult.result)
128 streamMap.push(`a:${i}`)
131 varStreamMap.push(streamMap.join(','))
134 command.complexFilter(complexFilter)
136 this.addDefaultLiveHLSParams({ ...pick(options, [ 'segmentDuration', 'segmentListSize' ]), outPath, masterPlaylistName })
138 command.outputOption('-var_stream_map', varStreamMap.join(' '))
143 getLiveMuxingCommand (options: {
146 masterPlaylistName: string
148 segmentListSize: number
149 segmentDuration: number
151 const { inputUrl, outPath, masterPlaylistName } = options
153 const command = this.commandWrapper.buildCommand(inputUrl)
155 command.outputOption('-c:v copy')
156 command.outputOption('-c:a copy')
157 command.outputOption('-map 0:a?')
158 command.outputOption('-map 0:v?')
160 this.addDefaultLiveHLSParams({ ...pick(options, [ 'segmentDuration', 'segmentListSize' ]), outPath, masterPlaylistName })
165 private addDefaultLiveHLSParams (options: {
167 masterPlaylistName: string
168 segmentListSize: number
169 segmentDuration: number
171 const { outPath, masterPlaylistName, segmentListSize, segmentDuration } = options
173 const command = this.commandWrapper.getCommand()
175 command.outputOption('-hls_time ' + segmentDuration)
176 command.outputOption('-hls_list_size ' + segmentListSize)
177 command.outputOption('-hls_flags delete_segments+independent_segments+program_date_time')
178 command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%06d.ts')}`)
179 command.outputOption('-master_pl_name ' + masterPlaylistName)
180 command.outputOption(`-f hls`)
182 command.output(join(outPath, '%v.m3u8'))