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