]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/helpers/ffmpeg-utils.ts
Support progress for ffmpeg tasks
[github/Chocobozzz/PeerTube.git] / server / helpers / ffmpeg-utils.ts
index 0aadf694ed5689e2b69f6ecb1423207dd14fd97f..a4d02908ddcd7ee7aa0e50ef8df57845077267da 100644 (file)
@@ -1,3 +1,4 @@
+import { Job } from 'bull'
 import * as ffmpeg from 'fluent-ffmpeg'
 import { readFile, remove, writeFile } from 'fs-extra'
 import { dirname, join } from 'path'
@@ -124,6 +125,8 @@ interface BaseTranscodeOptions {
   resolution: VideoResolution
 
   isPortraitMode?: boolean
+
+  job?: Job
 }
 
 interface HLSTranscodeOptions extends BaseTranscodeOptions {
@@ -137,6 +140,8 @@ interface HLSTranscodeOptions extends BaseTranscodeOptions {
 interface HLSFromTSTranscodeOptions extends BaseTranscodeOptions {
   type: 'hls-from-ts'
 
+  isAAC: boolean
+
   hlsPlaylist: {
     videoFilename: string
   }
@@ -186,7 +191,7 @@ async function transcode (options: TranscodeOptions) {
 
   command = await builders[options.type](command, options)
 
-  await runCommand(command)
+  await runCommand(command, options.job)
 
   await fixHLSPlaylistIfNeeded(options)
 }
@@ -208,7 +213,6 @@ async function getLiveTranscodingCommand (options: {
   const input = rtmpUrl
 
   const command = getFFmpeg(input, 'live')
-  command.inputOption('-fflags nobuffer')
 
   const varStreamMap: string[] = []
 
@@ -229,6 +233,7 @@ async function getLiveTranscodingCommand (options: {
   ])
 
   command.outputOption('-preset superfast')
+  command.outputOption('-sc_threshold 0')
 
   addDefaultEncoderGlobalParams({ command })
 
@@ -290,7 +295,6 @@ async function getLiveTranscodingCommand (options: {
 
 function getLiveMuxingCommand (rtmpUrl: string, outPath: string) {
   const command = getFFmpeg(rtmpUrl, 'live')
-  command.inputOption('-fflags nobuffer')
 
   command.outputOption('-c:v copy')
   command.outputOption('-c:a copy')
@@ -310,22 +314,6 @@ function buildStreamSuffix (base: string, streamNum?: number) {
   return base
 }
 
-// ---------------------------------------------------------------------------
-
-export {
-  getLiveTranscodingCommand,
-  getLiveMuxingCommand,
-  buildStreamSuffix,
-  convertWebPToJPG,
-  processGIF,
-  generateImageFromVideoFile,
-  TranscodeOptions,
-  TranscodeOptionsType,
-  transcode
-}
-
-// ---------------------------------------------------------------------------
-
 // ---------------------------------------------------------------------------
 // Default options
 // ---------------------------------------------------------------------------
@@ -371,7 +359,7 @@ function addDefaultEncoderParams (options: {
 function addDefaultLiveHLSParams (command: ffmpeg.FfmpegCommand, outPath: string) {
   command.outputOption('-hls_time ' + VIDEO_LIVE.SEGMENT_TIME_SECONDS)
   command.outputOption('-hls_list_size ' + VIDEO_LIVE.SEGMENTS_LIST_SIZE)
-  command.outputOption('-hls_flags delete_segments')
+  command.outputOption('-hls_flags delete_segments+independent_segments')
   command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%06d.ts')}`)
   command.outputOption('-master_pl_name master.m3u8')
   command.outputOption(`-f hls`)
@@ -456,11 +444,13 @@ async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTr
 async function buildHLSVODFromTSCommand (command: ffmpeg.FfmpegCommand, options: HLSFromTSTranscodeOptions) {
   const videoPath = getHLSVideoPath(options)
 
-  command.inputOption('-safe 0')
-  command.inputOption('-f concat')
+  command.outputOption('-c copy')
 
-  command.outputOption('-c:v copy')
-  command.audioFilter('aresample=async=1:first_pts=0')
+  if (options.isAAC) {
+    // Required for example when copying an AAC stream from an MPEG-TS
+    // Since it's a bitstream filter, we don't need to reencode the audio
+    command.outputOption('-bsf:a aac_adtstoasc')
+  }
 
   addCommonHLSVODCommandOptions(command, videoPath)
 
@@ -607,7 +597,10 @@ function presetOnlyAudio (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
 
 function getFFmpeg (input: string, type: 'live' | 'vod') {
   // We set cwd explicitly because ffmpeg appears to create temporary files when trancoding which fails in read-only file systems
-  const command = ffmpeg(input, { niceness: FFMPEG_NICE.TRANSCODING, cwd: CONFIG.STORAGE.TMP_DIR })
+  const command = ffmpeg(input, {
+    niceness: type === 'live' ? FFMPEG_NICE.LIVE : FFMPEG_NICE.VOD,
+    cwd: CONFIG.STORAGE.TMP_DIR
+  })
 
   const threads = type === 'live'
     ? CONFIG.LIVE.TRANSCODING.THREADS
@@ -621,21 +614,46 @@ function getFFmpeg (input: string, type: 'live' | 'vod') {
   return command
 }
 
-async function runCommand (command: ffmpeg.FfmpegCommand, onEnd?: Function) {
+async function runCommand (command: ffmpeg.FfmpegCommand, job?: Job) {
   return new Promise<void>((res, rej) => {
     command.on('error', (err, stdout, stderr) => {
-      if (onEnd) onEnd()
-
       logger.error('Error in transcoding job.', { stdout, stderr })
       rej(err)
     })
 
-    command.on('end', () => {
-      if (onEnd) onEnd()
+    command.on('end', (stdout, stderr) => {
+      logger.debug('FFmpeg command ended.', { stdout, stderr })
 
       res()
     })
 
+    if (job) {
+      command.on('progress', progress => {
+        if (!progress.percent) return
+
+        job.progress(Math.round(progress.percent))
+          .catch(err => logger.warn('Cannot set ffmpeg job progress.', { err }))
+      })
+    }
+
     command.run()
   })
 }
+
+// ---------------------------------------------------------------------------
+
+export {
+  getLiveTranscodingCommand,
+  getLiveMuxingCommand,
+  buildStreamSuffix,
+  convertWebPToJPG,
+  processGIF,
+  generateImageFromVideoFile,
+  TranscodeOptions,
+  TranscodeOptionsType,
+  transcode,
+  runCommand,
+
+  // builders
+  buildx264VODCommand
+}