From 5e47f6ab984a7d00782e4c7030afffa1ba480add Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 4 May 2023 15:29:34 +0200 Subject: Support studio transcoding in peertube runner --- .../lib/job-queue/handlers/video-studio-edition.ts | 79 ++--------- .../runners/job-handlers/abstract-job-handler.ts | 11 +- .../abstract-vod-transcoding-job-handler.ts | 10 +- server/lib/runners/job-handlers/index.ts | 3 +- .../live-rtmp-hls-transcoding-job-handler.ts | 2 +- .../runners/job-handlers/runner-job-handlers.ts | 4 +- .../video-edition-transcoding-job-handler.ts | 157 +++++++++++++++++++++ .../vod-audio-merge-transcoding-job-handler.ts | 2 +- .../vod-hls-transcoding-job-handler.ts | 2 +- .../vod-web-video-transcoding-job-handler.ts | 2 +- server/lib/runners/runner-urls.ts | 4 + server/lib/server-config-manager.ts | 5 +- .../shared/job-builders/abstract-job-builder.ts | 18 --- .../job-builders/transcoding-job-queue-builder.ts | 3 +- .../job-builders/transcoding-runner-job-builder.ts | 15 +- server/lib/transcoding/transcoding-priority.ts | 24 ++++ server/lib/video-studio.ts | 109 ++++++++++++-- 17 files changed, 326 insertions(+), 124 deletions(-) create mode 100644 server/lib/runners/job-handlers/video-edition-transcoding-job-handler.ts create mode 100644 server/lib/transcoding/transcoding-priority.ts (limited to 'server/lib') diff --git a/server/lib/job-queue/handlers/video-studio-edition.ts b/server/lib/job-queue/handlers/video-studio-edition.ts index 5e8dd4f51..df73caf72 100644 --- a/server/lib/job-queue/handlers/video-studio-edition.ts +++ b/server/lib/job-queue/handlers/video-studio-edition.ts @@ -1,25 +1,18 @@ import { Job } from 'bullmq' -import { move, remove } from 'fs-extra' +import { remove } from 'fs-extra' import { join } from 'path' import { getFFmpegCommandWrapperOptions } from '@server/helpers/ffmpeg' -import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent' import { CONFIG } from '@server/initializers/config' -import { VIDEO_FILTERS } from '@server/initializers/constants' -import { federateVideoIfNeeded } from '@server/lib/activitypub/videos' -import { generateWebTorrentVideoFilename } from '@server/lib/paths' -import { createOptimizeOrMergeAudioJobs } from '@server/lib/transcoding/create-transcoding-job' import { VideoTranscodingProfilesManager } from '@server/lib/transcoding/default-transcoding-profiles' import { isAbleToUploadVideo } from '@server/lib/user' -import { buildFileMetadata, removeHLSPlaylist, removeWebTorrentFile } from '@server/lib/video-file' import { VideoPathManager } from '@server/lib/video-path-manager' -import { approximateIntroOutroAdditionalSize, safeCleanupStudioTMPFiles } from '@server/lib/video-studio' +import { approximateIntroOutroAdditionalSize, onVideoEditionEnded, safeCleanupStudioTMPFiles } from '@server/lib/video-studio' import { UserModel } from '@server/models/user/user' import { VideoModel } from '@server/models/video/video' -import { VideoFileModel } from '@server/models/video/video-file' -import { MVideo, MVideoFile, MVideoFullLight, MVideoId, MVideoWithAllFiles } from '@server/types/models' -import { getLowercaseExtension, pick } from '@shared/core-utils' -import { buildUUID, getFileSize } from '@shared/extra-utils' -import { FFmpegEdition, ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamDuration, getVideoStreamFPS } from '@shared/ffmpeg' +import { MVideo, MVideoFullLight } from '@server/types/models' +import { pick } from '@shared/core-utils' +import { buildUUID } from '@shared/extra-utils' +import { FFmpegEdition } from '@shared/ffmpeg' import { VideoStudioEditionPayload, VideoStudioTask, @@ -46,7 +39,7 @@ async function processVideoStudioEdition (job: Job) { if (!video) { logger.info('Can\'t process job %d, video does not exist.', job.id, lTags) - await safeCleanupStudioTMPFiles(payload) + await safeCleanupStudioTMPFiles(payload.tasks) return undefined } @@ -81,28 +74,9 @@ async function processVideoStudioEdition (job: Job) { logger.info('Video edition ended for video %s.', video.uuid, lTags) - const newFile = await buildNewFile(video, editionResultPath) - - const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile) - await move(editionResultPath, outputPath) - - await safeCleanupStudioTMPFiles(payload) - - await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath) - await removeAllFiles(video, newFile) - - await newFile.save() - - video.duration = await getVideoStreamDuration(outputPath) - await video.save() - - await federateVideoIfNeeded(video, false, undefined) - - const user = await UserModel.loadByVideoId(video.id) - - await createOptimizeOrMergeAudioJobs({ video, videoFile: newFile, isNewVideo: false, user, videoFileAlreadyLocked: false }) + await onVideoEditionEnded({ video, editionResultPath, tasks: payload.tasks }) } catch (err) { - await safeCleanupStudioTMPFiles(payload) + await safeCleanupStudioTMPFiles(payload.tasks) throw err } @@ -181,44 +155,15 @@ function processAddWatermark (options: TaskProcessorOptions payload: RunnerJobLiveRTMPHLSTranscodingPayload privatePayload: RunnerJobLiveRTMPHLSTranscodingPrivatePayload + } | + { + type: Extract + payload: RunnerJobVideoEditionTranscodingPayload + privatePayload: RunnerJobVideoEditionTranscodingPrivatePayload } export abstract class AbstractJobHandler { @@ -62,6 +69,8 @@ export abstract class AbstractJobHandler { const { priority, dependsOnRunnerJob } = options + logger.debug('Creating runner job', { options, ...this.lTags(options.type) }) + const runnerJob = new RunnerJobModel({ ...pick(options, [ 'type', 'payload', 'privatePayload' ]), diff --git a/server/lib/runners/job-handlers/abstract-vod-transcoding-job-handler.ts b/server/lib/runners/job-handlers/abstract-vod-transcoding-job-handler.ts index 517645848..a910ae383 100644 --- a/server/lib/runners/job-handlers/abstract-vod-transcoding-job-handler.ts +++ b/server/lib/runners/job-handlers/abstract-vod-transcoding-job-handler.ts @@ -4,27 +4,19 @@ import { logger } from '@server/helpers/logger' import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video-state' import { VideoJobInfoModel } from '@server/models/video/video-job-info' import { MRunnerJob } from '@server/types/models/runners' -import { - LiveRTMPHLSTranscodingUpdatePayload, - RunnerJobSuccessPayload, - RunnerJobUpdatePayload, - RunnerJobVODPrivatePayload -} from '@shared/models' +import { RunnerJobSuccessPayload, RunnerJobUpdatePayload, RunnerJobVODPrivatePayload } from '@shared/models' import { AbstractJobHandler } from './abstract-job-handler' import { loadTranscodingRunnerVideo } from './shared' // eslint-disable-next-line max-len export abstract class AbstractVODTranscodingJobHandler extends AbstractJobHandler { - // --------------------------------------------------------------------------- - protected isAbortSupported () { return true } protected specificUpdate (_options: { runnerJob: MRunnerJob - updatePayload?: LiveRTMPHLSTranscodingUpdatePayload }) { // empty } diff --git a/server/lib/runners/job-handlers/index.ts b/server/lib/runners/job-handlers/index.ts index 0fca72b9a..a40cee865 100644 --- a/server/lib/runners/job-handlers/index.ts +++ b/server/lib/runners/job-handlers/index.ts @@ -1,6 +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 './vod-audio-merge-transcoding-job-handler' export * from './vod-hls-transcoding-job-handler' export * from './vod-web-video-transcoding-job-handler' -export * from './runner-job-handlers' diff --git a/server/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts b/server/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts index c3d0e427d..48a70d891 100644 --- a/server/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts +++ b/server/lib/runners/job-handlers/live-rtmp-hls-transcoding-job-handler.ts @@ -70,7 +70,7 @@ export class LiveRTMPHLSTranscodingJobHandler extends AbstractJobHandler 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/vod-audio-merge-transcoding-job-handler.ts b/server/lib/runners/job-handlers/vod-audio-merge-transcoding-job-handler.ts index a7b33f87e..5f247d792 100644 --- a/server/lib/runners/job-handlers/vod-audio-merge-transcoding-job-handler.ts +++ b/server/lib/runners/job-handlers/vod-audio-merge-transcoding-job-handler.ts @@ -64,7 +64,7 @@ export class VODAudioMergeTranscodingJobHandler extends AbstractVODTranscodingJo // --------------------------------------------------------------------------- - async specificComplete (options: { + protected async specificComplete (options: { runnerJob: MRunnerJob resultPayload: VODAudioMergeTranscodingSuccess }) { diff --git a/server/lib/runners/job-handlers/vod-hls-transcoding-job-handler.ts b/server/lib/runners/job-handlers/vod-hls-transcoding-job-handler.ts index 02566b9d5..cc94bcbda 100644 --- a/server/lib/runners/job-handlers/vod-hls-transcoding-job-handler.ts +++ b/server/lib/runners/job-handlers/vod-hls-transcoding-job-handler.ts @@ -71,7 +71,7 @@ export class VODHLSTranscodingJobHandler extends AbstractVODTranscodingJobHandle // --------------------------------------------------------------------------- - async specificComplete (options: { + protected async specificComplete (options: { runnerJob: MRunnerJob resultPayload: VODHLSTranscodingSuccess }) { diff --git a/server/lib/runners/job-handlers/vod-web-video-transcoding-job-handler.ts b/server/lib/runners/job-handlers/vod-web-video-transcoding-job-handler.ts index 57761a7a1..663d3306e 100644 --- a/server/lib/runners/job-handlers/vod-web-video-transcoding-job-handler.ts +++ b/server/lib/runners/job-handlers/vod-web-video-transcoding-job-handler.ts @@ -62,7 +62,7 @@ export class VODWebVideoTranscodingJobHandler extends AbstractVODTranscodingJobH // --------------------------------------------------------------------------- - async specificComplete (options: { + protected async specificComplete (options: { runnerJob: MRunnerJob resultPayload: VODWebVideoTranscodingSuccess }) { diff --git a/server/lib/runners/runner-urls.ts b/server/lib/runners/runner-urls.ts index 329fb1170..a27060b33 100644 --- a/server/lib/runners/runner-urls.ts +++ b/server/lib/runners/runner-urls.ts @@ -7,3 +7,7 @@ export function generateRunnerTranscodingVideoInputFileUrl (jobUUID: string, vid export function generateRunnerTranscodingVideoPreviewFileUrl (jobUUID: string, videoUUID: string) { return WEBSERVER.URL + '/api/v1/runners/jobs/' + jobUUID + '/files/videos/' + videoUUID + '/previews/max-quality' } + +export function generateRunnerEditionTranscodingVideoInputFileUrl (jobUUID: string, videoUUID: string, filename: string) { + return WEBSERVER.URL + '/api/v1/runners/jobs/' + jobUUID + '/files/videos/' + videoUUID + '/studio/task-files/' + filename +} diff --git a/server/lib/server-config-manager.ts b/server/lib/server-config-manager.ts index ba7916363..924adb337 100644 --- a/server/lib/server-config-manager.ts +++ b/server/lib/server-config-manager.ts @@ -166,7 +166,10 @@ class ServerConfigManager { } }, videoStudio: { - enabled: CONFIG.VIDEO_STUDIO.ENABLED + enabled: CONFIG.VIDEO_STUDIO.ENABLED, + remoteRunners: { + enabled: CONFIG.VIDEO_STUDIO.REMOTE_RUNNERS.ENABLED + } }, import: { videos: { diff --git a/server/lib/transcoding/shared/job-builders/abstract-job-builder.ts b/server/lib/transcoding/shared/job-builders/abstract-job-builder.ts index 576e786d5..80dc05bfb 100644 --- a/server/lib/transcoding/shared/job-builders/abstract-job-builder.ts +++ b/server/lib/transcoding/shared/job-builders/abstract-job-builder.ts @@ -1,6 +1,4 @@ -import { JOB_PRIORITY } from '@server/initializers/constants' -import { VideoModel } from '@server/models/video/video' import { MUserId, MVideoFile, MVideoFullLight } from '@server/types/models' export abstract class AbstractJobBuilder { @@ -20,20 +18,4 @@ export abstract class AbstractJobBuilder { isNewVideo: boolean user: MUserId | null }): Promise - - protected async getTranscodingJobPriority (options: { - user: MUserId - fallback: number - }) { - const { user, fallback } = options - - if (!user) return fallback - - const now = new Date() - const lastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7) - - const videoUploadedByUser = await VideoModel.countVideosUploadedByUserSince(user.id, lastWeek) - - return JOB_PRIORITY.TRANSCODING + videoUploadedByUser - } } diff --git a/server/lib/transcoding/shared/job-builders/transcoding-job-queue-builder.ts b/server/lib/transcoding/shared/job-builders/transcoding-job-queue-builder.ts index 5a9c93ee5..29ee2ca61 100644 --- a/server/lib/transcoding/shared/job-builders/transcoding-job-queue-builder.ts +++ b/server/lib/transcoding/shared/job-builders/transcoding-job-queue-builder.ts @@ -16,6 +16,7 @@ import { OptimizeTranscodingPayload, VideoTranscodingPayload } from '@shared/models' +import { getTranscodingJobPriority } from '../../transcoding-priority' import { canDoQuickTranscode } from '../../transcoding-quick-transcode' import { computeResolutionsToTranscode } from '../../transcoding-resolutions' import { AbstractJobBuilder } from './abstract-job-builder' @@ -178,7 +179,7 @@ export class TranscodingJobQueueBuilder extends AbstractJobBuilder { return { type: 'video-transcoding' as 'video-transcoding', - priority: await this.getTranscodingJobPriority({ user, fallback: undefined }), + priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: undefined }), payload } } diff --git a/server/lib/transcoding/shared/job-builders/transcoding-runner-job-builder.ts b/server/lib/transcoding/shared/job-builders/transcoding-runner-job-builder.ts index 274dce21b..90b035402 100644 --- a/server/lib/transcoding/shared/job-builders/transcoding-runner-job-builder.ts +++ b/server/lib/transcoding/shared/job-builders/transcoding-runner-job-builder.ts @@ -8,6 +8,7 @@ import { VideoPathManager } from '@server/lib/video-path-manager' import { MUserId, MVideoFile, MVideoFullLight, MVideoWithFileThumbnail } from '@server/types/models' import { MRunnerJob } from '@server/types/models/runners' import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, hasAudioStream, isAudioFile } from '@shared/ffmpeg' +import { getTranscodingJobPriority } from '../../transcoding-priority' import { computeResolutionsToTranscode } from '../../transcoding-resolutions' import { AbstractJobBuilder } from './abstract-job-builder' @@ -49,7 +50,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { : resolution const fps = computeOutputFPS({ inputFPS, resolution: maxResolution }) - const priority = await this.getTranscodingJobPriority({ user, fallback: 0 }) + const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) const mainRunnerJob = videoFile.isAudio() ? await new VODAudioMergeTranscodingJobHandler().create({ video, resolution: maxResolution, fps, isNewVideo, priority }) @@ -63,7 +64,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { fps, isNewVideo, dependsOnRunnerJob: mainRunnerJob, - priority: await this.getTranscodingJobPriority({ user, fallback: 0 }) + priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) }) } @@ -96,7 +97,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { const maxResolution = Math.max(...resolutions) const { fps: inputFPS } = await video.probeMaxQualityFile() const maxFPS = computeOutputFPS({ inputFPS, resolution: maxResolution }) - const priority = await this.getTranscodingJobPriority({ user, fallback: 0 }) + const priority = await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) const childrenResolutions = resolutions.filter(r => r !== maxResolution) @@ -121,7 +122,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { isNewVideo, deleteWebVideoFiles: false, dependsOnRunnerJob, - priority: await this.getTranscodingJobPriority({ user, fallback: 0 }) + priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) }) continue } @@ -133,7 +134,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { fps, isNewVideo, dependsOnRunnerJob, - priority: await this.getTranscodingJobPriority({ user, fallback: 0 }) + priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) }) continue } @@ -172,7 +173,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { fps, isNewVideo, dependsOnRunnerJob: mainRunnerJob, - priority: await this.getTranscodingJobPriority({ user, fallback: 0 }) + priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) }) } @@ -184,7 +185,7 @@ export class TranscodingRunnerJobBuilder extends AbstractJobBuilder { isNewVideo, deleteWebVideoFiles: false, dependsOnRunnerJob: mainRunnerJob, - priority: await this.getTranscodingJobPriority({ user, fallback: 0 }) + priority: await getTranscodingJobPriority({ user, type: 'vod', fallback: 0 }) }) } } diff --git a/server/lib/transcoding/transcoding-priority.ts b/server/lib/transcoding/transcoding-priority.ts new file mode 100644 index 000000000..82ab6f2f1 --- /dev/null +++ b/server/lib/transcoding/transcoding-priority.ts @@ -0,0 +1,24 @@ +import { JOB_PRIORITY } from '@server/initializers/constants' +import { VideoModel } from '@server/models/video/video' +import { MUserId } from '@server/types/models' + +export async function getTranscodingJobPriority (options: { + user: MUserId + fallback: number + type: 'vod' | 'studio' +}) { + const { user, fallback, type } = options + + if (!user) return fallback + + const now = new Date() + const lastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7) + + const videoUploadedByUser = await VideoModel.countVideosUploadedByUserSince(user.id, lastWeek) + + const base = type === 'vod' + ? JOB_PRIORITY.TRANSCODING + : JOB_PRIORITY.VIDEO_STUDIO + + return base + videoUploadedByUser +} diff --git a/server/lib/video-studio.ts b/server/lib/video-studio.ts index beda326a0..2c993faeb 100644 --- a/server/lib/video-studio.ts +++ b/server/lib/video-studio.ts @@ -1,19 +1,38 @@ -import { logger } from '@server/helpers/logger' -import { MVideoFullLight } from '@server/types/models' +import { move, remove } from 'fs-extra' +import { join } from 'path' +import { logger, loggerTagsFactory } from '@server/helpers/logger' +import { createTorrentAndSetInfoHashFromPath } from '@server/helpers/webtorrent' +import { CONFIG } from '@server/initializers/config' +import { UserModel } from '@server/models/user/user' +import { MUser, MVideo, MVideoFile, MVideoFullLight, MVideoWithAllFiles } from '@server/types/models' import { getVideoStreamDuration } from '@shared/ffmpeg' -import { VideoStudioEditionPayload, VideoStudioTask } from '@shared/models' -import { remove } from 'fs-extra' +import { VideoStudioEditionPayload, VideoStudioTask, VideoStudioTaskPayload } from '@shared/models' +import { federateVideoIfNeeded } from './activitypub/videos' +import { JobQueue } from './job-queue' +import { VideoEditionTranscodingJobHandler } 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' -function buildTaskFileFieldname (indice: number, fieldName = 'file') { +const lTags = loggerTagsFactory('video-edition') + +export function buildTaskFileFieldname (indice: number, fieldName = 'file') { return `tasks[${indice}][options][${fieldName}]` } -function getTaskFileFromReq (files: Express.Multer.File[], indice: number, fieldName = 'file') { +export function getTaskFileFromReq (files: Express.Multer.File[], indice: number, fieldName = 'file') { return files.find(f => f.fieldname === buildTaskFileFieldname(indice, fieldName)) } -async function safeCleanupStudioTMPFiles (payload: VideoStudioEditionPayload) { - for (const task of payload.tasks) { +export function getStudioTaskFilePath (filename: string) { + return join(CONFIG.STORAGE.TMP_PERSISTENT_DIR, filename) +} + +export async function safeCleanupStudioTMPFiles (tasks: VideoStudioTaskPayload[]) { + logger.info('Removing studio task files', { tasks, ...lTags() }) + + for (const task of tasks) { try { if (task.name === 'add-intro' || task.name === 'add-outro') { await remove(task.options.file) @@ -26,7 +45,13 @@ async function safeCleanupStudioTMPFiles (payload: VideoStudioEditionPayload) { } } -async function approximateIntroOutroAdditionalSize (video: MVideoFullLight, tasks: VideoStudioTask[], fileFinder: (i: number) => string) { +// --------------------------------------------------------------------------- + +export async function approximateIntroOutroAdditionalSize ( + video: MVideoFullLight, + tasks: VideoStudioTask[], + fileFinder: (i: number) => string +) { let additionalDuration = 0 for (let i = 0; i < tasks.length; i++) { @@ -41,9 +66,65 @@ async function approximateIntroOutroAdditionalSize (video: MVideoFullLight, task return (video.getMaxQualityFile().size / video.duration) * additionalDuration } -export { - approximateIntroOutroAdditionalSize, - buildTaskFileFieldname, - getTaskFileFromReq, - safeCleanupStudioTMPFiles +// --------------------------------------------------------------------------- + +export async function createVideoStudioJob (options: { + video: MVideo + user: MUser + payload: VideoStudioEditionPayload +}) { + const { video, user, payload } = 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 }) + return + } + + await JobQueue.Instance.createJob({ type: 'video-studio-edition', payload, priority }) +} + +export async function onVideoEditionEnded (options: { + editionResultPath: string + tasks: VideoStudioTaskPayload[] + video: MVideoFullLight +}) { + const { video, tasks, editionResultPath } = options + + const newFile = await buildNewFile({ path: editionResultPath, mode: 'web-video' }) + newFile.videoId = video.id + + const outputPath = VideoPathManager.Instance.getFSVideoFileOutputPath(video, newFile) + await move(editionResultPath, outputPath) + + await safeCleanupStudioTMPFiles(tasks) + + await createTorrentAndSetInfoHashFromPath(video, newFile, outputPath) + await removeAllFiles(video, newFile) + + await newFile.save() + + video.duration = await getVideoStreamDuration(outputPath) + await video.save() + + await federateVideoIfNeeded(video, false, undefined) + + const user = await UserModel.loadByVideoId(video.id) + + await createOptimizeOrMergeAudioJobs({ video, videoFile: newFile, isNewVideo: false, user, videoFileAlreadyLocked: false }) +} + +// --------------------------------------------------------------------------- +// Private +// --------------------------------------------------------------------------- + +async function removeAllFiles (video: MVideoWithAllFiles, webTorrentFileException: MVideoFile) { + await removeHLSPlaylist(video) + + for (const file of video.VideoFiles) { + if (file.id === webTorrentFileException.id) continue + + await removeWebTorrentFile(video, file.id) + } } -- cgit v1.2.3