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