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:
commit
c2074be826
19 files changed
+2134
No files matched your search
@@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
const { execSync, spawnSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const FFMPEG = process.env.FFMPEG_PATH || 'ffmpeg';
|
||||
|
||||
const SAMPLES = {
|
||||
test1: 'tests/samples/media/toshiba/Radarr/Film Test (2023)/Film Test (2023).mkv',
|
||||
test2: 'tests/samples/media/toshiba/Sonarr/Naruto S01/Naruto S01E01.mkv',
|
||||
test3: 'tests/samples/test3_ultrawide.mkv',
|
||||
test4: 'tests/samples/test4_subtitles.mkv',
|
||||
};
|
||||
|
||||
function run(cmd) {
|
||||
console.log(`\n>> ${cmd}\n`);
|
||||
const result = spawnSync(cmd, { shell: true, stdio: 'inherit' });
|
||||
if (result.status !== 0) {
|
||||
console.error(`Command failed with exit ${result.status}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDir(filePath) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
}
|
||||
|
||||
// Test 1 — skip if already generated
|
||||
if (!fs.existsSync(SAMPLES.test1)) {
|
||||
console.log('Generating Test 1: 23.976fps Film (AC3 ITA 448k + EAC3 ENG 640k)...');
|
||||
ensureDir(SAMPLES.test1);
|
||||
run([
|
||||
FFMPEG, '-y',
|
||||
'-f', 'lavfi', '-i', 'color=c=blue:s=1920x1080:r=24000/1001:d=30',
|
||||
'-f', 'lavfi', '-i', 'sine=f=440:r=48000:d=30',
|
||||
'-f', 'lavfi', '-i', 'sine=f=660:r=48000:d=30',
|
||||
'-map', '0:v', '-map', '1:a', '-map', '2:a',
|
||||
'-c:v', 'libx264', '-crf', '28', '-preset', 'ultrafast',
|
||||
'-c:a:0', 'ac3', '-b:a:0', '448k', '-ac:a:0', '6', '-metadata:s:a:0', 'language=ita',
|
||||
'-c:a:1', 'eac3', '-b:a:1', '640k', '-ac:a:1', '6', '-metadata:s:a:1', 'language=eng',
|
||||
`"${SAMPLES.test1}"`,
|
||||
].join(' '));
|
||||
console.log('Test 1 OK.\n');
|
||||
} else {
|
||||
console.log('Test 1 already exists — skipping.\n');
|
||||
}
|
||||
|
||||
// Test 2 — EAC3 ITA 640k 5.1 at 25fps (DTS is experimental in FFmpeg)
|
||||
// Note: DTS logic is fully covered by unit tests with synthetic streams.
|
||||
// For integration, we use EAC3 640k which triggers series clamping: 640>=384 -> Opus=384
|
||||
console.log('Generating Test 2: 25fps Anime EAC3 ITA 640k 5.1 (Naruto)...');
|
||||
ensureDir(SAMPLES.test2);
|
||||
run([
|
||||
FFMPEG, '-y',
|
||||
'-f', 'lavfi', '-i', 'color=c=green:s=1280x720:r=25:d=30',
|
||||
'-f', 'lavfi', '-i', 'sine=f=660:r=48000:d=30',
|
||||
'-map', '0:v', '-map', '1:a',
|
||||
'-c:v', 'libx264', '-crf', '28', '-preset', 'ultrafast',
|
||||
'-c:a:0', 'eac3', '-b:a:0', '640k', '-ac:a:0', '6', '-metadata:s:a:0', 'language=ita',
|
||||
`"${SAMPLES.test2}"`,
|
||||
].join(' '));
|
||||
console.log('Test 2 OK.\n');
|
||||
|
||||
// Test 3 — Ultrawide 3840x1600
|
||||
console.log('Generating Test 3: Ultrawide 3840x1600...');
|
||||
ensureDir(SAMPLES.test3);
|
||||
run([
|
||||
FFMPEG, '-y',
|
||||
'-f', 'lavfi', '-i', 'color=c=red:s=3840x1600:r=24:d=10',
|
||||
'-f', 'lavfi', '-i', 'sine=f=440:r=48000:d=10',
|
||||
'-map', '0:v', '-map', '1:a',
|
||||
'-c:v', 'libx264', '-crf', '28', '-preset', 'ultrafast',
|
||||
'-c:a', 'aac', '-b:a', '128k', '-metadata:s:a:0', 'language=eng',
|
||||
`"${SAMPLES.test3}"`,
|
||||
].join(' '));
|
||||
console.log('Test 3 OK.\n');
|
||||
|
||||
// Test 4 — SRT forced + SRT SDH
|
||||
console.log('Generating Test 4: Subtitles (SRT forced + SRT SDH)...');
|
||||
ensureDir(SAMPLES.test4);
|
||||
|
||||
const srtContent = '1\n00:00:01,000 --> 00:00:03,000\nTest subtitle line 1\n\n2\n00:00:04,000 --> 00:00:06,000\nTest subtitle line 2\n';
|
||||
const tmpSrt = 'tests/samples/tmp_sub.srt';
|
||||
fs.writeFileSync(tmpSrt, srtContent);
|
||||
|
||||
run([
|
||||
FFMPEG, '-y',
|
||||
'-f', 'lavfi', '-i', 'color=c=gray:s=1920x1080:r=25:d=30',
|
||||
'-f', 'lavfi', '-i', 'sine=f=440:r=48000:d=30',
|
||||
'-i', `"${tmpSrt}"`,
|
||||
'-i', `"${tmpSrt}"`,
|
||||
'-map', '0:v', '-map', '1:a', '-map', '2:s', '-map', '3:s',
|
||||
'-c:v', 'libx264', '-crf', '28', '-preset', 'ultrafast',
|
||||
'-c:a', 'aac', '-b:a', '128k', '-metadata:s:a:0', 'language=ita',
|
||||
'-c:s:0', 'srt', '-metadata:s:s:0', 'language=ita', '-disposition:s:0', 'forced',
|
||||
'-c:s:1', 'srt', '-metadata:s:s:1', 'language=ita', '-disposition:s:1', 'hearing_impaired',
|
||||
`"${SAMPLES.test4}"`,
|
||||
].join(' '));
|
||||
try { fs.unlinkSync(tmpSrt); } catch(_) {}
|
||||
console.log('Test 4 OK.\n');
|
||||
|
||||
console.log('All test files generated. Run: node tests/run-tests.js --skip-transcode');
|
||||
Reference in new issue
Block a user