Add transcoding module comments
This commit is contained in:
parent
33ff70baa6
commit
6b67897e2e
|
@ -9,6 +9,13 @@ import { getAudioStream, getClosestFramerateStandard, getVideoFileFPS } from './
|
||||||
import { processImage } from './image-utils'
|
import { processImage } from './image-utils'
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Functions that run transcoding/muxing ffmpeg processes
|
||||||
|
* Mainly called by lib/video-transcoding.ts and lib/live-manager.ts
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Encoder options
|
// Encoder options
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -5,6 +5,12 @@ import { CONFIG } from '../initializers/config'
|
||||||
import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
|
import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
|
||||||
import { logger } from './logger'
|
import { logger } from './logger'
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Helpers to run ffprobe and extract data from the JSON output
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
function ffprobePromise (path: string) {
|
function ffprobePromise (path: string) {
|
||||||
return new Promise<ffmpeg.FfprobeData>((res, rej) => {
|
return new Promise<ffmpeg.FfprobeData>((res, rej) => {
|
||||||
ffmpeg.ffprobe(path, (err, data) => {
|
ffmpeg.ffprobe(path, (err, data) => {
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
|
import { logger } from '@server/helpers/logger'
|
||||||
import { getTargetBitrate } from '../../shared/models/videos'
|
import { getTargetBitrate } from '../../shared/models/videos'
|
||||||
import { AvailableEncoders, buildStreamSuffix, EncoderOptionsBuilder } from '../helpers/ffmpeg-utils'
|
import { AvailableEncoders, buildStreamSuffix, EncoderOptionsBuilder } from '../helpers/ffmpeg-utils'
|
||||||
import { ffprobePromise, getAudioStream, getMaxAudioBitrate, getVideoFileBitrate, getVideoStreamFromFile } from '../helpers/ffprobe-utils'
|
import {
|
||||||
|
canDoQuickAudioTranscode,
|
||||||
|
ffprobePromise,
|
||||||
|
getAudioStream,
|
||||||
|
getMaxAudioBitrate,
|
||||||
|
getVideoFileBitrate,
|
||||||
|
getVideoStreamFromFile
|
||||||
|
} from '../helpers/ffprobe-utils'
|
||||||
import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
|
import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
/**
|
||||||
// Available encoders profiles
|
*
|
||||||
// ---------------------------------------------------------------------------
|
* Available encoders and profiles for the transcoding jobs
|
||||||
|
* These functions are used by ffmpeg-utils that will get the encoders and options depending on the chosen profile
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
// Resources:
|
// Resources:
|
||||||
// * https://slhck.info/video/2017/03/01/rate-control.html
|
// * https://slhck.info/video/2017/03/01/rate-control.html
|
||||||
|
@ -27,7 +38,8 @@ const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = async ({ input, reso
|
||||||
|
|
||||||
return {
|
return {
|
||||||
outputOptions: [
|
outputOptions: [
|
||||||
`-maxrate ${targetBitrate}`, `-bufsize ${targetBitrate * 2}`
|
`-maxrate ${targetBitrate}`,
|
||||||
|
`-bufsize ${targetBitrate * 2}`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +57,14 @@ const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = async ({ resolution
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum }) => {
|
const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum }) => {
|
||||||
const parsedAudio = await getAudioStream(input)
|
const probe = await ffprobePromise(input)
|
||||||
|
|
||||||
|
if (await canDoQuickAudioTranscode(input, probe)) {
|
||||||
|
logger.debug('Copy audio stream %s by AAC encoder.', input)
|
||||||
|
return { copy: true, outputOptions: [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedAudio = await getAudioStream(input, probe)
|
||||||
|
|
||||||
// We try to reduce the ceiling bitrate by making rough matches of bitrates
|
// We try to reduce the ceiling bitrate by making rough matches of bitrates
|
||||||
// Of course this is far from perfect, but it might save some space in the end
|
// Of course this is far from perfect, but it might save some space in the end
|
||||||
|
@ -54,11 +73,13 @@ const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNu
|
||||||
|
|
||||||
const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate)
|
const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate)
|
||||||
|
|
||||||
|
logger.debug('Calculating audio bitrate of %s by AAC encoder.', input, { bitrate: parsedAudio.bitrate, audioCodecName })
|
||||||
|
|
||||||
if (bitrate !== undefined && bitrate !== -1) {
|
if (bitrate !== undefined && bitrate !== -1) {
|
||||||
return { outputOptions: [ buildStreamSuffix('-b:a', streamNum), bitrate + 'k' ] }
|
return { outputOptions: [ buildStreamSuffix('-b:a', streamNum), bitrate + 'k' ] }
|
||||||
}
|
}
|
||||||
|
|
||||||
return { copy: true, outputOptions: [] }
|
return { outputOptions: [ ] }
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum }) => {
|
const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum }) => {
|
||||||
|
|
|
@ -16,8 +16,13 @@ import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath
|
||||||
import { availableEncoders } from './video-transcoding-profiles'
|
import { availableEncoders } from './video-transcoding-profiles'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optimize the original video file and replace it. The resolution is not changed.
|
*
|
||||||
|
* Functions that run transcoding functions, update the database, cleanup files, create torrent files...
|
||||||
|
* Mainly called by the job queue
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Optimize the original video file and replace it. The resolution is not changed.
|
||||||
async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) {
|
async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) {
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
const newExtname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
|
@ -62,9 +67,7 @@ async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFileA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Transcode the original video file to a lower resolution.
|
||||||
* Transcode the original video file to a lower resolution.
|
|
||||||
*/
|
|
||||||
async function transcodeNewResolution (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) {
|
async function transcodeNewResolution (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) {
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
const extname = '.mp4'
|
const extname = '.mp4'
|
||||||
|
@ -110,6 +113,7 @@ async function transcodeNewResolution (video: MVideoWithFile, resolution: VideoR
|
||||||
return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
|
return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge an image with an audio file to create a video
|
||||||
async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: VideoResolution) {
|
async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: VideoResolution) {
|
||||||
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
|
||||||
const newExtname = '.mp4'
|
const newExtname = '.mp4'
|
||||||
|
@ -159,6 +163,7 @@ async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: Video
|
||||||
return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
|
return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate an HLS playlist from an input file, and update the master playlist
|
||||||
async function generateHlsPlaylist (options: {
|
async function generateHlsPlaylist (options: {
|
||||||
video: MVideoWithFile
|
video: MVideoWithFile
|
||||||
videoInputPath: string
|
videoInputPath: string
|
||||||
|
|
|
@ -39,6 +39,7 @@ import {
|
||||||
viewVideo,
|
viewVideo,
|
||||||
wait,
|
wait,
|
||||||
waitJobs,
|
waitJobs,
|
||||||
|
waitUntilLivePublished,
|
||||||
waitUntilLiveStarts,
|
waitUntilLiveStarts,
|
||||||
waitUntilLog
|
waitUntilLog
|
||||||
} from '../../../../shared/extra-utils'
|
} from '../../../../shared/extra-utils'
|
||||||
|
@ -396,7 +397,7 @@ describe('Test live', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should enable transcoding with some resolutions and correctly save them', async function () {
|
it('Should enable transcoding with some resolutions and correctly save them', async function () {
|
||||||
this.timeout(60000)
|
this.timeout(120000)
|
||||||
|
|
||||||
const resolutions = [ 240, 360, 720 ]
|
const resolutions = [ 240, 360, 720 ]
|
||||||
|
|
||||||
|
@ -410,13 +411,14 @@ describe('Test live', function () {
|
||||||
await testVideoResolutions(liveVideoId, resolutions)
|
await testVideoResolutions(liveVideoId, resolutions)
|
||||||
|
|
||||||
await stopFfmpeg(command)
|
await stopFfmpeg(command)
|
||||||
|
await waitUntilLivePublished(servers[0].url, servers[0].accessToken, liveVideoId)
|
||||||
|
|
||||||
await waitJobs(servers)
|
await waitJobs(servers)
|
||||||
|
|
||||||
const bitrateLimits = {
|
const bitrateLimits = {
|
||||||
720: 2800 * 1000,
|
720: 3000 * 1000,
|
||||||
360: 780 * 1000,
|
360: 1100 * 1000,
|
||||||
240: 320 * 1000
|
240: 600 * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const server of servers) {
|
for (const server of servers) {
|
||||||
|
@ -442,7 +444,7 @@ describe('Test live', function () {
|
||||||
|
|
||||||
const probe = await ffprobePromise(segmentPath)
|
const probe = await ffprobePromise(segmentPath)
|
||||||
const videoStream = await getVideoStreamFromFile(segmentPath, probe)
|
const videoStream = await getVideoStreamFromFile(segmentPath, probe)
|
||||||
console.log(videoStream)
|
|
||||||
expect(probe.format.bit_rate).to.be.below(bitrateLimits[videoStream.height])
|
expect(probe.format.bit_rate).to.be.below(bitrateLimits[videoStream.height])
|
||||||
|
|
||||||
await makeRawRequest(file.torrentUrl, 200)
|
await makeRawRequest(file.torrentUrl, 200)
|
||||||
|
|
|
@ -128,7 +128,15 @@ async function stopFfmpeg (command: ffmpeg.FfmpegCommand) {
|
||||||
await wait(500)
|
await wait(500)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitUntilLiveStarts (url: string, token: string, videoId: number | string) {
|
function waitUntilLiveStarts (url: string, token: string, videoId: number | string) {
|
||||||
|
return waitWhileLiveState(url, token, videoId, VideoState.WAITING_FOR_LIVE)
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitUntilLivePublished (url: string, token: string, videoId: number | string) {
|
||||||
|
return waitWhileLiveState(url, token, videoId, VideoState.PUBLISHED)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitWhileLiveState (url: string, token: string, videoId: number | string, state: VideoState) {
|
||||||
let video: VideoDetails
|
let video: VideoDetails
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
@ -136,7 +144,7 @@ async function waitUntilLiveStarts (url: string, token: string, videoId: number
|
||||||
video = res.body
|
video = res.body
|
||||||
|
|
||||||
await wait(500)
|
await wait(500)
|
||||||
} while (video.state.id === VideoState.WAITING_FOR_LIVE)
|
} while (video.state.id === state)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkLiveCleanup (server: ServerInfo, videoUUID: string, resolutions: number[] = []) {
|
async function checkLiveCleanup (server: ServerInfo, videoUUID: string, resolutions: number[] = []) {
|
||||||
|
@ -168,6 +176,7 @@ async function checkLiveCleanup (server: ServerInfo, videoUUID: string, resoluti
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getLive,
|
getLive,
|
||||||
|
waitUntilLivePublished,
|
||||||
updateLive,
|
updateLive,
|
||||||
waitUntilLiveStarts,
|
waitUntilLiveStarts,
|
||||||
createLive,
|
createLive,
|
||||||
|
|
Loading…
Reference in New Issue