+function convertWebPToJPG (path: string, destination: string): Promise<void> {
+ return new Promise<void>(async (res, rej) => {
+ try {
+ const command = ffmpeg(path).output(destination)
+
+ command.on('error', (err, stdout, stderr) => {
+ logger.error('Error in ffmpeg webp convert process.', { stdout, stderr })
+ return rej(err)
+ })
+ .on('end', () => res())
+ .run()
+ } catch (err) {
+ return rej(err)
+ }
+ })
+}
+
+function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: number[], deleteSegments: boolean) {
+ const command = getFFmpeg(rtmpUrl)
+ command.inputOption('-fflags nobuffer')
+
+ 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}`
+ }))
+ ])
+
+ const liveFPS = VIDEO_TRANSCODING_FPS.AVERAGE
+
+ command.withFps(liveFPS)
+
+ command.outputOption('-b_strategy 1')
+ command.outputOption('-bf 16')
+ command.outputOption('-preset superfast')
+ command.outputOption('-level 3.1')
+ command.outputOption('-map_metadata -1')
+ command.outputOption('-pix_fmt yuv420p')
+
+ for (let i = 0; i < resolutions.length; i++) {
+ const resolution = resolutions[i]
+
+ command.outputOption(`-map [vout${resolution}]`)
+ command.outputOption(`-c:v:${i} libx264`)
+ command.outputOption(`-b:v:${i} ${getTargetBitrate(resolution, liveFPS, VIDEO_TRANSCODING_FPS)}`)
+
+ command.outputOption(`-map a:0`)
+ command.outputOption(`-c:a:${i} aac`)
+
+ varStreamMap.push(`v:${i},a:${i}`)
+ }
+
+ addDefaultLiveHLSParams(command, outPath, deleteSegments)
+
+ command.outputOption('-var_stream_map', varStreamMap.join(' '))
+
+ command.run()
+
+ return command
+}
+
+function runLiveMuxing (rtmpUrl: string, outPath: string, deleteSegments: boolean) {
+ const command = getFFmpeg(rtmpUrl)
+ command.inputOption('-fflags nobuffer')
+
+ command.outputOption('-c:v copy')
+ command.outputOption('-c:a copy')
+ command.outputOption('-map 0:a?')
+ command.outputOption('-map 0:v?')
+
+ addDefaultLiveHLSParams(command, outPath, deleteSegments)
+
+ command.run()
+
+ return command
+}
+
+async function hlsPlaylistToFragmentedMP4 (hlsDirectory: string, segmentFiles: string[], outputPath: string) {
+ const concatFile = 'concat.txt'
+ const concatFilePath = join(hlsDirectory, concatFile)
+ const content = segmentFiles.map(f => 'file ' + f)
+ .join('\n')
+
+ await writeFile(concatFilePath, content + '\n')
+
+ const command = getFFmpeg(concatFilePath)
+ command.inputOption('-safe 0')
+ command.inputOption('-f concat')
+
+ command.outputOption('-c copy')
+ command.output(outputPath)
+
+ command.run()
+
+ function cleaner () {
+ remove(concatFile)
+ .catch(err => logger.error('Cannot remove concat file in %s.', hlsDirectory, { err }))
+ }
+
+ return new Promise<string>((res, rej) => {
+ command.on('error', err => {
+ cleaner()
+
+ rej(err)
+ })
+
+ command.on('end', () => {
+ cleaner()
+
+ res()
+ })
+ })
+}
+