]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/controllers/api/videos/studio.ts
Support studio transcoding in peertube runner
[github/Chocobozzz/PeerTube.git] / server / controllers / api / videos / studio.ts
1 import Bluebird from 'bluebird'
2 import express from 'express'
3 import { move } from 'fs-extra'
4 import { basename } from 'path'
5 import { createAnyReqFiles } from '@server/helpers/express-utils'
6 import { MIMETYPES, VIDEO_FILTERS } from '@server/initializers/constants'
7 import { buildTaskFileFieldname, createVideoStudioJob, getStudioTaskFilePath, getTaskFileFromReq } from '@server/lib/video-studio'
8 import {
9 HttpStatusCode,
10 VideoState,
11 VideoStudioCreateEdition,
12 VideoStudioTask,
13 VideoStudioTaskCut,
14 VideoStudioTaskIntro,
15 VideoStudioTaskOutro,
16 VideoStudioTaskPayload,
17 VideoStudioTaskWatermark
18 } from '@shared/models'
19 import { asyncMiddleware, authenticate, videoStudioAddEditionValidator } from '../../../middlewares'
20
21 const studioRouter = express.Router()
22
23 const tasksFiles = createAnyReqFiles(
24 MIMETYPES.VIDEO.MIMETYPE_EXT,
25 (req: express.Request, file: Express.Multer.File, cb: (err: Error, result?: boolean) => void) => {
26 const body = req.body as VideoStudioCreateEdition
27
28 // Fetch array element
29 const matches = file.fieldname.match(/tasks\[(\d+)\]/)
30 if (!matches) return cb(new Error('Cannot find array element indice for ' + file.fieldname))
31
32 const indice = parseInt(matches[1])
33 const task = body.tasks[indice]
34
35 if (!task) return cb(new Error('Cannot find array element of indice ' + indice + ' for ' + file.fieldname))
36
37 if (
38 [ 'add-intro', 'add-outro', 'add-watermark' ].includes(task.name) &&
39 file.fieldname === buildTaskFileFieldname(indice)
40 ) {
41 return cb(null, true)
42 }
43
44 return cb(null, false)
45 }
46 )
47
48 studioRouter.post('/:videoId/studio/edit',
49 authenticate,
50 tasksFiles,
51 asyncMiddleware(videoStudioAddEditionValidator),
52 asyncMiddleware(createEditionTasks)
53 )
54
55 // ---------------------------------------------------------------------------
56
57 export {
58 studioRouter
59 }
60
61 // ---------------------------------------------------------------------------
62
63 async function createEditionTasks (req: express.Request, res: express.Response) {
64 const files = req.files as Express.Multer.File[]
65 const body = req.body as VideoStudioCreateEdition
66 const video = res.locals.videoAll
67
68 video.state = VideoState.TO_EDIT
69 await video.save()
70
71 const payload = {
72 videoUUID: video.uuid,
73 tasks: await Bluebird.mapSeries(body.tasks, (t, i) => buildTaskPayload(t, i, files))
74 }
75
76 await createVideoStudioJob({
77 user: res.locals.oauth.token.User,
78 payload,
79 video
80 })
81
82 return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
83 }
84
85 const taskPayloadBuilders: {
86 [id in VideoStudioTask['name']]: (
87 task: VideoStudioTask,
88 indice?: number,
89 files?: Express.Multer.File[]
90 ) => Promise<VideoStudioTaskPayload>
91 } = {
92 'add-intro': buildIntroOutroTask,
93 'add-outro': buildIntroOutroTask,
94 'cut': buildCutTask,
95 'add-watermark': buildWatermarkTask
96 }
97
98 function buildTaskPayload (task: VideoStudioTask, indice: number, files: Express.Multer.File[]): Promise<VideoStudioTaskPayload> {
99 return taskPayloadBuilders[task.name](task, indice, files)
100 }
101
102 async function buildIntroOutroTask (task: VideoStudioTaskIntro | VideoStudioTaskOutro, indice: number, files: Express.Multer.File[]) {
103 const destination = await moveStudioFileToPersistentTMP(getTaskFileFromReq(files, indice).path)
104
105 return {
106 name: task.name,
107 options: {
108 file: destination
109 }
110 }
111 }
112
113 function buildCutTask (task: VideoStudioTaskCut) {
114 return Promise.resolve({
115 name: task.name,
116 options: {
117 start: task.options.start,
118 end: task.options.end
119 }
120 })
121 }
122
123 async function buildWatermarkTask (task: VideoStudioTaskWatermark, indice: number, files: Express.Multer.File[]) {
124 const destination = await moveStudioFileToPersistentTMP(getTaskFileFromReq(files, indice).path)
125
126 return {
127 name: task.name,
128 options: {
129 file: destination,
130 watermarkSizeRatio: VIDEO_FILTERS.WATERMARK.SIZE_RATIO,
131 horitonzalMarginRatio: VIDEO_FILTERS.WATERMARK.HORIZONTAL_MARGIN_RATIO,
132 verticalMarginRatio: VIDEO_FILTERS.WATERMARK.VERTICAL_MARGIN_RATIO
133 }
134 }
135 }
136
137 async function moveStudioFileToPersistentTMP (file: string) {
138 const destination = getStudioTaskFilePath(basename(file))
139
140 await move(file, destination)
141
142 return destination
143 }