video-chapters.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import { parseChapters, sortBy } from '@peertube/peertube-core-utils'
  2. import { VideoChapter } from '@peertube/peertube-models'
  3. import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
  4. import { VideoChapterModel } from '@server/models/video/video-chapter.js'
  5. import { MVideoImmutable } from '@server/types/models/index.js'
  6. import { Transaction } from 'sequelize'
  7. import { InternalEventEmitter } from './internal-event-emitter.js'
  8. const lTags = loggerTagsFactory('video', 'chapters')
  9. export async function replaceChapters (options: {
  10. video: MVideoImmutable
  11. chapters: VideoChapter[]
  12. transaction: Transaction
  13. }) {
  14. const { chapters, transaction, video } = options
  15. await VideoChapterModel.deleteChapters(video.id, transaction)
  16. await createChapters({ videoId: video.id, chapters, transaction })
  17. InternalEventEmitter.Instance.emit('chapters-updated', { video })
  18. }
  19. export async function replaceChaptersIfNotExist (options: {
  20. video: MVideoImmutable
  21. chapters: VideoChapter[]
  22. transaction: Transaction
  23. }) {
  24. const { chapters, transaction, video } = options
  25. if (await VideoChapterModel.hasVideoChapters(video.id, transaction)) return
  26. await createChapters({ videoId: video.id, chapters, transaction })
  27. InternalEventEmitter.Instance.emit('chapters-updated', { video })
  28. }
  29. export async function replaceChaptersFromDescriptionIfNeeded (options: {
  30. oldDescription?: string
  31. newDescription: string
  32. video: MVideoImmutable
  33. transaction: Transaction
  34. }) {
  35. const { transaction, video, newDescription, oldDescription = '' } = options
  36. const chaptersFromOldDescription = sortBy(parseChapters(oldDescription), 'timecode')
  37. const existingChapters = await VideoChapterModel.listChaptersOfVideo(video.id, transaction)
  38. logger.debug(
  39. 'Check if we replace chapters from description',
  40. { oldDescription, chaptersFromOldDescription, newDescription, existingChapters, ...lTags(video.uuid) }
  41. )
  42. // Then we can update chapters from the new description
  43. if (areSameChapters(chaptersFromOldDescription, existingChapters)) {
  44. const chaptersFromNewDescription = sortBy(parseChapters(newDescription), 'timecode')
  45. if (chaptersFromOldDescription.length === 0 && chaptersFromNewDescription.length === 0) return false
  46. await replaceChapters({ video, chapters: chaptersFromNewDescription, transaction })
  47. logger.info('Replaced chapters of video ' + video.uuid, { chaptersFromNewDescription, ...lTags(video.uuid) })
  48. return true
  49. }
  50. return false
  51. }
  52. // ---------------------------------------------------------------------------
  53. // Private
  54. // ---------------------------------------------------------------------------
  55. async function createChapters (options: {
  56. videoId: number
  57. chapters: VideoChapter[]
  58. transaction: Transaction
  59. }) {
  60. const { chapters, transaction, videoId } = options
  61. for (const chapter of chapters) {
  62. await VideoChapterModel.create({
  63. title: chapter.title,
  64. timecode: chapter.timecode,
  65. videoId
  66. }, { transaction })
  67. }
  68. }
  69. function areSameChapters (chapters1: VideoChapter[], chapters2: VideoChapter[]) {
  70. if (chapters1.length !== chapters2.length) return false
  71. for (let i = 0; i < chapters1.length; i++) {
  72. if (chapters1[i].timecode !== chapters2[i].timecode) return false
  73. if (chapters1[i].title !== chapters2[i].title) return false
  74. }
  75. return true
  76. }