From ab14f0e0dca878dbaccc8f6a895a68e4269c9873 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 May 2023 15:55:51 +0200 Subject: Prefer video studio instead of video edition Clearer and easier to find in the project --- packages/peertube-runner/server/process/process.ts | 6 +- .../server/process/shared/common.ts | 9 +- .../peertube-runner/server/process/shared/index.ts | 1 - .../server/process/shared/process-studio.ts | 50 ++++--- .../server/process/shared/process-vod.ts | 85 ++++++----- .../server/process/shared/transcoding-profiles.ts | 134 ------------------ .../peertube-runner/server/shared/supported-job.ts | 4 +- server/controllers/api/runners/jobs-files.ts | 6 +- server/controllers/api/runners/jobs.ts | 4 +- server/helpers/custom-validators/runners/jobs.ts | 16 +-- .../lib/job-queue/handlers/video-studio-edition.ts | 6 +- .../runners/job-handlers/abstract-job-handler.ts | 10 +- server/lib/runners/job-handlers/index.ts | 2 +- .../runners/job-handlers/runner-job-handlers.ts | 4 +- .../video-edition-transcoding-job-handler.ts | 157 --------------------- .../video-studio-transcoding-job-handler.ts | 157 +++++++++++++++++++++ .../transcoding/default-transcoding-profiles.ts | 145 +------------------ server/lib/video-studio.ts | 8 +- server/middlewares/validators/runners/job-files.ts | 4 +- server/tests/api/check-params/runners.ts | 10 +- .../tests/api/runners/runner-studio-transcoding.ts | 10 +- server/tests/api/transcoding/video-studio.ts | 4 +- .../ffmpeg/ffmpeg-default-transcoding-profile.ts | 134 ++++++++++++++++++ shared/ffmpeg/index.ts | 1 + shared/models/runners/runner-job-payload.model.ts | 4 +- .../runners/runner-job-private-payload.model.ts | 4 +- .../runners/runner-job-success-body.model.ts | 4 +- shared/models/runners/runner-job-type.type.ts | 2 +- 28 files changed, 428 insertions(+), 553 deletions(-) delete mode 100644 packages/peertube-runner/server/process/shared/transcoding-profiles.ts delete mode 100644 server/lib/runners/job-handlers/video-edition-transcoding-job-handler.ts create mode 100644 server/lib/runners/job-handlers/video-studio-transcoding-job-handler.ts create mode 100644 shared/ffmpeg/ffmpeg-default-transcoding-profile.ts diff --git a/packages/peertube-runner/server/process/process.ts b/packages/peertube-runner/server/process/process.ts index ef231cb38..1caafda8c 100644 --- a/packages/peertube-runner/server/process/process.ts +++ b/packages/peertube-runner/server/process/process.ts @@ -1,7 +1,7 @@ import { logger } from 'packages/peertube-runner/shared/logger' import { RunnerJobLiveRTMPHLSTranscodingPayload, - RunnerJobVideoEditionTranscodingPayload, + RunnerJobStudioTranscodingPayload, RunnerJobVODAudioMergeTranscodingPayload, RunnerJobVODHLSTranscodingPayload, RunnerJobVODWebVideoTranscodingPayload @@ -23,8 +23,8 @@ export async function processJob (options: ProcessOptions) { await processHLSTranscoding(options as ProcessOptions) } else if (job.type === 'live-rtmp-hls-transcoding') { await new ProcessLiveRTMPHLSTranscoding(options as ProcessOptions).process() - } else if (job.type === 'video-edition-transcoding') { - await processStudioTranscoding(options as ProcessOptions) + } else if (job.type === 'video-studio-transcoding') { + await processStudioTranscoding(options as ProcessOptions) } else { logger.error(`Unknown job ${job.type} to process`) return diff --git a/packages/peertube-runner/server/process/shared/common.ts b/packages/peertube-runner/server/process/shared/common.ts index 3cac98388..88f7c33f1 100644 --- a/packages/peertube-runner/server/process/shared/common.ts +++ b/packages/peertube-runner/server/process/shared/common.ts @@ -1,13 +1,12 @@ +import { remove } from 'fs-extra' import { throttle } from 'lodash' import { ConfigManager, downloadFile, logger } from 'packages/peertube-runner/shared' import { join } from 'path' import { buildUUID } from '@shared/extra-utils' -import { FFmpegEdition, FFmpegLive, FFmpegVOD } from '@shared/ffmpeg' +import { FFmpegEdition, FFmpegLive, FFmpegVOD, getDefaultAvailableEncoders, getDefaultEncodersToTry } from '@shared/ffmpeg' import { RunnerJob, RunnerJobPayload } from '@shared/models' import { PeerTubeServer } from '@shared/server-commands' import { getTranscodingLogger } from './transcoding-logger' -import { getAvailableEncoders, getEncodersToTry } from './transcoding-profiles' -import { remove } from 'fs-extra' export type JobWithToken = RunnerJob & { jobToken: string } @@ -92,8 +91,8 @@ function getCommonFFmpegOptions () { tmpDirectory: ConfigManager.Instance.getTranscodingDirectory(), profile: 'default', availableEncoders: { - available: getAvailableEncoders(), - encodersToTry: getEncodersToTry() + available: getDefaultAvailableEncoders(), + encodersToTry: getDefaultEncodersToTry() }, logger: getTranscodingLogger() } diff --git a/packages/peertube-runner/server/process/shared/index.ts b/packages/peertube-runner/server/process/shared/index.ts index 8e09a7869..556c51365 100644 --- a/packages/peertube-runner/server/process/shared/index.ts +++ b/packages/peertube-runner/server/process/shared/index.ts @@ -1,4 +1,3 @@ export * from './common' export * from './process-vod' export * from './transcoding-logger' -export * from './transcoding-profiles' diff --git a/packages/peertube-runner/server/process/shared/process-studio.ts b/packages/peertube-runner/server/process/shared/process-studio.ts index f8262096e..9c745d031 100644 --- a/packages/peertube-runner/server/process/shared/process-studio.ts +++ b/packages/peertube-runner/server/process/shared/process-studio.ts @@ -1,11 +1,11 @@ import { remove } from 'fs-extra' import { pick } from 'lodash' import { logger } from 'packages/peertube-runner/shared' -import { extname, join } from 'path' +import { join } from 'path' import { buildUUID } from '@shared/extra-utils' import { - RunnerJobVideoEditionTranscodingPayload, - VideoEditionTranscodingSuccess, + RunnerJobStudioTranscodingPayload, + VideoStudioTranscodingSuccess, VideoStudioTask, VideoStudioTaskCutPayload, VideoStudioTaskIntroPayload, @@ -16,7 +16,7 @@ import { import { ConfigManager } from '../../../shared/config-manager' import { buildFFmpegEdition, downloadInputFile, JobWithToken, ProcessOptions } from './common' -export async function processStudioTranscoding (options: ProcessOptions) { +export async function processStudioTranscoding (options: ProcessOptions) { const { server, job, runnerToken } = options const payload = job.payload @@ -43,7 +43,7 @@ export async function processStudioTranscoding (options: ProcessOptions) { @@ -124,15 +128,19 @@ async function processAddWatermark (options: TaskProcessorOptions {}, + outputPath, - resolution: payload.output.resolution, - fps: payload.output.fps - }) + inputFileMutexReleaser: () => {}, - const successBody: VODWebVideoTranscodingSuccess = { - videoFile: outputPath - } + resolution: payload.output.resolution, + fps: payload.output.fps + }) - await server.runnerJobs.success({ - jobToken: job.jobToken, - jobUUID: job.uuid, - runnerToken, - payload: successBody - }) + const successBody: VODWebVideoTranscodingSuccess = { + videoFile: outputPath + } - await remove(outputPath) + await server.runnerJobs.success({ + jobToken: job.jobToken, + jobUUID: job.uuid, + runnerToken, + payload: successBody + }) + } finally { + await remove(inputPath) + await remove(outputPath) + } } export async function processHLSTranscoding (options: ProcessOptions) { @@ -105,30 +108,34 @@ export async function processAudioMergeTranscoding (options: ProcessOptions {}, + outputPath, - resolution: payload.output.resolution, - fps: payload.output.fps - }) + inputFileMutexReleaser: () => {}, - const successBody: VODAudioMergeTranscodingSuccess = { - videoFile: outputPath - } + resolution: payload.output.resolution, + fps: payload.output.fps + }) - await server.runnerJobs.success({ - jobToken: job.jobToken, - jobUUID: job.uuid, - runnerToken, - payload: successBody - }) + const successBody: VODAudioMergeTranscodingSuccess = { + videoFile: outputPath + } - await remove(outputPath) + await server.runnerJobs.success({ + jobToken: job.jobToken, + jobUUID: job.uuid, + runnerToken, + payload: successBody + }) + } finally { + await remove(audioPath) + await remove(inputPath) + await remove(outputPath) + } } diff --git a/packages/peertube-runner/server/process/shared/transcoding-profiles.ts b/packages/peertube-runner/server/process/shared/transcoding-profiles.ts deleted file mode 100644 index 492d17d6a..000000000 --- a/packages/peertube-runner/server/process/shared/transcoding-profiles.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils' -import { buildStreamSuffix, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '@shared/ffmpeg' -import { EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '@shared/models' - -const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { - const { fps, inputRatio, inputBitrate, resolution } = options - - const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution }) - - return { - outputOptions: [ - ...getCommonOutputOptions(targetBitrate), - - `-r ${fps}` - ] - } -} - -const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { - const { streamNum, fps, inputBitrate, inputRatio, resolution } = options - - const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution }) - - return { - outputOptions: [ - ...getCommonOutputOptions(targetBitrate, streamNum), - - `${buildStreamSuffix('-r:v', streamNum)} ${fps}`, - `${buildStreamSuffix('-b:v', streamNum)} ${targetBitrate}` - ] - } -} - -const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => { - const probe = await ffprobePromise(input) - - const parsedAudio = await getAudioStream(input, probe) - - // 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 - - const audioCodecName = parsedAudio.audioStream['codec_name'] - - const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate) - - // Force stereo as it causes some issues with HLS playback in Chrome - const base = [ '-channel_layout', 'stereo' ] - - if (bitrate !== -1) { - return { outputOptions: base.concat([ buildStreamSuffix('-b:a', streamNum), bitrate + 'k' ]) } - } - - return { outputOptions: base } -} - -const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum }) => { - return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] } -} - -export function getAvailableEncoders () { - return { - vod: { - libx264: { - default: defaultX264VODOptionsBuilder - }, - aac: { - default: defaultAACOptionsBuilder - }, - libfdk_aac: { - default: defaultLibFDKAACVODOptionsBuilder - } - }, - live: { - libx264: { - default: defaultX264LiveOptionsBuilder - }, - aac: { - default: defaultAACOptionsBuilder - } - } - } -} - -export function getEncodersToTry () { - return { - vod: { - video: [ 'libx264' ], - audio: [ 'libfdk_aac', 'aac' ] - }, - - live: { - video: [ 'libx264' ], - audio: [ 'libfdk_aac', 'aac' ] - } - } -} - -// --------------------------------------------------------------------------- - -function getTargetBitrate (options: { - inputBitrate: number - resolution: VideoResolution - ratio: number - fps: number -}) { - const { inputBitrate, resolution, ratio, fps } = options - - const capped = capBitrate(inputBitrate, getAverageBitrate({ resolution, fps, ratio })) - const limit = getMinLimitBitrate({ resolution, fps, ratio }) - - return Math.max(limit, capped) -} - -function capBitrate (inputBitrate: number, targetBitrate: number) { - if (!inputBitrate) return targetBitrate - - // Add 30% margin to input bitrate - const inputBitrateWithMargin = inputBitrate + (inputBitrate * 0.3) - - return Math.min(targetBitrate, inputBitrateWithMargin) -} - -function getCommonOutputOptions (targetBitrate: number, streamNum?: number) { - return [ - `-preset veryfast`, - `${buildStreamSuffix('-maxrate:v', streamNum)} ${targetBitrate}`, - `${buildStreamSuffix('-bufsize:v', streamNum)} ${targetBitrate * 2}`, - - // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it - `-b_strategy 1`, - // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 - `-bf 16` - ] -} diff --git a/packages/peertube-runner/server/shared/supported-job.ts b/packages/peertube-runner/server/shared/supported-job.ts index 87d5a39cc..1137d8206 100644 --- a/packages/peertube-runner/server/shared/supported-job.ts +++ b/packages/peertube-runner/server/shared/supported-job.ts @@ -2,7 +2,7 @@ import { RunnerJobLiveRTMPHLSTranscodingPayload, RunnerJobPayload, RunnerJobType, - RunnerJobVideoEditionTranscodingPayload, + RunnerJobStudioTranscodingPayload, RunnerJobVODAudioMergeTranscodingPayload, RunnerJobVODHLSTranscodingPayload, RunnerJobVODWebVideoTranscodingPayload, @@ -22,7 +22,7 @@ const supportedMatrix = { 'live-rtmp-hls-transcoding': (_payload: RunnerJobLiveRTMPHLSTranscodingPayload) => { return true }, - 'video-edition-transcoding': (payload: RunnerJobVideoEditionTranscodingPayload) => { + 'video-studio-transcoding': (payload: RunnerJobStudioTranscodingPayload) => { const tasks = payload?.tasks const supported = new Set([ 'add-intro', 'add-outro', 'add-watermark', 'cut' ]) diff --git a/server/controllers/api/runners/jobs-files.ts b/server/controllers/api/runners/jobs-files.ts index 4efa40b3a..260d824a8 100644 --- a/server/controllers/api/runners/jobs-files.ts +++ b/server/controllers/api/runners/jobs-files.ts @@ -31,7 +31,7 @@ runnerJobFilesRouter.post('/jobs/:jobUUID/files/videos/:videoId/studio/task-file asyncMiddleware(jobOfRunnerGetValidator), asyncMiddleware(runnerJobGetVideoTranscodingFileValidator), runnerJobGetVideoStudioTaskFileValidator, - getVideoEditionTaskFile + getVideoStudioTaskFile ) // --------------------------------------------------------------------------- @@ -94,14 +94,14 @@ function getMaxQualityVideoPreview (req: express.Request, res: express.Response) return res.sendFile(file.getPath()) } -function getVideoEditionTaskFile (req: express.Request, res: express.Response) { +function getVideoStudioTaskFile (req: express.Request, res: express.Response) { const runnerJob = res.locals.runnerJob const runner = runnerJob.Runner const video = res.locals.videoAll const filename = req.params.filename logger.info( - 'Get video edition task file %s of video %s of job %s for runner %s', filename, video.uuid, runnerJob.uuid, runner.name, + 'Get video studio task file %s of video %s of job %s for runner %s', filename, video.uuid, runnerJob.uuid, runner.name, lTags(runner.name, runnerJob.id, runnerJob.type) ) diff --git a/server/controllers/api/runners/jobs.ts b/server/controllers/api/runners/jobs.ts index 8e34c07a3..3f2a92182 100644 --- a/server/controllers/api/runners/jobs.ts +++ b/server/controllers/api/runners/jobs.ts @@ -42,7 +42,7 @@ import { RunnerJobUpdateBody, RunnerJobUpdatePayload, UserRight, - VideoEditionTranscodingSuccess, + VideoStudioTranscodingSuccess, VODAudioMergeTranscodingSuccess, VODHLSTranscodingSuccess, VODWebVideoTranscodingSuccess @@ -300,7 +300,7 @@ const jobSuccessPayloadBuilders: { } }, - 'video-edition-transcoding': (payload: VideoEditionTranscodingSuccess, files) => { + 'video-studio-transcoding': (payload: VideoStudioTranscodingSuccess, files) => { return { ...payload, diff --git a/server/helpers/custom-validators/runners/jobs.ts b/server/helpers/custom-validators/runners/jobs.ts index 934bd37c9..725a7658f 100644 --- a/server/helpers/custom-validators/runners/jobs.ts +++ b/server/helpers/custom-validators/runners/jobs.ts @@ -6,7 +6,7 @@ import { RunnerJobSuccessPayload, RunnerJobType, RunnerJobUpdatePayload, - VideoEditionTranscodingSuccess, + VideoStudioTranscodingSuccess, VODAudioMergeTranscodingSuccess, VODHLSTranscodingSuccess, VODWebVideoTranscodingSuccess @@ -25,7 +25,7 @@ function isRunnerJobSuccessPayloadValid (value: RunnerJobSuccessPayload, type: R isRunnerJobVODHLSResultPayloadValid(value as VODHLSTranscodingSuccess, type, files) || isRunnerJobVODAudioMergeResultPayloadValid(value as VODHLSTranscodingSuccess, type, files) || isRunnerJobLiveRTMPHLSResultPayloadValid(value as LiveRTMPHLSTranscodingSuccess, type) || - isRunnerJobVideoEditionResultPayloadValid(value as VideoEditionTranscodingSuccess, type, files) + isRunnerJobVideoStudioResultPayloadValid(value as VideoStudioTranscodingSuccess, type, files) } // --------------------------------------------------------------------------- @@ -37,7 +37,7 @@ function isRunnerJobProgressValid (value: string) { function isRunnerJobUpdatePayloadValid (value: RunnerJobUpdatePayload, type: RunnerJobType, files: UploadFilesForCheck) { return isRunnerJobVODWebVideoUpdatePayloadValid(value, type, files) || isRunnerJobVODHLSUpdatePayloadValid(value, type, files) || - isRunnerJobVideoEditionUpdatePayloadValid(value, type, files) || + isRunnerJobVideoStudioUpdatePayloadValid(value, type, files) || isRunnerJobVODAudioMergeUpdatePayloadValid(value, type, files) || isRunnerJobLiveRTMPHLSUpdatePayloadValid(value, type, files) } @@ -105,12 +105,12 @@ function isRunnerJobLiveRTMPHLSResultPayloadValid ( return type === 'live-rtmp-hls-transcoding' && (!value || (typeof value === 'object' && Object.keys(value).length === 0)) } -function isRunnerJobVideoEditionResultPayloadValid ( - _value: VideoEditionTranscodingSuccess, +function isRunnerJobVideoStudioResultPayloadValid ( + _value: VideoStudioTranscodingSuccess, type: RunnerJobType, files: UploadFilesForCheck ) { - return type === 'video-edition-transcoding' && + return type === 'video-studio-transcoding' && isFileValid({ files, field: 'payload[videoFile]', mimeTypeRegex: null, maxSize: null }) } @@ -177,11 +177,11 @@ function isRunnerJobLiveRTMPHLSUpdatePayloadValid ( ) } -function isRunnerJobVideoEditionUpdatePayloadValid ( +function isRunnerJobVideoStudioUpdatePayloadValid ( value: RunnerJobUpdatePayload, type: RunnerJobType, _files: UploadFilesForCheck ) { - return type === 'video-edition-transcoding' && + return type === 'video-studio-transcoding' && (!value || (typeof value === 'object' && Object.keys(value).length === 0)) } diff --git a/server/lib/job-queue/handlers/video-studio-edition.ts b/server/lib/job-queue/handlers/video-studio-edition.ts index df73caf72..caf051bfa 100644 --- a/server/lib/job-queue/handlers/video-studio-edition.ts +++ b/server/lib/job-queue/handlers/video-studio-edition.ts @@ -6,7 +6,7 @@ import { CONFIG } from '@server/initializers/config' import { VideoTranscodingProfilesManager } from '@server/lib/transcoding/default-transcoding-profiles' import { isAbleToUploadVideo } from '@server/lib/user' import { VideoPathManager } from '@server/lib/video-path-manager' -import { approximateIntroOutroAdditionalSize, onVideoEditionEnded, safeCleanupStudioTMPFiles } from '@server/lib/video-studio' +import { approximateIntroOutroAdditionalSize, onVideoStudioEnded, safeCleanupStudioTMPFiles } from '@server/lib/video-studio' import { UserModel } from '@server/models/user/user' import { VideoModel } from '@server/models/video/video' import { MVideo, MVideoFullLight } from '@server/types/models' @@ -24,7 +24,7 @@ import { } from '@shared/models' import { logger, loggerTagsFactory } from '../../../helpers/logger' -const lTagsBase = loggerTagsFactory('video-edition') +const lTagsBase = loggerTagsFactory('video-studio') async function processVideoStudioEdition (job: Job) { const payload = job.data as VideoStudioEditionPayload @@ -74,7 +74,7 @@ async function processVideoStudioEdition (job: Job) { logger.info('Video edition ended for video %s.', video.uuid, lTags) - await onVideoEditionEnded({ video, editionResultPath, tasks: payload.tasks }) + await onVideoStudioEnded({ video, editionResultPath, tasks: payload.tasks }) } catch (err) { await safeCleanupStudioTMPFiles(payload.tasks) diff --git a/server/lib/runners/job-handlers/abstract-job-handler.ts b/server/lib/runners/job-handlers/abstract-job-handler.ts index 76fd1c5ac..28c3e1eda 100644 --- a/server/lib/runners/job-handlers/abstract-job-handler.ts +++ b/server/lib/runners/job-handlers/abstract-job-handler.ts @@ -15,8 +15,8 @@ import { RunnerJobSuccessPayload, RunnerJobType, RunnerJobUpdatePayload, - RunnerJobVideoEditionTranscodingPayload, - RunnerJobVideoEditionTranscodingPrivatePayload, + RunnerJobStudioTranscodingPayload, + RunnerJobVideoStudioTranscodingPrivatePayload, RunnerJobVODAudioMergeTranscodingPayload, RunnerJobVODAudioMergeTranscodingPrivatePayload, RunnerJobVODHLSTranscodingPayload, @@ -47,9 +47,9 @@ type CreateRunnerJobArg = privatePayload: RunnerJobLiveRTMPHLSTranscodingPrivatePayload } | { - type: Extract - payload: RunnerJobVideoEditionTranscodingPayload - privatePayload: RunnerJobVideoEditionTranscodingPrivatePayload + type: Extract + payload: RunnerJobStudioTranscodingPayload + privatePayload: RunnerJobVideoStudioTranscodingPrivatePayload } export abstract class AbstractJobHandler { diff --git a/server/lib/runners/job-handlers/index.ts b/server/lib/runners/job-handlers/index.ts index a40cee865..40ad2f97a 100644 --- a/server/lib/runners/job-handlers/index.ts +++ b/server/lib/runners/job-handlers/index.ts @@ -1,7 +1,7 @@ export * from './abstract-job-handler' export * from './live-rtmp-hls-transcoding-job-handler' export * from './runner-job-handlers' -export * from './video-edition-transcoding-job-handler' +export * from './video-studio-transcoding-job-handler' export * from './vod-audio-merge-transcoding-job-handler' export * from './vod-hls-transcoding-job-handler' export * from './vod-web-video-transcoding-job-handler' diff --git a/server/lib/runners/job-handlers/runner-job-handlers.ts b/server/lib/runners/job-handlers/runner-job-handlers.ts index 4ea6684ea..85551c365 100644 --- a/server/lib/runners/job-handlers/runner-job-handlers.ts +++ b/server/lib/runners/job-handlers/runner-job-handlers.ts @@ -2,7 +2,7 @@ import { MRunnerJob } from '@server/types/models/runners' import { RunnerJobSuccessPayload, RunnerJobType, RunnerJobUpdatePayload } from '@shared/models' import { AbstractJobHandler } from './abstract-job-handler' import { LiveRTMPHLSTranscodingJobHandler } from './live-rtmp-hls-transcoding-job-handler' -import { VideoEditionTranscodingJobHandler } from './video-edition-transcoding-job-handler' +import { VideoStudioTranscodingJobHandler } from './video-studio-transcoding-job-handler' import { VODAudioMergeTranscodingJobHandler } from './vod-audio-merge-transcoding-job-handler' import { VODHLSTranscodingJobHandler } from './vod-hls-transcoding-job-handler' import { VODWebVideoTranscodingJobHandler } from './vod-web-video-transcoding-job-handler' @@ -12,7 +12,7 @@ const processors: Record AbstractJobHandler { - - async create (options: CreateOptions) { - const { video, priority, tasks } = options - - const jobUUID = buildUUID() - const payload: RunnerJobVideoEditionTranscodingPayload = { - input: { - videoFileUrl: generateRunnerTranscodingVideoInputFileUrl(jobUUID, video.uuid) - }, - tasks: tasks.map(t => { - if (isVideoStudioTaskIntro(t) || isVideoStudioTaskOutro(t)) { - return { - ...t, - - options: { - ...t.options, - - file: generateRunnerEditionTranscodingVideoInputFileUrl(jobUUID, video.uuid, basename(t.options.file)) - } - } - } - - if (isVideoStudioTaskWatermark(t)) { - return { - ...t, - - options: { - ...t.options, - - file: generateRunnerEditionTranscodingVideoInputFileUrl(jobUUID, video.uuid, basename(t.options.file)) - } - } - } - - return t - }) - } - - const privatePayload: RunnerJobVideoEditionTranscodingPrivatePayload = { - videoUUID: video.uuid, - originalTasks: tasks - } - - const job = await this.createRunnerJob({ - type: 'video-edition-transcoding', - jobUUID, - payload, - privatePayload, - priority - }) - - return job - } - - // --------------------------------------------------------------------------- - - protected isAbortSupported () { - return true - } - - protected specificUpdate (_options: { - runnerJob: MRunnerJob - }) { - // empty - } - - protected specificAbort (_options: { - runnerJob: MRunnerJob - }) { - // empty - } - - protected async specificComplete (options: { - runnerJob: MRunnerJob - resultPayload: VideoEditionTranscodingSuccess - }) { - const { runnerJob, resultPayload } = options - const privatePayload = runnerJob.privatePayload as RunnerJobVideoEditionTranscodingPrivatePayload - - const video = await loadTranscodingRunnerVideo(runnerJob, this.lTags) - if (!video) { - await safeCleanupStudioTMPFiles(privatePayload.originalTasks) - - } - - const videoFilePath = resultPayload.videoFile as string - - await onVideoEditionEnded({ video, editionResultPath: videoFilePath, tasks: privatePayload.originalTasks }) - - logger.info( - 'Runner video edition transcoding job %s for %s ended.', - runnerJob.uuid, video.uuid, this.lTags(video.uuid, runnerJob.uuid) - ) - } - - protected specificError (options: { - runnerJob: MRunnerJob - nextState: RunnerJobState - }) { - if (options.nextState === RunnerJobState.ERRORED) { - return this.specificErrorOrCancel(options) - } - - return Promise.resolve() - } - - protected specificCancel (options: { - runnerJob: MRunnerJob - }) { - return this.specificErrorOrCancel(options) - } - - private async specificErrorOrCancel (options: { - runnerJob: MRunnerJob - }) { - const { runnerJob } = options - - const payload = runnerJob.privatePayload as RunnerJobVideoEditionTranscodingPrivatePayload - await safeCleanupStudioTMPFiles(payload.originalTasks) - - const video = await loadTranscodingRunnerVideo(options.runnerJob, this.lTags) - if (!video) return - - return video.setNewState(VideoState.PUBLISHED, false, undefined) - } -} diff --git a/server/lib/runners/job-handlers/video-studio-transcoding-job-handler.ts b/server/lib/runners/job-handlers/video-studio-transcoding-job-handler.ts new file mode 100644 index 000000000..f604382b7 --- /dev/null +++ b/server/lib/runners/job-handlers/video-studio-transcoding-job-handler.ts @@ -0,0 +1,157 @@ + +import { basename } from 'path' +import { logger } from '@server/helpers/logger' +import { onVideoStudioEnded, safeCleanupStudioTMPFiles } from '@server/lib/video-studio' +import { MVideo } from '@server/types/models' +import { MRunnerJob } from '@server/types/models/runners' +import { buildUUID } from '@shared/extra-utils' +import { + isVideoStudioTaskIntro, + isVideoStudioTaskOutro, + isVideoStudioTaskWatermark, + RunnerJobState, + RunnerJobUpdatePayload, + RunnerJobStudioTranscodingPayload, + RunnerJobVideoStudioTranscodingPrivatePayload, + VideoStudioTranscodingSuccess, + VideoState, + VideoStudioTaskPayload +} from '@shared/models' +import { generateRunnerEditionTranscodingVideoInputFileUrl, generateRunnerTranscodingVideoInputFileUrl } from '../runner-urls' +import { AbstractJobHandler } from './abstract-job-handler' +import { loadTranscodingRunnerVideo } from './shared' + +type CreateOptions = { + video: MVideo + tasks: VideoStudioTaskPayload[] + priority: number +} + +// eslint-disable-next-line max-len +export class VideoStudioTranscodingJobHandler extends AbstractJobHandler { + + async create (options: CreateOptions) { + const { video, priority, tasks } = options + + const jobUUID = buildUUID() + const payload: RunnerJobStudioTranscodingPayload = { + input: { + videoFileUrl: generateRunnerTranscodingVideoInputFileUrl(jobUUID, video.uuid) + }, + tasks: tasks.map(t => { + if (isVideoStudioTaskIntro(t) || isVideoStudioTaskOutro(t)) { + return { + ...t, + + options: { + ...t.options, + + file: generateRunnerEditionTranscodingVideoInputFileUrl(jobUUID, video.uuid, basename(t.options.file)) + } + } + } + + if (isVideoStudioTaskWatermark(t)) { + return { + ...t, + + options: { + ...t.options, + + file: generateRunnerEditionTranscodingVideoInputFileUrl(jobUUID, video.uuid, basename(t.options.file)) + } + } + } + + return t + }) + } + + const privatePayload: RunnerJobVideoStudioTranscodingPrivatePayload = { + videoUUID: video.uuid, + originalTasks: tasks + } + + const job = await this.createRunnerJob({ + type: 'video-studio-transcoding', + jobUUID, + payload, + privatePayload, + priority + }) + + return job + } + + // --------------------------------------------------------------------------- + + protected isAbortSupported () { + return true + } + + protected specificUpdate (_options: { + runnerJob: MRunnerJob + }) { + // empty + } + + protected specificAbort (_options: { + runnerJob: MRunnerJob + }) { + // empty + } + + protected async specificComplete (options: { + runnerJob: MRunnerJob + resultPayload: VideoStudioTranscodingSuccess + }) { + const { runnerJob, resultPayload } = options + const privatePayload = runnerJob.privatePayload as RunnerJobVideoStudioTranscodingPrivatePayload + + const video = await loadTranscodingRunnerVideo(runnerJob, this.lTags) + if (!video) { + await safeCleanupStudioTMPFiles(privatePayload.originalTasks) + + } + + const videoFilePath = resultPayload.videoFile as string + + await onVideoStudioEnded({ video, editionResultPath: videoFilePath, tasks: privatePayload.originalTasks }) + + logger.info( + 'Runner video edition transcoding job %s for %s ended.', + runnerJob.uuid, video.uuid, this.lTags(video.uuid, runnerJob.uuid) + ) + } + + protected specificError (options: { + runnerJob: MRunnerJob + nextState: RunnerJobState + }) { + if (options.nextState === RunnerJobState.ERRORED) { + return this.specificErrorOrCancel(options) + } + + return Promise.resolve() + } + + protected specificCancel (options: { + runnerJob: MRunnerJob + }) { + return this.specificErrorOrCancel(options) + } + + private async specificErrorOrCancel (options: { + runnerJob: MRunnerJob + }) { + const { runnerJob } = options + + const payload = runnerJob.privatePayload as RunnerJobVideoStudioTranscodingPrivatePayload + await safeCleanupStudioTMPFiles(payload.originalTasks) + + const video = await loadTranscodingRunnerVideo(options.runnerJob, this.lTags) + if (!video) return + + return video.setNewState(VideoState.PUBLISHED, false, undefined) + } +} diff --git a/server/lib/transcoding/default-transcoding-profiles.ts b/server/lib/transcoding/default-transcoding-profiles.ts index 5251784ac..8f8fdd026 100644 --- a/server/lib/transcoding/default-transcoding-profiles.ts +++ b/server/lib/transcoding/default-transcoding-profiles.ts @@ -1,88 +1,7 @@ import { logger } from '@server/helpers/logger' -import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils' -import { buildStreamSuffix, FFmpegCommandWrapper, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '@shared/ffmpeg' -import { AvailableEncoders, EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '@shared/models' -import { canDoQuickAudioTranscode } from './transcoding-quick-transcode' - -/** - * - * 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: - * * https://slhck.info/video/2017/03/01/rate-control.html - * * https://trac.ffmpeg.org/wiki/Limiting%20the%20output%20bitrate - */ - -// --------------------------------------------------------------------------- -// Default builders -// --------------------------------------------------------------------------- - -const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { - const { fps, inputRatio, inputBitrate, resolution } = options - - // TODO: remove in 4.2, fps is not optional anymore - if (!fps) return { outputOptions: [ ] } - - const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution }) - - return { - outputOptions: [ - ...getCommonOutputOptions(targetBitrate), - - `-r ${fps}` - ] - } -} - -const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { - const { streamNum, fps, inputBitrate, inputRatio, resolution } = options - - const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution }) - - return { - outputOptions: [ - ...getCommonOutputOptions(targetBitrate, streamNum), - - `${buildStreamSuffix('-r:v', streamNum)} ${fps}`, - `${buildStreamSuffix('-b:v', streamNum)} ${targetBitrate}` - ] - } -} - -const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => { - const probe = await ffprobePromise(input) - - if (canCopyAudio && 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 - // Of course this is far from perfect, but it might save some space in the end - - const audioCodecName = parsedAudio.audioStream['codec_name'] - - const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate) - - logger.debug('Calculating audio bitrate of %s by AAC encoder.', input, { bitrate: parsedAudio.bitrate, audioCodecName }) - - // Force stereo as it causes some issues with HLS playback in Chrome - const base = [ '-channel_layout', 'stereo' ] - - if (bitrate !== -1) { - return { outputOptions: base.concat([ buildStreamSuffix('-b:a', streamNum), bitrate + 'k' ]) } - } - - return { outputOptions: base } -} - -const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum }) => { - return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] } -} +import { FFmpegCommandWrapper, getDefaultAvailableEncoders } from '@shared/ffmpeg' +import { AvailableEncoders, EncoderOptionsBuilder } from '@shared/models' // --------------------------------------------------------------------------- // Profile manager to get and change default profiles @@ -97,27 +16,7 @@ class VideoTranscodingProfilesManager { live: this.buildDefaultEncodersPriorities() } - private readonly availableEncoders = { - vod: { - libx264: { - default: defaultX264VODOptionsBuilder - }, - aac: { - default: defaultAACOptionsBuilder - }, - libfdk_aac: { - default: defaultLibFDKAACVODOptionsBuilder - } - }, - live: { - libx264: { - default: defaultX264LiveOptionsBuilder - }, - aac: { - default: defaultAACOptionsBuilder - } - } - } + private readonly availableEncoders = getDefaultAvailableEncoders() private availableProfiles = { vod: [] as string[], @@ -242,41 +141,3 @@ class VideoTranscodingProfilesManager { export { VideoTranscodingProfilesManager } - -// --------------------------------------------------------------------------- - -function getTargetBitrate (options: { - inputBitrate: number - resolution: VideoResolution - ratio: number - fps: number -}) { - const { inputBitrate, resolution, ratio, fps } = options - - const capped = capBitrate(inputBitrate, getAverageBitrate({ resolution, fps, ratio })) - const limit = getMinLimitBitrate({ resolution, fps, ratio }) - - return Math.max(limit, capped) -} - -function capBitrate (inputBitrate: number, targetBitrate: number) { - if (!inputBitrate) return targetBitrate - - // Add 30% margin to input bitrate - const inputBitrateWithMargin = inputBitrate + (inputBitrate * 0.3) - - return Math.min(targetBitrate, inputBitrateWithMargin) -} - -function getCommonOutputOptions (targetBitrate: number, streamNum?: number) { - return [ - `-preset veryfast`, - `${buildStreamSuffix('-maxrate:v', streamNum)} ${targetBitrate}`, - `${buildStreamSuffix('-bufsize:v', streamNum)} ${targetBitrate * 2}`, - - // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it - `-b_strategy 1`, - // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 - `-bf 16` - ] -} diff --git a/server/lib/video-studio.ts b/server/lib/video-studio.ts index 2c993faeb..0d3db8f60 100644 --- a/server/lib/video-studio.ts +++ b/server/lib/video-studio.ts @@ -9,13 +9,13 @@ import { getVideoStreamDuration } from '@shared/ffmpeg' import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@shared/models' import { federateVideoIfNeeded } from './activitypub/videos' import { JobQueue } from './job-queue' -import { VideoEditionTranscodingJobHandler } from './runners' +import { VideoStudioTranscodingJobHandler } from './runners' import { createOptimizeOrMergeAudioJobs } from './transcoding/create-transcoding-job' import { getTranscodingJobPriority } from './transcoding/transcoding-priority' import { buildNewFile, removeHLSPlaylist, removeWebTorrentFile } from './video-file' import { VideoPathManager } from './video-path-manager' -const lTags = loggerTagsFactory('video-edition') +const lTags = loggerTagsFactory('video-studio') export function buildTaskFileFieldname (indice: number, fieldName = 'file') { return `tasks[${indice}][options][${fieldName}]` @@ -78,14 +78,14 @@ export async function createVideoStudioJob (options: { const priority = await getTranscodingJobPriority({ user, type: 'studio', fallback: 0 }) if (CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED) { - await new VideoEditionTranscodingJobHandler().create({ video, tasks: payload.tasks, priority }) + await new VideoStudioTranscodingJobHandler().create({ video, tasks: payload.tasks, priority }) return } await JobQueue.Instance.createJob({ type: 'video-studio-edition', payload, priority }) } -export async function onVideoEditionEnded (options: { +export async function onVideoStudioEnded (options: { editionResultPath: string tasks: VideoStudioTaskPayload[] video: MVideoFullLight diff --git a/server/middlewares/validators/runners/job-files.ts b/server/middlewares/validators/runners/job-files.ts index e5afff0e5..57c27fcfe 100644 --- a/server/middlewares/validators/runners/job-files.ts +++ b/server/middlewares/validators/runners/job-files.ts @@ -2,7 +2,7 @@ import express from 'express' import { param } from 'express-validator' import { basename } from 'path' import { isSafeFilename } from '@server/helpers/custom-validators/misc' -import { hasVideoStudioTaskFile, HttpStatusCode, RunnerJobVideoEditionTranscodingPayload } from '@shared/models' +import { hasVideoStudioTaskFile, HttpStatusCode, RunnerJobStudioTranscodingPayload } from '@shared/models' import { areValidationErrors, doesVideoExist, isValidVideoIdParam } from '../shared' const tags = [ 'runner' ] @@ -37,7 +37,7 @@ export const runnerJobGetVideoStudioTaskFileValidator = [ const filename = req.params.filename - const payload = res.locals.runnerJob.payload as RunnerJobVideoEditionTranscodingPayload + const payload = res.locals.runnerJob.payload as RunnerJobStudioTranscodingPayload const found = Array.isArray(payload?.tasks) && payload.tasks.some(t => { if (hasVideoStudioTaskFile(t)) { diff --git a/server/tests/api/check-params/runners.ts b/server/tests/api/check-params/runners.ts index 90a301392..cee1993e6 100644 --- a/server/tests/api/check-params/runners.ts +++ b/server/tests/api/check-params/runners.ts @@ -8,7 +8,7 @@ import { RunnerJobState, RunnerJobSuccessPayload, RunnerJobUpdatePayload, - RunnerJobVideoEditionTranscodingPayload, + RunnerJobStudioTranscodingPayload, VideoPrivacy, VideoStudioTaskIntro } from '@shared/models' @@ -404,10 +404,10 @@ describe('Test managing runners', function () { tasks: VideoStudioCommand.getComplexTask() }) - const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'video-edition-transcoding' }) + const { job } = await server.runnerJobs.autoAccept({ runnerToken, type: 'video-studio-transcoding' }) studioAcceptedJob = job - const tasks = (job.payload as RunnerJobVideoEditionTranscodingPayload).tasks + const tasks = (job.payload as RunnerJobStudioTranscodingPayload).tasks const fileUrl = (tasks.find(t => isVideoStudioTaskIntro(t)) as VideoStudioTaskIntro).options.file as string studioFile = basename(fileUrl) } @@ -787,7 +787,7 @@ describe('Test managing runners', function () { describe('Video studio', function () { - it('Should fail with an invalid video edition transcoding payload', async function () { + it('Should fail with an invalid video studio transcoding payload', async function () { await server.runnerJobs.success({ jobUUID: studioAcceptedJob.uuid, jobToken: studioAcceptedJob.jobToken, @@ -849,7 +849,7 @@ describe('Test managing runners', function () { }) }) - describe('Video edition tasks file routes', function () { + describe('Video studio tasks file routes', function () { it('Should fail with an invalid studio filename', async function () { await fetchStudioFiles({ diff --git a/server/tests/api/runners/runner-studio-transcoding.ts b/server/tests/api/runners/runner-studio-transcoding.ts index 9ae629be6..41c556775 100644 --- a/server/tests/api/runners/runner-studio-transcoding.ts +++ b/server/tests/api/runners/runner-studio-transcoding.ts @@ -5,8 +5,8 @@ import { readFile } from 'fs-extra' import { checkPersistentTmpIsEmpty, checkVideoDuration } from '@server/tests/shared' import { buildAbsoluteFixturePath } from '@shared/core-utils' import { - RunnerJobVideoEditionTranscodingPayload, - VideoEditionTranscodingSuccess, + RunnerJobStudioTranscodingPayload, + VideoStudioTranscodingSuccess, VideoState, VideoStudioTask, VideoStudioTaskIntro @@ -121,10 +121,10 @@ describe('Test runner video studio transcoding', function () { await checkVideoDuration(server, videoUUID, 5) } - const { job } = await servers[0].runnerJobs.accept({ runnerToken, jobUUID }) + const { job } = await servers[0].runnerJobs.accept({ runnerToken, jobUUID }) const jobToken = job.jobToken - expect(job.type === 'video-edition-transcoding') + expect(job.type === 'video-studio-transcoding') expect(job.payload.input.videoFileUrl).to.exist // Check video input file @@ -150,7 +150,7 @@ describe('Test runner video studio transcoding', function () { expect(body).to.deep.equal(inputFile) } - const payload: VideoEditionTranscodingSuccess = { videoFile: 'video_very_short_240p.mp4' } + const payload: VideoStudioTranscodingSuccess = { videoFile: 'video_very_short_240p.mp4' } await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload }) await waitJobs(servers) diff --git a/server/tests/api/transcoding/video-studio.ts b/server/tests/api/transcoding/video-studio.ts index 2f64ef6bd..d22cfff4a 100644 --- a/server/tests/api/transcoding/video-studio.ts +++ b/server/tests/api/transcoding/video-studio.ts @@ -270,7 +270,7 @@ describe('Test video studio', function () { }) }) - describe('HLS only video edition', function () { + describe('HLS only studio edition', function () { before(async function () { // Disable webtorrent @@ -300,7 +300,7 @@ describe('Test video studio', function () { }) }) - describe('Object storage video edition', function () { + describe('Object storage studio edition', function () { if (areMockObjectStorageTestsDisabled()) return before(async function () { diff --git a/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts b/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts new file mode 100644 index 000000000..f7fc49465 --- /dev/null +++ b/shared/ffmpeg/ffmpeg-default-transcoding-profile.ts @@ -0,0 +1,134 @@ +import { getAverageBitrate, getMinLimitBitrate } from '@shared/core-utils' +import { buildStreamSuffix, ffprobePromise, getAudioStream, getMaxAudioBitrate } from '@shared/ffmpeg' +import { EncoderOptionsBuilder, EncoderOptionsBuilderParams, VideoResolution } from '@shared/models' + +const defaultX264VODOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { + const { fps, inputRatio, inputBitrate, resolution } = options + + const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution }) + + return { + outputOptions: [ + ...getCommonOutputOptions(targetBitrate), + + `-r ${fps}` + ] + } +} + +const defaultX264LiveOptionsBuilder: EncoderOptionsBuilder = (options: EncoderOptionsBuilderParams) => { + const { streamNum, fps, inputBitrate, inputRatio, resolution } = options + + const targetBitrate = getTargetBitrate({ inputBitrate, ratio: inputRatio, fps, resolution }) + + return { + outputOptions: [ + ...getCommonOutputOptions(targetBitrate, streamNum), + + `${buildStreamSuffix('-r:v', streamNum)} ${fps}`, + `${buildStreamSuffix('-b:v', streamNum)} ${targetBitrate}` + ] + } +} + +const defaultAACOptionsBuilder: EncoderOptionsBuilder = async ({ input, streamNum, canCopyAudio }) => { + const probe = await ffprobePromise(input) + + const parsedAudio = await getAudioStream(input, probe) + + // 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 + + const audioCodecName = parsedAudio.audioStream['codec_name'] + + const bitrate = getMaxAudioBitrate(audioCodecName, parsedAudio.bitrate) + + // Force stereo as it causes some issues with HLS playback in Chrome + const base = [ '-channel_layout', 'stereo' ] + + if (bitrate !== -1) { + return { outputOptions: base.concat([ buildStreamSuffix('-b:a', streamNum), bitrate + 'k' ]) } + } + + return { outputOptions: base } +} + +const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum }) => { + return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] } +} + +export function getDefaultAvailableEncoders () { + return { + vod: { + libx264: { + default: defaultX264VODOptionsBuilder + }, + aac: { + default: defaultAACOptionsBuilder + }, + libfdk_aac: { + default: defaultLibFDKAACVODOptionsBuilder + } + }, + live: { + libx264: { + default: defaultX264LiveOptionsBuilder + }, + aac: { + default: defaultAACOptionsBuilder + } + } + } +} + +export function getDefaultEncodersToTry () { + return { + vod: { + video: [ 'libx264' ], + audio: [ 'libfdk_aac', 'aac' ] + }, + + live: { + video: [ 'libx264' ], + audio: [ 'libfdk_aac', 'aac' ] + } + } +} + +// --------------------------------------------------------------------------- + +function getTargetBitrate (options: { + inputBitrate: number + resolution: VideoResolution + ratio: number + fps: number +}) { + const { inputBitrate, resolution, ratio, fps } = options + + const capped = capBitrate(inputBitrate, getAverageBitrate({ resolution, fps, ratio })) + const limit = getMinLimitBitrate({ resolution, fps, ratio }) + + return Math.max(limit, capped) +} + +function capBitrate (inputBitrate: number, targetBitrate: number) { + if (!inputBitrate) return targetBitrate + + // Add 30% margin to input bitrate + const inputBitrateWithMargin = inputBitrate + (inputBitrate * 0.3) + + return Math.min(targetBitrate, inputBitrateWithMargin) +} + +function getCommonOutputOptions (targetBitrate: number, streamNum?: number) { + return [ + `-preset veryfast`, + `${buildStreamSuffix('-maxrate:v', streamNum)} ${targetBitrate}`, + `${buildStreamSuffix('-bufsize:v', streamNum)} ${targetBitrate * 2}`, + + // NOTE: b-strategy 1 - heuristic algorithm, 16 is optimal B-frames for it + `-b_strategy 1`, + // NOTE: Why 16: https://github.com/Chocobozzz/PeerTube/pull/774. b-strategy 2 -> B-frames<16 + `-bf 16` + ] +} diff --git a/shared/ffmpeg/index.ts b/shared/ffmpeg/index.ts index 07a7d5402..1dab292da 100644 --- a/shared/ffmpeg/index.ts +++ b/shared/ffmpeg/index.ts @@ -1,4 +1,5 @@ export * from './ffmpeg-command-wrapper' +export * from './ffmpeg-default-transcoding-profile' export * from './ffmpeg-edition' export * from './ffmpeg-images' export * from './ffmpeg-live' diff --git a/shared/models/runners/runner-job-payload.model.ts b/shared/models/runners/runner-job-payload.model.ts index 9f0db0dc4..3dda6c51f 100644 --- a/shared/models/runners/runner-job-payload.model.ts +++ b/shared/models/runners/runner-job-payload.model.ts @@ -8,7 +8,7 @@ export type RunnerJobVODPayload = export type RunnerJobPayload = RunnerJobVODPayload | RunnerJobLiveRTMPHLSTranscodingPayload | - RunnerJobVideoEditionTranscodingPayload + RunnerJobStudioTranscodingPayload // --------------------------------------------------------------------------- @@ -46,7 +46,7 @@ export interface RunnerJobVODAudioMergeTranscodingPayload { } } -export interface RunnerJobVideoEditionTranscodingPayload { +export interface RunnerJobStudioTranscodingPayload { input: { videoFileUrl: string } diff --git a/shared/models/runners/runner-job-private-payload.model.ts b/shared/models/runners/runner-job-private-payload.model.ts index c8fe0a7d8..32b837de4 100644 --- a/shared/models/runners/runner-job-private-payload.model.ts +++ b/shared/models/runners/runner-job-private-payload.model.ts @@ -8,7 +8,7 @@ export type RunnerJobVODPrivatePayload = export type RunnerJobPrivatePayload = RunnerJobVODPrivatePayload | RunnerJobLiveRTMPHLSTranscodingPrivatePayload | - RunnerJobVideoEditionTranscodingPrivatePayload + RunnerJobVideoStudioTranscodingPrivatePayload // --------------------------------------------------------------------------- @@ -38,7 +38,7 @@ export interface RunnerJobLiveRTMPHLSTranscodingPrivatePayload { // --------------------------------------------------------------------------- -export interface RunnerJobVideoEditionTranscodingPrivatePayload { +export interface RunnerJobVideoStudioTranscodingPrivatePayload { videoUUID: string originalTasks: VideoStudioTaskPayload[] } diff --git a/shared/models/runners/runner-job-success-body.model.ts b/shared/models/runners/runner-job-success-body.model.ts index 17e921f69..f45336b05 100644 --- a/shared/models/runners/runner-job-success-body.model.ts +++ b/shared/models/runners/runner-job-success-body.model.ts @@ -12,7 +12,7 @@ export type RunnerJobSuccessPayload = VODHLSTranscodingSuccess | VODAudioMergeTranscodingSuccess | LiveRTMPHLSTranscodingSuccess | - VideoEditionTranscodingSuccess + VideoStudioTranscodingSuccess export interface VODWebVideoTranscodingSuccess { videoFile: Blob | string @@ -31,7 +31,7 @@ export interface LiveRTMPHLSTranscodingSuccess { } -export interface VideoEditionTranscodingSuccess { +export interface VideoStudioTranscodingSuccess { videoFile: Blob | string } diff --git a/shared/models/runners/runner-job-type.type.ts b/shared/models/runners/runner-job-type.type.ts index 3b997cb6e..91b92a729 100644 --- a/shared/models/runners/runner-job-type.type.ts +++ b/shared/models/runners/runner-job-type.type.ts @@ -3,4 +3,4 @@ export type RunnerJobType = 'vod-hls-transcoding' | 'vod-audio-merge-transcoding' | 'live-rtmp-hls-transcoding' | - 'video-edition-transcoding' + 'video-studio-transcoding' -- cgit v1.2.3