aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-02-11 10:51:33 +0100
committerChocobozzz <chocobozzz@cpy.re>2022-02-28 10:42:19 +0100
commitc729caf6cc34630877a0e5a1bda1719384cd0c8a (patch)
tree1d2e13722e518c73d2c9e6f0969615e29d51cf8c /server/tests/api
parenta24bf4dc659cebb65d887862bf21d7a35e9ec791 (diff)
downloadPeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.gz
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.zst
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.zip
Add basic video editor support
Diffstat (limited to 'server/tests/api')
-rw-r--r--server/tests/api/activitypub/refresher.ts6
-rw-r--r--server/tests/api/check-params/config.ts3
-rw-r--r--server/tests/api/check-params/index.ts1
-rw-r--r--server/tests/api/check-params/video-editor.ts385
-rw-r--r--server/tests/api/live/live.ts4
-rw-r--r--server/tests/api/search/search-channels.ts4
-rw-r--r--server/tests/api/search/search-playlists.ts4
-rw-r--r--server/tests/api/server/config.ts7
-rw-r--r--server/tests/api/server/stats.ts8
-rw-r--r--server/tests/api/videos/audio-only.ts7
-rw-r--r--server/tests/api/videos/index.ts1
-rw-r--r--server/tests/api/videos/video-editor.ts368
-rw-r--r--server/tests/api/videos/video-playlist-thumbnails.ts6
-rw-r--r--server/tests/api/videos/video-playlists.ts6
-rw-r--r--server/tests/api/videos/video-transcoder.ts39
15 files changed, 814 insertions, 35 deletions
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts
index 71e1c40ba..bb81d4565 100644
--- a/server/tests/api/activitypub/refresher.ts
+++ b/server/tests/api/activitypub/refresher.ts
@@ -25,12 +25,16 @@ describe('Test AP refresher', function () {
25 before(async function () { 25 before(async function () {
26 this.timeout(60000) 26 this.timeout(60000)
27 27
28 servers = await createMultipleServers(2, { transcoding: { enabled: false } }) 28 servers = await createMultipleServers(2)
29 29
30 // Get the access tokens 30 // Get the access tokens
31 await setAccessTokensToServers(servers) 31 await setAccessTokensToServers(servers)
32 await setDefaultVideoChannel(servers) 32 await setDefaultVideoChannel(servers)
33 33
34 for (const server of servers) {
35 await server.config.disableTranscoding()
36 }
37
34 { 38 {
35 videoUUID1 = (await servers[1].videos.quickUpload({ name: 'video1' })).uuid 39 videoUUID1 = (await servers[1].videos.quickUpload({ name: 'video1' })).uuid
36 videoUUID2 = (await servers[1].videos.quickUpload({ name: 'video2' })).uuid 40 videoUUID2 = (await servers[1].videos.quickUpload({ name: 'video2' })).uuid
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts
index 3cccb612a..ce067a892 100644
--- a/server/tests/api/check-params/config.ts
+++ b/server/tests/api/check-params/config.ts
@@ -145,6 +145,9 @@ describe('Test config API validators', function () {
145 } 145 }
146 } 146 }
147 }, 147 },
148 videoEditor: {
149 enabled: true
150 },
148 import: { 151 import: {
149 videos: { 152 videos: {
150 concurrency: 1, 153 concurrency: 1,
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts
index e052296db..c088b52cd 100644
--- a/server/tests/api/check-params/index.ts
+++ b/server/tests/api/check-params/index.ts
@@ -25,6 +25,7 @@ import './video-blacklist'
25import './video-captions' 25import './video-captions'
26import './video-channels' 26import './video-channels'
27import './video-comments' 27import './video-comments'
28import './video-editor'
28import './video-imports' 29import './video-imports'
29import './video-playlists' 30import './video-playlists'
30import './videos' 31import './videos'
diff --git a/server/tests/api/check-params/video-editor.ts b/server/tests/api/check-params/video-editor.ts
new file mode 100644
index 000000000..db284a3cc
--- /dev/null
+++ b/server/tests/api/check-params/video-editor.ts
@@ -0,0 +1,385 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import 'mocha'
4import { HttpStatusCode, VideoEditorTask } from '@shared/models'
5import {
6 cleanupTests,
7 createSingleServer,
8 PeerTubeServer,
9 setAccessTokensToServers,
10 VideoEditorCommand,
11 waitJobs
12} from '@shared/server-commands'
13
14describe('Test video editor API validator', function () {
15 let server: PeerTubeServer
16 let command: VideoEditorCommand
17 let userAccessToken: string
18 let videoUUID: string
19
20 // ---------------------------------------------------------------
21
22 before(async function () {
23 this.timeout(120_000)
24
25 server = await createSingleServer(1)
26
27 await setAccessTokensToServers([ server ])
28 userAccessToken = await server.users.generateUserAndToken('user1')
29
30 await server.config.enableMinimumTranscoding()
31
32 const { uuid } = await server.videos.quickUpload({ name: 'video' })
33 videoUUID = uuid
34
35 command = server.videoEditor
36
37 await waitJobs([ server ])
38 })
39
40 describe('Task creation', function () {
41
42 describe('Config settings', function () {
43
44 it('Should fail if editor is disabled', async function () {
45 await server.config.updateExistingSubConfig({
46 newConfig: {
47 videoEditor: {
48 enabled: false
49 }
50 }
51 })
52
53 await command.createEditionTasks({
54 videoId: videoUUID,
55 tasks: VideoEditorCommand.getComplexTask(),
56 expectedStatus: HttpStatusCode.BAD_REQUEST_400
57 })
58 })
59
60 it('Should fail to enable editor if transcoding is disabled', async function () {
61 await server.config.updateExistingSubConfig({
62 newConfig: {
63 videoEditor: {
64 enabled: true
65 },
66 transcoding: {
67 enabled: false
68 }
69 },
70 expectedStatus: HttpStatusCode.BAD_REQUEST_400
71 })
72 })
73
74 it('Should succeed to enable video editor', async function () {
75 await server.config.updateExistingSubConfig({
76 newConfig: {
77 videoEditor: {
78 enabled: true
79 },
80 transcoding: {
81 enabled: true
82 }
83 }
84 })
85 })
86 })
87
88 describe('Common tasks', function () {
89
90 it('Should fail without token', async function () {
91 await command.createEditionTasks({
92 token: null,
93 videoId: videoUUID,
94 tasks: VideoEditorCommand.getComplexTask(),
95 expectedStatus: HttpStatusCode.UNAUTHORIZED_401
96 })
97 })
98
99 it('Should fail with another user token', async function () {
100 await command.createEditionTasks({
101 token: userAccessToken,
102 videoId: videoUUID,
103 tasks: VideoEditorCommand.getComplexTask(),
104 expectedStatus: HttpStatusCode.FORBIDDEN_403
105 })
106 })
107
108 it('Should fail with an invalid video', async function () {
109 await command.createEditionTasks({
110 videoId: 'tintin',
111 tasks: VideoEditorCommand.getComplexTask(),
112 expectedStatus: HttpStatusCode.BAD_REQUEST_400
113 })
114 })
115
116 it('Should fail with an unknown video', async function () {
117 await command.createEditionTasks({
118 videoId: 42,
119 tasks: VideoEditorCommand.getComplexTask(),
120 expectedStatus: HttpStatusCode.NOT_FOUND_404
121 })
122 })
123
124 it('Should fail with an already in transcoding state video', async function () {
125 await server.jobs.pauseJobQueue()
126
127 const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })
128
129 await command.createEditionTasks({
130 videoId: uuid,
131 tasks: VideoEditorCommand.getComplexTask(),
132 expectedStatus: HttpStatusCode.CONFLICT_409
133 })
134
135 await server.jobs.resumeJobQueue()
136 })
137
138 it('Should fail with a bad complex task', async function () {
139 await command.createEditionTasks({
140 videoId: videoUUID,
141 tasks: [
142 {
143 name: 'cut',
144 options: {
145 start: 1,
146 end: 2
147 }
148 },
149 {
150 name: 'hadock',
151 options: {
152 start: 1,
153 end: 2
154 }
155 }
156 ] as any,
157 expectedStatus: HttpStatusCode.BAD_REQUEST_400
158 })
159 })
160
161 it('Should fail without task', async function () {
162 await command.createEditionTasks({
163 videoId: videoUUID,
164 tasks: [],
165 expectedStatus: HttpStatusCode.BAD_REQUEST_400
166 })
167 })
168
169 it('Should fail with too many tasks', async function () {
170 const tasks: VideoEditorTask[] = []
171
172 for (let i = 0; i < 110; i++) {
173 tasks.push({
174 name: 'cut',
175 options: {
176 start: 1
177 }
178 })
179 }
180
181 await command.createEditionTasks({
182 videoId: videoUUID,
183 tasks,
184 expectedStatus: HttpStatusCode.BAD_REQUEST_400
185 })
186 })
187
188 it('Should succeed with correct parameters', async function () {
189 await server.jobs.pauseJobQueue()
190
191 await command.createEditionTasks({
192 videoId: videoUUID,
193 tasks: VideoEditorCommand.getComplexTask(),
194 expectedStatus: HttpStatusCode.NO_CONTENT_204
195 })
196 })
197
198 it('Should fail with a video that is already waiting for edition', async function () {
199 this.timeout(120000)
200
201 await command.createEditionTasks({
202 videoId: videoUUID,
203 tasks: VideoEditorCommand.getComplexTask(),
204 expectedStatus: HttpStatusCode.CONFLICT_409
205 })
206
207 await server.jobs.resumeJobQueue()
208
209 await waitJobs([ server ])
210 })
211 })
212
213 describe('Cut task', function () {
214
215 async function cut (start: number, end: number, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
216 await command.createEditionTasks({
217 videoId: videoUUID,
218 tasks: [
219 {
220 name: 'cut',
221 options: {
222 start,
223 end
224 }
225 }
226 ],
227 expectedStatus
228 })
229 }
230
231 it('Should fail with bad start/end', async function () {
232 const invalid = [
233 'tintin',
234 -1,
235 undefined
236 ]
237
238 for (const value of invalid) {
239 await cut(value as any, undefined)
240 await cut(undefined, value as any)
241 }
242 })
243
244 it('Should fail with the same start/end', async function () {
245 await cut(2, 2)
246 })
247
248 it('Should fail with inconsistents start/end', async function () {
249 await cut(2, 1)
250 })
251
252 it('Should fail without start and end', async function () {
253 await cut(undefined, undefined)
254 })
255
256 it('Should succeed with the correct params', async function () {
257 this.timeout(120000)
258
259 await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
260
261 await waitJobs([ server ])
262 })
263 })
264
265 describe('Watermark task', function () {
266
267 async function addWatermark (file: string, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
268 await command.createEditionTasks({
269 videoId: videoUUID,
270 tasks: [
271 {
272 name: 'add-watermark',
273 options: {
274 file
275 }
276 }
277 ],
278 expectedStatus
279 })
280 }
281
282 it('Should fail without waterkmark', async function () {
283 await addWatermark(undefined)
284 })
285
286 it('Should fail with an invalid watermark', async function () {
287 await addWatermark('video_short.mp4')
288 })
289
290 it('Should succeed with the correct params', async function () {
291 this.timeout(120000)
292
293 await addWatermark('thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
294
295 await waitJobs([ server ])
296 })
297 })
298
299 describe('Intro/Outro task', function () {
300
301 async function addIntroOutro (type: 'add-intro' | 'add-outro', file: string, expectedStatus = HttpStatusCode.BAD_REQUEST_400) {
302 await command.createEditionTasks({
303 videoId: videoUUID,
304 tasks: [
305 {
306 name: type,
307 options: {
308 file
309 }
310 }
311 ],
312 expectedStatus
313 })
314 }
315
316 it('Should fail without file', async function () {
317 await addIntroOutro('add-intro', undefined)
318 await addIntroOutro('add-outro', undefined)
319 })
320
321 it('Should fail with an invalid file', async function () {
322 await addIntroOutro('add-intro', 'thumbnail.jpg')
323 await addIntroOutro('add-outro', 'thumbnail.jpg')
324 })
325
326 it('Should fail with a file that does not contain video stream', async function () {
327 await addIntroOutro('add-intro', 'sample.ogg')
328 await addIntroOutro('add-outro', 'sample.ogg')
329
330 })
331
332 it('Should succeed with the correct params', async function () {
333 this.timeout(120000)
334
335 await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
336 await waitJobs([ server ])
337
338 await addIntroOutro('add-outro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
339 await waitJobs([ server ])
340 })
341
342 it('Should check total quota when creating the task', async function () {
343 this.timeout(120000)
344
345 const user = await server.users.create({ username: 'user_quota_1' })
346 const token = await server.login.getAccessToken('user_quota_1')
347 const { uuid } = await server.videos.quickUpload({ token, name: 'video_quota_1', fixture: 'video_short.mp4' })
348
349 const addIntroOutroByUser = (type: 'add-intro' | 'add-outro', expectedStatus: HttpStatusCode) => {
350 return command.createEditionTasks({
351 token,
352 videoId: uuid,
353 tasks: [
354 {
355 name: type,
356 options: {
357 file: 'video_short.mp4'
358 }
359 }
360 ],
361 expectedStatus
362 })
363 }
364
365 await waitJobs([ server ])
366
367 const { videoQuotaUsed } = await server.users.getMyQuotaUsed({ token })
368 await server.users.update({ userId: user.id, videoQuota: Math.round(videoQuotaUsed * 2.5) })
369
370 // Still valid
371 await addIntroOutroByUser('add-intro', HttpStatusCode.NO_CONTENT_204)
372
373 await waitJobs([ server ])
374
375 // Too much quota
376 await addIntroOutroByUser('add-intro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
377 await addIntroOutroByUser('add-outro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
378 })
379 })
380 })
381
382 after(async function () {
383 await cleanupTests([ server ])
384 })
385})
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index 3f9355d2d..d756a02c1 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -3,7 +3,7 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { basename, join } from 'path' 5import { basename, join } from 'path'
6import { ffprobePromise, getVideoStreamFromFile } from '@server/helpers/ffprobe-utils' 6import { ffprobePromise, getVideoStream } from '@server/helpers/ffmpeg'
7import { checkLiveCleanupAfterSave, checkLiveSegmentHash, checkResolutionsInMasterPlaylist, testImage } from '@server/tests/shared' 7import { checkLiveCleanupAfterSave, checkLiveSegmentHash, checkResolutionsInMasterPlaylist, testImage } from '@server/tests/shared'
8import { wait } from '@shared/core-utils' 8import { wait } from '@shared/core-utils'
9import { 9import {
@@ -562,7 +562,7 @@ describe('Test live', function () {
562 const segmentPath = servers[0].servers.buildDirectory(join('streaming-playlists', 'hls', video.uuid, filename)) 562 const segmentPath = servers[0].servers.buildDirectory(join('streaming-playlists', 'hls', video.uuid, filename))
563 563
564 const probe = await ffprobePromise(segmentPath) 564 const probe = await ffprobePromise(segmentPath)
565 const videoStream = await getVideoStreamFromFile(segmentPath, probe) 565 const videoStream = await getVideoStream(segmentPath, probe)
566 566
567 expect(probe.format.bit_rate).to.be.below(maxBitrateLimits[videoStream.height]) 567 expect(probe.format.bit_rate).to.be.below(maxBitrateLimits[videoStream.height])
568 expect(probe.format.bit_rate).to.be.at.least(minBitrateLimits[videoStream.height]) 568 expect(probe.format.bit_rate).to.be.at.least(minBitrateLimits[videoStream.height])
diff --git a/server/tests/api/search/search-channels.ts b/server/tests/api/search/search-channels.ts
index 0073c71e1..cd4c053d2 100644
--- a/server/tests/api/search/search-channels.ts
+++ b/server/tests/api/search/search-channels.ts
@@ -26,7 +26,7 @@ describe('Test channels search', function () {
26 26
27 const servers = await Promise.all([ 27 const servers = await Promise.all([
28 createSingleServer(1), 28 createSingleServer(1),
29 createSingleServer(2, { transcoding: { enabled: false } }) 29 createSingleServer(2)
30 ]) 30 ])
31 server = servers[0] 31 server = servers[0]
32 remoteServer = servers[1] 32 remoteServer = servers[1]
@@ -35,6 +35,8 @@ describe('Test channels search', function () {
35 await setDefaultChannelAvatar(server) 35 await setDefaultChannelAvatar(server)
36 await setDefaultAccountAvatar(server) 36 await setDefaultAccountAvatar(server)
37 37
38 await servers[1].config.disableTranscoding()
39
38 { 40 {
39 await server.users.create({ username: 'user1' }) 41 await server.users.create({ username: 'user1' })
40 const channel = { 42 const channel = {
diff --git a/server/tests/api/search/search-playlists.ts b/server/tests/api/search/search-playlists.ts
index fcf2f2ee2..d9f12d316 100644
--- a/server/tests/api/search/search-playlists.ts
+++ b/server/tests/api/search/search-playlists.ts
@@ -29,7 +29,7 @@ describe('Test playlists search', function () {
29 29
30 const servers = await Promise.all([ 30 const servers = await Promise.all([
31 createSingleServer(1), 31 createSingleServer(1),
32 createSingleServer(2, { transcoding: { enabled: false } }) 32 createSingleServer(2)
33 ]) 33 ])
34 server = servers[0] 34 server = servers[0]
35 remoteServer = servers[1] 35 remoteServer = servers[1]
@@ -39,6 +39,8 @@ describe('Test playlists search', function () {
39 await setDefaultChannelAvatar([ remoteServer, server ]) 39 await setDefaultChannelAvatar([ remoteServer, server ])
40 await setDefaultAccountAvatar([ remoteServer, server ]) 40 await setDefaultAccountAvatar([ remoteServer, server ])
41 41
42 await servers[1].config.disableTranscoding()
43
42 { 44 {
43 const videoId = (await server.videos.upload()).uuid 45 const videoId = (await server.videos.upload()).uuid
44 46
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts
index 2356f701c..565b2953a 100644
--- a/server/tests/api/server/config.ts
+++ b/server/tests/api/server/config.ts
@@ -97,6 +97,8 @@ function checkInitialConfig (server: PeerTubeServer, data: CustomConfig) {
97 expect(data.live.transcoding.resolutions['1440p']).to.be.false 97 expect(data.live.transcoding.resolutions['1440p']).to.be.false
98 expect(data.live.transcoding.resolutions['2160p']).to.be.false 98 expect(data.live.transcoding.resolutions['2160p']).to.be.false
99 99
100 expect(data.videoEditor.enabled).to.be.false
101
100 expect(data.import.videos.concurrency).to.equal(2) 102 expect(data.import.videos.concurrency).to.equal(2)
101 expect(data.import.videos.http.enabled).to.be.true 103 expect(data.import.videos.http.enabled).to.be.true
102 expect(data.import.videos.torrent.enabled).to.be.true 104 expect(data.import.videos.torrent.enabled).to.be.true
@@ -197,6 +199,8 @@ function checkUpdatedConfig (data: CustomConfig) {
197 expect(data.live.transcoding.resolutions['1080p']).to.be.true 199 expect(data.live.transcoding.resolutions['1080p']).to.be.true
198 expect(data.live.transcoding.resolutions['2160p']).to.be.true 200 expect(data.live.transcoding.resolutions['2160p']).to.be.true
199 201
202 expect(data.videoEditor.enabled).to.be.true
203
200 expect(data.import.videos.concurrency).to.equal(4) 204 expect(data.import.videos.concurrency).to.equal(4)
201 expect(data.import.videos.http.enabled).to.be.false 205 expect(data.import.videos.http.enabled).to.be.false
202 expect(data.import.videos.torrent.enabled).to.be.false 206 expect(data.import.videos.torrent.enabled).to.be.false
@@ -341,6 +345,9 @@ const newCustomConfig: CustomConfig = {
341 } 345 }
342 } 346 }
343 }, 347 },
348 videoEditor: {
349 enabled: true
350 },
344 import: { 351 import: {
345 videos: { 352 videos: {
346 concurrency: 4, 353 concurrency: 4,
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts
index f0334532b..2296c0cb9 100644
--- a/server/tests/api/server/stats.ts
+++ b/server/tests/api/server/stats.ts
@@ -230,13 +230,7 @@ describe('Test stats (excluding redundancy)', function () {
230 it('Should have the correct AP stats', async function () { 230 it('Should have the correct AP stats', async function () {
231 this.timeout(60000) 231 this.timeout(60000)
232 232
233 await servers[0].config.updateCustomSubConfig({ 233 await servers[0].config.disableTranscoding()
234 newConfig: {
235 transcoding: {
236 enabled: false
237 }
238 }
239 })
240 234
241 const first = await servers[1].stats.get() 235 const first = await servers[1].stats.get()
242 236
diff --git a/server/tests/api/videos/audio-only.ts b/server/tests/api/videos/audio-only.ts
index e58360ffe..e7e73d382 100644
--- a/server/tests/api/videos/audio-only.ts
+++ b/server/tests/api/videos/audio-only.ts
@@ -2,7 +2,7 @@
2 2
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { getAudioStream, getVideoStreamSize } from '@server/helpers/ffprobe-utils' 5import { getAudioStream, getVideoStreamDimensionsInfo } from '@server/helpers/ffmpeg'
6import { 6import {
7 cleanupTests, 7 cleanupTests,
8 createMultipleServers, 8 createMultipleServers,
@@ -91,9 +91,8 @@ describe('Test audio only video transcoding', function () {
91 expect(audioStream['codec_name']).to.be.equal('aac') 91 expect(audioStream['codec_name']).to.be.equal('aac')
92 expect(audioStream['bit_rate']).to.be.at.most(384 * 8000) 92 expect(audioStream['bit_rate']).to.be.at.most(384 * 8000)
93 93
94 const size = await getVideoStreamSize(path) 94 const size = await getVideoStreamDimensionsInfo(path)
95 expect(size.height).to.equal(0) 95 expect(size).to.not.exist
96 expect(size.width).to.equal(0)
97 } 96 }
98 }) 97 })
99 98
diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts
index bedb9b8b6..72e6ae2b4 100644
--- a/server/tests/api/videos/index.ts
+++ b/server/tests/api/videos/index.ts
@@ -8,6 +8,7 @@ import './video-channels'
8import './video-comments' 8import './video-comments'
9import './video-create-transcoding' 9import './video-create-transcoding'
10import './video-description' 10import './video-description'
11import './video-editor'
11import './video-files' 12import './video-files'
12import './video-hls' 13import './video-hls'
13import './video-imports' 14import './video-imports'
diff --git a/server/tests/api/videos/video-editor.ts b/server/tests/api/videos/video-editor.ts
new file mode 100644
index 000000000..a9b6950cc
--- /dev/null
+++ b/server/tests/api/videos/video-editor.ts
@@ -0,0 +1,368 @@
1import { expect } from 'chai'
2import { expectStartWith, getAllFiles } from '@server/tests/shared'
3import { areObjectStorageTestsDisabled } from '@shared/core-utils'
4import { VideoEditorTask } from '@shared/models'
5import {
6 cleanupTests,
7 createMultipleServers,
8 doubleFollow,
9 ObjectStorageCommand,
10 PeerTubeServer,
11 setAccessTokensToServers,
12 setDefaultVideoChannel,
13 VideoEditorCommand,
14 waitJobs
15} from '@shared/server-commands'
16
17describe('Test video editor', 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: VideoEditorTask[]) {
43 await servers[0].videoEditor.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.updateExistingSubConfig({
60 newConfig: {
61 videoEditor: {
62 enabled: true
63 }
64 }
65 })
66 })
67
68 describe('Cutting', function () {
69
70 it('Should cut the beginning of the video', async function () {
71 this.timeout(120_000)
72
73 await renewVideo()
74 await waitJobs(servers)
75
76 const beforeTasks = new Date()
77
78 await createTasks([
79 {
80 name: 'cut',
81 options: {
82 start: 2
83 }
84 }
85 ])
86
87 for (const server of servers) {
88 await checkDuration(server, 3)
89
90 const video = await server.videos.get({ id: videoUUID })
91 expect(new Date(video.publishedAt)).to.be.below(beforeTasks)
92 }
93 })
94
95 it('Should cut the end of the video', async function () {
96 this.timeout(120_000)
97 await renewVideo()
98
99 await createTasks([
100 {
101 name: 'cut',
102 options: {
103 end: 2
104 }
105 }
106 ])
107
108 for (const server of servers) {
109 await checkDuration(server, 2)
110 }
111 })
112
113 it('Should cut start/end of the video', async function () {
114 this.timeout(120_000)
115 await renewVideo('video_short1.webm') // 10 seconds video duration
116
117 await createTasks([
118 {
119 name: 'cut',
120 options: {
121 start: 2,
122 end: 6
123 }
124 }
125 ])
126
127 for (const server of servers) {
128 await checkDuration(server, 4)
129 }
130 })
131 })
132
133 describe('Intro/Outro', function () {
134
135 it('Should add an intro', async function () {
136 this.timeout(120_000)
137 await renewVideo()
138
139 await createTasks([
140 {
141 name: 'add-intro',
142 options: {
143 file: 'video_short.webm'
144 }
145 }
146 ])
147
148 for (const server of servers) {
149 await checkDuration(server, 10)
150 }
151 })
152
153 it('Should add an outro', async function () {
154 this.timeout(120_000)
155 await renewVideo()
156
157 await createTasks([
158 {
159 name: 'add-outro',
160 options: {
161 file: 'video_very_short_240p.mp4'
162 }
163 }
164 ])
165
166 for (const server of servers) {
167 await checkDuration(server, 7)
168 }
169 })
170
171 it('Should add an intro/outro', async function () {
172 this.timeout(120_000)
173 await renewVideo()
174
175 await createTasks([
176 {
177 name: 'add-intro',
178 options: {
179 file: 'video_very_short_240p.mp4'
180 }
181 },
182 {
183 name: 'add-outro',
184 options: {
185 // Different frame rate
186 file: 'video_short2.webm'
187 }
188 }
189 ])
190
191 for (const server of servers) {
192 await checkDuration(server, 12)
193 }
194 })
195
196 it('Should add an intro to a video without audio', async function () {
197 this.timeout(120_000)
198 await renewVideo('video_short_no_audio.mp4')
199
200 await createTasks([
201 {
202 name: 'add-intro',
203 options: {
204 file: 'video_very_short_240p.mp4'
205 }
206 }
207 ])
208
209 for (const server of servers) {
210 await checkDuration(server, 7)
211 }
212 })
213
214 it('Should add an outro without audio to a video with audio', async function () {
215 this.timeout(120_000)
216 await renewVideo()
217
218 await createTasks([
219 {
220 name: 'add-outro',
221 options: {
222 file: 'video_short_no_audio.mp4'
223 }
224 }
225 ])
226
227 for (const server of servers) {
228 await checkDuration(server, 10)
229 }
230 })
231
232 it('Should add an outro without audio to a video with audio', async function () {
233 this.timeout(120_000)
234 await renewVideo('video_short_no_audio.mp4')
235
236 await createTasks([
237 {
238 name: 'add-outro',
239 options: {
240 file: 'video_short_no_audio.mp4'
241 }
242 }
243 ])
244
245 for (const server of servers) {
246 await checkDuration(server, 10)
247 }
248 })
249 })
250
251 describe('Watermark', function () {
252
253 it('Should add a watermark to the video', async function () {
254 this.timeout(120_000)
255 await renewVideo()
256
257 const video = await servers[0].videos.get({ id: videoUUID })
258 const oldFileUrls = getAllFiles(video).map(f => f.fileUrl)
259
260 await createTasks([
261 {
262 name: 'add-watermark',
263 options: {
264 file: 'thumbnail.png'
265 }
266 }
267 ])
268
269 for (const server of servers) {
270 const video = await server.videos.get({ id: videoUUID })
271 const fileUrls = getAllFiles(video).map(f => f.fileUrl)
272
273 for (const oldUrl of oldFileUrls) {
274 expect(fileUrls).to.not.include(oldUrl)
275 }
276 }
277 })
278 })
279
280 describe('Complex tasks', function () {
281 it('Should run a complex task', async function () {
282 this.timeout(240_000)
283 await renewVideo()
284
285 await createTasks(VideoEditorCommand.getComplexTask())
286
287 for (const server of servers) {
288 await checkDuration(server, 9)
289 }
290 })
291 })
292
293 describe('HLS only video edition', function () {
294
295 before(async function () {
296 // Disable webtorrent
297 await servers[0].config.updateExistingSubConfig({
298 newConfig: {
299 transcoding: {
300 webtorrent: {
301 enabled: false
302 }
303 }
304 }
305 })
306 })
307
308 it('Should run a complex task on HLS only video', async function () {
309 this.timeout(240_000)
310 await renewVideo()
311
312 await createTasks(VideoEditorCommand.getComplexTask())
313
314 for (const server of servers) {
315 const video = await server.videos.get({ id: videoUUID })
316 expect(video.files).to.have.lengthOf(0)
317
318 await checkDuration(server, 9)
319 }
320 })
321 })
322
323 describe('Object storage video edition', function () {
324 if (areObjectStorageTestsDisabled()) return
325
326 before(async function () {
327 await ObjectStorageCommand.prepareDefaultBuckets()
328
329 await servers[0].kill()
330 await servers[0].run(ObjectStorageCommand.getDefaultConfig())
331
332 await servers[0].config.enableMinimumTranscoding()
333 })
334
335 it('Should run a complex task on a video in object storage', async function () {
336 this.timeout(240_000)
337 await renewVideo()
338
339 const video = await servers[0].videos.get({ id: videoUUID })
340 const oldFileUrls = getAllFiles(video).map(f => f.fileUrl)
341
342 await createTasks(VideoEditorCommand.getComplexTask())
343
344 for (const server of servers) {
345 const video = await server.videos.get({ id: videoUUID })
346 const files = getAllFiles(video)
347
348 for (const f of files) {
349 expect(oldFileUrls).to.not.include(f.fileUrl)
350 }
351
352 for (const webtorrentFile of video.files) {
353 expectStartWith(webtorrentFile.fileUrl, ObjectStorageCommand.getWebTorrentBaseUrl())
354 }
355
356 for (const hlsFile of video.streamingPlaylists[0].files) {
357 expectStartWith(hlsFile.fileUrl, ObjectStorageCommand.getPlaylistBaseUrl())
358 }
359
360 await checkDuration(server, 9)
361 }
362 })
363 })
364
365 after(async function () {
366 await cleanupTests(servers)
367 })
368})
diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts
index 5fdb0fc03..3944dc344 100644
--- a/server/tests/api/videos/video-playlist-thumbnails.ts
+++ b/server/tests/api/videos/video-playlist-thumbnails.ts
@@ -45,12 +45,16 @@ describe('Playlist thumbnail', function () {
45 before(async function () { 45 before(async function () {
46 this.timeout(120000) 46 this.timeout(120000)
47 47
48 servers = await createMultipleServers(2, { transcoding: { enabled: false } }) 48 servers = await createMultipleServers(2)
49 49
50 // Get the access tokens 50 // Get the access tokens
51 await setAccessTokensToServers(servers) 51 await setAccessTokensToServers(servers)
52 await setDefaultVideoChannel(servers) 52 await setDefaultVideoChannel(servers)
53 53
54 for (const server of servers) {
55 await server.config.disableTranscoding()
56 }
57
54 // Server 1 and server 2 follow each other 58 // Server 1 and server 2 follow each other
55 await doubleFollow(servers[0], servers[1]) 59 await doubleFollow(servers[0], servers[1])
56 60
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts
index 1e8dbef02..c33a63df0 100644
--- a/server/tests/api/videos/video-playlists.ts
+++ b/server/tests/api/videos/video-playlists.ts
@@ -75,13 +75,17 @@ describe('Test video playlists', function () {
75 before(async function () { 75 before(async function () {
76 this.timeout(120000) 76 this.timeout(120000)
77 77
78 servers = await createMultipleServers(3, { transcoding: { enabled: false } }) 78 servers = await createMultipleServers(3)
79 79
80 // Get the access tokens 80 // Get the access tokens
81 await setAccessTokensToServers(servers) 81 await setAccessTokensToServers(servers)
82 await setDefaultVideoChannel(servers) 82 await setDefaultVideoChannel(servers)
83 await setDefaultAccountAvatar(servers) 83 await setDefaultAccountAvatar(servers)
84 84
85 for (const server of servers) {
86 await server.config.disableTranscoding()
87 }
88
85 // Server 1 and server 2 follow each other 89 // Server 1 and server 2 follow each other
86 await doubleFollow(servers[0], servers[1]) 90 await doubleFollow(servers[0], servers[1])
87 // Server 1 and server 3 follow each other 91 // Server 1 and server 3 follow each other
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts
index d24a8f4e1..245c4c012 100644
--- a/server/tests/api/videos/video-transcoder.ts
+++ b/server/tests/api/videos/video-transcoder.ts
@@ -3,10 +3,17 @@
3import 'mocha' 3import 'mocha'
4import * as chai from 'chai' 4import * as chai from 'chai'
5import { omit } from 'lodash' 5import { omit } from 'lodash'
6import { canDoQuickTranscode } from '@server/helpers/ffprobe-utils' 6import { canDoQuickTranscode } from '@server/helpers/ffmpeg'
7import { generateHighBitrateVideo, generateVideoWithFramerate } from '@server/tests/shared' 7import { generateHighBitrateVideo, generateVideoWithFramerate, getAllFiles } from '@server/tests/shared'
8import { buildAbsoluteFixturePath, getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils' 8import { buildAbsoluteFixturePath, getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils'
9import { getAudioStream, getMetadataFromFile, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '@shared/extra-utils' 9import {
10 getAudioStream,
11 buildFileMetadata,
12 getVideoStreamBitrate,
13 getVideoStreamFPS,
14 getVideoStreamDimensionsInfo,
15 hasAudioStream
16} from '@shared/extra-utils'
10import { HttpStatusCode, VideoState } from '@shared/models' 17import { HttpStatusCode, VideoState } from '@shared/models'
11import { 18import {
12 cleanupTests, 19 cleanupTests,
@@ -287,8 +294,7 @@ describe('Test video transcoding', function () {
287 const file = videoDetails.files.find(f => f.resolution.id === 240) 294 const file = videoDetails.files.find(f => f.resolution.id === 240)
288 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 295 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
289 296
290 const probe = await getAudioStream(path) 297 expect(await hasAudioStream(path)).to.be.false
291 expect(probe).to.not.have.property('audioStream')
292 } 298 }
293 }) 299 })
294 300
@@ -478,14 +484,14 @@ describe('Test video transcoding', function () {
478 for (const resolution of [ 144, 240, 360, 480 ]) { 484 for (const resolution of [ 144, 240, 360, 480 ]) {
479 const file = videoDetails.files.find(f => f.resolution.id === resolution) 485 const file = videoDetails.files.find(f => f.resolution.id === resolution)
480 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 486 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
481 const fps = await getVideoFileFPS(path) 487 const fps = await getVideoStreamFPS(path)
482 488
483 expect(fps).to.be.below(31) 489 expect(fps).to.be.below(31)
484 } 490 }
485 491
486 const file = videoDetails.files.find(f => f.resolution.id === 720) 492 const file = videoDetails.files.find(f => f.resolution.id === 720)
487 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 493 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
488 const fps = await getVideoFileFPS(path) 494 const fps = await getVideoStreamFPS(path)
489 495
490 expect(fps).to.be.above(58).and.below(62) 496 expect(fps).to.be.above(58).and.below(62)
491 } 497 }
@@ -499,7 +505,7 @@ describe('Test video transcoding', function () {
499 { 505 {
500 tempFixturePath = await generateVideoWithFramerate(59) 506 tempFixturePath = await generateVideoWithFramerate(59)
501 507
502 const fps = await getVideoFileFPS(tempFixturePath) 508 const fps = await getVideoStreamFPS(tempFixturePath)
503 expect(fps).to.be.equal(59) 509 expect(fps).to.be.equal(59)
504 } 510 }
505 511
@@ -522,14 +528,14 @@ describe('Test video transcoding', function () {
522 { 528 {
523 const file = video.files.find(f => f.resolution.id === 240) 529 const file = video.files.find(f => f.resolution.id === 240)
524 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 530 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
525 const fps = await getVideoFileFPS(path) 531 const fps = await getVideoStreamFPS(path)
526 expect(fps).to.be.equal(25) 532 expect(fps).to.be.equal(25)
527 } 533 }
528 534
529 { 535 {
530 const file = video.files.find(f => f.resolution.id === 720) 536 const file = video.files.find(f => f.resolution.id === 720)
531 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 537 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
532 const fps = await getVideoFileFPS(path) 538 const fps = await getVideoStreamFPS(path)
533 expect(fps).to.be.equal(59) 539 expect(fps).to.be.equal(59)
534 } 540 }
535 } 541 }
@@ -563,9 +569,9 @@ describe('Test video transcoding', function () {
563 const file = video.files.find(f => f.resolution.id === resolution) 569 const file = video.files.find(f => f.resolution.id === resolution)
564 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 570 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
565 571
566 const bitrate = await getVideoFileBitrate(path) 572 const bitrate = await getVideoStreamBitrate(path)
567 const fps = await getVideoFileFPS(path) 573 const fps = await getVideoStreamFPS(path)
568 const dataResolution = await getVideoFileResolution(path) 574 const dataResolution = await getVideoStreamDimensionsInfo(path)
569 575
570 expect(resolution).to.equal(resolution) 576 expect(resolution).to.equal(resolution)
571 577
@@ -613,7 +619,7 @@ describe('Test video transcoding', function () {
613 const file = video.files.find(f => f.resolution.id === r) 619 const file = video.files.find(f => f.resolution.id === r)
614 620
615 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 621 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
616 const bitrate = await getVideoFileBitrate(path) 622 const bitrate = await getVideoStreamBitrate(path)
617 623
618 const inputBitrate = 60_000 624 const inputBitrate = 60_000
619 const limit = getMinLimitBitrate({ fps: 10, ratio: 1, resolution: r }) 625 const limit = getMinLimitBitrate({ fps: 10, ratio: 1, resolution: r })
@@ -637,7 +643,7 @@ describe('Test video transcoding', function () {
637 const video = await servers[1].videos.get({ id: videoUUID }) 643 const video = await servers[1].videos.get({ id: videoUUID })
638 const file = video.files.find(f => f.resolution.id === 240) 644 const file = video.files.find(f => f.resolution.id === 240)
639 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl) 645 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
640 const metadata = await getMetadataFromFile(path) 646 const metadata = await buildFileMetadata(path)
641 647
642 // expected format properties 648 // expected format properties
643 for (const p of [ 649 for (const p of [
@@ -668,8 +674,7 @@ describe('Test video transcoding', function () {
668 for (const server of servers) { 674 for (const server of servers) {
669 const videoDetails = await server.videos.get({ id: videoUUID }) 675 const videoDetails = await server.videos.get({ id: videoUUID })
670 676
671 const videoFiles = videoDetails.files 677 const videoFiles = getAllFiles(videoDetails)
672 .concat(videoDetails.streamingPlaylists[0].files)
673 expect(videoFiles).to.have.lengthOf(10) 678 expect(videoFiles).to.have.lengthOf(10)
674 679
675 for (const file of videoFiles) { 680 for (const file of videoFiles) {