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 { 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 availableEncoders: AvailableEncoders
20 const { inputPath, outputPath, availableEncoders, profile } = options
22 logger.debug('Will cut the video.', { options, ...lTags() })
24 const mainProbe = await ffprobePromise(inputPath)
25 const fps = await getVideoStreamFPS(inputPath, mainProbe)
26 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, mainProbe)
28 let command = getFFmpeg(inputPath, 'vod')
31 command = await presetVOD({
43 command.outputOption('-ss ' + options.start)
47 command.outputOption('-to ' + options.end)
50 await runCommand({ command })
53 async function addWatermark (options: {
58 availableEncoders: AvailableEncoders
61 const { watermarkPath, inputPath, outputPath, availableEncoders, profile } = options
63 logger.debug('Will add watermark to the video.', { options, ...lTags() })
65 const videoProbe = await ffprobePromise(inputPath)
66 const fps = await getVideoStreamFPS(inputPath, videoProbe)
67 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, videoProbe)
69 let command = getFFmpeg(inputPath, 'vod')
71 command.input(watermarkPath)
73 command = await presetVOD({
84 const complexFilter: FilterSpecification[] = [
87 inputs: [ '[1]', '[0]' ],
91 h: `ih*${VIDEO_FILTERS.WATERMARK.SIZE_RATIO}`
93 outputs: [ '[watermark]', '[video]' ]
97 inputs: [ '[video]', '[watermark]' ],
100 x: `main_w - overlay_w - (main_h * ${VIDEO_FILTERS.WATERMARK.HORIZONTAL_MARGIN_RATIO})`,
101 y: `main_h * ${VIDEO_FILTERS.WATERMARK.VERTICAL_MARGIN_RATIO}`
106 command.complexFilter(complexFilter)
108 await runCommand({ command })
111 async function addIntroOutro (options: {
113 introOutroPath: string
115 type: 'intro' | 'outro'
117 availableEncoders: AvailableEncoders
120 const { introOutroPath, inputPath, outputPath, availableEncoders, profile, type } = options
122 logger.debug('Will add intro/outro to the video.', { options, ...lTags() })
124 const mainProbe = await ffprobePromise(inputPath)
125 const fps = await getVideoStreamFPS(inputPath, mainProbe)
126 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, mainProbe)
127 const mainHasAudio = await hasAudioStream(inputPath, mainProbe)
129 const introOutroProbe = await ffprobePromise(introOutroPath)
130 const introOutroHasAudio = await hasAudioStream(introOutroPath, introOutroProbe)
132 let command = getFFmpeg(inputPath, 'vod')
135 command.input(introOutroPath)
137 if (!introOutroHasAudio && mainHasAudio) {
138 const duration = await getVideoStreamDuration(introOutroPath, introOutroProbe)
140 command.input('anullsrc')
141 command.withInputFormat('lavfi')
142 command.withInputOption('-t ' + duration)
145 command = await presetVOD({
156 // Add black background to correctly scale intro/outro with padding
157 const complexFilter: FilterSpecification[] = [
159 inputs: [ '1', '0' ],
165 outputs: [ 'intro-outro', 'main' ]
168 inputs: [ 'intro-outro', 'main' ],
174 outputs: [ 'to-scale', 'main' ]
182 outputs: [ 'to-scale-bg' ]
185 inputs: [ '1', 'to-scale-bg' ],
190 force_original_aspect_ratio: 'decrease',
193 outputs: [ 'to-scale', 'to-scale-bg' ]
196 inputs: [ 'to-scale-bg', 'to-scale' ],
199 x: '(main_w - overlay_w)/2',
200 y: '(main_h - overlay_h)/2'
202 outputs: 'intro-outro-resized'
206 const concatFilter = {
217 const introOutroFilterInputs = [ 'intro-outro-resized' ]
218 const mainFilterInputs = [ 'main' ]
221 mainFilterInputs.push('0:a')
223 if (introOutroHasAudio) {
224 introOutroFilterInputs.push('1:a')
227 introOutroFilterInputs.push('2:a')
231 if (type === 'intro') {
232 concatFilter.inputs = [ ...introOutroFilterInputs, ...mainFilterInputs ]
234 concatFilter.inputs = [ ...mainFilterInputs, ...introOutroFilterInputs ]
238 concatFilter.options['a'] = 1
239 concatFilter.outputs.push('a')
241 command.outputOption('-map [a]')
244 command.outputOption('-map [v]')
246 complexFilter.push(concatFilter)
247 command.complexFilter(complexFilter)
249 await runCommand({ command })
252 // ---------------------------------------------------------------------------