UNPKG

n8n-nodes-audio-tools

Version:

Community audio processing nodes for n8n: concatWithGap & mergeTracks

169 lines (168 loc) 7.87 kB
"use strict"; /* ------------------------------------------------------------------------- * nodes/AudioConcatWithGap.node.ts · v3 * – Concatène deux pistes audio binaires (file1 → silence → file2) * – Pas de fichier final sur disque ; un petit fichier temp pour la 2ᵉ piste * ------------------------------------------------------------------------- */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AudioConcatWithGap = void 0; const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg")); const fs_1 = require("fs"); const os_1 = require("os"); const path_1 = __importDefault(require("path")); const ffmpeg_utils_1 = require("../helpers/ffmpeg-utils"); class AudioConcatWithGap { constructor() { /* ────── Node metadata ────── */ this.description = { name: "audioConcatWithGap", displayName: "Audio · Concat With Gap", group: ["transform"], version: 3, icon: "file:audio-tools.png", description: "Binary in → binary out. First file · silence · second file, optional fades.", defaults: { name: "Concat With Gap" }, inputs: ["main"], outputs: ["main"], properties: [ { displayName: "File 1 Binary Property", name: "file1Prop", type: "string", default: "file1", }, { displayName: "File 2 Binary Property", name: "file2Prop", type: "string", default: "file2", }, { displayName: "Gap Duration (s)", name: "gapDuration", type: "number", typeOptions: { minValue: 0 }, default: 0, }, { displayName: "Fade-out of File 1 (ms)", name: "fadeOutMs", type: "number", typeOptions: { minValue: 0 }, default: 0, }, { displayName: "Fade-in of File 2 (ms)", name: "fadeInMs", type: "number", typeOptions: { minValue: 0 }, default: 0, }, { 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", }, ], }; } /* ───────────────────────────────────────────────────────────────────── */ async execute() { var _a, _b; const items = this.getInputData(); const returnData = []; for (let i = 0; i < items.length; i++) { /* ────── params ────── */ const p1 = this.getNodeParameter("file1Prop", i); const p2 = this.getNodeParameter("file2Prop", i); const gapDur = this.getNodeParameter("gapDuration", i, 0); const fadeOut = this.getNodeParameter("fadeOutMs", i, 0); const fadeIn = this.getNodeParameter("fadeInMs", i, 0); const outFmt = this.getNodeParameter("outputFormat", i, "auto"); const outProp = this.getNodeParameter("binaryPropertyName", i, "data"); /* ────── binaries ────── */ const bin1 = (_a = items[i].binary) === null || _a === void 0 ? void 0 : _a[p1]; const bin2 = (_b = items[i].binary) === null || _b === void 0 ? void 0 : _b[p2]; (0, ffmpeg_utils_1.ensureBinaryExists)(bin1, p1); (0, ffmpeg_utils_1.ensureBinaryExists)(bin2, p2); 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 fmt = (outFmt === "auto" ? ext1 : outFmt); /* ────── write buf2 → fichier temp (limite 1 stream de fluent-ffmpeg) ────── */ const tmpFile2 = path_1.default.join((0, os_1.tmpdir)(), `n8n-audio-${Date.now()}-${Math.random() .toString(36) .slice(2)}.${ext2}`); await fs_1.promises.writeFile(tmpFile2, buf2); try { /* ────── FFmpeg cmd ────── */ let cmd = (0, fluent_ffmpeg_1.default)() .input((0, ffmpeg_utils_1.bufferToStream)(buf1)) .inputFormat(ext1) // 1er flux .input(`anullsrc=r=44100:cl=stereo:d=${gapDur}`) // silence .inputOptions(["-f", "lavfi"]) .input(tmpFile2); // 2ᵉ piste (fichier) /* ────── filtergraph ────── */ const filters = []; // fade-out piste 1 if (fadeOut > 0) { const d1 = await (0, ffmpeg_utils_1.probeDurationBuffer)(buf1, ext1); filters.push(`[0:a]afade=t=out:st=${d1 - fadeOut / 1000}:d=${fadeOut / 1000}[a0]`); } else filters.push("[0:a]anull[a0]"); // fade-in piste 2 if (fadeIn > 0) filters.push(`[2:a]afade=t=in:st=0:d=${fadeIn / 1000}[a2]`); else filters.push("[2:a]anull[a2]"); // concat + normalise filters.push("[a0][1:a][a2]concat=n=3:v=0:a=1, dynaudnorm[out]"); cmd = cmd.complexFilter(filters, "out"); /* ────── codecs ────── */ switch (fmt) { case "mp3": cmd.audioCodec("libmp3lame").outputOptions([ "-q:a", "0", ]); break; case "wav": cmd.audioCodec("pcm_s16le"); break; case "flac": cmd.audioCodec("flac"); break; } /* ────── run ────── */ const outBuf = await (0, ffmpeg_utils_1.runFfmpegToBuffer)(cmd, fmt); const binary = await this.helpers.prepareBinaryData(outBuf, `concat.${fmt}`); returnData.push({ json: {}, binary: { [outProp]: binary } }); } finally { /* nettoyage fichier temp */ await fs_1.promises.unlink(tmpFile2).catch(() => { }); } } return [returnData]; } } exports.AudioConcatWithGap = AudioConcatWithGap; exports.default = AudioConcatWithGap;