n8n-nodes-audio-tools
Version:
Community audio processing nodes for n8n: concatWithGap & mergeTracks
154 lines (153 loc) • 7.11 kB
JavaScript
;
/* -------------------------------------------------------------------------
* nodes/AudioMergeTracks.node.ts – v3 (full-RAM / binary-only version)
* -------------------------------------------------------------------------
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AudioMergeTracks = void 0;
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
const ffmpeg_utils_1 = require("../helpers/ffmpeg-utils");
/* --------------------------------------------------------------------- */
/* Node */
/* --------------------------------------------------------------------- */
class AudioMergeTracks {
constructor() {
/* -------- Node metadata -------- */
this.description = {
name: "audioMergeTracks",
displayName: "Audio · Merge Tracks",
group: ["transform"],
version: 3,
icon: "file:audio-tools.png",
description: "Mixes two audio binaries (delay & gain on track 2) and returns the result as a binary",
defaults: { name: "Merge Tracks" },
inputs: ["main"],
outputs: ["main"],
properties: [
/* ------------- Inputs ------------- */
{
displayName: "File 1 – Binary Property",
name: "file1Prop",
type: "string",
default: "file1",
description: "Name of the binary property that contains the first audio file",
required: true,
},
{
displayName: "File 2 – Binary Property",
name: "file2Prop",
type: "string",
default: "file2",
description: "Name of the binary property that contains the second audio file",
required: true,
},
{
displayName: "Delay for File 2 (ms)",
name: "delayMs",
type: "number",
typeOptions: { minValue: 0 },
default: 0,
},
{
displayName: "Gain for File 2 (dB)",
name: "file2GainDb",
type: "number",
default: 0,
},
/* ------------- Output ------------- */
{
displayName: "Output Format",
name: "outputFormat",
type: "options",
options: [
{ name: "Same as File 1", value: "auto" },
{ name: "WAV", value: "wav" },
{ name: "MP3", value: "mp3" },
{ name: "FLAC", value: "flac" },
],
default: "auto",
},
{
displayName: "Output Binary Property Name",
name: "binaryPropertyName",
type: "string",
default: "data",
},
],
};
}
/* ------------------------------------------------------------------- */
/* EXECUTE */
/* ------------------------------------------------------------------- */
async execute() {
var _a, _b;
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
/* ---------- read parameters ---------- */
const bin1Prop = this.getNodeParameter("file1Prop", i);
const bin2Prop = this.getNodeParameter("file2Prop", i);
const delayMs = this.getNodeParameter("delayMs", i, 0);
const gainDb = this.getNodeParameter("file2GainDb", i, 0);
const outFormat = this.getNodeParameter("outputFormat", i, "auto");
const outBinProp = this.getNodeParameter("binaryPropertyName", i, "data");
/* ---------- fetch binaries ---------- */
const bin1 = (_a = items[i].binary) === null || _a === void 0 ? void 0 : _a[bin1Prop];
const bin2 = (_b = items[i].binary) === null || _b === void 0 ? void 0 : _b[bin2Prop];
(0, ffmpeg_utils_1.ensureBinaryExists)(bin1, bin1Prop);
(0, ffmpeg_utils_1.ensureBinaryExists)(bin2, bin2Prop);
const buf1 = Buffer.from(bin1.data, "base64");
const buf2 = Buffer.from(bin2.data, "base64");
const ext1 = (0, ffmpeg_utils_1.getExtensionFromName)(bin1.fileName);
const ext2 = (0, ffmpeg_utils_1.getExtensionFromName)(bin2.fileName);
const formatExt = (outFormat === "auto" ? ext1 : outFormat);
/* ---------- build ffmpeg command ---------- */
let cmd = (0, fluent_ffmpeg_1.default)()
.input((0, ffmpeg_utils_1.bufferToStream)(buf1))
.inputFormat(ext1)
.input((0, ffmpeg_utils_1.bufferToStream)(buf2))
.inputFormat(ext2);
/* -- filtergraph --------------------------------------------------- */
const filters = [];
/* Track 1 : unchanged */
filters.push("[0:a]anull[a0]");
/* Track 2 : optional delay & gain */
const mods = [];
if (delayMs > 0)
mods.push(`adelay=${delayMs}|${delayMs}`);
if (gainDb !== 0)
mods.push(`volume=${gainDb}dB`);
if (mods.length) {
filters.push(`[1:a]${mods.join(",")}[a1mod]`);
}
else {
filters.push("[1:a]anull[a1mod]");
}
/* Mix + normalise */
filters.push("[a0][a1mod]amix=inputs=2:duration=longest:normalize=1[out]");
cmd = cmd.complexFilter(filters, "out");
/* -- output codec / quality --------------------------------------- */
switch (formatExt) {
case "mp3":
cmd.audioCodec("libmp3lame").outputOptions(["-q:a", "0"]); // VBR V0 ≈245 kb/s
break;
case "wav":
cmd.audioCodec("pcm_s16le");
break;
case "flac":
cmd.audioCodec("flac");
break;
}
/* ---------- run & push result ---------- */
const outBuf = await (0, ffmpeg_utils_1.runFfmpegToBuffer)(cmd, formatExt);
const binary = await this.helpers.prepareBinaryData(outBuf, `merge.${formatExt}`);
returnData.push({ json: {}, binary: { [outBinProp]: binary } });
}
return [returnData];
}
}
exports.AudioMergeTracks = AudioMergeTracks;
exports.default = AudioMergeTracks;