diff options
Diffstat (limited to 'server/helpers/ffmpeg-utils.ts')
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 66 |
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' | |||
9 | import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils' | 9 | import { computeFPS, getAudioStream, getVideoFileFPS } from './ffprobe-utils' |
10 | import { processImage } from './image-utils' | 10 | import { processImage } from './image-utils' |
11 | import { logger } from './logger' | 11 | import { logger } from './logger' |
12 | import { 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 | ||
406 | async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: MergeAudioTranscodeOptions) { | 408 | async 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 | ||
558 | async function presetVideo ( | 561 | async 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 { | |||
628 | function applyEncoderOptions (command: ffmpeg.FfmpegCommand, options: EncoderOptions): ffmpeg.FfmpegCommand { | 639 | function 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 | ||
645 | function 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 | // --------------------------------------------------------------------------- |