feat(transcode): scale 2-pass progress to 50% per pass for total 100%

This commit is contained in:
PickleRick committed 2026-09-30 23:58:28 +02:00
1 parent 2daa75df60
commit 70f065a3a2
2 files changed
+87 -7

No files matched your search

@@ -156,6 +156,17 @@ const shouldCopyVideo = (videoStream, copyHevcBelowKbps, streams, format) => {
&& bitrateKbps > 0 && bitrateKbps < copyHevcBelowKbps; && bitrateKbps > 0 && bitrateKbps < copyHevcBelowKbps;
}; };
const scalePassPercentage = (pass, percentage) => {
const p = Math.min(100, Math.max(0, Number(percentage) || 0));
if (pass === 1) {
return +(p * 0.5).toFixed(1);
}
if (pass === 2) {
return +(50 + p * 0.5).toFixed(1);
}
return p;
};
// ─── Plugin ─────────────────────────────────────────────────────────────────── // ─── Plugin ───────────────────────────────────────────────────────────────────
const plugin = async (args) => { const plugin = async (args) => {
@@ -297,9 +308,62 @@ const plugin = async (args) => {
} }
const { CLI } = require('../../../../FlowHelpers/1.0.0/cliUtils'); const { CLI } = require('../../../../FlowHelpers/1.0.0/cliUtils');
const { updateProgress } = args;
// Scale 2-pass progress so Pass 1 counts for 0-50% and Pass 2 counts for 50-100%
const pass1UpdateWorker = (progressData) => {
if (typeof updateWorker !== 'function') return;
if (progressData && typeof progressData.percentage === 'number') {
updateWorker({
...progressData,
percentage: scalePassPercentage(1, progressData.percentage),
});
} else {
updateWorker(progressData);
}
};
const pass1UpdateProgress = (progressData) => {
if (typeof updateProgress !== 'function') return;
if (progressData && typeof progressData.progress === 'number') {
updateProgress({
...progressData,
progress: scalePassPercentage(1, progressData.progress),
});
} else {
updateProgress(progressData);
}
};
const pass2UpdateWorker = (progressData) => {
if (typeof updateWorker !== 'function') return;
if (progressData && typeof progressData.percentage === 'number') {
updateWorker({
...progressData,
percentage: scalePassPercentage(2, progressData.percentage),
});
} else {
updateWorker(progressData);
}
};
const pass2UpdateProgress = (progressData) => {
if (typeof updateProgress !== 'function') return;
if (progressData && typeof progressData.progress === 'number') {
updateProgress({
...progressData,
progress: scalePassPercentage(2, progressData.progress),
});
} else {
updateProgress(progressData);
}
};
// ══ PASS 1 ════════════════════════════════════════════════════════════════ // ══ PASS 1 ════════════════════════════════════════════════════════════════
jobLog('[transcode2PassX265] Starting Pass 1...\n'); jobLog('[transcode2PassX265] Starting Pass 1...\n');
pass1UpdateWorker({ percentage: 0 });
pass1UpdateProgress({ progress: 0 });
const pass1Cli = new CLI({ const pass1Cli = new CLI({
cli: args.ffmpegPath, cli: args.ffmpegPath,
spawnArgs: [ spawnArgs: [
@@ -317,16 +381,18 @@ const plugin = async (args) => {
WorkDir: workDir, WorkDir: workDir,
jobLog, jobLog,
inputFileObj, inputFileObj,
updateWorker, updateWorker: pass1UpdateWorker,
logFullCliOutput: args.logFullCliOutput || false, logFullCliOutput: args.logFullCliOutput || false,
args, args: { ...args, updateWorker: pass1UpdateWorker, updateProgress: pass1UpdateProgress },
}); });
const { cliExitCode: pass1Exit } = await pass1Cli.runCli(); const { cliExitCode: pass1Exit } = await pass1Cli.runCli();
if (pass1Exit !== 0) { if (pass1Exit !== 0) {
throw new Error(`[transcode2PassX265] Pass 1 failed with exit code ${pass1Exit}`); throw new Error(`[transcode2PassX265] Pass 1 failed with exit code ${pass1Exit}`);
} }
jobLog('[transcode2PassX265] Pass 1 complete.\n'); pass1UpdateWorker({ percentage: 100 });
pass1UpdateProgress({ progress: 100 });
jobLog('[transcode2PassX265] Pass 1 complete (50% total progress).\n');
// ══ PASS 2 ════════════════════════════════════════════════════════════════ // ══ PASS 2 ════════════════════════════════════════════════════════════════
const audioArgs = buildAudioArgs(variables); const audioArgs = buildAudioArgs(variables);
@@ -335,6 +401,9 @@ const plugin = async (args) => {
} }
jobLog('[transcode2PassX265] Starting Pass 2...\n'); jobLog('[transcode2PassX265] Starting Pass 2...\n');
pass2UpdateWorker({ percentage: 0 });
pass2UpdateProgress({ progress: 0 });
const pass2Cli = new CLI({ const pass2Cli = new CLI({
cli: args.ffmpegPath, cli: args.ffmpegPath,
spawnArgs: [ spawnArgs: [
@@ -355,16 +424,18 @@ const plugin = async (args) => {
jobLog, jobLog,
outputFilePath: outFile, outputFilePath: outFile,
inputFileObj, inputFileObj,
updateWorker, updateWorker: pass2UpdateWorker,
logFullCliOutput: args.logFullCliOutput || false, logFullCliOutput: args.logFullCliOutput || false,
args, args: { ...args, updateWorker: pass2UpdateWorker, updateProgress: pass2UpdateProgress },
}); });
const { cliExitCode: pass2Exit } = await pass2Cli.runCli(); const { cliExitCode: pass2Exit } = await pass2Cli.runCli();
if (pass2Exit !== 0) { if (pass2Exit !== 0) {
throw new Error(`[transcode2PassX265] Pass 2 failed with exit code ${pass2Exit}`); throw new Error(`[transcode2PassX265] Pass 2 failed with exit code ${pass2Exit}`);
} }
jobLog('[transcode2PassX265] Pass 2 complete.\n'); pass2UpdateWorker({ percentage: 100 });
pass2UpdateProgress({ progress: 100 });
jobLog('[transcode2PassX265] Pass 2 complete (100% total progress).\n');
// ── Cleanup 2-pass log files and temporary input copy ──────────────────── // ── Cleanup 2-pass log files and temporary input copy ────────────────────
[statsFile, `${statsFile}.mbtree`, `${statsFile}.cutree`].forEach((f) => { [statsFile, `${statsFile}.mbtree`, `${statsFile}.cutree`].forEach((f) => {
@@ -389,3 +460,4 @@ const plugin = async (args) => {
module.exports.details = details; module.exports.details = details;
module.exports.plugin = plugin; module.exports.plugin = plugin;
module.exports.scalePassPercentage = scalePassPercentage;
+9 -1
View File
@@ -236,8 +236,16 @@ const AUDIO_INPUTS = {
ok(transcodeCode.includes("normalizedKey === 'bps' || normalizedKey === 'bps-eng'"), 'HEVC copy check falls back to Matroska BPS metadata'); ok(transcodeCode.includes("normalizedKey === 'bps' || normalizedKey === 'bps-eng'"), 'HEVC copy check falls back to Matroska BPS metadata');
ok(transcodeCode.includes('containerBitrate - otherStreamBitrate'), 'HEVC copy check estimates video bitrate from container and companion streams'); ok(transcodeCode.includes('containerBitrate - otherStreamBitrate'), 'HEVC copy check estimates video bitrate from container and companion streams');
ok(!transcodeCode.includes("'-map_metadata -1'"), 'pass 1 separates -map_metadata from its value'); ok(!transcodeCode.includes("'-map_metadata -1'"), 'pass 1 separates -map_metadata from its value');
ok(!transcodeCode.includes("'-map_chapters -1'"), 'pass 1 separates -map_chapters from its value');
ok(transcodeCode.includes('const statsForX265 = path.basename(statsFile)'), 'x265 stats file is relative to workDir on Windows'); ok(transcodeCode.includes('const statsForX265 = path.basename(statsFile)'), 'x265 stats file is relative to workDir on Windows');
const { scalePassPercentage } = require(path.join(ROOT, 'LocalFlowPlugins/video/transcode2PassX265/1.0.0/index.js'));
eq(scalePassPercentage(1, 0), 0, 'pass 1 at 0% maps to 0%');
eq(scalePassPercentage(1, 50), 25, 'pass 1 at 50% maps to 25%');
eq(scalePassPercentage(1, 100), 50, 'pass 1 at 100% maps to 50%');
eq(scalePassPercentage(2, 0), 50, 'pass 2 at 0% maps to 50%');
eq(scalePassPercentage(2, 50), 75, 'pass 2 at 50% maps to 75%');
eq(scalePassPercentage(2, 100), 100, 'pass 2 at 100% maps to 100%');
ok(transcodeCode.includes('scalePassPercentage(1,'), 'pass 1 wraps updateWorker with 50% scale');
ok(transcodeCode.includes('scalePassPercentage(2,'), 'pass 2 wraps updateWorker with 50% scale');
// ── copyToWorkDir & replaceOriginalFile v1.0.0 checks ───────────────────── // ── copyToWorkDir & replaceOriginalFile v1.0.0 checks ─────────────────────
const copyPluginCode = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/copyToWorkDir/1.0.0/index.js'), 'utf8'); const copyPluginCode = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/copyToWorkDir/1.0.0/index.js'), 'utf8');