]>
Commit | Line | Data |
---|---|---|
1 | import { remove } from 'fs-extra' | |
2 | import { pick } from 'lodash' | |
3 | import { logger } from 'packages/peertube-runner/shared' | |
4 | import { join } from 'path' | |
5 | import { buildUUID } from '@shared/extra-utils' | |
6 | import { | |
7 | RunnerJobStudioTranscodingPayload, | |
8 | VideoStudioTranscodingSuccess, | |
9 | VideoStudioTask, | |
10 | VideoStudioTaskCutPayload, | |
11 | VideoStudioTaskIntroPayload, | |
12 | VideoStudioTaskOutroPayload, | |
13 | VideoStudioTaskPayload, | |
14 | VideoStudioTaskWatermarkPayload | |
15 | } from '@shared/models' | |
16 | import { ConfigManager } from '../../../shared/config-manager' | |
17 | import { buildFFmpegEdition, downloadInputFile, JobWithToken, ProcessOptions } from './common' | |
18 | ||
19 | export async function processStudioTranscoding (options: ProcessOptions<RunnerJobStudioTranscodingPayload>) { | |
20 | const { server, job, runnerToken } = options | |
21 | const payload = job.payload | |
22 | ||
23 | let outputPath: string | |
24 | const inputPath = await downloadInputFile({ url: payload.input.videoFileUrl, runnerToken, job }) | |
25 | let tmpInputFilePath = inputPath | |
26 | ||
27 | try { | |
28 | for (const task of payload.tasks) { | |
29 | const outputFilename = 'output-edition-' + buildUUID() + '.mp4' | |
30 | outputPath = join(ConfigManager.Instance.getTranscodingDirectory(), outputFilename) | |
31 | ||
32 | await processTask({ | |
33 | inputPath: tmpInputFilePath, | |
34 | outputPath, | |
35 | task, | |
36 | job, | |
37 | runnerToken | |
38 | }) | |
39 | ||
40 | if (tmpInputFilePath) await remove(tmpInputFilePath) | |
41 | ||
42 | // For the next iteration | |
43 | tmpInputFilePath = outputPath | |
44 | } | |
45 | ||
46 | const successBody: VideoStudioTranscodingSuccess = { | |
47 | videoFile: outputPath | |
48 | } | |
49 | ||
50 | await server.runnerJobs.success({ | |
51 | jobToken: job.jobToken, | |
52 | jobUUID: job.uuid, | |
53 | runnerToken, | |
54 | payload: successBody | |
55 | }) | |
56 | } finally { | |
57 | await remove(tmpInputFilePath) | |
58 | await remove(outputPath) | |
59 | } | |
60 | } | |
61 | ||
62 | // --------------------------------------------------------------------------- | |
63 | // Private | |
64 | // --------------------------------------------------------------------------- | |
65 | ||
66 | type TaskProcessorOptions <T extends VideoStudioTaskPayload = VideoStudioTaskPayload> = { | |
67 | inputPath: string | |
68 | outputPath: string | |
69 | task: T | |
70 | runnerToken: string | |
71 | job: JobWithToken | |
72 | } | |
73 | ||
74 | const taskProcessors: { [id in VideoStudioTask['name']]: (options: TaskProcessorOptions) => Promise<any> } = { | |
75 | 'add-intro': processAddIntroOutro, | |
76 | 'add-outro': processAddIntroOutro, | |
77 | 'cut': processCut, | |
78 | 'add-watermark': processAddWatermark | |
79 | } | |
80 | ||
81 | async function processTask (options: TaskProcessorOptions) { | |
82 | const { task } = options | |
83 | ||
84 | const processor = taskProcessors[options.task.name] | |
85 | if (!process) throw new Error('Unknown task ' + task.name) | |
86 | ||
87 | return processor(options) | |
88 | } | |
89 | ||
90 | async function processAddIntroOutro (options: TaskProcessorOptions<VideoStudioTaskIntroPayload | VideoStudioTaskOutroPayload>) { | |
91 | const { inputPath, task, runnerToken, job } = options | |
92 | ||
93 | logger.debug('Adding intro/outro to ' + inputPath) | |
94 | ||
95 | const introOutroPath = await downloadInputFile({ url: task.options.file, runnerToken, job }) | |
96 | ||
97 | try { | |
98 | await buildFFmpegEdition().addIntroOutro({ | |
99 | ...pick(options, [ 'inputPath', 'outputPath' ]), | |
100 | ||
101 | introOutroPath, | |
102 | type: task.name === 'add-intro' | |
103 | ? 'intro' | |
104 | : 'outro' | |
105 | }) | |
106 | } finally { | |
107 | await remove(introOutroPath) | |
108 | } | |
109 | } | |
110 | ||
111 | function processCut (options: TaskProcessorOptions<VideoStudioTaskCutPayload>) { | |
112 | const { inputPath, task } = options | |
113 | ||
114 | logger.debug(`Cutting ${inputPath}`) | |
115 | ||
116 | return buildFFmpegEdition().cutVideo({ | |
117 | ...pick(options, [ 'inputPath', 'outputPath' ]), | |
118 | ||
119 | start: task.options.start, | |
120 | end: task.options.end | |
121 | }) | |
122 | } | |
123 | ||
124 | async function processAddWatermark (options: TaskProcessorOptions<VideoStudioTaskWatermarkPayload>) { | |
125 | const { inputPath, task, runnerToken, job } = options | |
126 | ||
127 | logger.debug('Adding watermark to ' + inputPath) | |
128 | ||
129 | const watermarkPath = await downloadInputFile({ url: task.options.file, runnerToken, job }) | |
130 | ||
131 | try { | |
132 | await buildFFmpegEdition().addWatermark({ | |
133 | ...pick(options, [ 'inputPath', 'outputPath' ]), | |
134 | ||
135 | watermarkPath, | |
136 | ||
137 | videoFilters: { | |
138 | watermarkSizeRatio: task.options.watermarkSizeRatio, | |
139 | horitonzalMarginRatio: task.options.horitonzalMarginRatio, | |
140 | verticalMarginRatio: task.options.verticalMarginRatio | |
141 | } | |
142 | }) | |
143 | } finally { | |
144 | await remove(watermarkPath) | |
145 | } | |
146 | } |