@lee-jisoo/n8n-nodes-mediafx 1.6.0 → 1.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/fonts/DejaVuSans.ttf +0 -0
  2. package/dist/fonts/Inter-Regular.ttf +0 -0
  3. package/dist/fonts/NanumGothic-Regular.ttf +0 -0
  4. package/dist/fonts/NotoSansKR-Regular.ttf +0 -0
  5. package/dist/fonts/Pretendard-Regular.otf +0 -0
  6. package/dist/fonts/Roboto-Regular.ttf +0 -0
  7. package/dist/nodes/MediaFX/MediaFX.node.d.ts +12 -0
  8. package/dist/nodes/MediaFX/MediaFX.node.js +559 -0
  9. package/dist/nodes/MediaFX/mediafx.png +0 -0
  10. package/dist/nodes/MediaFX/operations/addSubtitle.d.ts +2 -0
  11. package/dist/nodes/MediaFX/operations/addSubtitle.js +202 -0
  12. package/dist/nodes/MediaFX/operations/addText.d.ts +2 -0
  13. package/dist/nodes/MediaFX/operations/addText.js +108 -0
  14. package/dist/nodes/MediaFX/operations/extractAudio.d.ts +2 -0
  15. package/dist/nodes/MediaFX/operations/extractAudio.js +57 -0
  16. package/dist/nodes/MediaFX/operations/imageToVideo.d.ts +5 -0
  17. package/dist/nodes/MediaFX/operations/imageToVideo.js +65 -0
  18. package/dist/nodes/MediaFX/operations/index.d.ts +13 -0
  19. package/dist/nodes/MediaFX/operations/index.js +29 -0
  20. package/dist/nodes/MediaFX/operations/merge.d.ts +2 -0
  21. package/dist/nodes/MediaFX/operations/merge.js +121 -0
  22. package/dist/nodes/MediaFX/operations/mixAudio.d.ts +2 -0
  23. package/dist/nodes/MediaFX/operations/mixAudio.js +141 -0
  24. package/dist/nodes/MediaFX/operations/multiVideoTransition.d.ts +2 -0
  25. package/dist/nodes/MediaFX/operations/multiVideoTransition.js +245 -0
  26. package/dist/nodes/MediaFX/operations/overlayVideo.d.ts +16 -0
  27. package/dist/nodes/MediaFX/operations/overlayVideo.js +240 -0
  28. package/dist/nodes/MediaFX/operations/separateAudio.d.ts +17 -0
  29. package/dist/nodes/MediaFX/operations/separateAudio.js +78 -0
  30. package/dist/nodes/MediaFX/operations/singleVideoFade.d.ts +2 -0
  31. package/dist/nodes/MediaFX/operations/singleVideoFade.js +60 -0
  32. package/dist/nodes/MediaFX/operations/speed.d.ts +12 -0
  33. package/dist/nodes/MediaFX/operations/speed.js +110 -0
  34. package/dist/nodes/MediaFX/operations/stampImage.d.ts +2 -0
  35. package/dist/nodes/MediaFX/operations/stampImage.js +146 -0
  36. package/dist/nodes/MediaFX/operations/trim.d.ts +2 -0
  37. package/dist/nodes/MediaFX/operations/trim.js +49 -0
  38. package/dist/nodes/MediaFX/properties/audio.properties.d.ts +2 -0
  39. package/dist/nodes/MediaFX/properties/audio.properties.js +394 -0
  40. package/dist/nodes/MediaFX/properties/font.properties.d.ts +2 -0
  41. package/dist/nodes/MediaFX/properties/font.properties.js +186 -0
  42. package/dist/nodes/MediaFX/properties/image.properties.d.ts +2 -0
  43. package/dist/nodes/MediaFX/properties/image.properties.js +333 -0
  44. package/dist/nodes/MediaFX/properties/resources.properties.d.ts +2 -0
  45. package/dist/nodes/MediaFX/properties/resources.properties.js +34 -0
  46. package/dist/nodes/MediaFX/properties/subtitle.properties.d.ts +2 -0
  47. package/dist/nodes/MediaFX/properties/subtitle.properties.js +361 -0
  48. package/dist/nodes/MediaFX/properties/video.properties.d.ts +2 -0
  49. package/dist/nodes/MediaFX/properties/video.properties.js +1135 -0
  50. package/dist/nodes/MediaFX/utils/ffmpegVersion.d.ts +14 -0
  51. package/dist/nodes/MediaFX/utils/ffmpegVersion.js +97 -0
  52. package/dist/nodes/MediaFX/utils.d.ts +43 -0
  53. package/dist/nodes/MediaFX/utils.js +410 -0
  54. package/package.json +1 -1
@@ -0,0 +1,202 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.executeAddSubtitle = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ const path = __importStar(require("path"));
29
+ const fs = __importStar(require("fs-extra"));
30
+ const ffmpeg = require("fluent-ffmpeg");
31
+ const utils_1 = require("../utils");
32
+ /**
33
+ * Escape special characters in file path for FFmpeg subtitles filter.
34
+ */
35
+ function escapeSubtitlePath(filePath) {
36
+ return filePath
37
+ .replace(/\\/g, '\\\\\\\\')
38
+ .replace(/:/g, '\\:')
39
+ .replace(/\[/g, '\\[')
40
+ .replace(/\]/g, '\\]')
41
+ .replace(/'/g, "\\'");
42
+ }
43
+ /**
44
+ * Convert color to ASS format (&HAABBGGRR)
45
+ * @param color - CSS color (hex #RRGGBB or named color)
46
+ * @param opacity - Opacity 0-1 (0=transparent, 1=solid)
47
+ */
48
+ function colorToASS(color, opacity = 1) {
49
+ let r = 'FF', g = 'FF', b = 'FF';
50
+ if (color && color.startsWith('#') && color.length === 7) {
51
+ r = color.substring(1, 3);
52
+ g = color.substring(3, 5);
53
+ b = color.substring(5, 7);
54
+ }
55
+ else {
56
+ const namedColors = {
57
+ white: 'FFFFFF',
58
+ black: '000000',
59
+ red: 'FF0000',
60
+ green: '00FF00',
61
+ blue: '0000FF',
62
+ yellow: 'FFFF00',
63
+ cyan: '00FFFF',
64
+ magenta: 'FF00FF',
65
+ orange: 'FFA500',
66
+ };
67
+ const hex = namedColors[color.toLowerCase()] || 'FFFFFF';
68
+ r = hex.substring(0, 2);
69
+ g = hex.substring(2, 4);
70
+ b = hex.substring(4, 6);
71
+ }
72
+ // ASS format: &HAABBGGRR (AA = alpha, 00=solid, FF=transparent)
73
+ const alpha = Math.round((1 - opacity) * 255).toString(16).padStart(2, '0').toUpperCase();
74
+ return `&H${alpha}${b}${g}${r}`.toUpperCase();
75
+ }
76
+ /**
77
+ * Get ASS alignment number (numpad style)
78
+ */
79
+ function getASSAlignment(horizontalAlign, verticalAlign) {
80
+ var _a, _b;
81
+ const alignMap = {
82
+ top: { left: 7, center: 8, right: 9 },
83
+ middle: { left: 4, center: 5, right: 6 },
84
+ bottom: { left: 1, center: 2, right: 3 },
85
+ };
86
+ return (_b = (_a = alignMap[verticalAlign]) === null || _a === void 0 ? void 0 : _a[horizontalAlign]) !== null && _b !== void 0 ? _b : 2;
87
+ }
88
+ /**
89
+ * Parse SRT file and convert to ASS format
90
+ */
91
+ function convertSRTtoASS(srtContent, fontName, fontSize, primaryColor, outlineColor, outlineWidth, backgroundColor, backgroundOpacity, enableBackground, alignment, marginV) {
92
+ // ASS Header
93
+ const borderStyle = enableBackground ? 4 : 1; // 4=box, 1=outline+shadow
94
+ const backColor = colorToASS(backgroundColor, backgroundOpacity);
95
+ const assHeader = `[Script Info]
96
+ Title: Converted from SRT
97
+ ScriptType: v4.00+
98
+ PlayResX: 1920
99
+ PlayResY: 1080
100
+ WrapStyle: 0
101
+
102
+ [V4+ Styles]
103
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
104
+ Style: Default,${fontName},${fontSize},${primaryColor},${primaryColor},${outlineColor},${backColor},0,0,0,0,100,100,0,0,${borderStyle},${outlineWidth},0,${alignment},20,20,${marginV},1
105
+
106
+ [Events]
107
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
108
+ `;
109
+ // Parse SRT content
110
+ const srtBlocks = srtContent.trim().split(/\n\s*\n/);
111
+ const dialogueLines = [];
112
+ for (const block of srtBlocks) {
113
+ const lines = block.trim().split('\n');
114
+ if (lines.length < 3)
115
+ continue;
116
+ // Parse timestamp line (format: 00:00:00,000 --> 00:00:00,000)
117
+ const timestampLine = lines[1];
118
+ const timestampMatch = timestampLine.match(/(\d{2}):(\d{2}):(\d{2})[,.](\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2})[,.](\d{3})/);
119
+ if (!timestampMatch)
120
+ continue;
121
+ const startTime = `${timestampMatch[1]}:${timestampMatch[2]}:${timestampMatch[3]}.${timestampMatch[4].substring(0, 2)}`;
122
+ const endTime = `${timestampMatch[5]}:${timestampMatch[6]}:${timestampMatch[7]}.${timestampMatch[8].substring(0, 2)}`;
123
+ // Get text (lines 3+)
124
+ const text = lines.slice(2).join('\\N').replace(/\r/g, '');
125
+ dialogueLines.push(`Dialogue: 0,${startTime},${endTime},Default,,0,0,0,,${text}`);
126
+ }
127
+ return assHeader + dialogueLines.join('\n');
128
+ }
129
+ /**
130
+ * Check if file is ASS/SSA format
131
+ */
132
+ function isASSFile(filePath) {
133
+ const ext = path.extname(filePath).toLowerCase();
134
+ return ext === '.ass' || ext === '.ssa';
135
+ }
136
+ async function executeAddSubtitle(video, subtitleFile, style, itemIndex) {
137
+ var _a, _b, _c, _d;
138
+ const outputPath = (0, utils_1.getTempFile)(path.extname(video));
139
+ // 1. Get Font
140
+ const allFonts = (0, utils_1.getAvailableFonts)();
141
+ const fontKey = style.fontKey || 'noto-sans-kr';
142
+ const font = allFonts[fontKey];
143
+ if (!font || !font.path) {
144
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font key '${fontKey}' is not valid or its file path is missing.`, { itemIndex });
145
+ }
146
+ const fontName = font.name || 'Sans';
147
+ const fontSize = style.size || 48;
148
+ const fontColor = style.color || 'white';
149
+ const outlineWidth = (_a = style.outlineWidth) !== null && _a !== void 0 ? _a : 1;
150
+ const outlineColor = style.outlineColor || 'black';
151
+ const enableBackground = (_b = style.enableBackground) !== null && _b !== void 0 ? _b : false;
152
+ const backgroundColor = style.backgroundColor || 'black';
153
+ const backgroundOpacity = (_c = style.backgroundOpacity) !== null && _c !== void 0 ? _c : 0.5;
154
+ // 2. Get alignment
155
+ const positionType = style.positionType || 'alignment';
156
+ let horizontalAlign = 'center';
157
+ let verticalAlign = 'bottom';
158
+ let marginV = 20;
159
+ if (positionType === 'alignment') {
160
+ horizontalAlign = style.horizontalAlign || 'center';
161
+ verticalAlign = style.verticalAlign || 'bottom';
162
+ marginV = (_d = style.paddingY) !== null && _d !== void 0 ? _d : 20;
163
+ }
164
+ const alignment = getASSAlignment(horizontalAlign, verticalAlign);
165
+ // 3. Prepare subtitle file
166
+ let finalSubtitlePath = subtitleFile;
167
+ let tempAssFile = null;
168
+ if (isASSFile(subtitleFile)) {
169
+ // ASS file: use directly (ignore style options)
170
+ finalSubtitlePath = subtitleFile;
171
+ }
172
+ else {
173
+ // SRT file: convert to ASS with styles
174
+ const srtContent = await fs.readFile(subtitleFile, 'utf-8');
175
+ const assContent = convertSRTtoASS(srtContent, fontName, fontSize, colorToASS(fontColor, 1), colorToASS(outlineColor, 1), outlineWidth, backgroundColor, backgroundOpacity, enableBackground, alignment, marginV);
176
+ tempAssFile = (0, utils_1.getTempFile)('.ass');
177
+ await fs.writeFile(tempAssFile, assContent, 'utf-8');
178
+ finalSubtitlePath = tempAssFile;
179
+ }
180
+ // 4. Build FFmpeg command
181
+ const escapedPath = escapeSubtitlePath(finalSubtitlePath);
182
+ const subtitlesFilter = `subtitles='${escapedPath}'`;
183
+ const command = ffmpeg(video)
184
+ .videoFilters([subtitlesFilter])
185
+ .audioCodec('copy')
186
+ .save(outputPath);
187
+ try {
188
+ await (0, utils_1.runFfmpeg)(command);
189
+ return outputPath;
190
+ }
191
+ catch (error) {
192
+ await fs.remove(outputPath).catch(() => { });
193
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error adding subtitles to video. FFmpeg error: ${error.message}`, { itemIndex });
194
+ }
195
+ finally {
196
+ // Clean up temp ASS file
197
+ if (tempAssFile) {
198
+ await fs.remove(tempAssFile).catch(() => { });
199
+ }
200
+ }
201
+ }
202
+ exports.executeAddSubtitle = executeAddSubtitle;
@@ -0,0 +1,2 @@
1
+ import { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2
+ export declare function executeAddText(this: IExecuteFunctions, video: string, text: string, options: IDataObject, itemIndex: number): Promise<string>;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.executeAddText = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ const path = __importStar(require("path"));
29
+ const ffmpeg = require("fluent-ffmpeg");
30
+ const utils_1 = require("../utils");
31
+ const fs = __importStar(require("fs-extra"));
32
+ function getPositionFromAlignment(horizontalAlign, verticalAlign, paddingX, paddingY) {
33
+ let x;
34
+ let y;
35
+ // Set X position based on horizontal alignment
36
+ switch (horizontalAlign) {
37
+ case 'left':
38
+ x = `${paddingX}`;
39
+ break;
40
+ case 'right':
41
+ x = `w-text_w-${paddingX}`;
42
+ break;
43
+ case 'center':
44
+ default:
45
+ x = '(w-text_w)/2';
46
+ break;
47
+ }
48
+ // Set Y position based on vertical alignment
49
+ switch (verticalAlign) {
50
+ case 'top':
51
+ y = `${paddingY}`;
52
+ break;
53
+ case 'bottom':
54
+ y = `h-th-${paddingY}`;
55
+ break;
56
+ case 'middle':
57
+ default:
58
+ y = '(h-text_h)/2';
59
+ break;
60
+ }
61
+ return { x, y };
62
+ }
63
+ async function executeAddText(video, text, options, itemIndex) {
64
+ var _a, _b, _c, _d;
65
+ const allFonts = (0, utils_1.getAvailableFonts)();
66
+ const fontKey = options.fontKey || 'noto-sans-kr';
67
+ const font = allFonts[fontKey];
68
+ if (!font || !font.path) {
69
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font key '${fontKey}' is not valid or its file path is missing.`, { itemIndex });
70
+ }
71
+ const fontPath = font.path;
72
+ // Set default values for text options
73
+ const fontSize = options.size || 48;
74
+ const fontColor = options.color || 'white';
75
+ const startTime = options.startTime || 0;
76
+ const endTime = options.endTime || 5;
77
+ // Handle position based on position type
78
+ let positionX;
79
+ let positionY;
80
+ const positionType = options.positionType || 'alignment';
81
+ if (positionType === 'alignment') {
82
+ const horizontalAlign = options.horizontalAlign || 'center';
83
+ const verticalAlign = options.verticalAlign || 'bottom';
84
+ const paddingX = (_b = (_a = options.paddingX) !== null && _a !== void 0 ? _a : options.padding) !== null && _b !== void 0 ? _b : 20;
85
+ const paddingY = (_d = (_c = options.paddingY) !== null && _c !== void 0 ? _c : options.padding) !== null && _d !== void 0 ? _d : 20;
86
+ const position = getPositionFromAlignment(horizontalAlign, verticalAlign, paddingX, paddingY);
87
+ positionX = position.x;
88
+ positionY = position.y;
89
+ }
90
+ else {
91
+ // Custom position
92
+ positionX = options.x || '(w-text_w)/2';
93
+ positionY = options.y || 'h-th-10';
94
+ }
95
+ const outputPath = (0, utils_1.getTempFile)(path.extname(video));
96
+ const drawtext = `drawtext=fontfile=${fontPath}:text='${text.replace(/'/g, `''`)}':fontsize=${fontSize}:fontcolor=${fontColor}:x=${positionX}:y=${positionY}:enable='between(t,${startTime},${endTime})'`;
97
+ const command = ffmpeg(video).videoFilters(drawtext).audioCodec('copy').save(outputPath);
98
+ try {
99
+ await (0, utils_1.runFfmpeg)(command);
100
+ return outputPath;
101
+ }
102
+ catch (error) {
103
+ // Clean up output file if creation failed
104
+ await fs.remove(outputPath).catch(() => { });
105
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error adding text to video. FFmpeg error: ${error.message}`, { itemIndex });
106
+ }
107
+ }
108
+ exports.executeAddText = executeAddText;
@@ -0,0 +1,2 @@
1
+ import { IExecuteFunctions } from 'n8n-workflow';
2
+ export declare function executeExtractAudio(this: IExecuteFunctions, input: string, format: string, codec: string, bitrate: string, itemIndex: number): Promise<string>;
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.executeExtractAudio = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ const ffmpeg = require("fluent-ffmpeg");
29
+ const utils_1 = require("../utils");
30
+ const fs = __importStar(require("fs-extra"));
31
+ async function executeExtractAudio(input, format, codec, bitrate, itemIndex) {
32
+ const outputPath = (0, utils_1.getTempFile)(`.${format}`);
33
+ let finalCodec = codec;
34
+ // If stream copy ('copy') is selected for a format that requires a specific
35
+ // codec (like mp3), we must override it to prevent an FFmpeg error.
36
+ if (finalCodec === 'copy' && format === 'mp3') {
37
+ // Cannot stream copy a non-mp3 stream (e.g., aac) into an mp3 container.
38
+ // Default to a high-quality mp3 encoder.
39
+ finalCodec = 'libmp3lame';
40
+ }
41
+ const command = ffmpeg(input)
42
+ .output(outputPath)
43
+ .noVideo()
44
+ .audioCodec(finalCodec)
45
+ .audioBitrate(bitrate)
46
+ .outputOptions('-map', '0:a:0'); // Select first audio stream
47
+ try {
48
+ await (0, utils_1.runFfmpeg)(command);
49
+ return outputPath;
50
+ }
51
+ catch (error) {
52
+ // Clean up output file if creation failed
53
+ await fs.remove(outputPath).catch(() => { });
54
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error extracting audio. Please ensure the source video contains an audio track. FFmpeg error: ${error.message}`, { itemIndex });
55
+ }
56
+ }
57
+ exports.executeExtractAudio = executeExtractAudio;
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions } from 'n8n-workflow';
2
+ export declare function executeImageToVideo(this: IExecuteFunctions, imagePath: string, duration: number, videoSize: {
3
+ width?: number;
4
+ height?: number;
5
+ }, outputFormat: string, itemIndex: number): Promise<string>;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.executeImageToVideo = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ const utils_1 = require("../utils");
29
+ const ffmpeg = require("fluent-ffmpeg");
30
+ const fs = __importStar(require("fs-extra"));
31
+ async function executeImageToVideo(imagePath, duration, videoSize, outputFormat, itemIndex) {
32
+ const outputPath = (0, utils_1.getTempFile)(`.${outputFormat}`);
33
+ const command = ffmpeg()
34
+ .input(imagePath)
35
+ .loop(duration)
36
+ // Add a silent audio track for compatibility
37
+ .input('anullsrc')
38
+ .inputFormat('lavfi')
39
+ // Use libx264 codec for broad compatibility
40
+ .videoCodec('libx264')
41
+ // Set pixel format
42
+ .outputOptions('-pix_fmt', 'yuv420p')
43
+ .audioCodec('aac')
44
+ .outputOptions('-shortest'); // End when the shortest input ends.
45
+ if (videoSize.width && videoSize.height) {
46
+ // Many codecs require even dimensions, but we'll let the user decide.
47
+ // For more robustness, we could add validation or rounding logic here.
48
+ command.videoFilters(`scale=${videoSize.width}:${videoSize.height}`);
49
+ }
50
+ else {
51
+ // Default behavior: ensure even dimensions for compatibility
52
+ command.videoFilters('scale=trunc(iw/2)*2:trunc(ih/2)*2');
53
+ }
54
+ command.save(outputPath);
55
+ try {
56
+ await (0, utils_1.runFfmpeg)(command);
57
+ return outputPath;
58
+ }
59
+ catch (error) {
60
+ // Clean up output file if creation failed
61
+ await fs.remove(outputPath).catch(() => { });
62
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error converting image to video. Please ensure the source image is valid. FFmpeg error: ${error.message}`, { itemIndex });
63
+ }
64
+ }
65
+ exports.executeImageToVideo = executeImageToVideo;
@@ -0,0 +1,13 @@
1
+ export * from './merge';
2
+ export * from './trim';
3
+ export * from './speed';
4
+ export * from './mixAudio';
5
+ export * from './addSubtitle';
6
+ export * from './addText';
7
+ export * from './extractAudio';
8
+ export * from './separateAudio';
9
+ export * from './multiVideoTransition';
10
+ export * from './singleVideoFade';
11
+ export * from './imageToVideo';
12
+ export * from './stampImage';
13
+ export * from './overlayVideo';
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./merge"), exports);
18
+ __exportStar(require("./trim"), exports);
19
+ __exportStar(require("./speed"), exports);
20
+ __exportStar(require("./mixAudio"), exports);
21
+ __exportStar(require("./addSubtitle"), exports);
22
+ __exportStar(require("./addText"), exports);
23
+ __exportStar(require("./extractAudio"), exports);
24
+ __exportStar(require("./separateAudio"), exports);
25
+ __exportStar(require("./multiVideoTransition"), exports);
26
+ __exportStar(require("./singleVideoFade"), exports);
27
+ __exportStar(require("./imageToVideo"), exports);
28
+ __exportStar(require("./stampImage"), exports);
29
+ __exportStar(require("./overlayVideo"), exports);
@@ -0,0 +1,2 @@
1
+ import { IExecuteFunctions } from 'n8n-workflow';
2
+ export declare function executeMerge(this: IExecuteFunctions, inputs: string[], outputFormat: string, itemIndex: number): Promise<string>;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.executeMerge = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ const ffmpeg = require("fluent-ffmpeg");
29
+ const fs = __importStar(require("fs-extra"));
30
+ const utils_1 = require("../utils");
31
+ async function normalizeVideo(inputPath, refInfo) {
32
+ const normalizedPath = (0, utils_1.getTempFile)('.ts');
33
+ const mainCleanup = () => fs.remove(normalizedPath);
34
+ let silentAudioCleanup = null;
35
+ const hasAudio = await (0, utils_1.fileHasAudio)(inputPath);
36
+ const command = ffmpeg(inputPath);
37
+ if (!hasAudio) {
38
+ const duration = await (0, utils_1.getDuration)(inputPath);
39
+ const { filePath: silentAudioPath, cleanup } = await (0, utils_1.createSilentAudio)(duration);
40
+ command.addInput(silentAudioPath);
41
+ silentAudioCleanup = cleanup;
42
+ }
43
+ const targetWidth = refInfo.width;
44
+ const targetHeight = refInfo.height;
45
+ const targetSar = (refInfo.sample_aspect_ratio && refInfo.sample_aspect_ratio !== 'N/A') ? refInfo.sample_aspect_ratio : '1:1';
46
+ const targetFrameRate = refInfo.r_frame_rate || '30';
47
+ const targetPixFmt = 'yuv420p';
48
+ 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]`;
49
+ const audioFilter = hasAudio
50
+ ? `[0:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,asetpts=PTS-STARTPTS[a_out]`
51
+ : `[1:a]aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=stereo,asetpts=PTS-STARTPTS[a_out]`;
52
+ command
53
+ .complexFilter([videoFilter, audioFilter])
54
+ .outputOptions(['-map', '[v_out]', '-map', '[a_out]'])
55
+ .videoCodec('libx264')
56
+ .audioCodec('aac')
57
+ .save(normalizedPath);
58
+ await (0, utils_1.runFfmpeg)(command);
59
+ const combinedCleanup = async () => {
60
+ await mainCleanup();
61
+ if (silentAudioCleanup) {
62
+ await silentAudioCleanup();
63
+ }
64
+ };
65
+ return { normalizedPath, cleanup: combinedCleanup };
66
+ }
67
+ async function executeMerge(inputs, outputFormat, itemIndex) {
68
+ // Verify FFmpeg is available before proceeding
69
+ try {
70
+ (0, utils_1.verifyFfmpegAvailability)();
71
+ }
72
+ catch (error) {
73
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `FFmpeg is not available: ${error.message}`, { itemIndex });
74
+ }
75
+ if (inputs.length < 1) {
76
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Merge operation requires at least one source.', {
77
+ itemIndex,
78
+ });
79
+ }
80
+ const intermediateFiles = [];
81
+ let outputPath = null;
82
+ const finalCleanup = async () => {
83
+ for (const file of intermediateFiles) {
84
+ await file.cleanup();
85
+ }
86
+ };
87
+ try {
88
+ const videoInfos = await Promise.all(inputs.map(utils_1.getVideoStreamInfo));
89
+ const refInfo = videoInfos.find((info) => info !== undefined);
90
+ if (!refInfo || !refInfo.width || !refInfo.height) {
91
+ 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 });
92
+ }
93
+ // 1. Normalization Stage
94
+ for (const inputPath of inputs) {
95
+ const { normalizedPath, cleanup } = await normalizeVideo.call(this, inputPath, refInfo);
96
+ intermediateFiles.push({ path: normalizedPath, cleanup });
97
+ }
98
+ // 2. Merging Stage - Using the concat protocol for stability
99
+ outputPath = (0, utils_1.getTempFile)(`.${outputFormat}`);
100
+ const normalizedPaths = intermediateFiles.map((f) => f.path);
101
+ const concatString = `concat:${normalizedPaths.join('|')}`;
102
+ const command = ffmpeg()
103
+ .input(concatString)
104
+ .outputOptions('-c', 'copy')
105
+ .save(outputPath);
106
+ await (0, utils_1.runFfmpeg)(command);
107
+ return outputPath;
108
+ }
109
+ catch (error) {
110
+ // Clean up output file if creation failed
111
+ if (outputPath) {
112
+ await fs.remove(outputPath).catch(() => { });
113
+ }
114
+ await finalCleanup();
115
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error merging videos. Please ensure all source videos are valid. FFmpeg error: ${error.message}`, { itemIndex });
116
+ }
117
+ finally {
118
+ await finalCleanup();
119
+ }
120
+ }
121
+ exports.executeMerge = executeMerge;
@@ -0,0 +1,2 @@
1
+ import { IDataObject, IExecuteFunctions } from 'n8n-workflow';
2
+ export declare function executeMixAudio(this: IExecuteFunctions, videoPath: string, audioPath: string, videoVolume: number, audioVolume: number, matchLength: 'shortest' | 'longest' | 'first', advancedMixing: IDataObject, itemIndex: number): Promise<string>;