aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/tests/api/videos
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/videos
parenta24bf4dc659cebb65d887862bf21d7a35e9ec791 (diff)
downloadPeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.gz
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.tar.zst
PeerTube-c729caf6cc34630877a0e5a1bda1719384cd0c8a.zip
Add basic video editor support
Diffstat (limited to 'server/tests/api/videos')
-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
6 files changed, 404 insertions, 23 deletions
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) {