UNPKG

n8n-nodes-mediafx

Version:

N8N custom nodes for video editing and media processing

144 lines (143 loc) 5.84 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.executeAddSubtitle = void 0; const n8n_workflow_1 = require("n8n-workflow"); const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const ffmpeg = require("fluent-ffmpeg"); // import SrtParser from 'srt-parser-2'; const utils_1 = require("../utils"); function getPositionFromAlignment(horizontalAlign, verticalAlign, paddingX, paddingY) { let x; let y; // Set X position based on horizontal alignment switch (horizontalAlign) { case 'left': x = `${paddingX}`; break; case 'right': x = `w-text_w-${paddingX}`; break; case 'center': default: x = '(w-text_w)/2'; break; } // Set Y position based on vertical alignment switch (verticalAlign) { case 'top': y = `${paddingY}`; break; case 'bottom': y = `h-th-${paddingY}`; break; case 'middle': default: y = '(h-text_h)/2'; break; } return { x, y }; } // Helper to convert srt time format "00:00:08,220" to seconds function srtTimeToSeconds(time) { const parts = time.split(/[:,]/); const hours = parseInt(parts[0], 10); const minutes = parseInt(parts[1], 10); const seconds = parseInt(parts[2], 10); const milliseconds = parseInt(parts[3], 10); return hours * 3600 + minutes * 60 + seconds + milliseconds / 1000; } async function executeAddSubtitle(video, subtitleFile, style, itemIndex) { var _a, _b, _c, _d; const outputPath = (0, utils_1.getTempFile)(path.extname(video)); // 1. Get Font Path from fontKey const allFonts = (0, utils_1.getAvailableFonts)(); const fontKey = style.fontKey || 'noto-sans-kr'; const font = allFonts[fontKey]; if (!font || !font.path) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font key '${fontKey}' is not valid or its file path is missing.`, { itemIndex }); } const fontPath = font.path; // 2. Parse SRT file const srtContent = fs.readFileSync(subtitleFile, 'utf8'); const SrtParser = (await Promise.resolve().then(() => __importStar(require('srt-parser-2')))).default; const parser = new SrtParser(); const srtEntries = parser.fromSrt(srtContent); if (srtEntries.length === 0) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Could not parse SRT file or it is empty.`, { itemIndex, }); } // 3. Handle position based on position type let positionX; let positionY; const positionType = style.positionType || 'alignment'; if (positionType === 'alignment') { const horizontalAlign = style.horizontalAlign || 'center'; const verticalAlign = style.verticalAlign || 'bottom'; const paddingX = (_b = (_a = style.paddingX) !== null && _a !== void 0 ? _a : style.padding) !== null && _b !== void 0 ? _b : 20; const paddingY = (_d = (_c = style.paddingY) !== null && _c !== void 0 ? _c : style.padding) !== null && _d !== void 0 ? _d : 20; const position = getPositionFromAlignment(horizontalAlign, verticalAlign, paddingX, paddingY); positionX = position.x; positionY = position.y; } else { // Custom position positionX = style.x || '(w-text_w)/2'; positionY = style.y || 'h-th-50'; } // 4. Build drawtext filter chain const drawtextFilters = srtEntries.map((entry) => { const text = entry.text.replace(/'/g, `''`).replace(/:/g, `\\:`); // Escape single quotes and colons for ffmpeg const startTime = srtTimeToSeconds(entry.startTime); const endTime = srtTimeToSeconds(entry.endTime); const styleArgs = [ `fontfile=${fontPath}`, `text='${text}'`, `fontsize=${style.size || 48}`, `fontcolor=${style.color || 'white'}`, `box=1:boxcolor=black@0.5:boxborderw=5`, `x=${positionX}`, `y=${positionY}`, `enable='between(t,${startTime},${endTime})'`, ]; return `drawtext=${styleArgs.join(':')}`; }); const command = ffmpeg(video) .videoFilters(drawtextFilters) .audioCodec('copy') .save(outputPath); try { await (0, utils_1.runFfmpeg)(command); return outputPath; } catch (error) { // Clean up output file if creation failed await fs.remove(outputPath).catch(() => { }); throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error adding subtitles to video. FFmpeg error: ${error.message}`, { itemIndex }); } } exports.executeAddSubtitle = executeAddSubtitle;