aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--scripts/print-transcode-command.ts4
-rw-r--r--server/helpers/ffmpeg-utils.ts51
-rw-r--r--server/initializers/constants.ts12
-rw-r--r--server/lib/live-manager.ts4
-rw-r--r--server/lib/video-transcoding-profiles.ts85
-rw-r--r--server/lib/video-transcoding.ts12
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
4import * as program from 'commander' 4import * as program from 'commander'
5import * as ffmpeg from 'fluent-ffmpeg' 5import * as ffmpeg from 'fluent-ffmpeg'
6import { availableEncoders } from '@server/lib/video-transcoding-profiles'
7import { buildx264VODCommand, runCommand, TranscodeOptions } from '@server/helpers/ffmpeg-utils' 6import { buildx264VODCommand, runCommand, TranscodeOptions } from '@server/helpers/ffmpeg-utils'
8import { exit } from 'process' 7import { exit } from 'process'
8import { VideoTranscodingProfilesManager } from '@server/lib/video-transcoding-profiles'
9 9
10program 10program
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'
2import * as ffmpeg from 'fluent-ffmpeg' 2import * as ffmpeg from 'fluent-ffmpeg'
3import { readFile, remove, writeFile } from 'fs-extra' 3import { readFile, remove, writeFile } from 'fs-extra'
4import { dirname, join } from 'path' 4import { dirname, join } from 'path'
5import { FFMPEG_NICE, VIDEO_LIVE, VIDEO_TRANSCODING_ENCODERS } from '@server/initializers/constants' 5import { FFMPEG_NICE, VIDEO_LIVE } from '@server/initializers/constants'
6import { VideoResolution } from '../../shared/models/videos' 6import { VideoResolution } from '../../shared/models/videos'
7import { checkFFmpegEncoders } from '../initializers/checker-before-init' 7import { checkFFmpegEncoders } from '../initializers/checker-before-init'
8import { CONFIG } from '../initializers/config' 8import { CONFIG } from '../initializers/config'
@@ -46,11 +46,22 @@ export interface EncoderProfile <T> {
46} 46}
47 47
48export type AvailableEncoders = { 48export 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
63type 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
483async function getEncoderBuilderResult (options: { 501async 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
344const 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
355const DEFAULT_AUDIO_RESOLUTION = VideoResolution.H_480P 344const DEFAULT_AUDIO_RESOLUTION = VideoResolution.H_480P
356 345
357const VIDEO_RATE_TYPES: { [ id: string ]: VideoRateType } = { 346const 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'
25import { PeerTubeSocket } from './peertube-socket' 25import { PeerTubeSocket } from './peertube-socket'
26import { isAbleToUploadVideo } from './user' 26import { isAbleToUploadVideo } from './user'
27import { getHLSDirectory } from './video-paths' 27import { getHLSDirectory } from './video-paths'
28import { availableEncoders } from './video-transcoding-profiles' 28import { VideoTranscodingProfilesManager } from './video-transcoding-profiles'
29 29
30import memoizee = require('memoizee') 30import memoizee = require('memoizee')
31const NodeRtmpSession = require('node-media-server/node_rtmp_session') 31const 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
81const availableEncoders: AvailableEncoders = { 81// Used to get and update available encoders
82 vod: { 82class 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
105export { 156export {
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'
14import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 14import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
15import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' 15import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls'
16import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath } from './video-paths' 16import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath } from './video-paths'
17import { availableEncoders } from './video-transcoding-profiles' 17import { 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,