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,109 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* mock-tdarr-runtime.js
|
||||
* Creates a complete mock of the Tdarr args object for isolated plugin testing.
|
||||
* Supports loading real ffprobe data from actual MKV files.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
/**
|
||||
* Run ffprobe on a file and return parsed JSON.
|
||||
* Falls back to a minimal synthetic structure if ffprobe is unavailable.
|
||||
*/
|
||||
function probeFile(filePath) {
|
||||
try {
|
||||
const raw = execSync(
|
||||
`ffprobe -v quiet -print_format json -show_streams -show_format "${filePath}"`,
|
||||
{ encoding: 'utf8', timeout: 15000 },
|
||||
);
|
||||
return JSON.parse(raw);
|
||||
} catch (err) {
|
||||
console.warn(`[mock] ffprobe failed for ${filePath}: ${err.message}`);
|
||||
return { streams: [], format: {} };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a mock Tdarr args object.
|
||||
*
|
||||
* @param {object} opts
|
||||
* @param {string} opts.filePath - Path to the input MKV file
|
||||
* @param {object} [opts.inputs] - Plugin input overrides (UI inputs)
|
||||
* @param {object} [opts.variables] - Pre-populated variables
|
||||
* @param {string} [opts.ffmpegPath] - Override ffmpeg path
|
||||
* @param {string} [opts.workDir] - Override work directory
|
||||
*/
|
||||
function buildMockArgs(opts) {
|
||||
const {
|
||||
filePath,
|
||||
inputs = {},
|
||||
variables = {},
|
||||
ffmpegPath = process.env.FFMPEG_PATH || 'ffmpeg',
|
||||
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tdarr-test-')),
|
||||
} = opts;
|
||||
|
||||
const ffProbeData = probeFile(filePath);
|
||||
const logs = [];
|
||||
|
||||
return {
|
||||
inputFileObj: {
|
||||
_id: filePath,
|
||||
file: filePath,
|
||||
ffProbeData,
|
||||
mediaInfo: { track: [] },
|
||||
container: path.extname(filePath).replace('.', ''),
|
||||
},
|
||||
variables: { ...variables },
|
||||
inputs,
|
||||
workDir,
|
||||
ffmpegPath,
|
||||
ffprobePath: process.env.FFPROBE_PATH || 'ffprobe',
|
||||
mkvpropeditPath: process.env.MKVPROPEDIT_PATH || 'mkvpropedit',
|
||||
userVariables: { global: {}, library: {} },
|
||||
jobLog: (msg) => {
|
||||
process.stdout.write(msg);
|
||||
logs.push(msg);
|
||||
},
|
||||
_logs: logs,
|
||||
_workDir: workDir,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup a workDir created by buildMockArgs.
|
||||
*/
|
||||
function cleanupWorkDir(mockArgs) {
|
||||
const dir = mockArgs._workDir || mockArgs.workDir;
|
||||
if (dir && dir.startsWith(os.tmpdir()) && fs.existsSync(dir)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple assertion helper.
|
||||
*/
|
||||
function assert(condition, message) {
|
||||
if (!condition) throw new Error(`ASSERTION FAILED: ${message}`);
|
||||
console.log(` [OK] ${message}`);
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, label) {
|
||||
assert(
|
||||
actual === expected,
|
||||
`${label}: expected "${expected}", got "${actual}"`,
|
||||
);
|
||||
}
|
||||
|
||||
function assertLessOrEqual(actual, max, label) {
|
||||
assert(
|
||||
parseInt(actual, 10) <= max,
|
||||
`${label}: ${actual} <= ${max}`,
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { buildMockArgs, cleanupWorkDir, assert, assertEqual, assertLessOrEqual };
|
||||
Reference in new issue
Block a user