feat(all): ffmpeg quality options improvements (#2161)

* feat: change target scaling to resolution in ffmpeg config

* feat(microservices): scale vertical video correctly, only scale if video is larger than target
This commit is contained in:
Zack Pollard
2023-04-04 02:42:53 +01:00
committed by GitHub
parent 9076f3e69e
commit 808d6423be
12 changed files with 76 additions and 46 deletions

View File

@@ -16,7 +16,7 @@ import { AssetEntity, AssetType, TranscodePreset } from '@app/infra/entities';
import { Process, Processor } from '@nestjs/bull';
import { Inject, Logger } from '@nestjs/common';
import { Job } from 'bull';
import ffmpeg, { FfprobeData } from 'fluent-ffmpeg';
import ffmpeg, { FfprobeData, FfprobeStream } from 'fluent-ffmpeg';
import { join } from 'path';
@Processor(QueueName.VIDEO_CONVERSION)
@@ -74,22 +74,22 @@ export class VideoTranscodeProcessor {
async runVideoEncode(asset: AssetEntity, savedEncodedPath: string): Promise<void> {
const config = await this.systemConfigService.getConfig();
const videoStream = await this.getVideoStream(asset);
const transcode = await this.needsTranscoding(asset, config.ffmpeg);
const transcode = await this.needsTranscoding(videoStream, config.ffmpeg);
if (transcode) {
//TODO: If video or audio are already the correct format, don't re-encode, copy the stream
return this.runFFMPEGPipeLine(asset, savedEncodedPath);
return this.runFFMPEGPipeLine(asset, videoStream, savedEncodedPath);
}
}
async needsTranscoding(asset: AssetEntity, ffmpegConfig: SystemConfigFFmpegDto): Promise<boolean> {
async needsTranscoding(videoStream: FfprobeStream, ffmpegConfig: SystemConfigFFmpegDto): Promise<boolean> {
switch (ffmpegConfig.transcode) {
case TranscodePreset.ALL:
return true;
case TranscodePreset.REQUIRED:
{
const videoStream = await this.getVideoStream(asset);
if (videoStream.codec_name !== ffmpegConfig.targetVideoCodec) {
return true;
}
@@ -97,12 +97,13 @@ export class VideoTranscodeProcessor {
break;
case TranscodePreset.OPTIMAL: {
const videoStream = await this.getVideoStream(asset);
if (videoStream.codec_name !== ffmpegConfig.targetVideoCodec) {
return true;
}
const videoHeightThreshold = 1080;
const config = await this.systemConfigService.getConfig();
const videoHeightThreshold = Number.parseInt(config.ffmpeg.targetResolution);
return !videoStream.height || videoStream.height > videoHeightThreshold;
}
}
@@ -125,22 +126,45 @@ export class VideoTranscodeProcessor {
return longestVideoStream;
}
async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise<void> {
async runFFMPEGPipeLine(asset: AssetEntity, videoStream: FfprobeStream, savedEncodedPath: string): Promise<void> {
const config = await this.systemConfigService.getConfig();
const ffmpegOptions = [
`-crf ${config.ffmpeg.crf}`,
`-preset ${config.ffmpeg.preset}`,
`-vcodec ${config.ffmpeg.targetVideoCodec}`,
`-acodec ${config.ffmpeg.targetAudioCodec}`,
// Makes a second pass moving the moov atom to the beginning of
// the file for improved playback speed.
`-movflags faststart`,
];
if (!videoStream.height || !videoStream.width) {
this.logger.error('Height or width undefined for video stream');
return;
}
const streamHeight = videoStream.height;
const streamWidth = videoStream.width;
const targetResolution = Number.parseInt(config.ffmpeg.targetResolution);
let scaling = `-2:${targetResolution}`;
const shouldScale = Math.min(streamHeight, streamWidth) > targetResolution;
const videoIsRotated = Math.abs(Number.parseInt(`${videoStream.rotation ?? 0}`)) === 90;
if (streamHeight > streamWidth || videoIsRotated) {
scaling = `${targetResolution}:-2`;
}
if (shouldScale) {
ffmpegOptions.push(`-vf scale=${scaling}`);
}
return new Promise((resolve, reject) => {
ffmpeg(asset.originalPath)
.outputOptions([
`-crf ${config.ffmpeg.crf}`,
`-preset ${config.ffmpeg.preset}`,
`-vcodec ${config.ffmpeg.targetVideoCodec}`,
`-acodec ${config.ffmpeg.targetAudioCodec}`,
`-vf scale=${config.ffmpeg.targetScaling}`,
// Makes a second pass moving the moov atom to the beginning of
// the file for improved playback speed.
`-movflags faststart`,
])
.outputOptions(ffmpegOptions)
.output(savedEncodedPath)
.on('start', () => {
this.logger.log('Start Converting Video');