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
110 lines
2.9 KiB
JavaScript
110 lines
2.9 KiB
JavaScript
'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 };
|