]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/helpers/ffmpeg-utils.ts
Use different p2p policy for embeds and webapp
[github/Chocobozzz/PeerTube.git] / server / helpers / ffmpeg-utils.ts
index a99c9ee7c70809a35c5e87b26356f4fc9ba191ba..78ee5fa7f9a12f5f03f52cab8def28c9daab3731 100644 (file)
@@ -1,5 +1,5 @@
 import { Job } from 'bull'
-import * as ffmpeg from 'fluent-ffmpeg'
+import ffmpeg, { FfmpegCommand, FilterSpecification, getAvailableEncoders } from 'fluent-ffmpeg'
 import { readFile, remove, writeFile } from 'fs-extra'
 import { dirname, join } from 'path'
 import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants'
@@ -16,7 +16,9 @@ import { CONFIG } from '../initializers/config'
 import { execPromise, promisify0 } from './core-utils'
 import { computeFPS, ffprobePromise, getAudioStream, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from './ffprobe-utils'
 import { processImage } from './image-utils'
-import { logger } from './logger'
+import { logger, loggerTagsFactory } from './logger'
+
+const lTags = loggerTagsFactory('ffmpeg')
 
 /**
  *
@@ -42,7 +44,7 @@ async function checkFFmpegEncoders (peertubeAvailableEncoders: AvailableEncoders
     return supportedEncoders
   }
 
-  const getAvailableEncodersPromise = promisify0(ffmpeg.getAvailableEncoders)
+  const getAvailableEncodersPromise = promisify0(getAvailableEncoders)
   const availableFFmpegEncoders = await getAvailableEncodersPromise()
 
   const searchEncoders = new Set<string>()
@@ -60,7 +62,7 @@ async function checkFFmpegEncoders (peertubeAvailableEncoders: AvailableEncoders
     supportedEncoders.set(searchEncoder, availableFFmpegEncoders[searchEncoder] !== undefined)
   }
 
-  logger.info('Built supported ffmpeg encoders.', { supportedEncoders, searchEncoders })
+  logger.info('Built supported ffmpeg encoders.', { supportedEncoders, searchEncoders, ...lTags() })
 
   return supportedEncoders
 }
@@ -115,12 +117,12 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima
     const destination = join(folder, imageName)
     await processImage(pendingImagePath, destination, size)
   } catch (err) {
-    logger.error('Cannot generate image from video %s.', fromPath, { err })
+    logger.error('Cannot generate image from video %s.', fromPath, { err, ...lTags() })
 
     try {
       await remove(pendingImagePath)
     } catch (err) {
-      logger.debug('Cannot remove pending image path after generation error.', { err })
+      logger.debug('Cannot remove pending image path after generation error.', { err, ...lTags() })
     }
   }
 }
@@ -191,7 +193,7 @@ type TranscodeOptions =
   | QuickTranscodeOptions
 
 const builders: {
-  [ type in TranscodeOptionsType ]: (c: ffmpeg.FfmpegCommand, o?: TranscodeOptions) => Promise<ffmpeg.FfmpegCommand> | ffmpeg.FfmpegCommand
+  [ type in TranscodeOptionsType ]: (c: FfmpegCommand, o?: TranscodeOptions) => Promise<FfmpegCommand> | FfmpegCommand
 } = {
   'quick-transcode': buildQuickTranscodeCommand,
   'hls': buildHLSVODCommand,
@@ -202,7 +204,7 @@ const builders: {
 }
 
 async function transcode (options: TranscodeOptions) {
-  logger.debug('Will run transcode.', { options })
+  logger.debug('Will run transcode.', { options, ...lTags() })
 
   let command = getFFmpeg(options.inputPath, 'vod')
     .output(options.outputPath)
@@ -219,7 +221,7 @@ async function transcode (options: TranscodeOptions) {
 // ---------------------------------------------------------------------------
 
 async function getLiveTranscodingCommand (options: {
-  rtmpUrl: string
+  inputUrl: string
 
   outPath: string
   masterPlaylistName: string
@@ -234,14 +236,13 @@ async function getLiveTranscodingCommand (options: {
   availableEncoders: AvailableEncoders
   profile: string
 }) {
-  const { rtmpUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio } = options
-  const input = rtmpUrl
+  const { inputUrl, outPath, resolutions, fps, bitrate, availableEncoders, profile, masterPlaylistName, ratio } = options
 
-  const command = getFFmpeg(input, 'live')
+  const command = getFFmpeg(inputUrl, 'live')
 
   const varStreamMap: string[] = []
 
-  const complexFilter: ffmpeg.FilterSpecification[] = [
+  const complexFilter: FilterSpecification[] = [
     {
       inputs: '[v:0]',
       filter: 'split',
@@ -259,7 +260,7 @@ async function getLiveTranscodingCommand (options: {
     const resolutionFPS = computeFPS(fps, resolution)
 
     const baseEncoderBuilderParams = {
-      input,
+      input: inputUrl,
 
       availableEncoders,
       profile,
@@ -285,7 +286,10 @@ async function getLiveTranscodingCommand (options: {
 
       addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
 
-      logger.debug('Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile, builderResult)
+      logger.debug(
+        'Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile,
+        { builderResult, fps: resolutionFPS, resolution, ...lTags() }
+      )
 
       command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`)
       applyEncoderOptions(command, builderResult.result)
@@ -309,7 +313,10 @@ async function getLiveTranscodingCommand (options: {
 
       addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i })
 
-      logger.debug('Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile, builderResult)
+      logger.debug(
+        'Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile,
+        { builderResult, fps: resolutionFPS, resolution, ...lTags() }
+      )
 
       command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`)
       applyEncoderOptions(command, builderResult.result)
@@ -327,8 +334,8 @@ async function getLiveTranscodingCommand (options: {
   return command
 }
 
-function getLiveMuxingCommand (rtmpUrl: string, outPath: string, masterPlaylistName: string) {
-  const command = getFFmpeg(rtmpUrl, 'live')
+function getLiveMuxingCommand (inputUrl: string, outPath: string, masterPlaylistName: string) {
+  const command = getFFmpeg(inputUrl, 'live')
 
   command.outputOption('-c:v copy')
   command.outputOption('-c:a copy')
@@ -353,7 +360,7 @@ function buildStreamSuffix (base: string, streamNum?: number) {
 // ---------------------------------------------------------------------------
 
 function addDefaultEncoderGlobalParams (options: {
-  command: ffmpeg.FfmpegCommand
+  command: FfmpegCommand
 }) {
   const { command } = options
 
@@ -361,16 +368,12 @@ function addDefaultEncoderGlobalParams (options: {
   command.outputOption('-max_muxing_queue_size 1024')
          // strip all metadata
          .outputOption('-map_metadata -1')
-         // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it
-         .outputOption('-b_strategy 1')
-         // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16
-         .outputOption('-bf 16')
          // allows import of source material with incompatible pixel formats (e.g. MJPEG video)
          .outputOption('-pix_fmt yuv420p')
 }
 
 function addDefaultEncoderParams (options: {
-  command: ffmpeg.FfmpegCommand
+  command: FfmpegCommand
   encoder: 'libx264' | string
   streamNum?: number
   fps?: number
@@ -390,7 +393,7 @@ function addDefaultEncoderParams (options: {
   }
 }
 
-function addDefaultLiveHLSParams (command: ffmpeg.FfmpegCommand, outPath: string, masterPlaylistName: string) {
+function addDefaultLiveHLSParams (command: FfmpegCommand, outPath: string, masterPlaylistName: 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+independent_segments')
@@ -405,7 +408,7 @@ function addDefaultLiveHLSParams (command: ffmpeg.FfmpegCommand, outPath: string
 // Transcode VOD command builders
 // ---------------------------------------------------------------------------
 
-async function buildx264VODCommand (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) {
+async function buildx264VODCommand (command: FfmpegCommand, options: TranscodeOptions) {
   let fps = await getVideoFileFPS(options.inputPath)
   fps = computeFPS(fps, options.resolution)
 
@@ -422,7 +425,7 @@ async function buildx264VODCommand (command: ffmpeg.FfmpegCommand, options: Tran
   return command
 }
 
-async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) {
+async function buildAudioMergeCommand (command: FfmpegCommand, options: MergeAudioTranscodeOptions) {
   command = command.loop(undefined)
 
   const scaleFilterValue = getScaleCleanerValue()
@@ -437,13 +440,13 @@ async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: M
   return command
 }
 
-function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, _options: OnlyAudioTranscodeOptions) {
+function buildOnlyAudioCommand (command: FfmpegCommand, _options: OnlyAudioTranscodeOptions) {
   command = presetOnlyAudio(command)
 
   return command
 }
 
-function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
+function buildQuickTranscodeCommand (command: FfmpegCommand) {
   command = presetCopy(command)
 
   command = command.outputOption('-map_metadata -1') // strip all metadata
@@ -452,7 +455,7 @@ function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) {
   return command
 }
 
-function addCommonHLSVODCommandOptions (command: ffmpeg.FfmpegCommand, outputPath: string) {
+function addCommonHLSVODCommandOptions (command: FfmpegCommand, outputPath: string) {
   return command.outputOption('-hls_time 4')
                 .outputOption('-hls_list_size 0')
                 .outputOption('-hls_playlist_type vod')
@@ -462,7 +465,7 @@ function addCommonHLSVODCommandOptions (command: ffmpeg.FfmpegCommand, outputPat
                 .outputOption('-hls_flags single_file')
 }
 
-async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) {
+async function buildHLSVODCommand (command: FfmpegCommand, options: HLSTranscodeOptions) {
   const videoPath = getHLSVideoPath(options)
 
   if (options.copyCodecs) command = presetCopy(command)
@@ -474,7 +477,7 @@ async function buildHLSVODCommand (command: ffmpeg.FfmpegCommand, options: HLSTr
   return command
 }
 
-function buildHLSVODFromTSCommand (command: ffmpeg.FfmpegCommand, options: HLSFromTSTranscodeOptions) {
+function buildHLSVODFromTSCommand (command: FfmpegCommand, options: HLSFromTSTranscodeOptions) {
   const videoPath = getHLSVideoPath(options)
 
   command.outputOption('-c copy')
@@ -532,12 +535,12 @@ async function getEncoderBuilderResult (options: EncoderOptionsBuilderParams & {
 
   for (const encoder of encodersToTry) {
     if (!(await checkFFmpegEncoders(availableEncoders)).get(encoder)) {
-      logger.debug('Encoder %s not available in ffmpeg, skipping.', encoder)
+      logger.debug('Encoder %s not available in ffmpeg, skipping.', encoder, lTags())
       continue
     }
 
     if (!encoders[encoder]) {
-      logger.debug('Encoder %s not available in peertube encoders, skipping.', encoder)
+      logger.debug('Encoder %s not available in peertube encoders, skipping.', encoder, lTags())
       continue
     }
 
@@ -546,11 +549,11 @@ async function getEncoderBuilderResult (options: EncoderOptionsBuilderParams & {
     let builder = builderProfiles[profile]
 
     if (!builder) {
-      logger.debug('Profile %s for encoder %s not available. Fallback to default.', profile, encoder)
+      logger.debug('Profile %s for encoder %s not available. Fallback to default.', profile, encoder, lTags())
       builder = builderProfiles.default
 
       if (!builder) {
-        logger.debug('Default profile for encoder %s not available. Try next available encoder.', encoder)
+        logger.debug('Default profile for encoder %s not available. Try next available encoder.', encoder, lTags())
         continue
       }
     }
@@ -571,7 +574,7 @@ async function getEncoderBuilderResult (options: EncoderOptionsBuilderParams & {
 }
 
 async function presetVideo (options: {
-  command: ffmpeg.FfmpegCommand
+  command: FfmpegCommand
   input: string
   transcodeOptions: TranscodeOptions
   fps?: number
@@ -620,7 +623,8 @@ async function presetVideo (options: {
 
     logger.debug(
       'Apply ffmpeg params from %s for %s stream of input %s using %s profile.',
-      builderResult.encoder, streamType, input, profile, builderResult
+      builderResult.encoder, streamType, input, profile,
+      { builderResult, resolution, fps, ...lTags() }
     )
 
     if (streamType === 'video') {
@@ -640,21 +644,21 @@ async function presetVideo (options: {
   return localCommand
 }
 
-function presetCopy (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
+function presetCopy (command: FfmpegCommand): FfmpegCommand {
   return command
     .format('mp4')
     .videoCodec('copy')
     .audioCodec('copy')
 }
 
-function presetOnlyAudio (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
+function presetOnlyAudio (command: FfmpegCommand): FfmpegCommand {
   return command
     .format('mp4')
     .audioCodec('copy')
     .noVideo()
 }
 
-function applyEncoderOptions (command: ffmpeg.FfmpegCommand, options: EncoderOptions): ffmpeg.FfmpegCommand {
+function applyEncoderOptions (command: FfmpegCommand, options: EncoderOptions): FfmpegCommand {
   return command
     .inputOptions(options.inputOptions ?? [])
     .outputOptions(options.outputOptions ?? [])
@@ -714,7 +718,7 @@ function getFFmpegVersion () {
 }
 
 async function runCommand (options: {
-  command: ffmpeg.FfmpegCommand
+  command: FfmpegCommand
   silent?: boolean // false
   job?: Job
 }) {
@@ -726,13 +730,13 @@ async function runCommand (options: {
     command.on('start', cmdline => { shellCommand = cmdline })
 
     command.on('error', (err, stdout, stderr) => {
-      if (silent !== true) logger.error('Error in ffmpeg.', { stdout, stderr })
+      if (silent !== true) logger.error('Error in ffmpeg.', { stdout, stderr, shellCommand, ...lTags() })
 
       rej(err)
     })
 
     command.on('end', (stdout, stderr) => {
-      logger.debug('FFmpeg command ended.', { stdout, stderr, shellCommand })
+      logger.debug('FFmpeg command ended.', { stdout, stderr, shellCommand, ...lTags() })
 
       res()
     })
@@ -742,7 +746,7 @@ async function runCommand (options: {
         if (!progress.percent) return
 
         job.progress(Math.round(progress.percent))
-          .catch(err => logger.warn('Cannot set ffmpeg job progress.', { err }))
+          .catch(err => logger.warn('Cannot set ffmpeg job progress.', { err, ...lTags() }))
       })
     }