video-transcoding.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
  2. import { join } from 'path'
  3. import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils'
  4. import { ensureDir, move, remove, stat } from 'fs-extra'
  5. import { logger } from '../helpers/logger'
  6. import { VideoResolution } from '../../shared/models/videos'
  7. import { VideoFileModel } from '../models/video/video-file'
  8. import { VideoModel } from '../models/video/video'
  9. import { updateMasterHLSPlaylist, updateSha256Segments } from './hls'
  10. import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
  11. import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
  12. import { CONFIG } from '../initializers/config'
  13. /**
  14. * Optimize the original video file and replace it. The resolution is not changed.
  15. */
  16. async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) {
  17. const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
  18. const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
  19. const newExtname = '.mp4'
  20. const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile()
  21. const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
  22. const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
  23. const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath)
  24. ? 'quick-transcode'
  25. : 'video'
  26. const transcodeOptions: TranscodeOptions = {
  27. type: transcodeType as any, // FIXME: typing issue
  28. inputPath: videoInputPath,
  29. outputPath: videoTranscodedPath,
  30. resolution: inputVideoFile.resolution
  31. }
  32. // Could be very long!
  33. await transcode(transcodeOptions)
  34. try {
  35. await remove(videoInputPath)
  36. // Important to do this before getVideoFilename() to take in account the new file extension
  37. inputVideoFile.extname = newExtname
  38. const videoOutputPath = video.getVideoFilePath(inputVideoFile)
  39. await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
  40. } catch (err) {
  41. // Auto destruction...
  42. video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err }))
  43. throw err
  44. }
  45. }
  46. /**
  47. * Transcode the original video file to a lower resolution.
  48. */
  49. async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) {
  50. const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
  51. const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
  52. const extname = '.mp4'
  53. // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
  54. const videoInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))
  55. const newVideoFile = new VideoFileModel({
  56. resolution,
  57. extname,
  58. size: 0,
  59. videoId: video.id
  60. })
  61. const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile))
  62. const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile))
  63. const transcodeOptions = {
  64. type: 'video' as 'video',
  65. inputPath: videoInputPath,
  66. outputPath: videoTranscodedPath,
  67. resolution,
  68. isPortraitMode: isPortrait
  69. }
  70. await transcode(transcodeOptions)
  71. return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
  72. }
  73. async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) {
  74. const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
  75. const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
  76. const newExtname = '.mp4'
  77. const inputVideoFile = video.getOriginalFile()
  78. const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))
  79. const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
  80. const transcodeOptions = {
  81. type: 'merge-audio' as 'merge-audio',
  82. inputPath: video.getPreview().getPath(),
  83. outputPath: videoTranscodedPath,
  84. audioPath: audioInputPath,
  85. resolution
  86. }
  87. await transcode(transcodeOptions)
  88. await remove(audioInputPath)
  89. // Important to do this before getVideoFilename() to take in account the new file extension
  90. inputVideoFile.extname = newExtname
  91. const videoOutputPath = video.getVideoFilePath(inputVideoFile)
  92. return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
  93. }
  94. async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) {
  95. const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
  96. await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
  97. const videoInputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(video.getOriginalFile()))
  98. const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution))
  99. const transcodeOptions = {
  100. type: 'hls' as 'hls',
  101. inputPath: videoInputPath,
  102. outputPath,
  103. resolution,
  104. isPortraitMode,
  105. hlsPlaylist: {
  106. videoFilename: VideoStreamingPlaylistModel.getHlsVideoName(video.uuid, resolution)
  107. }
  108. }
  109. await transcode(transcodeOptions)
  110. await updateMasterHLSPlaylist(video)
  111. await updateSha256Segments(video)
  112. const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid)
  113. await VideoStreamingPlaylistModel.upsert({
  114. videoId: video.id,
  115. playlistUrl,
  116. segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid),
  117. p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrl, video.VideoFiles),
  118. p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION,
  119. type: VideoStreamingPlaylistType.HLS
  120. })
  121. }
  122. // ---------------------------------------------------------------------------
  123. export {
  124. generateHlsPlaylist,
  125. optimizeVideofile,
  126. transcodeOriginalVideofile,
  127. mergeAudioVideofile
  128. }
  129. // ---------------------------------------------------------------------------
  130. async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) {
  131. const stats = await stat(transcodingPath)
  132. const fps = await getVideoFileFPS(transcodingPath)
  133. await move(transcodingPath, outputPath)
  134. videoFile.set('size', stats.size)
  135. videoFile.set('fps', fps)
  136. await video.createTorrentAndSetInfoHash(videoFile)
  137. const updatedVideoFile = await videoFile.save()
  138. // Add it if this is a new created file
  139. if (video.VideoFiles.some(f => f.id === videoFile.id) === false) {
  140. video.VideoFiles.push(updatedVideoFile)
  141. }
  142. return video
  143. }