]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/videos/video-transcoder.ts
Simplify createServer args
[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'
daf6e480 6import { join } from 'path'
0e1dc3e7 7import {
7243f84d
C
8 buildAbsoluteFixturePath,
9 cleanupTests,
254d3579 10 createMultipleServers,
4c7e60bc 11 doubleFollow,
d175a6f7 12 generateHighBitrateVideo,
837666fe 13 generateVideoWithFramerate,
b345a804 14 makeGetRequest,
254d3579 15 PeerTubeServer,
2186386c 16 setAccessTokensToServers,
1600235a 17 waitJobs,
d175a6f7 18 webtorrentAdd
d23dd9fb 19} from '@shared/extra-utils'
4c7e60bc 20import { getMaxBitrate, HttpStatusCode, VideoResolution, VideoState } from '@shared/models'
d23dd9fb 21import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
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 let tempFixturePath: string
7160878c 195
40930fda
C
196 {
197 tempFixturePath = await generateHighBitrateVideo()
7160878c 198
40930fda
C
199 const bitrate = await getVideoFileBitrate(tempFixturePath)
200 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
b2977eec 201 }
7160878c 202
40930fda 203 for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
d23dd9fb 204 const attributes = {
40930fda
C
205 name: fixture,
206 fixture
207 }
7160878c 208
89d241a7 209 await servers[1].videos.upload({ attributes })
7160878c 210
40930fda 211 await waitJobs(servers)
7160878c 212
40930fda 213 for (const server of servers) {
89d241a7 214 const { data } = await server.videos.list()
7160878c 215
d23dd9fb 216 const video = data.find(v => v.name === attributes.name)
89d241a7 217 const videoDetails = await server.videos.get({ id: video.id })
40930fda 218 expect(videoDetails.files).to.have.lengthOf(4)
7160878c 219
40930fda
C
220 const magnetUri = videoDetails.files[0].magnetUri
221 expect(magnetUri).to.contain('.mp4')
222 }
223 }
224 })
7160878c 225
40930fda
C
226 it('Should transcode a 4k video', async function () {
227 this.timeout(200_000)
7160878c 228
d23dd9fb 229 const attributes = {
40930fda
C
230 name: '4k video',
231 fixture: 'video_short_4k.mp4'
232 }
7160878c 233
89d241a7 234 const { uuid } = await servers[1].videos.upload({ attributes })
d23dd9fb 235 video4k = uuid
b2977eec 236
40930fda 237 await waitJobs(servers)
b2977eec 238
40930fda 239 const resolutions = [ 240, 360, 480, 720, 1080, 1440, 2160 ]
ca5c612b 240
40930fda 241 for (const server of servers) {
89d241a7 242 const videoDetails = await server.videos.get({ id: video4k })
40930fda 243 expect(videoDetails.files).to.have.lengthOf(resolutions.length)
ca5c612b 244
40930fda
C
245 for (const r of resolutions) {
246 expect(videoDetails.files.find(f => f.resolution.id === r)).to.not.be.undefined
247 expect(videoDetails.streamingPlaylists[0].files.find(f => f.resolution.id === r)).to.not.be.undefined
248 }
b2977eec 249 }
40930fda 250 })
7160878c
RK
251 })
252
40930fda 253 describe('Audio transcoding', function () {
73c69591 254
40930fda
C
255 it('Should transcode high bit rate mp3 to proper bit rate', async function () {
256 this.timeout(60_000)
73c69591 257
d23dd9fb 258 const attributes = {
40930fda
C
259 name: 'mp3_256k',
260 fixture: 'video_short_mp3_256k.mp4'
261 }
89d241a7 262 await servers[1].videos.upload({ attributes })
73c69591 263
40930fda 264 await waitJobs(servers)
73c69591 265
40930fda 266 for (const server of servers) {
89d241a7 267 const { data } = await server.videos.list()
73c69591 268
d23dd9fb 269 const video = data.find(v => v.name === attributes.name)
89d241a7 270 const videoDetails = await server.videos.get({ id: video.id })
73c69591 271
40930fda 272 expect(videoDetails.files).to.have.lengthOf(4)
73c69591 273
89d241a7 274 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-240.mp4'))
40930fda
C
275 const probe = await getAudioStream(path)
276
277 if (probe.audioStream) {
278 expect(probe.audioStream['codec_name']).to.be.equal('aac')
279 expect(probe.audioStream['bit_rate']).to.be.at.most(384 * 8000)
280 } else {
281 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath)
282 }
b2977eec 283 }
40930fda 284 })
3a6f351b 285
40930fda
C
286 it('Should transcode video with no audio and have no audio itself', async function () {
287 this.timeout(60_000)
2186386c 288
d23dd9fb 289 const attributes = {
40930fda
C
290 name: 'no_audio',
291 fixture: 'video_short_no_audio.mp4'
292 }
89d241a7 293 await servers[1].videos.upload({ attributes })
2186386c 294
40930fda 295 await waitJobs(servers)
2186386c 296
40930fda 297 for (const server of servers) {
89d241a7 298 const { data } = await server.videos.list()
2186386c 299
d23dd9fb 300 const video = data.find(v => v.name === attributes.name)
89d241a7 301 const videoDetails = await server.videos.get({ id: video.id })
2186386c 302
40930fda 303 expect(videoDetails.files).to.have.lengthOf(4)
89d241a7 304 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-240.mp4'))
40930fda
C
305 const probe = await getAudioStream(path)
306 expect(probe).to.not.have.property('audioStream')
307 }
308 })
2186386c 309
40930fda
C
310 it('Should leave the audio untouched, but properly transcode the video', async function () {
311 this.timeout(60_000)
edb4ffc7 312
d23dd9fb 313 const attributes = {
40930fda
C
314 name: 'untouched_audio',
315 fixture: 'video_short.mp4'
316 }
89d241a7 317 await servers[1].videos.upload({ attributes })
74cd011b 318
40930fda 319 await waitJobs(servers)
edb4ffc7 320
40930fda 321 for (const server of servers) {
89d241a7 322 const { data } = await server.videos.list()
edb4ffc7 323
d23dd9fb 324 const video = data.find(v => v.name === attributes.name)
89d241a7 325 const videoDetails = await server.videos.get({ id: video.id })
edb4ffc7 326
40930fda 327 expect(videoDetails.files).to.have.lengthOf(4)
edb4ffc7 328
d23dd9fb 329 const fixturePath = buildAbsoluteFixturePath(attributes.fixture)
40930fda 330 const fixtureVideoProbe = await getAudioStream(fixturePath)
89d241a7 331 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-240.mp4'))
edb4ffc7 332
40930fda 333 const videoProbe = await getAudioStream(path)
edb4ffc7 334
40930fda
C
335 if (videoProbe.audioStream && fixtureVideoProbe.audioStream) {
336 const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ]
337 expect(omit(videoProbe.audioStream, toOmit)).to.be.deep.equal(omit(fixtureVideoProbe.audioStream, toOmit))
338 } else {
339 this.fail('Could not retrieve the audio stream on ' + videoProbe.absolutePath)
340 }
341 }
342 })
343 })
edb4ffc7 344
40930fda
C
345 describe('Audio upload', function () {
346
f6d6e7f8 347 function runSuite (mode: 'legacy' | 'resumable') {
348
349 before(async function () {
89d241a7 350 await servers[1].config.updateCustomSubConfig({
65e6e260
C
351 newConfig: {
352 transcoding: {
353 hls: { enabled: true },
354 webtorrent: { enabled: true },
355 resolutions: {
356 '0p': false,
357 '240p': false,
358 '360p': false,
359 '480p': false,
360 '720p': false,
361 '1080p': false,
362 '1440p': false,
363 '2160p': false
364 }
f6d6e7f8 365 }
40930fda 366 }
f6d6e7f8 367 })
40930fda 368 })
edb4ffc7 369
f6d6e7f8 370 it('Should merge an audio file with the preview file', async function () {
371 this.timeout(60_000)
edb4ffc7 372
d23dd9fb 373 const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
89d241a7 374 await servers[1].videos.upload({ attributes, mode })
14e2014a 375
f6d6e7f8 376 await waitJobs(servers)
7ed2c1a4 377
f6d6e7f8 378 for (const server of servers) {
89d241a7 379 const { data } = await server.videos.list()
7ed2c1a4 380
d23dd9fb 381 const video = data.find(v => v.name === 'audio_with_preview')
89d241a7 382 const videoDetails = await server.videos.get({ id: video.id })
7ed2c1a4 383
f6d6e7f8 384 expect(videoDetails.files).to.have.lengthOf(1)
14e2014a 385
c0e8b12e
C
386 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
387 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, expectedStatus: HttpStatusCode.OK_200 })
40930fda 388
f6d6e7f8 389 const magnetUri = videoDetails.files[0].magnetUri
390 expect(magnetUri).to.contain('.mp4')
391 }
392 })
14e2014a 393
f6d6e7f8 394 it('Should upload an audio file and choose a default background image', async function () {
395 this.timeout(60_000)
14e2014a 396
d23dd9fb 397 const attributes = { name: 'audio_without_preview', fixture: 'sample.ogg' }
89d241a7 398 await servers[1].videos.upload({ attributes, mode })
14e2014a 399
f6d6e7f8 400 await waitJobs(servers)
14e2014a 401
f6d6e7f8 402 for (const server of servers) {
89d241a7 403 const { data } = await server.videos.list()
40930fda 404
d23dd9fb 405 const video = data.find(v => v.name === 'audio_without_preview')
89d241a7 406 const videoDetails = await server.videos.get({ id: video.id })
14e2014a 407
f6d6e7f8 408 expect(videoDetails.files).to.have.lengthOf(1)
14e2014a 409
c0e8b12e
C
410 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
411 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, expectedStatus: HttpStatusCode.OK_200 })
7ed2c1a4 412
f6d6e7f8 413 const magnetUri = videoDetails.files[0].magnetUri
414 expect(magnetUri).to.contain('.mp4')
40930fda
C
415 }
416 })
7ed2c1a4 417
f6d6e7f8 418 it('Should upload an audio file and create an audio version only', async function () {
419 this.timeout(60_000)
420
89d241a7 421 await servers[1].config.updateCustomSubConfig({
65e6e260
C
422 newConfig: {
423 transcoding: {
424 hls: { enabled: true },
425 webtorrent: { enabled: true },
426 resolutions: {
427 '0p': true,
428 '240p': false,
429 '360p': false
430 }
f6d6e7f8 431 }
432 }
433 })
b345a804 434
d23dd9fb 435 const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
89d241a7 436 const { id } = await servers[1].videos.upload({ attributes, mode })
b345a804 437
f6d6e7f8 438 await waitJobs(servers)
b345a804 439
f6d6e7f8 440 for (const server of servers) {
89d241a7 441 const videoDetails = await server.videos.get({ id })
f6d6e7f8 442
443 for (const files of [ videoDetails.files, videoDetails.streamingPlaylists[0].files ]) {
444 expect(files).to.have.lengthOf(2)
445 expect(files.find(f => f.resolution.id === 0)).to.not.be.undefined
446 }
40930fda 447 }
b345a804 448
f6d6e7f8 449 await updateConfigForTranscoding(servers[1])
450 })
451 }
452
453 describe('Legacy upload', function () {
454 runSuite('legacy')
455 })
456
457 describe('Resumable upload', function () {
458 runSuite('resumable')
40930fda
C
459 })
460 })
b345a804 461
40930fda 462 describe('Framerate', function () {
b345a804 463
40930fda
C
464 it('Should transcode a 60 FPS video', async function () {
465 this.timeout(60_000)
b345a804 466
d23dd9fb 467 const attributes = {
40930fda
C
468 name: 'my super 30fps name for server 2',
469 description: 'my super 30fps description for server 2',
470 fixture: '60fps_720p_small.mp4'
471 }
89d241a7 472 await servers[1].videos.upload({ attributes })
b345a804 473
40930fda 474 await waitJobs(servers)
b345a804 475
40930fda 476 for (const server of servers) {
89d241a7 477 const { data } = await server.videos.list()
b345a804 478
d23dd9fb 479 const video = data.find(v => v.name === attributes.name)
89d241a7 480 const videoDetails = await server.videos.get({ id: video.id })
b345a804 481
40930fda
C
482 expect(videoDetails.files).to.have.lengthOf(4)
483 expect(videoDetails.files[0].fps).to.be.above(58).and.below(62)
484 expect(videoDetails.files[1].fps).to.be.below(31)
485 expect(videoDetails.files[2].fps).to.be.below(31)
486 expect(videoDetails.files[3].fps).to.be.below(31)
b345a804 487
40930fda 488 for (const resolution of [ '240', '360', '480' ]) {
89d241a7 489 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-' + resolution + '.mp4'))
40930fda 490 const fps = await getVideoFileFPS(path)
b345a804 491
40930fda
C
492 expect(fps).to.be.below(31)
493 }
b345a804 494
89d241a7 495 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-720.mp4'))
40930fda 496 const fps = await getVideoFileFPS(path)
b345a804 497
40930fda
C
498 expect(fps).to.be.above(58).and.below(62)
499 }
500 })
b345a804 501
40930fda
C
502 it('Should downscale to the closest divisor standard framerate', async function () {
503 this.timeout(200_000)
837666fe 504
40930fda 505 let tempFixturePath: string
837666fe 506
40930fda
C
507 {
508 tempFixturePath = await generateVideoWithFramerate(59)
837666fe 509
40930fda
C
510 const fps = await getVideoFileFPS(tempFixturePath)
511 expect(fps).to.be.equal(59)
512 }
837666fe 513
d23dd9fb 514 const attributes = {
40930fda
C
515 name: '59fps video',
516 description: '59fps video',
517 fixture: tempFixturePath
518 }
837666fe 519
89d241a7 520 await servers[1].videos.upload({ attributes })
837666fe 521
40930fda 522 await waitJobs(servers)
837666fe 523
40930fda 524 for (const server of servers) {
89d241a7 525 const { data } = await server.videos.list()
837666fe 526
d23dd9fb 527 const video = data.find(v => v.name === attributes.name)
837666fe 528
40930fda 529 {
89d241a7 530 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-240.mp4'))
40930fda
C
531 const fps = await getVideoFileFPS(path)
532 expect(fps).to.be.equal(25)
533 }
534
535 {
89d241a7 536 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-720.mp4'))
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 () {
545 it('Should respect maximum bitrate values', async function () {
546 this.timeout(160_000)
547
548 let tempFixturePath: string
c7f36e4f
C
549
550 {
40930fda 551 tempFixturePath = await generateHighBitrateVideo()
837666fe 552
40930fda
C
553 const bitrate = await getVideoFileBitrate(tempFixturePath)
554 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
d218e7de 555 }
d218e7de 556
d23dd9fb 557 const attributes = {
40930fda
C
558 name: 'high bitrate video',
559 description: 'high bitrate video',
560 fixture: tempFixturePath
561 }
d218e7de 562
89d241a7 563 await servers[1].videos.upload({ attributes })
d218e7de 564
40930fda 565 await waitJobs(servers)
d218e7de 566
40930fda 567 for (const server of servers) {
89d241a7 568 const { data } = await server.videos.list()
d218e7de 569
d23dd9fb 570 const video = data.find(v => v.name === attributes.name)
8319d6ae 571
40930fda 572 for (const resolution of [ '240', '360', '480', '720', '1080' ]) {
89d241a7 573 const path = servers[1].servers.buildDirectory(join('videos', video.uuid + '-' + resolution + '.mp4'))
8319d6ae 574
40930fda
C
575 const bitrate = await getVideoFileBitrate(path)
576 const fps = await getVideoFileFPS(path)
577 const resolution2 = await getVideoFileResolution(path)
8319d6ae 578
40930fda
C
579 expect(resolution2.videoFileResolution.toString()).to.equal(resolution)
580 expect(bitrate).to.be.below(getMaxBitrate(resolution2.videoFileResolution, fps, VIDEO_TRANSCODING_FPS))
581 }
7b81edc8 582 }
40930fda 583 })
8319d6ae 584
40930fda
C
585 it('Should not transcode to an higher bitrate than the original file', async function () {
586 this.timeout(160_000)
587
65e6e260 588 const newConfig = {
40930fda
C
589 transcoding: {
590 enabled: true,
591 resolutions: {
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
89d241a7 611 const { uuid } = await servers[1].videos.upload({ attributes })
7b81edc8 612
40930fda 613 await waitJobs(servers)
8319d6ae 614
40930fda
C
615 const resolutions = [ 240, 360, 480, 720, 1080 ]
616 for (const r of resolutions) {
d23dd9fb 617 const path = `videos/${uuid}-${r}.mp4`
89d241a7 618 const size = await servers[1].servers.getServerFileSize(path)
40930fda 619 expect(size, `${path} not below ${60_000}`).to.be.below(60_000)
8319d6ae 620 }
40930fda 621 })
8319d6ae
RK
622 })
623
40930fda 624 describe('FFprobe', function () {
f5961a8c 625
40930fda
C
626 it('Should provide valid ffprobe data', async function () {
627 this.timeout(160_000)
f5961a8c 628
89d241a7 629 const videoUUID = (await servers[1].videos.quickUpload({ name: 'ffprobe data' })).uuid
40930fda 630 await waitJobs(servers)
f5961a8c 631
40930fda 632 {
89d241a7 633 const path = servers[1].servers.buildDirectory(join('videos', videoUUID + '-240.mp4'))
40930fda
C
634 const metadata = await getMetadataFromFile(path)
635
636 // expected format properties
637 for (const p of [
638 'tags.encoder',
639 'format_long_name',
640 'size',
641 'bit_rate'
642 ]) {
643 expect(metadata.format).to.have.nested.property(p)
644 }
645
646 // expected stream properties
647 for (const p of [
648 'codec_long_name',
649 'profile',
650 'width',
651 'height',
652 'display_aspect_ratio',
653 'avg_frame_rate',
654 'pix_fmt'
655 ]) {
656 expect(metadata.streams[0]).to.have.nested.property(p)
657 }
658
659 expect(metadata).to.not.have.nested.property('format.filename')
660 }
f5961a8c 661
40930fda 662 for (const server of servers) {
89d241a7 663 const videoDetails = await server.videos.get({ id: videoUUID })
40930fda
C
664
665 const videoFiles = videoDetails.files
666 .concat(videoDetails.streamingPlaylists[0].files)
667 expect(videoFiles).to.have.lengthOf(8)
668
669 for (const file of videoFiles) {
670 expect(file.metadata).to.be.undefined
671 expect(file.metadataUrl).to.exist
672 expect(file.metadataUrl).to.contain(servers[1].url)
673 expect(file.metadataUrl).to.contain(videoUUID)
674
89d241a7 675 const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl })
40930fda
C
676 expect(metadata).to.have.nested.property('format.size')
677 }
678 }
679 })
f5961a8c 680
40930fda
C
681 it('Should correctly detect if quick transcode is possible', async function () {
682 this.timeout(10_000)
f5961a8c 683
40930fda
C
684 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
685 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
686 })
f5961a8c
C
687 })
688
40930fda 689 describe('Transcoding job queue', function () {
6939cbac 690
40930fda 691 it('Should have the appropriate priorities for transcoding jobs', async function () {
89d241a7 692 const body = await servers[1].jobs.getJobsList({
40930fda
C
693 start: 0,
694 count: 100,
695 sort: '-createdAt',
696 jobType: 'video-transcoding'
697 })
6939cbac 698
9c6327f8 699 const jobs = body.data
40930fda 700 const transcodingJobs = jobs.filter(j => j.data.videoUUID === video4k)
6939cbac 701
40930fda 702 expect(transcodingJobs).to.have.lengthOf(14)
6939cbac 703
40930fda
C
704 const hlsJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-hls')
705 const webtorrentJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-webtorrent')
706 const optimizeJobs = transcodingJobs.filter(j => j.data.type === 'optimize-to-webtorrent')
6939cbac 707
40930fda
C
708 expect(hlsJobs).to.have.lengthOf(7)
709 expect(webtorrentJobs).to.have.lengthOf(6)
710 expect(optimizeJobs).to.have.lengthOf(1)
6939cbac 711
a6e37eeb 712 for (const j of optimizeJobs.concat(hlsJobs.concat(webtorrentJobs))) {
40930fda
C
713 expect(j.priority).to.be.greaterThan(100)
714 expect(j.priority).to.be.lessThan(150)
715 }
716 })
6939cbac
C
717 })
718
7c3b7976
C
719 after(async function () {
720 await cleanupTests(servers)
0e1dc3e7
C
721 })
722})