]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - shared/extra-utils/videos/videos.ts
Add ability to filter my videos by live
[github/Chocobozzz/PeerTube.git] / shared / extra-utils / videos / videos.ts
1 /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/no-floating-promises */
2
3 import { expect } from 'chai'
4 import { pathExists, 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 { v4 as uuidv4 } from 'uuid'
9 import validator from 'validator'
10 import { HttpStatusCode } from '@shared/core-utils'
11 import { VideosCommonQuery } from '@shared/models'
12 import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../server/initializers/constants'
13 import { VideoDetails, VideoPrivacy } from '../../models/videos'
14 import {
15 buildAbsoluteFixturePath,
16 buildServerDirectory,
17 dateIsValid,
18 immutableAssign,
19 testImage,
20 wait,
21 webtorrentAdd
22 } from '../miscs/miscs'
23 import { makeGetRequest, makePutBodyRequest, makeRawRequest, makeUploadRequest } from '../requests/requests'
24 import { waitJobs } from '../server/jobs'
25 import { ServerInfo } from '../server/servers'
26 import { getMyUserInformation } from '../users/users'
27
28 loadLanguages()
29
30 type VideoAttributes = {
31 name?: string
32 category?: number
33 licence?: number
34 language?: string
35 nsfw?: boolean
36 commentsEnabled?: boolean
37 downloadEnabled?: boolean
38 waitTranscoding?: boolean
39 description?: string
40 originallyPublishedAt?: string
41 tags?: string[]
42 channelId?: number
43 privacy?: VideoPrivacy
44 fixture?: string
45 thumbnailfile?: string
46 previewfile?: string
47 scheduleUpdate?: {
48 updateAt: string
49 privacy?: VideoPrivacy
50 }
51 }
52
53 function getVideoCategories (url: string) {
54 const path = '/api/v1/videos/categories'
55
56 return makeGetRequest({
57 url,
58 path,
59 statusCodeExpected: HttpStatusCode.OK_200
60 })
61 }
62
63 function getVideoLicences (url: string) {
64 const path = '/api/v1/videos/licences'
65
66 return makeGetRequest({
67 url,
68 path,
69 statusCodeExpected: HttpStatusCode.OK_200
70 })
71 }
72
73 function getVideoLanguages (url: string) {
74 const path = '/api/v1/videos/languages'
75
76 return makeGetRequest({
77 url,
78 path,
79 statusCodeExpected: HttpStatusCode.OK_200
80 })
81 }
82
83 function getVideoPrivacies (url: string) {
84 const path = '/api/v1/videos/privacies'
85
86 return makeGetRequest({
87 url,
88 path,
89 statusCodeExpected: HttpStatusCode.OK_200
90 })
91 }
92
93 function getVideo (url: string, id: number | string, expectedStatus = HttpStatusCode.OK_200) {
94 const path = '/api/v1/videos/' + id
95
96 return request(url)
97 .get(path)
98 .set('Accept', 'application/json')
99 .expect(expectedStatus)
100 }
101
102 async function getVideoIdFromUUID (url: string, uuid: string) {
103 const res = await getVideo(url, uuid)
104
105 return res.body.id
106 }
107
108 function getVideoFileMetadataUrl (url: string) {
109 return request(url)
110 .get('/')
111 .set('Accept', 'application/json')
112 .expect(HttpStatusCode.OK_200)
113 .expect('Content-Type', /json/)
114 }
115
116 function viewVideo (url: string, id: number | string, expectedStatus = HttpStatusCode.NO_CONTENT_204, xForwardedFor?: string) {
117 const path = '/api/v1/videos/' + id + '/views'
118
119 const req = request(url)
120 .post(path)
121 .set('Accept', 'application/json')
122
123 if (xForwardedFor) {
124 req.set('X-Forwarded-For', xForwardedFor)
125 }
126
127 return req.expect(expectedStatus)
128 }
129
130 function getVideoWithToken (url: string, token: string, id: number | string, expectedStatus = HttpStatusCode.OK_200) {
131 const path = '/api/v1/videos/' + id
132
133 return request(url)
134 .get(path)
135 .set('Authorization', 'Bearer ' + token)
136 .set('Accept', 'application/json')
137 .expect(expectedStatus)
138 }
139
140 function getVideoDescription (url: string, descriptionPath: string) {
141 return request(url)
142 .get(descriptionPath)
143 .set('Accept', 'application/json')
144 .expect(HttpStatusCode.OK_200)
145 .expect('Content-Type', /json/)
146 }
147
148 function getVideosList (url: string) {
149 const path = '/api/v1/videos'
150
151 return request(url)
152 .get(path)
153 .query({ sort: 'name' })
154 .set('Accept', 'application/json')
155 .expect(HttpStatusCode.OK_200)
156 .expect('Content-Type', /json/)
157 }
158
159 function getVideosListWithToken (url: string, token: string, query: { nsfw?: boolean } = {}) {
160 const path = '/api/v1/videos'
161
162 return request(url)
163 .get(path)
164 .set('Authorization', 'Bearer ' + token)
165 .query(immutableAssign(query, { sort: 'name' }))
166 .set('Accept', 'application/json')
167 .expect(HttpStatusCode.OK_200)
168 .expect('Content-Type', /json/)
169 }
170
171 function getLocalVideos (url: string) {
172 const path = '/api/v1/videos'
173
174 return request(url)
175 .get(path)
176 .query({ sort: 'name', filter: 'local' })
177 .set('Accept', 'application/json')
178 .expect(HttpStatusCode.OK_200)
179 .expect('Content-Type', /json/)
180 }
181
182 function getMyVideos (url: string, accessToken: string, start: number, count: number, sort?: string, search?: string) {
183 const path = '/api/v1/users/me/videos'
184
185 const req = request(url)
186 .get(path)
187 .query({ start: start })
188 .query({ count: count })
189 .query({ search: search })
190
191 if (sort) req.query({ sort })
192
193 return req.set('Accept', 'application/json')
194 .set('Authorization', 'Bearer ' + accessToken)
195 .expect(HttpStatusCode.OK_200)
196 .expect('Content-Type', /json/)
197 }
198
199 function getMyVideosWithFilter (url: string, accessToken: string, query: { isLive?: boolean }) {
200 const path = '/api/v1/users/me/videos'
201
202 return makeGetRequest({
203 url,
204 path,
205 token: accessToken,
206 query,
207 statusCodeExpected: HttpStatusCode.OK_200
208 })
209 }
210
211 function getAccountVideos (
212 url: string,
213 accessToken: string,
214 accountName: string,
215 start: number,
216 count: number,
217 sort?: string,
218 query: {
219 nsfw?: boolean
220 search?: string
221 } = {}
222 ) {
223 const path = '/api/v1/accounts/' + accountName + '/videos'
224
225 return makeGetRequest({
226 url,
227 path,
228 query: immutableAssign(query, {
229 start,
230 count,
231 sort
232 }),
233 token: accessToken,
234 statusCodeExpected: HttpStatusCode.OK_200
235 })
236 }
237
238 function getVideoChannelVideos (
239 url: string,
240 accessToken: string,
241 videoChannelName: string,
242 start: number,
243 count: number,
244 sort?: string,
245 query: { nsfw?: boolean } = {}
246 ) {
247 const path = '/api/v1/video-channels/' + videoChannelName + '/videos'
248
249 return makeGetRequest({
250 url,
251 path,
252 query: immutableAssign(query, {
253 start,
254 count,
255 sort
256 }),
257 token: accessToken,
258 statusCodeExpected: HttpStatusCode.OK_200
259 })
260 }
261
262 function getPlaylistVideos (
263 url: string,
264 accessToken: string,
265 playlistId: number | string,
266 start: number,
267 count: number,
268 query: { nsfw?: boolean } = {}
269 ) {
270 const path = '/api/v1/video-playlists/' + playlistId + '/videos'
271
272 return makeGetRequest({
273 url,
274 path,
275 query: immutableAssign(query, {
276 start,
277 count
278 }),
279 token: accessToken,
280 statusCodeExpected: HttpStatusCode.OK_200
281 })
282 }
283
284 function getVideosListPagination (url: string, start: number, count: number, sort?: string, skipCount?: boolean) {
285 const path = '/api/v1/videos'
286
287 const req = request(url)
288 .get(path)
289 .query({ start: start })
290 .query({ count: count })
291
292 if (sort) req.query({ sort })
293 if (skipCount) req.query({ skipCount })
294
295 return req.set('Accept', 'application/json')
296 .expect(HttpStatusCode.OK_200)
297 .expect('Content-Type', /json/)
298 }
299
300 function getVideosListSort (url: string, sort: string) {
301 const path = '/api/v1/videos'
302
303 return request(url)
304 .get(path)
305 .query({ sort: sort })
306 .set('Accept', 'application/json')
307 .expect(HttpStatusCode.OK_200)
308 .expect('Content-Type', /json/)
309 }
310
311 function getVideosWithFilters (url: string, query: VideosCommonQuery) {
312 const path = '/api/v1/videos'
313
314 return request(url)
315 .get(path)
316 .query(query)
317 .set('Accept', 'application/json')
318 .expect(HttpStatusCode.OK_200)
319 .expect('Content-Type', /json/)
320 }
321
322 function removeVideo (url: string, token: string, id: number | string, expectedStatus = HttpStatusCode.NO_CONTENT_204) {
323 const path = '/api/v1/videos'
324
325 return request(url)
326 .delete(path + '/' + id)
327 .set('Accept', 'application/json')
328 .set('Authorization', 'Bearer ' + token)
329 .expect(expectedStatus)
330 }
331
332 async function removeAllVideos (server: ServerInfo) {
333 const resVideos = await getVideosList(server.url)
334
335 for (const v of resVideos.body.data) {
336 await removeVideo(server.url, server.accessToken, v.id)
337 }
338 }
339
340 async function checkVideoFilesWereRemoved (
341 videoUUID: string,
342 serverNumber: number,
343 directories = [
344 'redundancy',
345 'videos',
346 'thumbnails',
347 'torrents',
348 'previews',
349 'captions',
350 join('playlists', 'hls'),
351 join('redundancy', 'hls')
352 ]
353 ) {
354 for (const directory of directories) {
355 const directoryPath = buildServerDirectory({ internalServerNumber: serverNumber }, directory)
356
357 const directoryExists = await pathExists(directoryPath)
358 if (directoryExists === false) continue
359
360 const files = await readdir(directoryPath)
361 for (const file of files) {
362 expect(file, `File ${file} should not exist in ${directoryPath}`).to.not.contain(videoUUID)
363 }
364 }
365 }
366
367 async function uploadVideo (url: string, accessToken: string, videoAttributesArg: VideoAttributes, specialStatus = HttpStatusCode.OK_200) {
368 const path = '/api/v1/videos/upload'
369 let defaultChannelId = '1'
370
371 try {
372 const res = await getMyUserInformation(url, accessToken)
373 defaultChannelId = res.body.videoChannels[0].id
374 } catch (e) { /* empty */ }
375
376 // Override default attributes
377 const attributes = Object.assign({
378 name: 'my super video',
379 category: 5,
380 licence: 4,
381 language: 'zh',
382 channelId: defaultChannelId,
383 nsfw: true,
384 waitTranscoding: false,
385 description: 'my super description',
386 support: 'my super support text',
387 tags: [ 'tag' ],
388 privacy: VideoPrivacy.PUBLIC,
389 commentsEnabled: true,
390 downloadEnabled: true,
391 fixture: 'video_short.webm'
392 }, videoAttributesArg)
393
394 const req = request(url)
395 .post(path)
396 .set('Accept', 'application/json')
397 .set('Authorization', 'Bearer ' + accessToken)
398 .field('name', attributes.name)
399 .field('nsfw', JSON.stringify(attributes.nsfw))
400 .field('commentsEnabled', JSON.stringify(attributes.commentsEnabled))
401 .field('downloadEnabled', JSON.stringify(attributes.downloadEnabled))
402 .field('waitTranscoding', JSON.stringify(attributes.waitTranscoding))
403 .field('privacy', attributes.privacy.toString())
404 .field('channelId', attributes.channelId)
405
406 if (attributes.support !== undefined) {
407 req.field('support', attributes.support)
408 }
409
410 if (attributes.description !== undefined) {
411 req.field('description', attributes.description)
412 }
413 if (attributes.language !== undefined) {
414 req.field('language', attributes.language.toString())
415 }
416 if (attributes.category !== undefined) {
417 req.field('category', attributes.category.toString())
418 }
419 if (attributes.licence !== undefined) {
420 req.field('licence', attributes.licence.toString())
421 }
422
423 const tags = attributes.tags || []
424 for (let i = 0; i < tags.length; i++) {
425 req.field('tags[' + i + ']', attributes.tags[i])
426 }
427
428 if (attributes.thumbnailfile !== undefined) {
429 req.attach('thumbnailfile', buildAbsoluteFixturePath(attributes.thumbnailfile))
430 }
431 if (attributes.previewfile !== undefined) {
432 req.attach('previewfile', buildAbsoluteFixturePath(attributes.previewfile))
433 }
434
435 if (attributes.scheduleUpdate) {
436 req.field('scheduleUpdate[updateAt]', attributes.scheduleUpdate.updateAt)
437
438 if (attributes.scheduleUpdate.privacy) {
439 req.field('scheduleUpdate[privacy]', attributes.scheduleUpdate.privacy)
440 }
441 }
442
443 if (attributes.originallyPublishedAt !== undefined) {
444 req.field('originallyPublishedAt', attributes.originallyPublishedAt)
445 }
446
447 const res = await req.attach('videofile', buildAbsoluteFixturePath(attributes.fixture))
448 .expect(specialStatus)
449
450 // Wait torrent generation
451 if (specialStatus === HttpStatusCode.OK_200) {
452 let video: VideoDetails
453 do {
454 const resVideo = await getVideoWithToken(url, accessToken, res.body.video.uuid)
455 video = resVideo.body
456
457 await wait(50)
458 } while (!video.files[0].torrentUrl)
459 }
460
461 return res
462 }
463
464 function updateVideo (
465 url: string,
466 accessToken: string,
467 id: number | string,
468 attributes: VideoAttributes,
469 statusCodeExpected = HttpStatusCode.NO_CONTENT_204
470 ) {
471 const path = '/api/v1/videos/' + id
472 const body = {}
473
474 if (attributes.name) body['name'] = attributes.name
475 if (attributes.category) body['category'] = attributes.category
476 if (attributes.licence) body['licence'] = attributes.licence
477 if (attributes.language) body['language'] = attributes.language
478 if (attributes.nsfw !== undefined) body['nsfw'] = JSON.stringify(attributes.nsfw)
479 if (attributes.commentsEnabled !== undefined) body['commentsEnabled'] = JSON.stringify(attributes.commentsEnabled)
480 if (attributes.downloadEnabled !== undefined) body['downloadEnabled'] = JSON.stringify(attributes.downloadEnabled)
481 if (attributes.originallyPublishedAt !== undefined) body['originallyPublishedAt'] = attributes.originallyPublishedAt
482 if (attributes.description) body['description'] = attributes.description
483 if (attributes.tags) body['tags'] = attributes.tags
484 if (attributes.privacy) body['privacy'] = attributes.privacy
485 if (attributes.channelId) body['channelId'] = attributes.channelId
486 if (attributes.scheduleUpdate) body['scheduleUpdate'] = attributes.scheduleUpdate
487
488 // Upload request
489 if (attributes.thumbnailfile || attributes.previewfile) {
490 const attaches: any = {}
491 if (attributes.thumbnailfile) attaches.thumbnailfile = attributes.thumbnailfile
492 if (attributes.previewfile) attaches.previewfile = attributes.previewfile
493
494 return makeUploadRequest({
495 url,
496 method: 'PUT',
497 path,
498 token: accessToken,
499 fields: body,
500 attaches,
501 statusCodeExpected
502 })
503 }
504
505 return makePutBodyRequest({
506 url,
507 path,
508 fields: body,
509 token: accessToken,
510 statusCodeExpected
511 })
512 }
513
514 function rateVideo (url: string, accessToken: string, id: number | string, rating: string, specialStatus = HttpStatusCode.NO_CONTENT_204) {
515 const path = '/api/v1/videos/' + id + '/rate'
516
517 return request(url)
518 .put(path)
519 .set('Accept', 'application/json')
520 .set('Authorization', 'Bearer ' + accessToken)
521 .send({ rating })
522 .expect(specialStatus)
523 }
524
525 function parseTorrentVideo (server: ServerInfo, videoUUID: string, resolution: number) {
526 return new Promise<any>((res, rej) => {
527 const torrentName = videoUUID + '-' + resolution + '.torrent'
528 const torrentPath = buildServerDirectory(server, join('torrents', torrentName))
529
530 readFile(torrentPath, (err, data) => {
531 if (err) return rej(err)
532
533 return res(parseTorrent(data))
534 })
535 })
536 }
537
538 async function completeVideoCheck (
539 url: string,
540 video: any,
541 attributes: {
542 name: string
543 category: number
544 licence: number
545 language: string
546 nsfw: boolean
547 commentsEnabled: boolean
548 downloadEnabled: boolean
549 description: string
550 publishedAt?: string
551 support: string
552 originallyPublishedAt?: string
553 account: {
554 name: string
555 host: string
556 }
557 isLocal: boolean
558 tags: string[]
559 privacy: number
560 likes?: number
561 dislikes?: number
562 duration: number
563 channel: {
564 displayName: string
565 name: string
566 description
567 isLocal: boolean
568 }
569 fixture: string
570 files: {
571 resolution: number
572 size: number
573 }[]
574 thumbnailfile?: string
575 previewfile?: string
576 }
577 ) {
578 if (!attributes.likes) attributes.likes = 0
579 if (!attributes.dislikes) attributes.dislikes = 0
580
581 const host = new URL(url).host
582 const originHost = attributes.account.host
583
584 expect(video.name).to.equal(attributes.name)
585 expect(video.category.id).to.equal(attributes.category)
586 expect(video.category.label).to.equal(attributes.category !== null ? VIDEO_CATEGORIES[attributes.category] : 'Misc')
587 expect(video.licence.id).to.equal(attributes.licence)
588 expect(video.licence.label).to.equal(attributes.licence !== null ? VIDEO_LICENCES[attributes.licence] : 'Unknown')
589 expect(video.language.id).to.equal(attributes.language)
590 expect(video.language.label).to.equal(attributes.language !== null ? VIDEO_LANGUAGES[attributes.language] : 'Unknown')
591 expect(video.privacy.id).to.deep.equal(attributes.privacy)
592 expect(video.privacy.label).to.deep.equal(VIDEO_PRIVACIES[attributes.privacy])
593 expect(video.nsfw).to.equal(attributes.nsfw)
594 expect(video.description).to.equal(attributes.description)
595 expect(video.account.id).to.be.a('number')
596 expect(video.account.host).to.equal(attributes.account.host)
597 expect(video.account.name).to.equal(attributes.account.name)
598 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
599 expect(video.channel.name).to.equal(attributes.channel.name)
600 expect(video.likes).to.equal(attributes.likes)
601 expect(video.dislikes).to.equal(attributes.dislikes)
602 expect(video.isLocal).to.equal(attributes.isLocal)
603 expect(video.duration).to.equal(attributes.duration)
604 expect(dateIsValid(video.createdAt)).to.be.true
605 expect(dateIsValid(video.publishedAt)).to.be.true
606 expect(dateIsValid(video.updatedAt)).to.be.true
607
608 if (attributes.publishedAt) {
609 expect(video.publishedAt).to.equal(attributes.publishedAt)
610 }
611
612 if (attributes.originallyPublishedAt) {
613 expect(video.originallyPublishedAt).to.equal(attributes.originallyPublishedAt)
614 } else {
615 expect(video.originallyPublishedAt).to.be.null
616 }
617
618 const res = await getVideo(url, video.uuid)
619 const videoDetails: VideoDetails = res.body
620
621 expect(videoDetails.files).to.have.lengthOf(attributes.files.length)
622 expect(videoDetails.tags).to.deep.equal(attributes.tags)
623 expect(videoDetails.account.name).to.equal(attributes.account.name)
624 expect(videoDetails.account.host).to.equal(attributes.account.host)
625 expect(video.channel.displayName).to.equal(attributes.channel.displayName)
626 expect(video.channel.name).to.equal(attributes.channel.name)
627 expect(videoDetails.channel.host).to.equal(attributes.account.host)
628 expect(videoDetails.channel.isLocal).to.equal(attributes.channel.isLocal)
629 expect(dateIsValid(videoDetails.channel.createdAt.toString())).to.be.true
630 expect(dateIsValid(videoDetails.channel.updatedAt.toString())).to.be.true
631 expect(videoDetails.commentsEnabled).to.equal(attributes.commentsEnabled)
632 expect(videoDetails.downloadEnabled).to.equal(attributes.downloadEnabled)
633
634 for (const attributeFile of attributes.files) {
635 const file = videoDetails.files.find(f => f.resolution.id === attributeFile.resolution)
636 expect(file).not.to.be.undefined
637
638 let extension = extname(attributes.fixture)
639 // Transcoding enabled: extension will always be .mp4
640 if (attributes.files.length > 1) extension = '.mp4'
641
642 expect(file.magnetUri).to.have.lengthOf.above(2)
643
644 expect(file.torrentDownloadUrl).to.equal(`http://${host}/download/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
645 expect(file.torrentUrl).to.equal(`http://${host}/lazy-static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`)
646
647 expect(file.fileUrl).to.equal(`http://${originHost}/static/webseed/${videoDetails.uuid}-${file.resolution.id}${extension}`)
648 expect(file.fileDownloadUrl).to.equal(`http://${originHost}/download/videos/${videoDetails.uuid}-${file.resolution.id}${extension}`)
649
650 await Promise.all([
651 makeRawRequest(file.torrentUrl, 200),
652 makeRawRequest(file.torrentDownloadUrl, 200),
653 makeRawRequest(file.metadataUrl, 200),
654 // Backward compatibility
655 makeRawRequest(`http://${originHost}/static/torrents/${videoDetails.uuid}-${file.resolution.id}.torrent`, 200)
656 ])
657
658 expect(file.resolution.id).to.equal(attributeFile.resolution)
659 expect(file.resolution.label).to.equal(attributeFile.resolution + 'p')
660
661 const minSize = attributeFile.size - ((10 * attributeFile.size) / 100)
662 const maxSize = attributeFile.size + ((10 * attributeFile.size) / 100)
663 expect(
664 file.size,
665 'File size for resolution ' + file.resolution.label + ' outside confidence interval (' + minSize + '> size <' + maxSize + ')'
666 ).to.be.above(minSize).and.below(maxSize)
667
668 const torrent = await webtorrentAdd(file.magnetUri, true)
669 expect(torrent.files).to.be.an('array')
670 expect(torrent.files.length).to.equal(1)
671 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
672 }
673
674 await testImage(url, attributes.thumbnailfile || attributes.fixture, videoDetails.thumbnailPath)
675
676 if (attributes.previewfile) {
677 await testImage(url, attributes.previewfile, videoDetails.previewPath)
678 }
679 }
680
681 async function videoUUIDToId (url: string, id: number | string) {
682 if (validator.isUUID('' + id) === false) return id
683
684 const res = await getVideo(url, id)
685 return res.body.id
686 }
687
688 async function uploadVideoAndGetId (options: {
689 server: ServerInfo
690 videoName: string
691 nsfw?: boolean
692 privacy?: VideoPrivacy
693 token?: string
694 fixture?: string
695 }) {
696 const videoAttrs: any = { name: options.videoName }
697 if (options.nsfw) videoAttrs.nsfw = options.nsfw
698 if (options.privacy) videoAttrs.privacy = options.privacy
699 if (options.fixture) videoAttrs.fixture = options.fixture
700
701 const res = await uploadVideo(options.server.url, options.token || options.server.accessToken, videoAttrs)
702
703 return { id: res.body.video.id, uuid: res.body.video.uuid }
704 }
705
706 async function getLocalIdByUUID (url: string, uuid: string) {
707 const res = await getVideo(url, uuid)
708
709 return res.body.id
710 }
711
712 // serverNumber starts from 1
713 async function uploadRandomVideoOnServers (servers: ServerInfo[], serverNumber: number, additionalParams: any = {}) {
714 const server = servers.find(s => s.serverNumber === serverNumber)
715 const res = await uploadRandomVideo(server, false, additionalParams)
716
717 await waitJobs(servers)
718
719 return res
720 }
721
722 async function uploadRandomVideo (server: ServerInfo, wait = true, additionalParams: any = {}) {
723 const prefixName = additionalParams.prefixName || ''
724 const name = prefixName + uuidv4()
725
726 const data = Object.assign({ name }, additionalParams)
727 const res = await uploadVideo(server.url, server.accessToken, data)
728
729 if (wait) await waitJobs([ server ])
730
731 return { uuid: res.body.video.uuid, name }
732 }
733
734 // ---------------------------------------------------------------------------
735
736 export {
737 getVideoDescription,
738 getVideoCategories,
739 uploadRandomVideo,
740 getVideoLicences,
741 videoUUIDToId,
742 getVideoPrivacies,
743 getVideoLanguages,
744 getMyVideos,
745 getAccountVideos,
746 getVideoChannelVideos,
747 getVideo,
748 getVideoFileMetadataUrl,
749 getVideoWithToken,
750 getVideosList,
751 removeAllVideos,
752 getVideosListPagination,
753 getVideosListSort,
754 removeVideo,
755 getVideosListWithToken,
756 uploadVideo,
757 getVideosWithFilters,
758 uploadRandomVideoOnServers,
759 updateVideo,
760 rateVideo,
761 viewVideo,
762 parseTorrentVideo,
763 getLocalVideos,
764 completeVideoCheck,
765 checkVideoFilesWereRemoved,
766 getPlaylistVideos,
767 getMyVideosWithFilter,
768 uploadVideoAndGetId,
769 getLocalIdByUUID,
770 getVideoIdFromUUID
771 }