diff options
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 52 |
1 files changed, 28 insertions, 24 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index a964abdd4..17f35fe8d 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -116,28 +116,27 @@ type TranscodeOptions = { | |||
116 | 116 | ||
117 | function transcode (options: TranscodeOptions) { | 117 | function transcode (options: TranscodeOptions) { |
118 | return new Promise<void>(async (res, rej) => { | 118 | return new Promise<void>(async (res, rej) => { |
119 | let fps = await getVideoFileFPS(options.inputPath) | ||
120 | // On small/medium resolutions, limit FPS | ||
121 | if (options.resolution !== undefined && | ||
122 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
123 | fps > VIDEO_TRANSCODING_FPS.AVERAGE) { | ||
124 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
125 | } | ||
126 | |||
119 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) | 127 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) |
120 | .output(options.outputPath) | 128 | .output(options.outputPath) |
121 | .preset(standard) | 129 | command = await presetH264(command, options.resolution, fps) |
122 | 130 | ||
123 | if (CONFIG.TRANSCODING.THREADS > 0) { | 131 | if (CONFIG.TRANSCODING.THREADS > 0) { |
124 | // if we don't set any threads ffmpeg will chose automatically | 132 | // if we don't set any threads ffmpeg will chose automatically |
125 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) | 133 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) |
126 | } | 134 | } |
127 | 135 | ||
128 | let fps = await getVideoFileFPS(options.inputPath) | ||
129 | if (options.resolution !== undefined) { | 136 | if (options.resolution !== undefined) { |
130 | // '?x720' or '720x?' for example | 137 | // '?x720' or '720x?' for example |
131 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` | 138 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` |
132 | command = command.size(size) | 139 | command = command.size(size) |
133 | |||
134 | // On small/medium resolutions, limit FPS | ||
135 | if ( | ||
136 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
137 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | ||
138 | ) { | ||
139 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
140 | } | ||
141 | } | 140 | } |
142 | 141 | ||
143 | if (fps) { | 142 | if (fps) { |
@@ -148,12 +147,6 @@ function transcode (options: TranscodeOptions) { | |||
148 | command = command.withFPS(fps) | 147 | command = command.withFPS(fps) |
149 | } | 148 | } |
150 | 149 | ||
151 | // Constrained Encoding (VBV) | ||
152 | // https://slhck.info/video/2017/03/01/rate-control.html | ||
153 | // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate | ||
154 | const targetBitrate = getTargetBitrate(options.resolution, fps, VIDEO_TRANSCODING_FPS) | ||
155 | command.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) | ||
156 | |||
157 | command | 150 | command |
158 | .on('error', (err, stdout, stderr) => { | 151 | .on('error', (err, stdout, stderr) => { |
159 | logger.error('Error in transcoding job.', { stdout, stderr }) | 152 | logger.error('Error in transcoding job.', { stdout, stderr }) |
@@ -199,9 +192,9 @@ function getVideoFileStream (path: string) { | |||
199 | * and quality. Superfast and ultrafast will give you better | 192 | * and quality. Superfast and ultrafast will give you better |
200 | * performance, but then quality is noticeably worse. | 193 | * performance, but then quality is noticeably worse. |
201 | */ | 194 | */ |
202 | function veryfast (_ffmpeg) { | 195 | async function presetH264VeryFast (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { |
203 | _ffmpeg | 196 | const localFfmpeg = await presetH264(ffmpeg, resolution, fps) |
204 | .preset(standard) | 197 | localFfmpeg |
205 | .outputOption('-preset:v veryfast') | 198 | .outputOption('-preset:v veryfast') |
206 | .outputOption(['--aq-mode=2', '--aq-strength=1.3']) | 199 | .outputOption(['--aq-mode=2', '--aq-strength=1.3']) |
207 | /* | 200 | /* |
@@ -220,9 +213,9 @@ function veryfast (_ffmpeg) { | |||
220 | /** | 213 | /** |
221 | * A preset optimised for a stillimage audio video | 214 | * A preset optimised for a stillimage audio video |
222 | */ | 215 | */ |
223 | function audio (_ffmpeg) { | 216 | async function presetStillImageWithAudio (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { |
224 | _ffmpeg | 217 | const localFfmpeg = await presetH264VeryFast(ffmpeg, resolution, fps) |
225 | .preset(veryfast) | 218 | localFfmpeg |
226 | .outputOption('-tune stillimage') | 219 | .outputOption('-tune stillimage') |
227 | } | 220 | } |
228 | 221 | ||
@@ -290,8 +283,8 @@ namespace audio { | |||
290 | * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel | 283 | * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel |
291 | * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr | 284 | * See https://trac.ffmpeg.org/wiki/Encode/AAC#fdk_vbr |
292 | */ | 285 | */ |
293 | async function standard (_ffmpeg) { | 286 | async function presetH264 (ffmpeg: ffmpeg, resolution: VideoResolution, fps: number): ffmpeg { |
294 | let localFfmpeg = _ffmpeg | 287 | let localFfmpeg = ffmpeg |
295 | .format('mp4') | 288 | .format('mp4') |
296 | .videoCodec('libx264') | 289 | .videoCodec('libx264') |
297 | .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution | 290 | .outputOption('-level 3.1') // 3.1 is the minimal ressource allocation for our highest supported resolution |
@@ -324,5 +317,16 @@ async function standard (_ffmpeg) { | |||
324 | 317 | ||
325 | if (bitrate !== undefined) return localFfmpeg.audioBitrate(bitrate) | 318 | if (bitrate !== undefined) return localFfmpeg.audioBitrate(bitrate) |
326 | 319 | ||
320 | // Constrained Encoding (VBV) | ||
321 | // https://slhck.info/video/2017/03/01/rate-control.html | ||
322 | // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate | ||
323 | const targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) | ||
324 | localFfmpeg.outputOptions([`-maxrate ${ targetBitrate }`, `-bufsize ${ targetBitrate * 2 }`]) | ||
325 | |||
326 | // Keyframe interval of 2 seconds for faster seeking and resolution switching. | ||
327 | // https://streaminglearningcenter.com/blogs/whats-the-right-keyframe-interval.html | ||
328 | // https://superuser.com/a/908325 | ||
329 | localFfmpeg.outputOption(`-g ${ fps * 2 }`) | ||
330 | |||
327 | return localFfmpeg | 331 | return localFfmpeg |
328 | } | 332 | } |