Implement ab-av1 crf-search execution for VMAF mode (v2.30)

- Added executeAbAv1CrfSearch() function with synchronous execution
- Parses CRF and VMAF score from ab-av1 output
- 5-minute timeout for sample encodes
- Graceful fallback to configured CRF on errors
- Updates FFmpeg command with found CRF value
- Proper logging of ab-av1 results and errors
This commit is contained in:
Tdarr Plugin Developer
2025-12-15 19:59:46 -08:00
parent 04d7ff59e9
commit 4be3310d7e
2 changed files with 115 additions and 15 deletions

View File

@@ -6,12 +6,12 @@ const details = () => ({
Operation: 'Transcode',
Description: `
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.
**Rate Control Modes**: VBR (predictable file sizes), CRF (quality-based), or VMAF (quality-targeted with ab-av1 crf-search).
Features resolution-aware CRF, source-relative bitrate strategies, ab-av1 auto-CRF, 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.25',
Tags: 'video,av1,svt,quality,performance,speed-optimized,vbr,crf',
Version: '2.30',
Tags: 'video,av1,svt,quality,performance,speed-optimized,vbr,crf,vmaf',
Inputs: [
{
name: 'crf',
@@ -474,6 +474,77 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
return null;
};
// Execute ab-av1 crf-search synchronously to find optimal CRF for target VMAF
// Returns { success: boolean, crf: number|null, vmaf: number|null, error: string|null }
const executeAbAv1CrfSearch = (abav1Path, inputFile, vmafTarget, sampleCount, preset) => {
const { execSync } = require('child_process');
try {
// Build ab-av1 command
// --min-vmaf is the target VMAF score to achieve
// --samples controls how many sample segments to test
// --encoder specifies the encoder (libsvtav1 for FFmpeg)
const args = [
'crf-search',
'-i', `"${inputFile}"`,
'--min-vmaf', vmafTarget.toString(),
'--samples', sampleCount.toString(),
'--encoder', 'libsvtav1',
'--preset', preset.toString(),
];
const command = `${abav1Path} ${args.join(' ')}`;
// Execute with timeout (5 minutes should be enough for sample encodes)
const output = execSync(command, {
encoding: 'utf8',
timeout: 300000, // 5 minute timeout
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
stdio: ['pipe', 'pipe', 'pipe']
});
// Parse ab-av1 output for CRF value
// Expected format: "crf 28, VMAF 95.2" or similar
// Also matches: "Best crf: 28" or "crf: 28 vmaf: 95.2"
const crfMatch = output.match(/(?:crf|CRF)[:\s]+(\d+)/i);
const vmafMatch = output.match(/(?:vmaf|VMAF)[:\s]+([\d.]+)/i);
if (crfMatch) {
return {
success: true,
crf: parseInt(crfMatch[1]),
vmaf: vmafMatch ? parseFloat(vmafMatch[1]) : null,
error: null,
output: output.substring(0, 500) // Truncate for logging
};
} else {
return {
success: false,
crf: null,
vmaf: null,
error: 'Could not parse CRF from ab-av1 output',
output: output.substring(0, 500)
};
}
} catch (error) {
// Handle execution errors
let errorMsg = error.message;
if (error.killed) {
errorMsg = 'ab-av1 timed out after 5 minutes';
} else if (error.status) {
errorMsg = `ab-av1 exited with code ${error.status}`;
}
return {
success: false,
crf: null,
vmaf: null,
error: errorMsg,
output: error.stderr ? error.stderr.substring(0, 500) : ''
};
}
};
// Detect actual input container format via ffprobe
const actualFormatName = file.ffProbeData?.format?.format_name || '';
const looksLikeAppleMp4Family = actualFormatName.includes('mov') || actualFormatName.includes('mp4');
@@ -821,7 +892,8 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
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
// Fall through to standard CRF encoding - use user's configured CRF
bitrateControlInfo = `CRF ${finalCrf} (VMAF fallback - ab-av1 not found)`;
} else {
response.infoLog += `Using ab-av1 for quality-targeted encoding (target VMAF ${sanitized.vmaf_target}).\n`;
response.infoLog += `ab-av1 binary: ${abav1Path}\n`;
@@ -829,16 +901,44 @@ const plugin = (file, librarySettings, inputs, otherArguments) => {
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 += `Running ab-av1 crf-search to find optimal CRF for VMAF ${vmafTarget}...\n`;
response.infoLog += `Using ${sampleCount} sample segments for quality analysis.\n`;
// Execute ab-av1 crf-search synchronously
const crfResult = executeAbAv1CrfSearch(
abav1Path,
file.file,
vmafTarget,
sampleCount,
finalPreset
);
if (crfResult.success && crfResult.crf !== null) {
// Success! Use the found CRF
const foundCrf = crfResult.crf;
response.infoLog += `✅ ab-av1 found optimal CRF: ${foundCrf}`;
if (crfResult.vmaf) {
response.infoLog += ` (predicted VMAF: ${crfResult.vmaf})`;
}
response.infoLog += '\n';
// Update qualityArgs with the ab-av1 determined CRF
// Replace the CRF in qualityArgs (which was set earlier with user's default)
qualityArgs = qualityArgs.replace(/-crf \d+/, `-crf ${foundCrf}`);
bitrateControlInfo = `VMAF-targeted CRF ${foundCrf} (target VMAF: ${vmafTarget}, achieved: ${crfResult.vmaf || 'unknown'})`;
// Store metadata for logging/debugging
response.abav1CrfResult = foundCrf;
response.abav1VmafResult = crfResult.vmaf;
} else {
// ab-av1 failed - fall back to user's configured CRF
response.infoLog += `⚠️ ab-av1 crf-search failed: ${crfResult.error}\n`;
if (crfResult.output) {
response.infoLog += `ab-av1 output: ${crfResult.output}\n`;
}
response.infoLog += `Falling back to configured CRF ${finalCrf}.\n`;
bitrateControlInfo = `CRF ${finalCrf} (VMAF fallback - ab-av1 failed)`;
}
}
} else if (calculatedMaxrate) {
// CRF Mode with maxrate cap

View File

@@ -1,7 +1,7 @@
# Tdarr Plugin Suite Documentation
> **Version**: 2025-12-15
> **Plugins**: misc_fixes v2.8 | stream_organizer v4.10 | audio_standardizer v1.15 | av1_converter v2.25
> **Plugins**: misc_fixes v2.8 | stream_organizer v4.10 | audio_standardizer v1.15 | av1_converter v2.30
---