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