]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/tests/api/videos/video-transcoder.ts
Merge branch 'develop' of https://github.com/Chocobozzz/PeerTube into move-utils...
[github/Chocobozzz/PeerTube.git] / server / tests / api / videos / video-transcoder.ts
1 /* tslint:disable:no-unused-expression */
2
3 import * as chai from 'chai'
4 import 'mocha'
5 import { omit } from 'lodash'
6 import * as ffmpeg from 'fluent-ffmpeg'
7 import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
8 import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
9 import {
10 buildAbsoluteFixturePath,
11 doubleFollow,
12 flushAndRunMultipleServers,
13 getMyVideos,
14 getVideo,
15 getVideosList,
16 killallServers,
17 root,
18 ServerInfo,
19 setAccessTokensToServers,
20 uploadVideo,
21 webtorrentAdd,
22 generateHighBitrateVideo
23 } from '../../../../shared/utils'
24 import { join } from 'path'
25 import { waitJobs } from '../../../../shared/utils/server/jobs'
26 import { pathExists } from 'fs-extra'
27 import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
28
29 const expect = chai.expect
30
31 describe('Test video transcoding', function () {
32 let servers: ServerInfo[] = []
33
34 before(async function () {
35 this.timeout(30000)
36
37 // Run servers
38 servers = await flushAndRunMultipleServers(2)
39
40 await setAccessTokensToServers(servers)
41
42 await doubleFollow(servers[0], servers[1])
43 })
44
45 it('Should not transcode video on server 1', async function () {
46 this.timeout(60000)
47
48 const videoAttributes = {
49 name: 'my super name for server 1',
50 description: 'my super description for server 1',
51 fixture: 'video_short.webm'
52 }
53 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
54
55 await waitJobs(servers)
56
57 for (const server of servers) {
58 const res = await getVideosList(server.url)
59 const video = res.body.data[ 0 ]
60
61 const res2 = await getVideo(server.url, video.id)
62 const videoDetails = res2.body
63 expect(videoDetails.files).to.have.lengthOf(1)
64
65 const magnetUri = videoDetails.files[ 0 ].magnetUri
66 expect(magnetUri).to.match(/\.webm/)
67
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$/)
72 }
73 })
74
75 it('Should transcode video on server 2', async function () {
76 this.timeout(60000)
77
78 const videoAttributes = {
79 name: 'my super name for server 2',
80 description: 'my super description for server 2',
81 fixture: 'video_short.webm'
82 }
83 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
84
85 await waitJobs(servers)
86
87 for (const server of servers) {
88 const res = await getVideosList(server.url)
89
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
93
94 expect(videoDetails.files).to.have.lengthOf(4)
95
96 const magnetUri = videoDetails.files[ 0 ].magnetUri
97 expect(magnetUri).to.match(/\.mp4/)
98
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$/)
103 }
104 })
105
106 it('Should transcode high bit rate mp3 to proper bit rate', async function () {
107 this.timeout(60000)
108
109 const videoAttributes = {
110 name: 'mp3_256k',
111 fixture: 'video_short_mp3_256k.mp4'
112 }
113 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
114
115 await waitJobs(servers)
116
117 for (const server of servers) {
118 const res = await getVideosList(server.url)
119
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
123
124 expect(videoDetails.files).to.have.lengthOf(4)
125
126 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
127 const probe = await audio.get(path)
128
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)
132 } else {
133 this.fail('Could not retrieve the audio stream on ' + probe.absolutePath)
134 }
135 }
136 })
137
138 it('Should transcode video with no audio and have no audio itself', async function () {
139 this.timeout(60000)
140
141 const videoAttributes = {
142 name: 'no_audio',
143 fixture: 'video_short_no_audio.mp4'
144 }
145 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
146
147 await waitJobs(servers)
148
149 for (const server of servers) {
150 const res = await getVideosList(server.url)
151
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
155
156 expect(videoDetails.files).to.have.lengthOf(4)
157 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
158 const probe = await audio.get(path)
159 expect(probe).to.not.have.property('audioStream')
160 }
161 })
162
163 it('Should leave the audio untouched, but properly transcode the video', async function () {
164 this.timeout(60000)
165
166 const videoAttributes = {
167 name: 'untouched_audio',
168 fixture: 'video_short.mp4'
169 }
170 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
171
172 await waitJobs(servers)
173
174 for (const server of servers) {
175 const res = await getVideosList(server.url)
176
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
180
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(), 'test2', '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))
189 } else {
190 this.fail('Could not retrieve the audio stream on ' + videoProbe.absolutePath)
191 }
192 }
193 })
194
195 it('Should transcode a 60 FPS video', async function () {
196 this.timeout(60000)
197
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'
202 }
203 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
204
205 await waitJobs(servers)
206
207 for (const server of servers) {
208 const res = await getVideosList(server.url)
209
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
213
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)
219
220 for (const resolution of [ '240', '360', '480' ]) {
221 const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4')
222 const fps = await getVideoFileFPS(path)
223
224 expect(fps).to.be.below(31)
225 }
226
227 const path = join(root(), 'test2', 'videos', video.uuid + '-720.mp4')
228 const fps = await getVideoFileFPS(path)
229
230 expect(fps).to.be.above(58).and.below(62)
231 }
232 })
233
234 it('Should wait for transcoding before publishing the video', async function () {
235 this.timeout(80000)
236
237 {
238 // Upload the video, but wait transcoding
239 const videoAttributes = {
240 name: 'waiting video',
241 fixture: 'video_short1.webm',
242 waitTranscoding: true
243 }
244 const resVideo = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes)
245 const videoId = resVideo.body.video.uuid
246
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
253
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
261
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
266
267 // Server 1 should not have the video yet
268 await getVideo(servers[0].url, videoId, 404)
269 }
270
271 await waitJobs(servers)
272
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
277
278 const res2 = await getVideo(server.url, videoToFind.id)
279 const videoDetails: VideoDetails = res2.body
280
281 expect(videoDetails.state.id).to.equal(VideoState.PUBLISHED)
282 expect(videoDetails.state.label).to.equal('Published')
283 expect(videoDetails.waitTranscoding).to.be.true
284 }
285 })
286
287 it('Should respect maximum bitrate values', async function () {
288 this.timeout(160000)
289
290 let tempFixturePath: string
291
292 {
293 tempFixturePath = await generateHighBitrateVideo()
294
295 const bitrate = await getVideoFileBitrate(tempFixturePath)
296 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
297 }
298
299 const videoAttributes = {
300 name: 'high bitrate video',
301 description: 'high bitrate video',
302 fixture: tempFixturePath
303 }
304
305 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
306
307 await waitJobs(servers)
308
309 for (const server of servers) {
310 const res = await getVideosList(server.url)
311
312 const video = res.body.data.find(v => v.name === videoAttributes.name)
313
314 for (const resolution of ['240', '360', '480', '720', '1080']) {
315 const path = join(root(), 'test2', 'videos', video.uuid + '-' + resolution + '.mp4')
316 const bitrate = await getVideoFileBitrate(path)
317 const fps = await getVideoFileFPS(path)
318 const resolution2 = await getVideoFileResolution(path)
319
320 expect(resolution2.videoFileResolution.toString()).to.equal(resolution)
321 expect(bitrate).to.be.below(getMaxBitrate(resolution2.videoFileResolution, fps, VIDEO_TRANSCODING_FPS))
322 }
323 }
324 })
325
326 after(async function () {
327 killallServers(servers)
328 })
329 })