Prefer video studio instead of video edition

Clearer and easier to find in the project
pull/5593/head
Chocobozzz 2023-05-04 15:55:51 +02:00 committed by Chocobozzz
parent 5e47f6ab98
commit ab14f0e0dc
26 changed files with 151 additions and 276 deletions

View File

@ -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<RunnerJobVODHLSTranscodingPayload>)
} else if (job.type === 'live-rtmp-hls-transcoding') {
await new ProcessLiveRTMPHLSTranscoding(options as ProcessOptions<RunnerJobLiveRTMPHLSTranscodingPayload>).process()
} else if (job.type === 'video-edition-transcoding') {
await processStudioTranscoding(options as ProcessOptions<RunnerJobVideoEditionTranscodingPayload>)
} else if (job.type === 'video-studio-transcoding') {
await processStudioTranscoding(options as ProcessOptions<RunnerJobStudioTranscodingPayload>)
} else {
logger.error(`Unknown job ${job.type} to process`)
return

View File

@ -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 <T extends RunnerJobPayload = RunnerJobPayload> = RunnerJob<T> & { 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()
}

View File

@ -1,4 +1,3 @@
export * from './common'
export * from './process-vod'
export * from './transcoding-logger'
export * from './transcoding-profiles'

View File

@ -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<RunnerJobVideoEditionTranscodingPayload>) {
export async function processStudioTranscoding (options: ProcessOptions<RunnerJobStudioTranscodingPayload>) {
const { server, job, runnerToken } = options
const payload = job.payload
@ -43,7 +43,7 @@ export async function processStudioTranscoding (options: ProcessOptions<RunnerJo
tmpInputFilePath = outputPath
}
const successBody: VideoEditionTranscodingSuccess = {
const successBody: VideoStudioTranscodingSuccess = {
videoFile: outputPath
}
@ -94,14 +94,18 @@ async function processAddIntroOutro (options: TaskProcessorOptions<VideoStudioTa
const introOutroPath = await downloadInputFile({ url: task.options.file, runnerToken, job })
return buildFFmpegEdition().addIntroOutro({
...pick(options, [ 'inputPath', 'outputPath' ]),
try {
await buildFFmpegEdition().addIntroOutro({
...pick(options, [ 'inputPath', 'outputPath' ]),
introOutroPath,
type: task.name === 'add-intro'
? 'intro'
: 'outro'
})
introOutroPath,
type: task.name === 'add-intro'
? 'intro'
: 'outro'
})
} finally {
await remove(introOutroPath)
}
}
function processCut (options: TaskProcessorOptions<VideoStudioTaskCutPayload>) {
@ -124,15 +128,19 @@ async function processAddWatermark (options: TaskProcessorOptions<VideoStudioTas
const watermarkPath = await downloadInputFile({ url: task.options.file, runnerToken, job })
return buildFFmpegEdition().addWatermark({
...pick(options, [ 'inputPath', 'outputPath' ]),
try {
await buildFFmpegEdition().addWatermark({
...pick(options, [ 'inputPath', 'outputPath' ]),
watermarkPath,
watermarkPath,
videoFilters: {
watermarkSizeRatio: task.options.watermarkSizeRatio,
horitonzalMarginRatio: task.options.horitonzalMarginRatio,
verticalMarginRatio: task.options.verticalMarginRatio
}
})
videoFilters: {
watermarkSizeRatio: task.options.watermarkSizeRatio,
horitonzalMarginRatio: task.options.horitonzalMarginRatio,
verticalMarginRatio: task.options.verticalMarginRatio
}
})
} finally {
await remove(watermarkPath)
}
}

View File

@ -22,31 +22,34 @@ export async function processWebVideoTranscoding (options: ProcessOptions<Runner
const outputPath = join(ConfigManager.Instance.getTranscodingDirectory(), `output-${buildUUID()}.mp4`)
await ffmpegVod.transcode({
type: 'video',
try {
await ffmpegVod.transcode({
type: 'video',
inputPath,
inputPath,
outputPath,
outputPath,
inputFileMutexReleaser: () => {},
inputFileMutexReleaser: () => {},
resolution: payload.output.resolution,
fps: payload.output.fps
})
resolution: payload.output.resolution,
fps: payload.output.fps
})
const successBody: VODWebVideoTranscodingSuccess = {
videoFile: outputPath
const successBody: VODWebVideoTranscodingSuccess = {
videoFile: outputPath
}
await server.runnerJobs.success({
jobToken: job.jobToken,
jobUUID: job.uuid,
runnerToken,
payload: successBody
})
} finally {
await remove(inputPath)
await remove(outputPath)
}
await server.runnerJobs.success({
jobToken: job.jobToken,
jobUUID: job.uuid,
runnerToken,
payload: successBody
})
await remove(outputPath)
}
export async function processHLSTranscoding (options: ProcessOptions<RunnerJobVODHLSTranscodingPayload>) {
@ -105,30 +108,34 @@ export async function processAudioMergeTranscoding (options: ProcessOptions<Runn
const ffmpegVod = buildFFmpegVOD({ job, server, runnerToken })
await ffmpegVod.transcode({
type: 'merge-audio',
try {
await ffmpegVod.transcode({
type: 'merge-audio',
audioPath,
inputPath,
audioPath,
inputPath,
outputPath,
outputPath,
inputFileMutexReleaser: () => {},
inputFileMutexReleaser: () => {},
resolution: payload.output.resolution,
fps: payload.output.fps
})
resolution: payload.output.resolution,
fps: payload.output.fps
})
const successBody: VODAudioMergeTranscodingSuccess = {
videoFile: outputPath
const successBody: VODAudioMergeTranscodingSuccess = {
videoFile: outputPath
}
await server.runnerJobs.success({
jobToken: job.jobToken,
jobUUID: job.uuid,
runnerToken,
payload: successBody
})
} finally {
await remove(audioPath)
await remove(inputPath)
await remove(outputPath)
}
await server.runnerJobs.success({
jobToken: job.jobToken,
jobUUID: job.uuid,
runnerToken,
payload: successBody
})
await remove(outputPath)
}

View File

@ -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<VideoStudioTaskPayload['name']>([ 'add-intro', 'add-outro', 'add-watermark', 'cut' ])

View File

@ -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)
)

View File

@ -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,

View File

@ -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))
}

View File

@ -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)

View File

@ -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<RunnerJobType, 'video-edition-transcoding'>
payload: RunnerJobVideoEditionTranscodingPayload
privatePayload: RunnerJobVideoEditionTranscodingPrivatePayload
type: Extract<RunnerJobType, 'video-studio-transcoding'>
payload: RunnerJobStudioTranscodingPayload
privatePayload: RunnerJobVideoStudioTranscodingPrivatePayload
}
export abstract class AbstractJobHandler <C, U extends RunnerJobUpdatePayload, S extends RunnerJobSuccessPayload> {

View File

@ -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'

View File

@ -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<RunnerJobType, new() => AbstractJobHandler<unknown, Run
'vod-hls-transcoding': VODHLSTranscodingJobHandler,
'vod-audio-merge-transcoding': VODAudioMergeTranscodingJobHandler,
'live-rtmp-hls-transcoding': LiveRTMPHLSTranscodingJobHandler,
'video-edition-transcoding': VideoEditionTranscodingJobHandler
'video-studio-transcoding': VideoStudioTranscodingJobHandler
}
export function getRunnerJobHandlerClass (job: MRunnerJob) {

View File

@ -1,7 +1,7 @@
import { basename } from 'path'
import { logger } from '@server/helpers/logger'
import { onVideoEditionEnded, safeCleanupStudioTMPFiles } from '@server/lib/video-studio'
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'
@ -11,9 +11,9 @@ import {
isVideoStudioTaskWatermark,
RunnerJobState,
RunnerJobUpdatePayload,
RunnerJobVideoEditionTranscodingPayload,
RunnerJobVideoEditionTranscodingPrivatePayload,
VideoEditionTranscodingSuccess,
RunnerJobStudioTranscodingPayload,
RunnerJobVideoStudioTranscodingPrivatePayload,
VideoStudioTranscodingSuccess,
VideoState,
VideoStudioTaskPayload
} from '@shared/models'
@ -28,13 +28,13 @@ type CreateOptions = {
}
// eslint-disable-next-line max-len
export class VideoEditionTranscodingJobHandler extends AbstractJobHandler<CreateOptions, RunnerJobUpdatePayload, VideoEditionTranscodingSuccess> {
export class VideoStudioTranscodingJobHandler extends AbstractJobHandler<CreateOptions, RunnerJobUpdatePayload, VideoStudioTranscodingSuccess> {
async create (options: CreateOptions) {
const { video, priority, tasks } = options
const jobUUID = buildUUID()
const payload: RunnerJobVideoEditionTranscodingPayload = {
const payload: RunnerJobStudioTranscodingPayload = {
input: {
videoFileUrl: generateRunnerTranscodingVideoInputFileUrl(jobUUID, video.uuid)
},
@ -67,13 +67,13 @@ export class VideoEditionTranscodingJobHandler extends AbstractJobHandler<Create
})
}
const privatePayload: RunnerJobVideoEditionTranscodingPrivatePayload = {
const privatePayload: RunnerJobVideoStudioTranscodingPrivatePayload = {
videoUUID: video.uuid,
originalTasks: tasks
}
const job = await this.createRunnerJob({
type: 'video-edition-transcoding',
type: 'video-studio-transcoding',
jobUUID,
payload,
privatePayload,
@ -103,10 +103,10 @@ export class VideoEditionTranscodingJobHandler extends AbstractJobHandler<Create
protected async specificComplete (options: {
runnerJob: MRunnerJob
resultPayload: VideoEditionTranscodingSuccess
resultPayload: VideoStudioTranscodingSuccess
}) {
const { runnerJob, resultPayload } = options
const privatePayload = runnerJob.privatePayload as RunnerJobVideoEditionTranscodingPrivatePayload
const privatePayload = runnerJob.privatePayload as RunnerJobVideoStudioTranscodingPrivatePayload
const video = await loadTranscodingRunnerVideo(runnerJob, this.lTags)
if (!video) {
@ -116,7 +116,7 @@ export class VideoEditionTranscodingJobHandler extends AbstractJobHandler<Create
const videoFilePath = resultPayload.videoFile as string
await onVideoEditionEnded({ video, editionResultPath: videoFilePath, tasks: privatePayload.originalTasks })
await onVideoStudioEnded({ video, editionResultPath: videoFilePath, tasks: privatePayload.originalTasks })
logger.info(
'Runner video edition transcoding job %s for %s ended.',
@ -146,7 +146,7 @@ export class VideoEditionTranscodingJobHandler extends AbstractJobHandler<Create
}) {
const { runnerJob } = options
const payload = runnerJob.privatePayload as RunnerJobVideoEditionTranscodingPrivatePayload
const payload = runnerJob.privatePayload as RunnerJobVideoStudioTranscodingPrivatePayload
await safeCleanupStudioTMPFiles(payload.originalTasks)
const video = await loadTranscodingRunnerVideo(options.runnerJob, this.lTags)

View File

@ -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`
]
}

View File

@ -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

View File

@ -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)) {

View File

@ -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({

View File

@ -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<RunnerJobVideoEditionTranscodingPayload>({ runnerToken, jobUUID })
const { job } = await servers[0].runnerJobs.accept<RunnerJobStudioTranscodingPayload>({ 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)

View File

@ -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 () {

View File

@ -57,7 +57,7 @@ const defaultLibFDKAACVODOptionsBuilder: EncoderOptionsBuilder = ({ streamNum })
return { outputOptions: [ buildStreamSuffix('-q:a', streamNum), '5' ] }
}
export function getAvailableEncoders () {
export function getDefaultAvailableEncoders () {
return {
vod: {
libx264: {
@ -81,7 +81,7 @@ export function getAvailableEncoders () {
}
}
export function getEncodersToTry () {
export function getDefaultEncodersToTry () {
return {
vod: {
video: [ 'libx264' ],

View File

@ -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'

View File

@ -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
}

View File

@ -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[]
}

View File

@ -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
}

View File

@ -3,4 +3,4 @@ export type RunnerJobType =
'vod-hls-transcoding' |
'vod-audio-merge-transcoding' |
'live-rtmp-hls-transcoding' |
'video-edition-transcoding'
'video-studio-transcoding'