video-chapters.ts 3.6 KB

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