aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers/ffmpeg-utils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'server/helpers/ffmpeg-utils.ts')
-rw-r--r--server/helpers/ffmpeg-utils.ts66
1 files changed, 41 insertions, 25 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 685a35886..aa4223cda 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -9,6 +9,7 @@ import { execPromise, promisify0 } from './core-utils'
9import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils' 9import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils'
10import { processImage } from './image-utils' 10import { processImage } from './image-utils'
11import { logger } from './logger' 11import { logger } from './logger'
12import { FilterSpecification } from 'fluent-ffmpeg'
12 13
13/** 14/**
14 * 15 *
@@ -226,21 +227,14 @@ async function getLiveTranscodingCommand (options: {
226 227
227 const varStreamMap: string[] = [] 228 const varStreamMap: string[] = []
228 229
229 command.complexFilter([ 230 const complexFilter: FilterSpecification[] = [
230 { 231 {
231 inputs: '[v:0]', 232 inputs: '[v:0]',
232 filter: 'split', 233 filter: 'split',
233 options: resolutions.length, 234 options: resolutions.length,
234 outputs: resolutions.map(r => `vtemp${r}`) 235 outputs: resolutions.map(r => `vtemp${r}`)
235 }, 236 }
236 237 ]
237 ...resolutions.map(r => ({
238 inputs: `vtemp${r}`,
239 filter: 'scale',
240 options: `w=-2:h=${r}`,
241 outputs: `vout${r}`
242 }))
243 ])
244 238
245 command.outputOption('-preset superfast') 239 command.outputOption('-preset superfast')
246 command.outputOption('-sc_threshold 0') 240 command.outputOption('-sc_threshold 0')
@@ -278,6 +272,13 @@ async function getLiveTranscodingCommand (options: {
278 272
279 command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`) 273 command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`)
280 applyEncoderOptions(command, builderResult.result) 274 applyEncoderOptions(command, builderResult.result)
275
276 complexFilter.push({
277 inputs: `vtemp${resolution}`,
278 filter: getScaleFilter(builderResult.result),
279 options: `w=-2:h=${resolution}`,
280 outputs: `vout${resolution}`
281 })
281 } 282 }
282 283
283 { 284 {
@@ -300,6 +301,8 @@ async function getLiveTranscodingCommand (options: {
300 varStreamMap.push(`v:${i},a:${i}`) 301 varStreamMap.push(`v:${i},a:${i}`)
301 } 302 }
302 303
304 command.complexFilter(complexFilter)
305
303 addDefaultLiveHLSParams(command, outPath) 306 addDefaultLiveHLSParams(command, outPath)
304 307
305 command.outputOption('-var_stream_map', varStreamMap.join(' ')) 308 command.outputOption('-var_stream_map', varStreamMap.join(' '))
@@ -389,29 +392,29 @@ async function buildx264VODCommand (command: ffmpeg.FfmpegCommand, options: Tran
389 let fps = await getVideoFileFPS(options.inputPath) 392 let fps = await getVideoFileFPS(options.inputPath)
390 fps = computeFPS(fps, options.resolution) 393 fps = computeFPS(fps, options.resolution)
391 394
392 command = await presetVideo(command, options.inputPath, options, fps) 395 let scaleFilterValue: string
393 396
394 if (options.resolution !== undefined) { 397 if (options.resolution !== undefined) {
395 // '?x720' or '720x?' for example 398 scaleFilterValue = options.isPortraitMode === true
396 const size = options.isPortraitMode === true 399 ? `${options.resolution}:-2`
397 ? `${options.resolution}x?` 400 : `-2:${options.resolution}`
398 : `?x${options.resolution}`
399
400 command = command.size(size)
401 } 401 }
402 402
403 command = await presetVideo({ command, input: options.inputPath, transcodeOptions: options, fps, scaleFilterValue })
404
403 return command 405 return command
404} 406}
405 407
406async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) { 408async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) {
407 command = command.loop(undefined) 409 command = command.loop(undefined)
408 410
409 command = await presetVideo(command, options.audioPath, options) 411 // Avoid "height not divisible by 2" error
412 const scaleFilterValue = 'trunc(iw/2)*2:trunc(ih/2)*2'
413 command = await presetVideo({ command, input: options.audioPath, transcodeOptions: options, scaleFilterValue })
410 414
411 command.outputOption('-preset:v veryfast') 415 command.outputOption('-preset:v veryfast')
412 416
413 command = command.input(options.audioPath) 417 command = command.input(options.audioPath)
414 .videoFilter('scale=trunc(iw/2)*2:trunc(ih/2)*2') // Avoid "height not divisible by 2" error
415 .outputOption('-tune stillimage') 418 .outputOption('-tune stillimage')
416 .outputOption('-shortest') 419 .outputOption('-shortest')
417 420
@@ -555,12 +558,15 @@ async function getEncoderBuilderResult (options: {
555 return null 558 return null
556} 559}
557 560
558async function presetVideo ( 561async function presetVideo (options: {
559 command: ffmpeg.FfmpegCommand, 562 command: ffmpeg.FfmpegCommand
560 input: string, 563 input: string
561 transcodeOptions: TranscodeOptions, 564 transcodeOptions: TranscodeOptions
562 fps?: number 565 fps?: number
563) { 566 scaleFilterValue?: string
567}) {
568 const { command, input, transcodeOptions, fps, scaleFilterValue } = options
569
564 let localCommand = command 570 let localCommand = command
565 .format('mp4') 571 .format('mp4')
566 .outputOption('-movflags faststart') 572 .outputOption('-movflags faststart')
@@ -601,9 +607,14 @@ async function presetVideo (
601 607
602 if (streamType === 'video') { 608 if (streamType === 'video') {
603 localCommand.videoCodec(builderResult.encoder) 609 localCommand.videoCodec(builderResult.encoder)
610
611 if (scaleFilterValue) {
612 localCommand.outputOption(`-vf ${getScaleFilter(builderResult.result)}=${scaleFilterValue}`)
613 }
604 } else if (streamType === 'audio') { 614 } else if (streamType === 'audio') {
605 localCommand.audioCodec(builderResult.encoder) 615 localCommand.audioCodec(builderResult.encoder)
606 } 616 }
617
607 applyEncoderOptions(localCommand, builderResult.result) 618 applyEncoderOptions(localCommand, builderResult.result)
608 addDefaultEncoderParams({ command: localCommand, encoder: builderResult.encoder, fps }) 619 addDefaultEncoderParams({ command: localCommand, encoder: builderResult.encoder, fps })
609 } 620 }
@@ -628,10 +639,15 @@ function presetOnlyAudio (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand {
628function applyEncoderOptions (command: ffmpeg.FfmpegCommand, options: EncoderOptions): ffmpeg.FfmpegCommand { 639function applyEncoderOptions (command: ffmpeg.FfmpegCommand, options: EncoderOptions): ffmpeg.FfmpegCommand {
629 return command 640 return command
630 .inputOptions(options.inputOptions ?? []) 641 .inputOptions(options.inputOptions ?? [])
631 .videoFilters(options.videoFilters ?? [])
632 .outputOptions(options.outputOptions ?? []) 642 .outputOptions(options.outputOptions ?? [])
633} 643}
634 644
645function getScaleFilter (options: EncoderOptions): string {
646 if (options.scaleFilter) return options.scaleFilter.name
647
648 return 'scale'
649}
650
635// --------------------------------------------------------------------------- 651// ---------------------------------------------------------------------------
636// Utils 652// Utils
637// --------------------------------------------------------------------------- 653// ---------------------------------------------------------------------------