1 import { FilterSpecification } from 'fluent-ffmpeg'
2 import { FFmpegCommandWrapper, FFmpegCommandWrapperOptions } from './ffmpeg-command-wrapper'
3 import { presetVOD } from './shared/presets'
4 import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamDuration, getVideoStreamFPS, hasAudioStream } from './ffprobe'
6 export class FFmpegEdition {
7 private readonly commandWrapper: FFmpegCommandWrapper
9 constructor (options: FFmpegCommandWrapperOptions) {
10 this.commandWrapper = new FFmpegCommandWrapper(options)
13 async cutVideo (options: {
19 const { inputPath, outputPath } = options
21 const mainProbe = await ffprobePromise(inputPath)
22 const fps = await getVideoStreamFPS(inputPath, mainProbe)
23 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, mainProbe)
25 const command = this.commandWrapper.buildCommand(inputPath)
29 commandWrapper: this.commandWrapper,
38 command.outputOption('-ss ' + options.start)
42 command.outputOption('-to ' + options.end)
45 await this.commandWrapper.runCommand()
48 async addWatermark (options: {
54 watermarkSizeRatio: number
55 horitonzalMarginRatio: number
56 verticalMarginRatio: number
59 const { watermarkPath, inputPath, outputPath, videoFilters } = options
61 const videoProbe = await ffprobePromise(inputPath)
62 const fps = await getVideoStreamFPS(inputPath, videoProbe)
63 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, videoProbe)
65 const command = this.commandWrapper.buildCommand(inputPath)
68 command.input(watermarkPath)
71 commandWrapper: this.commandWrapper,
79 const complexFilter: FilterSpecification[] = [
82 inputs: [ '[1]', '[0]' ],
86 h: `ih*${videoFilters.watermarkSizeRatio}`
88 outputs: [ '[watermark]', '[video]' ]
92 inputs: [ '[video]', '[watermark]' ],
95 x: `main_w - overlay_w - (main_h * ${videoFilters.horitonzalMarginRatio})`,
96 y: `main_h * ${videoFilters.verticalMarginRatio}`
101 command.complexFilter(complexFilter)
103 await this.commandWrapper.runCommand()
106 async addIntroOutro (options: {
108 introOutroPath: string
110 type: 'intro' | 'outro'
112 const { introOutroPath, inputPath, outputPath, type } = options
114 const mainProbe = await ffprobePromise(inputPath)
115 const fps = await getVideoStreamFPS(inputPath, mainProbe)
116 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, mainProbe)
117 const mainHasAudio = await hasAudioStream(inputPath, mainProbe)
119 const introOutroProbe = await ffprobePromise(introOutroPath)
120 const introOutroHasAudio = await hasAudioStream(introOutroPath, introOutroProbe)
122 const command = this.commandWrapper.buildCommand(inputPath)
125 command.input(introOutroPath)
127 if (!introOutroHasAudio && mainHasAudio) {
128 const duration = await getVideoStreamDuration(introOutroPath, introOutroProbe)
130 command.input('anullsrc')
131 command.withInputFormat('lavfi')
132 command.withInputOption('-t ' + duration)
136 commandWrapper: this.commandWrapper,
144 // Add black background to correctly scale intro/outro with padding
145 const complexFilter: FilterSpecification[] = [
147 inputs: [ '1', '0' ],
153 outputs: [ 'intro-outro', 'main' ]
156 inputs: [ 'intro-outro', 'main' ],
162 outputs: [ 'to-scale', 'main' ]
170 outputs: [ 'to-scale-bg' ]
173 inputs: [ '1', 'to-scale-bg' ],
178 force_original_aspect_ratio: 'decrease',
181 outputs: [ 'to-scale', 'to-scale-bg' ]
184 inputs: [ 'to-scale-bg', 'to-scale' ],
187 x: '(main_w - overlay_w)/2',
188 y: '(main_h - overlay_h)/2'
190 outputs: 'intro-outro-resized'
194 const concatFilter = {
205 const introOutroFilterInputs = [ 'intro-outro-resized' ]
206 const mainFilterInputs = [ 'main' ]
209 mainFilterInputs.push('0:a')
211 if (introOutroHasAudio) {
212 introOutroFilterInputs.push('1:a')
215 introOutroFilterInputs.push('2:a')
219 if (type === 'intro') {
220 concatFilter.inputs = [ ...introOutroFilterInputs, ...mainFilterInputs ]
222 concatFilter.inputs = [ ...mainFilterInputs, ...introOutroFilterInputs ]
226 concatFilter.options['a'] = 1
227 concatFilter.outputs.push('a')
229 command.outputOption('-map [a]')
232 command.outputOption('-map [v]')
234 complexFilter.push(concatFilter)
235 command.complexFilter(complexFilter)
237 await this.commandWrapper.runCommand()