]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/videos/video-transcoder.ts
Introduce server commands
[github/Chocobozzz/PeerTube.git] / server / tests / api / videos / video-transcoder.ts
CommitLineData
a1587156 1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
0e1dc3e7 2
a7ba16b6 3import 'mocha'
daf6e480
C
4import * as chai from 'chai'
5import { FfprobeData } from 'fluent-ffmpeg'
7160878c 6import { omit } from 'lodash'
daf6e480 7import { join } from 'path'
daf6e480 8import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
6939cbac 9import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
0e1dc3e7 10import {
7243f84d
C
11 buildAbsoluteFixturePath,
12 cleanupTests,
2186386c
C
13 doubleFollow,
14 flushAndRunMultipleServers,
d175a6f7 15 generateHighBitrateVideo,
837666fe 16 generateVideoWithFramerate,
2186386c
C
17 getMyVideos,
18 getVideo,
8319d6ae 19 getVideoFileMetadataUrl,
2186386c 20 getVideosList,
b345a804 21 makeGetRequest,
2186386c
C
22 ServerInfo,
23 setAccessTokensToServers,
daf6e480
C
24 uploadVideo,
25 uploadVideoAndGetId,
1600235a 26 waitJobs,
d175a6f7 27 webtorrentAdd
94565d52 28} from '../../../../shared/extra-utils'
daf6e480
C
29import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
30import {
31 canDoQuickTranscode,
32 getAudioStream,
33 getMetadataFromFile,
34 getVideoFileBitrate,
35 getVideoFileFPS,
36 getVideoFileResolution
37} from '../../../helpers/ffprobe-utils'
a7ba16b6
C
38
39const expect = chai.expect
0e1dc3e7 40
40930fda 41function updateConfigForTranscoding (server: ServerInfo) {
65e6e260
C
42 return server.configCommand.updateCustomSubConfig({
43 newConfig: {
44 transcoding: {
45 enabled: true,
46 allowAdditionalExtensions: true,
47 allowAudioFiles: true,
48 hls: { enabled: true },
49 webtorrent: { enabled: true },
50 resolutions: {
51 '0p': false,
52 '240p': true,
53 '360p': true,
54 '480p': true,
55 '720p': true,
56 '1080p': true,
57 '1440p': true,
58 '2160p': true
59 }
40930fda
C
60 }
61 }
62 })
63}
64
0e1dc3e7
C
65describe('Test video transcoding', function () {
66 let servers: ServerInfo[] = []
6939cbac 67 let video4k: string
0e1dc3e7
C
68
69 before(async function () {
454c20fa 70 this.timeout(30_000)
0e1dc3e7
C
71
72 // Run servers
73 servers = await flushAndRunMultipleServers(2)
74
75 await setAccessTokensToServers(servers)
b2977eec
C
76
77 await doubleFollow(servers[0], servers[1])
40930fda
C
78
79 await updateConfigForTranscoding(servers[1])
0e1dc3e7
C
80 })
81
40930fda 82 describe('Basic transcoding (or not)', function () {
0e1dc3e7 83
40930fda
C
84 it('Should not transcode video on server 1', async function () {
85 this.timeout(60_000)
0e1dc3e7 86
40930fda
C
87 const videoAttributes = {
88 name: 'my super name for server 1',
89 description: 'my super description for server 1',
90 fixture: 'video_short.webm'
91 }
92 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
0e1dc3e7 93
40930fda 94 await waitJobs(servers)
40298b02 95
40930fda
C
96 for (const server of servers) {
97 const res = await getVideosList(server.url)
98 const video = res.body.data[0]
5f04dd2f 99
40930fda
C
100 const res2 = await getVideo(server.url, video.id)
101 const videoDetails = res2.body
102 expect(videoDetails.files).to.have.lengthOf(1)
0e1dc3e7 103
40930fda
C
104 const magnetUri = videoDetails.files[0].magnetUri
105 expect(magnetUri).to.match(/\.webm/)
0e1dc3e7 106
40930fda
C
107 const torrent = await webtorrentAdd(magnetUri, true)
108 expect(torrent.files).to.be.an('array')
109 expect(torrent.files.length).to.equal(1)
110 expect(torrent.files[0].path).match(/\.webm$/)
111 }
112 })
0e1dc3e7 113
40930fda
C
114 it('Should transcode video on server 2', async function () {
115 this.timeout(120_000)
116
117 const videoAttributes = {
118 name: 'my super name for server 2',
119 description: 'my super description for server 2',
120 fixture: 'video_short.webm'
121 }
122 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
123
124 await waitJobs(servers)
125
126 for (const server of servers) {
127 const res = await getVideosList(server.url)
0e1dc3e7 128
40930fda
C
129 const video = res.body.data.find(v => v.name === videoAttributes.name)
130 const res2 = await getVideo(server.url, video.id)
131 const videoDetails = res2.body
0e1dc3e7 132
40930fda 133 expect(videoDetails.files).to.have.lengthOf(4)
0e1dc3e7 134
40930fda
C
135 const magnetUri = videoDetails.files[0].magnetUri
136 expect(magnetUri).to.match(/\.mp4/)
5f04dd2f 137
40930fda
C
138 const torrent = await webtorrentAdd(magnetUri, true)
139 expect(torrent.files).to.be.an('array')
140 expect(torrent.files.length).to.equal(1)
141 expect(torrent.files[0].path).match(/\.mp4$/)
142 }
143 })
40298b02 144
40930fda
C
145 it('Should wait for transcoding before publishing the video', async function () {
146 this.timeout(160_000)
0e1dc3e7 147
40930fda
C
148 {
149 // Upload the video, but wait transcoding
150 const videoAttributes = {
151 name: 'waiting video',
152 fixture: 'video_short1.webm',
153 waitTranscoding: true
154 }
155 const resVideo = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
156 const videoId = resVideo.body.video.uuid
157
158 // Should be in transcode state
159 const { body } = await getVideo(servers[1].url, videoId)
160 expect(body.name).to.equal('waiting video')
161 expect(body.state.id).to.equal(VideoState.TO_TRANSCODE)
162 expect(body.state.label).to.equal('To transcode')
163 expect(body.waitTranscoding).to.be.true
164
165 // Should have my video
166 const resMyVideos = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 10)
167 const videoToFindInMine = resMyVideos.body.data.find(v => v.name === videoAttributes.name)
168 expect(videoToFindInMine).not.to.be.undefined
169 expect(videoToFindInMine.state.id).to.equal(VideoState.TO_TRANSCODE)
170 expect(videoToFindInMine.state.label).to.equal('To transcode')
171 expect(videoToFindInMine.waitTranscoding).to.be.true
172
173 // Should not list this video
174 const resVideos = await getVideosList(servers[1].url)
175 const videoToFindInList = resVideos.body.data.find(v => v.name === videoAttributes.name)
176 expect(videoToFindInList).to.be.undefined
177
178 // Server 1 should not have the video yet
179 await getVideo(servers[0].url, videoId, HttpStatusCode.NOT_FOUND_404)
180 }
0e1dc3e7 181
40930fda 182 await waitJobs(servers)
7160878c 183
40930fda
C
184 for (const server of servers) {
185 const res = await getVideosList(server.url)
186 const videoToFind = res.body.data.find(v => v.name === 'waiting video')
187 expect(videoToFind).not.to.be.undefined
7160878c 188
40930fda
C
189 const res2 = await getVideo(server.url, videoToFind.id)
190 const videoDetails: VideoDetails = res2.body
7160878c 191
40930fda
C
192 expect(videoDetails.state.id).to.equal(VideoState.PUBLISHED)
193 expect(videoDetails.state.label).to.equal('Published')
194 expect(videoDetails.waitTranscoding).to.be.true
195 }
196 })
7160878c 197
40930fda
C
198 it('Should accept and transcode additional extensions', async function () {
199 this.timeout(300_000)
7160878c 200
40930fda 201 let tempFixturePath: string
7160878c 202
40930fda
C
203 {
204 tempFixturePath = await generateHighBitrateVideo()
7160878c 205
40930fda
C
206 const bitrate = await getVideoFileBitrate(tempFixturePath)
207 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
b2977eec 208 }
7160878c 209
40930fda
C
210 for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
211 const videoAttributes = {
212 name: fixture,
213 fixture
214 }
7160878c 215
40930fda 216 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
7160878c 217
40930fda 218 await waitJobs(servers)
7160878c 219
40930fda
C
220 for (const server of servers) {
221 const res = await getVideosList(server.url)
7160878c 222
40930fda
C
223 const video = res.body.data.find(v => v.name === videoAttributes.name)
224 const res2 = await getVideo(server.url, video.id)
225 const videoDetails = res2.body
7160878c 226
40930fda 227 expect(videoDetails.files).to.have.lengthOf(4)
7160878c 228
40930fda
C
229 const magnetUri = videoDetails.files[0].magnetUri
230 expect(magnetUri).to.contain('.mp4')
231 }
232 }
233 })
7160878c 234
40930fda
C
235 it('Should transcode a 4k video', async function () {
236 this.timeout(200_000)
7160878c 237
40930fda
C
238 const videoAttributes = {
239 name: '4k video',
240 fixture: 'video_short_4k.mp4'
241 }
7160878c 242
40930fda
C
243 const resUpload = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
244 video4k = resUpload.body.video.uuid
b2977eec 245
40930fda 246 await waitJobs(servers)
b2977eec 247
40930fda 248 const resolutions = [ 240, 360, 480, 720, 1080, 1440, 2160 ]
ca5c612b 249
40930fda
C
250 for (const server of servers) {
251 const res = await getVideo(server.url, video4k)
252 const videoDetails: VideoDetails = res.body
ca5c612b 253
40930fda 254 expect(videoDetails.files).to.have.lengthOf(resolutions.length)
ca5c612b 255
40930fda
C
256 for (const r of resolutions) {
257 expect(videoDetails.files.find(f => f.resolution.id === r)).to.not.be.undefined
258 expect(videoDetails.streamingPlaylists[0].files.find(f => f.resolution.id === r)).to.not.be.undefined
259 }
b2977eec 260 }
40930fda 261 })
7160878c
RK
262 })
263
40930fda 264 describe('Audio transcoding', function () {
73c69591 265
40930fda
C
266 it('Should transcode high bit rate mp3 to proper bit rate', async function () {
267 this.timeout(60_000)
73c69591 268
40930fda
C
269 const videoAttributes = {
270 name: 'mp3_256k',
271 fixture: 'video_short_mp3_256k.mp4'
272 }
273 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
73c69591 274
40930fda 275 await waitJobs(servers)
73c69591 276
40930fda
C
277 for (const server of servers) {
278 const res = await getVideosList(server.url)
73c69591 279
40930fda
C
280 const video = res.body.data.find(v => v.name === videoAttributes.name)
281 const res2 = await getVideo(server.url, video.id)
282 const videoDetails: VideoDetails = res2.body
73c69591 283
40930fda 284 expect(videoDetails.files).to.have.lengthOf(4)
73c69591 285
6c5065a0 286 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-240.mp4'))
40930fda
C
287 const probe = await getAudioStream(path)
288
289 if (probe.audioStream) {
290 expect(probe.audioStream['codec_name']).to.be.equal('aac')
291 expect(probe.audioStream['bit_rate']).to.be.at.most(384 * 8000)
292 } else {
293 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath)
294 }
b2977eec 295 }
40930fda 296 })
3a6f351b 297
40930fda
C
298 it('Should transcode video with no audio and have no audio itself', async function () {
299 this.timeout(60_000)
2186386c 300
2186386c 301 const videoAttributes = {
40930fda
C
302 name: 'no_audio',
303 fixture: 'video_short_no_audio.mp4'
304 }
305 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
2186386c 306
40930fda 307 await waitJobs(servers)
2186386c 308
40930fda
C
309 for (const server of servers) {
310 const res = await getVideosList(server.url)
2186386c 311
40930fda
C
312 const video = res.body.data.find(v => v.name === videoAttributes.name)
313 const res2 = await getVideo(server.url, video.id)
314 const videoDetails: VideoDetails = res2.body
2186386c 315
40930fda 316 expect(videoDetails.files).to.have.lengthOf(4)
6c5065a0 317 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-240.mp4'))
40930fda
C
318 const probe = await getAudioStream(path)
319 expect(probe).to.not.have.property('audioStream')
320 }
321 })
2186386c 322
40930fda
C
323 it('Should leave the audio untouched, but properly transcode the video', async function () {
324 this.timeout(60_000)
edb4ffc7 325
40930fda
C
326 const videoAttributes = {
327 name: 'untouched_audio',
328 fixture: 'video_short.mp4'
329 }
330 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
74cd011b 331
40930fda 332 await waitJobs(servers)
edb4ffc7 333
40930fda
C
334 for (const server of servers) {
335 const res = await getVideosList(server.url)
edb4ffc7 336
40930fda
C
337 const video = res.body.data.find(v => v.name === videoAttributes.name)
338 const res2 = await getVideo(server.url, video.id)
339 const videoDetails: VideoDetails = res2.body
edb4ffc7 340
40930fda 341 expect(videoDetails.files).to.have.lengthOf(4)
edb4ffc7 342
40930fda
C
343 const fixturePath = buildAbsoluteFixturePath(videoAttributes.fixture)
344 const fixtureVideoProbe = await getAudioStream(fixturePath)
6c5065a0 345 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-240.mp4'))
edb4ffc7 346
40930fda 347 const videoProbe = await getAudioStream(path)
edb4ffc7 348
40930fda
C
349 if (videoProbe.audioStream && fixtureVideoProbe.audioStream) {
350 const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ]
351 expect(omit(videoProbe.audioStream, toOmit)).to.be.deep.equal(omit(fixtureVideoProbe.audioStream, toOmit))
352 } else {
353 this.fail('Could not retrieve the audio stream on ' + videoProbe.absolutePath)
354 }
355 }
356 })
357 })
edb4ffc7 358
40930fda
C
359 describe('Audio upload', function () {
360
f6d6e7f8 361 function runSuite (mode: 'legacy' | 'resumable') {
362
363 before(async function () {
65e6e260
C
364 await servers[1].configCommand.updateCustomSubConfig({
365 newConfig: {
366 transcoding: {
367 hls: { enabled: true },
368 webtorrent: { enabled: true },
369 resolutions: {
370 '0p': false,
371 '240p': false,
372 '360p': false,
373 '480p': false,
374 '720p': false,
375 '1080p': false,
376 '1440p': false,
377 '2160p': false
378 }
f6d6e7f8 379 }
40930fda 380 }
f6d6e7f8 381 })
40930fda 382 })
edb4ffc7 383
f6d6e7f8 384 it('Should merge an audio file with the preview file', async function () {
385 this.timeout(60_000)
edb4ffc7 386
f6d6e7f8 387 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
388 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg, HttpStatusCode.OK_200, mode)
14e2014a 389
f6d6e7f8 390 await waitJobs(servers)
7ed2c1a4 391
f6d6e7f8 392 for (const server of servers) {
393 const res = await getVideosList(server.url)
7ed2c1a4 394
f6d6e7f8 395 const video = res.body.data.find(v => v.name === 'audio_with_preview')
396 const res2 = await getVideo(server.url, video.id)
397 const videoDetails: VideoDetails = res2.body
7ed2c1a4 398
f6d6e7f8 399 expect(videoDetails.files).to.have.lengthOf(1)
14e2014a 400
f6d6e7f8 401 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: HttpStatusCode.OK_200 })
402 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: HttpStatusCode.OK_200 })
40930fda 403
f6d6e7f8 404 const magnetUri = videoDetails.files[0].magnetUri
405 expect(magnetUri).to.contain('.mp4')
406 }
407 })
14e2014a 408
f6d6e7f8 409 it('Should upload an audio file and choose a default background image', async function () {
410 this.timeout(60_000)
14e2014a 411
f6d6e7f8 412 const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' }
413 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg, HttpStatusCode.OK_200, mode)
14e2014a 414
f6d6e7f8 415 await waitJobs(servers)
14e2014a 416
f6d6e7f8 417 for (const server of servers) {
418 const res = await getVideosList(server.url)
40930fda 419
f6d6e7f8 420 const video = res.body.data.find(v => v.name === 'audio_without_preview')
421 const res2 = await getVideo(server.url, video.id)
422 const videoDetails = res2.body
14e2014a 423
f6d6e7f8 424 expect(videoDetails.files).to.have.lengthOf(1)
14e2014a 425
f6d6e7f8 426 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: HttpStatusCode.OK_200 })
427 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: HttpStatusCode.OK_200 })
7ed2c1a4 428
f6d6e7f8 429 const magnetUri = videoDetails.files[0].magnetUri
430 expect(magnetUri).to.contain('.mp4')
40930fda
C
431 }
432 })
7ed2c1a4 433
f6d6e7f8 434 it('Should upload an audio file and create an audio version only', async function () {
435 this.timeout(60_000)
436
65e6e260
C
437 await servers[1].configCommand.updateCustomSubConfig({
438 newConfig: {
439 transcoding: {
440 hls: { enabled: true },
441 webtorrent: { enabled: true },
442 resolutions: {
443 '0p': true,
444 '240p': false,
445 '360p': false
446 }
f6d6e7f8 447 }
448 }
449 })
b345a804 450
f6d6e7f8 451 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
452 const resVideo = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg, HttpStatusCode.OK_200, mode)
b345a804 453
f6d6e7f8 454 await waitJobs(servers)
b345a804 455
f6d6e7f8 456 for (const server of servers) {
457 const res2 = await getVideo(server.url, resVideo.body.video.id)
458 const videoDetails: VideoDetails = res2.body
459
460 for (const files of [ videoDetails.files, videoDetails.streamingPlaylists[0].files ]) {
461 expect(files).to.have.lengthOf(2)
462 expect(files.find(f => f.resolution.id === 0)).to.not.be.undefined
463 }
40930fda 464 }
b345a804 465
f6d6e7f8 466 await updateConfigForTranscoding(servers[1])
467 })
468 }
469
470 describe('Legacy upload', function () {
471 runSuite('legacy')
472 })
473
474 describe('Resumable upload', function () {
475 runSuite('resumable')
40930fda
C
476 })
477 })
b345a804 478
40930fda 479 describe('Framerate', function () {
b345a804 480
40930fda
C
481 it('Should transcode a 60 FPS video', async function () {
482 this.timeout(60_000)
b345a804 483
40930fda
C
484 const videoAttributes = {
485 name: 'my super 30fps name for server 2',
486 description: 'my super 30fps description for server 2',
487 fixture: '60fps_720p_small.mp4'
488 }
489 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
b345a804 490
40930fda 491 await waitJobs(servers)
b345a804 492
40930fda
C
493 for (const server of servers) {
494 const res = await getVideosList(server.url)
b345a804 495
40930fda
C
496 const video = res.body.data.find(v => v.name === videoAttributes.name)
497 const res2 = await getVideo(server.url, video.id)
498 const videoDetails: VideoDetails = res2.body
b345a804 499
40930fda
C
500 expect(videoDetails.files).to.have.lengthOf(4)
501 expect(videoDetails.files[0].fps).to.be.above(58).and.below(62)
502 expect(videoDetails.files[1].fps).to.be.below(31)
503 expect(videoDetails.files[2].fps).to.be.below(31)
504 expect(videoDetails.files[3].fps).to.be.below(31)
b345a804 505
40930fda 506 for (const resolution of [ '240', '360', '480' ]) {
6c5065a0 507 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-' + resolution + '.mp4'))
40930fda 508 const fps = await getVideoFileFPS(path)
b345a804 509
40930fda
C
510 expect(fps).to.be.below(31)
511 }
b345a804 512
6c5065a0 513 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-720.mp4'))
40930fda 514 const fps = await getVideoFileFPS(path)
b345a804 515
40930fda
C
516 expect(fps).to.be.above(58).and.below(62)
517 }
518 })
b345a804 519
40930fda
C
520 it('Should downscale to the closest divisor standard framerate', async function () {
521 this.timeout(200_000)
837666fe 522
40930fda 523 let tempFixturePath: string
837666fe 524
40930fda
C
525 {
526 tempFixturePath = await generateVideoWithFramerate(59)
837666fe 527
40930fda
C
528 const fps = await getVideoFileFPS(tempFixturePath)
529 expect(fps).to.be.equal(59)
530 }
837666fe 531
40930fda
C
532 const videoAttributes = {
533 name: '59fps video',
534 description: '59fps video',
535 fixture: tempFixturePath
536 }
837666fe 537
40930fda 538 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
837666fe 539
40930fda 540 await waitJobs(servers)
837666fe 541
40930fda
C
542 for (const server of servers) {
543 const res = await getVideosList(server.url)
837666fe 544
40930fda 545 const video = res.body.data.find(v => v.name === videoAttributes.name)
837666fe 546
40930fda 547 {
6c5065a0 548 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-240.mp4'))
40930fda
C
549 const fps = await getVideoFileFPS(path)
550 expect(fps).to.be.equal(25)
551 }
552
553 {
6c5065a0 554 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-720.mp4'))
40930fda
C
555 const fps = await getVideoFileFPS(path)
556 expect(fps).to.be.equal(59)
557 }
c7f36e4f 558 }
40930fda
C
559 })
560 })
561
562 describe('Bitrate control', function () {
563 it('Should respect maximum bitrate values', async function () {
564 this.timeout(160_000)
565
566 let tempFixturePath: string
c7f36e4f
C
567
568 {
40930fda 569 tempFixturePath = await generateHighBitrateVideo()
837666fe 570
40930fda
C
571 const bitrate = await getVideoFileBitrate(tempFixturePath)
572 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
d218e7de 573 }
d218e7de 574
40930fda
C
575 const videoAttributes = {
576 name: 'high bitrate video',
577 description: 'high bitrate video',
578 fixture: tempFixturePath
579 }
d218e7de 580
40930fda 581 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
d218e7de 582
40930fda 583 await waitJobs(servers)
d218e7de 584
40930fda
C
585 for (const server of servers) {
586 const res = await getVideosList(server.url)
d218e7de 587
40930fda 588 const video = res.body.data.find(v => v.name === videoAttributes.name)
8319d6ae 589
40930fda 590 for (const resolution of [ '240', '360', '480', '720', '1080' ]) {
6c5065a0 591 const path = servers[1].serversCommand.buildDirectory(join('videos', video.uuid + '-' + resolution + '.mp4'))
8319d6ae 592
40930fda
C
593 const bitrate = await getVideoFileBitrate(path)
594 const fps = await getVideoFileFPS(path)
595 const resolution2 = await getVideoFileResolution(path)
8319d6ae 596
40930fda
C
597 expect(resolution2.videoFileResolution.toString()).to.equal(resolution)
598 expect(bitrate).to.be.below(getMaxBitrate(resolution2.videoFileResolution, fps, VIDEO_TRANSCODING_FPS))
599 }
7b81edc8 600 }
40930fda 601 })
8319d6ae 602
40930fda
C
603 it('Should not transcode to an higher bitrate than the original file', async function () {
604 this.timeout(160_000)
605
65e6e260 606 const newConfig = {
40930fda
C
607 transcoding: {
608 enabled: true,
609 resolutions: {
610 '240p': true,
611 '360p': true,
612 '480p': true,
613 '720p': true,
614 '1080p': true,
615 '1440p': true,
616 '2160p': true
617 },
618 webtorrent: { enabled: true },
619 hls: { enabled: true }
620 }
8319d6ae 621 }
65e6e260 622 await servers[1].configCommand.updateCustomSubConfig({ newConfig })
7b81edc8 623
40930fda
C
624 const videoAttributes = {
625 name: 'low bitrate',
626 fixture: 'low-bitrate.mp4'
627 }
8319d6ae 628
40930fda
C
629 const resUpload = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
630 const videoUUID = resUpload.body.video.uuid
7b81edc8 631
40930fda 632 await waitJobs(servers)
8319d6ae 633
40930fda
C
634 const resolutions = [ 240, 360, 480, 720, 1080 ]
635 for (const r of resolutions) {
636 const path = `videos/${videoUUID}-${r}.mp4`
6c5065a0 637 const size = await servers[1].serversCommand.getServerFileSize(path)
40930fda 638 expect(size, `${path} not below ${60_000}`).to.be.below(60_000)
8319d6ae 639 }
40930fda 640 })
8319d6ae
RK
641 })
642
40930fda 643 describe('FFprobe', function () {
f5961a8c 644
40930fda
C
645 it('Should provide valid ffprobe data', async function () {
646 this.timeout(160_000)
f5961a8c 647
40930fda
C
648 const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'ffprobe data' })).uuid
649 await waitJobs(servers)
f5961a8c 650
40930fda 651 {
6c5065a0 652 const path = servers[1].serversCommand.buildDirectory(join('videos', videoUUID + '-240.mp4'))
40930fda
C
653 const metadata = await getMetadataFromFile(path)
654
655 // expected format properties
656 for (const p of [
657 'tags.encoder',
658 'format_long_name',
659 'size',
660 'bit_rate'
661 ]) {
662 expect(metadata.format).to.have.nested.property(p)
663 }
664
665 // expected stream properties
666 for (const p of [
667 'codec_long_name',
668 'profile',
669 'width',
670 'height',
671 'display_aspect_ratio',
672 'avg_frame_rate',
673 'pix_fmt'
674 ]) {
675 expect(metadata.streams[0]).to.have.nested.property(p)
676 }
677
678 expect(metadata).to.not.have.nested.property('format.filename')
679 }
f5961a8c 680
40930fda
C
681 for (const server of servers) {
682 const res2 = await getVideo(server.url, videoUUID)
683 const videoDetails: VideoDetails = res2.body
684
685 const videoFiles = videoDetails.files
686 .concat(videoDetails.streamingPlaylists[0].files)
687 expect(videoFiles).to.have.lengthOf(8)
688
689 for (const file of videoFiles) {
690 expect(file.metadata).to.be.undefined
691 expect(file.metadataUrl).to.exist
692 expect(file.metadataUrl).to.contain(servers[1].url)
693 expect(file.metadataUrl).to.contain(videoUUID)
694
695 const res3 = await getVideoFileMetadataUrl(file.metadataUrl)
696 const metadata: FfprobeData = res3.body
697 expect(metadata).to.have.nested.property('format.size')
698 }
699 }
700 })
f5961a8c 701
40930fda
C
702 it('Should correctly detect if quick transcode is possible', async function () {
703 this.timeout(10_000)
f5961a8c 704
40930fda
C
705 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
706 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
707 })
f5961a8c
C
708 })
709
40930fda 710 describe('Transcoding job queue', function () {
6939cbac 711
40930fda 712 it('Should have the appropriate priorities for transcoding jobs', async function () {
9c6327f8 713 const body = await servers[1].jobsCommand.getJobsList({
40930fda
C
714 start: 0,
715 count: 100,
716 sort: '-createdAt',
717 jobType: 'video-transcoding'
718 })
6939cbac 719
9c6327f8 720 const jobs = body.data
40930fda 721 const transcodingJobs = jobs.filter(j => j.data.videoUUID === video4k)
6939cbac 722
40930fda 723 expect(transcodingJobs).to.have.lengthOf(14)
6939cbac 724
40930fda
C
725 const hlsJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-hls')
726 const webtorrentJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-webtorrent')
727 const optimizeJobs = transcodingJobs.filter(j => j.data.type === 'optimize-to-webtorrent')
6939cbac 728
40930fda
C
729 expect(hlsJobs).to.have.lengthOf(7)
730 expect(webtorrentJobs).to.have.lengthOf(6)
731 expect(optimizeJobs).to.have.lengthOf(1)
6939cbac 732
a6e37eeb 733 for (const j of optimizeJobs.concat(hlsJobs.concat(webtorrentJobs))) {
40930fda
C
734 expect(j.priority).to.be.greaterThan(100)
735 expect(j.priority).to.be.lessThan(150)
736 }
737 })
6939cbac
C
738 })
739
7c3b7976
C
740 after(async function () {
741 await cleanupTests(servers)
0e1dc3e7
C
742 })
743})