diff options
Diffstat (limited to 'server/lib/video-transcoding.ts')
-rw-r--r-- | server/lib/video-transcoding.ts | 97 |
1 files changed, 76 insertions, 21 deletions
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index 0fe0ff12a..8d786e0ef 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -1,6 +1,6 @@ | |||
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 { join } from 'path' | 2 | import { join } from 'path' |
3 | import { getVideoFileFPS, transcode } from '../helpers/ffmpeg-utils' | 3 | import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' |
4 | import { ensureDir, move, remove, stat } from 'fs-extra' | 4 | import { ensureDir, move, remove, stat } from 'fs-extra' |
5 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../shared/models/videos' |
@@ -11,15 +11,24 @@ import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-pla | |||
11 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 11 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' |
12 | import { CONFIG } from '../initializers/config' | 12 | import { CONFIG } from '../initializers/config' |
13 | 13 | ||
14 | /** | ||
15 | * Optimize the original video file and replace it. The resolution is not changed. | ||
16 | */ | ||
14 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { | 17 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { |
15 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 18 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
19 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | ||
16 | const newExtname = '.mp4' | 20 | const newExtname = '.mp4' |
17 | 21 | ||
18 | const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile() | 22 | const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile() |
19 | const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) | 23 | const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) |
20 | const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname) | 24 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) |
21 | 25 | ||
22 | const transcodeOptions = { | 26 | const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath) |
27 | ? 'quick-transcode' | ||
28 | : 'video' | ||
29 | |||
30 | const transcodeOptions: TranscodeOptions = { | ||
31 | type: transcodeType as any, // FIXME: typing issue | ||
23 | inputPath: videoInputPath, | 32 | inputPath: videoInputPath, |
24 | outputPath: videoTranscodedPath, | 33 | outputPath: videoTranscodedPath, |
25 | resolution: inputVideoFile.resolution | 34 | resolution: inputVideoFile.resolution |
@@ -32,18 +41,11 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
32 | await remove(videoInputPath) | 41 | await remove(videoInputPath) |
33 | 42 | ||
34 | // Important to do this before getVideoFilename() to take in account the new file extension | 43 | // Important to do this before getVideoFilename() to take in account the new file extension |
35 | inputVideoFile.set('extname', newExtname) | 44 | inputVideoFile.extname = newExtname |
36 | 45 | ||
37 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) | 46 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) |
38 | await move(videoTranscodedPath, videoOutputPath) | ||
39 | const stats = await stat(videoOutputPath) | ||
40 | const fps = await getVideoFileFPS(videoOutputPath) | ||
41 | 47 | ||
42 | inputVideoFile.set('size', stats.size) | 48 | await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) |
43 | inputVideoFile.set('fps', fps) | ||
44 | |||
45 | await video.createTorrentAndSetInfoHash(inputVideoFile) | ||
46 | await inputVideoFile.save() | ||
47 | } catch (err) { | 49 | } catch (err) { |
48 | // Auto destruction... | 50 | // Auto destruction... |
49 | video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) | 51 | video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) |
@@ -52,8 +54,12 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
52 | } | 54 | } |
53 | } | 55 | } |
54 | 56 | ||
57 | /** | ||
58 | * Transcode the original video file to a lower resolution. | ||
59 | */ | ||
55 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { | 60 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { |
56 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 61 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
62 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | ||
57 | const extname = '.mp4' | 63 | const extname = '.mp4' |
58 | 64 | ||
59 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed | 65 | // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed |
@@ -66,27 +72,49 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR | |||
66 | videoId: video.id | 72 | videoId: video.id |
67 | }) | 73 | }) |
68 | const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) | 74 | const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) |
75 | const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile)) | ||
69 | 76 | ||
70 | const transcodeOptions = { | 77 | const transcodeOptions = { |
78 | type: 'video' as 'video', | ||
71 | inputPath: videoInputPath, | 79 | inputPath: videoInputPath, |
72 | outputPath: videoOutputPath, | 80 | outputPath: videoTranscodedPath, |
73 | resolution, | 81 | resolution, |
74 | isPortraitMode: isPortrait | 82 | isPortraitMode: isPortrait |
75 | } | 83 | } |
76 | 84 | ||
77 | await transcode(transcodeOptions) | 85 | await transcode(transcodeOptions) |
78 | 86 | ||
79 | const stats = await stat(videoOutputPath) | 87 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) |
80 | const fps = await getVideoFileFPS(videoOutputPath) | 88 | } |
89 | |||
90 | async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { | ||
91 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | ||
92 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | ||
93 | const newExtname = '.mp4' | ||
94 | |||
95 | const inputVideoFile = video.getOriginalFile() | ||
81 | 96 | ||
82 | newVideoFile.set('size', stats.size) | 97 | const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile())) |
83 | newVideoFile.set('fps', fps) | 98 | const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) |
84 | 99 | ||
85 | await video.createTorrentAndSetInfoHash(newVideoFile) | 100 | const transcodeOptions = { |
101 | type: 'merge-audio' as 'merge-audio', | ||
102 | inputPath: video.getPreview().getPath(), | ||
103 | outputPath: videoTranscodedPath, | ||
104 | audioPath: audioInputPath, | ||
105 | resolution | ||
106 | } | ||
107 | |||
108 | await transcode(transcodeOptions) | ||
86 | 109 | ||
87 | await newVideoFile.save() | 110 | await remove(audioInputPath) |
88 | 111 | ||
89 | video.VideoFiles.push(newVideoFile) | 112 | // Important to do this before getVideoFilename() to take in account the new file extension |
113 | inputVideoFile.extname = newExtname | ||
114 | |||
115 | const videoOutputPath = video.getVideoFilePath(inputVideoFile) | ||
116 | |||
117 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) | ||
90 | } | 118 | } |
91 | 119 | ||
92 | async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { | 120 | async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { |
@@ -97,6 +125,7 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti | |||
97 | const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) | 125 | const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) |
98 | 126 | ||
99 | const transcodeOptions = { | 127 | const transcodeOptions = { |
128 | type: 'hls' as 'hls', | ||
100 | inputPath: videoInputPath, | 129 | inputPath: videoInputPath, |
101 | outputPath, | 130 | outputPath, |
102 | resolution, | 131 | resolution, |
@@ -125,8 +154,34 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti | |||
125 | }) | 154 | }) |
126 | } | 155 | } |
127 | 156 | ||
157 | // --------------------------------------------------------------------------- | ||
158 | |||
128 | export { | 159 | export { |
129 | generateHlsPlaylist, | 160 | generateHlsPlaylist, |
130 | optimizeVideofile, | 161 | optimizeVideofile, |
131 | transcodeOriginalVideofile | 162 | transcodeOriginalVideofile, |
163 | mergeAudioVideofile | ||
164 | } | ||
165 | |||
166 | // --------------------------------------------------------------------------- | ||
167 | |||
168 | async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { | ||
169 | const stats = await stat(transcodingPath) | ||
170 | const fps = await getVideoFileFPS(transcodingPath) | ||
171 | |||
172 | await move(transcodingPath, outputPath) | ||
173 | |||
174 | videoFile.set('size', stats.size) | ||
175 | videoFile.set('fps', fps) | ||
176 | |||
177 | await video.createTorrentAndSetInfoHash(videoFile) | ||
178 | |||
179 | const updatedVideoFile = await videoFile.save() | ||
180 | |||
181 | // Add it if this is a new created file | ||
182 | if (video.VideoFiles.some(f => f.id === videoFile.id) === false) { | ||
183 | video.VideoFiles.push(updatedVideoFile) | ||
184 | } | ||
185 | |||
186 | return video | ||
132 | } | 187 | } |