UNPKG

n8n-nodes-mediafx

Version:

N8N custom nodes for video editing and media processing

122 lines (121 loc) 5.57 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeMerge = void 0; const n8n_workflow_1 = require("n8n-workflow"); const ffmpeg = require("fluent-ffmpeg"); const fs = __importStar(require("fs-extra")); const utils_1 = require("../utils"); async function normalizeVideo(inputPath, refInfo) { const normalizedPath = (0, utils_1.getTempFile)('.ts'); const mainCleanup = () => fs.remove(normalizedPath); let silentAudioCleanup = null; const hasAudio = await (0, utils_1.fileHasAudio)(inputPath); const command = ffmpeg(inputPath); if (!hasAudio) { const duration = await (0, utils_1.getDuration)(inputPath); const { filePath: silentAudioPath, cleanup } = await (0, utils_1.createSilentAudio)(duration); command.addInput(silentAudioPath); silentAudioCleanup = cleanup; } const targetWidth = refInfo.width; const targetHeight = refInfo.height; const targetSar = (refInfo.sample_aspect_ratio && refInfo.sample_aspect_ratio !== 'N/A') ? refInfo.sample_aspect_ratio : '1:1'; const targetFrameRate = refInfo.r_frame_rate || '30'; const targetPixFmt = 'yuv420p'; const videoFilter = `[0:v]scale=${targetWidth}:${targetHeight}:force_original_aspect_ratio=decrease,pad=${targetWidth}:${targetHeight}:-1:-1:color=black,setsar=${targetSar},format=${targetPixFmt},fps=${targetFrameRate},setpts=PTS-STARTPTS[v_out]`; const audioFilter = hasAudio ? `[0:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,asetpts=PTS-STARTPTS[a_out]` : `[1:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,asetpts=PTS-STARTPTS[a_out]`; command .complexFilter([videoFilter, audioFilter]) .outputOptions(['-map', '[v_out]', '-map', '[a_out]']) .videoCodec('libx264') .audioCodec('aac') .save(normalizedPath); await (0, utils_1.runFfmpeg)(command); const combinedCleanup = async () => { await mainCleanup(); if (silentAudioCleanup) { await silentAudioCleanup(); } }; return { normalizedPath, cleanup: combinedCleanup }; } async function executeMerge(inputs, outputFormat, itemIndex) { // Verify FFmpeg is available before proceeding try { (0, utils_1.verifyFfmpegAvailability)(); } catch (error) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `FFmpeg is not available: ${error.message}`, { itemIndex }); } if (inputs.length < 1) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Merge operation requires at least one source.', { itemIndex, }); } const intermediateFiles = []; let outputPath = null; const finalCleanup = async () => { for (const file of intermediateFiles) { await file.cleanup(); } }; try { const videoInfos = await Promise.all(inputs.map(utils_1.getVideoStreamInfo)); const refInfo = videoInfos.find((info) => info !== undefined); if (!refInfo || !refInfo.width || !refInfo.height) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Could not determine reference video properties for merging. At least one input must be a valid video.', { itemIndex }); } // 1. Normalization Stage for (const inputPath of inputs) { const { normalizedPath, cleanup } = await normalizeVideo.call(this, inputPath, refInfo); intermediateFiles.push({ path: normalizedPath, cleanup }); } // 2. Merging Stage - Using the concat protocol for stability outputPath = (0, utils_1.getTempFile)(`.${outputFormat}`); const normalizedPaths = intermediateFiles.map((f) => f.path); const concatString = `concat:${normalizedPaths.join('|')}`; const command = ffmpeg() .input(concatString) .outputOptions('-c', 'copy') .save(outputPath); await (0, utils_1.runFfmpeg)(command); return outputPath; } catch (error) { // Clean up output file if creation failed if (outputPath) { await fs.remove(outputPath).catch(() => { }); } await finalCleanup(); throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error merging videos. Please ensure all source videos are valid. FFmpeg error: ${error.message}`, { itemIndex }); } finally { await finalCleanup(); } } exports.executeMerge = executeMerge;