]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/tests/api/videos/video-transcoder.ts
Speedup peertube startup
[github/Chocobozzz/PeerTube.git] / server / tests / api / videos / video-transcoder.ts
CommitLineData
0e1dc3e7
C
1/* tslint:disable:no-unused-expression */
2
0e1dc3e7 3import * as chai from 'chai'
a7ba16b6 4import 'mocha'
7160878c
RK
5import { omit } from 'lodash'
6import * as ffmpeg from 'fluent-ffmpeg'
c1c86c15
C
7import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos'
8import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
0e1dc3e7 9import {
7160878c 10 buildAbsoluteFixturePath,
2186386c
C
11 doubleFollow,
12 flushAndRunMultipleServers,
2186386c
C
13 getMyVideos,
14 getVideo,
15 getVideosList,
16 killallServers,
17 root,
18 ServerInfo,
19 setAccessTokensToServers,
20 uploadVideo,
74cd011b
C
21 webtorrentAdd,
22 generateHighBitrateVideo
a7ba16b6 23} from '../../utils'
c1c86c15 24import { join } from 'path'
3cd0734f 25import { waitJobs } from '../../utils/server/jobs'
c1c86c15 26import { pathExists } from 'fs-extra'
edb4ffc7 27import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants'
a7ba16b6
C
28
29const expect = chai.expect
0e1dc3e7
C
30
31describe('Test video transcoding', function () {
32 let servers: ServerInfo[] = []
33
34 before(async function () {
e212f887 35 this.timeout(30000)
0e1dc3e7
C
36
37 // Run servers
38 servers = await flushAndRunMultipleServers(2)
39
40 await setAccessTokensToServers(servers)
b2977eec
C
41
42 await doubleFollow(servers[0], servers[1])
0e1dc3e7
C
43 })
44
45 it('Should not transcode video on server 1', async function () {
46 this.timeout(60000)
47
48 const videoAttributes = {
975e6e0e
C
49 name: 'my super name for server 1',
50 description: 'my super description for server 1',
0e1dc3e7
C
51 fixture: 'video_short.webm'
52 }
53 await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes)
54
3cd0734f 55 await waitJobs(servers)
0e1dc3e7 56
b2977eec
C
57 for (const server of servers) {
58 const res = await getVideosList(server.url)
59 const video = res.body.data[ 0 ]
40298b02 60
b2977eec
C
61 const res2 = await getVideo(server.url, video.id)
62 const videoDetails = res2.body
63 expect(videoDetails.files).to.have.lengthOf(1)
5f04dd2f 64
b2977eec
C
65 const magnetUri = videoDetails.files[ 0 ].magnetUri
66 expect(magnetUri).to.match(/\.webm/)
0e1dc3e7 67
b2977eec
C
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 }
0e1dc3e7
C
73 })
74
75 it('Should transcode video on server 2', async function () {
76 this.timeout(60000)
77
78 const videoAttributes = {
975e6e0e
C
79 name: 'my super name for server 2',
80 description: 'my super description for server 2',
0e1dc3e7
C
81 fixture: 'video_short.webm'
82 }
83 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
84
3cd0734f 85 await waitJobs(servers)
0e1dc3e7 86
b2977eec
C
87 for (const server of servers) {
88 const res = await getVideosList(server.url)
0e1dc3e7 89
b2977eec
C
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
5f04dd2f 93
b2977eec 94 expect(videoDetails.files).to.have.lengthOf(4)
40298b02 95
b2977eec
C
96 const magnetUri = videoDetails.files[ 0 ].magnetUri
97 expect(magnetUri).to.match(/\.mp4/)
0e1dc3e7 98
b2977eec
C
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 }
0e1dc3e7
C
104 })
105
7160878c
RK
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
b2977eec
C
117 for (const server of servers) {
118 const res = await getVideosList(server.url)
7160878c 119
b2977eec
C
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
7160878c 123
b2977eec 124 expect(videoDetails.files).to.have.lengthOf(4)
7160878c 125
b2977eec 126 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
cdf4cb9e 127 const probe = await audio.get(path)
7160878c 128
b2977eec
C
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 }
7160878c
RK
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
b2977eec
C
149 for (const server of servers) {
150 const res = await getVideosList(server.url)
7160878c 151
b2977eec
C
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
7160878c 155
b2977eec
C
156 expect(videoDetails.files).to.have.lengthOf(4)
157 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
cdf4cb9e 158 const probe = await audio.get(path)
b2977eec
C
159 expect(probe).to.not.have.property('audioStream')
160 }
7160878c
RK
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
b2977eec
C
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)
cdf4cb9e 183 const fixtureVideoProbe = await audio.get(fixturePath)
b2977eec 184 const path = join(root(), 'test2', 'videos', video.uuid + '-240.mp4')
cdf4cb9e 185 const videoProbe = await audio.get(path)
b2977eec
C
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 }
7160878c
RK
192 }
193 })
194
3a6f351b 195 it('Should transcode a 60 FPS video', async function () {
73c69591
C
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',
3a6f351b 201 fixture: '60fps_720p_small.mp4'
73c69591
C
202 }
203 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
204
3cd0734f 205 await waitJobs(servers)
73c69591 206
b2977eec
C
207 for (const server of servers) {
208 const res = await getVideosList(server.url)
73c69591 209
b2977eec
C
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
73c69591 213
b2977eec
C
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)
73c69591 219
b2977eec
C
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)
73c69591 223
b2977eec
C
224 expect(fps).to.be.below(31)
225 }
3a6f351b 226
b2977eec
C
227 const path = join(root(), 'test2', 'videos', video.uuid + '-720.mp4')
228 const fps = await getVideoFileFPS(path)
3a6f351b 229
b2977eec
C
230 expect(fps).to.be.above(58).and.below(62)
231 }
73c69591
C
232 })
233
edb4ffc7 234 it('Should wait for transcoding before publishing the video', async function () {
2186386c
C
235 this.timeout(80000)
236
2186386c
C
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)
7160878c 256 const videoToFindInMine = resMyVideos.body.data.find(v => v.name === videoAttributes.name)
2186386c
C
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)
7160878c 264 const videoToFindInList = resVideos.body.data.find(v => v.name === videoAttributes.name)
2186386c
C
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
3cd0734f 271 await waitJobs(servers)
2186386c
C
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
edb4ffc7
FA
287 it('Should respect maximum bitrate values', async function () {
288 this.timeout(160000)
289
74cd011b
C
290 let tempFixturePath: string
291
edb4ffc7 292 {
74cd011b 293 tempFixturePath = await generateHighBitrateVideo()
edb4ffc7
FA
294
295 const bitrate = await getVideoFileBitrate(tempFixturePath)
296 expect(bitrate).to.be.above(getMaxBitrate(VideoResolution.H_1080P, 60, VIDEO_TRANSCODING_FPS))
c1c86c15 297 }
edb4ffc7 298
c1c86c15
C
299 const videoAttributes = {
300 name: 'high bitrate video',
301 description: 'high bitrate video',
302 fixture: tempFixturePath
303 }
edb4ffc7 304
c1c86c15 305 await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes)
edb4ffc7 306
c1c86c15 307 await waitJobs(servers)
edb4ffc7 308
c1c86c15
C
309 for (const server of servers) {
310 const res = await getVideosList(server.url)
edb4ffc7 311
c1c86c15 312 const video = res.body.data.find(v => v.name === videoAttributes.name)
edb4ffc7 313
c1c86c15
C
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)
edb4ffc7 319
c1c86c15
C
320 expect(resolution2.videoFileResolution.toString()).to.equal(resolution)
321 expect(bitrate).to.be.below(getMaxBitrate(resolution2.videoFileResolution, fps, VIDEO_TRANSCODING_FPS))
edb4ffc7
FA
322 }
323 }
324 })
325
0e1dc3e7
C
326 after(async function () {
327 killallServers(servers)
0e1dc3e7
C
328 })
329})