diff options
Diffstat (limited to 'server/lib')
-rw-r--r-- | server/lib/live-manager.ts | 8 | ||||
-rw-r--r-- | server/lib/video-transcoding.ts | 114 |
2 files changed, 115 insertions, 7 deletions
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts index 4d2e9b1b3..b9e5d4561 100644 --- a/server/lib/live-manager.ts +++ b/server/lib/live-manager.ts | |||
@@ -4,7 +4,7 @@ import { FfmpegCommand } from 'fluent-ffmpeg' | |||
4 | import { ensureDir, stat } from 'fs-extra' | 4 | import { ensureDir, stat } from 'fs-extra' |
5 | import { basename } from 'path' | 5 | import { basename } from 'path' |
6 | import { isTestInstance } from '@server/helpers/core-utils' | 6 | import { isTestInstance } from '@server/helpers/core-utils' |
7 | import { runLiveMuxing, runLiveTranscoding } from '@server/helpers/ffmpeg-utils' | 7 | import { getLiveMuxingCommand, getLiveTranscodingCommand } from '@server/helpers/ffmpeg-utils' |
8 | import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils' | 8 | import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution } from '@server/helpers/ffprobe-utils' |
9 | import { logger } from '@server/helpers/logger' | 9 | import { logger } from '@server/helpers/logger' |
10 | import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' | 10 | import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config' |
@@ -264,8 +264,8 @@ class LiveManager { | |||
264 | const deleteSegments = videoLive.saveReplay === false | 264 | const deleteSegments = videoLive.saveReplay === false |
265 | 265 | ||
266 | const ffmpegExec = CONFIG.LIVE.TRANSCODING.ENABLED | 266 | const ffmpegExec = CONFIG.LIVE.TRANSCODING.ENABLED |
267 | ? runLiveTranscoding(rtmpUrl, outPath, allResolutions, fps, deleteSegments) | 267 | ? getLiveTranscodingCommand(rtmpUrl, outPath, allResolutions, fps, deleteSegments) |
268 | : runLiveMuxing(rtmpUrl, outPath, deleteSegments) | 268 | : getLiveMuxingCommand(rtmpUrl, outPath, deleteSegments) |
269 | 269 | ||
270 | logger.info('Running live muxing/transcoding for %s.', videoUUID) | 270 | logger.info('Running live muxing/transcoding for %s.', videoUUID) |
271 | this.transSessions.set(sessionId, ffmpegExec) | 271 | this.transSessions.set(sessionId, ffmpegExec) |
@@ -382,6 +382,8 @@ class LiveManager { | |||
382 | }) | 382 | }) |
383 | 383 | ||
384 | ffmpegExec.on('end', () => onFFmpegEnded()) | 384 | ffmpegExec.on('end', () => onFFmpegEnded()) |
385 | |||
386 | ffmpegExec.run() | ||
385 | } | 387 | } |
386 | 388 | ||
387 | private async onEndTransmuxing (videoId: number, cleanupNow = false) { | 389 | private async onEndTransmuxing (videoId: number, cleanupNow = false) { |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index ca969b235..0e6a0e984 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -2,13 +2,26 @@ import { copyFile, ensureDir, move, remove, stat } from 'fs-extra' | |||
2 | import { basename, extname as extnameUtil, join } from 'path' | 2 | import { basename, extname as extnameUtil, join } from 'path' |
3 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 3 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
4 | import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models' | 4 | import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models' |
5 | import { VideoResolution } from '../../shared/models/videos' | 5 | import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' |
6 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 6 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' |
7 | import { transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' | 7 | import { AvailableEncoders, EncoderOptionsBuilder, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' |
8 | import { canDoQuickTranscode, getDurationFromVideoFile, getMetadataFromFile, getVideoFileFPS } from '../helpers/ffprobe-utils' | 8 | import { |
9 | canDoQuickTranscode, | ||
10 | getAudioStream, | ||
11 | getDurationFromVideoFile, | ||
12 | getMaxAudioBitrate, | ||
13 | getMetadataFromFile, | ||
14 | getVideoFileBitrate, | ||
15 | getVideoFileFPS | ||
16 | } from '../helpers/ffprobe-utils' | ||
9 | import { logger } from '../helpers/logger' | 17 | import { logger } from '../helpers/logger' |
10 | import { CONFIG } from '../initializers/config' | 18 | import { CONFIG } from '../initializers/config' |
11 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' | 19 | import { |
20 | HLS_STREAMING_PLAYLIST_DIRECTORY, | ||
21 | P2P_MEDIA_LOADER_PEER_VERSION, | ||
22 | VIDEO_TRANSCODING_FPS, | ||
23 | WEBSERVER | ||
24 | } from '../initializers/constants' | ||
12 | import { VideoFileModel } from '../models/video/video-file' | 25 | import { VideoFileModel } from '../models/video/video-file' |
13 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 26 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
14 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' | 27 | import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls' |
@@ -31,8 +44,13 @@ async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFileA | |||
31 | 44 | ||
32 | const transcodeOptions: TranscodeOptions = { | 45 | const transcodeOptions: TranscodeOptions = { |
33 | type: transcodeType, | 46 | type: transcodeType, |
47 | |||
34 | inputPath: videoInputPath, | 48 | inputPath: videoInputPath, |
35 | outputPath: videoTranscodedPath, | 49 | outputPath: videoTranscodedPath, |
50 | |||
51 | availableEncoders, | ||
52 | profile: 'default', | ||
53 | |||
36 | resolution: inputVideoFile.resolution | 54 | resolution: inputVideoFile.resolution |
37 | } | 55 | } |
38 | 56 | ||
@@ -78,14 +96,23 @@ async function transcodeNewResolution (video: MVideoWithFile, resolution: VideoR | |||
78 | const transcodeOptions = resolution === VideoResolution.H_NOVIDEO | 96 | const transcodeOptions = resolution === VideoResolution.H_NOVIDEO |
79 | ? { | 97 | ? { |
80 | type: 'only-audio' as 'only-audio', | 98 | type: 'only-audio' as 'only-audio', |
99 | |||
81 | inputPath: videoInputPath, | 100 | inputPath: videoInputPath, |
82 | outputPath: videoTranscodedPath, | 101 | outputPath: videoTranscodedPath, |
102 | |||
103 | availableEncoders, | ||
104 | profile: 'default', | ||
105 | |||
83 | resolution | 106 | resolution |
84 | } | 107 | } |
85 | : { | 108 | : { |
86 | type: 'video' as 'video', | 109 | type: 'video' as 'video', |
87 | inputPath: videoInputPath, | 110 | inputPath: videoInputPath, |
88 | outputPath: videoTranscodedPath, | 111 | outputPath: videoTranscodedPath, |
112 | |||
113 | availableEncoders, | ||
114 | profile: 'default', | ||
115 | |||
89 | resolution, | 116 | resolution, |
90 | isPortraitMode: isPortrait | 117 | isPortraitMode: isPortrait |
91 | } | 118 | } |
@@ -111,8 +138,13 @@ async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: Video | |||
111 | 138 | ||
112 | const transcodeOptions = { | 139 | const transcodeOptions = { |
113 | type: 'merge-audio' as 'merge-audio', | 140 | type: 'merge-audio' as 'merge-audio', |
141 | |||
114 | inputPath: tmpPreviewPath, | 142 | inputPath: tmpPreviewPath, |
115 | outputPath: videoTranscodedPath, | 143 | outputPath: videoTranscodedPath, |
144 | |||
145 | availableEncoders, | ||
146 | profile: 'default', | ||
147 | |||
116 | audioPath: audioInputPath, | 148 | audioPath: audioInputPath, |
117 | resolution | 149 | resolution |
118 | } | 150 | } |
@@ -156,8 +188,13 @@ async function generateHlsPlaylist (options: { | |||
156 | 188 | ||
157 | const transcodeOptions = { | 189 | const transcodeOptions = { |
158 | type: 'hls' as 'hls', | 190 | type: 'hls' as 'hls', |
191 | |||
159 | inputPath: videoInputPath, | 192 | inputPath: videoInputPath, |
160 | outputPath, | 193 | outputPath, |
194 | |||
195 | availableEncoders, | ||
196 | profile: 'default', | ||
197 | |||
161 | resolution, | 198 | resolution, |
162 | copyCodecs, | 199 | copyCodecs, |
163 | isPortraitMode, | 200 | isPortraitMode, |
@@ -216,6 +253,75 @@ async function generateHlsPlaylist (options: { | |||
216 | } | 253 | } |
217 | 254 | ||
218 | // --------------------------------------------------------------------------- | 255 | // --------------------------------------------------------------------------- |
256 | // Available encoders profiles | ||
257 | // --------------------------------------------------------------------------- | ||
258 | |||
259 | const defaultX264OptionsBuilder: EncoderOptionsBuilder = async ({ input, resolution, fps }) => { | ||
260 | if (!fps) return { outputOptions: [] } | ||
261 | |||
262 | let targetBitrate = getTargetBitrate(resolution, fps, VIDEO_TRANSCODING_FPS) | ||
263 | |||
264 | // Don't transcode to an higher bitrate than the original file | ||
265 | const fileBitrate = await getVideoFileBitrate(input) | ||
266 | targetBitrate = Math.min(targetBitrate, fileBitrate) | ||
267 | |||
268 | return { | ||
269 | outputOptions: [ | ||
270 | // Constrained Encoding (VBV) | ||
271 | // https://slhck.info/video/2017/03/01/rate-control.html | ||
272 | // https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate | ||
273 | `-maxrate ${targetBitrate}`, `-bufsize ${targetBitrate * 2}` | ||
274 | ] | ||
275 | } | ||
276 | } | ||
277 | |||
278 | const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input }) => { | ||
279 | const parsedAudio = await getAudioStream(input) | ||
280 | |||
281 | // we try to reduce the ceiling bitrate by making rough matches of bitrates | ||
282 | // of course this is far from perfect, but it might save some space in the end | ||
283 | |||
284 | const audioCodecName = parsedAudio.audioStream['codec_name'] | ||
285 | |||
286 | const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate) | ||
287 | |||
288 | if (bitrate !== undefined && bitrate !== -1) { | ||
289 | return { outputOptions: [ '-b:a', bitrate + 'k' ] } | ||
290 | } | ||
291 | |||
292 | return { outputOptions: [] } | ||
293 | } | ||
294 | |||
295 | const defaultLibFDKAACOptionsBuilder: EncoderOptionsBuilder = () => { | ||
296 | return { outputOptions: [ '-aq', '5' ] } | ||
297 | } | ||
298 | |||
299 | const availableEncoders: AvailableEncoders = { | ||
300 | vod: { | ||
301 | libx264: { | ||
302 | default: defaultX264OptionsBuilder | ||
303 | }, | ||
304 | aac: { | ||
305 | default: defaultAACOptionsBuilder | ||
306 | }, | ||
307 | libfdkAAC: { | ||
308 | default: defaultLibFDKAACOptionsBuilder | ||
309 | } | ||
310 | }, | ||
311 | live: { | ||
312 | libx264: { | ||
313 | default: defaultX264OptionsBuilder | ||
314 | }, | ||
315 | aac: { | ||
316 | default: defaultAACOptionsBuilder | ||
317 | }, | ||
318 | libfdkAAC: { | ||
319 | default: defaultLibFDKAACOptionsBuilder | ||
320 | } | ||
321 | } | ||
322 | } | ||
323 | |||
324 | // --------------------------------------------------------------------------- | ||
219 | 325 | ||
220 | export { | 326 | export { |
221 | generateHlsPlaylist, | 327 | generateHlsPlaylist, |