1 /* tslint:disable:no-unused-expression */
3 import * as chai from 'chai'
5 import { omit } from 'lodash'
6 import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
7 import { audio, canDoQuickTranscode, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
9 buildAbsoluteFixturePath,
12 flushAndRunMultipleServers,
13 generateHighBitrateVideo,
14 generateVideoWithFramerate,
21 setAccessTokensToServers,
25 } from '../../../../shared/extra-utils'
26 import { join } from 'path'
27 import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
29 const expect = chai.expect
31 describe('Test video transcoding', function () {
32 let servers: ServerInfo[] = []
34 before(async function () {
38 servers = await flushAndRunMultipleServers(2)
40 await setAccessTokensToServers(servers)
42 await doubleFollow(servers[0], servers[1])
45 it('Should not transcode video on server 1', async function () {
48 const videoAttributes = {
49 name: 'my super name for server 1',
50 description: 'my super description for server 1',
51 fixture: 'video_short.webm'
53 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
55 await waitJobs(servers)
57 for (const server of servers) {
58 const res = await getVideosList(server.url)
59 const video = res.body.data[ 0 ]
61 const res2 = await getVideo(server.url, video.id)
62 const videoDetails = res2.body
63 expect(videoDetails.files).to.have.lengthOf(1)
65 const magnetUri = videoDetails.files[ 0 ].magnetUri
66 expect(magnetUri).to.match(/\.webm/)
68 const torrent = await webtorrentAdd(magnetUri, true)
69 expect(torrent.files).to.be.an('array')
70 expect(torrent.files.length).to.equal(1)
71 expect(torrent.files[ 0 ].path).match(/\.webm$/)
75 it('Should transcode video on server 2', async function () {
78 const videoAttributes = {
79 name: 'my super name for server 2',
80 description: 'my super description for server 2',
81 fixture: 'video_short.webm'
83 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
85 await waitJobs(servers)
87 for (const server of servers) {
88 const res = await getVideosList(server.url)
90 const video = res.body.data.find(v => v.name === videoAttributes.name)
91 const res2 = await getVideo(server.url, video.id)
92 const videoDetails = res2.body
94 expect(videoDetails.files).to.have.lengthOf(4)
96 const magnetUri = videoDetails.files[ 0 ].magnetUri
97 expect(magnetUri).to.match(/\.mp4/)
99 const torrent = await webtorrentAdd(magnetUri, true)
100 expect(torrent.files).to.be.an('array')
101 expect(torrent.files.length).to.equal(1)
102 expect(torrent.files[ 0 ].path).match(/\.mp4$/)
106 it('Should transcode high bit rate mp3 to proper bit rate', async function () {
109 const videoAttributes = {
111 fixture: 'video_short_mp3_256k.mp4'
113 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
115 await waitJobs(servers)
117 for (const server of servers) {
118 const res = await getVideosList(server.url)
120 const video = res.body.data.find(v => v.name === videoAttributes.name)
121 const res2 = await getVideo(server.url, video.id)
122 const videoDetails: VideoDetails = res2.body
124 expect(videoDetails.files).to.have.lengthOf(4)
126 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
127 const probe = await audio.get(path)
129 if (probe.audioStream) {
130 expect(probe.audioStream[ 'codec_name' ]).to.be.equal('aac')
131 expect(probe.audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000)
133 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath)
138 it('Should transcode video with no audio and have no audio itself', async function () {
141 const videoAttributes = {
143 fixture: 'video_short_no_audio.mp4'
145 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
147 await waitJobs(servers)
149 for (const server of servers) {
150 const res = await getVideosList(server.url)
152 const video = res.body.data.find(v => v.name === videoAttributes.name)
153 const res2 = await getVideo(server.url, video.id)
154 const videoDetails: VideoDetails = res2.body
156 expect(videoDetails.files).to.have.lengthOf(4)
157 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
158 const probe = await audio.get(path)
159 expect(probe).to.not.have.property('audioStream')
163 it('Should leave the audio untouched, but properly transcode the video', async function () {
166 const videoAttributes = {
167 name: 'untouched_audio',
168 fixture: 'video_short.mp4'
170 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
172 await waitJobs(servers)
174 for (const server of servers) {
175 const res = await getVideosList(server.url)
177 const video = res.body.data.find(v => v.name === videoAttributes.name)
178 const res2 = await getVideo(server.url, video.id)
179 const videoDetails: VideoDetails = res2.body
181 expect(videoDetails.files).to.have.lengthOf(4)
182 const fixturePath = buildAbsoluteFixturePath(videoAttributes.fixture)
183 const fixtureVideoProbe = await audio.get(fixturePath)
184 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4')
185 const videoProbe = await audio.get(path)
186 if (videoProbe.audioStream && fixtureVideoProbe.audioStream) {
187 const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ]
188 expect(omit(videoProbe.audioStream, toOmit)).to.be.deep.equal(omit(fixtureVideoProbe.audioStream, toOmit))
190 this.fail('Could not retrieve the audio stream on ' + videoProbe.absolutePath)
195 it('Should transcode a 60 FPS video', async function () {
198 const videoAttributes = {
199 name: 'my super 30fps name for server 2',
200 description: 'my super 30fps description for server 2',
201 fixture: '60fps_720p_small.mp4'
203 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
205 await waitJobs(servers)
207 for (const server of servers) {
208 const res = await getVideosList(server.url)
210 const video = res.body.data.find(v => v.name === videoAttributes.name)
211 const res2 = await getVideo(server.url, video.id)
212 const videoDetails: VideoDetails = res2.body
214 expect(videoDetails.files).to.have.lengthOf(4)
215 expect(videoDetails.files[ 0 ].fps).to.be.above(58).and.below(62)
216 expect(videoDetails.files[ 1 ].fps).to.be.below(31)
217 expect(videoDetails.files[ 2 ].fps).to.be.below(31)
218 expect(videoDetails.files[ 3 ].fps).to.be.below(31)
220 for (const resolution of [ '240', '360', '480' ]) {
221 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
222 const fps = await getVideoFileFPS(path)
224 expect(fps).to.be.below(31)
227 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-720.mp4')
228 const fps = await getVideoFileFPS(path)
230 expect(fps).to.be.above(58).and.below(62)
234 it('Should wait for transcoding before publishing the video', async function () {
238 // Upload the video, but wait transcoding
239 const videoAttributes = {
240 name: 'waiting video',
241 fixture: 'video_short1.webm',
242 waitTranscoding: true
244 const resVideo = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes)
245 const videoId = resVideo.body.video.uuid
247 // Should be in transcode state
248 const { body } = await getVideo(servers[ 1 ].url, videoId)
249 expect(body.name).to.equal('waiting video')
250 expect(body.state.id).to.equal(VideoState.TO_TRANSCODE)
251 expect(body.state.label).to.equal('To transcode')
252 expect(body.waitTranscoding).to.be.true
254 // Should have my video
255 const resMyVideos = await getMyVideos(servers[1].url, servers[1].accessToken, 0, 10)
256 const videoToFindInMine = resMyVideos.body.data.find(v => v.name === videoAttributes.name)
257 expect(videoToFindInMine).not.to.be.undefined
258 expect(videoToFindInMine.state.id).to.equal(VideoState.TO_TRANSCODE)
259 expect(videoToFindInMine.state.label).to.equal('To transcode')
260 expect(videoToFindInMine.waitTranscoding).to.be.true
262 // Should not list this video
263 const resVideos = await getVideosList(servers[1].url)
264 const videoToFindInList = resVideos.body.data.find(v => v.name === videoAttributes.name)
265 expect(videoToFindInList).to.be.undefined
267 // Server 1 should not have the video yet
268 await getVideo(servers[0].url, videoId, 404)
271 await waitJobs(servers)
273 for (const server of servers) {
274 const res = await getVideosList(server.url)
275 const videoToFind = res.body.data.find(v => v.name === 'waiting video')
276 expect(videoToFind).not.to.be.undefined
278 const res2 = await getVideo(server.url, videoToFind.id)
279 const videoDetails: VideoDetails = res2.body
281 expect(videoDetails.state.id).to.equal(VideoState.PUBLISHED)
282 expect(videoDetails.state.label).to.equal('Published')
283 expect(videoDetails.waitTranscoding).to.be.true
287 it('Should respect maximum bitrate values', async function () {
290 let tempFixturePath: string
293 tempFixturePath = await generateHighBitrateVideo()
295 const bitrate = await getVideoFileBitrate(tempFixturePath)
296 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
299 const videoAttributes = {
300 name: 'high bitrate video',
301 description: 'high bitrate video',
302 fixture: tempFixturePath
305 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
307 await waitJobs(servers)
309 for (const server of servers) {
310 const res = await getVideosList(server.url)
312 const video = res.body.data.find(v => v.name === videoAttributes.name)
314 for (const resolution of ['240', '360', '480', '720', '1080']) {
315 const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4')
316 const bitrate = await getVideoFileBitrate(path)
317 const fps = await getVideoFileFPS(path)
318 const resolution2 = await getVideoFileResolution(path)
320 expect(resolution2.videoFileResolution.toString()).to.equal(resolution)
321 expect(bitrate).to.be.below(getMaxBitrate(resolution2.videoFileResolution, fps, VIDEO_TRANSCODING_FPS))
326 it('Should accept and transcode additional extensions', async function () {
329 let tempFixturePath: string
332 tempFixturePath = await generateHighBitrateVideo()
334 const bitrate = await getVideoFileBitrate(tempFixturePath)
335 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 25, VIDEO_TRANSCODING_FPS))
338 for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
339 const videoAttributes = {
344 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes)
346 await waitJobs(servers)
348 for (const server of servers) {
349 const res = await getVideosList(server.url)
351 const video = res.body.data.find(v => v.name === videoAttributes.name)
352 const res2 = await getVideo(server.url, video.id)
353 const videoDetails = res2.body
355 expect(videoDetails.files).to.have.lengthOf(4)
357 const magnetUri = videoDetails.files[ 0 ].magnetUri
358 expect(magnetUri).to.contain('.mp4')
363 it('Should correctly detect if quick transcode is possible', async function () {
366 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
367 expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
370 it('Should merge an audio file with the preview file', async function () {
373 const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' }
374 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg)
376 await waitJobs(servers)
378 for (const server of servers) {
379 const res = await getVideosList(server.url)
381 const video = res.body.data.find(v => v.name === 'audio_with_preview')
382 const res2 = await getVideo(server.url, video.id)
383 const videoDetails: VideoDetails = res2.body
385 expect(videoDetails.files).to.have.lengthOf(1)
387 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
388 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
390 const magnetUri = videoDetails.files[ 0 ].magnetUri
391 expect(magnetUri).to.contain('.mp4')
395 it('Should upload an audio file and choose a default background image', async function () {
398 const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' }
399 await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg)
401 await waitJobs(servers)
403 for (const server of servers) {
404 const res = await getVideosList(server.url)
406 const video = res.body.data.find(v => v.name === 'audio_without_preview')
407 const res2 = await getVideo(server.url, video.id)
408 const videoDetails = res2.body
410 expect(videoDetails.files).to.have.lengthOf(1)
412 await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 })
413 await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 })
415 const magnetUri = videoDetails.files[ 0 ].magnetUri
416 expect(magnetUri).to.contain('.mp4')
420 it('Should downscale to the closest divisor standard framerate', async function () {
423 let tempFixturePath: string
426 tempFixturePath = await generateVideoWithFramerate(59)
428 const fps = await getVideoFileFPS(tempFixturePath)
429 expect(fps).to.be.equal(59)
432 const videoAttributes = {
434 description: '59fps video',
435 fixture: tempFixturePath
438 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
440 await waitJobs(servers)
442 for (const server of servers) {
443 const res = await getVideosList(server.url)
445 const video = res.body.data.find(v => v.name === videoAttributes.name)
448 const path = join(root(), 'test' + servers[ 1 ].internalServerNumber, 'videos', video.uuid + '-240.mp4')
449 const fps = await getVideoFileFPS(path)
450 expect(fps).to.be.equal(25)
454 const path = join(root(), 'test' + servers[ 1 ].internalServerNumber, 'videos', video.uuid + '-720.mp4')
455 const fps = await getVideoFileFPS(path)
456 expect(fps).to.be.equal(59)
461 after(async function () {
462 await cleanupTests(servers)