2 import { basename } from 'path'
3 import { logger } from '@server/helpers/logger'
4 import { onVideoStudioEnded, safeCleanupStudioTMPFiles } from '@server/lib/video-studio'
5 import { MVideo } from '@server/types/models'
6 import { MRunnerJob } from '@server/types/models/runners'
7 import { buildUUID } from '@shared/extra-utils'
9 isVideoStudioTaskIntro,
10 isVideoStudioTaskOutro,
11 isVideoStudioTaskWatermark,
13 RunnerJobUpdatePayload,
14 RunnerJobStudioTranscodingPayload,
15 RunnerJobVideoStudioTranscodingPrivatePayload,
16 VideoStudioTranscodingSuccess,
18 VideoStudioTaskPayload
19 } from '@shared/models'
20 import { generateRunnerEditionTranscodingVideoInputFileUrl, generateRunnerTranscodingVideoInputFileUrl } from '../runner-urls'
21 import { AbstractJobHandler } from './abstract-job-handler'
22 import { loadTranscodingRunnerVideo } from './shared'
24 type CreateOptions = {
26 tasks: VideoStudioTaskPayload[]
30 // eslint-disable-next-line max-len
31 export class VideoStudioTranscodingJobHandler extends AbstractJobHandler<CreateOptions, RunnerJobUpdatePayload, VideoStudioTranscodingSuccess> {
33 async create (options: CreateOptions) {
34 const { video, priority, tasks } = options
36 const jobUUID = buildUUID()
37 const payload: RunnerJobStudioTranscodingPayload = {
39 videoFileUrl: generateRunnerTranscodingVideoInputFileUrl(jobUUID, video.uuid)
41 tasks: tasks.map(t => {
42 if (isVideoStudioTaskIntro(t) || isVideoStudioTaskOutro(t)) {
49 file: generateRunnerEditionTranscodingVideoInputFileUrl(jobUUID, video.uuid, basename(t.options.file))
54 if (isVideoStudioTaskWatermark(t)) {
61 file: generateRunnerEditionTranscodingVideoInputFileUrl(jobUUID, video.uuid, basename(t.options.file))
70 const privatePayload: RunnerJobVideoStudioTranscodingPrivatePayload = {
71 videoUUID: video.uuid,
75 const job = await this.createRunnerJob({
76 type: 'video-studio-transcoding',
86 // ---------------------------------------------------------------------------
88 protected isAbortSupported () {
92 protected specificUpdate (_options: {
98 protected specificAbort (_options: {
104 protected async specificComplete (options: {
105 runnerJob: MRunnerJob
106 resultPayload: VideoStudioTranscodingSuccess
108 const { runnerJob, resultPayload } = options
109 const privatePayload = runnerJob.privatePayload as RunnerJobVideoStudioTranscodingPrivatePayload
111 const video = await loadTranscodingRunnerVideo(runnerJob, this.lTags)
113 await safeCleanupStudioTMPFiles(privatePayload.originalTasks)
117 const videoFilePath = resultPayload.videoFile as string
119 await onVideoStudioEnded({ video, editionResultPath: videoFilePath, tasks: privatePayload.originalTasks })
122 'Runner video edition transcoding job %s for %s ended.',
123 runnerJob.uuid, video.uuid, this.lTags(video.uuid, runnerJob.uuid)
127 protected specificError (options: {
128 runnerJob: MRunnerJob
129 nextState: RunnerJobState
131 if (options.nextState === RunnerJobState.ERRORED) {
132 return this.specificErrorOrCancel(options)
135 return Promise.resolve()
138 protected specificCancel (options: {
139 runnerJob: MRunnerJob
141 return this.specificErrorOrCancel(options)
144 private async specificErrorOrCancel (options: {
145 runnerJob: MRunnerJob
147 const { runnerJob } = options
149 const payload = runnerJob.privatePayload as RunnerJobVideoStudioTranscodingPrivatePayload
150 await safeCleanupStudioTMPFiles(payload.originalTasks)
152 const video = await loadTranscodingRunnerVideo(options.runnerJob, this.lTags)
155 return video.setNewState(VideoState.PUBLISHED, false, undefined)