Use Tdarr server logs to read normalisation data

make-only-subtitle-default
HaveAGitGat 3 years ago
parent 78bf921e86
commit 4afe5cc0b8

@ -1,4 +1,6 @@
/* eslint-disable */ /* eslint-disable no-unused-vars */
module.exports.dependencies = ['axios@0.27.2'];
// PLugin runs multipass loudnorm filter // PLugin runs multipass loudnorm filter
// first run gets the required details and stores for the next pass // first run gets the required details and stores for the next pass
// second pass applies the values // second pass applies the values
@ -8,230 +10,218 @@
// Applying Normalisation // Applying Normalisation
// Normalisation Complete // Normalisation Complete
//setup global vars
var secondPass = false;
var logOutFile = '';
// tdarrSkipTest // tdarrSkipTest
const details = () => { const details = () => ({
return { id: 'Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation',
id: "Tdarr_Plugin_NIfPZuCLU_2_Pass_Loudnorm_Audio_Normalisation",
Stage: 'Pre-processing', Stage: 'Pre-processing',
Name: "2 Pass Loudnorm Volume Normalisation", Name: '2 Pass Loudnorm Volume Normalisation',
Type: "Video", Type: 'Video',
Operation: "Transcode", Operation: 'Transcode',
Description: "PLEASE READ FULL DESCRIPTION BEFORE USE \n Uses multiple passes to normalise audio streams of videos using loudnorm.\n\n The first pass will create an log file in the same directory as the video.\nSecond pass will apply the values determined in the first pass to the file.\nOutput will be MKV to allow metadata to be added for tracking normalisation stage.", Description: `PLEASE READ FULL DESCRIPTION BEFORE USE
Version: "0.1", Uses multiple passes to normalise audio streams of videos using loudnorm.
Tags: "pre-processing,ffmpeg,configurable", The first pass will create an log file in the same directory as the video.
Second pass will apply the values determined in the first pass to the file.
Output will be MKV to allow metadata to be added for tracking normalisation stage.`,
Version: '0.1',
Tags: 'pre-processing,ffmpeg,configurable',
Inputs: [ Inputs: [
// (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI // (Optional) Inputs you'd like the user to enter to allow your plugin to be easily configurable from the UI
{ {
name: "i", name: 'i',
type: 'string', type: 'string',
defaultValue: '-23.0', defaultValue: '-23.0',
inputUI: { inputUI: {
type: 'text', type: 'text',
}, },
tooltip: `\"I\" value used in loudnorm pass \n tooltip: `"i" value used in loudnorm pass \\n
defaults to -23.0`, //Each line following `Example:` will be clearly formatted. \\n used for line breaks defaults to -23.0`,
}, },
{ {
name: "lra", name: 'lra',
type: 'string', type: 'string',
defaultValue: '7.0', defaultValue: '7.0',
inputUI: { inputUI: {
type: 'text', type: 'text',
}, },
tooltip: `Desired lra value. \n Defaults to 7.0 tooltip: `Desired lra value. \\n Defaults to 7.0
`, `,
}, },
{ {
name: "tp", name: 'tp',
type: 'string', type: 'string',
defaultValue: '-2.0', defaultValue: '-2.0',
inputUI: { inputUI: {
type: 'text', type: 'text',
}, },
tooltip: `Desired \"tp\" value. \n Defaults to -2.0 tooltip: `Desired "tp" value. \\n Defaults to -2.0
`, `,
}, },
{ ],
name: "offset", });
type: 'string',
defaultValue:'0.0', const parseJobName = (text) => {
inputUI: { const parts0 = text.split('.txt');
type: 'text', const parts1 = parts0[0].split('()');
return {
jobId: parts1[3],
start: Number(parts1[4]),
};
};
const getloudNormValues = async (response, file) => {
// eslint-disable-next-line import/no-unresolved
const axios = require('axios');
const serverUrl = `http://${process.env.serverIp}:${process.env.serverPort}`;
let loudNormValues = {};
try {
// wait for job report to be updated by server,
await new Promise((resolve) => setTimeout(resolve, 10000));
const logFilesReq = await axios.post(`${serverUrl}/api/v2/list-footprintId-reports`, {
data: {
footprintId: file.footprintId,
}, },
tooltip: `Desired "offset" value. \n Defaults to 0.0 });
`,
if (logFilesReq.status !== 200) {
throw new Error('Failed to get log files, please rerun');
}
let logFiles = logFilesReq.data;
logFiles = logFiles.sort((a, b) => {
const joba = parseJobName(a);
const jobb = parseJobName(b);
return jobb.start - joba.start;
});
const latestJob = logFiles[0];
const reportReq = await axios.post(`${serverUrl}/api/v2/read-job-file`, {
data: {
footprintId: file.footprintId,
jobId: parseJobName(latestJob).jobId,
jobFileId: latestJob,
}, },
], });
if (reportReq.status !== 200) {
throw new Error('Failed to get read latest log file, please rerun');
} }
const report = reportReq.data.text;
const lines = report.split('\n');
let idx = -1;
// get last index of Parsed_loudnorm
lines.forEach((line, i) => {
if (line.includes('Parsed_loudnorm')) {
idx = i;
} }
});
// eslint-disable-next-line no-unused-vars if (idx === -1) {
const plugin = (file, librarySettings, inputs, otherArguments) => { throw new Error('Failed to find loudnorm in report, please rerun');
}
const loudNormDataArr = [];
for (let i = (idx + 1); i < lines.length; i += 1) {
const lineArr = lines[i].split(' ');
lineArr.shift();
loudNormDataArr.push(lineArr.join(' '));
if (lines[i].includes('}')) {
break;
}
}
loudNormValues = JSON.parse(loudNormDataArr.join(''));
} catch (err) {
response.infoLog += err;
throw new Error(err);
}
return loudNormValues;
};
// eslint-disable-next-line no-unused-vars
const plugin = async (file, librarySettings, inputs, otherArguments) => {
const lib = require('../methods/lib')(); const fs = require('fs'); const lib = require('../methods/lib')(); const fs = require('fs');
// eslint-disable-next-line no-unused-vars,no-param-reassign // eslint-disable-next-line no-unused-vars,no-param-reassign
inputs = lib.loadDefaultValues(inputs, details); inputs = lib.loadDefaultValues(inputs, details);
// Must return this object at some point // Must return this object at some point
var response = { const response = {
processFile: false, processFile: false,
preset: '', preset: '',
container: '.mkv', container: `.${file.container}`,
handBrakeMode: false, handBrakeMode: false,
FFmpegMode: true, FFmpegMode: false,
reQueueAfter: true,
infoLog: '', infoLog: '',
custom: {
args: [],
cliPath: '',
outputPath: ',',
},
};
} response.infoLog += '';
response.infoLog += "" const probeData = file.ffProbeData;
//grab the current file being processed and make an out file for the ffmpeg log
let currentfilename = file._id;
logOutFile = currentfilename.substr(0, currentfilename.lastIndexOf(".")) + ".out"
console.log("Log out file: " + logOutFile)
let probeData;
if (file && file.ffProbeData && file.ffProbeData.format) {
probeData = file.ffProbeData;
} else {
//get an updated version of the file for checking metadata
probeData = JSON.parse(require("child_process").execSync(`ffprobe -v quiet -print_format json -show_format -show_streams "${currentfilename}"`).toString())
}
// setup required varibles // setup required varibles
var loudNorm_i = -23.0 let loudNorm_i = -23.0;
var lra = 7.0 let lra = 7.0;
var tp = -2.0 let tp = -2.0;
var offset = 0.0
// create local varibles for inputs // create local varibles for inputs
if (inputs !== undefined) { if (inputs !== undefined) {
if (inputs.i !== undefined) loudNorm_i = inputs.i if (inputs.i !== undefined) loudNorm_i = inputs.i;
if (inputs.lra !== undefined) lra = inputs.lra if (inputs.lra !== undefined) lra = inputs.lra;
if (inputs.tp !== undefined) tp = inputs.tp if (inputs.tp !== undefined) tp = inputs.tp;
if (inputs.offset !== undefined) offset = inputs.offset
} }
// check for previous pass tags // check for previous pass tags
if (!probeData?.format?.tags?.NORMALISATIONSTAGE) {
if (typeof probeData.format === "undefined" || typeof probeData.format.tags.NORMALISATIONSTAGE === "undefined" || probeData.format.tags.NORMALISATIONSTAGE === "" || file.forceProcessing === true) {
// no metadata found first pass is required // no metadata found first pass is required
console.log("Searching for audio normailisation values") response.infoLog += 'Searching for required normalisation values. \n';
response.infoLog += "Searching for required normalisation values. \n" response.infoLog += 'Normalisation first pass processing \n';
var loudNormInfo = "";
// Do the first pass, output the log to the out file and use a secondary output for an unchanged file to
//Do the first pass, output the log to the out file and use a secondary output for an unchanged file to allow Tdarr to track, Set metadata stage // allow Tdarr to track, Set metadata stage
response.preset = `<io>-af loudnorm=I=${loudNorm_i}:LRA=${lra}:TP=${tp}:print_format=json -f null NUL -map 0 -c copy -metadata NORMALISATIONSTAGE="FirstPassComplete" 2>"${logOutFile}"` response.preset = `<io>-af loudnorm=I=${loudNorm_i}:LRA=${lra}:TP=${tp}:print_format=json`
response.container = '.mkv' + ' -f null NUL -map 0 -c copy -metadata NORMALISATIONSTAGE=FirstPassComplete';
response.handBrakeMode = false response.FFmpegMode = true;
response.FFmpegMode = true response.processFile = true;
response.reQueueAfter = true; return response;
response.processFile = true } if (
response.infoLog += "Normalisation first pass processing \n" probeData.format.tags.NORMALISATIONSTAGE === 'FirstPassComplete'
return response ) {
} const loudNormValues = await getloudNormValues(response, file);
if (probeData.format.tags.NORMALISATIONSTAGE === "FirstPassComplete") {
//ensure previous out file exists
if (fs.existsSync(logOutFile)) {
secondPass = true;
loudNormInfo = fs.readFileSync(logOutFile).toString();
//grab the json from the out file
var startIndex = loudNormInfo.lastIndexOf("{");
var endIndex = loudNormInfo.lastIndexOf("}");
var outValues = loudNormInfo.toString().substr(startIndex, endIndex)
response.infoLog += "Loudnorm first pass values returned: \n" + outValues
//parse the JSON response.infoLog += `Loudnorm first pass values returned: \n${JSON.stringify(loudNormValues)}`;
var loudNormValues = JSON.parse(outValues)
// use parsed values in second pass // use parsed values in second pass
response.preset = `-y<io>-af loudnorm=print_format=summary:linear=true:I=${loudNorm_i}:LRA=${lra}:TP=${tp}:measured_i=${loudNormValues.input_i}:measured_lra=${loudNormValues.input_lra}:measured_tp=${loudNormValues.input_tp}:measured_thresh=${loudNormValues.input_thresh}:offset=${loudNormValues.target_offset} -c:a aac -b:a 192k -c:s copy -c:v copy -metadata NORMALISATIONSTAGE="Complete"` response.preset = `-y<io>-af loudnorm=print_format=summary:linear=true:I=${loudNorm_i}:LRA=${lra}:TP=${tp}:`
response.container = '.mkv' + `measured_i=${loudNormValues.input_i}:`
response.handBrakeMode = false + `measured_lra=${loudNormValues.input_lra}:`
response.FFmpegMode = true + `measured_tp=${loudNormValues.input_tp}:`
response.reQueueAfter = true; + `measured_thresh=${loudNormValues.input_thresh}:offset=${loudNormValues.target_offset} `
response.processFile = true + '-c:a aac -b:a 192k -c:s copy -c:v copy -metadata NORMALISATIONSTAGE=Complete';
response.infoLog += "Normalisation pass processing \n" response.FFmpegMode = true;
return response response.processFile = true;
} else { response.infoLog += 'Normalisation pass processing \n';
response.infoLog += "Previous log output file is missing. Please rerun with force processing to regenerate."
response.processFile = false;
return response
}
}
if(probeData.format.tags.NORMALISATIONSTAGE === "Complete"){
response.processFile = false;
response.infoLog += "File is already marked as normalised \n"
return response
} else {
//what is this tag?
response.processFile = false;
response.infoLog += "Unknown normalisation stage tag: \n" + probeData.format.tags.NORMALISATIONSTAGE
return response
}
}
module.exports.onTranscodeSuccess = function onTranscodeSuccess(
file,
librarySettings,
inputs
) {
const fs = require('fs');
var response = {
file,
removeFromDB: false,
updateDB: true,
};
if (secondPass) {
response.infoLog += "Audio normalisation complete. \n"
//remove old out file
if (fs.existsSync(logOutFile)) {
fs.unlinkSync(logOutFile);
}
return response; return response;
} } if (probeData.format.tags.NORMALISATIONSTAGE === 'Complete') {
else { response.infoLog += 'File is already marked as normalised \n';
response.infoLog += "Audio normalisation first pass complete. \n"
return response; return response;
} }
}; // what is this tag?
response.infoLog += `Unknown normalisation stage tag: \n${probeData.format.tags.NORMALISATIONSTAGE}`;
module.exports.onTranscodeError = function onTranscodeError(
file,
librarySettings,
inputs
) {
console.log("Failed to normalise audio");
//Optional response if you need to modify database
var response = {
file,
removeFromDB: false,
updateDB: false,
};
return response; return response;
}; };
module.exports.details = details; module.exports.details = details;
module.exports.plugin = plugin; module.exports.plugin = plugin;
Loading…
Cancel
Save