Update plugins: VMAF mode, documentation fixes, version sync

- Added VMAF quality-targeted mode to av1_svt_converter (v2.25)
- Fixed documentation version mismatch (misc_fixes v2.8, stream_organizer v4.10, audio_standardizer v1.15)
- Updated rate control documentation with VMAF mode details
- Added vmaf_target and vmaf_samples input options
- Added ab-av1 binary detection with ABAV1_PATH env var support
This commit is contained in:
Tdarr Plugin Developer
2025-12-15 19:55:19 -08:00
parent 44fe7b50b0
commit 04d7ff59e9
272 changed files with 1140 additions and 35 deletions

View File

@@ -5,13 +5,13 @@ const details = () => ({
Type: 'Video',
Operation: 'Transcode',
Description: `
AV1 conversion plugin with advanced quality control and performance optimizations for SVT-AV1 v3.0+ (2025).
Features resolution-aware CRF, improved threading, and flexible bitrate control (custom maxrate or source-relative strategies).
**Balanced high-quality defaults**: Preset 6, CRF 26, tune 0 (VQ), 10-bit, SCD 1, AQ 2, lookahead -1, TF on, keyint -2, fast-decode 0.
Use presets 35 and/or lower CRF for higher quality when speed is less important.
AV1 conversion plugin with advanced quality control for SVT-AV1 v3.0+ (2025).
**Rate Control Modes**: VBR (predictable file sizes with target average bitrate), CRF (quality-based, unpredictable sizes), or VMAF (quality-targeted with ab-av1).
Features resolution-aware CRF, source-relative bitrate strategies, and performance optimizations.
**Balanced defaults**: Preset 6, CRF 26, tune 0 (VQ), 10-bit, SCD 1, AQ 2, lookahead -1, TF on, keyint -2, fast-decode 0.
`,
Version: '2.22',
Tags: 'video,av1,svt,quality,performance,speed-optimized,capped-crf',
Version: '2.25',
Tags: 'video,av1,svt,quality,performance,speed-optimized,vbr,crf',
Inputs: [
{
name: 'crf',
@@ -59,7 +59,52 @@ const details = () => ({
'25%_source'
],
},
tooltip: 'Target bitrate strategy. \'static\' uses custom_maxrate. Other options set maxrate relative to detected source bitrate.',
tooltip: 'Target bitrate strategy. \'static\' uses custom_maxrate. Other options set target/maxrate relative to detected source bitrate.',
},
{
name: 'rate_control_mode',
type: 'string',
defaultValue: 'crf*',
inputUI: {
type: 'dropdown',
options: [
'crf*',
'vbr',
'vmaf'
],
},
tooltip: 'Rate control mode. \'crf\' = Quality-based (CRF + maxrate cap), \'vbr\' = Bitrate-based (target average + maxrate peaks), \'vmaf\' = Quality-targeted (ab-av1 auto CRF selection, requires ab-av1 binary).',
},
{
name: 'vmaf_target',
type: 'string',
defaultValue: '95*',
inputUI: {
type: 'dropdown',
options: [
'85',
'90',
'95*',
'97',
'99'
],
},
tooltip: 'Target VMAF quality score (vmaf mode only). Higher = better quality but larger files. 95 = visually transparent (recommended), 90 = good quality, 85 = acceptable quality.',
},
{
name: 'vmaf_samples',
type: 'string',
defaultValue: '4*',
inputUI: {
type: 'dropdown',
options: [
'2',
'4*',
'6',
'8'
],
},
tooltip: 'Number of sample segments for ab-av1 quality analysis (vmaf mode only). More samples = more accurate CRF selection but slower analysis. 4 samples is a good balance.',
},
{
name: 'max_resolution',
@@ -386,8 +431,47 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
resolution_crf_adjust: stripStar(inputs.resolution_crf_adjust),
custom_maxrate: stripStar(inputs.custom_maxrate),
target_bitrate_strategy: stripStar(inputs.target_bitrate_strategy),
rate_control_mode: stripStar(inputs.rate_control_mode),
skip_hevc: stripStar(inputs.skip_hevc),
force_transcode: stripStar(inputs.force_transcode),
vmaf_target: stripStar(inputs.vmaf_target),
vmaf_samples: stripStar(inputs.vmaf_samples),
};
// Detect ab-av1 binary path with multi-level fallback
const getAbAv1Path = () => {
// Try environment variable first
const envPath = (process.env.ABAV1_PATH || '').trim();
if (envPath) {
try {
if (require('fs').existsSync(envPath)) {
require('fs').accessSync(envPath, require('fs').constants.X_OK);
return envPath;
}
} catch (e) {
// Continue to next detection method
}
}
// Try common installation paths
const commonPaths = [
'/usr/local/bin/ab-av1',
'/usr/bin/ab-av1',
];
for (const path of commonPaths) {
try {
if (require('fs').existsSync(path)) {
require('fs').accessSync(path, require('fs').constants.X_OK);
return path;
}
} catch (e) {
// Continue to next path
}
}
// Not found in any known location
return null;
};
// Detect actual input container format via ffprobe
@@ -583,14 +667,43 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
}
}
// Calculate target maxrate using precedence logic
let calculatedMaxrate = null;
let maxrateSource = '';
// Estimate expected average bitrate for a given CRF and resolution
// Based on SVT-AV1 CRF 30, preset ~6, average movie content (VMAF ~95)
// Lower CRF = higher bitrate (roughly 10-15% increase per CRF step down)
const estimateCrfBitrate = (crf, height) => {
// Baseline bitrates for CRF 30
let baselineCrf30 = 3000; // Default to 1080p
// Priority 1: target_bitrate_strategy (if not static)
if (height >= 2160) {
baselineCrf30 = 12000; // 4K average
} else if (height >= 1440) {
baselineCrf30 = 6000; // 1440p estimate (between 1080p and 4K)
} else if (height >= 1080) {
baselineCrf30 = 3000; // 1080p average
} else if (height >= 720) {
baselineCrf30 = 2000; // 720p average
} else {
baselineCrf30 = 1200; // 480p average
}
// Adjust for CRF difference from baseline (CRF 30)
// Each CRF step down increases bitrate by ~12%
const crfDiff = 30 - parseInt(crf);
const bitrateFactor = Math.pow(1.12, crfDiff);
return Math.round(baselineCrf30 * bitrateFactor);
};
// Calculate target bitrate and maxrate based on rate control mode
let calculatedTargetBitrate = null; // For VBR mode
let calculatedMaxrate = null; // For both modes
let bitrateSource = '';
// Step 1: Calculate base bitrate from strategy
if (sanitized.target_bitrate_strategy !== 'static') {
if (sourceBitrateKbps) {
let multiplier = 1.0;
switch (sanitized.target_bitrate_strategy) {
case 'match_source':
multiplier = 1.0;
@@ -608,9 +721,35 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
multiplier = 0.25;
break;
}
calculatedMaxrate = Math.round(sourceBitrateKbps * multiplier);
maxrateSource = `target_bitrate_strategy '${sanitized.target_bitrate_strategy}': Source ${sourceBitrateKbps}k → Maxrate ${calculatedMaxrate}k`;
response.infoLog += `Using ${maxrateSource}.\n`;
const baseBitrate = Math.round(sourceBitrateKbps * multiplier);
// Step 2: Apply mode-specific logic
if (sanitized.rate_control_mode === 'vbr') {
// VBR Mode: Target average = base, Maxrate = base * 2.0 (headroom for peaks)
calculatedTargetBitrate = baseBitrate;
calculatedMaxrate = Math.round(baseBitrate * 2.0);
bitrateSource = `VBR mode with target_bitrate_strategy '${sanitized.target_bitrate_strategy}': Source ${sourceBitrateKbps}k * ${multiplier} = Target ${calculatedTargetBitrate}k, Maxrate ${calculatedMaxrate}k (2.0x headroom)`;
response.infoLog += `Using ${bitrateSource}.\n`;
} else {
// CRF Mode: Ensure maxrate is higher than what CRF would naturally produce
// Estimate what the CRF will average based on resolution
const estimatedCrfAvg = estimateCrfBitrate(finalCrf, outputHeight || 1080);
// Set maxrate to the higher of: user's calculated value OR 1.8x estimated CRF average
// The 1.8x ensures headroom for peaks above the CRF average
const minSafeMaxrate = Math.round(estimatedCrfAvg * 1.8);
if (baseBitrate < minSafeMaxrate) {
calculatedMaxrate = minSafeMaxrate;
bitrateSource = `CRF mode: Calculated ${baseBitrate}k from strategy, but CRF ${finalCrf} @ ${outputHeight || 1080}p averages ~${estimatedCrfAvg}k. Using Maxrate ${calculatedMaxrate}k (1.8x avg) for headroom`;
response.infoLog += `${bitrateSource}.\n`;
} else {
calculatedMaxrate = baseBitrate;
bitrateSource = `CRF mode with target_bitrate_strategy '${sanitized.target_bitrate_strategy}': Source ${sourceBitrateKbps}k * ${multiplier} = Maxrate ${calculatedMaxrate}k (above CRF estimate)`;
response.infoLog += `Using ${bitrateSource}.\n`;
}
}
} else {
response.infoLog += `Warning: target_bitrate_strategy '${sanitized.target_bitrate_strategy}' selected but source bitrate unavailable. Falling back to static mode.\n`;
}
@@ -620,9 +759,27 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
if (!calculatedMaxrate && sanitized.custom_maxrate && sanitized.custom_maxrate !== '' && sanitized.custom_maxrate !== '0') {
const customValue = parseInt(sanitized.custom_maxrate);
if (!isNaN(customValue) && customValue > 0) {
calculatedMaxrate = customValue;
maxrateSource = `custom_maxrate: ${calculatedMaxrate}k`;
response.infoLog += `Using ${maxrateSource}.\n`;
if (sanitized.rate_control_mode === 'vbr') {
// VBR mode: Custom value is the target, maxrate = target * 2.0
calculatedTargetBitrate = customValue;
calculatedMaxrate = Math.round(customValue * 2.0);
bitrateSource = `VBR mode with custom_maxrate: Target ${calculatedTargetBitrate}k, Maxrate ${calculatedMaxrate}k (2.0x headroom)`;
response.infoLog += `Using ${bitrateSource}.\n`;
} else {
// CRF mode: Ensure custom maxrate is reasonable for the CRF
const estimatedCrfAvg = estimateCrfBitrate(finalCrf, outputHeight || 1080);
const minSafeMaxrate = Math.round(estimatedCrfAvg * 1.8);
if (customValue < minSafeMaxrate) {
calculatedMaxrate = minSafeMaxrate;
bitrateSource = `CRF mode: Custom ${customValue}k is below safe minimum for CRF ${finalCrf} @ ${outputHeight || 1080}p (est. ~${estimatedCrfAvg}k avg). Using ${calculatedMaxrate}k (1.8x) for headroom`;
response.infoLog += `${bitrateSource}.\n`;
} else {
calculatedMaxrate = customValue;
bitrateSource = `CRF mode with custom_maxrate: Maxrate ${calculatedMaxrate}k`;
response.infoLog += `Using ${bitrateSource}.\n`;
}
}
} else {
response.infoLog += `Warning: Invalid custom_maxrate value '${sanitized.custom_maxrate}'. Using uncapped CRF.\n`;
}
@@ -639,20 +796,62 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
};
const minBitrate = getMinBitrate(outputHeight || 1080);
if (calculatedMaxrate && calculatedMaxrate < minBitrate) {
// Adjust target and maxrate if below minimum
if (calculatedTargetBitrate && calculatedTargetBitrate < minBitrate) {
response.infoLog += `Warning: Calculated target bitrate ${calculatedTargetBitrate}k is below minimum for ${outputHeight || 1080}p. Raising to ${minBitrate}k.\n`;
calculatedTargetBitrate = minBitrate;
calculatedMaxrate = Math.round(minBitrate * 2.0); // Adjust maxrate proportionally
} else if (calculatedMaxrate && calculatedMaxrate < minBitrate) {
response.infoLog += `Warning: Calculated maxrate ${calculatedMaxrate}k is below minimum for ${outputHeight || 1080}p. Raising to ${minBitrate}k.\n`;
calculatedMaxrate = minBitrate;
}
if (calculatedMaxrate) {
// Step 3: Build quality/bitrate arguments based on mode
if (sanitized.rate_control_mode === 'vbr' && calculatedTargetBitrate) {
// VBR Mode: Use target bitrate + maxrate
const bufsize = calculatedMaxrate; // Buffer size = maxrate for VBR
qualityArgs += ` -b:v ${calculatedTargetBitrate}k -maxrate ${calculatedMaxrate}k -bufsize ${bufsize}k`;
bitrateControlInfo += ` with VBR target ${calculatedTargetBitrate}k, maxrate ${calculatedMaxrate}k (bufsize: ${bufsize}k)`;
response.infoLog += `VBR encoding: Target average ${calculatedTargetBitrate}k, peak ${calculatedMaxrate}k, buffer ${bufsize}k.\n`;
} else if (sanitized.rate_control_mode === 'vmaf') {
// VMAF Mode: Use ab-av1 for automatic CRF calculation
const abav1Path = getAbAv1Path();
if (!abav1Path) {
response.infoLog += 'VMAF mode selected but ab-av1 binary not found. Falling back to CRF mode.\n';
response.infoLog += 'To use VMAF mode, ensure ab-av1 is installed and accessible (check ABAV1_PATH env var or /usr/local/bin/ab-av1).\n';
// Fall through to standard CRF encoding - no maxrate cap in fallback
} else {
response.infoLog += `Using ab-av1 for quality-targeted encoding (target VMAF ${sanitized.vmaf_target}).\n`;
response.infoLog += `ab-av1 binary: ${abav1Path}\n`;
const vmafTarget = parseInt(sanitized.vmaf_target);
const sampleCount = parseInt(sanitized.vmaf_samples);
// Store ab-av1 metadata for worker to execute crf-search before encoding
response.useAbAv1 = true;
response.abav1Path = abav1Path;
response.vmafTarget = vmafTarget;
response.vmafSampleCount = sampleCount;
response.sourceFile = file.file;
bitrateControlInfo = `VMAF-targeted encoding (target VMAF: ${vmafTarget}, samples: ${sampleCount})`;
response.infoLog += `ab-av1 will automatically determine optimal CRF for VMAF ${vmafTarget}.\n`;
response.infoLog += `Using ${sampleCount} sample segments for quality analysis.\n`;
}
} else if (calculatedMaxrate) {
// CRF Mode with maxrate cap
const bufsize = Math.round(calculatedMaxrate * 2.0); // Buffer size = 2.0x maxrate for stability
qualityArgs += ` -maxrate ${calculatedMaxrate}k -bufsize ${bufsize}k`;
bitrateControlInfo += ` with capped bitrate at ${calculatedMaxrate}k (bufsize: ${bufsize}k)`;
response.infoLog += `Capped CRF enabled: Max bitrate ${calculatedMaxrate}k, buffer size ${bufsize}k for optimal bandwidth management.\n`;
} else {
// Uncapped CRF
response.infoLog += `Using uncapped CRF for maximum quality efficiency.\n`;
}
// Add tile options for 4K content (improves parallel encoding/decoding)
let tileArgs = '';
if (outputHeight && outputHeight >= 2160) {