Fix copy progress, remove v2.0.0, fix subtitle default naming, enforce strict no-upcoding on audio
Build / Lint & Test (push) Successful in 3m15s

This commit is contained in:
PickleRick committed 2026-09-30 17:26:19 +02:00
1 parent 7ee5a5bc48
commit 86448988ed
12 files changed
+470 -348

No files matched your search

+61 -20
View File
@@ -1,4 +1,4 @@
'use strict';
'use strict';
const path = require('path');
const os = require('os');
const fs = require('fs');
@@ -127,22 +127,53 @@ const AUDIO_INPUTS = {
eq(o.variables.audioItaOpusBitrate, '384', 'Series DTS 768k >= 384 -> Opus=384');
cleanup(a); }
// Series AC3 320k < 384 threshold -> 256
// Series AC3 320k < 384 target -> 320 (strict no upcoding)
{ const streams = [
makeStream({ index:0, codec_type:'video' }),
makeStream({ index:1, codec_type:'audio', codec_name:'ac3', bit_rate:'320000', channels:6, tags:{language:'ita'} }),
];
const a = makeArgs('/media/Sonarr/Show/ep.mkv', streams, AUDIO_INPUTS, { isMovie:'false' });
const a = makeArgs('/media/Sonarr/Show/ep.mkv', streams, AUDIO_INPUTS, { isMovie:'false', isAnimation:'false' });
const o = await audioPlugin(a);
eq(o.variables.audioItaOpusBitrate, '256', 'Series AC3 320k < 384 -> Opus=256');
eq(o.variables.audioItaOpusBitrate, '320', 'Series AC3 320k -> min(384,320)=320 (no upcoding)');
cleanup(a); }
// NEW: Series bitrate unknown (0) -> safe default 384k
// Series multichannel low bitrate 192k -> 192 (strict no upcoding to 256)
{ const streams = [
makeStream({ index:0, codec_type:'video' }),
makeStream({ index:1, codec_type:'audio', codec_name:'aac', bit_rate:'192000', channels:6, tags:{language:'ita'} }),
];
const a = makeArgs('/media/Sonarr/Show/ep.mkv', streams, AUDIO_INPUTS, { isMovie:'false', isAnimation:'false' });
const o = await audioPlugin(a);
eq(o.variables.audioItaOpusBitrate, '192', 'Series 192k multichannel -> stays 192k (never upcoded to 256)');
cleanup(a); }
// Series / Animation with 256k threshold -> capped at 256k
{ const streams = [
makeStream({ index:0, codec_type:'video' }),
makeStream({ index:1, codec_type:'audio', codec_name:'ac3', bit_rate:'448000', channels:6, tags:{language:'ita'} }),
];
const customInputs = Object.assign({}, AUDIO_INPUTS, { seriesMultiChannelThreshold: '256' });
const a = makeArgs('/media/Sonarr/Naruto/ep.mkv', streams, customInputs, { isMovie:'false' });
const o = await audioPlugin(a);
eq(o.variables.audioItaOpusBitrate, '256', 'Animation 448k multichannel with 256k threshold -> capped at 256k');
cleanup(a); }
// Detection via tags.BPS when stream.bit_rate is 0
{ const streams = [
makeStream({ index:0, codec_type:'video' }),
makeStream({ index:1, codec_type:'audio', codec_name:'ac3', bit_rate:'0', channels:6, tags:{language:'ita', BPS:'384000'} }),
];
const a = makeArgs('/media/Sonarr/Show/ep.mkv', streams, AUDIO_INPUTS, { isMovie:'false', isAnimation:'false' });
const o = await audioPlugin(a);
eq(o.variables.audioItaOpusBitrate, '384', 'Bitrate detected from tags.BPS -> 384k');
cleanup(a); }
// Series bitrate unknown (0) and no BPS tag -> safe default 384k
{ const streams = [
makeStream({ index:0, codec_type:'video' }),
makeStream({ index:1, codec_type:'audio', codec_name:'dts', bit_rate:'0', channels:6, tags:{language:'ita'} }),
];
const a = makeArgs('/media/Sonarr/Show/ep.mkv', streams, AUDIO_INPUTS, { isMovie:'false' });
const a = makeArgs('/media/Sonarr/Show/ep.mkv', streams, AUDIO_INPUTS, { isMovie:'false', isAnimation:'false' });
const o = await audioPlugin(a);
eq(o.variables.audioItaOpusBitrate, '384', 'Series unknown bitrate (0) -> safe default 384k');
cleanup(a); }
@@ -208,25 +239,35 @@ const AUDIO_INPUTS = {
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');
// ── copyToWorkDir & replaceOriginalFile v1.0.0 checks ─────────────────────
const copyPluginCode = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/copyToWorkDir/1.0.0/index.js'), 'utf8');
ok(copyPluginCode.includes('[copyToWorkDir] Progress:'), 'copyToWorkDir reports copy progress in the job log');
const copyPluginV2Code = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/copyToWorkDir/2.0.0/index.js'), 'utf8');
ok(copyPluginV2Code.includes('updateProgress: args.updateProgress'), 'copyToWorkDir v2 forwards UI progress');
const replacePluginCode = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/replaceOriginalFile/2.0.0/index.js'), 'utf8');
ok(replacePluginCode.includes('.partial.old'), 'replaceOriginalFile v2 uses a rollback backup');
ok(replacePluginCode.includes('fs.renameSync(temporary, destination)'), 'replaceOriginalFile v2 atomically installs the verified copy');
const replacePluginV1Code = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/replaceOriginalFile/1.0.0/index.js'), 'utf8');
ok(replacePluginV1Code.includes('copyAndVerify'), 'replaceOriginalFile v1 verifies the copied file');
ok(!replacePluginV1Code.includes('updateProgress'), 'replaceOriginalFile v1 has no progress callback');
ok(copyPluginCode.includes('copyWithProgress'), 'copyToWorkDir v1 uses copyWithProgress');
ok(copyPluginCode.includes('updateWorker'), 'copyToWorkDir v1 forwards updateWorker callback');
ok(!fs.existsSync(path.join(ROOT, 'LocalFlowPlugins/utility/copyToWorkDir/2.0.0')), 'copyToWorkDir v2.0.0 is deleted');
const replacePluginCode = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/replaceOriginalFile/1.0.0/index.js'), 'utf8');
ok(replacePluginCode.includes('copyWithProgress'), 'replaceOriginalFile v1 uses copyWithProgress');
ok(replacePluginCode.includes('updateWorker'), 'replaceOriginalFile v1 forwards updateWorker callback');
ok(replacePluginCode.includes('.partial.old'), 'replaceOriginalFile v1 uses a rollback backup');
ok(replacePluginCode.includes('fs.renameSync(temporary, destination)'), 'replaceOriginalFile v1 atomically installs the verified copy');
ok(!fs.existsSync(path.join(ROOT, 'LocalFlowPlugins/utility/replaceOriginalFile/2.0.0')), 'replaceOriginalFile v2.0.0 is deleted');
const copyHelperCode = require('fs').readFileSync(path.join(ROOT, 'FlowHelpers/1.0.0/copyWithProgress.js'), 'utf8');
ok(copyHelperCode.includes('MAX_RETRIES = 2'), 'copy helper retries failed writes');
ok(copyHelperCode.includes('Copy size mismatch'), 'copy helper verifies destination size');
ok(copyHelperCode.includes('fs.unlinkSync(destination)'), 'copy helper removes partial copies before retrying');
ok(copyHelperCode.includes('fs.promises.copyFile(source, destination)'), 'copy helper uses native file copying');
ok(copyHelperCode.includes('PROGRESS_INTERVAL_MS = 250'), 'copy helper polls progress frequently');
ok(copyHelperCode.includes('intervalSpeed'), 'copy helper reports interval speed');
ok(!copyPluginV2Code.includes('copyMethod'), 'copyToWorkDir v2 has no copy method setting');
ok(!replacePluginCode.includes('bufferSizeMb'), 'replaceOriginalFile v2 has no buffer setting');
ok(copyHelperCode.includes('formatEta'), 'copy helper formats ETA');
ok(copyHelperCode.includes('updateWorker'), 'copy helper updates Tdarr worker progress');
// ── Subtitle default naming checks ─────────────────────────────────────────
console.log('\n── Subtitle default naming rules ────────────────────');
const subPluginCode = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/subtitles/extractSubtitlesAndOcr/1.0.0/index.js'), 'utf8');
ok(subPluginCode.includes("lang === 'ita' && isForced"), 'extractSubtitlesAndOcr: only ita.forced gets default flag');
ok(!subPluginCode.includes("if (isForced || (stream.disposition && stream.disposition.default))"), 'extractSubtitlesAndOcr: old buggy default condition removed');
const finalizeCode = require('fs').readFileSync(path.join(ROOT, 'LocalFlowPlugins/utility/finalizeMedia/1.0.0/index.js'), 'utf8');
ok(finalizeCode.includes("value === 'default'"), 'finalizeMedia: strips default before checking for ita.forced');
ok(finalizeCode.includes("isItaForced ? '.default' : ''"), 'finalizeMedia: expected subtitle sidecar only expects .default on ita.forced');
// ── Summary ───────────────────────────────────────────────────────────────
console.log(`\n${'='.repeat(55)}`);