]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/transcoding/transcoder.ts
Create another test suite for transcoding jobs
[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
a7ba16b6 3import 'mocha'
daf6e480 4import * as chai from 'chai'
7160878c 5import { omit } from 'lodash'
c729caf6
C
6import { canDoQuickTranscode } from '@server/helpers/ffmpeg'
7import { generateHighBitrateVideo, generateVideoWithFramerate, getAllFiles } from '@server/tests/shared'
c55e3d72 8import { buildAbsoluteFixturePath, getMaxBitrate, getMinLimitBitrate } from '@shared/core-utils'
c729caf6
C
9import {
10 getAudioStream,
11 buildFileMetadata,
12 getVideoStreamBitrate,
13 getVideoStreamFPS,
14 getVideoStreamDimensionsInfo,
15 hasAudioStream
16} from '@shared/extra-utils'
c55e3d72 17import { HttpStatusCode, VideoState } from '@shared/models'
0e1dc3e7 18import {
7243f84d 19 cleanupTests,
254d3579 20 createMultipleServers,
4c7e60bc 21 doubleFollow,
b345a804 22 makeGetRequest,
254d3579 23 PeerTubeServer,
2186386c 24 setAccessTokensToServers,
1600235a 25 waitJobs,
d175a6f7 26 webtorrentAdd
bf54587a 27} from '@shared/server-commands'
a7ba16b6
C
28
29const expect = chai.expect
0e1dc3e7 30
254d3579 31function updateConfigForTranscoding (server: PeerTubeServer) {
89d241a7 32 return server.config.updateCustomSubConfig({
65e6e260
C
33 newConfig: {
34 transcoding: {
35 enabled: true,
36 allowAdditionalExtensions: true,
37 allowAudioFiles: true,
38 hls: { enabled: true },
39 webtorrent: { enabled: true },
40 resolutions: {
41 '0p': false,
8dd754c7 42 '144p': true,
65e6e260
C
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
8dd754c7 122 expect(videoDetails.files).to.have.lengthOf(5)
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 })
8dd754c7 208 expect(videoDetails.files).to.have.lengthOf(5)
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
8dd754c7 229 const resolutions = [ 144, 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
8dd754c7 262 expect(videoDetails.files).to.have.lengthOf(5)
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
c729caf6 297 expect(await hasAudioStream(path)).to.be.false
40930fda
C
298 }
299 })
2186386c 300
40930fda
C
301 it('Should leave the audio untouched, but properly transcode the video', async function () {
302 this.timeout(60_000)
edb4ffc7 303
d23dd9fb 304 const attributes = {
40930fda
C
305 name: 'untouched_audio',
306 fixture: 'video_short.mp4'
307 }
89d241a7 308 await servers[1].videos.upload({ attributes })
74cd011b 309
40930fda 310 await waitJobs(servers)
edb4ffc7 311
40930fda 312 for (const server of servers) {
89d241a7 313 const { data } = await server.videos.list()
edb4ffc7 314
d23dd9fb 315 const video = data.find(v => v.name === attributes.name)
89d241a7 316 const videoDetails = await server.videos.get({ id: video.id })
edb4ffc7 317
8dd754c7 318 expect(videoDetails.files).to.have.lengthOf(5)
edb4ffc7 319
d23dd9fb 320 const fixturePath = buildAbsoluteFixturePath(attributes.fixture)
40930fda 321 const fixtureVideoProbe = await getAudioStream(fixturePath)
83903cb6
C
322
323 const file = videoDetails.files.find(f => f.resolution.id === 240)
324 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
edb4ffc7 325
40930fda 326 const videoProbe = await getAudioStream(path)
edb4ffc7 327
40930fda
C
328 if (videoProbe.audioStream && fixtureVideoProbe.audioStream) {
329 const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ]
330 expect(omit(videoProbe.audioStream, toOmit)).to.be.deep.equal(omit(fixtureVideoProbe.audioStream, toOmit))
331 } else {
332 this.fail('Could not retrieve the audio stream on ' + videoProbe.absolutePath)
333 }
334 }
335 })
336 })
edb4ffc7 337
40930fda
C
338 describe('Audio upload', function () {
339
f6d6e7f8 340 function runSuite (mode: 'legacy' | 'resumable') {
341
342 before(async function () {
89d241a7 343 await servers[1].config.updateCustomSubConfig({
65e6e260
C
344 newConfig: {
345 transcoding: {
346 hls: { enabled: true },
347 webtorrent: { enabled: true },
348 resolutions: {
349 '0p': false,
8dd754c7 350 '144p': false,
65e6e260
C
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,
8dd754c7 422 '144p': false,
65e6e260
C
423 '240p': false,
424 '360p': false
425 }
f6d6e7f8 426 }
427 }
428 })
b345a804 429
d23dd9fb 430 const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
89d241a7 431 const { id } = await servers[1].videos.upload({ attributes, mode })
b345a804 432
f6d6e7f8 433 await waitJobs(servers)
b345a804 434
f6d6e7f8 435 for (const server of servers) {
89d241a7 436 const videoDetails = await server.videos.get({ id })
f6d6e7f8 437
438 for (const files of [ videoDetails.files, videoDetails.streamingPlaylists[0].files ]) {
439 expect(files).to.have.lengthOf(2)
440 expect(files.find(f => f.resolution.id === 0)).to.not.be.undefined
441 }
40930fda 442 }
b345a804 443
f6d6e7f8 444 await updateConfigForTranscoding(servers[1])
445 })
446 }
447
448 describe('Legacy upload', function () {
449 runSuite('legacy')
450 })
451
452 describe('Resumable upload', function () {
453 runSuite('resumable')
40930fda
C
454 })
455 })
b345a804 456
40930fda 457 describe('Framerate', function () {
b345a804 458
40930fda
C
459 it('Should transcode a 60 FPS video', async function () {
460 this.timeout(60_000)
b345a804 461
d23dd9fb 462 const attributes = {
40930fda
C
463 name: 'my super 30fps name for server 2',
464 description: 'my super 30fps description for server 2',
465 fixture: '60fps_720p_small.mp4'
466 }
89d241a7 467 await servers[1].videos.upload({ attributes })
b345a804 468
40930fda 469 await waitJobs(servers)
b345a804 470
40930fda 471 for (const server of servers) {
89d241a7 472 const { data } = await server.videos.list()
b345a804 473
d23dd9fb 474 const video = data.find(v => v.name === attributes.name)
89d241a7 475 const videoDetails = await server.videos.get({ id: video.id })
b345a804 476
8dd754c7 477 expect(videoDetails.files).to.have.lengthOf(5)
40930fda
C
478 expect(videoDetails.files[0].fps).to.be.above(58).and.below(62)
479 expect(videoDetails.files[1].fps).to.be.below(31)
480 expect(videoDetails.files[2].fps).to.be.below(31)
481 expect(videoDetails.files[3].fps).to.be.below(31)
8dd754c7 482 expect(videoDetails.files[4].fps).to.be.below(31)
b345a804 483
8dd754c7 484 for (const resolution of [ 144, 240, 360, 480 ]) {
83903cb6
C
485 const file = videoDetails.files.find(f => f.resolution.id === resolution)
486 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
c729caf6 487 const fps = await getVideoStreamFPS(path)
b345a804 488
40930fda
C
489 expect(fps).to.be.below(31)
490 }
b345a804 491
83903cb6
C
492 const file = videoDetails.files.find(f => f.resolution.id === 720)
493 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
c729caf6 494 const fps = await getVideoStreamFPS(path)
b345a804 495
40930fda
C
496 expect(fps).to.be.above(58).and.below(62)
497 }
498 })
b345a804 499
40930fda
C
500 it('Should downscale to the closest divisor standard framerate', async function () {
501 this.timeout(200_000)
837666fe 502
40930fda 503 let tempFixturePath: string
837666fe 504
40930fda
C
505 {
506 tempFixturePath = await generateVideoWithFramerate(59)
837666fe 507
c729caf6 508 const fps = await getVideoStreamFPS(tempFixturePath)
40930fda
C
509 expect(fps).to.be.equal(59)
510 }
837666fe 511
d23dd9fb 512 const attributes = {
40930fda
C
513 name: '59fps video',
514 description: '59fps video',
515 fixture: tempFixturePath
516 }
837666fe 517
89d241a7 518 await servers[1].videos.upload({ attributes })
837666fe 519
40930fda 520 await waitJobs(servers)
837666fe 521
40930fda 522 for (const server of servers) {
89d241a7 523 const { data } = await server.videos.list()
837666fe 524
83903cb6
C
525 const { id } = data.find(v => v.name === attributes.name)
526 const video = await server.videos.get({ id })
837666fe 527
40930fda 528 {
83903cb6
C
529 const file = video.files.find(f => f.resolution.id === 240)
530 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
c729caf6 531 const fps = await getVideoStreamFPS(path)
40930fda
C
532 expect(fps).to.be.equal(25)
533 }
534
535 {
83903cb6
C
536 const file = video.files.find(f => f.resolution.id === 720)
537 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
c729caf6 538 const fps = await getVideoStreamFPS(path)
40930fda
C
539 expect(fps).to.be.equal(59)
540 }
c7f36e4f 541 }
40930fda
C
542 })
543 })
544
545 describe('Bitrate control', function () {
83903cb6 546
40930fda
C
547 it('Should respect maximum bitrate values', async function () {
548 this.timeout(160_000)
549
679c12e6 550 const tempFixturePath = await generateHighBitrateVideo()
d218e7de 551
d23dd9fb 552 const attributes = {
40930fda
C
553 name: 'high bitrate video',
554 description: 'high bitrate video',
555 fixture: tempFixturePath
556 }
d218e7de 557
89d241a7 558 await servers[1].videos.upload({ attributes })
d218e7de 559
40930fda 560 await waitJobs(servers)
d218e7de 561
40930fda 562 for (const server of servers) {
89d241a7 563 const { data } = await server.videos.list()
d218e7de 564
83903cb6
C
565 const { id } = data.find(v => v.name === attributes.name)
566 const video = await server.videos.get({ id })
8319d6ae 567
83903cb6
C
568 for (const resolution of [ 240, 360, 480, 720, 1080 ]) {
569 const file = video.files.find(f => f.resolution.id === resolution)
570 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
8319d6ae 571
c729caf6
C
572 const bitrate = await getVideoStreamBitrate(path)
573 const fps = await getVideoStreamFPS(path)
574 const dataResolution = await getVideoStreamDimensionsInfo(path)
679c12e6
C
575
576 expect(resolution).to.equal(resolution)
8319d6ae 577
679c12e6
C
578 const maxBitrate = getMaxBitrate({ ...dataResolution, fps })
579 expect(bitrate).to.be.below(maxBitrate)
40930fda 580 }
7b81edc8 581 }
40930fda 582 })
8319d6ae 583
d78b51aa 584 it('Should not transcode to an higher bitrate than the original file but above our low limit', async function () {
40930fda
C
585 this.timeout(160_000)
586
65e6e260 587 const newConfig = {
40930fda
C
588 transcoding: {
589 enabled: true,
590 resolutions: {
8dd754c7 591 '144p': true,
40930fda
C
592 '240p': true,
593 '360p': true,
594 '480p': true,
595 '720p': true,
596 '1080p': true,
597 '1440p': true,
598 '2160p': true
599 },
600 webtorrent: { enabled: true },
601 hls: { enabled: true }
602 }
8319d6ae 603 }
89d241a7 604 await servers[1].config.updateCustomSubConfig({ newConfig })
7b81edc8 605
d23dd9fb 606 const attributes = {
40930fda
C
607 name: 'low bitrate',
608 fixture: 'low-bitrate.mp4'
609 }
8319d6ae 610
83903cb6 611 const { id } = await servers[1].videos.upload({ attributes })
7b81edc8 612
40930fda 613 await waitJobs(servers)
8319d6ae 614
83903cb6
C
615 const video = await servers[1].videos.get({ id })
616
40930fda
C
617 const resolutions = [ 240, 360, 480, 720, 1080 ]
618 for (const r of resolutions) {
83903cb6
C
619 const file = video.files.find(f => f.resolution.id === r)
620
621 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
c729caf6 622 const bitrate = await getVideoStreamBitrate(path)
d78b51aa
C
623
624 const inputBitrate = 60_000
625 const limit = getMinLimitBitrate({ fps: 10, ratio: 1, resolution: r })
626 let belowValue = Math.max(inputBitrate, limit)
627 belowValue += belowValue * 0.20 // Apply 20% margin because bitrate control is not very precise
628
629 expect(bitrate, `${path} not below ${limit}`).to.be.below(belowValue)
8319d6ae 630 }
40930fda 631 })
8319d6ae
RK
632 })
633
40930fda 634 describe('FFprobe', function () {
f5961a8c 635
40930fda
C
636 it('Should provide valid ffprobe data', async function () {
637 this.timeout(160_000)
f5961a8c 638
89d241a7 639 const videoUUID = (await servers[1].videos.quickUpload({ name: 'ffprobe data' })).uuid
40930fda 640 await waitJobs(servers)
f5961a8c 641
40930fda 642 {
83903cb6
C
643 const video = await servers[1].videos.get({ id: videoUUID })
644 const file = video.files.find(f => f.resolution.id === 240)
645 const path = servers[1].servers.buildWebTorrentFilePath(file.fileUrl)
c729caf6 646 const metadata = await buildFileMetadata(path)
40930fda
C
647
648 // expected format properties
649 for (const p of [
650 'tags.encoder',
651 'format_long_name',
652 'size',
653 'bit_rate'
654 ]) {
655 expect(metadata.format).to.have.nested.property(p)
656 }
657
658 // expected stream properties
659 for (const p of [
660 'codec_long_name',
661 'profile',
662 'width',
663 'height',
664 'display_aspect_ratio',
665 'avg_frame_rate',
666 'pix_fmt'
667 ]) {
668 expect(metadata.streams[0]).to.have.nested.property(p)
669 }
670
671 expect(metadata).to.not.have.nested.property('format.filename')
672 }
f5961a8c 673
40930fda 674 for (const server of servers) {
89d241a7 675 const videoDetails = await server.videos.get({ id: videoUUID })
40930fda 676
c729caf6 677 const videoFiles = getAllFiles(videoDetails)
8dd754c7 678 expect(videoFiles).to.have.lengthOf(10)
40930fda
C
679
680 for (const file of videoFiles) {
681 expect(file.metadata).to.be.undefined
682 expect(file.metadataUrl).to.exist
683 expect(file.metadataUrl).to.contain(servers[1].url)
684 expect(file.metadataUrl).to.contain(videoUUID)
685
89d241a7 686 const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl })
40930fda
C
687 expect(metadata).to.have.nested.property('format.size')
688 }
689 }
690 })
f5961a8c 691
40930fda
C
692 it('Should correctly detect if quick transcode is possible', async function () {
693 this.timeout(10_000)
f5961a8c 694
40930fda
C
695 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
696 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
697 })
f5961a8c
C
698 })
699
40930fda 700 describe('Transcoding job queue', function () {
6939cbac 701
40930fda 702 it('Should have the appropriate priorities for transcoding jobs', async function () {
851675c5 703 const body = await servers[1].jobs.list({
40930fda
C
704 start: 0,
705 count: 100,
8dd754c7 706 sort: 'createdAt',
40930fda
C
707 jobType: 'video-transcoding'
708 })
6939cbac 709
9c6327f8 710 const jobs = body.data
40930fda 711 const transcodingJobs = jobs.filter(j => j.data.videoUUID === video4k)
6939cbac 712
8dd754c7 713 expect(transcodingJobs).to.have.lengthOf(16)
6939cbac 714
40930fda
C
715 const hlsJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-hls')
716 const webtorrentJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-webtorrent')
717 const optimizeJobs = transcodingJobs.filter(j => j.data.type === 'optimize-to-webtorrent')
6939cbac 718
8dd754c7
FC
719 expect(hlsJobs).to.have.lengthOf(8)
720 expect(webtorrentJobs).to.have.lengthOf(7)
40930fda 721 expect(optimizeJobs).to.have.lengthOf(1)
6939cbac 722
a6e37eeb 723 for (const j of optimizeJobs.concat(hlsJobs.concat(webtorrentJobs))) {
40930fda
C
724 expect(j.priority).to.be.greaterThan(100)
725 expect(j.priority).to.be.lessThan(150)
726 }
727 })
6939cbac
C
728 })
729
7c3b7976
C
730 after(async function () {
731 await cleanupTests(servers)
0e1dc3e7
C
732 })
733})