]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/video-transcoding.ts
Fix caption validators test
[github/Chocobozzz/PeerTube.git] / server / lib / video-transcoding.ts
CommitLineData
7ed2c1a4 1import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
eba06469
C
2import { basename, join } from 'path'
3import {
4 canDoQuickTranscode,
5 getDurationFromVideoFile,
6 getVideoFileFPS,
7 transcode,
8 TranscodeOptions,
9 TranscodeOptionsType
10} from '../helpers/ffmpeg-utils'
11import { copyFile, ensureDir, move, remove, stat } from 'fs-extra'
098eb377
C
12import { logger } from '../helpers/logger'
13import { VideoResolution } from '../../shared/models/videos'
14import { VideoFileModel } from '../models/video/video-file'
09209296
C
15import { updateMasterHLSPlaylist, updateSha256Segments } from './hls'
16import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
17import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
6dd9de95 18import { CONFIG } from '../initializers/config'
453e83ea 19import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models'
098eb377 20
658a47ab
FA
21/**
22 * Optimize the original video file and replace it. The resolution is not changed.
23 */
453e83ea 24async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) {
098eb377 25 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
2fbd5e25 26 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
098eb377 27 const newExtname = '.mp4'
9f1ddd24
C
28
29 const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile()
30 const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile))
2fbd5e25 31 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
098eb377 32
536598cf
C
33 const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath)
34 ? 'quick-transcode'
35 : 'video'
5ba49f26 36
536598cf
C
37 const transcodeOptions: TranscodeOptions = {
38 type: transcodeType as any, // FIXME: typing issue
098eb377 39 inputPath: videoInputPath,
09209296 40 outputPath: videoTranscodedPath,
536598cf 41 resolution: inputVideoFile.resolution
098eb377
C
42 }
43
44 // Could be very long!
45 await transcode(transcodeOptions)
46
47 try {
48 await remove(videoInputPath)
49
50 // Important to do this before getVideoFilename() to take in account the new file extension
536598cf 51 inputVideoFile.extname = newExtname
2fbd5e25 52
098eb377 53 const videoOutputPath = video.getVideoFilePath(inputVideoFile)
098eb377 54
536598cf 55 await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
098eb377
C
56 } catch (err) {
57 // Auto destruction...
58 video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err }))
59
60 throw err
61 }
62}
63
658a47ab
FA
64/**
65 * Transcode the original video file to a lower resolution.
66 */
453e83ea 67async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) {
098eb377 68 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
2fbd5e25 69 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
098eb377
C
70 const extname = '.mp4'
71
72 // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed
73 const videoInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))
74
75 const newVideoFile = new VideoFileModel({
76 resolution,
77 extname,
78 size: 0,
79 videoId: video.id
80 })
09209296 81 const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile))
2fbd5e25 82 const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile))
098eb377
C
83
84 const transcodeOptions = {
536598cf 85 type: 'video' as 'video',
098eb377 86 inputPath: videoInputPath,
2fbd5e25 87 outputPath: videoTranscodedPath,
098eb377 88 resolution,
09209296 89 isPortraitMode: isPortrait
098eb377
C
90 }
91
92 await transcode(transcodeOptions)
93
536598cf
C
94 return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath)
95}
96
453e83ea 97async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) {
536598cf
C
98 const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR
99 const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
100 const newExtname = '.mp4'
101
102 const inputVideoFile = video.getOriginalFile()
2fbd5e25 103
536598cf
C
104 const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile()))
105 const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname)
098eb377 106
eba06469
C
107 // If the user updates the video preview during transcoding
108 const previewPath = video.getPreview().getPath()
109 const tmpPreviewPath = join(CONFIG.STORAGE.TMP_DIR, basename(previewPath))
110 await copyFile(previewPath, tmpPreviewPath)
111
536598cf
C
112 const transcodeOptions = {
113 type: 'merge-audio' as 'merge-audio',
eba06469 114 inputPath: tmpPreviewPath,
536598cf
C
115 outputPath: videoTranscodedPath,
116 audioPath: audioInputPath,
117 resolution
118 }
098eb377 119
eba06469
C
120 try {
121 await transcode(transcodeOptions)
098eb377 122
eba06469
C
123 await remove(audioInputPath)
124 await remove(tmpPreviewPath)
125 } catch (err) {
126 await remove(tmpPreviewPath)
127 throw err
128 }
098eb377 129
536598cf
C
130 // Important to do this before getVideoFilename() to take in account the new file extension
131 inputVideoFile.extname = newExtname
132
133 const videoOutputPath = video.getVideoFilePath(inputVideoFile)
eba06469
C
134 // ffmpeg generated a new video file, so update the video duration
135 // See https://trac.ffmpeg.org/ticket/5456
136 video.duration = await getDurationFromVideoFile(videoTranscodedPath)
137 await video.save()
536598cf
C
138
139 return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath)
098eb377
C
140}
141
453e83ea 142async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) {
9c6ca37f
C
143 const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)
144 await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid))
09209296 145
29d4e137 146 const videoInputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(video.getFile(resolution)))
09209296
C
147 const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution))
148
149 const transcodeOptions = {
536598cf 150 type: 'hls' as 'hls',
09209296
C
151 inputPath: videoInputPath,
152 outputPath,
153 resolution,
154 isPortraitMode,
4c280004
C
155
156 hlsPlaylist: {
157 videoFilename: VideoStreamingPlaylistModel.getHlsVideoName(video.uuid, resolution)
158 }
09209296
C
159 }
160
161 await transcode(transcodeOptions)
162
163 await updateMasterHLSPlaylist(video)
164 await updateSha256Segments(video)
165
6dd9de95 166 const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid)
09209296
C
167
168 await VideoStreamingPlaylistModel.upsert({
169 videoId: video.id,
170 playlistUrl,
6dd9de95 171 segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid),
09209296 172 p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrl, video.VideoFiles),
594d0c6a 173 p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION,
09209296
C
174
175 type: VideoStreamingPlaylistType.HLS
176 })
177}
178
536598cf
C
179// ---------------------------------------------------------------------------
180
098eb377 181export {
09209296 182 generateHlsPlaylist,
edb4ffc7 183 optimizeVideofile,
536598cf
C
184 transcodeOriginalVideofile,
185 mergeAudioVideofile
186}
187
188// ---------------------------------------------------------------------------
189
453e83ea 190async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) {
536598cf
C
191 const stats = await stat(transcodingPath)
192 const fps = await getVideoFileFPS(transcodingPath)
193
194 await move(transcodingPath, outputPath)
195
453e83ea
C
196 videoFile.size = stats.size
197 videoFile.fps = fps
536598cf
C
198
199 await video.createTorrentAndSetInfoHash(videoFile)
200
201 const updatedVideoFile = await videoFile.save()
202
203 // Add it if this is a new created file
204 if (video.VideoFiles.some(f => f.id === videoFile.id) === false) {
205 video.VideoFiles.push(updatedVideoFile)
206 }
207
208 return video
098eb377 209}