diff options
-rw-r--r-- | scripts/print-transcode-command.ts | 4 | ||||
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 51 | ||||
-rw-r--r-- | server/initializers/constants.ts | 12 | ||||
-rw-r--r-- | server/lib/live-manager.ts | 4 | ||||
-rw-r--r-- | server/lib/video-transcoding-profiles.ts | 85 | ||||
-rw-r--r-- | server/lib/video-transcoding.ts | 12 |
6 files changed, 116 insertions, 52 deletions
diff --git a/scripts/print-transcode-command.ts b/scripts/print-transcode-command.ts index b75b711a4..f3b169b32 100644 --- a/scripts/print-transcode-command.ts +++ b/scripts/print-transcode-command.ts | |||
@@ -3,9 +3,9 @@ registerTSPaths() | |||
3 | 3 | ||
4 | import * as program from 'commander' | 4 | import * as program from 'commander' |
5 | import * as ffmpeg from 'fluent-ffmpeg' | 5 | import * as ffmpeg from 'fluent-ffmpeg' |
6 | import { availableEncoders } from '@server/lib/video-transcoding-profiles' | ||
7 | import { buildx264VODCommand, runCommand, TranscodeOptions } from '@server/helpers/ffmpeg-utils' | 6 | import { buildx264VODCommand, runCommand, TranscodeOptions } from '@server/helpers/ffmpeg-utils' |
8 | import { exit } from 'process' | 7 | import { exit } from 'process' |
8 | import { VideoTranscodingProfilesManager } from '@server/lib/video-transcoding-profiles' | ||
9 | 9 | ||
10 | program | 10 | program |
11 | .arguments('<path>') | 11 | .arguments('<path>') |
@@ -31,7 +31,7 @@ async function run (path: string, cmd: any) { | |||
31 | inputPath: path, | 31 | inputPath: path, |
32 | outputPath: '/dev/null', | 32 | outputPath: '/dev/null', |
33 | 33 | ||
34 | availableEncoders, | 34 | availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), |
35 | profile: 'default', | 35 | profile: 'default', |
36 | 36 | ||
37 | resolution: +cmd.resolution, | 37 | resolution: +cmd.resolution, |
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index f85b9f316..7d46130ec 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -2,7 +2,7 @@ import { Job } from 'bull' | |||
2 | import * as ffmpeg from 'fluent-ffmpeg' | 2 | 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, VIDEO_TRANSCODING_ENCODERS } from '@server/initializers/constants' | 5 | import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../shared/models/videos' |
7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' | 7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' |
8 | import { CONFIG } from '../initializers/config' | 8 | import { CONFIG } from '../initializers/config' |
@@ -46,11 +46,22 @@ export interface EncoderProfile <T> { | |||
46 | } | 46 | } |
47 | 47 | ||
48 | export type AvailableEncoders = { | 48 | export type AvailableEncoders = { |
49 | [ id in 'live' | 'vod' ]: { | 49 | live: { |
50 | [ encoder in 'libx264' | 'aac' | 'libfdk_aac' ]?: EncoderProfile<EncoderOptionsBuilder> | 50 | [ encoder: string ]: EncoderProfile<EncoderOptionsBuilder> |
51 | } | ||
52 | |||
53 | vod: { | ||
54 | [ encoder: string ]: EncoderProfile<EncoderOptionsBuilder> | ||
55 | } | ||
56 | |||
57 | encodersToTry: { | ||
58 | video: string[] | ||
59 | audio: string[] | ||
51 | } | 60 | } |
52 | } | 61 | } |
53 | 62 | ||
63 | type StreamType = 'audio' | 'video' | ||
64 | |||
54 | // --------------------------------------------------------------------------- | 65 | // --------------------------------------------------------------------------- |
55 | // Image manipulation | 66 | // Image manipulation |
56 | // --------------------------------------------------------------------------- | 67 | // --------------------------------------------------------------------------- |
@@ -243,8 +254,10 @@ async function getLiveTranscodingCommand (options: { | |||
243 | 254 | ||
244 | const baseEncoderBuilderParams = { | 255 | const baseEncoderBuilderParams = { |
245 | input, | 256 | input, |
257 | |||
246 | availableEncoders, | 258 | availableEncoders, |
247 | profile, | 259 | profile, |
260 | |||
248 | fps: resolutionFPS, | 261 | fps: resolutionFPS, |
249 | resolution, | 262 | resolution, |
250 | streamNum: i, | 263 | streamNum: i, |
@@ -252,7 +265,8 @@ async function getLiveTranscodingCommand (options: { | |||
252 | } | 265 | } |
253 | 266 | ||
254 | { | 267 | { |
255 | const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType: 'VIDEO' })) | 268 | const streamType: StreamType = 'video' |
269 | const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType })) | ||
256 | if (!builderResult) { | 270 | if (!builderResult) { |
257 | throw new Error('No available live video encoder found') | 271 | throw new Error('No available live video encoder found') |
258 | } | 272 | } |
@@ -268,7 +282,8 @@ async function getLiveTranscodingCommand (options: { | |||
268 | } | 282 | } |
269 | 283 | ||
270 | { | 284 | { |
271 | const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType: 'AUDIO' })) | 285 | const streamType: StreamType = 'audio' |
286 | const builderResult = await getEncoderBuilderResult(Object.assign({}, baseEncoderBuilderParams, { streamType })) | ||
272 | if (!builderResult) { | 287 | if (!builderResult) { |
273 | throw new Error('No available live audio encoder found') | 288 | throw new Error('No available live audio encoder found') |
274 | } | 289 | } |
@@ -480,8 +495,11 @@ function getHLSVideoPath (options: HLSTranscodeOptions | HLSFromTSTranscodeOptio | |||
480 | // Transcoding presets | 495 | // Transcoding presets |
481 | // --------------------------------------------------------------------------- | 496 | // --------------------------------------------------------------------------- |
482 | 497 | ||
498 | // Run encoder builder depending on available encoders | ||
499 | // Try encoders by priority: if the encoder is available, run the chosen profile or fallback to the default one | ||
500 | // If the default one does not exist, check the next encoder | ||
483 | async function getEncoderBuilderResult (options: { | 501 | async function getEncoderBuilderResult (options: { |
484 | streamType: string | 502 | streamType: 'video' | 'audio' |
485 | input: string | 503 | input: string |
486 | 504 | ||
487 | availableEncoders: AvailableEncoders | 505 | availableEncoders: AvailableEncoders |
@@ -495,17 +513,24 @@ async function getEncoderBuilderResult (options: { | |||
495 | }) { | 513 | }) { |
496 | const { availableEncoders, input, profile, resolution, streamType, fps, streamNum, videoType } = options | 514 | const { availableEncoders, input, profile, resolution, streamType, fps, streamNum, videoType } = options |
497 | 515 | ||
498 | const encodersToTry: string[] = VIDEO_TRANSCODING_ENCODERS[streamType] | 516 | const encodersToTry = availableEncoders.encodersToTry[streamType] |
517 | const encoders = availableEncoders[videoType] | ||
499 | 518 | ||
500 | for (const encoder of encodersToTry) { | 519 | for (const encoder of encodersToTry) { |
501 | if (!(await checkFFmpegEncoders()).get(encoder) || !availableEncoders[videoType][encoder]) continue | 520 | if (!(await checkFFmpegEncoders()).get(encoder) || !encoders[encoder]) continue |
502 | 521 | ||
503 | const builderProfiles: EncoderProfile<EncoderOptionsBuilder> = availableEncoders[videoType][encoder] | 522 | // An object containing available profiles for this encoder |
523 | const builderProfiles: EncoderProfile<EncoderOptionsBuilder> = encoders[encoder] | ||
504 | let builder = builderProfiles[profile] | 524 | let builder = builderProfiles[profile] |
505 | 525 | ||
506 | if (!builder) { | 526 | if (!builder) { |
507 | logger.debug('Profile %s for encoder %s not available. Fallback to default.', profile, encoder) | 527 | logger.debug('Profile %s for encoder %s not available. Fallback to default.', profile, encoder) |
508 | builder = builderProfiles.default | 528 | builder = builderProfiles.default |
529 | |||
530 | if (!builder) { | ||
531 | logger.debug('Default profile for encoder %s not available. Try next available encoder.', encoder) | ||
532 | continue | ||
533 | } | ||
509 | } | 534 | } |
510 | 535 | ||
511 | const result = await builder({ input, resolution: resolution, fps, streamNum }) | 536 | const result = await builder({ input, resolution: resolution, fps, streamNum }) |
@@ -538,11 +563,11 @@ async function presetVideo ( | |||
538 | // Audio encoder | 563 | // Audio encoder |
539 | const parsedAudio = await getAudioStream(input) | 564 | const parsedAudio = await getAudioStream(input) |
540 | 565 | ||
541 | let streamsToProcess = [ 'AUDIO', 'VIDEO' ] | 566 | let streamsToProcess: StreamType[] = [ 'audio', 'video' ] |
542 | 567 | ||
543 | if (!parsedAudio.audioStream) { | 568 | if (!parsedAudio.audioStream) { |
544 | localCommand = localCommand.noAudio() | 569 | localCommand = localCommand.noAudio() |
545 | streamsToProcess = [ 'VIDEO' ] | 570 | streamsToProcess = [ 'audio' ] |
546 | } | 571 | } |
547 | 572 | ||
548 | for (const streamType of streamsToProcess) { | 573 | for (const streamType of streamsToProcess) { |
@@ -564,9 +589,9 @@ async function presetVideo ( | |||
564 | 589 | ||
565 | logger.debug('Apply ffmpeg params from %s.', builderResult.encoder, builderResult) | 590 | logger.debug('Apply ffmpeg params from %s.', builderResult.encoder, builderResult) |
566 | 591 | ||
567 | if (streamType === 'VIDEO') { | 592 | if (streamType === 'video') { |
568 | localCommand.videoCodec(builderResult.encoder) | 593 | localCommand.videoCodec(builderResult.encoder) |
569 | } else if (streamType === 'AUDIO') { | 594 | } else if (streamType === 'audio') { |
570 | localCommand.audioCodec(builderResult.encoder) | 595 | localCommand.audioCodec(builderResult.encoder) |
571 | } | 596 | } |
572 | 597 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 89491708e..0fab872a9 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -341,17 +341,6 @@ const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = { | |||
341 | KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum) | 341 | KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum) |
342 | } | 342 | } |
343 | 343 | ||
344 | const VIDEO_TRANSCODING_ENCODERS = { | ||
345 | VIDEO: [ 'libx264' ], | ||
346 | |||
347 | // Try the first one, if not available try the second one etc | ||
348 | AUDIO: [ | ||
349 | // we favor VBR, if a good AAC encoder is available | ||
350 | 'libfdk_aac', | ||
351 | 'aac' | ||
352 | ] | ||
353 | } | ||
354 | |||
355 | const DEFAULT_AUDIO_RESOLUTION = VideoResolution.H_480P | 344 | const DEFAULT_AUDIO_RESOLUTION = VideoResolution.H_480P |
356 | 345 | ||
357 | const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { | 346 | const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { |
@@ -828,7 +817,6 @@ export { | |||
828 | ACTOR_FOLLOW_SCORE, | 817 | ACTOR_FOLLOW_SCORE, |
829 | PREVIEWS_SIZE, | 818 | PREVIEWS_SIZE, |
830 | REMOTE_SCHEME, | 819 | REMOTE_SCHEME, |
831 | VIDEO_TRANSCODING_ENCODERS, | ||
832 | FOLLOW_STATES, | 820 | FOLLOW_STATES, |
833 | DEFAULT_USER_THEME_NAME, | 821 | DEFAULT_USER_THEME_NAME, |
834 | SERVER_ACTOR_NAME, | 822 | SERVER_ACTOR_NAME, |
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts index d968f05da..c8e5bcb77 100644 --- a/server/lib/live-manager.ts +++ b/server/lib/live-manager.ts | |||
@@ -25,7 +25,7 @@ import { cleanupLive } from './job-queue/handlers/video-live-ending' | |||
25 | import { PeerTubeSocket } from './peertube-socket' | 25 | import { PeerTubeSocket } from './peertube-socket' |
26 | import { isAbleToUploadVideo } from './user' | 26 | import { isAbleToUploadVideo } from './user' |
27 | import { getHLSDirectory } from './video-paths' | 27 | import { getHLSDirectory } from './video-paths' |
28 | import { availableEncoders } from './video-transcoding-profiles' | 28 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' |
29 | 29 | ||
30 | import memoizee = require('memoizee') | 30 | import memoizee = require('memoizee') |
31 | const NodeRtmpSession = require('node-media-server/node_rtmp_session') | 31 | const NodeRtmpSession = require('node-media-server/node_rtmp_session') |
@@ -337,7 +337,7 @@ class LiveManager { | |||
337 | outPath, | 337 | outPath, |
338 | resolutions: allResolutions, | 338 | resolutions: allResolutions, |
339 | fps, | 339 | fps, |
340 | availableEncoders, | 340 | availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), |
341 | profile: 'default' | 341 | profile: 'default' |
342 | }) | 342 | }) |
343 | : getLiveMuxingCommand(rtmpUrl, outPath) | 343 | : getLiveMuxingCommand(rtmpUrl, outPath) |
diff --git a/server/lib/video-transcoding-profiles.ts b/server/lib/video-transcoding-profiles.ts index 338f4de4a..bbe556e75 100644 --- a/server/lib/video-transcoding-profiles.ts +++ b/server/lib/video-transcoding-profiles.ts | |||
@@ -78,32 +78,83 @@ const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum }) | |||
78 | return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] } | 78 | return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] } |
79 | } | 79 | } |
80 | 80 | ||
81 | const availableEncoders: AvailableEncoders = { | 81 | // Used to get and update available encoders |
82 | vod: { | 82 | class VideoTranscodingProfilesManager { |
83 | libx264: { | 83 | private static instance: VideoTranscodingProfilesManager |
84 | default: defaultX264VODOptionsBuilder | 84 | |
85 | }, | 85 | // 1 === less priority |
86 | aac: { | 86 | private readonly encodersPriorities = { |
87 | default: defaultAACOptionsBuilder | 87 | video: [ |
88 | { name: 'libx264', priority: 100 } | ||
89 | ], | ||
90 | |||
91 | // Try the first one, if not available try the second one etc | ||
92 | audio: [ | ||
93 | // we favor VBR, if a good AAC encoder is available | ||
94 | { name: 'libfdk_aac', priority: 200 }, | ||
95 | { name: 'aac', priority: 100 } | ||
96 | ] | ||
97 | } | ||
98 | |||
99 | private readonly availableEncoders = { | ||
100 | vod: { | ||
101 | libx264: { | ||
102 | default: defaultX264VODOptionsBuilder | ||
103 | }, | ||
104 | aac: { | ||
105 | default: defaultAACOptionsBuilder | ||
106 | }, | ||
107 | libfdk_aac: { | ||
108 | default: defaultLibFDKAACVODOptionsBuilder | ||
109 | } | ||
88 | }, | 110 | }, |
89 | libfdk_aac: { | 111 | live: { |
90 | default: defaultLibFDKAACVODOptionsBuilder | 112 | libx264: { |
113 | default: defaultX264LiveOptionsBuilder | ||
114 | }, | ||
115 | aac: { | ||
116 | default: defaultAACOptionsBuilder | ||
117 | } | ||
91 | } | 118 | } |
92 | }, | 119 | } |
93 | live: { | 120 | |
94 | libx264: { | 121 | private constructor () { |
95 | default: defaultX264LiveOptionsBuilder | 122 | |
96 | }, | 123 | } |
97 | aac: { | 124 | |
98 | default: defaultAACOptionsBuilder | 125 | getAvailableEncoders (): AvailableEncoders { |
126 | const encodersToTry = { | ||
127 | video: this.getEncodersByPriority('video'), | ||
128 | audio: this.getEncodersByPriority('audio') | ||
99 | } | 129 | } |
130 | |||
131 | return Object.assign({}, this.availableEncoders, { encodersToTry }) | ||
132 | } | ||
133 | |||
134 | getAvailableProfiles (type: 'vod' | 'live') { | ||
135 | return this.availableEncoders[type] | ||
136 | } | ||
137 | |||
138 | private getEncodersByPriority (type: 'video' | 'audio') { | ||
139 | return this.encodersPriorities[type] | ||
140 | .sort((e1, e2) => { | ||
141 | if (e1.priority > e2.priority) return -1 | ||
142 | else if (e1.priority === e2.priority) return 0 | ||
143 | |||
144 | return 1 | ||
145 | }) | ||
146 | .map(e => e.name) | ||
147 | } | ||
148 | |||
149 | static get Instance () { | ||
150 | return this.instance || (this.instance = new this()) | ||
100 | } | 151 | } |
101 | } | 152 | } |
102 | 153 | ||
103 | // --------------------------------------------------------------------------- | 154 | // --------------------------------------------------------------------------- |
104 | 155 | ||
105 | export { | 156 | export { |
106 | availableEncoders | 157 | VideoTranscodingProfilesManager |
107 | } | 158 | } |
108 | 159 | ||
109 | // --------------------------------------------------------------------------- | 160 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index 7af7a481c..c4b3425d1 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -14,7 +14,7 @@ import { VideoFileModel } from '../models/video/video-file' | |||
14 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 14 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
15 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' | 15 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' |
16 | import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath } from './video-paths' | 16 | import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath } from './video-paths' |
17 | import { availableEncoders } from './video-transcoding-profiles' | 17 | import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' |
18 | 18 | ||
19 | /** | 19 | /** |
20 | * | 20 | * |
@@ -41,7 +41,7 @@ async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFile: | |||
41 | inputPath: videoInputPath, | 41 | inputPath: videoInputPath, |
42 | outputPath: videoTranscodedPath, | 42 | outputPath: videoTranscodedPath, |
43 | 43 | ||
44 | availableEncoders, | 44 | availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), |
45 | profile: 'default', | 45 | profile: 'default', |
46 | 46 | ||
47 | resolution: inputVideoFile.resolution, | 47 | resolution: inputVideoFile.resolution, |
@@ -95,7 +95,7 @@ async function transcodeNewWebTorrentResolution (video: MVideoWithFile, resoluti | |||
95 | inputPath: videoInputPath, | 95 | inputPath: videoInputPath, |
96 | outputPath: videoTranscodedPath, | 96 | outputPath: videoTranscodedPath, |
97 | 97 | ||
98 | availableEncoders, | 98 | availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), |
99 | profile: 'default', | 99 | profile: 'default', |
100 | 100 | ||
101 | resolution, | 101 | resolution, |
@@ -107,7 +107,7 @@ async function transcodeNewWebTorrentResolution (video: MVideoWithFile, resoluti | |||
107 | inputPath: videoInputPath, | 107 | inputPath: videoInputPath, |
108 | outputPath: videoTranscodedPath, | 108 | outputPath: videoTranscodedPath, |
109 | 109 | ||
110 | availableEncoders, | 110 | availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), |
111 | profile: 'default', | 111 | profile: 'default', |
112 | 112 | ||
113 | resolution, | 113 | resolution, |
@@ -142,7 +142,7 @@ async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: Video | |||
142 | inputPath: tmpPreviewPath, | 142 | inputPath: tmpPreviewPath, |
143 | outputPath: videoTranscodedPath, | 143 | outputPath: videoTranscodedPath, |
144 | 144 | ||
145 | availableEncoders, | 145 | availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), |
146 | profile: 'default', | 146 | profile: 'default', |
147 | 147 | ||
148 | audioPath: audioInputPath, | 148 | audioPath: audioInputPath, |
@@ -283,7 +283,7 @@ async function generateHlsPlaylistCommon (options: { | |||
283 | inputPath, | 283 | inputPath, |
284 | outputPath, | 284 | outputPath, |
285 | 285 | ||
286 | availableEncoders, | 286 | availableEncoders: VideoTranscodingProfilesManager.Instance.getAvailableEncoders(), |
287 | profile: 'default', | 287 | profile: 'default', |
288 | 288 | ||
289 | resolution, | 289 | resolution, |