diff options
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 96 |
1 files changed, 59 insertions, 37 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index b6b64de3f..c477e2e89 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -130,51 +130,20 @@ type TranscodeOptions = { | |||
130 | function transcode (options: TranscodeOptions) { | 130 | function transcode (options: TranscodeOptions) { |
131 | return new Promise<void>(async (res, rej) => { | 131 | return new Promise<void>(async (res, rej) => { |
132 | try { | 132 | try { |
133 | let fps = await getVideoFileFPS(options.inputPath) | ||
134 | // On small/medium resolutions, limit FPS | ||
135 | if ( | ||
136 | options.resolution !== undefined && | ||
137 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
138 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | ||
139 | ) { | ||
140 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
141 | } | ||
142 | |||
143 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) | 133 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) |
144 | .output(options.outputPath) | 134 | .output(options.outputPath) |
145 | command = await presetH264(command, options.resolution, fps) | 135 | |
136 | if (options.hlsPlaylist) { | ||
137 | command = await buildHLSCommand(command, options) | ||
138 | } else { | ||
139 | command = await buildx264Command(command, options) | ||
140 | } | ||
146 | 141 | ||
147 | if (CONFIG.TRANSCODING.THREADS > 0) { | 142 | if (CONFIG.TRANSCODING.THREADS > 0) { |
148 | // if we don't set any threads ffmpeg will chose automatically | 143 | // if we don't set any threads ffmpeg will chose automatically |
149 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) | 144 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) |
150 | } | 145 | } |
151 | 146 | ||
152 | if (options.resolution !== undefined) { | ||
153 | // '?x720' or '720x?' for example | ||
154 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` | ||
155 | command = command.size(size) | ||
156 | } | ||
157 | |||
158 | if (fps) { | ||
159 | // Hard FPS limits | ||
160 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX | ||
161 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN | ||
162 | |||
163 | command = command.withFPS(fps) | ||
164 | } | ||
165 | |||
166 | if (options.hlsPlaylist) { | ||
167 | const videoPath = getHLSVideoPath(options) | ||
168 | |||
169 | command = command.outputOption('-hls_time 4') | ||
170 | .outputOption('-hls_list_size 0') | ||
171 | .outputOption('-hls_playlist_type vod') | ||
172 | .outputOption('-hls_segment_filename ' + videoPath) | ||
173 | .outputOption('-hls_segment_type fmp4') | ||
174 | .outputOption('-f hls') | ||
175 | .outputOption('-hls_flags single_file') | ||
176 | } | ||
177 | |||
178 | command | 147 | command |
179 | .on('error', (err, stdout, stderr) => { | 148 | .on('error', (err, stdout, stderr) => { |
180 | logger.error('Error in transcoding job.', { stdout, stderr }) | 149 | logger.error('Error in transcoding job.', { stdout, stderr }) |
@@ -208,6 +177,52 @@ export { | |||
208 | 177 | ||
209 | // --------------------------------------------------------------------------- | 178 | // --------------------------------------------------------------------------- |
210 | 179 | ||
180 | async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { | ||
181 | let fps = await getVideoFileFPS(options.inputPath) | ||
182 | // On small/medium resolutions, limit FPS | ||
183 | if ( | ||
184 | options.resolution !== undefined && | ||
185 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
186 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | ||
187 | ) { | ||
188 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
189 | } | ||
190 | |||
191 | command = await presetH264(command, options.resolution, fps) | ||
192 | |||
193 | if (options.resolution !== undefined) { | ||
194 | // '?x720' or '720x?' for example | ||
195 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` | ||
196 | command = command.size(size) | ||
197 | } | ||
198 | |||
199 | if (fps) { | ||
200 | // Hard FPS limits | ||
201 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX | ||
202 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN | ||
203 | |||
204 | command = command.withFPS(fps) | ||
205 | } | ||
206 | |||
207 | return command | ||
208 | } | ||
209 | |||
210 | async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { | ||
211 | const videoPath = getHLSVideoPath(options) | ||
212 | |||
213 | command = await presetCopy(command) | ||
214 | |||
215 | command = command.outputOption('-hls_time 4') | ||
216 | .outputOption('-hls_list_size 0') | ||
217 | .outputOption('-hls_playlist_type vod') | ||
218 | .outputOption('-hls_segment_filename ' + videoPath) | ||
219 | .outputOption('-hls_segment_type fmp4') | ||
220 | .outputOption('-f hls') | ||
221 | .outputOption('-hls_flags single_file') | ||
222 | |||
223 | return command | ||
224 | } | ||
225 | |||
211 | function getHLSVideoPath (options: TranscodeOptions) { | 226 | function getHLSVideoPath (options: TranscodeOptions) { |
212 | return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` | 227 | return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` |
213 | } | 228 | } |
@@ -397,3 +412,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol | |||
397 | 412 | ||
398 | return localCommand | 413 | return localCommand |
399 | } | 414 | } |
415 | |||
416 | async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { | ||
417 | return command | ||
418 | .format('mp4') | ||
419 | .videoCodec('copy') | ||
420 | .audioCodec('copy') | ||
421 | } | ||