1 import { FilterSpecification } from 'fluent-ffmpeg'
2 import { VIDEO_FILTERS } from '@server/initializers/constants'
3 import { AvailableEncoders } from '@shared/models'
4 import { logger, loggerTagsFactory } from '../logger'
5 import { getFFmpeg, runCommand } from './ffmpeg-commons'
6 import { presetCopy, presetVOD } from './ffmpeg-presets'
7 import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamDuration, getVideoStreamFPS, hasAudioStream } from './ffprobe-utils'
9 const lTags = loggerTagsFactory('ffmpeg')
11 async function cutVideo (options: {
17 const { inputPath, outputPath } = options
19 logger.debug('Will cut the video.', { options, ...lTags() })
21 let command = getFFmpeg(inputPath, 'vod')
24 command = presetCopy(command)
26 if (options.start) command.inputOption('-ss ' + options.start)
29 const endSeeking = options.end - (options.start || 0)
31 command.outputOption('-to ' + endSeeking)
34 await runCommand({ command })
37 async function addWatermark (options: {
42 availableEncoders: AvailableEncoders
45 const { watermarkPath, inputPath, outputPath, availableEncoders, profile } = options
47 logger.debug('Will add watermark to the video.', { options, ...lTags() })
49 const videoProbe = await ffprobePromise(inputPath)
50 const fps = await getVideoStreamFPS(inputPath, videoProbe)
51 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, videoProbe)
53 let command = getFFmpeg(inputPath, 'vod')
55 command.input(watermarkPath)
57 command = await presetVOD({
68 const complexFilter: FilterSpecification[] = [
71 inputs: [ '[1]', '[0]' ],
75 h: `ih*${VIDEO_FILTERS.WATERMARK.SIZE_RATIO}`
77 outputs: [ '[watermark]', '[video]' ]
81 inputs: [ '[video]', '[watermark]' ],
84 x: `main_w - overlay_w - (main_h * ${VIDEO_FILTERS.WATERMARK.HORIZONTAL_MARGIN_RATIO})`,
85 y: `main_h * ${VIDEO_FILTERS.WATERMARK.VERTICAL_MARGIN_RATIO}`
90 command.complexFilter(complexFilter)
92 await runCommand({ command })
95 async function addIntroOutro (options: {
97 introOutroPath: string
99 type: 'intro' | 'outro'
101 availableEncoders: AvailableEncoders
104 const { introOutroPath, inputPath, outputPath, availableEncoders, profile, type } = options
106 logger.debug('Will add intro/outro to the video.', { options, ...lTags() })
108 const mainProbe = await ffprobePromise(inputPath)
109 const fps = await getVideoStreamFPS(inputPath, mainProbe)
110 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, mainProbe)
111 const mainHasAudio = await hasAudioStream(inputPath, mainProbe)
113 const introOutroProbe = await ffprobePromise(introOutroPath)
114 const introOutroHasAudio = await hasAudioStream(introOutroPath, introOutroProbe)
116 let command = getFFmpeg(inputPath, 'vod')
119 command.input(introOutroPath)
121 if (!introOutroHasAudio && mainHasAudio) {
122 const duration = await getVideoStreamDuration(introOutroPath, introOutroProbe)
124 command.input('anullsrc')
125 command.withInputFormat('lavfi')
126 command.withInputOption('-t ' + duration)
129 command = await presetVOD({
140 // Add black background to correctly scale intro/outro with padding
141 const complexFilter: FilterSpecification[] = [
143 inputs: [ '1', '0' ],
149 outputs: [ 'intro-outro', 'main' ]
152 inputs: [ 'intro-outro', 'main' ],
158 outputs: [ 'to-scale', 'main' ]
166 outputs: [ 'to-scale-bg' ]
169 inputs: [ '1', 'to-scale-bg' ],
174 force_original_aspect_ratio: 'decrease',
177 outputs: [ 'to-scale', 'to-scale-bg' ]
180 inputs: [ 'to-scale-bg', 'to-scale' ],
183 x: '(main_w - overlay_w)/2',
184 y: '(main_h - overlay_h)/2'
186 outputs: 'intro-outro-resized'
190 const concatFilter = {
201 const introOutroFilterInputs = [ 'intro-outro-resized' ]
202 const mainFilterInputs = [ 'main' ]
205 mainFilterInputs.push('0:a')
207 if (introOutroHasAudio) {
208 introOutroFilterInputs.push('1:a')
211 introOutroFilterInputs.push('2:a')
215 if (type === 'intro') {
216 concatFilter.inputs = [ ...introOutroFilterInputs, ...mainFilterInputs ]
218 concatFilter.inputs = [ ...mainFilterInputs, ...introOutroFilterInputs ]
222 concatFilter.options['a'] = 1
223 concatFilter.outputs.push('a')
225 command.outputOption('-map [a]')
228 command.outputOption('-map [v]')
230 complexFilter.push(concatFilter)
231 command.complexFilter(complexFilter)
233 await runCommand({ command })
236 // ---------------------------------------------------------------------------