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