diff options
Diffstat (limited to 'server/helpers/ffmpeg/ffmpeg-edition.ts')
-rw-r--r-- | server/helpers/ffmpeg/ffmpeg-edition.ts | 258 |
1 files changed, 0 insertions, 258 deletions
diff --git a/server/helpers/ffmpeg/ffmpeg-edition.ts b/server/helpers/ffmpeg/ffmpeg-edition.ts deleted file mode 100644 index 02c5ea8de..000000000 --- a/server/helpers/ffmpeg/ffmpeg-edition.ts +++ /dev/null | |||
@@ -1,258 +0,0 @@ | |||
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 | } | ||