video-studio.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
  2. import { HttpStatusCode, HttpStatusCodeType, VideoStudioTask } from '@peertube/peertube-models'
  3. import {
  4. cleanupTests,
  5. createSingleServer,
  6. PeerTubeServer,
  7. setAccessTokensToServers,
  8. VideoStudioCommand,
  9. waitJobs
  10. } from '@peertube/peertube-server-commands'
  11. describe('Test video studio API validator', function () {
  12. let server: PeerTubeServer
  13. let command: VideoStudioCommand
  14. let userAccessToken: string
  15. let videoUUID: string
  16. // ---------------------------------------------------------------
  17. before(async function () {
  18. this.timeout(120_000)
  19. server = await createSingleServer(1)
  20. await setAccessTokensToServers([ server ])
  21. userAccessToken = await server.users.generateUserAndToken('user1')
  22. await server.config.enableMinimumTranscoding()
  23. const { uuid } = await server.videos.quickUpload({ name: 'video' })
  24. videoUUID = uuid
  25. command = server.videoStudio
  26. await waitJobs([ server ])
  27. })
  28. describe('Task creation', function () {
  29. describe('Config settings', function () {
  30. it('Should fail if studio is disabled', async function () {
  31. await server.config.updateExistingConfig({
  32. newConfig: {
  33. videoStudio: {
  34. enabled: false
  35. }
  36. }
  37. })
  38. await command.createEditionTasks({
  39. videoId: videoUUID,
  40. tasks: VideoStudioCommand.getComplexTask(),
  41. expectedStatus: HttpStatusCode.BAD_REQUEST_400
  42. })
  43. })
  44. it('Should fail to enable studio if transcoding is disabled', async function () {
  45. await server.config.updateExistingConfig({
  46. newConfig: {
  47. videoStudio: {
  48. enabled: true
  49. },
  50. transcoding: {
  51. enabled: false
  52. }
  53. },
  54. expectedStatus: HttpStatusCode.BAD_REQUEST_400
  55. })
  56. })
  57. it('Should succeed to enable video studio', async function () {
  58. await server.config.updateExistingConfig({
  59. newConfig: {
  60. videoStudio: {
  61. enabled: true
  62. },
  63. transcoding: {
  64. enabled: true
  65. }
  66. }
  67. })
  68. })
  69. })
  70. describe('Common tasks', function () {
  71. it('Should fail without token', async function () {
  72. await command.createEditionTasks({
  73. token: null,
  74. videoId: videoUUID,
  75. tasks: VideoStudioCommand.getComplexTask(),
  76. expectedStatus: HttpStatusCode.UNAUTHORIZED_401
  77. })
  78. })
  79. it('Should fail with another user token', async function () {
  80. await command.createEditionTasks({
  81. token: userAccessToken,
  82. videoId: videoUUID,
  83. tasks: VideoStudioCommand.getComplexTask(),
  84. expectedStatus: HttpStatusCode.FORBIDDEN_403
  85. })
  86. })
  87. it('Should fail with an invalid video', async function () {
  88. await command.createEditionTasks({
  89. videoId: 'tintin',
  90. tasks: VideoStudioCommand.getComplexTask(),
  91. expectedStatus: HttpStatusCode.BAD_REQUEST_400
  92. })
  93. })
  94. it('Should fail with an unknown video', async function () {
  95. await command.createEditionTasks({
  96. videoId: 42,
  97. tasks: VideoStudioCommand.getComplexTask(),
  98. expectedStatus: HttpStatusCode.NOT_FOUND_404
  99. })
  100. })
  101. it('Should fail with an already in transcoding state video', async function () {
  102. this.timeout(60000)
  103. const { uuid } = await server.videos.quickUpload({ name: 'transcoded video' })
  104. await waitJobs([ server ])
  105. await server.jobs.pauseJobQueue()
  106. await server.videos.runTranscoding({ videoId: uuid, transcodingType: 'hls' })
  107. await command.createEditionTasks({
  108. videoId: uuid,
  109. tasks: VideoStudioCommand.getComplexTask(),
  110. expectedStatus: HttpStatusCode.CONFLICT_409
  111. })
  112. await server.jobs.resumeJobQueue()
  113. })
  114. it('Should fail with a bad complex task', async function () {
  115. await command.createEditionTasks({
  116. videoId: videoUUID,
  117. tasks: [
  118. {
  119. name: 'cut',
  120. options: {
  121. start: 1,
  122. end: 2
  123. }
  124. },
  125. {
  126. name: 'hadock',
  127. options: {
  128. start: 1,
  129. end: 2
  130. }
  131. }
  132. ] as any,
  133. expectedStatus: HttpStatusCode.BAD_REQUEST_400
  134. })
  135. })
  136. it('Should fail without task', async function () {
  137. await command.createEditionTasks({
  138. videoId: videoUUID,
  139. tasks: [],
  140. expectedStatus: HttpStatusCode.BAD_REQUEST_400
  141. })
  142. })
  143. it('Should fail with too many tasks', async function () {
  144. const tasks: VideoStudioTask[] = []
  145. for (let i = 0; i < 110; i++) {
  146. tasks.push({
  147. name: 'cut',
  148. options: {
  149. start: 1
  150. }
  151. })
  152. }
  153. await command.createEditionTasks({
  154. videoId: videoUUID,
  155. tasks,
  156. expectedStatus: HttpStatusCode.BAD_REQUEST_400
  157. })
  158. })
  159. it('Should succeed with correct parameters', async function () {
  160. await server.jobs.pauseJobQueue()
  161. await command.createEditionTasks({
  162. videoId: videoUUID,
  163. tasks: VideoStudioCommand.getComplexTask(),
  164. expectedStatus: HttpStatusCode.NO_CONTENT_204
  165. })
  166. })
  167. it('Should fail with a video that is already waiting for edition', async function () {
  168. this.timeout(360000)
  169. await command.createEditionTasks({
  170. videoId: videoUUID,
  171. tasks: VideoStudioCommand.getComplexTask(),
  172. expectedStatus: HttpStatusCode.CONFLICT_409
  173. })
  174. await server.jobs.resumeJobQueue()
  175. await waitJobs([ server ])
  176. })
  177. })
  178. describe('Cut task', function () {
  179. async function cut (start: number, end: number, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
  180. await command.createEditionTasks({
  181. videoId: videoUUID,
  182. tasks: [
  183. {
  184. name: 'cut',
  185. options: {
  186. start,
  187. end
  188. }
  189. }
  190. ],
  191. expectedStatus
  192. })
  193. }
  194. it('Should fail with bad start/end', async function () {
  195. const invalid = [
  196. 'tintin',
  197. -1,
  198. undefined
  199. ]
  200. for (const value of invalid) {
  201. await cut(value as any, undefined)
  202. await cut(undefined, value as any)
  203. }
  204. })
  205. it('Should fail with the same start/end', async function () {
  206. await cut(2, 2)
  207. })
  208. it('Should fail with inconsistents start/end', async function () {
  209. await cut(2, 1)
  210. })
  211. it('Should fail without start and end', async function () {
  212. await cut(undefined, undefined)
  213. })
  214. it('Should succeed with the correct params', async function () {
  215. this.timeout(360000)
  216. await cut(0, 2, HttpStatusCode.NO_CONTENT_204)
  217. await waitJobs([ server ])
  218. })
  219. })
  220. describe('Watermark task', function () {
  221. async function addWatermark (file: string, expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400) {
  222. await command.createEditionTasks({
  223. videoId: videoUUID,
  224. tasks: [
  225. {
  226. name: 'add-watermark',
  227. options: {
  228. file
  229. }
  230. }
  231. ],
  232. expectedStatus
  233. })
  234. }
  235. it('Should fail without waterkmark', async function () {
  236. await addWatermark(undefined)
  237. })
  238. it('Should fail with an invalid watermark', async function () {
  239. await addWatermark('video_short.mp4')
  240. })
  241. it('Should succeed with the correct params', async function () {
  242. this.timeout(360000)
  243. await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204)
  244. await waitJobs([ server ])
  245. })
  246. })
  247. describe('Intro/Outro task', function () {
  248. async function addIntroOutro (
  249. type: 'add-intro' | 'add-outro',
  250. file: string,
  251. expectedStatus: HttpStatusCodeType = HttpStatusCode.BAD_REQUEST_400
  252. ) {
  253. await command.createEditionTasks({
  254. videoId: videoUUID,
  255. tasks: [
  256. {
  257. name: type,
  258. options: {
  259. file
  260. }
  261. }
  262. ],
  263. expectedStatus
  264. })
  265. }
  266. it('Should fail without file', async function () {
  267. await addIntroOutro('add-intro', undefined)
  268. await addIntroOutro('add-outro', undefined)
  269. })
  270. it('Should fail with an invalid file', async function () {
  271. await addIntroOutro('add-intro', 'custom-thumbnail.jpg')
  272. await addIntroOutro('add-outro', 'custom-thumbnail.jpg')
  273. })
  274. it('Should fail with a file that does not contain video stream', async function () {
  275. await addIntroOutro('add-intro', 'sample.ogg')
  276. await addIntroOutro('add-outro', 'sample.ogg')
  277. })
  278. it('Should succeed with the correct params', async function () {
  279. this.timeout(360000)
  280. await addIntroOutro('add-intro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
  281. await waitJobs([ server ])
  282. await addIntroOutro('add-outro', 'video_very_short_240p.mp4', HttpStatusCode.NO_CONTENT_204)
  283. await waitJobs([ server ])
  284. })
  285. it('Should check total quota when creating the task', async function () {
  286. this.timeout(360000)
  287. const user = await server.users.create({ username: 'user_quota_1' })
  288. const token = await server.login.getAccessToken('user_quota_1')
  289. const { uuid } = await server.videos.quickUpload({ token, name: 'video_quota_1', fixture: 'video_short.mp4' })
  290. const addIntroOutroByUser = (type: 'add-intro' | 'add-outro', expectedStatus: HttpStatusCodeType) => {
  291. return command.createEditionTasks({
  292. token,
  293. videoId: uuid,
  294. tasks: [
  295. {
  296. name: type,
  297. options: {
  298. file: 'video_short.mp4'
  299. }
  300. }
  301. ],
  302. expectedStatus
  303. })
  304. }
  305. await waitJobs([ server ])
  306. const { videoQuotaUsed } = await server.users.getMyQuotaUsed({ token })
  307. await server.users.update({ userId: user.id, videoQuota: Math.round(videoQuotaUsed * 2.5) })
  308. // Still valid
  309. await addIntroOutroByUser('add-intro', HttpStatusCode.NO_CONTENT_204)
  310. await waitJobs([ server ])
  311. // Too much quota
  312. await addIntroOutroByUser('add-intro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
  313. await addIntroOutroByUser('add-outro', HttpStatusCode.PAYLOAD_TOO_LARGE_413)
  314. })
  315. })
  316. })
  317. after(async function () {
  318. await cleanupTests([ server ])
  319. })
  320. })