videos.ts 21 KB

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