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