]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/helpers/ffmpeg-utils.ts
Fix jobs pagination
[github/Chocobozzz/PeerTube.git] / server / helpers / ffmpeg-utils.ts
index fac2595f1001559e79d7a5e0212cf177fdca77f4..69564a1a3b4c068b4aaad19987c5b3b0a7b0d74a 100644 (file)
@@ -5,7 +5,7 @@ import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata'
 import { getMaxBitrate, getTargetBitrate, VideoResolution } from '../../shared/models/videos'
 import { checkFFmpegEncoders } from '../initializers/checker-before-init'
 import { CONFIG } from '../initializers/config'
-import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
+import { FFMPEG_NICE, VIDEO_LIVE, VIDEO_TRANSCODING_FPS } from '../initializers/constants'
 import { processImage } from './image-utils'
 import { logger } from './logger'
 
@@ -271,6 +271,8 @@ type TranscodeOptions =
   | QuickTranscodeOptions
 
 function transcode (options: TranscodeOptions) {
+  logger.debug('Will run transcode.', { options })
+
   return new Promise<void>(async (res, rej) => {
     try {
       let command = getFFmpeg(options.inputPath)
@@ -353,7 +355,7 @@ function convertWebPToJPG (path: string, destination: string): Promise<void> {
   })
 }
 
-function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: number[]) {
+function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: number[], fps, deleteSegments: boolean) {
   const command = getFFmpeg(rtmpUrl)
   command.inputOption('-fflags nobuffer')
 
@@ -375,23 +377,21 @@ function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: numb
     }))
   ])
 
-  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')
+  command.outputOption('-max_muxing_queue_size 1024')
+  command.outputOption('-g ' + (fps * 2))
 
   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(`-b:v:${i} ${getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS)}`)
 
     command.outputOption(`-map a:0`)
     command.outputOption(`-c:a:${i} aac`)
@@ -399,7 +399,7 @@ function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: numb
     varStreamMap.push(`v:${i},a:${i}`)
   }
 
-  addDefaultLiveHLSParams(command, outPath)
+  addDefaultLiveHLSParams(command, outPath, deleteSegments)
 
   command.outputOption('-var_stream_map', varStreamMap.join(' '))
 
@@ -408,7 +408,7 @@ function runLiveTranscoding (rtmpUrl: string, outPath: string, resolutions: numb
   return command
 }
 
-function runLiveMuxing (rtmpUrl: string, outPath: string) {
+function runLiveMuxing (rtmpUrl: string, outPath: string, deleteSegments: boolean) {
   const command = getFFmpeg(rtmpUrl)
   command.inputOption('-fflags nobuffer')
 
@@ -417,13 +417,56 @@ function runLiveMuxing (rtmpUrl: string, outPath: string) {
   command.outputOption('-map 0:a?')
   command.outputOption('-map 0:v?')
 
-  addDefaultLiveHLSParams(command, outPath)
+  addDefaultLiveHLSParams(command, outPath, deleteSegments)
 
   command.run()
 
   return command
 }
 
+async function hlsPlaylistToFragmentedMP4 (hlsDirectory: string, segmentFiles: string[], outputPath: string) {
+  const concatFilePath = join(hlsDirectory, 'concat.txt')
+
+  function cleaner () {
+    remove(concatFilePath)
+      .catch(err => logger.error('Cannot remove concat file in %s.', hlsDirectory, { err }))
+  }
+
+  // First concat the ts files to a mp4 file
+  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:v copy')
+  command.audioFilter('aresample=async=1:first_pts=0')
+  command.output(outputPath)
+
+  return runCommand(command, cleaner)
+}
+
+async function runCommand (command: ffmpeg.FfmpegCommand, onEnd?: Function) {
+  command.run()
+
+  return new Promise<string>((res, rej) => {
+    command.on('error', err => {
+      if (onEnd) onEnd()
+
+      rej(err)
+    })
+
+    command.on('end', () => {
+      if (onEnd) onEnd()
+
+      res()
+    })
+  })
+}
+
 // ---------------------------------------------------------------------------
 
 export {
@@ -443,6 +486,7 @@ export {
   getVideoFileFPS,
   computeResolutionsToTranscode,
   audio,
+  hlsPlaylistToFragmentedMP4,
   getVideoFileBitrate,
   canDoQuickTranscode
 }
@@ -457,11 +501,15 @@ function addDefaultX264Params (command: ffmpeg.FfmpegCommand) {
          .outputOption('-map_metadata -1') // strip all metadata
 }
 
-function addDefaultLiveHLSParams (command: ffmpeg.FfmpegCommand, outPath: string) {
-  command.outputOption('-hls_time 4')
-  command.outputOption('-hls_list_size 15')
-  command.outputOption('-hls_flags delete_segments')
-  command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%d.ts')}`)
+function addDefaultLiveHLSParams (command: ffmpeg.FfmpegCommand, outPath: string, deleteSegments: boolean) {
+  command.outputOption('-hls_time ' + VIDEO_LIVE.SEGMENT_TIME_SECONDS)
+  command.outputOption('-hls_list_size ' + VIDEO_LIVE.SEGMENTS_LIST_SIZE)
+
+  if (deleteSegments === true) {
+    command.outputOption('-hls_flags delete_segments')
+  }
+
+  command.outputOption(`-hls_segment_filename ${join(outPath, '%v-%04d.ts')}`)
   command.outputOption('-master_pl_name master.m3u8')
   command.outputOption(`-f hls`)