-function transcode (options: TranscodeOptions) {
- return new Promise<void>(async (res, rej) => {
- try {
- let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING })
- .output(options.outputPath)
-
- if (options.type === 'quick-transcode') {
- command = await buildQuickTranscodeCommand(command)
- } else if (options.type === 'hls') {
- command = await buildHLSCommand(command, options)
- } else if (options.type === 'merge-audio') {
- command = await buildAudioMergeCommand(command, options)
- } else {
- command = await buildx264Command(command, options)
+type TranscodeOptions =
+ HLSTranscodeOptions
+ | HLSFromTSTranscodeOptions
+ | VideoTranscodeOptions
+ | MergeAudioTranscodeOptions
+ | OnlyAudioTranscodeOptions
+ | QuickTranscodeOptions
+
+const builders: {
+ [ type in TranscodeOptionsType ]: (c: ffmpeg.FfmpegCommand, o?: TranscodeOptions) => Promise<ffmpeg.FfmpegCommand> | ffmpeg.FfmpegCommand
+} = {
+ 'quick-transcode': buildQuickTranscodeCommand,
+ 'hls': buildHLSVODCommand,
+ 'hls-from-ts': buildHLSVODFromTSCommand,
+ 'merge-audio': buildAudioMergeCommand,
+ 'only-audio': buildOnlyAudioCommand,
+ 'video': buildx264VODCommand
+}
+
+async function transcode (options: TranscodeOptions) {
+ logger.debug('Will run transcode.', { options })
+
+ let command = getFFmpeg(options.inputPath, 'vod')
+ .output(options.outputPath)
+
+ command = await builders[options.type](command, options)
+
+ await runCommand({ command, job: options.job })
+
+ await fixHLSPlaylistIfNeeded(options)
+}
+
+// ---------------------------------------------------------------------------
+// Live muxing/transcoding functions
+// ---------------------------------------------------------------------------
+
+async function getLiveTranscodingCommand (options: {
+ rtmpUrl: string
+ outPath: string
+ resolutions: number[]
+ fps: number
+
+ availableEncoders: AvailableEncoders
+ profile: string
+}) {
+ const { rtmpUrl, outPath, resolutions, fps, availableEncoders, profile } = options
+ const input = rtmpUrl
+
+ const command = getFFmpeg(input, 'live')
+
+ const varStreamMap: string[] = []
+
+ command.complexFilter([
+ {
+ inputs: '[v:0]',
+ filter: 'split',
+ options: resolutions.length,
+ outputs: resolutions.map(r => `vtemp${r}`)
+ },
+
+ ...resolutions.map(r => ({
+ inputs: `vtemp${r}`,
+ filter: 'scale',
+ options: `w=-2:h=${r}`,
+ outputs: `vout${r}`
+ }))
+ ])
+
+ command.outputOption('-preset superfast')
+ command.outputOption('-sc_threshold 0')
+
+ addDefaultEncoderGlobalParams({ command })
+
+ for (let i = 0; i < resolutions.length; i++) {
+ const resolution = resolutions[i]
+ const resolutionFPS = computeFPS(fps, resolution)
+
+ const baseEncoderBuilderParams = {
+ input,
+
+ availableEncoders,
+ profile,
+
+ fps: resolutionFPS,
+ resolution,
+ streamNum: i,
+ videoType: 'live' as 'live'
+ }
+
+ {
+ const streamType: StreamType = 'video'
+ const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType }))
+ if (!builderResult) {
+ throw new Error('No available live video encoder found')