]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - shared/utils/videos/videos.ts
39c808d1f3f4d7d3ac014d6ba89af824e0c7dd37
[github/Chocobozzz/PeerTube.git] / shared / utils / videos / videos.ts
1 /* tslint:disable:no-unused-expression */
2
3 import { expect } from 'chai'
4 import { existsSync, readdir, readFile } from 'fs-extra'
5 import * as parseTorrent from 'parse-torrent'
6 import { extname, join } from 'path'
7 import * as request from 'supertest'
8 import {
9 buildAbsoluteFixturePath,
10 getMyUserInformation,
11 immutableAssign,
12 makeGetRequest,
13 makePutBodyRequest,
14 makeUploadRequest,
15 root,
16 ServerInfo,
17 testImage
18 } from '../'
19
20 import { VideoDetails, VideoPrivacy } from '../../models/videos'
21 import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
22 import { dateIsValid, webtorrentAdd } from '../miscs/miscs'
23
24 type VideoAttributes = {
25 name?: string
26 category?: number
27 licence?: number
28 language?: string
29 nsfw?: boolean
30 commentsEnabled?: boolean
31 downloadEnabled?: boolean
32 waitTranscoding?: boolean
33 description?: string
34 tags?: string[]
35 channelId?: number
36 privacy?: VideoPrivacy
37 fixture?: string
38 thumbnailfile?: string
39 previewfile?: string
40 scheduleUpdate?: {
41 updateAt: string
42 privacy?: VideoPrivacy
43 }
44 }
45
46 function getVideoCategories (url: string) {
47 const path = '/api/v1/videos/categories'
48
49 return makeGetRequest({
50 url,
51 path,
52 statusCodeExpected: 200
53 })
54 }
55
56 function getVideoLicences (url: string) {
57 const path = '/api/v1/videos/licences'
58
59 return makeGetRequest({
60 url,
61 path,
62 statusCodeExpected: 200
63 })
64 }
65
66 function getVideoLanguages (url: string) {
67 const path = '/api/v1/videos/languages'
68
69 return makeGetRequest({
70 url,
71 path,
72 statusCodeExpected: 200
73 })
74 }
75
76 function getVideoPrivacies (url: string) {
77 const path = '/api/v1/videos/privacies'
78
79 return makeGetRequest({
80 url,
81 path,
82 statusCodeExpected: 200
83 })
84 }
85
86 function getVideo (url: string, id: number | string, expectedStatus = 200) {
87 const path = '/api/v1/videos/' + id
88
89 return request(url)
90 .get(path)
91 .set('Accept', 'application/json')
92 .expect(expectedStatus)
93 }
94
95 function viewVideo (url: string, id: number | string, expectedStatus = 204, xForwardedFor?: string) {
96 const path = '/api/v1/videos/' + id + '/views'
97
98 const req = request(url)
99 .post(path)
100 .set('Accept', 'application/json')
101
102 if (xForwardedFor) {
103 req.set('X-Forwarded-For', xForwardedFor)
104 }
105
106 return req.expect(expectedStatus)
107 }
108
109 function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = 200) {
110 const path = '/api/v1/videos/' + id
111
112 return request(url)
113 .get(path)
114 .set('Authorization', 'Bearer ' + token)
115 .set('Accept', 'application/json')
116 .expect(expectedStatus)
117 }
118
119 function getVideoDescription (url: string, descriptionPath: string) {
120 return request(url)
121 .get(descriptionPath)
122 .set('Accept', 'application/json')
123 .expect(200)
124 .expect('Content-Type', /json/)
125 }
126
127 function getVideosList (url: string) {
128 const path = '/api/v1/videos'
129
130 return request(url)
131 .get(path)
132 .query({ sort: 'name' })
133 .set('Accept', 'application/json')
134 .expect(200)
135 .expect('Content-Type', /json/)
136 }
137
138 function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) {
139 const path = '/api/v1/videos'
140
141 return request(url)
142 .get(path)
143 .set('Authorization', 'Bearer ' + token)
144 .query(immutableAssign(query, { sort: 'name' }))
145 .set('Accept', 'application/json')
146 .expect(200)
147 .expect('Content-Type', /json/)
148 }
149
150 function getLocalVideos (url: string) {
151 const path = '/api/v1/videos'
152
153 return request(url)
154 .get(path)
155 .query({ sort: 'name', filter: 'local' })
156 .set('Accept', 'application/json')
157 .expect(200)
158 .expect('Content-Type', /json/)
159 }
160
161 function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string) {
162 const path = '/api/v1/users/me/videos'
163
164 const req = request(url)
165 .get(path)
166 .query({ start: start })
167 .query({ count: count })
168
169 if (sort) req.query({ sort })
170
171 return req.set('Accept', 'application/json')
172 .set('Authorization', 'Bearer ' + accessToken)
173 .expect(200)
174 .expect('Content-Type', /json/)
175 }
176
177 function getAccountVideos (
178 url: string,
179 accessToken: string,
180 accountName: string,
181 start: number,
182 count: number,
183 sort?: string,
184 query: { nsfw?: boolean } = {}
185 ) {
186 const path = '/api/v1/accounts/' + accountName + '/videos'
187
188 return makeGetRequest({
189 url,
190 path,
191 query: immutableAssign(query, {
192 start,
193 count,
194 sort
195 }),
196 token: accessToken,
197 statusCodeExpected: 200
198 })
199 }
200
201 function getVideoChannelVideos (
202 url: string,
203 accessToken: string,
204 videoChannelName: string,
205 start: number,
206 count: number,
207 sort?: string,
208 query: { nsfw?: boolean } = {}
209 ) {
210 const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
211
212 return makeGetRequest({
213 url,
214 path,
215 query: immutableAssign(query, {
216 start,
217 count,
218 sort
219 }),
220 token: accessToken,
221 statusCodeExpected: 200
222 })
223 }
224
225 function getVideosListPagination (url: string, start: number, count: number, sort?: string) {
226 const path = '/api/v1/videos'
227
228 const req = request(url)
229 .get(path)
230 .query({ start: start })
231 .query({ count: count })
232
233 if (sort) req.query({ sort })
234
235 return req.set('Accept', 'application/json')
236 .expect(200)
237 .expect('Content-Type', /json/)
238 }
239
240 function getVideosListSort (url: string, sort: string) {
241 const path = '/api/v1/videos'
242
243 return request(url)
244 .get(path)
245 .query({ sort: sort })
246 .set('Accept', 'application/json')
247 .expect(200)
248 .expect('Content-Type', /json/)
249 }
250
251 function getVideosWithFilters (url: string, query: { tagsAllOf: string[], categoryOneOf: number[] | number }) {
252 const path = '/api/v1/videos'
253
254 return request(url)
255 .get(path)
256 .query(query)
257 .set('Accept', 'application/json')
258 .expect(200)
259 .expect('Content-Type', /json/)
260 }
261
262 function removeVideo (url: string, token: string, id: number | string, expectedStatus = 204) {
263 const path = '/api/v1/videos'
264
265 return request(url)
266 .delete(path + '/' + id)
267 .set('Accept', 'application/json')
268 .set('Authorization', 'Bearer ' + token)
269 .expect(expectedStatus)
270 }
271
272 async function checkVideoFilesWereRemoved (
273 videoUUID: string,
274 serverNumber: number,
275 directories = [
276 'redundancy',
277 'videos',
278 'thumbnails',
279 'torrents',
280 'previews',
281 'captions',
282 join('playlists', 'hls'),
283 join('redundancy', 'hls')
284 ]
285 ) {
286 const testDirectory = 'test' + serverNumber
287
288 for (const directory of directories) {
289 const directoryPath = join(root(), testDirectory, directory)
290
291 const directoryExists = existsSync(directoryPath)
292 if (!directoryExists) continue
293
294 const files = await readdir(directoryPath)
295 for (const file of files) {
296 expect(file).to.not.contain(videoUUID)
297 }
298 }
299 }
300
301 async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = 200) {
302 const path = '/api/v1/videos/upload'
303 let defaultChannelId = '1'
304
305 try {
306 const res = await getMyUserInformation(url, accessToken)
307 defaultChannelId = res.body.videoChannels[0].id
308 } catch (e) { /* empty */ }
309
310 // Override default attributes
311 const attributes = Object.assign({
312 name: 'my super video',
313 category: 5,
314 licence: 4,
315 language: 'zh',
316 channelId: defaultChannelId,
317 nsfw: true,
318 waitTranscoding: false,
319 description: 'my super description',
320 support: 'my super support text',
321 tags: [ 'tag' ],
322 privacy: VideoPrivacy.PUBLIC,
323 commentsEnabled: true,
324 downloadEnabled: true,
325 fixture: 'video_short.webm'
326 }, videoAttributesArg)
327
328 const req = request(url)
329 .post(path)
330 .set('Accept', 'application/json')
331 .set('Authorization', 'Bearer ' + accessToken)
332 .field('name', attributes.name)
333 .field('nsfw', JSON.stringify(attributes.nsfw))
334 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
335 .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled))
336 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
337 .field('privacy', attributes.privacy.toString())
338 .field('channelId', attributes.channelId)
339
340 if (attributes.description !== undefined) {
341 req.field('description', attributes.description)
342 }
343 if (attributes.language !== undefined) {
344 req.field('language', attributes.language.toString())
345 }
346 if (attributes.category !== undefined) {
347 req.field('category', attributes.category.toString())
348 }
349 if (attributes.licence !== undefined) {
350 req.field('licence', attributes.licence.toString())
351 }
352
353 for (let i = 0; i < attributes.tags.length; i++) {
354 req.field('tags[' + i + ']', attributes.tags[i])
355 }
356
357 if (attributes.thumbnailfile !== undefined) {
358 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
359 }
360 if (attributes.previewfile !== undefined) {
361 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
362 }
363
364 if (attributes.scheduleUpdate) {
365 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
366
367 if (attributes.scheduleUpdate.privacy) {
368 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
369 }
370 }
371
372 return req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
373 .expect(specialStatus)
374 }
375
376 function updateVideo (url: string, accessToken: string, id: number | string, attributes: VideoAttributes, statusCodeExpected = 204) {
377 const path = '/api/v1/videos/' + id
378 const body = {}
379
380 if (attributes.name) body['name'] = attributes.name
381 if (attributes.category) body['category'] = attributes.category
382 if (attributes.licence) body['licence'] = attributes.licence
383 if (attributes.language) body['language'] = attributes.language
384 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
385 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
386 if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled)
387 if (attributes.description) body['description'] = attributes.description
388 if (attributes.tags) body['tags'] = attributes.tags
389 if (attributes.privacy) body['privacy'] = attributes.privacy
390 if (attributes.channelId) body['channelId'] = attributes.channelId
391 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
392
393 // Upload request
394 if (attributes.thumbnailfile || attributes.previewfile) {
395 const attaches: any = {}
396 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
397 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
398
399 return makeUploadRequest({
400 url,
401 method: 'PUT',
402 path,
403 token: accessToken,
404 fields: body,
405 attaches,
406 statusCodeExpected
407 })
408 }
409
410 return makePutBodyRequest({
411 url,
412 path,
413 fields: body,
414 token: accessToken,
415 statusCodeExpected
416 })
417 }
418
419 function rateVideo (url: string, accessToken: string, id: number, rating: string, specialStatus = 204) {
420 const path = '/api/v1/videos/' + id + '/rate'
421
422 return request(url)
423 .put(path)
424 .set('Accept', 'application/json')
425 .set('Authorization', 'Bearer ' + accessToken)
426 .send({ rating })
427 .expect(specialStatus)
428 }
429
430 function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
431 return new Promise<any>((res, rej) => {
432 const torrentName = videoUUID + '-' + resolution + '.torrent'
433 const torrentPath = join(root(), 'test' + server.serverNumber, 'torrents', torrentName)
434 readFile(torrentPath, (err, data) => {
435 if (err) return rej(err)
436
437 return res(parseTorrent(data))
438 })
439 })
440 }
441
442 async function completeVideoCheck (
443 url: string,
444 video: any,
445 attributes: {
446 name: string
447 category: number
448 licence: number
449 language: string
450 nsfw: boolean
451 commentsEnabled: boolean
452 downloadEnabled: boolean
453 description: string
454 publishedAt?: string
455 support: string
456 account: {
457 name: string
458 host: string
459 }
460 isLocal: boolean
461 tags: string[]
462 privacy: number
463 likes?: number
464 dislikes?: number
465 duration: number
466 channel: {
467 displayName: string
468 name: string
469 description
470 isLocal: boolean
471 }
472 fixture: string
473 files: {
474 resolution: number
475 size: number
476 }[],
477 thumbnailfile?: string
478 previewfile?: string
479 }
480 ) {
481 if (!attributes.likes) attributes.likes = 0
482 if (!attributes.dislikes) attributes.dislikes = 0
483
484 expect(video.name).to.equal(attributes.name)
485 expect(video.category.id).to.equal(attributes.category)
486 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
487 expect(video.licence.id).to.equal(attributes.licence)
488 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
489 expect(video.language.id).to.equal(attributes.language)
490 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
491 expect(video.privacy.id).to.deep.equal(attributes.privacy)
492 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
493 expect(video.nsfw).to.equal(attributes.nsfw)
494 expect(video.description).to.equal(attributes.description)
495 expect(video.account.id).to.be.a('number')
496 expect(video.account.uuid).to.be.a('string')
497 expect(video.account.host).to.equal(attributes.account.host)
498 expect(video.account.name).to.equal(attributes.account.name)
499 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
500 expect(video.channel.name).to.equal(attributes.channel.name)
501 expect(video.likes).to.equal(attributes.likes)
502 expect(video.dislikes).to.equal(attributes.dislikes)
503 expect(video.isLocal).to.equal(attributes.isLocal)
504 expect(video.duration).to.equal(attributes.duration)
505 expect(dateIsValid(video.createdAt)).to.be.true
506 expect(dateIsValid(video.publishedAt)).to.be.true
507 expect(dateIsValid(video.updatedAt)).to.be.true
508
509 if (attributes.publishedAt) {
510 expect(video.publishedAt).to.equal(attributes.publishedAt)
511 }
512
513 const res = await getVideo(url, video.uuid)
514 const videoDetails: VideoDetails = res.body
515
516 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
517 expect(videoDetails.tags).to.deep.equal(attributes.tags)
518 expect(videoDetails.account.name).to.equal(attributes.account.name)
519 expect(videoDetails.account.host).to.equal(attributes.account.host)
520 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
521 expect(video.channel.name).to.equal(attributes.channel.name)
522 expect(videoDetails.channel.host).to.equal(attributes.account.host)
523 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
524 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
525 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
526 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
527 expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled)
528
529 for (const attributeFile of attributes.files) {
530 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
531 expect(file).not.to.be.undefined
532
533 let extension = extname(attributes.fixture)
534 // Transcoding enabled on server 2, extension will always be .mp4
535 if (attributes.account.host === 'localhost:9002') extension = '.mp4'
536
537 const magnetUri = file.magnetUri
538 expect(file.magnetUri).to.have.lengthOf.above(2)
539 expect(file.torrentUrl).to.equal(`http://${attributes.account.host}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
540 expect(file.fileUrl).to.equal(`http://${attributes.account.host}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
541 expect(file.resolution.id).to.equal(attributeFile.resolution)
542 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
543
544 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
545 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
546 expect(file.size,
547 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')')
548 .to.be.above(minSize).and.below(maxSize)
549
550 {
551 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
552 }
553
554 if (attributes.previewfile) {
555 await testImage(url, attributes.previewfile, videoDetails.previewPath)
556 }
557
558 const torrent = await webtorrentAdd(magnetUri, true)
559 expect(torrent.files).to.be.an('array')
560 expect(torrent.files.length).to.equal(1)
561 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
562 }
563 }
564
565 // ---------------------------------------------------------------------------
566
567 export {
568 getVideoDescription,
569 getVideoCategories,
570 getVideoLicences,
571 getVideoPrivacies,
572 getVideoLanguages,
573 getMyVideos,
574 getAccountVideos,
575 getVideoChannelVideos,
576 getVideo,
577 getVideoWithToken,
578 getVideosList,
579 getVideosListPagination,
580 getVideosListSort,
581 removeVideo,
582 getVideosListWithToken,
583 uploadVideo,
584 getVideosWithFilters,
585 updateVideo,
586 rateVideo,
587 viewVideo,
588 parseTorrentVideo,
589 getLocalVideos,
590 completeVideoCheck,
591 checkVideoFilesWereRemoved
592 }