diff options
author | Chocobozzz <me@florianbigard.com> | 2021-01-28 15:52:44 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2021-01-28 15:55:39 +0100 |
commit | 1896bca09e088b0da9d5e845407ecebae330618c (patch) | |
tree | 56041c445c0cd49aca536d0fd6b586730f4d341e /server/helpers/ffmpeg-utils.ts | |
parent | 529b37527cff5203a0689a15ce73dcee6e1eece2 (diff) | |
download | PeerTube-1896bca09e088b0da9d5e845407ecebae330618c.tar.gz PeerTube-1896bca09e088b0da9d5e845407ecebae330618c.tar.zst PeerTube-1896bca09e088b0da9d5e845407ecebae330618c.zip |
Support transcoding options/encoders by plugins
Diffstat (limited to 'server/helpers/ffmpeg-utils.ts')
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 88 |
1 files changed, 50 insertions, 38 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 7d46130ec..33c625c9e 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -3,9 +3,9 @@ import * as ffmpeg from 'fluent-ffmpeg' | |||
3 | import { readFile, remove, writeFile } from 'fs-extra' | 3 | import { readFile, remove, writeFile } from 'fs-extra' |
4 | import { dirname, join } from 'path' | 4 | import { dirname, join } from 'path' |
5 | import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants' | 5 | import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { AvailableEncoders, EncoderOptionsBuilder, EncoderProfile, VideoResolution } from '../../shared/models/videos' |
7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' | ||
8 | import { CONFIG } from '../initializers/config' | 7 | import { CONFIG } from '../initializers/config' |
8 | import { 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' |
@@ -21,46 +21,45 @@ import { logger } from './logger' | |||
21 | // Encoder options | 21 | // Encoder options |
22 | // --------------------------------------------------------------------------- | 22 | // --------------------------------------------------------------------------- |
23 | 23 | ||
24 | // Options builders | 24 | type StreamType = 'audio' | 'video' |
25 | |||
26 | export type EncoderOptionsBuilder = (params: { | ||
27 | input: string | ||
28 | resolution: VideoResolution | ||
29 | fps?: number | ||
30 | streamNum?: number | ||
31 | }) => Promise<EncoderOptions> | EncoderOptions | ||
32 | 25 | ||
33 | // Options types | 26 | // --------------------------------------------------------------------------- |
27 | // Encoders support | ||
28 | // --------------------------------------------------------------------------- | ||
34 | 29 | ||
35 | export interface EncoderOptions { | 30 | // Detect supported encoders by ffmpeg |
36 | copy?: boolean | 31 | let supportedEncoders: Map<string, boolean> |
37 | outputOptions: string[] | 32 | async function checkFFmpegEncoders (peertubeAvailableEncoders: AvailableEncoders): Promise<Map<string, boolean>> { |
38 | } | 33 | if (supportedEncoders !== undefined) { |
34 | return supportedEncoders | ||
35 | } | ||
39 | 36 | ||
40 | // All our encoders | 37 | const getAvailableEncodersPromise = promisify0(ffmpeg.getAvailableEncoders) |
38 | const availableFFmpegEncoders = await getAvailableEncodersPromise() | ||
41 | 39 | ||
42 | export interface EncoderProfile <T> { | 40 | const searchEncoders = new Set<string>() |
43 | [ profile: string ]: T | 41 | for (const type of [ 'live', 'vod' ]) { |
42 | for (const streamType of [ 'audio', 'video' ]) { | ||
43 | for (const encoder of peertubeAvailableEncoders.encodersToTry[type][streamType]) { | ||
44 | searchEncoders.add(encoder) | ||
45 | } | ||
46 | } | ||
47 | } | ||
44 | 48 | ||
45 | default: T | 49 | supportedEncoders = new Map<string, boolean>() |
46 | } | ||
47 | 50 | ||
48 | export type AvailableEncoders = { | 51 | for (const searchEncoder of searchEncoders) { |
49 | live: { | 52 | supportedEncoders.set(searchEncoder, availableFFmpegEncoders[searchEncoder] !== undefined) |
50 | [ encoder: string ]: EncoderProfile<EncoderOptionsBuilder> | ||
51 | } | 53 | } |
52 | 54 | ||
53 | vod: { | 55 | logger.info('Built supported ffmpeg encoders.', { supportedEncoders, searchEncoders }) |
54 | [ encoder: string ]: EncoderProfile<EncoderOptionsBuilder> | ||
55 | } | ||
56 | 56 | ||
57 | encodersToTry: { | 57 | return supportedEncoders |
58 | video: string[] | ||
59 | audio: string[] | ||
60 | } | ||
61 | } | 58 | } |
62 | 59 | ||
63 | type StreamType = 'audio' | 'video' | 60 | function resetSupportedEncoders () { |
61 | supportedEncoders = undefined | ||
62 | } | ||
64 | 63 | ||
65 | // --------------------------------------------------------------------------- | 64 | // --------------------------------------------------------------------------- |
66 | // Image manipulation | 65 | // Image manipulation |
@@ -275,7 +274,7 @@ async function getLiveTranscodingCommand (options: { | |||
275 | 274 | ||
276 | addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) | 275 | addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) |
277 | 276 | ||
278 | logger.debug('Apply ffmpeg live video params from %s.', builderResult.encoder, builderResult) | 277 | logger.debug('Apply ffmpeg live video params from %s using %s profile.', builderResult.encoder, profile, builderResult) |
279 | 278 | ||
280 | command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`) | 279 | command.outputOption(`${buildStreamSuffix('-c:v', i)} ${builderResult.encoder}`) |
281 | command.addOutputOptions(builderResult.result.outputOptions) | 280 | command.addOutputOptions(builderResult.result.outputOptions) |
@@ -292,7 +291,7 @@ async function getLiveTranscodingCommand (options: { | |||
292 | 291 | ||
293 | addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) | 292 | addDefaultEncoderParams({ command, encoder: builderResult.encoder, fps: resolutionFPS, streamNum: i }) |
294 | 293 | ||
295 | logger.debug('Apply ffmpeg live audio params from %s.', builderResult.encoder, builderResult) | 294 | logger.debug('Apply ffmpeg live audio params from %s using %s profile.', builderResult.encoder, profile, builderResult) |
296 | 295 | ||
297 | command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`) | 296 | command.outputOption(`${buildStreamSuffix('-c:a', i)} ${builderResult.encoder}`) |
298 | command.addOutputOptions(builderResult.result.outputOptions) | 297 | command.addOutputOptions(builderResult.result.outputOptions) |
@@ -513,11 +512,19 @@ async function getEncoderBuilderResult (options: { | |||
513 | }) { | 512 | }) { |
514 | const { availableEncoders, input, profile, resolution, streamType, fps, streamNum, videoType } = options | 513 | const { availableEncoders, input, profile, resolution, streamType, fps, streamNum, videoType } = options |
515 | 514 | ||
516 | const encodersToTry = availableEncoders.encodersToTry[streamType] | 515 | const encodersToTry = availableEncoders.encodersToTry[videoType][streamType] |
517 | const encoders = availableEncoders[videoType] | 516 | const encoders = availableEncoders.available[videoType] |
518 | 517 | ||
519 | for (const encoder of encodersToTry) { | 518 | for (const encoder of encodersToTry) { |
520 | if (!(await checkFFmpegEncoders()).get(encoder) || !encoders[encoder]) continue | 519 | if (!(await checkFFmpegEncoders(availableEncoders)).get(encoder)) { |
520 | logger.debug('Encoder %s not available in ffmpeg, skipping.', encoder) | ||
521 | continue | ||
522 | } | ||
523 | |||
524 | if (!encoders[encoder]) { | ||
525 | logger.debug('Encoder %s not available in peertube encoders, skipping.', encoder) | ||
526 | continue | ||
527 | } | ||
521 | 528 | ||
522 | // An object containing available profiles for this encoder | 529 | // An object containing available profiles for this encoder |
523 | const builderProfiles: EncoderProfile<EncoderOptionsBuilder> = encoders[encoder] | 530 | const builderProfiles: EncoderProfile<EncoderOptionsBuilder> = encoders[encoder] |
@@ -567,7 +574,7 @@ async function presetVideo ( | |||
567 | 574 | ||
568 | if (!parsedAudio.audioStream) { | 575 | if (!parsedAudio.audioStream) { |
569 | localCommand = localCommand.noAudio() | 576 | localCommand = localCommand.noAudio() |
570 | streamsToProcess = [ 'audio' ] | 577 | streamsToProcess = [ 'video' ] |
571 | } | 578 | } |
572 | 579 | ||
573 | for (const streamType of streamsToProcess) { | 580 | for (const streamType of streamsToProcess) { |
@@ -587,7 +594,10 @@ async function presetVideo ( | |||
587 | throw new Error('No available encoder found for stream ' + streamType) | 594 | throw new Error('No available encoder found for stream ' + streamType) |
588 | } | 595 | } |
589 | 596 | ||
590 | logger.debug('Apply ffmpeg params from %s.', builderResult.encoder, builderResult) | 597 | logger.debug( |
598 | 'Apply ffmpeg params from %s for %s stream of input %s using %s profile.', | ||
599 | builderResult.encoder, streamType, input, profile, builderResult | ||
600 | ) | ||
591 | 601 | ||
592 | if (streamType === 'video') { | 602 | if (streamType === 'video') { |
593 | localCommand.videoCodec(builderResult.encoder) | 603 | localCommand.videoCodec(builderResult.encoder) |
@@ -679,6 +689,8 @@ export { | |||
679 | transcode, | 689 | transcode, |
680 | runCommand, | 690 | runCommand, |
681 | 691 | ||
692 | resetSupportedEncoders, | ||
693 | |||
682 | // builders | 694 | // builders |
683 | buildx264VODCommand | 695 | buildx264VODCommand |
684 | } | 696 | } |