diff options
Diffstat (limited to 'server/helpers/ffmpeg-utils.ts')
-rw-r--r-- | server/helpers/ffmpeg-utils.ts | 49 |
1 files changed, 43 insertions, 6 deletions
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 76b744de8..2fdf34cb7 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,18 @@ 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 | |||
143 | command | ||
144 | .format('mp4') | ||
145 | .addOption('-c:v copy') | ||
146 | .addOption('-c:a copy') | ||
147 | .outputOption('-map_metadata -1') // strip all metadata | ||
148 | .outputOption('-movflags faststart') | ||
149 | } else if (options.hlsPlaylist) { | ||
138 | command = await buildHLSCommand(command, options) | 150 | command = await buildHLSCommand(command, options) |
139 | } else { | 151 | } else { |
140 | command = await buildx264Command(command, options) | 152 | command = await buildx264Command(command, options) |
@@ -162,6 +174,30 @@ function transcode (options: TranscodeOptions) { | |||
162 | }) | 174 | }) |
163 | } | 175 | } |
164 | 176 | ||
177 | async function canDoQuickTranscode (path: string): Promise<boolean> { | ||
178 | // NOTE: This could be optimized by running ffprobe only once (but it runs fast anyway) | ||
179 | const videoStream = await getVideoStreamFromFile(path) | ||
180 | const parsedAudio = await audio.get(path) | ||
181 | const fps = await getVideoFileFPS(path) | ||
182 | const bitRate = await getVideoFileBitrate(path) | ||
183 | const resolution = await getVideoFileResolution(path) | ||
184 | |||
185 | // check video params | ||
186 | if (videoStream[ 'codec_name' ] !== 'h264') return false | ||
187 | if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false | ||
188 | if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false | ||
189 | |||
190 | // check audio params (if audio stream exists) | ||
191 | if (parsedAudio.audioStream) { | ||
192 | if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false | ||
193 | |||
194 | const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ]) | ||
195 | if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false | ||
196 | } | ||
197 | |||
198 | return true | ||
199 | } | ||
200 | |||
165 | // --------------------------------------------------------------------------- | 201 | // --------------------------------------------------------------------------- |
166 | 202 | ||
167 | export { | 203 | export { |
@@ -173,7 +209,8 @@ export { | |||
173 | getVideoFileFPS, | 209 | getVideoFileFPS, |
174 | computeResolutionsToTranscode, | 210 | computeResolutionsToTranscode, |
175 | audio, | 211 | audio, |
176 | getVideoFileBitrate | 212 | getVideoFileBitrate, |
213 | canDoQuickTranscode | ||
177 | } | 214 | } |
178 | 215 | ||
179 | // --------------------------------------------------------------------------- | 216 | // --------------------------------------------------------------------------- |
@@ -243,7 +280,7 @@ async function onTranscodingSuccess (options: TranscodeOptions) { | |||
243 | await writeFile(options.outputPath, newContent) | 280 | await writeFile(options.outputPath, newContent) |
244 | } | 281 | } |
245 | 282 | ||
246 | function getVideoFileStream (path: string) { | 283 | function getVideoStreamFromFile (path: string) { |
247 | return new Promise<any>((res, rej) => { | 284 | return new Promise<any>((res, rej) => { |
248 | ffmpeg.ffprobe(path, (err, metadata) => { | 285 | ffmpeg.ffprobe(path, (err, metadata) => { |
249 | if (err) return rej(err) | 286 | if (err) return rej(err) |