transcoder.ts 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802
  1. /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
  2. import { expect } from 'chai'
  3. import { getAllFiles, getMaxTheoreticalBitrate, getMinTheoreticalBitrate, omit } from '@peertube/peertube-core-utils'
  4. import { HttpStatusCode, VideoFileMetadata, VideoState } from '@peertube/peertube-models'
  5. import { canDoQuickTranscode } from '@peertube/peertube-server/core/lib/transcoding/transcoding-quick-transcode.js'
  6. import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
  7. import {
  8. ffprobePromise,
  9. getAudioStream,
  10. getVideoStreamBitrate,
  11. getVideoStreamDimensionsInfo,
  12. getVideoStreamFPS,
  13. hasAudioStream
  14. } from '@peertube/peertube-ffmpeg'
  15. import {
  16. cleanupTests,
  17. createMultipleServers,
  18. doubleFollow,
  19. makeGetRequest,
  20. PeerTubeServer,
  21. setAccessTokensToServers,
  22. waitJobs
  23. } from '@peertube/peertube-server-commands'
  24. import { generateVideoWithFramerate, generateHighBitrateVideo } from '@tests/shared/generate.js'
  25. import { checkWebTorrentWorks } from '@tests/shared/webtorrent.js'
  26. function updateConfigForTranscoding (server: PeerTubeServer) {
  27. return server.config.updateCustomSubConfig({
  28. newConfig: {
  29. transcoding: {
  30. enabled: true,
  31. allowAdditionalExtensions: true,
  32. allowAudioFiles: true,
  33. hls: { enabled: true },
  34. webVideos: { enabled: true },
  35. resolutions: {
  36. '0p': false,
  37. '144p': true,
  38. '240p': true,
  39. '360p': true,
  40. '480p': true,
  41. '720p': true,
  42. '1080p': true,
  43. '1440p': true,
  44. '2160p': true
  45. }
  46. }
  47. }
  48. })
  49. }
  50. describe('Test video transcoding', function () {
  51. let servers: PeerTubeServer[] = []
  52. let video4k: string
  53. before(async function () {
  54. this.timeout(30_000)
  55. // Run servers
  56. servers = await createMultipleServers(2)
  57. await setAccessTokensToServers(servers)
  58. await doubleFollow(servers[0], servers[1])
  59. await updateConfigForTranscoding(servers[1])
  60. })
  61. describe('Basic transcoding (or not)', function () {
  62. it('Should not transcode video on server 1', async function () {
  63. this.timeout(60_000)
  64. const attributes = {
  65. name: 'my super name for server 1',
  66. description: 'my super description for server 1',
  67. fixture: 'video_short.webm'
  68. }
  69. await servers[0].videos.upload({ attributes })
  70. await waitJobs(servers)
  71. for (const server of servers) {
  72. const { data } = await server.videos.list()
  73. const video = data[0]
  74. const videoDetails = await server.videos.get({ id: video.id })
  75. expect(videoDetails.files).to.have.lengthOf(1)
  76. const magnetUri = videoDetails.files[0].magnetUri
  77. expect(magnetUri).to.match(/\.webm/)
  78. await checkWebTorrentWorks(magnetUri, /\.webm$/)
  79. }
  80. })
  81. it('Should transcode video on server 2', async function () {
  82. this.timeout(120_000)
  83. const attributes = {
  84. name: 'my super name for server 2',
  85. description: 'my super description for server 2',
  86. fixture: 'video_short.webm'
  87. }
  88. await servers[1].videos.upload({ attributes })
  89. await waitJobs(servers)
  90. for (const server of servers) {
  91. const { data } = await server.videos.list()
  92. const video = data.find(v => v.name === attributes.name)
  93. const videoDetails = await server.videos.get({ id: video.id })
  94. expect(videoDetails.files).to.have.lengthOf(5)
  95. const magnetUri = videoDetails.files[0].magnetUri
  96. expect(magnetUri).to.match(/\.mp4/)
  97. await checkWebTorrentWorks(magnetUri, /\.mp4$/)
  98. }
  99. })
  100. it('Should wait for transcoding before publishing the video', async function () {
  101. this.timeout(160_000)
  102. {
  103. // Upload the video, but wait transcoding
  104. const attributes = {
  105. name: 'waiting video',
  106. fixture: 'video_short1.webm',
  107. waitTranscoding: true
  108. }
  109. const { uuid } = await servers[1].videos.upload({ attributes })
  110. const videoId = uuid
  111. // Should be in transcode state
  112. const body = await servers[1].videos.get({ id: videoId })
  113. expect(body.name).to.equal('waiting video')
  114. expect(body.state.id).to.equal(VideoState.TO_TRANSCODE)
  115. expect(body.state.label).to.equal('To transcode')
  116. expect(body.waitTranscoding).to.be.true
  117. {
  118. // Should have my video
  119. const { data } = await servers[1].videos.listMyVideos()
  120. const videoToFindInMine = data.find(v => v.name === attributes.name)
  121. expect(videoToFindInMine).not.to.be.undefined
  122. expect(videoToFindInMine.state.id).to.equal(VideoState.TO_TRANSCODE)
  123. expect(videoToFindInMine.state.label).to.equal('To transcode')
  124. expect(videoToFindInMine.waitTranscoding).to.be.true
  125. }
  126. {
  127. // Should not list this video
  128. const { data } = await servers[1].videos.list()
  129. const videoToFindInList = data.find(v => v.name === attributes.name)
  130. expect(videoToFindInList).to.be.undefined
  131. }
  132. // Server 1 should not have the video yet
  133. await servers[0].videos.get({ id: videoId, expectedStatus: HttpStatusCode.NOT_FOUND_404 })
  134. }
  135. await waitJobs(servers)
  136. for (const server of servers) {
  137. const { data } = await server.videos.list()
  138. const videoToFind = data.find(v => v.name === 'waiting video')
  139. expect(videoToFind).not.to.be.undefined
  140. const videoDetails = await server.videos.get({ id: videoToFind.id })
  141. expect(videoDetails.state.id).to.equal(VideoState.PUBLISHED)
  142. expect(videoDetails.state.label).to.equal('Published')
  143. expect(videoDetails.waitTranscoding).to.be.true
  144. }
  145. })
  146. it('Should accept and transcode additional extensions', async function () {
  147. this.timeout(300_000)
  148. for (const fixture of [ 'video_short.mkv', 'video_short.avi' ]) {
  149. const attributes = {
  150. name: fixture,
  151. fixture
  152. }
  153. await servers[1].videos.upload({ attributes })
  154. await waitJobs(servers)
  155. for (const server of servers) {
  156. const { data } = await server.videos.list()
  157. const video = data.find(v => v.name === attributes.name)
  158. const videoDetails = await server.videos.get({ id: video.id })
  159. expect(videoDetails.files).to.have.lengthOf(5)
  160. const magnetUri = videoDetails.files[0].magnetUri
  161. expect(magnetUri).to.contain('.mp4')
  162. }
  163. }
  164. })
  165. it('Should transcode a 4k video', async function () {
  166. this.timeout(200_000)
  167. const attributes = {
  168. name: '4k video',
  169. fixture: 'video_short_4k.mp4'
  170. }
  171. const { uuid } = await servers[1].videos.upload({ attributes })
  172. video4k = uuid
  173. await waitJobs(servers)
  174. const resolutions = [ 144, 240, 360, 480, 720, 1080, 1440, 2160 ]
  175. for (const server of servers) {
  176. const videoDetails = await server.videos.get({ id: video4k })
  177. expect(videoDetails.files).to.have.lengthOf(resolutions.length)
  178. for (const r of resolutions) {
  179. expect(videoDetails.files.find(f => f.resolution.id === r)).to.not.be.undefined
  180. expect(videoDetails.streamingPlaylists[0].files.find(f => f.resolution.id === r)).to.not.be.undefined
  181. }
  182. }
  183. })
  184. })
  185. describe('Audio transcoding', function () {
  186. it('Should transcode high bit rate mp3 to proper bit rate', async function () {
  187. this.timeout(60_000)
  188. const attributes = {
  189. name: 'mp3_256k',
  190. fixture: 'video_short_mp3_256k.mp4'
  191. }
  192. await servers[1].videos.upload({ attributes })
  193. await waitJobs(servers)
  194. for (const server of servers) {
  195. const { data } = await server.videos.list()
  196. const video = data.find(v => v.name === attributes.name)
  197. const videoDetails = await server.videos.get({ id: video.id })
  198. expect(videoDetails.files).to.have.lengthOf(5)
  199. const file = videoDetails.files.find(f => f.resolution.id === 240)
  200. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  201. const probe = await getAudioStream(path)
  202. if (probe.audioStream) {
  203. expect(probe.audioStream['codec_name']).to.be.equal('aac')
  204. expect(probe.audioStream['bit_rate']).to.be.at.most(384 * 8000)
  205. } else {
  206. this.fail('Could not retrieve the audio stream on ' + probe.absolutePath)
  207. }
  208. }
  209. })
  210. it('Should transcode video with no audio and have no audio itself', async function () {
  211. this.timeout(60_000)
  212. const attributes = {
  213. name: 'no_audio',
  214. fixture: 'video_short_no_audio.mp4'
  215. }
  216. await servers[1].videos.upload({ attributes })
  217. await waitJobs(servers)
  218. for (const server of servers) {
  219. const { data } = await server.videos.list()
  220. const video = data.find(v => v.name === attributes.name)
  221. const videoDetails = await server.videos.get({ id: video.id })
  222. const file = videoDetails.files.find(f => f.resolution.id === 240)
  223. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  224. expect(await hasAudioStream(path)).to.be.false
  225. }
  226. })
  227. it('Should leave the audio untouched, but properly transcode the video', async function () {
  228. this.timeout(60_000)
  229. const attributes = {
  230. name: 'untouched_audio',
  231. fixture: 'video_short.mp4'
  232. }
  233. await servers[1].videos.upload({ attributes })
  234. await waitJobs(servers)
  235. for (const server of servers) {
  236. const { data } = await server.videos.list()
  237. const video = data.find(v => v.name === attributes.name)
  238. const videoDetails = await server.videos.get({ id: video.id })
  239. expect(videoDetails.files).to.have.lengthOf(5)
  240. const fixturePath = buildAbsoluteFixturePath(attributes.fixture)
  241. const fixtureVideoProbe = await getAudioStream(fixturePath)
  242. const file = videoDetails.files.find(f => f.resolution.id === 240)
  243. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  244. const videoProbe = await getAudioStream(path)
  245. if (videoProbe.audioStream && fixtureVideoProbe.audioStream) {
  246. const toOmit = [ 'max_bit_rate', 'duration', 'duration_ts', 'nb_frames', 'start_time', 'start_pts' ]
  247. expect(omit(videoProbe.audioStream, toOmit)).to.be.deep.equal(omit(fixtureVideoProbe.audioStream, toOmit))
  248. } else {
  249. this.fail('Could not retrieve the audio stream on ' + videoProbe.absolutePath)
  250. }
  251. }
  252. })
  253. })
  254. describe('Audio upload', function () {
  255. function runSuite (mode: 'legacy' | 'resumable') {
  256. before(async function () {
  257. await servers[1].config.updateCustomSubConfig({
  258. newConfig: {
  259. transcoding: {
  260. hls: { enabled: true },
  261. webVideos: { enabled: true },
  262. resolutions: {
  263. '0p': false,
  264. '144p': false,
  265. '240p': false,
  266. '360p': false,
  267. '480p': false,
  268. '720p': false,
  269. '1080p': false,
  270. '1440p': false,
  271. '2160p': false
  272. }
  273. }
  274. }
  275. })
  276. })
  277. it('Should merge an audio file with the preview file', async function () {
  278. this.timeout(60_000)
  279. const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' }
  280. await servers[1].videos.upload({ attributes, mode })
  281. await waitJobs(servers)
  282. for (const server of servers) {
  283. const { data } = await server.videos.list()
  284. const video = data.find(v => v.name === 'audio_with_preview')
  285. const videoDetails = await server.videos.get({ id: video.id })
  286. expect(videoDetails.files).to.have.lengthOf(1)
  287. await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
  288. await makeGetRequest({ url: server.url, path: videoDetails.previewPath, expectedStatus: HttpStatusCode.OK_200 })
  289. const magnetUri = videoDetails.files[0].magnetUri
  290. expect(magnetUri).to.contain('.mp4')
  291. }
  292. })
  293. it('Should upload an audio file and choose a default background image', async function () {
  294. this.timeout(60_000)
  295. const attributes = { name: 'audio_without_preview', fixture: 'sample.ogg' }
  296. await servers[1].videos.upload({ attributes, mode })
  297. await waitJobs(servers)
  298. for (const server of servers) {
  299. const { data } = await server.videos.list()
  300. const video = data.find(v => v.name === 'audio_without_preview')
  301. const videoDetails = await server.videos.get({ id: video.id })
  302. expect(videoDetails.files).to.have.lengthOf(1)
  303. await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, expectedStatus: HttpStatusCode.OK_200 })
  304. await makeGetRequest({ url: server.url, path: videoDetails.previewPath, expectedStatus: HttpStatusCode.OK_200 })
  305. const magnetUri = videoDetails.files[0].magnetUri
  306. expect(magnetUri).to.contain('.mp4')
  307. }
  308. })
  309. it('Should upload an audio file and create an audio version only', async function () {
  310. this.timeout(60_000)
  311. await servers[1].config.updateCustomSubConfig({
  312. newConfig: {
  313. transcoding: {
  314. hls: { enabled: true },
  315. webVideos: { enabled: true },
  316. resolutions: {
  317. '0p': true,
  318. '144p': false,
  319. '240p': false,
  320. '360p': false
  321. }
  322. }
  323. }
  324. })
  325. const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' }
  326. const { id } = await servers[1].videos.upload({ attributes, mode })
  327. await waitJobs(servers)
  328. for (const server of servers) {
  329. const videoDetails = await server.videos.get({ id })
  330. for (const files of [ videoDetails.files, videoDetails.streamingPlaylists[0].files ]) {
  331. expect(files).to.have.lengthOf(2)
  332. expect(files.find(f => f.resolution.id === 0)).to.not.be.undefined
  333. }
  334. }
  335. await updateConfigForTranscoding(servers[1])
  336. })
  337. }
  338. describe('Legacy upload', function () {
  339. runSuite('legacy')
  340. })
  341. describe('Resumable upload', function () {
  342. runSuite('resumable')
  343. })
  344. })
  345. describe('Framerate', function () {
  346. it('Should transcode a 60 FPS video', async function () {
  347. this.timeout(60_000)
  348. const attributes = {
  349. name: 'my super 30fps name for server 2',
  350. description: 'my super 30fps description for server 2',
  351. fixture: '60fps_720p_small.mp4'
  352. }
  353. await servers[1].videos.upload({ attributes })
  354. await waitJobs(servers)
  355. for (const server of servers) {
  356. const { data } = await server.videos.list()
  357. const video = data.find(v => v.name === attributes.name)
  358. const videoDetails = await server.videos.get({ id: video.id })
  359. expect(videoDetails.files).to.have.lengthOf(5)
  360. expect(videoDetails.files[0].fps).to.be.above(58).and.below(62)
  361. expect(videoDetails.files[1].fps).to.be.below(31)
  362. expect(videoDetails.files[2].fps).to.be.below(31)
  363. expect(videoDetails.files[3].fps).to.be.below(31)
  364. expect(videoDetails.files[4].fps).to.be.below(31)
  365. for (const resolution of [ 144, 240, 360, 480 ]) {
  366. const file = videoDetails.files.find(f => f.resolution.id === resolution)
  367. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  368. const fps = await getVideoStreamFPS(path)
  369. expect(fps).to.be.below(31)
  370. }
  371. const file = videoDetails.files.find(f => f.resolution.id === 720)
  372. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  373. const fps = await getVideoStreamFPS(path)
  374. expect(fps).to.be.above(58).and.below(62)
  375. }
  376. })
  377. it('Should downscale to the closest divisor standard framerate', async function () {
  378. this.timeout(200_000)
  379. let tempFixturePath: string
  380. {
  381. tempFixturePath = await generateVideoWithFramerate(59)
  382. const fps = await getVideoStreamFPS(tempFixturePath)
  383. expect(fps).to.be.equal(59)
  384. }
  385. const attributes = {
  386. name: '59fps video',
  387. description: '59fps video',
  388. fixture: tempFixturePath
  389. }
  390. await servers[1].videos.upload({ attributes })
  391. await waitJobs(servers)
  392. for (const server of servers) {
  393. const { data } = await server.videos.list()
  394. const { id } = data.find(v => v.name === attributes.name)
  395. const video = await server.videos.get({ id })
  396. {
  397. const file = video.files.find(f => f.resolution.id === 240)
  398. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  399. const fps = await getVideoStreamFPS(path)
  400. expect(fps).to.be.equal(25)
  401. }
  402. {
  403. const file = video.files.find(f => f.resolution.id === 720)
  404. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  405. const fps = await getVideoStreamFPS(path)
  406. expect(fps).to.be.equal(59)
  407. }
  408. }
  409. })
  410. })
  411. describe('Bitrate control', function () {
  412. it('Should respect maximum bitrate values', async function () {
  413. this.timeout(160_000)
  414. const tempFixturePath = await generateHighBitrateVideo()
  415. const attributes = {
  416. name: 'high bitrate video',
  417. description: 'high bitrate video',
  418. fixture: tempFixturePath
  419. }
  420. await servers[1].videos.upload({ attributes })
  421. await waitJobs(servers)
  422. for (const server of servers) {
  423. const { data } = await server.videos.list()
  424. const { id } = data.find(v => v.name === attributes.name)
  425. const video = await server.videos.get({ id })
  426. for (const resolution of [ 240, 360, 480, 720, 1080 ]) {
  427. const file = video.files.find(f => f.resolution.id === resolution)
  428. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  429. const bitrate = await getVideoStreamBitrate(path)
  430. const fps = await getVideoStreamFPS(path)
  431. const dataResolution = await getVideoStreamDimensionsInfo(path)
  432. expect(resolution).to.equal(resolution)
  433. const maxBitrate = getMaxTheoreticalBitrate({ ...dataResolution, fps })
  434. expect(bitrate).to.be.below(maxBitrate)
  435. }
  436. }
  437. })
  438. it('Should not transcode to an higher bitrate than the original file but above our low limit', async function () {
  439. this.timeout(160_000)
  440. const newConfig = {
  441. transcoding: {
  442. enabled: true,
  443. resolutions: {
  444. '144p': true,
  445. '240p': true,
  446. '360p': true,
  447. '480p': true,
  448. '720p': true,
  449. '1080p': true,
  450. '1440p': true,
  451. '2160p': true
  452. },
  453. webVideos: { enabled: true },
  454. hls: { enabled: true }
  455. }
  456. }
  457. await servers[1].config.updateCustomSubConfig({ newConfig })
  458. const attributes = {
  459. name: 'low bitrate',
  460. fixture: 'low-bitrate.mp4'
  461. }
  462. const { id } = await servers[1].videos.upload({ attributes })
  463. await waitJobs(servers)
  464. const video = await servers[1].videos.get({ id })
  465. const resolutions = [ 240, 360, 480, 720, 1080 ]
  466. for (const r of resolutions) {
  467. const file = video.files.find(f => f.resolution.id === r)
  468. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  469. const bitrate = await getVideoStreamBitrate(path)
  470. const inputBitrate = 60_000
  471. const limit = getMinTheoreticalBitrate({ fps: 10, ratio: 1, resolution: r })
  472. let belowValue = Math.max(inputBitrate, limit)
  473. belowValue += belowValue * 0.20 // Apply 20% margin because bitrate control is not very precise
  474. expect(bitrate, `${path} not below ${limit}`).to.be.below(belowValue)
  475. }
  476. })
  477. })
  478. describe('FFprobe', function () {
  479. it('Should provide valid ffprobe data', async function () {
  480. this.timeout(160_000)
  481. const videoUUID = (await servers[1].videos.quickUpload({ name: 'ffprobe data' })).uuid
  482. await waitJobs(servers)
  483. {
  484. const video = await servers[1].videos.get({ id: videoUUID })
  485. const file = video.files.find(f => f.resolution.id === 240)
  486. const path = servers[1].servers.buildWebVideoFilePath(file.fileUrl)
  487. const probe = await ffprobePromise(path)
  488. const metadata = new VideoFileMetadata(probe)
  489. // expected format properties
  490. for (const p of [
  491. 'tags.encoder',
  492. 'format_long_name',
  493. 'size',
  494. 'bit_rate'
  495. ]) {
  496. expect(metadata.format).to.have.nested.property(p)
  497. }
  498. // expected stream properties
  499. for (const p of [
  500. 'codec_long_name',
  501. 'profile',
  502. 'width',
  503. 'height',
  504. 'display_aspect_ratio',
  505. 'avg_frame_rate',
  506. 'pix_fmt'
  507. ]) {
  508. expect(metadata.streams[0]).to.have.nested.property(p)
  509. }
  510. expect(metadata).to.not.have.nested.property('format.filename')
  511. }
  512. for (const server of servers) {
  513. const videoDetails = await server.videos.get({ id: videoUUID })
  514. const videoFiles = getAllFiles(videoDetails)
  515. expect(videoFiles).to.have.lengthOf(10)
  516. for (const file of videoFiles) {
  517. expect(file.metadata).to.be.undefined
  518. expect(file.metadataUrl).to.exist
  519. expect(file.metadataUrl).to.contain(servers[1].url)
  520. expect(file.metadataUrl).to.contain(videoUUID)
  521. const metadata = await server.videos.getFileMetadata({ url: file.metadataUrl })
  522. expect(metadata).to.have.nested.property('format.size')
  523. }
  524. }
  525. })
  526. it('Should correctly detect if quick transcode is possible', async function () {
  527. this.timeout(10_000)
  528. expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.mp4'))).to.be.true
  529. expect(await canDoQuickTranscode(buildAbsoluteFixturePath('video_short.webm'))).to.be.false
  530. })
  531. })
  532. describe('Transcoding job queue', function () {
  533. it('Should have the appropriate priorities for transcoding jobs', async function () {
  534. const body = await servers[1].jobs.list({
  535. start: 0,
  536. count: 100,
  537. sort: 'createdAt',
  538. jobType: 'video-transcoding'
  539. })
  540. const jobs = body.data
  541. const transcodingJobs = jobs.filter(j => j.data.videoUUID === video4k)
  542. expect(transcodingJobs).to.have.lengthOf(16)
  543. const hlsJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-hls')
  544. const webVideoJobs = transcodingJobs.filter(j => j.data.type === 'new-resolution-to-web-video')
  545. const optimizeJobs = transcodingJobs.filter(j => j.data.type === 'optimize-to-web-video')
  546. expect(hlsJobs).to.have.lengthOf(8)
  547. expect(webVideoJobs).to.have.lengthOf(7)
  548. expect(optimizeJobs).to.have.lengthOf(1)
  549. for (const j of optimizeJobs.concat(hlsJobs.concat(webVideoJobs))) {
  550. expect(j.priority).to.be.greaterThan(100)
  551. expect(j.priority).to.be.lessThan(150)
  552. }
  553. })
  554. })
  555. describe('Bounded transcoding', function () {
  556. it('Should not generate an upper resolution than original file', async function () {
  557. this.timeout(120_000)
  558. await servers[0].config.updateExistingSubConfig({
  559. newConfig: {
  560. transcoding: {
  561. enabled: true,
  562. hls: { enabled: true },
  563. webVideos: { enabled: true },
  564. resolutions: {
  565. '0p': false,
  566. '144p': false,
  567. '240p': true,
  568. '360p': false,
  569. '480p': true,
  570. '720p': false,
  571. '1080p': false,
  572. '1440p': false,
  573. '2160p': false
  574. },
  575. alwaysTranscodeOriginalResolution: false
  576. }
  577. }
  578. })
  579. const { uuid } = await servers[0].videos.quickUpload({ name: 'video', fixture: 'video_short.webm' })
  580. await waitJobs(servers)
  581. const video = await servers[0].videos.get({ id: uuid })
  582. const hlsFiles = video.streamingPlaylists[0].files
  583. expect(video.files).to.have.lengthOf(2)
  584. expect(hlsFiles).to.have.lengthOf(2)
  585. // eslint-disable-next-line @typescript-eslint/require-array-sort-compare
  586. const resolutions = getAllFiles(video).map(f => f.resolution.id).sort()
  587. expect(resolutions).to.deep.equal([ 240, 240, 480, 480 ])
  588. })
  589. it('Should only keep the original resolution if all resolutions are disabled', async function () {
  590. this.timeout(120_000)
  591. await servers[0].config.updateExistingSubConfig({
  592. newConfig: {
  593. transcoding: {
  594. resolutions: {
  595. '0p': false,
  596. '144p': false,
  597. '240p': false,
  598. '360p': false,
  599. '480p': false,
  600. '720p': false,
  601. '1080p': false,
  602. '1440p': false,
  603. '2160p': false
  604. }
  605. }
  606. }
  607. })
  608. const { uuid } = await servers[0].videos.quickUpload({ name: 'video', fixture: 'video_short.webm' })
  609. await waitJobs(servers)
  610. const video = await servers[0].videos.get({ id: uuid })
  611. const hlsFiles = video.streamingPlaylists[0].files
  612. expect(video.files).to.have.lengthOf(1)
  613. expect(hlsFiles).to.have.lengthOf(1)
  614. expect(video.files[0].resolution.id).to.equal(720)
  615. expect(hlsFiles[0].resolution.id).to.equal(720)
  616. })
  617. })
  618. after(async function () {
  619. await cleanupTests(servers)
  620. })
  621. })