UNPKG

n8n-nodes-audio-tools

Version:

Community audio processing nodes for n8n: concatWithGap & mergeTracks

154 lines (153 loc) 7.11 kB
"use strict"; /* ------------------------------------------------------------------------- * 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;