feat: implement hardware and software upscaling
This commit is contained in:
+5
-1
@@ -79,7 +79,11 @@ function getDefaultSettings() {
|
||||
audioMixPreset: 'auto', // auto | itu | night | cinematic | passthrough
|
||||
// Probe cache settings
|
||||
probeCacheTTL: 300, // 5 minutes for URL probe cache
|
||||
seriesProbeCacheDays: 7 // 7 days for series episode probe cache
|
||||
seriesProbeCacheDays: 7, // 7 days for series episode probe cache
|
||||
// Upscaling settings
|
||||
upscaleEnabled: false,
|
||||
upscaleMethod: 'hardware', // hardware | software
|
||||
upscaleTarget: '1080p' // 1080p | 4k | 720p
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,10 @@ router.post('/session', async (req, res) => {
|
||||
maxResolution: settings.maxResolution || '1080p',
|
||||
quality: settings.quality || 'medium',
|
||||
audioMixPreset: settings.audioMixPreset || 'auto', // Audio downmix preset
|
||||
// Upscaling options
|
||||
upscaleEnabled: settings.upscaleEnabled || false,
|
||||
upscaleMethod: settings.upscaleMethod || 'hardware',
|
||||
upscaleTarget: settings.upscaleTarget || '1080p',
|
||||
videoMode: videoMode, // 'copy' or 'encode'
|
||||
videoCodec: videoCodec, // 'h264', 'hevc', etc.
|
||||
audioCodec: audioCodec, // 'aac', 'ac3', etc.
|
||||
|
||||
@@ -72,6 +72,10 @@ class TranscodeSession extends EventEmitter {
|
||||
hwEncoder: options.hwEncoder || 'software',
|
||||
maxResolution: options.maxResolution || '1080p',
|
||||
quality: options.quality || 'medium',
|
||||
// Upscaling options
|
||||
upscaleEnabled: options.upscaleEnabled || false,
|
||||
upscaleMethod: options.upscaleMethod || 'hardware', // 'hardware' or 'software'
|
||||
upscaleTarget: options.upscaleTarget || '1080p',
|
||||
...options
|
||||
};
|
||||
}
|
||||
@@ -355,24 +359,65 @@ class TranscodeSession extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get target height based on maxResolution setting
|
||||
* Get target height based on maxResolution or upscaleTarget setting
|
||||
* @param {boolean} forUpscale - If true, returns upscale target instead of max resolution
|
||||
*/
|
||||
getTargetHeight() {
|
||||
getTargetHeight(forUpscale = false) {
|
||||
const resolutionMap = {
|
||||
'4k': 2160,
|
||||
'1080p': 1080,
|
||||
'720p': 720,
|
||||
'480p': 480
|
||||
};
|
||||
if (forUpscale && this.options.upscaleEnabled) {
|
||||
return resolutionMap[this.options.upscaleTarget] || 1080;
|
||||
}
|
||||
return resolutionMap[this.options.maxResolution] || 1080;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build scale filter string based on encoder and upscaling settings
|
||||
* @param {string} encoder - The encoder being used
|
||||
* @param {number} height - Target height
|
||||
*/
|
||||
buildScaleFilter(encoder, height) {
|
||||
const useUpscale = this.options.upscaleEnabled;
|
||||
const upscaleMethod = this.options.upscaleMethod || 'hardware';
|
||||
|
||||
// Log upscaling status
|
||||
if (useUpscale) {
|
||||
console.log(`[TranscodeSession ${this.id}] Upscaling: ${upscaleMethod} method to ${height}p`);
|
||||
}
|
||||
|
||||
// Hardware scaling filters (for both upscale and downscale)
|
||||
if (upscaleMethod === 'hardware' || !useUpscale) {
|
||||
switch (encoder) {
|
||||
case 'nvenc':
|
||||
// NVIDIA CUDA scaling with Lanczos for upscaling
|
||||
return `scale_cuda=-2:${height}:interp_algo=lanczos`;
|
||||
case 'vaapi':
|
||||
return `scale_vaapi=w=-2:h=${height}:format=nv12`;
|
||||
case 'qsv':
|
||||
return `scale_qsv=w=-2:h=${height}`;
|
||||
case 'amf':
|
||||
// AMF uses CPU decode, so use software scale
|
||||
return useUpscale ? `scale=-2:${height}:flags=lanczos` : `scale=-2:${height}`;
|
||||
case 'software':
|
||||
default:
|
||||
return useUpscale ? `scale=-2:${height}:flags=lanczos` : `scale=-2:${height}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Software Lanczos scaling (high quality, slower)
|
||||
return `scale=-2:${height}:flags=lanczos`;
|
||||
}
|
||||
|
||||
/**
|
||||
* NVIDIA NVENC encoder arguments
|
||||
*/
|
||||
addNvencEncoderArgs(args, height, qp) {
|
||||
// Video filter for scaling on GPU
|
||||
args.push('-vf', `scale_cuda=-2:${height}:interp_algo=lanczos`);
|
||||
args.push('-vf', this.buildScaleFilter('nvenc', height));
|
||||
|
||||
// NVENC encoder with quality settings
|
||||
// Using portable options that work across FFmpeg builds
|
||||
@@ -390,7 +435,7 @@ class TranscodeSession extends EventEmitter {
|
||||
*/
|
||||
addAmfEncoderArgs(args, height, qp) {
|
||||
// CPU decoding + software scale + AMF encode
|
||||
args.push('-vf', `scale=-2:${height}`);
|
||||
args.push('-vf', this.buildScaleFilter('amf', height));
|
||||
|
||||
args.push(
|
||||
'-c:v', 'h264_amf',
|
||||
@@ -410,7 +455,7 @@ class TranscodeSession extends EventEmitter {
|
||||
// 1. scale_vaapi to resize on GPU
|
||||
// 2. Ensure output format is nv12 for maximum encoder compatibility
|
||||
// The format is handled automatically when using -hwaccel_output_format vaapi
|
||||
args.push('-vf', `scale_vaapi=w=-2:h=${height}:format=nv12`);
|
||||
args.push('-vf', this.buildScaleFilter('vaapi', height));
|
||||
|
||||
// VAAPI encoder with quality setting
|
||||
// Note: -global_quality is the portable way to set quality for VAAPI
|
||||
@@ -427,7 +472,7 @@ class TranscodeSession extends EventEmitter {
|
||||
*/
|
||||
addQsvEncoderArgs(args, height, qp) {
|
||||
// Scale on QSV
|
||||
args.push('-vf', `scale_qsv=w=-2:h=${height}`);
|
||||
args.push('-vf', this.buildScaleFilter('qsv', height));
|
||||
|
||||
args.push(
|
||||
'-c:v', 'h264_qsv',
|
||||
@@ -442,8 +487,8 @@ class TranscodeSession extends EventEmitter {
|
||||
* Software encoder arguments (fallback)
|
||||
*/
|
||||
addSoftwareEncoderArgs(args, height, crf) {
|
||||
// Software scaling
|
||||
args.push('-vf', `scale=-2:${height}`);
|
||||
// Software scaling (use Lanczos for upscaling if enabled)
|
||||
args.push('-vf', this.buildScaleFilter('software', height));
|
||||
|
||||
args.push(
|
||||
'-c:v', 'libx264',
|
||||
|
||||
Reference in New Issue
Block a user