diff options
Diffstat (limited to 'server/lib/video-transcoding.ts')
-rw-r--r-- | server/lib/video-transcoding.ts | 91 |
1 files changed, 63 insertions, 28 deletions
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index 612d388ee..9243d1742 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' | 1 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' |
2 | import { basename, join } from 'path' | 2 | import { basename, extname as extnameUtil, join } from 'path' |
3 | import { | 3 | import { |
4 | canDoQuickTranscode, | 4 | canDoQuickTranscode, |
5 | getDurationFromVideoFile, | 5 | getDurationFromVideoFile, |
@@ -16,18 +16,19 @@ import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' | |||
16 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 16 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
17 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 17 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' |
18 | import { CONFIG } from '../initializers/config' | 18 | import { CONFIG } from '../initializers/config' |
19 | import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models' | 19 | import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/typings/models' |
20 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | ||
21 | import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath } from './video-paths' | ||
20 | 22 | ||
21 | /** | 23 | /** |
22 | * Optimize the original video file and replace it. The resolution is not changed. | 24 | * Optimize the original video file and replace it. The resolution is not changed. |
23 | */ | 25 | */ |
24 | async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) { | 26 | async function optimizeOriginalVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) { |
25 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
26 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 27 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
27 | const newExtname = '.mp4' | 28 | const newExtname = '.mp4' |
28 | 29 | ||
29 | const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile() | 30 | const inputVideoFile = inputVideoFileArg || video.getMaxQualityFile() |
30 | const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) | 31 | const videoInputPath = getVideoFilePath(video, inputVideoFile) |
31 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) | 32 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) |
32 | 33 | ||
33 | const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath) | 34 | const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath) |
@@ -35,7 +36,7 @@ async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVi | |||
35 | : 'video' | 36 | : 'video' |
36 | 37 | ||
37 | const transcodeOptions: TranscodeOptions = { | 38 | const transcodeOptions: TranscodeOptions = { |
38 | type: transcodeType as any, // FIXME: typing issue | 39 | type: transcodeType, |
39 | inputPath: videoInputPath, | 40 | inputPath: videoInputPath, |
40 | outputPath: videoTranscodedPath, | 41 | outputPath: videoTranscodedPath, |
41 | resolution: inputVideoFile.resolution | 42 | resolution: inputVideoFile.resolution |
@@ -50,7 +51,7 @@ async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVi | |||
50 | // Important to do this before getVideoFilename() to take in account the new file extension | 51 | // Important to do this before getVideoFilename() to take in account the new file extension |
51 | inputVideoFile.extname = newExtname | 52 | inputVideoFile.extname = newExtname |
52 | 53 | ||
53 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) | 54 | const videoOutputPath = getVideoFilePath(video, inputVideoFile) |
54 | 55 | ||
55 | await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) | 56 | await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) |
56 | } catch (err) { | 57 | } catch (err) { |
@@ -64,13 +65,12 @@ async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVi | |||
64 | /** | 65 | /** |
65 | * Transcode the original video file to a lower resolution. | 66 | * Transcode the original video file to a lower resolution. |
66 | */ | 67 | */ |
67 | async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) { | 68 | async function transcodeNewResolution (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) { |
68 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
69 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 69 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
70 | const extname = '.mp4' | 70 | const extname = '.mp4' |
71 | 71 | ||
72 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed | 72 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed |
73 | const videoInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile())) | 73 | const videoInputPath = getVideoFilePath(video, video.getMaxQualityFile()) |
74 | 74 | ||
75 | const newVideoFile = new VideoFileModel({ | 75 | const newVideoFile = new VideoFileModel({ |
76 | resolution, | 76 | resolution, |
@@ -78,8 +78,8 @@ async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: Vi | |||
78 | size: 0, | 78 | size: 0, |
79 | videoId: video.id | 79 | videoId: video.id |
80 | }) | 80 | }) |
81 | const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) | 81 | const videoOutputPath = getVideoFilePath(video, newVideoFile) |
82 | const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile)) | 82 | const videoTranscodedPath = join(transcodeDirectory, getVideoFilename(video, newVideoFile)) |
83 | 83 | ||
84 | const transcodeOptions = { | 84 | const transcodeOptions = { |
85 | type: 'video' as 'video', | 85 | type: 'video' as 'video', |
@@ -94,14 +94,13 @@ async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: Vi | |||
94 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) | 94 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) |
95 | } | 95 | } |
96 | 96 | ||
97 | async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) { | 97 | async function mergeAudioVideofile (video: MVideoWithAllFiles, resolution: VideoResolution) { |
98 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
99 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 98 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
100 | const newExtname = '.mp4' | 99 | const newExtname = '.mp4' |
101 | 100 | ||
102 | const inputVideoFile = video.getOriginalFile() | 101 | const inputVideoFile = video.getMaxQualityFile() |
103 | 102 | ||
104 | const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile())) | 103 | const audioInputPath = getVideoFilePath(video, inputVideoFile) |
105 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) | 104 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) |
106 | 105 | ||
107 | // If the user updates the video preview during transcoding | 106 | // If the user updates the video preview during transcoding |
@@ -130,7 +129,7 @@ async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: | |||
130 | // Important to do this before getVideoFilename() to take in account the new file extension | 129 | // Important to do this before getVideoFilename() to take in account the new file extension |
131 | inputVideoFile.extname = newExtname | 130 | inputVideoFile.extname = newExtname |
132 | 131 | ||
133 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) | 132 | const videoOutputPath = getVideoFilePath(video, inputVideoFile) |
134 | // ffmpeg generated a new video file, so update the video duration | 133 | // ffmpeg generated a new video file, so update the video duration |
135 | // See https://trac.ffmpeg.org/ticket/5456 | 134 | // See https://trac.ffmpeg.org/ticket/5456 |
136 | video.duration = await getDurationFromVideoFile(videoTranscodedPath) | 135 | video.duration = await getDurationFromVideoFile(videoTranscodedPath) |
@@ -139,33 +138,40 @@ async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: | |||
139 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) | 138 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) |
140 | } | 139 | } |
141 | 140 | ||
142 | async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) { | 141 | async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, copyCodecs: boolean, isPortraitMode: boolean) { |
143 | const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | 142 | const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
144 | await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) | 143 | await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) |
145 | 144 | ||
146 | const videoInputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(video.getFile(resolution))) | 145 | const videoFileInput = copyCodecs |
146 | ? video.getWebTorrentFile(resolution) | ||
147 | : video.getMaxQualityFile() | ||
148 | |||
149 | const videoOrStreamingPlaylist = videoFileInput.getVideoOrStreamingPlaylist() | ||
150 | const videoInputPath = getVideoFilePath(videoOrStreamingPlaylist, videoFileInput) | ||
151 | |||
147 | const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) | 152 | const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) |
153 | const videoFilename = generateVideoStreamingPlaylistName(video.uuid, resolution) | ||
148 | 154 | ||
149 | const transcodeOptions = { | 155 | const transcodeOptions = { |
150 | type: 'hls' as 'hls', | 156 | type: 'hls' as 'hls', |
151 | inputPath: videoInputPath, | 157 | inputPath: videoInputPath, |
152 | outputPath, | 158 | outputPath, |
153 | resolution, | 159 | resolution, |
160 | copyCodecs, | ||
154 | isPortraitMode, | 161 | isPortraitMode, |
155 | 162 | ||
156 | hlsPlaylist: { | 163 | hlsPlaylist: { |
157 | videoFilename: VideoStreamingPlaylistModel.getHlsVideoName(video.uuid, resolution) | 164 | videoFilename |
158 | } | 165 | } |
159 | } | 166 | } |
160 | 167 | ||
161 | await transcode(transcodeOptions) | 168 | logger.debug('Will run transcode.', { transcodeOptions }) |
162 | 169 | ||
163 | await updateMasterHLSPlaylist(video) | 170 | await transcode(transcodeOptions) |
164 | await updateSha256Segments(video) | ||
165 | 171 | ||
166 | const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) | 172 | const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) |
167 | 173 | ||
168 | await VideoStreamingPlaylistModel.upsert({ | 174 | const [ videoStreamingPlaylist ] = await VideoStreamingPlaylistModel.upsert({ |
169 | videoId: video.id, | 175 | videoId: video.id, |
170 | playlistUrl, | 176 | playlistUrl, |
171 | segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid), | 177 | segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid), |
@@ -173,15 +179,44 @@ async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoReso | |||
173 | p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, | 179 | p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, |
174 | 180 | ||
175 | type: VideoStreamingPlaylistType.HLS | 181 | type: VideoStreamingPlaylistType.HLS |
182 | }, { returning: true }) as [ MStreamingPlaylistFilesVideo, boolean ] | ||
183 | videoStreamingPlaylist.Video = video | ||
184 | |||
185 | const newVideoFile = new VideoFileModel({ | ||
186 | resolution, | ||
187 | extname: extnameUtil(videoFilename), | ||
188 | size: 0, | ||
189 | fps: -1, | ||
190 | videoStreamingPlaylistId: videoStreamingPlaylist.id | ||
176 | }) | 191 | }) |
192 | |||
193 | const videoFilePath = getVideoFilePath(videoStreamingPlaylist, newVideoFile) | ||
194 | const stats = await stat(videoFilePath) | ||
195 | |||
196 | newVideoFile.size = stats.size | ||
197 | newVideoFile.fps = await getVideoFileFPS(videoFilePath) | ||
198 | |||
199 | await createTorrentAndSetInfoHash(videoStreamingPlaylist, newVideoFile) | ||
200 | |||
201 | const updatedVideoFile = await newVideoFile.save() | ||
202 | |||
203 | videoStreamingPlaylist.VideoFiles = await videoStreamingPlaylist.$get('VideoFiles') as VideoFileModel[] | ||
204 | videoStreamingPlaylist.VideoFiles.push(updatedVideoFile) | ||
205 | |||
206 | video.setHLSPlaylist(videoStreamingPlaylist) | ||
207 | |||
208 | await updateMasterHLSPlaylist(video) | ||
209 | await updateSha256Segments(video) | ||
210 | |||
211 | return video | ||
177 | } | 212 | } |
178 | 213 | ||
179 | // --------------------------------------------------------------------------- | 214 | // --------------------------------------------------------------------------- |
180 | 215 | ||
181 | export { | 216 | export { |
182 | generateHlsPlaylist, | 217 | generateHlsPlaylist, |
183 | optimizeVideofile, | 218 | optimizeOriginalVideofile, |
184 | transcodeOriginalVideofile, | 219 | transcodeNewResolution, |
185 | mergeAudioVideofile | 220 | mergeAudioVideofile |
186 | } | 221 | } |
187 | 222 | ||
@@ -196,7 +231,7 @@ async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoF | |||
196 | videoFile.size = stats.size | 231 | videoFile.size = stats.size |
197 | videoFile.fps = fps | 232 | videoFile.fps = fps |
198 | 233 | ||
199 | await video.createTorrentAndSetInfoHash(videoFile) | 234 | await createTorrentAndSetInfoHash(video, videoFile) |
200 | 235 | ||
201 | const updatedVideoFile = await videoFile.save() | 236 | const updatedVideoFile = await videoFile.save() |
202 | 237 | ||