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