video-file.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { FfprobeData } from 'fluent-ffmpeg'
  2. import { VideoFileMetadata, VideoResolution } from '@peertube/peertube-models'
  3. import { logger } from '@server/helpers/logger.js'
  4. import { VideoFileModel } from '@server/models/video/video-file.js'
  5. import { MVideoWithAllFiles } from '@server/types/models/index.js'
  6. import { getFileSize, getLowercaseExtension } from '@peertube/peertube-node-utils'
  7. import { ffprobePromise, getVideoStreamDimensionsInfo, getVideoStreamFPS, isAudioFile } from '@peertube/peertube-ffmpeg'
  8. import { lTags } from './object-storage/shared/index.js'
  9. import { generateHLSVideoFilename, generateWebVideoFilename } from './paths.js'
  10. import { VideoPathManager } from './video-path-manager.js'
  11. import { MIMETYPES } from '@server/initializers/constants.js'
  12. async function buildNewFile (options: {
  13. path: string
  14. mode: 'web-video' | 'hls'
  15. ffprobe?: FfprobeData
  16. }) {
  17. const { path, mode, ffprobe: probeArg } = options
  18. const probe = probeArg ?? await ffprobePromise(path)
  19. const size = await getFileSize(path)
  20. const videoFile = new VideoFileModel({
  21. extname: getLowercaseExtension(path),
  22. size,
  23. metadata: await buildFileMetadata(path, probe)
  24. })
  25. if (await isAudioFile(path, probe)) {
  26. videoFile.resolution = VideoResolution.H_NOVIDEO
  27. } else {
  28. videoFile.fps = await getVideoStreamFPS(path, probe)
  29. videoFile.resolution = (await getVideoStreamDimensionsInfo(path, probe)).resolution
  30. }
  31. videoFile.filename = mode === 'web-video'
  32. ? generateWebVideoFilename(videoFile.resolution, videoFile.extname)
  33. : generateHLSVideoFilename(videoFile.resolution)
  34. return videoFile
  35. }
  36. // ---------------------------------------------------------------------------
  37. async function removeHLSPlaylist (video: MVideoWithAllFiles) {
  38. const hls = video.getHLSPlaylist()
  39. if (!hls) return
  40. const videoFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
  41. try {
  42. await video.removeStreamingPlaylistFiles(hls)
  43. await hls.destroy()
  44. video.VideoStreamingPlaylists = video.VideoStreamingPlaylists.filter(p => p.id !== hls.id)
  45. } finally {
  46. videoFileMutexReleaser()
  47. }
  48. }
  49. async function removeHLSFile (video: MVideoWithAllFiles, fileToDeleteId: number) {
  50. logger.info('Deleting HLS file %d of %s.', fileToDeleteId, video.url, lTags(video.uuid))
  51. const hls = video.getHLSPlaylist()
  52. const files = hls.VideoFiles
  53. if (files.length === 1) {
  54. await removeHLSPlaylist(video)
  55. return undefined
  56. }
  57. const videoFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
  58. try {
  59. const toDelete = files.find(f => f.id === fileToDeleteId)
  60. await video.removeStreamingPlaylistVideoFile(video.getHLSPlaylist(), toDelete)
  61. await toDelete.destroy()
  62. hls.VideoFiles = hls.VideoFiles.filter(f => f.id !== toDelete.id)
  63. } finally {
  64. videoFileMutexReleaser()
  65. }
  66. return hls
  67. }
  68. // ---------------------------------------------------------------------------
  69. async function removeAllWebVideoFiles (video: MVideoWithAllFiles) {
  70. const videoFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
  71. try {
  72. for (const file of video.VideoFiles) {
  73. await video.removeWebVideoFile(file)
  74. await file.destroy()
  75. }
  76. video.VideoFiles = []
  77. } finally {
  78. videoFileMutexReleaser()
  79. }
  80. return video
  81. }
  82. async function removeWebVideoFile (video: MVideoWithAllFiles, fileToDeleteId: number) {
  83. const files = video.VideoFiles
  84. if (files.length === 1) {
  85. return removeAllWebVideoFiles(video)
  86. }
  87. const videoFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
  88. try {
  89. const toDelete = files.find(f => f.id === fileToDeleteId)
  90. await video.removeWebVideoFile(toDelete)
  91. await toDelete.destroy()
  92. video.VideoFiles = files.filter(f => f.id !== toDelete.id)
  93. } finally {
  94. videoFileMutexReleaser()
  95. }
  96. return video
  97. }
  98. // ---------------------------------------------------------------------------
  99. async function buildFileMetadata (path: string, existingProbe?: FfprobeData) {
  100. const metadata = existingProbe || await ffprobePromise(path)
  101. return new VideoFileMetadata(metadata)
  102. }
  103. function getVideoFileMimeType (extname: string, isAudio: boolean) {
  104. return isAudio && extname === '.mp4' // We use .mp4 even for audio file only
  105. ? MIMETYPES.AUDIO.EXT_MIMETYPE['.m4a']
  106. : MIMETYPES.VIDEO.EXT_MIMETYPE[extname]
  107. }
  108. // ---------------------------------------------------------------------------
  109. export {
  110. buildNewFile,
  111. removeHLSPlaylist,
  112. removeHLSFile,
  113. removeAllWebVideoFiles,
  114. removeWebVideoFile,
  115. buildFileMetadata,
  116. getVideoFileMimeType
  117. }