2023-05-04 08:29:34 -05:00
|
|
|
import { remove } from 'fs-extra'
|
|
|
|
import { pick } from 'lodash'
|
|
|
|
import { logger } from 'packages/peertube-runner/shared'
|
2023-05-04 08:55:51 -05:00
|
|
|
import { join } from 'path'
|
2023-05-04 08:29:34 -05:00
|
|
|
import { buildUUID } from '@shared/extra-utils'
|
|
|
|
import {
|
2023-05-04 08:55:51 -05:00
|
|
|
RunnerJobStudioTranscodingPayload,
|
2023-05-04 08:29:34 -05:00
|
|
|
VideoStudioTask,
|
|
|
|
VideoStudioTaskCutPayload,
|
|
|
|
VideoStudioTaskIntroPayload,
|
|
|
|
VideoStudioTaskOutroPayload,
|
|
|
|
VideoStudioTaskPayload,
|
2023-06-22 08:25:39 -05:00
|
|
|
VideoStudioTaskWatermarkPayload,
|
|
|
|
VideoStudioTranscodingSuccess
|
2023-05-04 08:29:34 -05:00
|
|
|
} from '@shared/models'
|
|
|
|
import { ConfigManager } from '../../../shared/config-manager'
|
2023-06-22 08:25:39 -05:00
|
|
|
import { buildFFmpegEdition, downloadInputFile, JobWithToken, ProcessOptions, scheduleTranscodingProgress } from './common'
|
2023-05-04 08:29:34 -05:00
|
|
|
|
2023-05-04 08:55:51 -05:00
|
|
|
export async function processStudioTranscoding (options: ProcessOptions<RunnerJobStudioTranscodingPayload>) {
|
2023-05-04 08:29:34 -05:00
|
|
|
const { server, job, runnerToken } = options
|
|
|
|
const payload = job.payload
|
|
|
|
|
2023-06-22 08:25:39 -05:00
|
|
|
let inputPath: string
|
2023-05-04 08:29:34 -05:00
|
|
|
let outputPath: string
|
2023-06-22 08:25:39 -05:00
|
|
|
let tmpInputFilePath: string
|
|
|
|
|
|
|
|
let tasksProgress = 0
|
2023-05-04 08:29:34 -05:00
|
|
|
|
2023-06-22 08:25:39 -05:00
|
|
|
const updateProgressInterval = scheduleTranscodingProgress({
|
|
|
|
job,
|
|
|
|
server,
|
|
|
|
runnerToken,
|
|
|
|
progressGetter: () => tasksProgress
|
|
|
|
})
|
2023-06-22 08:02:27 -05:00
|
|
|
|
2023-05-04 08:29:34 -05:00
|
|
|
try {
|
2023-06-22 08:25:39 -05:00
|
|
|
logger.info(`Downloading input file ${payload.input.videoFileUrl} for job ${job.jobToken}`)
|
|
|
|
|
|
|
|
inputPath = await downloadInputFile({ url: payload.input.videoFileUrl, runnerToken, job })
|
|
|
|
tmpInputFilePath = inputPath
|
|
|
|
|
|
|
|
logger.info(`Input file ${payload.input.videoFileUrl} downloaded for job ${job.jobToken}. Running studio transcoding tasks.`)
|
|
|
|
|
2023-05-04 08:29:34 -05:00
|
|
|
for (const task of payload.tasks) {
|
|
|
|
const outputFilename = 'output-edition-' + buildUUID() + '.mp4'
|
|
|
|
outputPath = join(ConfigManager.Instance.getTranscodingDirectory(), outputFilename)
|
|
|
|
|
|
|
|
await processTask({
|
|
|
|
inputPath: tmpInputFilePath,
|
|
|
|
outputPath,
|
|
|
|
task,
|
|
|
|
job,
|
|
|
|
runnerToken
|
|
|
|
})
|
|
|
|
|
|
|
|
if (tmpInputFilePath) await remove(tmpInputFilePath)
|
|
|
|
|
|
|
|
// For the next iteration
|
|
|
|
tmpInputFilePath = outputPath
|
2023-06-22 08:25:39 -05:00
|
|
|
|
|
|
|
tasksProgress += Math.floor(100 / payload.tasks.length)
|
2023-05-04 08:29:34 -05:00
|
|
|
}
|
|
|
|
|
2023-05-04 08:55:51 -05:00
|
|
|
const successBody: VideoStudioTranscodingSuccess = {
|
2023-05-04 08:29:34 -05:00
|
|
|
videoFile: outputPath
|
|
|
|
}
|
|
|
|
|
|
|
|
await server.runnerJobs.success({
|
|
|
|
jobToken: job.jobToken,
|
|
|
|
jobUUID: job.uuid,
|
|
|
|
runnerToken,
|
|
|
|
payload: successBody
|
|
|
|
})
|
|
|
|
} finally {
|
2023-06-22 08:25:39 -05:00
|
|
|
if (tmpInputFilePath) await remove(tmpInputFilePath)
|
|
|
|
if (outputPath) await remove(outputPath)
|
|
|
|
if (updateProgressInterval) clearInterval(updateProgressInterval)
|
2023-05-04 08:29:34 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Private
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
type TaskProcessorOptions <T extends VideoStudioTaskPayload = VideoStudioTaskPayload> = {
|
|
|
|
inputPath: string
|
|
|
|
outputPath: string
|
|
|
|
task: T
|
|
|
|
runnerToken: string
|
|
|
|
job: JobWithToken
|
|
|
|
}
|
|
|
|
|
|
|
|
const taskProcessors: { [id in VideoStudioTask['name']]: (options: TaskProcessorOptions) => Promise<any> } = {
|
|
|
|
'add-intro': processAddIntroOutro,
|
|
|
|
'add-outro': processAddIntroOutro,
|
|
|
|
'cut': processCut,
|
|
|
|
'add-watermark': processAddWatermark
|
|
|
|
}
|
|
|
|
|
|
|
|
async function processTask (options: TaskProcessorOptions) {
|
|
|
|
const { task } = options
|
|
|
|
|
|
|
|
const processor = taskProcessors[options.task.name]
|
|
|
|
if (!process) throw new Error('Unknown task ' + task.name)
|
|
|
|
|
|
|
|
return processor(options)
|
|
|
|
}
|
|
|
|
|
|
|
|
async function processAddIntroOutro (options: TaskProcessorOptions<VideoStudioTaskIntroPayload | VideoStudioTaskOutroPayload>) {
|
|
|
|
const { inputPath, task, runnerToken, job } = options
|
|
|
|
|
|
|
|
logger.debug('Adding intro/outro to ' + inputPath)
|
|
|
|
|
|
|
|
const introOutroPath = await downloadInputFile({ url: task.options.file, runnerToken, job })
|
|
|
|
|
2023-05-04 08:55:51 -05:00
|
|
|
try {
|
|
|
|
await buildFFmpegEdition().addIntroOutro({
|
|
|
|
...pick(options, [ 'inputPath', 'outputPath' ]),
|
2023-05-04 08:29:34 -05:00
|
|
|
|
2023-05-04 08:55:51 -05:00
|
|
|
introOutroPath,
|
|
|
|
type: task.name === 'add-intro'
|
|
|
|
? 'intro'
|
|
|
|
: 'outro'
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
await remove(introOutroPath)
|
|
|
|
}
|
2023-05-04 08:29:34 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function processCut (options: TaskProcessorOptions<VideoStudioTaskCutPayload>) {
|
|
|
|
const { inputPath, task } = options
|
|
|
|
|
|
|
|
logger.debug(`Cutting ${inputPath}`)
|
|
|
|
|
|
|
|
return buildFFmpegEdition().cutVideo({
|
|
|
|
...pick(options, [ 'inputPath', 'outputPath' ]),
|
|
|
|
|
|
|
|
start: task.options.start,
|
|
|
|
end: task.options.end
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async function processAddWatermark (options: TaskProcessorOptions<VideoStudioTaskWatermarkPayload>) {
|
|
|
|
const { inputPath, task, runnerToken, job } = options
|
|
|
|
|
|
|
|
logger.debug('Adding watermark to ' + inputPath)
|
|
|
|
|
|
|
|
const watermarkPath = await downloadInputFile({ url: task.options.file, runnerToken, job })
|
|
|
|
|
2023-05-04 08:55:51 -05:00
|
|
|
try {
|
|
|
|
await buildFFmpegEdition().addWatermark({
|
|
|
|
...pick(options, [ 'inputPath', 'outputPath' ]),
|
2023-05-04 08:29:34 -05:00
|
|
|
|
2023-05-04 08:55:51 -05:00
|
|
|
watermarkPath,
|
2023-05-04 08:29:34 -05:00
|
|
|
|
2023-05-04 08:55:51 -05:00
|
|
|
videoFilters: {
|
|
|
|
watermarkSizeRatio: task.options.watermarkSizeRatio,
|
|
|
|
horitonzalMarginRatio: task.options.horitonzalMarginRatio,
|
|
|
|
verticalMarginRatio: task.options.verticalMarginRatio
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} finally {
|
|
|
|
await remove(watermarkPath)
|
|
|
|
}
|
2023-05-04 08:29:34 -05:00
|
|
|
}
|