video-path-manager.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import { FileStorage } from '@peertube/peertube-models'
  2. import { buildUUID } from '@peertube/peertube-node-utils'
  3. import { logger, loggerTagsFactory } from '@server/helpers/logger.js'
  4. import { extractVideo } from '@server/helpers/video.js'
  5. import { CONFIG } from '@server/initializers/config.js'
  6. import { DIRECTORIES } from '@server/initializers/constants.js'
  7. import {
  8. MStreamingPlaylistVideo,
  9. MVideo,
  10. MVideoFile,
  11. MVideoFileStreamingPlaylistVideo,
  12. MVideoFileVideo
  13. } from '@server/types/models/index.js'
  14. import { Mutex } from 'async-mutex'
  15. import { remove } from 'fs-extra/esm'
  16. import { extname, join } from 'path'
  17. import { makeHLSFileAvailable, makeWebVideoFileAvailable } from './object-storage/index.js'
  18. import { getHLSDirectory, getHLSRedundancyDirectory, getHlsResolutionPlaylistFilename } from './paths.js'
  19. import { isVideoInPrivateDirectory } from './video-privacy.js'
  20. type MakeAvailableCB <T> = (path: string) => Promise<T> | T
  21. const lTags = loggerTagsFactory('video-path-manager')
  22. class VideoPathManager {
  23. private static instance: VideoPathManager
  24. // Key is a video UUID
  25. private readonly videoFileMutexStore = new Map<string, Mutex>()
  26. private constructor () {}
  27. getFSHLSOutputPath (video: MVideo, filename?: string) {
  28. const base = getHLSDirectory(video)
  29. if (!filename) return base
  30. return join(base, filename)
  31. }
  32. getFSRedundancyVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
  33. if (videoFile.isHLS()) {
  34. const video = extractVideo(videoOrPlaylist)
  35. return join(getHLSRedundancyDirectory(video), videoFile.filename)
  36. }
  37. return join(CONFIG.STORAGE.REDUNDANCY_DIR, videoFile.filename)
  38. }
  39. getFSVideoFileOutputPath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
  40. const video = extractVideo(videoOrPlaylist)
  41. if (videoFile.isHLS()) {
  42. return join(getHLSDirectory(video), videoFile.filename)
  43. }
  44. if (isVideoInPrivateDirectory(video.privacy)) {
  45. return join(DIRECTORIES.WEB_VIDEOS.PRIVATE, videoFile.filename)
  46. }
  47. return join(DIRECTORIES.WEB_VIDEOS.PUBLIC, videoFile.filename)
  48. }
  49. getFSOriginalVideoFilePath (filename: string) {
  50. return join(DIRECTORIES.ORIGINAL_VIDEOS, filename)
  51. }
  52. async makeAvailableVideoFile <T> (videoFile: MVideoFileVideo | MVideoFileStreamingPlaylistVideo, cb: MakeAvailableCB<T>) {
  53. if (videoFile.storage === FileStorage.FILE_SYSTEM) {
  54. return this.makeAvailableFactory(
  55. () => this.getFSVideoFileOutputPath(videoFile.getVideoOrStreamingPlaylist(), videoFile),
  56. false,
  57. cb
  58. )
  59. }
  60. const destination = this.buildTMPDestination(videoFile.filename)
  61. if (videoFile.isHLS()) {
  62. const playlist = (videoFile as MVideoFileStreamingPlaylistVideo).VideoStreamingPlaylist
  63. return this.makeAvailableFactory(
  64. () => makeHLSFileAvailable(playlist, videoFile.filename, destination),
  65. true,
  66. cb
  67. )
  68. }
  69. return this.makeAvailableFactory(
  70. () => makeWebVideoFileAvailable(videoFile.filename, destination),
  71. true,
  72. cb
  73. )
  74. }
  75. async makeAvailableResolutionPlaylistFile <T> (videoFile: MVideoFileStreamingPlaylistVideo, cb: MakeAvailableCB<T>) {
  76. const filename = getHlsResolutionPlaylistFilename(videoFile.filename)
  77. if (videoFile.storage === FileStorage.FILE_SYSTEM) {
  78. return this.makeAvailableFactory(
  79. () => join(getHLSDirectory(videoFile.getVideo()), filename),
  80. false,
  81. cb
  82. )
  83. }
  84. const playlist = videoFile.VideoStreamingPlaylist
  85. return this.makeAvailableFactory(
  86. () => makeHLSFileAvailable(playlist, filename, this.buildTMPDestination(filename)),
  87. true,
  88. cb
  89. )
  90. }
  91. async makeAvailablePlaylistFile <T> (playlist: MStreamingPlaylistVideo, filename: string, cb: MakeAvailableCB<T>) {
  92. if (playlist.storage === FileStorage.FILE_SYSTEM) {
  93. return this.makeAvailableFactory(
  94. () => join(getHLSDirectory(playlist.Video), filename),
  95. false,
  96. cb
  97. )
  98. }
  99. return this.makeAvailableFactory(
  100. () => makeHLSFileAvailable(playlist, filename, this.buildTMPDestination(filename)),
  101. true,
  102. cb
  103. )
  104. }
  105. async lockFiles (videoUUID: string) {
  106. if (!this.videoFileMutexStore.has(videoUUID)) {
  107. this.videoFileMutexStore.set(videoUUID, new Mutex())
  108. }
  109. const mutex = this.videoFileMutexStore.get(videoUUID)
  110. const releaser = await mutex.acquire()
  111. logger.debug('Locked files of %s.', videoUUID, lTags(videoUUID))
  112. return releaser
  113. }
  114. unlockFiles (videoUUID: string) {
  115. const mutex = this.videoFileMutexStore.get(videoUUID)
  116. mutex.release()
  117. logger.debug('Released lockfiles of %s.', videoUUID, lTags(videoUUID))
  118. }
  119. private async makeAvailableFactory <T> (method: () => Promise<string> | string, clean: boolean, cb: MakeAvailableCB<T>) {
  120. let result: T
  121. const destination = await method()
  122. try {
  123. result = await cb(destination)
  124. } catch (err) {
  125. if (destination && clean) await remove(destination)
  126. throw err
  127. }
  128. if (clean) await remove(destination)
  129. return result
  130. }
  131. private buildTMPDestination (filename: string) {
  132. return join(CONFIG.STORAGE.TMP_DIR, buildUUID() + extname(filename))
  133. }
  134. static get Instance () {
  135. return this.instance || (this.instance = new this())
  136. }
  137. }
  138. // ---------------------------------------------------------------------------
  139. export {
  140. VideoPathManager
  141. }