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