import * as ffmpeg from 'fluent-ffmpeg'
-import { VideoResolution } from '../../shared/models/videos/video-resolution.enum'
-import { CONFIG } from '../initializers'
+import { join } from 'path'
+import { VideoResolution } from '../../shared/models/videos'
+import { CONFIG, MAX_VIDEO_TRANSCODING_FPS } from '../initializers'
+import { unlinkPromise } from './core-utils'
+import { processImage } from './image-utils'
+import { logger } from './logger'
-function getVideoFileHeight (path: string) {
- return new Promise<number>((res, rej) => {
- ffmpeg.ffprobe(path, (err, metadata) => {
- if (err) return rej(err)
+async function getVideoFileResolution (path: string) {
+ const videoStream = await getVideoFileStream(path)
- const videoStream = metadata.streams.find(s => s.codec_type === 'video')
- return res(videoStream.height)
- })
- })
+ return {
+ videoFileResolution: Math.min(videoStream.height, videoStream.width),
+ isPortraitMode: videoStream.height > videoStream.width
+ }
+}
+
+async function getVideoFileFPS (path: string) {
+ const videoStream = await getVideoFileStream(path)
+
+ for (const key of [ 'r_frame_rate' , 'avg_frame_rate' ]) {
+ const valuesText: string = videoStream[key]
+ if (!valuesText) continue
+
+ const [ frames, seconds ] = valuesText.split('/')
+ if (!frames || !seconds) continue
+
+ const result = parseInt(frames, 10) / parseInt(seconds, 10)
+ if (result > 0) return result
+ }
+
+ return 0
}
function getDurationFromVideoFile (path: string) {
})
}
-function generateImageFromVideoFile (fromPath: string, folder: string, imageName: string, size: string) {
+async function generateImageFromVideoFile (fromPath: string, folder: string, imageName: string, size: { width: number, height: number }) {
+ const pendingImageName = 'pending-' + imageName
+
const options = {
- filename: imageName,
+ filename: pendingImageName,
count: 1,
folder
}
- if (size !== undefined) {
- options['size'] = size
- }
+ const pendingImagePath = join(folder, pendingImageName)
- return new Promise<string>((res, rej) => {
- ffmpeg(fromPath)
- .on('error', rej)
- .on('end', () => res(imageName))
- .thumbnail(options)
- })
+ try {
+ await new Promise<string>((res, rej) => {
+ ffmpeg(fromPath)
+ .on('error', rej)
+ .on('end', () => res(imageName))
+ .thumbnail(options)
+ })
+
+ const destination = join(folder, imageName)
+ await processImage({ path: pendingImagePath }, destination, size)
+ } catch (err) {
+ logger.error('Cannot generate image from video %s.', fromPath, err)
+
+ try {
+ await unlinkPromise(pendingImagePath)
+ } catch (err) {
+ logger.debug('Cannot remove pending image path after generation error.', err)
+ }
+ }
}
type TranscodeOptions = {
inputPath: string
outputPath: string
resolution?: VideoResolution
+ isPortraitMode?: boolean
}
function transcode (options: TranscodeOptions) {
- return new Promise<void>((res, rej) => {
+ return new Promise<void>(async (res, rej) => {
+ const fps = await getVideoFileFPS(options.inputPath)
+
let command = ffmpeg(options.inputPath)
.output(options.outputPath)
.videoCodec('libx264')
.outputOption('-movflags faststart')
// .outputOption('-crf 18')
+ if (fps > MAX_VIDEO_TRANSCODING_FPS) command = command.withFPS(MAX_VIDEO_TRANSCODING_FPS)
+
if (options.resolution !== undefined) {
- const size = `?x${options.resolution}` // '?x720' for example
+ // '?x720' or '720x?' for example
+ const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}`
command = command.size(size)
}
// ---------------------------------------------------------------------------
export {
- getVideoFileHeight,
+ getVideoFileResolution,
getDurationFromVideoFile,
generateImageFromVideoFile,
- transcode
+ transcode,
+ getVideoFileFPS
+}
+
+// ---------------------------------------------------------------------------
+
+function getVideoFileStream (path: string) {
+ return new Promise<any>((res, rej) => {
+ ffmpeg.ffprobe(path, (err, metadata) => {
+ if (err) return rej(err)
+
+ const videoStream = metadata.streams.find(s => s.codec_type === 'video')
+ if (!videoStream) throw new Error('Cannot find video stream of ' + path)
+
+ return res(videoStream)
+ })
+ })
}