]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/helpers/ffmpeg/ffmpeg-edition.ts
Fix unregister default value
[github/Chocobozzz/PeerTube.git] / server / helpers / ffmpeg / ffmpeg-edition.ts
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'
8
9 const lTags = loggerTagsFactory('ffmpeg')
10
11 async function cutVideo (options: {
12 inputPath: string
13 outputPath: string
14 start?: number
15 end?: number
16
17 availableEncoders: AvailableEncoders
18 profile: string
19 }) {
20 const { inputPath, outputPath, availableEncoders, profile } = options
21
22 logger.debug('Will cut the video.', { options, ...lTags() })
23
24 const mainProbe = await ffprobePromise(inputPath)
25 const fps = await getVideoStreamFPS(inputPath, mainProbe)
26 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, mainProbe)
27
28 let command = getFFmpeg(inputPath, 'vod')
29 .output(outputPath)
30
31 command = await presetVOD({
32 command,
33 input: inputPath,
34 availableEncoders,
35 profile,
36 resolution,
37 fps,
38 canCopyAudio: false,
39 canCopyVideo: false
40 })
41
42 if (options.start) {
43 command.outputOption('-ss ' + options.start)
44 }
45
46 if (options.end) {
47 command.outputOption('-to ' + options.end)
48 }
49
50 await runCommand({ command })
51 }
52
53 async function addWatermark (options: {
54 inputPath: string
55 watermarkPath: string
56 outputPath: string
57
58 availableEncoders: AvailableEncoders
59 profile: string
60 }) {
61 const { watermarkPath, inputPath, outputPath, availableEncoders, profile } = options
62
63 logger.debug('Will add watermark to the video.', { options, ...lTags() })
64
65 const videoProbe = await ffprobePromise(inputPath)
66 const fps = await getVideoStreamFPS(inputPath, videoProbe)
67 const { resolution } = await getVideoStreamDimensionsInfo(inputPath, videoProbe)
68
69 let command = getFFmpeg(inputPath, 'vod')
70 .output(outputPath)
71 command.input(watermarkPath)
72
73 command = await presetVOD({
74 command,
75 input: inputPath,
76 availableEncoders,
77 profile,
78 resolution,
79 fps,
80 canCopyAudio: true,
81 canCopyVideo: false
82 })
83
84 const complexFilter: FilterSpecification[] = [
85 // Scale watermark
86 {
87 inputs: [ '[1]', '[0]' ],
88 filter: 'scale2ref',
89 options: {
90 w: 'oh*mdar',
91 h: `ih*${VIDEO_FILTERS.WATERMARK.SIZE_RATIO}`
92 },
93 outputs: [ '[watermark]', '[video]' ]
94 },
95
96 {
97 inputs: [ '[video]', '[watermark]' ],
98 filter: 'overlay',
99 options: {
100 x: `main_w - overlay_w - (main_h * ${VIDEO_FILTERS.WATERMARK.HORIZONTAL_MARGIN_RATIO})`,
101 y: `main_h * ${VIDEO_FILTERS.WATERMARK.VERTICAL_MARGIN_RATIO}`
102 }
103 }
104 ]
105
106 command.complexFilter(complexFilter)
107
108 await runCommand({ command })
109 }
110
111 async function addIntroOutro (options: {
112 inputPath: string
113 introOutroPath: string
114 outputPath: string
115 type: 'intro' | 'outro'
116
117 availableEncoders: AvailableEncoders
118 profile: string
119 }) {
120 const { introOutroPath, inputPath, outputPath, availableEncoders, profile, type } = options
121
122 logger.debug('Will add intro/outro to the video.', { options, ...lTags() })
123
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)
128
129 const introOutroProbe = await ffprobePromise(introOutroPath)
130 const introOutroHasAudio = await hasAudioStream(introOutroPath, introOutroProbe)
131
132 let command = getFFmpeg(inputPath, 'vod')
133 .output(outputPath)
134
135 command.input(introOutroPath)
136
137 if (!introOutroHasAudio && mainHasAudio) {
138 const duration = await getVideoStreamDuration(introOutroPath, introOutroProbe)
139
140 command.input('anullsrc')
141 command.withInputFormat('lavfi')
142 command.withInputOption('-t ' + duration)
143 }
144
145 command = await presetVOD({
146 command,
147 input: inputPath,
148 availableEncoders,
149 profile,
150 resolution,
151 fps,
152 canCopyAudio: false,
153 canCopyVideo: false
154 })
155
156 // Add black background to correctly scale intro/outro with padding
157 const complexFilter: FilterSpecification[] = [
158 {
159 inputs: [ '1', '0' ],
160 filter: 'scale2ref',
161 options: {
162 w: 'iw',
163 h: `ih`
164 },
165 outputs: [ 'intro-outro', 'main' ]
166 },
167 {
168 inputs: [ 'intro-outro', 'main' ],
169 filter: 'scale2ref',
170 options: {
171 w: 'iw',
172 h: `ih`
173 },
174 outputs: [ 'to-scale', 'main' ]
175 },
176 {
177 inputs: 'to-scale',
178 filter: 'drawbox',
179 options: {
180 t: 'fill'
181 },
182 outputs: [ 'to-scale-bg' ]
183 },
184 {
185 inputs: [ '1', 'to-scale-bg' ],
186 filter: 'scale2ref',
187 options: {
188 w: 'iw',
189 h: 'ih',
190 force_original_aspect_ratio: 'decrease',
191 flags: 'spline'
192 },
193 outputs: [ 'to-scale', 'to-scale-bg' ]
194 },
195 {
196 inputs: [ 'to-scale-bg', 'to-scale' ],
197 filter: 'overlay',
198 options: {
199 x: '(main_w - overlay_w)/2',
200 y: '(main_h - overlay_h)/2'
201 },
202 outputs: 'intro-outro-resized'
203 }
204 ]
205
206 const concatFilter = {
207 inputs: [],
208 filter: 'concat',
209 options: {
210 n: 2,
211 v: 1,
212 unsafe: 1
213 },
214 outputs: [ 'v' ]
215 }
216
217 const introOutroFilterInputs = [ 'intro-outro-resized' ]
218 const mainFilterInputs = [ 'main' ]
219
220 if (mainHasAudio) {
221 mainFilterInputs.push('0:a')
222
223 if (introOutroHasAudio) {
224 introOutroFilterInputs.push('1:a')
225 } else {
226 // Silent input
227 introOutroFilterInputs.push('2:a')
228 }
229 }
230
231 if (type === 'intro') {
232 concatFilter.inputs = [ ...introOutroFilterInputs, ...mainFilterInputs ]
233 } else {
234 concatFilter.inputs = [ ...mainFilterInputs, ...introOutroFilterInputs ]
235 }
236
237 if (mainHasAudio) {
238 concatFilter.options['a'] = 1
239 concatFilter.outputs.push('a')
240
241 command.outputOption('-map [a]')
242 }
243
244 command.outputOption('-map [v]')
245
246 complexFilter.push(concatFilter)
247 command.complexFilter(complexFilter)
248
249 await runCommand({ command })
250 }
251
252 // ---------------------------------------------------------------------------
253
254 export {
255 cutVideo,
256 addIntroOutro,
257 addWatermark
258 }