diff options
Diffstat (limited to 'server/helpers/ffmpeg-utils.ts')
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 52 |
1 files changed, 46 insertions, 6 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 76b744de8..13bb2e894 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as ffmpeg from 'fluent-ffmpeg' | 1 | import * as ffmpeg from 'fluent-ffmpeg' |
2 | import { dirname, join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' | 3 | import { getTargetBitrate, getMaxBitrate, VideoResolution } from '../../shared/models/videos' |
4 | import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' | 4 | import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' |
5 | import { processImage } from './image-utils' | 5 | import { processImage } from './image-utils' |
6 | import { logger } from './logger' | 6 | import { logger } from './logger' |
@@ -31,7 +31,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
31 | } | 31 | } |
32 | 32 | ||
33 | async function getVideoFileSize (path: string) { | 33 | async function getVideoFileSize (path: string) { |
34 | const videoStream = await getVideoFileStream(path) | 34 | const videoStream = await getVideoStreamFromFile(path) |
35 | 35 | ||
36 | return { | 36 | return { |
37 | width: videoStream.width, | 37 | width: videoStream.width, |
@@ -49,7 +49,7 @@ async function getVideoFileResolution (path: string) { | |||
49 | } | 49 | } |
50 | 50 | ||
51 | async function getVideoFileFPS (path: string) { | 51 | async function getVideoFileFPS (path: string) { |
52 | const videoStream = await getVideoFileStream(path) | 52 | const videoStream = await getVideoStreamFromFile(path) |
53 | 53 | ||
54 | for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { | 54 | for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { |
55 | const valuesText: string = videoStream[key] | 55 | const valuesText: string = videoStream[key] |
@@ -122,6 +122,7 @@ type TranscodeOptions = { | |||
122 | outputPath: string | 122 | outputPath: string |
123 | resolution: VideoResolution | 123 | resolution: VideoResolution |
124 | isPortraitMode?: boolean | 124 | isPortraitMode?: boolean |
125 | doQuickTranscode?: Boolean | ||
125 | 126 | ||
126 | hlsPlaylist?: { | 127 | hlsPlaylist?: { |
127 | videoFilename: string | 128 | videoFilename: string |
@@ -134,7 +135,17 @@ function transcode (options: TranscodeOptions) { | |||
134 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) | 135 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) |
135 | .output(options.outputPath) | 136 | .output(options.outputPath) |
136 | 137 | ||
137 | if (options.hlsPlaylist) { | 138 | if (options.doQuickTranscode) { |
139 | if (options.hlsPlaylist) { | ||
140 | throw(Error("Quick transcode and HLS can't be used at the same time")) | ||
141 | } | ||
142 | command | ||
143 | .format('mp4') | ||
144 | .addOption('-c:v copy') | ||
145 | .addOption('-c:a copy') | ||
146 | .outputOption('-map_metadata -1') // strip all metadata | ||
147 | .outputOption('-movflags faststart') | ||
148 | } else if (options.hlsPlaylist) { | ||
138 | command = await buildHLSCommand(command, options) | 149 | command = await buildHLSCommand(command, options) |
139 | } else { | 150 | } else { |
140 | command = await buildx264Command(command, options) | 151 | command = await buildx264Command(command, options) |
@@ -162,6 +173,34 @@ function transcode (options: TranscodeOptions) { | |||
162 | }) | 173 | }) |
163 | } | 174 | } |
164 | 175 | ||
176 | async function canDoQuickTranscode (path: string) { | ||
177 | // NOTE: This could be optimized by running ffprobe only once (but it runs fast anyway) | ||
178 | const videoStream = await getVideoStreamFromFile(path) | ||
179 | const parsedAudio = await audio.get(path) | ||
180 | const fps = await getVideoFileFPS(path) | ||
181 | const bitRate = await getVideoFileBitrate(path) | ||
182 | const resolution = await getVideoFileResolution(path) | ||
183 | |||
184 | // check video params | ||
185 | if (videoStream[ 'codec_name' ] !== 'h264') | ||
186 | return false | ||
187 | if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) | ||
188 | return false | ||
189 | if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) | ||
190 | return false | ||
191 | |||
192 | // check audio params (if audio stream exists) | ||
193 | if (parsedAudio.audioStream) { | ||
194 | if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') | ||
195 | return false | ||
196 | const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ]) | ||
197 | if (maxAudioBitrate != -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) | ||
198 | return false | ||
199 | } | ||
200 | |||
201 | return true | ||
202 | } | ||
203 | |||
165 | // --------------------------------------------------------------------------- | 204 | // --------------------------------------------------------------------------- |
166 | 205 | ||
167 | export { | 206 | export { |
@@ -173,7 +212,8 @@ export { | |||
173 | getVideoFileFPS, | 212 | getVideoFileFPS, |
174 | computeResolutionsToTranscode, | 213 | computeResolutionsToTranscode, |
175 | audio, | 214 | audio, |
176 | getVideoFileBitrate | 215 | getVideoFileBitrate, |
216 | canDoQuickTranscode | ||
177 | } | 217 | } |
178 | 218 | ||
179 | // --------------------------------------------------------------------------- | 219 | // --------------------------------------------------------------------------- |
@@ -243,7 +283,7 @@ async function onTranscodingSuccess (options: TranscodeOptions) { | |||
243 | await writeFile(options.outputPath, newContent) | 283 | await writeFile(options.outputPath, newContent) |
244 | } | 284 | } |
245 | 285 | ||
246 | function getVideoFileStream (path: string) { | 286 | function getVideoStreamFromFile (path: string) { |
247 | return new Promise<any>((res, rej) => { | 287 | return new Promise<any>((res, rej) => { |
248 | ffmpeg.ffprobe(path, (err, metadata) => { | 288 | ffmpeg.ffprobe(path, (err, metadata) => { |
249 | if (err) return rej(err) | 289 | if (err) return rej(err) |