]>
Commit | Line | Data |
---|---|---|
5e47f6ab C |
1 | import { remove } from 'fs-extra' |
2 | import { pick } from 'lodash' | |
3 | import { logger } from 'packages/peertube-runner/shared' | |
4 | import { extname, join } from 'path' | |
5 | import { buildUUID } from '@shared/extra-utils' | |
6 | import { | |
7 | RunnerJobVideoEditionTranscodingPayload, | |
8 | VideoEditionTranscodingSuccess, | |
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<RunnerJobVideoEditionTranscodingPayload>) { | |
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: VideoEditionTranscodingSuccess = { | |
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 | return buildFFmpegEdition().addIntroOutro({ | |
98 | ...pick(options, [ 'inputPath', 'outputPath' ]), | |
99 | ||
100 | introOutroPath, | |
101 | type: task.name === 'add-intro' | |
102 | ? 'intro' | |
103 | : 'outro' | |
104 | }) | |
105 | } | |
106 | ||
107 | function processCut (options: TaskProcessorOptions<VideoStudioTaskCutPayload>) { | |
108 | const { inputPath, task } = options | |
109 | ||
110 | logger.debug(`Cutting ${inputPath}`) | |
111 | ||
112 | return buildFFmpegEdition().cutVideo({ | |
113 | ...pick(options, [ 'inputPath', 'outputPath' ]), | |
114 | ||
115 | start: task.options.start, | |
116 | end: task.options.end | |
117 | }) | |
118 | } | |
119 | ||
120 | async function processAddWatermark (options: TaskProcessorOptions<VideoStudioTaskWatermarkPayload>) { | |
121 | const { inputPath, task, runnerToken, job } = options | |
122 | ||
123 | logger.debug('Adding watermark to ' + inputPath) | |
124 | ||
125 | const watermarkPath = await downloadInputFile({ url: task.options.file, runnerToken, job }) | |
126 | ||
127 | return buildFFmpegEdition().addWatermark({ | |
128 | ...pick(options, [ 'inputPath', 'outputPath' ]), | |
129 | ||
130 | watermarkPath, | |
131 | ||
132 | videoFilters: { | |
133 | watermarkSizeRatio: task.options.watermarkSizeRatio, | |
134 | horitonzalMarginRatio: task.options.horitonzalMarginRatio, | |
135 | verticalMarginRatio: task.options.verticalMarginRatio | |
136 | } | |
137 | }) | |
138 | } |