]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/transcoding/video-studio.ts
Add TMP persistent directory
[github/Chocobozzz/PeerTube.git] / server / tests / api / transcoding / video-studio.ts
1 import { expect } from 'chai'
2 import { checkPersistentTmpIsEmpty, expectStartWith } from '@server/tests/shared'
3 import { areMockObjectStorageTestsDisabled, getAllFiles } from '@shared/core-utils'
4 import { VideoStudioTask } from '@shared/models'
5 import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 ObjectStorageCommand,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultVideoChannel,
13 VideoStudioCommand,
14 waitJobs
15 } from '@shared/server-commands'
16
17 describe('Test video studio', function () {
18 let servers: PeerTubeServer[] = []
19 let videoUUID: string
20
21 async function checkDuration (server: PeerTubeServer, duration: number) {
22 const video = await server.videos.get({ id: videoUUID })
23
24 expect(video.duration).to.be.approximately(duration, 1)
25
26 for (const file of video.files) {
27 const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl })
28
29 for (const stream of metadata.streams) {
30 expect(Math.round(stream.duration)).to.be.approximately(duration, 1)
31 }
32 }
33 }
34
35 async function renewVideo (fixture = 'video_short.webm') {
36 const video = await servers[0].videos.quickUpload({ name: 'video', fixture })
37 videoUUID = video.uuid
38
39 await waitJobs(servers)
40 }
41
42 async function createTasks (tasks: VideoStudioTask[]) {
43 await servers[0].videoStudio.createEditionTasks({ videoId: videoUUID, tasks })
44 await waitJobs(servers)
45 }
46
47 before(async function () {
48 this.timeout(120_000)
49
50 servers = await createMultipleServers(2)
51
52 await setAccessTokensToServers(servers)
53 await setDefaultVideoChannel(servers)
54
55 await doubleFollow(servers[0], servers[1])
56
57 await servers[0].config.enableMinimumTranscoding()
58
59 await servers[0].config.enableStudio()
60 })
61
62 describe('Cutting', function () {
63
64 it('Should cut the beginning of the video', async function () {
65 this.timeout(120_000)
66
67 await renewVideo()
68 await waitJobs(servers)
69
70 const beforeTasks = new Date()
71
72 await createTasks([
73 {
74 name: 'cut',
75 options: {
76 start: 2
77 }
78 }
79 ])
80
81 for (const server of servers) {
82 await checkDuration(server, 3)
83
84 const video = await server.videos.get({ id: videoUUID })
85 expect(new Date(video.publishedAt)).to.be.below(beforeTasks)
86 }
87 })
88
89 it('Should cut the end of the video', async function () {
90 this.timeout(120_000)
91 await renewVideo()
92
93 await createTasks([
94 {
95 name: 'cut',
96 options: {
97 end: 2
98 }
99 }
100 ])
101
102 for (const server of servers) {
103 await checkDuration(server, 2)
104 }
105 })
106
107 it('Should cut start/end of the video', async function () {
108 this.timeout(120_000)
109 await renewVideo('video_short1.webm') // 10 seconds video duration
110
111 await createTasks([
112 {
113 name: 'cut',
114 options: {
115 start: 2,
116 end: 6
117 }
118 }
119 ])
120
121 for (const server of servers) {
122 await checkDuration(server, 4)
123 }
124 })
125 })
126
127 describe('Intro/Outro', function () {
128
129 it('Should add an intro', async function () {
130 this.timeout(120_000)
131 await renewVideo()
132
133 await createTasks([
134 {
135 name: 'add-intro',
136 options: {
137 file: 'video_short.webm'
138 }
139 }
140 ])
141
142 for (const server of servers) {
143 await checkDuration(server, 10)
144 }
145 })
146
147 it('Should add an outro', async function () {
148 this.timeout(120_000)
149 await renewVideo()
150
151 await createTasks([
152 {
153 name: 'add-outro',
154 options: {
155 file: 'video_very_short_240p.mp4'
156 }
157 }
158 ])
159
160 for (const server of servers) {
161 await checkDuration(server, 7)
162 }
163 })
164
165 it('Should add an intro/outro', async function () {
166 this.timeout(120_000)
167 await renewVideo()
168
169 await createTasks([
170 {
171 name: 'add-intro',
172 options: {
173 file: 'video_very_short_240p.mp4'
174 }
175 },
176 {
177 name: 'add-outro',
178 options: {
179 // Different frame rate
180 file: 'video_short2.webm'
181 }
182 }
183 ])
184
185 for (const server of servers) {
186 await checkDuration(server, 12)
187 }
188 })
189
190 it('Should add an intro to a video without audio', async function () {
191 this.timeout(120_000)
192 await renewVideo('video_short_no_audio.mp4')
193
194 await createTasks([
195 {
196 name: 'add-intro',
197 options: {
198 file: 'video_very_short_240p.mp4'
199 }
200 }
201 ])
202
203 for (const server of servers) {
204 await checkDuration(server, 7)
205 }
206 })
207
208 it('Should add an outro without audio to a video with audio', async function () {
209 this.timeout(120_000)
210 await renewVideo()
211
212 await createTasks([
213 {
214 name: 'add-outro',
215 options: {
216 file: 'video_short_no_audio.mp4'
217 }
218 }
219 ])
220
221 for (const server of servers) {
222 await checkDuration(server, 10)
223 }
224 })
225
226 it('Should add an outro without audio to a video with audio', async function () {
227 this.timeout(120_000)
228 await renewVideo('video_short_no_audio.mp4')
229
230 await createTasks([
231 {
232 name: 'add-outro',
233 options: {
234 file: 'video_short_no_audio.mp4'
235 }
236 }
237 ])
238
239 for (const server of servers) {
240 await checkDuration(server, 10)
241 }
242 })
243 })
244
245 describe('Watermark', function () {
246
247 it('Should add a watermark to the video', async function () {
248 this.timeout(120_000)
249 await renewVideo()
250
251 const video = await servers[0].videos.get({ id: videoUUID })
252 const oldFileUrls = getAllFiles(video).map(f => f.fileUrl)
253
254 await createTasks([
255 {
256 name: 'add-watermark',
257 options: {
258 file: 'thumbnail.png'
259 }
260 }
261 ])
262
263 for (const server of servers) {
264 const video = await server.videos.get({ id: videoUUID })
265 const fileUrls = getAllFiles(video).map(f => f.fileUrl)
266
267 for (const oldUrl of oldFileUrls) {
268 expect(fileUrls).to.not.include(oldUrl)
269 }
270 }
271 })
272 })
273
274 describe('Complex tasks', function () {
275 it('Should run a complex task', async function () {
276 this.timeout(240_000)
277 await renewVideo()
278
279 await createTasks(VideoStudioCommand.getComplexTask())
280
281 for (const server of servers) {
282 await checkDuration(server, 9)
283 }
284 })
285 })
286
287 describe('HLS only video edition', function () {
288
289 before(async function () {
290 // Disable webtorrent
291 await servers[0].config.updateExistingSubConfig({
292 newConfig: {
293 transcoding: {
294 webtorrent: {
295 enabled: false
296 }
297 }
298 }
299 })
300 })
301
302 it('Should run a complex task on HLS only video', async function () {
303 this.timeout(240_000)
304 await renewVideo()
305
306 await createTasks(VideoStudioCommand.getComplexTask())
307
308 for (const server of servers) {
309 const video = await server.videos.get({ id: videoUUID })
310 expect(video.files).to.have.lengthOf(0)
311
312 await checkDuration(server, 9)
313 }
314 })
315 })
316
317 describe('Object storage video edition', function () {
318 if (areMockObjectStorageTestsDisabled()) return
319
320 before(async function () {
321 await ObjectStorageCommand.prepareDefaultMockBuckets()
322
323 await servers[0].kill()
324 await servers[0].run(ObjectStorageCommand.getDefaultMockConfig())
325
326 await servers[0].config.enableMinimumTranscoding()
327 })
328
329 it('Should run a complex task on a video in object storage', async function () {
330 this.timeout(240_000)
331 await renewVideo()
332
333 const video = await servers[0].videos.get({ id: videoUUID })
334 const oldFileUrls = getAllFiles(video).map(f => f.fileUrl)
335
336 await createTasks(VideoStudioCommand.getComplexTask())
337
338 for (const server of servers) {
339 const video = await server.videos.get({ id: videoUUID })
340 const files = getAllFiles(video)
341
342 for (const f of files) {
343 expect(oldFileUrls).to.not.include(f.fileUrl)
344 }
345
346 for (const webtorrentFile of video.files) {
347 expectStartWith(webtorrentFile.fileUrl, ObjectStorageCommand.getMockWebTorrentBaseUrl())
348 }
349
350 for (const hlsFile of video.streamingPlaylists[0].files) {
351 expectStartWith(hlsFile.fileUrl, ObjectStorageCommand.getMockPlaylistBaseUrl())
352 }
353
354 await checkDuration(server, 9)
355 }
356 })
357 })
358
359 describe('Server restart', function () {
360
361 it('Should still be able to run video edition after a server restart', async function () {
362 this.timeout(240_000)
363
364 await renewVideo()
365 await servers[0].videoStudio.createEditionTasks({ videoId: videoUUID, tasks: VideoStudioCommand.getComplexTask() })
366
367 await servers[0].kill()
368 await servers[0].run()
369
370 await waitJobs(servers)
371
372 for (const server of servers) {
373 await checkDuration(server, 9)
374 }
375 })
376
377 it('Should have an empty persistent tmp directory', async function () {
378 await checkPersistentTmpIsEmpty(servers[0])
379 })
380 })
381
382 after(async function () {
383 await cleanupTests(servers)
384 })
385 })