aboutsummaryrefslogtreecommitdiffhomepage
path: root/packages/tests/src/api/runners/runner-vod-transcoding.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tests/src/api/runners/runner-vod-transcoding.ts')
-rw-r--r--packages/tests/src/api/runners/runner-vod-transcoding.ts545
1 files changed, 545 insertions, 0 deletions
diff --git a/packages/tests/src/api/runners/runner-vod-transcoding.ts b/packages/tests/src/api/runners/runner-vod-transcoding.ts
new file mode 100644
index 000000000..fe1c8f0b2
--- /dev/null
+++ b/packages/tests/src/api/runners/runner-vod-transcoding.ts
@@ -0,0 +1,545 @@
1/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
2
3import { expect } from 'chai'
4import { readFile } from 'fs/promises'
5import { completeCheckHlsPlaylist } from '@tests/shared/streaming-playlists.js'
6import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
7import {
8 HttpStatusCode,
9 RunnerJobSuccessPayload,
10 RunnerJobVODAudioMergeTranscodingPayload,
11 RunnerJobVODHLSTranscodingPayload,
12 RunnerJobVODPayload,
13 RunnerJobVODWebVideoTranscodingPayload,
14 VideoState,
15 VODAudioMergeTranscodingSuccess,
16 VODHLSTranscodingSuccess,
17 VODWebVideoTranscodingSuccess
18} from '@peertube/peertube-models'
19import {
20 cleanupTests,
21 createMultipleServers,
22 doubleFollow,
23 makeGetRequest,
24 makeRawRequest,
25 PeerTubeServer,
26 setAccessTokensToServers,
27 setDefaultVideoChannel,
28 waitJobs
29} from '@peertube/peertube-server-commands'
30
31async function processAllJobs (server: PeerTubeServer, runnerToken: string) {
32 do {
33 const { availableJobs } = await server.runnerJobs.requestVOD({ runnerToken })
34 if (availableJobs.length === 0) break
35
36 const { job } = await server.runnerJobs.accept<RunnerJobVODPayload>({ runnerToken, jobUUID: availableJobs[0].uuid })
37
38 const payload: RunnerJobSuccessPayload = {
39 videoFile: `video_short_${job.payload.output.resolution}p.mp4`,
40 resolutionPlaylistFile: `video_short_${job.payload.output.resolution}p.m3u8`
41 }
42 await server.runnerJobs.success({ runnerToken, jobUUID: job.uuid, jobToken: job.jobToken, payload })
43 } while (true)
44
45 await waitJobs([ server ])
46}
47
48describe('Test runner VOD transcoding', function () {
49 let servers: PeerTubeServer[] = []
50 let runnerToken: string
51
52 before(async function () {
53 this.timeout(120_000)
54
55 servers = await createMultipleServers(2)
56
57 await setAccessTokensToServers(servers)
58 await setDefaultVideoChannel(servers)
59
60 await doubleFollow(servers[0], servers[1])
61
62 await servers[0].config.enableRemoteTranscoding()
63 runnerToken = await servers[0].runners.autoRegisterRunner()
64 })
65
66 describe('Without transcoding', function () {
67
68 before(async function () {
69 this.timeout(60000)
70
71 await servers[0].config.disableTranscoding()
72 await servers[0].videos.quickUpload({ name: 'video' })
73
74 await waitJobs(servers)
75 })
76
77 it('Should not have available jobs', async function () {
78 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
79 expect(availableJobs).to.have.lengthOf(0)
80 })
81 })
82
83 describe('With classic transcoding enabled', function () {
84
85 before(async function () {
86 this.timeout(60000)
87
88 await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
89 })
90
91 it('Should error a transcoding job', async function () {
92 this.timeout(60000)
93
94 await servers[0].runnerJobs.cancelAllJobs()
95 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
96 await waitJobs(servers)
97
98 const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
99 const jobUUID = availableJobs[0].uuid
100
101 for (let i = 0; i < 5; i++) {
102 const { job } = await servers[0].runnerJobs.accept({ runnerToken, jobUUID })
103 const jobToken = job.jobToken
104
105 await servers[0].runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error' })
106 }
107
108 const video = await servers[0].videos.get({ id: uuid })
109 expect(video.state.id).to.equal(VideoState.TRANSCODING_FAILED)
110 })
111
112 it('Should cancel a transcoding job', async function () {
113 await servers[0].runnerJobs.cancelAllJobs()
114 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
115 await waitJobs(servers)
116
117 const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
118 const jobUUID = availableJobs[0].uuid
119
120 await servers[0].runnerJobs.cancelByAdmin({ jobUUID })
121
122 const video = await servers[0].videos.get({ id: uuid })
123 expect(video.state.id).to.equal(VideoState.PUBLISHED)
124 })
125 })
126
127 describe('Web video transcoding only', function () {
128 let videoUUID: string
129 let jobToken: string
130 let jobUUID: string
131
132 before(async function () {
133 this.timeout(60000)
134
135 await servers[0].runnerJobs.cancelAllJobs()
136 await servers[0].config.enableTranscoding({ hls: false, webVideo: true })
137
138 const { uuid } = await servers[0].videos.quickUpload({ name: 'web video', fixture: 'video_short.webm' })
139 videoUUID = uuid
140
141 await waitJobs(servers)
142 })
143
144 it('Should have jobs available for remote runners', async function () {
145 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
146 expect(availableJobs).to.have.lengthOf(1)
147
148 jobUUID = availableJobs[0].uuid
149 })
150
151 it('Should have a valid first transcoding job', async function () {
152 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
153 jobToken = job.jobToken
154
155 expect(job.type === 'vod-web-video-transcoding')
156 expect(job.payload.input.videoFileUrl).to.exist
157 expect(job.payload.output.resolution).to.equal(720)
158 expect(job.payload.output.fps).to.equal(25)
159
160 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
161 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.webm'))
162
163 expect(body).to.deep.equal(inputFile)
164 })
165
166 it('Should transcode the max video resolution and send it back to the server', async function () {
167 this.timeout(60000)
168
169 const payload: VODWebVideoTranscodingSuccess = {
170 videoFile: 'video_short.mp4'
171 }
172 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
173
174 await waitJobs(servers)
175 })
176
177 it('Should have the video updated', async function () {
178 for (const server of servers) {
179 const video = await server.videos.get({ id: videoUUID })
180 expect(video.files).to.have.lengthOf(1)
181 expect(video.streamingPlaylists).to.have.lengthOf(0)
182
183 const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
184 expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short.mp4')))
185 }
186 })
187
188 it('Should have 4 lower resolution to transcode', async function () {
189 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
190 expect(availableJobs).to.have.lengthOf(4)
191
192 for (const resolution of [ 480, 360, 240, 144 ]) {
193 const job = availableJobs.find(j => j.payload.output.resolution === resolution)
194 expect(job).to.exist
195 expect(job.type).to.equal('vod-web-video-transcoding')
196
197 if (resolution === 240) jobUUID = job.uuid
198 }
199 })
200
201 it('Should process one of these transcoding jobs', async function () {
202 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
203 jobToken = job.jobToken
204
205 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
206 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
207
208 expect(body).to.deep.equal(inputFile)
209
210 const payload: VODWebVideoTranscodingSuccess = { videoFile: `video_short_${job.payload.output.resolution}p.mp4` }
211 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
212 })
213
214 it('Should process all other jobs', async function () {
215 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
216 expect(availableJobs).to.have.lengthOf(3)
217
218 for (const resolution of [ 480, 360, 144 ]) {
219 const availableJob = availableJobs.find(j => j.payload.output.resolution === resolution)
220 expect(availableJob).to.exist
221 jobUUID = availableJob.uuid
222
223 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
224 jobToken = job.jobToken
225
226 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
227 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
228 expect(body).to.deep.equal(inputFile)
229
230 const payload: VODWebVideoTranscodingSuccess = { videoFile: `video_short_${resolution}p.mp4` }
231 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
232 }
233
234 await waitJobs(servers)
235 })
236
237 it('Should have the video updated', async function () {
238 for (const server of servers) {
239 const video = await server.videos.get({ id: videoUUID })
240 expect(video.files).to.have.lengthOf(5)
241 expect(video.streamingPlaylists).to.have.lengthOf(0)
242
243 const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
244 expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short.mp4')))
245
246 for (const file of video.files) {
247 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
248 await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
249 }
250 }
251 })
252
253 it('Should not have available jobs anymore', async function () {
254 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
255 expect(availableJobs).to.have.lengthOf(0)
256 })
257 })
258
259 describe('HLS transcoding only', function () {
260 let videoUUID: string
261 let jobToken: string
262 let jobUUID: string
263
264 before(async function () {
265 this.timeout(60000)
266
267 await servers[0].config.enableTranscoding({ hls: true, webVideo: false })
268
269 const { uuid } = await servers[0].videos.quickUpload({ name: 'hls video', fixture: 'video_short.webm' })
270 videoUUID = uuid
271
272 await waitJobs(servers)
273 })
274
275 it('Should run the optimize job', async function () {
276 this.timeout(60000)
277
278 await servers[0].runnerJobs.autoProcessWebVideoJob(runnerToken)
279 })
280
281 it('Should have 5 HLS resolution to transcode', async function () {
282 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
283 expect(availableJobs).to.have.lengthOf(5)
284
285 for (const resolution of [ 720, 480, 360, 240, 144 ]) {
286 const job = availableJobs.find(j => j.payload.output.resolution === resolution)
287 expect(job).to.exist
288 expect(job.type).to.equal('vod-hls-transcoding')
289
290 if (resolution === 480) jobUUID = job.uuid
291 }
292 })
293
294 it('Should process one of these transcoding jobs', async function () {
295 this.timeout(60000)
296
297 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
298 jobToken = job.jobToken
299
300 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
301 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
302
303 expect(body).to.deep.equal(inputFile)
304
305 const payload: VODHLSTranscodingSuccess = {
306 videoFile: 'video_short_480p.mp4',
307 resolutionPlaylistFile: 'video_short_480p.m3u8'
308 }
309 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
310
311 await waitJobs(servers)
312 })
313
314 it('Should have the video updated', async function () {
315 for (const server of servers) {
316 const video = await server.videos.get({ id: videoUUID })
317
318 expect(video.files).to.have.lengthOf(1)
319 expect(video.streamingPlaylists).to.have.lengthOf(1)
320
321 const hls = video.streamingPlaylists[0]
322 expect(hls.files).to.have.lengthOf(1)
323
324 await completeCheckHlsPlaylist({ videoUUID, hlsOnly: false, servers, resolutions: [ 480 ] })
325 }
326 })
327
328 it('Should process all other jobs', async function () {
329 this.timeout(60000)
330
331 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
332 expect(availableJobs).to.have.lengthOf(4)
333
334 let maxQualityFile = 'video_short.mp4'
335
336 for (const resolution of [ 720, 360, 240, 144 ]) {
337 const availableJob = availableJobs.find(j => j.payload.output.resolution === resolution)
338 expect(availableJob).to.exist
339 jobUUID = availableJob.uuid
340
341 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
342 jobToken = job.jobToken
343
344 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
345 const inputFile = await readFile(buildAbsoluteFixturePath(maxQualityFile))
346 expect(body).to.deep.equal(inputFile)
347
348 const payload: VODHLSTranscodingSuccess = {
349 videoFile: `video_short_${resolution}p.mp4`,
350 resolutionPlaylistFile: `video_short_${resolution}p.m3u8`
351 }
352 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
353
354 if (resolution === 720) {
355 maxQualityFile = 'video_short_720p.mp4'
356 }
357 }
358
359 await waitJobs(servers)
360 })
361
362 it('Should have the video updated', async function () {
363 for (const server of servers) {
364 const video = await server.videos.get({ id: videoUUID })
365
366 expect(video.files).to.have.lengthOf(0)
367 expect(video.streamingPlaylists).to.have.lengthOf(1)
368
369 const hls = video.streamingPlaylists[0]
370 expect(hls.files).to.have.lengthOf(5)
371
372 await completeCheckHlsPlaylist({ videoUUID, hlsOnly: true, servers, resolutions: [ 720, 480, 360, 240, 144 ] })
373 }
374 })
375
376 it('Should not have available jobs anymore', async function () {
377 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
378 expect(availableJobs).to.have.lengthOf(0)
379 })
380 })
381
382 describe('Web video and HLS transcoding', function () {
383
384 before(async function () {
385 this.timeout(60000)
386
387 await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
388
389 await servers[0].videos.quickUpload({ name: 'web video and hls video', fixture: 'video_short.webm' })
390
391 await waitJobs(servers)
392 })
393
394 it('Should process the first optimize job', async function () {
395 this.timeout(60000)
396
397 await servers[0].runnerJobs.autoProcessWebVideoJob(runnerToken)
398 })
399
400 it('Should have 9 jobs to process', async function () {
401 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
402
403 expect(availableJobs).to.have.lengthOf(9)
404
405 const webVideoJobs = availableJobs.filter(j => j.type === 'vod-web-video-transcoding')
406 const hlsJobs = availableJobs.filter(j => j.type === 'vod-hls-transcoding')
407
408 expect(webVideoJobs).to.have.lengthOf(4)
409 expect(hlsJobs).to.have.lengthOf(5)
410 })
411
412 it('Should process all available jobs', async function () {
413 await processAllJobs(servers[0], runnerToken)
414 })
415 })
416
417 describe('Audio merge transcoding', function () {
418 let videoUUID: string
419 let jobToken: string
420 let jobUUID: string
421
422 before(async function () {
423 this.timeout(60000)
424
425 await servers[0].config.enableTranscoding({ hls: true, webVideo: true })
426
427 const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' }
428 const { uuid } = await servers[0].videos.upload({ attributes, mode: 'legacy' })
429 videoUUID = uuid
430
431 await waitJobs(servers)
432 })
433
434 it('Should have an audio merge transcoding job', async function () {
435 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
436 expect(availableJobs).to.have.lengthOf(1)
437
438 expect(availableJobs[0].type).to.equal('vod-audio-merge-transcoding')
439
440 jobUUID = availableJobs[0].uuid
441 })
442
443 it('Should have a valid remote audio merge transcoding job', async function () {
444 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODAudioMergeTranscodingPayload>({ runnerToken, jobUUID })
445 jobToken = job.jobToken
446
447 expect(job.type === 'vod-audio-merge-transcoding')
448 expect(job.payload.input.audioFileUrl).to.exist
449 expect(job.payload.input.previewFileUrl).to.exist
450 expect(job.payload.output.resolution).to.equal(480)
451
452 {
453 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.audioFileUrl, jobToken, runnerToken })
454 const inputFile = await readFile(buildAbsoluteFixturePath('sample.ogg'))
455 expect(body).to.deep.equal(inputFile)
456 }
457
458 {
459 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.previewFileUrl, jobToken, runnerToken })
460
461 const video = await servers[0].videos.get({ id: videoUUID })
462 const { body: inputFile } = await makeGetRequest({
463 url: servers[0].url,
464 path: video.previewPath,
465 expectedStatus: HttpStatusCode.OK_200
466 })
467
468 expect(body).to.deep.equal(inputFile)
469 }
470 })
471
472 it('Should merge the audio', async function () {
473 this.timeout(60000)
474
475 const payload: VODAudioMergeTranscodingSuccess = { videoFile: 'video_short_480p.mp4' }
476 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
477
478 await waitJobs(servers)
479 })
480
481 it('Should have the video updated', async function () {
482 for (const server of servers) {
483 const video = await server.videos.get({ id: videoUUID })
484 expect(video.files).to.have.lengthOf(1)
485 expect(video.streamingPlaylists).to.have.lengthOf(0)
486
487 const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
488 expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short_480p.mp4')))
489 }
490 })
491
492 it('Should have 7 lower resolutions to transcode', async function () {
493 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
494 expect(availableJobs).to.have.lengthOf(7)
495
496 for (const resolution of [ 360, 240, 144 ]) {
497 const jobs = availableJobs.filter(j => j.payload.output.resolution === resolution)
498 expect(jobs).to.have.lengthOf(2)
499 }
500
501 jobUUID = availableJobs.find(j => j.payload.output.resolution === 480).uuid
502 })
503
504 it('Should process one other job', async function () {
505 this.timeout(60000)
506
507 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
508 jobToken = job.jobToken
509
510 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
511 const inputFile = await readFile(buildAbsoluteFixturePath('video_short_480p.mp4'))
512 expect(body).to.deep.equal(inputFile)
513
514 const payload: VODHLSTranscodingSuccess = {
515 videoFile: `video_short_480p.mp4`,
516 resolutionPlaylistFile: `video_short_480p.m3u8`
517 }
518 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
519
520 await waitJobs(servers)
521 })
522
523 it('Should have the video updated', async function () {
524 for (const server of servers) {
525 const video = await server.videos.get({ id: videoUUID })
526
527 expect(video.files).to.have.lengthOf(1)
528 expect(video.streamingPlaylists).to.have.lengthOf(1)
529
530 const hls = video.streamingPlaylists[0]
531 expect(hls.files).to.have.lengthOf(1)
532
533 await completeCheckHlsPlaylist({ videoUUID, hlsOnly: false, servers, resolutions: [ 480 ] })
534 }
535 })
536
537 it('Should process all available jobs', async function () {
538 await processAllJobs(servers[0], runnerToken)
539 })
540 })
541
542 after(async function () {
543 await cleanupTests(servers)
544 })
545})