X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fhelpers%2Fffmpeg-utils.ts;h=bfc942fa3c82440cf9ed41b311b945498c57d055;hb=ff193d5e3fbe648f689be2e235dcf88daa1bba48;hp=f18b6bd9a12827918ad3626fe17480887481e1aa;hpb=a6218a0b8f685078e6f7d21e9110b6418c5594fe;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index f18b6bd9a..bfc942fa3 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts @@ -1,18 +1,35 @@ -import * as Promise from 'bluebird' import * as ffmpeg from 'fluent-ffmpeg' +import { join } from 'path' +import { VideoResolution } from '../../shared/models/videos' +import { CONFIG, VIDEO_TRANSCODING_FPS } from '../initializers' +import { unlinkPromise } from './core-utils' +import { processImage } from './image-utils' +import { logger } from './logger' -import { CONFIG } from '../initializers' -import { VideoResolution } from '../../shared/models/videos/video-resolution.enum' +async function getVideoFileResolution (path: string) { + const videoStream = await getVideoFileStream(path) -function getVideoFileHeight (path: string) { - return new Promise((res, rej) => { - ffmpeg.ffprobe(path, (err, metadata) => { - if (err) return rej(err) + return { + videoFileResolution: Math.min(videoStream.height, videoStream.width), + isPortraitMode: videoStream.height > videoStream.width + } +} - const videoStream = metadata.streams.find(s => s.codec_type === 'video') - return res(videoStream.height) - }) - }) +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) { @@ -25,33 +42,49 @@ 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((res, rej) => { - ffmpeg(fromPath) - .on('error', rej) - .on('end', () => res(imageName)) - .thumbnail(options) - }) + try { + await new Promise((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((res, rej) => { + return new Promise(async (res, rej) => { + const fps = await getVideoFileFPS(options.inputPath) + let command = ffmpeg(options.inputPath) .output(options.outputPath) .videoCodec('libx264') @@ -59,22 +92,47 @@ function transcode (options: TranscodeOptions) { .outputOption('-movflags faststart') // .outputOption('-crf 18') + // Our player has some FPS limits + if (fps > VIDEO_TRANSCODING_FPS.MAX) command = command.withFPS(VIDEO_TRANSCODING_FPS.MAX) + else if (fps < VIDEO_TRANSCODING_FPS.MIN) command = command.withFPS(VIDEO_TRANSCODING_FPS.MIN) + 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) } - command.on('error', rej) - .on('end', res) - .run() + command + .on('error', (err, stdout, stderr) => { + logger.error('Error in transcoding job.', { stdout, stderr }) + return rej(err) + }) + .on('end', res) + .run() }) } // --------------------------------------------------------------------------- export { - getVideoFileHeight, + getVideoFileResolution, getDurationFromVideoFile, generateImageFromVideoFile, - transcode + transcode, + getVideoFileFPS +} + +// --------------------------------------------------------------------------- + +function getVideoFileStream (path: string) { + return new Promise((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) + }) + }) }