aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/utils/videos
diff options
context:
space:
mode:
Diffstat (limited to 'shared/utils/videos')
-rw-r--r--shared/utils/videos/services.ts23
-rw-r--r--shared/utils/videos/video-abuses.ts65
-rw-r--r--shared/utils/videos/video-blacklist.ts74
-rw-r--r--shared/utils/videos/video-captions.ts71
-rw-r--r--shared/utils/videos/video-change-ownership.ts54
-rw-r--r--shared/utils/videos/video-channels.ts118
-rw-r--r--shared/utils/videos/video-comments.ts87
-rw-r--r--shared/utils/videos/video-history.ts39
-rw-r--r--shared/utils/videos/video-imports.ts57
-rw-r--r--shared/utils/videos/video-playlists.ts51
-rw-r--r--shared/utils/videos/videos.ts592
11 files changed, 1231 insertions, 0 deletions
diff --git a/shared/utils/videos/services.ts b/shared/utils/videos/services.ts
new file mode 100644
index 000000000..1a53dd4cf
--- /dev/null
+++ b/shared/utils/videos/services.ts
@@ -0,0 +1,23 @@
1import * as request from 'supertest'
2
3function getOEmbed (url: string, oembedUrl: string, format?: string, maxHeight?: number, maxWidth?: number) {
4 const path = '/services/oembed'
5 const query = {
6 url: oembedUrl,
7 format,
8 maxheight: maxHeight,
9 maxwidth: maxWidth
10 }
11
12 return request(url)
13 .get(path)
14 .query(query)
15 .set('Accept', 'application/json')
16 .expect(200)
17}
18
19// ---------------------------------------------------------------------------
20
21export {
22 getOEmbed
23}
diff --git a/shared/utils/videos/video-abuses.ts b/shared/utils/videos/video-abuses.ts
new file mode 100644
index 000000000..7f011ec0f
--- /dev/null
+++ b/shared/utils/videos/video-abuses.ts
@@ -0,0 +1,65 @@
1import * as request from 'supertest'
2import { VideoAbuseUpdate } from '../../models/videos/abuse/video-abuse-update.model'
3import { makeDeleteRequest, makePutBodyRequest } from '../requests/requests'
4
5function reportVideoAbuse (url: string, token: string, videoId: number | string, reason: string, specialStatus = 200) {
6 const path = '/api/v1/videos/' + videoId + '/abuse'
7
8 return request(url)
9 .post(path)
10 .set('Accept', 'application/json')
11 .set('Authorization', 'Bearer ' + token)
12 .send({ reason })
13 .expect(specialStatus)
14}
15
16function getVideoAbusesList (url: string, token: string) {
17 const path = '/api/v1/videos/abuse'
18
19 return request(url)
20 .get(path)
21 .query({ sort: 'createdAt' })
22 .set('Accept', 'application/json')
23 .set('Authorization', 'Bearer ' + token)
24 .expect(200)
25 .expect('Content-Type', /json/)
26}
27
28function updateVideoAbuse (
29 url: string,
30 token: string,
31 videoId: string | number,
32 videoAbuseId: number,
33 body: VideoAbuseUpdate,
34 statusCodeExpected = 204
35) {
36 const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId
37
38 return makePutBodyRequest({
39 url,
40 token,
41 path,
42 fields: body,
43 statusCodeExpected
44 })
45}
46
47function deleteVideoAbuse (url: string, token: string, videoId: string | number, videoAbuseId: number, statusCodeExpected = 204) {
48 const path = '/api/v1/videos/' + videoId + '/abuse/' + videoAbuseId
49
50 return makeDeleteRequest({
51 url,
52 token,
53 path,
54 statusCodeExpected
55 })
56}
57
58// ---------------------------------------------------------------------------
59
60export {
61 reportVideoAbuse,
62 getVideoAbusesList,
63 updateVideoAbuse,
64 deleteVideoAbuse
65}
diff --git a/shared/utils/videos/video-blacklist.ts b/shared/utils/videos/video-blacklist.ts
new file mode 100644
index 000000000..f2ae0ed26
--- /dev/null
+++ b/shared/utils/videos/video-blacklist.ts
@@ -0,0 +1,74 @@
1import * as request from 'supertest'
2
3function addVideoToBlacklist (
4 url: string,
5 token: string,
6 videoId: number | string,
7 reason?: string,
8 unfederate?: boolean,
9 specialStatus = 204
10) {
11 const path = '/api/v1/videos/' + videoId + '/blacklist'
12
13 return request(url)
14 .post(path)
15 .send({ reason, unfederate })
16 .set('Accept', 'application/json')
17 .set('Authorization', 'Bearer ' + token)
18 .expect(specialStatus)
19}
20
21function updateVideoBlacklist (url: string, token: string, videoId: number, reason?: string, specialStatus = 204) {
22 const path = '/api/v1/videos/' + videoId + '/blacklist'
23
24 return request(url)
25 .put(path)
26 .send({ reason })
27 .set('Accept', 'application/json')
28 .set('Authorization', 'Bearer ' + token)
29 .expect(specialStatus)
30}
31
32function removeVideoFromBlacklist (url: string, token: string, videoId: number | string, specialStatus = 204) {
33 const path = '/api/v1/videos/' + videoId + '/blacklist'
34
35 return request(url)
36 .delete(path)
37 .set('Accept', 'application/json')
38 .set('Authorization', 'Bearer ' + token)
39 .expect(specialStatus)
40}
41
42function getBlacklistedVideosList (url: string, token: string, specialStatus = 200) {
43 const path = '/api/v1/videos/blacklist/'
44
45 return request(url)
46 .get(path)
47 .query({ sort: 'createdAt' })
48 .set('Accept', 'application/json')
49 .set('Authorization', 'Bearer ' + token)
50 .expect(specialStatus)
51 .expect('Content-Type', /json/)
52}
53
54function getSortedBlacklistedVideosList (url: string, token: string, sort: string, specialStatus = 200) {
55 const path = '/api/v1/videos/blacklist/'
56
57 return request(url)
58 .get(path)
59 .query({ sort: sort })
60 .set('Accept', 'application/json')
61 .set('Authorization', 'Bearer ' + token)
62 .expect(specialStatus)
63 .expect('Content-Type', /json/)
64}
65
66// ---------------------------------------------------------------------------
67
68export {
69 addVideoToBlacklist,
70 removeVideoFromBlacklist,
71 getBlacklistedVideosList,
72 getSortedBlacklistedVideosList,
73 updateVideoBlacklist
74}
diff --git a/shared/utils/videos/video-captions.ts b/shared/utils/videos/video-captions.ts
new file mode 100644
index 000000000..8d67f617b
--- /dev/null
+++ b/shared/utils/videos/video-captions.ts
@@ -0,0 +1,71 @@
1import { makeDeleteRequest, makeGetRequest, makeUploadRequest } from '../requests/requests'
2import * as request from 'supertest'
3import * as chai from 'chai'
4import { buildAbsoluteFixturePath } from '../miscs/miscs'
5
6const expect = chai.expect
7
8function createVideoCaption (args: {
9 url: string,
10 accessToken: string
11 videoId: string | number
12 language: string
13 fixture: string,
14 mimeType?: string,
15 statusCodeExpected?: number
16}) {
17 const path = '/api/v1/videos/' + args.videoId + '/captions/' + args.language
18
19 const captionfile = buildAbsoluteFixturePath(args.fixture)
20 const captionfileAttach = args.mimeType ? [ captionfile, { contentType: args.mimeType } ] : captionfile
21
22 return makeUploadRequest({
23 method: 'PUT',
24 url: args.url,
25 path,
26 token: args.accessToken,
27 fields: {},
28 attaches: {
29 captionfile: captionfileAttach
30 },
31 statusCodeExpected: args.statusCodeExpected || 204
32 })
33}
34
35function listVideoCaptions (url: string, videoId: string | number) {
36 const path = '/api/v1/videos/' + videoId + '/captions'
37
38 return makeGetRequest({
39 url,
40 path,
41 statusCodeExpected: 200
42 })
43}
44
45function deleteVideoCaption (url: string, token: string, videoId: string | number, language: string) {
46 const path = '/api/v1/videos/' + videoId + '/captions/' + language
47
48 return makeDeleteRequest({
49 url,
50 token,
51 path,
52 statusCodeExpected: 204
53 })
54}
55
56async function testCaptionFile (url: string, captionPath: string, containsString: string) {
57 const res = await request(url)
58 .get(captionPath)
59 .expect(200)
60
61 expect(res.text).to.contain(containsString)
62}
63
64// ---------------------------------------------------------------------------
65
66export {
67 createVideoCaption,
68 listVideoCaptions,
69 testCaptionFile,
70 deleteVideoCaption
71}
diff --git a/shared/utils/videos/video-change-ownership.ts b/shared/utils/videos/video-change-ownership.ts
new file mode 100644
index 000000000..f288692ea
--- /dev/null
+++ b/shared/utils/videos/video-change-ownership.ts
@@ -0,0 +1,54 @@
1import * as request from 'supertest'
2
3function changeVideoOwnership (url: string, token: string, videoId: number | string, username) {
4 const path = '/api/v1/videos/' + videoId + '/give-ownership'
5
6 return request(url)
7 .post(path)
8 .set('Accept', 'application/json')
9 .set('Authorization', 'Bearer ' + token)
10 .send({ username })
11 .expect(204)
12}
13
14function getVideoChangeOwnershipList (url: string, token: string) {
15 const path = '/api/v1/videos/ownership'
16
17 return request(url)
18 .get(path)
19 .query({ sort: '-createdAt' })
20 .set('Accept', 'application/json')
21 .set('Authorization', 'Bearer ' + token)
22 .expect(200)
23 .expect('Content-Type', /json/)
24}
25
26function acceptChangeOwnership (url: string, token: string, ownershipId: string, channelId: number, expectedStatus = 204) {
27 const path = '/api/v1/videos/ownership/' + ownershipId + '/accept'
28
29 return request(url)
30 .post(path)
31 .set('Accept', 'application/json')
32 .set('Authorization', 'Bearer ' + token)
33 .send({ channelId })
34 .expect(expectedStatus)
35}
36
37function refuseChangeOwnership (url: string, token: string, ownershipId: string, expectedStatus = 204) {
38 const path = '/api/v1/videos/ownership/' + ownershipId + '/refuse'
39
40 return request(url)
41 .post(path)
42 .set('Accept', 'application/json')
43 .set('Authorization', 'Bearer ' + token)
44 .expect(expectedStatus)
45}
46
47// ---------------------------------------------------------------------------
48
49export {
50 changeVideoOwnership,
51 getVideoChangeOwnershipList,
52 acceptChangeOwnership,
53 refuseChangeOwnership
54}
diff --git a/shared/utils/videos/video-channels.ts b/shared/utils/videos/video-channels.ts
new file mode 100644
index 000000000..3935c261e
--- /dev/null
+++ b/shared/utils/videos/video-channels.ts
@@ -0,0 +1,118 @@
1import * as request from 'supertest'
2import { VideoChannelCreate, VideoChannelUpdate } from '../../models/videos'
3import { updateAvatarRequest } from '../requests/requests'
4
5function getVideoChannelsList (url: string, start: number, count: number, sort?: string) {
6 const path = '/api/v1/video-channels'
7
8 const req = request(url)
9 .get(path)
10 .query({ start: start })
11 .query({ count: count })
12
13 if (sort) req.query({ sort })
14
15 return req.set('Accept', 'application/json')
16 .expect(200)
17 .expect('Content-Type', /json/)
18}
19
20function getAccountVideoChannelsList (url: string, accountName: string, specialStatus = 200) {
21 const path = '/api/v1/accounts/' + accountName + '/video-channels'
22
23 return request(url)
24 .get(path)
25 .set('Accept', 'application/json')
26 .expect(specialStatus)
27 .expect('Content-Type', /json/)
28}
29
30function addVideoChannel (
31 url: string,
32 token: string,
33 videoChannelAttributesArg: VideoChannelCreate,
34 expectedStatus = 200
35) {
36 const path = '/api/v1/video-channels/'
37
38 // Default attributes
39 let attributes = {
40 displayName: 'my super video channel',
41 description: 'my super channel description',
42 support: 'my super channel support'
43 }
44 attributes = Object.assign(attributes, videoChannelAttributesArg)
45
46 return request(url)
47 .post(path)
48 .send(attributes)
49 .set('Accept', 'application/json')
50 .set('Authorization', 'Bearer ' + token)
51 .expect(expectedStatus)
52}
53
54function updateVideoChannel (
55 url: string,
56 token: string,
57 channelName: string,
58 attributes: VideoChannelUpdate,
59 expectedStatus = 204
60) {
61 const body = {}
62 const path = '/api/v1/video-channels/' + channelName
63
64 if (attributes.displayName) body['displayName'] = attributes.displayName
65 if (attributes.description) body['description'] = attributes.description
66 if (attributes.support) body['support'] = attributes.support
67
68 return request(url)
69 .put(path)
70 .send(body)
71 .set('Accept', 'application/json')
72 .set('Authorization', 'Bearer ' + token)
73 .expect(expectedStatus)
74}
75
76function deleteVideoChannel (url: string, token: string, channelName: string, expectedStatus = 204) {
77 const path = '/api/v1/video-channels/' + channelName
78
79 return request(url)
80 .delete(path)
81 .set('Accept', 'application/json')
82 .set('Authorization', 'Bearer ' + token)
83 .expect(expectedStatus)
84}
85
86function getVideoChannel (url: string, channelName: string) {
87 const path = '/api/v1/video-channels/' + channelName
88
89 return request(url)
90 .get(path)
91 .set('Accept', 'application/json')
92 .expect(200)
93 .expect('Content-Type', /json/)
94}
95
96function updateVideoChannelAvatar (options: {
97 url: string,
98 accessToken: string,
99 fixture: string,
100 videoChannelName: string | number
101}) {
102
103 const path = '/api/v1/video-channels/' + options.videoChannelName + '/avatar/pick'
104
105 return updateAvatarRequest(Object.assign(options, { path }))
106}
107
108// ---------------------------------------------------------------------------
109
110export {
111 updateVideoChannelAvatar,
112 getVideoChannelsList,
113 getAccountVideoChannelsList,
114 addVideoChannel,
115 updateVideoChannel,
116 deleteVideoChannel,
117 getVideoChannel
118}
diff --git a/shared/utils/videos/video-comments.ts b/shared/utils/videos/video-comments.ts
new file mode 100644
index 000000000..0ebf69ced
--- /dev/null
+++ b/shared/utils/videos/video-comments.ts
@@ -0,0 +1,87 @@
1import * as request from 'supertest'
2import { makeDeleteRequest } from '../requests/requests'
3
4function getVideoCommentThreads (url: string, videoId: number | string, start: number, count: number, sort?: string, token?: string) {
5 const path = '/api/v1/videos/' + videoId + '/comment-threads'
6
7 const req = request(url)
8 .get(path)
9 .query({ start: start })
10 .query({ count: count })
11
12 if (sort) req.query({ sort })
13 if (token) req.set('Authorization', 'Bearer ' + token)
14
15 return req.set('Accept', 'application/json')
16 .expect(200)
17 .expect('Content-Type', /json/)
18}
19
20function getVideoThreadComments (url: string, videoId: number | string, threadId: number, token?: string) {
21 const path = '/api/v1/videos/' + videoId + '/comment-threads/' + threadId
22
23 const req = request(url)
24 .get(path)
25 .set('Accept', 'application/json')
26
27 if (token) req.set('Authorization', 'Bearer ' + token)
28
29 return req.expect(200)
30 .expect('Content-Type', /json/)
31}
32
33function addVideoCommentThread (url: string, token: string, videoId: number | string, text: string, expectedStatus = 200) {
34 const path = '/api/v1/videos/' + videoId + '/comment-threads'
35
36 return request(url)
37 .post(path)
38 .send({ text })
39 .set('Accept', 'application/json')
40 .set('Authorization', 'Bearer ' + token)
41 .expect(expectedStatus)
42}
43
44function addVideoCommentReply (
45 url: string,
46 token: string,
47 videoId: number | string,
48 inReplyToCommentId: number,
49 text: string,
50 expectedStatus = 200
51) {
52 const path = '/api/v1/videos/' + videoId + '/comments/' + inReplyToCommentId
53
54 return request(url)
55 .post(path)
56 .send({ text })
57 .set('Accept', 'application/json')
58 .set('Authorization', 'Bearer ' + token)
59 .expect(expectedStatus)
60}
61
62function deleteVideoComment (
63 url: string,
64 token: string,
65 videoId: number | string,
66 commentId: number,
67 statusCodeExpected = 204
68) {
69 const path = '/api/v1/videos/' + videoId + '/comments/' + commentId
70
71 return makeDeleteRequest({
72 url,
73 path,
74 token,
75 statusCodeExpected
76 })
77}
78
79// ---------------------------------------------------------------------------
80
81export {
82 getVideoCommentThreads,
83 getVideoThreadComments,
84 addVideoCommentThread,
85 addVideoCommentReply,
86 deleteVideoComment
87}
diff --git a/shared/utils/videos/video-history.ts b/shared/utils/videos/video-history.ts
new file mode 100644
index 000000000..dc7095b4d
--- /dev/null
+++ b/shared/utils/videos/video-history.ts
@@ -0,0 +1,39 @@
1import { makeGetRequest, makePostBodyRequest, makePutBodyRequest } from '../requests/requests'
2
3function userWatchVideo (url: string, token: string, videoId: number | string, currentTime: number, statusCodeExpected = 204) {
4 const path = '/api/v1/videos/' + videoId + '/watching'
5 const fields = { currentTime }
6
7 return makePutBodyRequest({ url, path, token, fields, statusCodeExpected })
8}
9
10function listMyVideosHistory (url: string, token: string) {
11 const path = '/api/v1/users/me/history/videos'
12
13 return makeGetRequest({
14 url,
15 path,
16 token,
17 statusCodeExpected: 200
18 })
19}
20
21function removeMyVideosHistory (url: string, token: string, beforeDate?: string) {
22 const path = '/api/v1/users/me/history/videos/remove'
23
24 return makePostBodyRequest({
25 url,
26 path,
27 token,
28 fields: beforeDate ? { beforeDate } : {},
29 statusCodeExpected: 204
30 })
31}
32
33// ---------------------------------------------------------------------------
34
35export {
36 userWatchVideo,
37 listMyVideosHistory,
38 removeMyVideosHistory
39}
diff --git a/shared/utils/videos/video-imports.ts b/shared/utils/videos/video-imports.ts
new file mode 100644
index 000000000..ec77cdcda
--- /dev/null
+++ b/shared/utils/videos/video-imports.ts
@@ -0,0 +1,57 @@
1
2import { VideoImportCreate } from '../../models/videos'
3import { makeGetRequest, makeUploadRequest } from '../requests/requests'
4
5function getYoutubeVideoUrl () {
6 return 'https://youtu.be/msX3jv1XdvM'
7}
8
9function getMagnetURI () {
10 // tslint:disable:max-line-length
11 return 'magnet:?xs=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Ftorrents%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.torrent&xt=urn:btih:0f498834733e8057ed5c6f2ee2b4efd8d84a76ee&dn=super+peertube2+video&tr=wss%3A%2F%2Fpeertube2.cpy.re%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube2.cpy.re%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube2.cpy.re%2Fstatic%2Fwebseed%2Fb209ca00-c8bb-4b2b-b421-1ede169f3dbc-720.mp4'
12}
13
14function getBadVideoUrl () {
15 return 'https://download.cpy.re/peertube/bad_video.mp4'
16}
17
18function importVideo (url: string, token: string, attributes: VideoImportCreate) {
19 const path = '/api/v1/videos/imports'
20
21 let attaches: any = {}
22 if (attributes.torrentfile) attaches = { torrentfile: attributes.torrentfile }
23
24 return makeUploadRequest({
25 url,
26 path,
27 token,
28 attaches,
29 fields: attributes,
30 statusCodeExpected: 200
31 })
32}
33
34function getMyVideoImports (url: string, token: string, sort?: string) {
35 const path = '/api/v1/users/me/videos/imports'
36
37 const query = {}
38 if (sort) query['sort'] = sort
39
40 return makeGetRequest({
41 url,
42 query,
43 path,
44 token,
45 statusCodeExpected: 200
46 })
47}
48
49// ---------------------------------------------------------------------------
50
51export {
52 getBadVideoUrl,
53 getYoutubeVideoUrl,
54 importVideo,
55 getMagnetURI,
56 getMyVideoImports
57}
diff --git a/shared/utils/videos/video-playlists.ts b/shared/utils/videos/video-playlists.ts
new file mode 100644
index 000000000..eb25011cb
--- /dev/null
+++ b/shared/utils/videos/video-playlists.ts
@@ -0,0 +1,51 @@
1import { makeRawRequest } from '../requests/requests'
2import { sha256 } from '../../../server/helpers/core-utils'
3import { VideoStreamingPlaylist } from '../../models/videos/video-streaming-playlist.model'
4import { expect } from 'chai'
5
6function getPlaylist (url: string, statusCodeExpected = 200) {
7 return makeRawRequest(url, statusCodeExpected)
8}
9
10function getSegment (url: string, statusCodeExpected = 200, range?: string) {
11 return makeRawRequest(url, statusCodeExpected, range)
12}
13
14function getSegmentSha256 (url: string, statusCodeExpected = 200) {
15 return makeRawRequest(url, statusCodeExpected)
16}
17
18async function checkSegmentHash (
19 baseUrlPlaylist: string,
20 baseUrlSegment: string,
21 videoUUID: string,
22 resolution: number,
23 hlsPlaylist: VideoStreamingPlaylist
24) {
25 const res = await getPlaylist(`${baseUrlPlaylist}/${videoUUID}/${resolution}.m3u8`)
26 const playlist = res.text
27
28 const videoName = `${videoUUID}-${resolution}-fragmented.mp4`
29
30 const matches = /#EXT-X-BYTERANGE:(\d+)@(\d+)/.exec(playlist)
31
32 const length = parseInt(matches[1], 10)
33 const offset = parseInt(matches[2], 10)
34 const range = `${offset}-${offset + length - 1}`
35
36 const res2 = await getSegment(`${baseUrlSegment}/${videoUUID}/${videoName}`, 206, `bytes=${range}`)
37
38 const resSha = await getSegmentSha256(hlsPlaylist.segmentsSha256Url)
39
40 const sha256Server = resSha.body[ videoName ][range]
41 expect(sha256(res2.body)).to.equal(sha256Server)
42}
43
44// ---------------------------------------------------------------------------
45
46export {
47 getPlaylist,
48 getSegment,
49 getSegmentSha256,
50 checkSegmentHash
51}
diff --git a/shared/utils/videos/videos.ts b/shared/utils/videos/videos.ts
new file mode 100644
index 000000000..39c808d1f
--- /dev/null
+++ b/shared/utils/videos/videos.ts
@@ -0,0 +1,592 @@
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 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
46function getVideoCategories (url: string) {
47 const path = '/api/v1/videos/categories'
48
49 return makeGetRequest({
50 url,
51 path,
52 statusCodeExpected: 200
53 })
54}
55
56function getVideoLicences (url: string) {
57 const path = '/api/v1/videos/licences'
58
59 return makeGetRequest({
60 url,
61 path,
62 statusCodeExpected: 200
63 })
64}
65
66function getVideoLanguages (url: string) {
67 const path = '/api/v1/videos/languages'
68
69 return makeGetRequest({
70 url,
71 path,
72 statusCodeExpected: 200
73 })
74}
75
76function getVideoPrivacies (url: string) {
77 const path = '/api/v1/videos/privacies'
78
79 return makeGetRequest({
80 url,
81 path,
82 statusCodeExpected: 200
83 })
84}
85
86function 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
95function 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
109function 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
119function 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
127function 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
138function 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
150function 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
161function 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
177function 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
201function 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
225function 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
240function 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
251function 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
262function 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
272async 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
301async 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
376function 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
419function 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
430function 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
442async 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
567export {
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}