feat: Tdarr Local Flow Plugin Suite v1.0

7 atomic plugins for automated x265 transcoding pipeline:
- utility/mediaAnalyzerGOP    : GOP calc, FPS detection, film/series/anime routing
- utility/setAnimationFlag    : manual animation flag override
- utility/extractChaptersXml  : mkvextract chapters to XML
- audio/audioSelectorClamping : codec scoring, clamped Opus bitrates, 2 outputs
- audio/backupSourceAudio     : lossless DTS/TrueHD ITA backup (-c:a copy)
- subtitles/extractSubtitlesAndOcr : SRT extract + SubtitleEdit OCR for PGS
- video/transcode2PassX265    : 2-pass x265, Lanczos 1080p bound, Opus mux

Test suite: 34 unit tests + 3 integration tests (FFmpeg real files)
FlowHelpers mock for local testing included
Docs: integration-guide.md with canvas wiring and deploy instructions
This commit is contained in:
PickleRick committed 2026-09-18 23:23:05 +02:00
commit c2074be826
19 files changed
+2134

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
'use strict';
/**
* run-tests.js
* Orchestrates all 4 test cases.
* Usage: node tests/run-tests.js [--skip-transcode]
*
* Options:
* --skip-transcode Skip test3 (which runs actual FFmpeg x265 encode)
*/
const args = process.argv.slice(2);
const skipTranscode = args.includes('--skip-transcode');
const tests = [
{ name: 'Test 1 — Film 23.976fps (AC3 ITA + EAC3 ENG)', file: './test-cases/test1-movie-23976fps', heavy: false },
{ name: 'Test 2 — Anime 25fps DTS ITA (Naruto)', file: './test-cases/test2-anime-25fps', heavy: false },
{ name: 'Test 3 — Ultrawide 3840x1600 Scale', file: './test-cases/test3-ultrawide', heavy: true },
{ name: 'Test 4 — Subtitle Extraction (forced + SDH)', file: './test-cases/test4-subtitles', heavy: false },
];
async function main() {
console.log('╔══════════════════════════════════════════════════════════╗');
console.log('║ Tdarr Plugin Suite — Test Runner ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
const results = [];
for (const test of tests) {
if (test.heavy && skipTranscode) {
console.log(`[SKIP] ${test.name} (--skip-transcode)\n`);
results.push({ name: test.name, status: 'SKIPPED' });
continue;
}
console.log(`[RUN] ${test.name}`);
try {
const fn = require(test.file);
await fn();
results.push({ name: test.name, status: 'PASSED' });
} catch (err) {
console.error(`\n[FAIL] ${test.name}\n ${err.message}\n`);
results.push({ name: test.name, status: 'FAILED', error: err.message });
}
}
// ── Summary ───────────────────────────────────────────────────────────────
console.log('\n╔══════════════════════════════════════════════════════════╗');
console.log('║ SUMMARY ║');
console.log('╚══════════════════════════════════════════════════════════╝\n');
let passed = 0, failed = 0, skipped = 0;
for (const r of results) {
const icon = r.status === 'PASSED' ? '[OK]' : r.status === 'SKIPPED' ? '[--]' : '[!!]';
console.log(` ${icon} ${r.name}`);
if (r.error) console.log(` Error: ${r.error}`);
if (r.status === 'PASSED') passed++;
if (r.status === 'FAILED') failed++;
if (r.status === 'SKIPPED') skipped++;
}
console.log(`\n Results: ${passed} passed, ${failed} failed, ${skipped} skipped\n`);
if (failed > 0) process.exit(1);
}
main().catch((err) => {
console.error('Fatal error:', err);
process.exit(1);
});