1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
3 import { expect } from 'chai'
4 import { readFile } from 'fs-extra'
5 import { completeCheckHlsPlaylist } from '@server/tests/shared'
6 import { buildAbsoluteFixturePath } from '@shared/core-utils'
9 RunnerJobSuccessPayload,
10 RunnerJobVODAudioMergeTranscodingPayload,
11 RunnerJobVODHLSTranscodingPayload,
13 RunnerJobVODWebVideoTranscodingPayload,
15 VODAudioMergeTranscodingSuccess,
16 VODHLSTranscodingSuccess,
17 VODWebVideoTranscodingSuccess
18 } from '@shared/models'
21 createMultipleServers,
26 setAccessTokensToServers,
27 setDefaultVideoChannel,
29 } from '@shared/server-commands'
31 async function processAllJobs (server: PeerTubeServer, runnerToken: string) {
33 const { availableJobs } = await server.runnerJobs.requestVOD({ runnerToken })
34 if (availableJobs.length === 0) break
36 const { job } = await server.runnerJobs.accept<RunnerJobVODPayload>({ runnerToken, jobUUID: availableJobs[0].uuid })
38 const payload: RunnerJobSuccessPayload = {
39 videoFile: `video_short_${job.payload.output.resolution}p.mp4`,
40 resolutionPlaylistFile: `video_short_${job.payload.output.resolution}p.m3u8`
42 await server.runnerJobs.success({ runnerToken, jobUUID: job.uuid, jobToken: job.jobToken, payload })
45 await waitJobs([ server ])
48 describe('Test runner VOD transcoding', function () {
49 let servers: PeerTubeServer[] = []
50 let runnerToken: string
52 before(async function () {
55 servers = await createMultipleServers(2)
57 await setAccessTokensToServers(servers)
58 await setDefaultVideoChannel(servers)
60 await doubleFollow(servers[0], servers[1])
62 await servers[0].config.enableRemoteTranscoding()
63 runnerToken = await servers[0].runners.autoRegisterRunner()
66 describe('Without transcoding', function () {
68 before(async function () {
71 await servers[0].config.disableTranscoding()
72 await servers[0].videos.quickUpload({ name: 'video' })
74 await waitJobs(servers)
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)
83 describe('With classic transcoding enabled', function () {
85 before(async function () {
88 await servers[0].config.enableTranscoding(true, true)
91 it('Should error a transcoding job', async function () {
94 await servers[0].runnerJobs.cancelAllJobs()
95 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
96 await waitJobs(servers)
98 const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
99 const jobUUID = availableJobs[0].uuid
101 const { job } = await servers[0].runnerJobs.accept({ runnerToken, jobUUID })
102 const jobToken = job.jobToken
104 await servers[0].runnerJobs.error({ runnerToken, jobUUID, jobToken, message: 'Error' })
106 const video = await servers[0].videos.get({ id: uuid })
107 expect(video.state.id).to.equal(VideoState.TRANSCODING_FAILED)
110 it('Should cancel a transcoding job', async function () {
111 await servers[0].runnerJobs.cancelAllJobs()
112 const { uuid } = await servers[0].videos.quickUpload({ name: 'video' })
113 await waitJobs(servers)
115 const { availableJobs } = await servers[0].runnerJobs.request({ runnerToken })
116 const jobUUID = availableJobs[0].uuid
118 await servers[0].runnerJobs.cancelByAdmin({ jobUUID })
120 const video = await servers[0].videos.get({ id: uuid })
121 expect(video.state.id).to.equal(VideoState.PUBLISHED)
125 describe('Web video transcoding only', function () {
126 let videoUUID: string
130 before(async function () {
133 await servers[0].runnerJobs.cancelAllJobs()
134 await servers[0].config.enableTranscoding(true, false)
136 const { uuid } = await servers[0].videos.quickUpload({ name: 'web video', fixture: 'video_short.webm' })
139 await waitJobs(servers)
142 it('Should have jobs available for remote runners', async function () {
143 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
144 expect(availableJobs).to.have.lengthOf(1)
146 jobUUID = availableJobs[0].uuid
149 it('Should have a valid first transcoding job', async function () {
150 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
151 jobToken = job.jobToken
153 expect(job.type === 'vod-web-video-transcoding')
154 expect(job.payload.input.videoFileUrl).to.exist
155 expect(job.payload.output.resolution).to.equal(720)
156 expect(job.payload.output.fps).to.equal(25)
158 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
159 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.webm'))
161 expect(body).to.deep.equal(inputFile)
164 it('Should transcode the max video resolution and send it back to the server', async function () {
167 const payload: VODWebVideoTranscodingSuccess = {
168 videoFile: 'video_short.mp4'
170 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
172 await waitJobs(servers)
175 it('Should have the video updated', async function () {
176 for (const server of servers) {
177 const video = await server.videos.get({ id: videoUUID })
178 expect(video.files).to.have.lengthOf(1)
179 expect(video.streamingPlaylists).to.have.lengthOf(0)
181 const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
182 expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short.mp4')))
186 it('Should have 4 lower resolution to transcode', async function () {
187 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
188 expect(availableJobs).to.have.lengthOf(4)
190 for (const resolution of [ 480, 360, 240, 144 ]) {
191 const job = availableJobs.find(j => j.payload.output.resolution === resolution)
193 expect(job.type).to.equal('vod-web-video-transcoding')
195 if (resolution === 240) jobUUID = job.uuid
199 it('Should process one of these transcoding jobs', async function () {
200 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
201 jobToken = job.jobToken
203 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
204 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
206 expect(body).to.deep.equal(inputFile)
208 const payload: VODWebVideoTranscodingSuccess = { videoFile: `video_short_${job.payload.output.resolution}p.mp4` }
209 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
212 it('Should process all other jobs', async function () {
213 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
214 expect(availableJobs).to.have.lengthOf(3)
216 for (const resolution of [ 480, 360, 144 ]) {
217 const availableJob = availableJobs.find(j => j.payload.output.resolution === resolution)
218 expect(availableJob).to.exist
219 jobUUID = availableJob.uuid
221 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODWebVideoTranscodingPayload>({ runnerToken, jobUUID })
222 jobToken = job.jobToken
224 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
225 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
226 expect(body).to.deep.equal(inputFile)
228 const payload: VODWebVideoTranscodingSuccess = { videoFile: `video_short_${resolution}p.mp4` }
229 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
232 await waitJobs(servers)
235 it('Should have the video updated', async function () {
236 for (const server of servers) {
237 const video = await server.videos.get({ id: videoUUID })
238 expect(video.files).to.have.lengthOf(5)
239 expect(video.streamingPlaylists).to.have.lengthOf(0)
241 const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
242 expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short.mp4')))
244 for (const file of video.files) {
245 await makeRawRequest({ url: file.fileUrl, expectedStatus: HttpStatusCode.OK_200 })
246 await makeRawRequest({ url: file.torrentUrl, expectedStatus: HttpStatusCode.OK_200 })
251 it('Should not have available jobs anymore', async function () {
252 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
253 expect(availableJobs).to.have.lengthOf(0)
257 describe('HLS transcoding only', function () {
258 let videoUUID: string
262 before(async function () {
265 await servers[0].config.enableTranscoding(false, true)
267 const { uuid } = await servers[0].videos.quickUpload({ name: 'hls video', fixture: 'video_short.webm' })
270 await waitJobs(servers)
273 it('Should run the optimize job', async function () {
276 await servers[0].runnerJobs.autoProcessWebVideoJob(runnerToken)
279 it('Should have 5 HLS resolution to transcode', async function () {
280 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
281 expect(availableJobs).to.have.lengthOf(5)
283 for (const resolution of [ 720, 480, 360, 240, 144 ]) {
284 const job = availableJobs.find(j => j.payload.output.resolution === resolution)
286 expect(job.type).to.equal('vod-hls-transcoding')
288 if (resolution === 480) jobUUID = job.uuid
292 it('Should process one of these transcoding jobs', async function () {
295 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
296 jobToken = job.jobToken
298 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
299 const inputFile = await readFile(buildAbsoluteFixturePath('video_short.mp4'))
301 expect(body).to.deep.equal(inputFile)
303 const payload: VODHLSTranscodingSuccess = {
304 videoFile: 'video_short_480p.mp4',
305 resolutionPlaylistFile: 'video_short_480p.m3u8'
307 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
309 await waitJobs(servers)
312 it('Should have the video updated', async function () {
313 for (const server of servers) {
314 const video = await server.videos.get({ id: videoUUID })
316 expect(video.files).to.have.lengthOf(1)
317 expect(video.streamingPlaylists).to.have.lengthOf(1)
319 const hls = video.streamingPlaylists[0]
320 expect(hls.files).to.have.lengthOf(1)
322 await completeCheckHlsPlaylist({ videoUUID, hlsOnly: false, servers, resolutions: [ 480 ] })
326 it('Should process all other jobs', async function () {
329 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
330 expect(availableJobs).to.have.lengthOf(4)
332 let maxQualityFile = 'video_short.mp4'
334 for (const resolution of [ 720, 360, 240, 144 ]) {
335 const availableJob = availableJobs.find(j => j.payload.output.resolution === resolution)
336 expect(availableJob).to.exist
337 jobUUID = availableJob.uuid
339 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
340 jobToken = job.jobToken
342 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
343 const inputFile = await readFile(buildAbsoluteFixturePath(maxQualityFile))
344 expect(body).to.deep.equal(inputFile)
346 const payload: VODHLSTranscodingSuccess = {
347 videoFile: `video_short_${resolution}p.mp4`,
348 resolutionPlaylistFile: `video_short_${resolution}p.m3u8`
350 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
352 if (resolution === 720) {
353 maxQualityFile = 'video_short_720p.mp4'
357 await waitJobs(servers)
360 it('Should have the video updated', async function () {
361 for (const server of servers) {
362 const video = await server.videos.get({ id: videoUUID })
364 expect(video.files).to.have.lengthOf(0)
365 expect(video.streamingPlaylists).to.have.lengthOf(1)
367 const hls = video.streamingPlaylists[0]
368 expect(hls.files).to.have.lengthOf(5)
370 await completeCheckHlsPlaylist({ videoUUID, hlsOnly: true, servers, resolutions: [ 720, 480, 360, 240, 144 ] })
374 it('Should not have available jobs anymore', async function () {
375 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
376 expect(availableJobs).to.have.lengthOf(0)
380 describe('Web video and HLS transcoding', function () {
382 before(async function () {
385 await servers[0].config.enableTranscoding(true, true)
387 await servers[0].videos.quickUpload({ name: 'web video and hls video', fixture: 'video_short.webm' })
389 await waitJobs(servers)
392 it('Should process the first optimize job', async function () {
395 await servers[0].runnerJobs.autoProcessWebVideoJob(runnerToken)
398 it('Should have 9 jobs to process', async function () {
399 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
401 expect(availableJobs).to.have.lengthOf(9)
403 const webVideoJobs = availableJobs.filter(j => j.type === 'vod-web-video-transcoding')
404 const hlsJobs = availableJobs.filter(j => j.type === 'vod-hls-transcoding')
406 expect(webVideoJobs).to.have.lengthOf(4)
407 expect(hlsJobs).to.have.lengthOf(5)
410 it('Should process all available jobs', async function () {
411 await processAllJobs(servers[0], runnerToken)
415 describe('Audio merge transcoding', function () {
416 let videoUUID: string
420 before(async function () {
423 await servers[0].config.enableTranscoding(true, true)
425 const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
426 const { uuid } = await servers[0].videos.upload({ attributes, mode: 'legacy' })
429 await waitJobs(servers)
432 it('Should have an audio merge transcoding job', async function () {
433 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
434 expect(availableJobs).to.have.lengthOf(1)
436 expect(availableJobs[0].type).to.equal('vod-audio-merge-transcoding')
438 jobUUID = availableJobs[0].uuid
441 it('Should have a valid remote audio merge transcoding job', async function () {
442 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODAudioMergeTranscodingPayload>({ runnerToken, jobUUID })
443 jobToken = job.jobToken
445 expect(job.type === 'vod-audio-merge-transcoding')
446 expect(job.payload.input.audioFileUrl).to.exist
447 expect(job.payload.input.previewFileUrl).to.exist
448 expect(job.payload.output.resolution).to.equal(480)
451 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.audioFileUrl, jobToken, runnerToken })
452 const inputFile = await readFile(buildAbsoluteFixturePath('sample.ogg'))
453 expect(body).to.deep.equal(inputFile)
457 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.previewFileUrl, jobToken, runnerToken })
459 const video = await servers[0].videos.get({ id: videoUUID })
460 const { body: inputFile } = await makeGetRequest({
462 path: video.previewPath,
463 expectedStatus: HttpStatusCode.OK_200
466 expect(body).to.deep.equal(inputFile)
470 it('Should merge the audio', async function () {
473 const payload: VODAudioMergeTranscodingSuccess = { videoFile: 'video_short_480p.mp4' }
474 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
476 await waitJobs(servers)
479 it('Should have the video updated', async function () {
480 for (const server of servers) {
481 const video = await server.videos.get({ id: videoUUID })
482 expect(video.files).to.have.lengthOf(1)
483 expect(video.streamingPlaylists).to.have.lengthOf(0)
485 const { body } = await makeRawRequest({ url: video.files[0].fileUrl, expectedStatus: HttpStatusCode.OK_200 })
486 expect(body).to.deep.equal(await readFile(buildAbsoluteFixturePath('video_short_480p.mp4')))
490 it('Should have 7 lower resolutions to transcode', async function () {
491 const { availableJobs } = await servers[0].runnerJobs.requestVOD({ runnerToken })
492 expect(availableJobs).to.have.lengthOf(7)
494 for (const resolution of [ 360, 240, 144 ]) {
495 const jobs = availableJobs.filter(j => j.payload.output.resolution === resolution)
496 expect(jobs).to.have.lengthOf(2)
499 jobUUID = availableJobs.find(j => j.payload.output.resolution === 480).uuid
502 it('Should process one other job', async function () {
505 const { job } = await servers[0].runnerJobs.accept<RunnerJobVODHLSTranscodingPayload>({ runnerToken, jobUUID })
506 jobToken = job.jobToken
508 const { body } = await servers[0].runnerJobs.getJobFile({ url: job.payload.input.videoFileUrl, jobToken, runnerToken })
509 const inputFile = await readFile(buildAbsoluteFixturePath('video_short_480p.mp4'))
510 expect(body).to.deep.equal(inputFile)
512 const payload: VODHLSTranscodingSuccess = {
513 videoFile: `video_short_480p.mp4`,
514 resolutionPlaylistFile: `video_short_480p.m3u8`
516 await servers[0].runnerJobs.success({ runnerToken, jobUUID, jobToken, payload })
518 await waitJobs(servers)
521 it('Should have the video updated', async function () {
522 for (const server of servers) {
523 const video = await server.videos.get({ id: videoUUID })
525 expect(video.files).to.have.lengthOf(1)
526 expect(video.streamingPlaylists).to.have.lengthOf(1)
528 const hls = video.streamingPlaylists[0]
529 expect(hls.files).to.have.lengthOf(1)
531 await completeCheckHlsPlaylist({ videoUUID, hlsOnly: false, servers, resolutions: [ 480 ] })
535 it('Should process all available jobs', async function () {
536 await processAllJobs(servers[0], runnerToken)
540 after(async function () {
541 await cleanupTests(servers)