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

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 (55) 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 +146 -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 +306 -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
  55. package/CHANGELOG.md +0 -65
Binary file
Binary file
Binary file
@@ -0,0 +1,12 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription, INodePropertyOptions, ILoadOptionsFunctions } from 'n8n-workflow';
2
+ export declare class MediaFX implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ loadOptions: {
6
+ getFonts(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
7
+ getTransitionEffects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
8
+ getUserFonts(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]>;
9
+ };
10
+ };
11
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
12
+ }
@@ -0,0 +1,559 @@
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.MediaFX = void 0;
27
+ const n8n_workflow_1 = require("n8n-workflow");
28
+ const fs = __importStar(require("fs-extra"));
29
+ const path = __importStar(require("path"));
30
+ // import ffmpeg = require('fluent-ffmpeg');
31
+ const utils_1 = require("./utils");
32
+ const operations_1 = require("./operations");
33
+ const audio_properties_1 = require("./properties/audio.properties");
34
+ const font_properties_1 = require("./properties/font.properties");
35
+ const image_properties_1 = require("./properties/image.properties");
36
+ const resources_properties_1 = require("./properties/resources.properties");
37
+ const subtitle_properties_1 = require("./properties/subtitle.properties");
38
+ const video_properties_1 = require("./properties/video.properties");
39
+ // --- OPERATION EXECUTORS ---
40
+ // ALL OPERATION EXECUTORS MOVED TO ./operations/*
41
+ class MediaFX {
42
+ constructor() {
43
+ this.description = {
44
+ displayName: 'MediaFX',
45
+ name: 'mediaFX',
46
+ icon: 'file:mediafx.png',
47
+ group: ['transform'],
48
+ version: 1,
49
+ description: 'Process videos, audio, and media files with FFmpeg',
50
+ defaults: {
51
+ name: 'MediaFX',
52
+ },
53
+ inputs: ['main'],
54
+ outputs: ['main'],
55
+ // No credentials needed for local processing
56
+ properties: [
57
+ ...resources_properties_1.resourceSelection,
58
+ ...video_properties_1.videoProperties,
59
+ ...audio_properties_1.audioProperties,
60
+ ...subtitle_properties_1.subtitleProperties,
61
+ ...image_properties_1.imageProperties,
62
+ ...font_properties_1.fontProperties,
63
+ ],
64
+ };
65
+ this.methods = {
66
+ loadOptions: {
67
+ // Load available fonts from API
68
+ async getFonts() {
69
+ try {
70
+ const allFonts = (0, utils_1.getAvailableFonts)();
71
+ return Object.entries(allFonts).map(([key, font]) => ({
72
+ name: `${font.name || key} (${font.type})`,
73
+ value: key,
74
+ description: font.description || '',
75
+ }));
76
+ }
77
+ catch (error) {
78
+ return [];
79
+ }
80
+ },
81
+ // Load available transition effects from a static list
82
+ async getTransitionEffects() {
83
+ // Mark effects that require FFmpeg 4.3+
84
+ const effects = [
85
+ { name: 'Fade', value: 'fade', description: 'Works with all FFmpeg versions' },
86
+ { name: 'Fade Black', value: 'fadeblack', description: 'Works with all FFmpeg versions' },
87
+ { name: 'Fade White', value: 'fadewhite', description: 'Works with all FFmpeg versions' },
88
+ { name: 'Wipe Left', value: 'wipeleft', description: 'Requires FFmpeg 4.3+' },
89
+ { name: 'Wipe Right', value: 'wiperight', description: 'Requires FFmpeg 4.3+' },
90
+ { name: 'Wipe Up', value: 'wipeup', description: 'Requires FFmpeg 4.3+' },
91
+ { name: 'Wipe Down', value: 'wipedown', description: 'Requires FFmpeg 4.3+' },
92
+ { name: 'Slide Left', value: 'slideleft', description: 'Requires FFmpeg 4.3+' },
93
+ { name: 'Slide Right', value: 'slideright', description: 'Requires FFmpeg 4.3+' },
94
+ { name: 'Slide Up', value: 'slideup', description: 'Requires FFmpeg 4.3+' },
95
+ { name: 'Slide Down', value: 'slidedown', description: 'Requires FFmpeg 4.3+' },
96
+ { name: 'Circle Crop', value: 'circlecrop', description: 'Requires FFmpeg 4.3+' },
97
+ { name: 'Rect Crop', value: 'rectcrop', description: 'Requires FFmpeg 4.3+' },
98
+ { name: 'Distance', value: 'distance', description: 'Requires FFmpeg 4.3+' },
99
+ { name: 'Fade Grayscale', value: 'fadegrays', description: 'Requires FFmpeg 4.3+' },
100
+ { name: 'Radial', value: 'radial', description: 'Requires FFmpeg 4.3+' },
101
+ { name: 'Circle Open', value: 'circleopen', description: 'Requires FFmpeg 4.3+' },
102
+ { name: 'Circle Close', value: 'circleclose', description: 'Requires FFmpeg 4.3+' },
103
+ { name: 'Pixelize', value: 'pixelize', description: 'Requires FFmpeg 4.3+' },
104
+ { name: 'Dissolve', value: 'dissolve', description: 'Requires FFmpeg 4.3+' },
105
+ { name: 'Checkerboard', value: 'diagtl', description: 'Requires FFmpeg 4.3+' },
106
+ { name: 'Box-in', value: 'boxin', description: 'Requires FFmpeg 4.3+' },
107
+ { name: 'Iris', value: 'iris', description: 'Requires FFmpeg 4.3+' },
108
+ ];
109
+ return effects;
110
+ },
111
+ async getUserFonts() {
112
+ try {
113
+ const userFonts = (0, utils_1.getUserFonts)();
114
+ return Object.entries(userFonts).map(([key, font]) => ({
115
+ name: `${font.name || key} (user)`,
116
+ value: key,
117
+ description: font.description || 'User uploaded font',
118
+ }));
119
+ }
120
+ catch (error) {
121
+ // This is optional, so return empty on error
122
+ return [];
123
+ }
124
+ },
125
+ },
126
+ };
127
+ }
128
+ async execute() {
129
+ const items = this.getInputData();
130
+ const returnData = [];
131
+ // Periodically clean up old temporary files (every 10th execution)
132
+ if (Math.random() < 0.1) {
133
+ (0, utils_1.cleanupOldTempFiles)(24).catch(() => {
134
+ // Ignore cleanup errors to avoid disrupting main operation
135
+ });
136
+ }
137
+ for (let i = 0; i < items.length; i++) {
138
+ let cleanup = async () => { }; // Initialize cleanup function for each iteration
139
+ let resource = ''; // Initialize resource variable for error handling
140
+ try {
141
+ resource = this.getNodeParameter('resource', i);
142
+ const operation = this.getNodeParameter('operation', i);
143
+ let resultData = null;
144
+ let outputPath = null;
145
+ // ===================================
146
+ // FONT RESOURCE OPERATIONS
147
+ // ===================================
148
+ if (resource === 'font') {
149
+ switch (operation) {
150
+ case 'list': {
151
+ const filterOptions = this.getNodeParameter('filterOptions', i, {});
152
+ const fontTypeFilter = filterOptions.fontType || 'all';
153
+ const allFonts = (0, utils_1.getAvailableFonts)();
154
+ if (fontTypeFilter === 'all') {
155
+ resultData = allFonts;
156
+ }
157
+ else {
158
+ resultData = Object.fromEntries(Object.entries(allFonts).filter(([, font]) => font.type === fontTypeFilter));
159
+ }
160
+ break;
161
+ }
162
+ case 'upload': {
163
+ const fontSource = this.getNodeParameter('fontSource', i);
164
+ const fontKey = this.getNodeParameter('fontKeyUpload', i);
165
+ const fontName = this.getNodeParameter('fontName', i, '');
166
+ const description = this.getNodeParameter('description', i, '');
167
+ let buffer;
168
+ let originalname;
169
+ if (fontSource === 'binary') {
170
+ const binaryProperty = this.getNodeParameter('binaryProperty', i);
171
+ const binaryData = items[i].binary;
172
+ if (!binaryData || !binaryData[binaryProperty]) {
173
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `No binary data found in property '${binaryProperty}'`, { itemIndex: i });
174
+ }
175
+ buffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
176
+ originalname = binaryData[binaryProperty].fileName || 'font.ttf';
177
+ }
178
+ else {
179
+ // filepath
180
+ const filePath = this.getNodeParameter('filePath', i);
181
+ if (!fs.existsSync(filePath)) {
182
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Font file not found at path: ${filePath}`, { itemIndex: i });
183
+ }
184
+ buffer = fs.readFileSync(filePath);
185
+ originalname = path.basename(filePath);
186
+ }
187
+ resultData = (0, utils_1.saveUserFont)(fontKey, fontName, description, originalname, buffer);
188
+ break;
189
+ }
190
+ case 'delete': {
191
+ const fontKey = this.getNodeParameter('fontKey', i);
192
+ (0, utils_1.deleteUserFont)(fontKey);
193
+ resultData = { message: `Font '${fontKey}' deleted successfully.` };
194
+ break;
195
+ }
196
+ }
197
+ }
198
+ // ===================================
199
+ // MEDIA RESOURCE OPERATIONS
200
+ // ===================================
201
+ else {
202
+ switch (operation) {
203
+ // Video Operations
204
+ case 'merge': {
205
+ const sourcesParam = this.getNodeParameter('videoSources', i, {});
206
+ const sourcesConfig = sourcesParam.sources || [];
207
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, sourcesConfig);
208
+ cleanup = c;
209
+ const mergeOutputFormat = this.getNodeParameter('videoOutputFormat', i);
210
+ outputPath = await operations_1.executeMerge.call(this, paths, mergeOutputFormat, i);
211
+ break;
212
+ }
213
+ case 'trim': {
214
+ const sourceParam = this.getNodeParameter('source', i, {});
215
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
216
+ cleanup = c;
217
+ const startTime = this.getNodeParameter('startTime', i, 0);
218
+ const endTime = this.getNodeParameter('endTime', i, 10);
219
+ const outputFormat = this.getNodeParameter('videoOutputFormat', i, 'mp4');
220
+ outputPath = await operations_1.executeTrim.call(this, paths[0], startTime, endTime, outputFormat, i);
221
+ break;
222
+ }
223
+ case 'speed': {
224
+ const sourceParam = this.getNodeParameter('speedSource', i, {});
225
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
226
+ cleanup = c;
227
+ const speed = this.getNodeParameter('speed', i, 1);
228
+ const adjustAudio = this.getNodeParameter('adjustAudio', i, true);
229
+ const maintainPitch = this.getNodeParameter('maintainPitch', i, false);
230
+ const outputFormat = this.getNodeParameter('speedOutputFormat', i, 'mp4');
231
+ outputPath = await operations_1.executeSpeed.call(this, paths[0], speed, adjustAudio, maintainPitch, outputFormat, i);
232
+ break;
233
+ }
234
+ // Audio Operations
235
+ case 'extract': {
236
+ const sourceParam = this.getNodeParameter('source', i, {});
237
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam]);
238
+ cleanup = c;
239
+ const extractFormat = this.getNodeParameter('audioOutputFormat', i);
240
+ const advancedOptions = this.getNodeParameter('advancedOptions', i, {});
241
+ const extractCodec = advancedOptions.audioCodec || 'copy';
242
+ const extractBitrate = advancedOptions.audioBitrate || '192k';
243
+ outputPath = await operations_1.executeExtractAudio.call(this, paths[0], extractFormat, extractCodec, extractBitrate, i);
244
+ break;
245
+ }
246
+ case 'mixAudio': {
247
+ // Construct source objects from flattened properties
248
+ const videoSourceType = this.getNodeParameter('mixVideoSourceType', i, 'url');
249
+ const videoSourceParam = {
250
+ sourceType: videoSourceType,
251
+ value: videoSourceType === 'url'
252
+ ? this.getNodeParameter('mixVideoSourceUrl', i, '')
253
+ : '',
254
+ binaryProperty: videoSourceType === 'binary'
255
+ ? this.getNodeParameter('mixVideoSourceBinary', i, 'data')
256
+ : '',
257
+ };
258
+ const audioSourceType = this.getNodeParameter('mixAudioSourceType', i, 'url');
259
+ const audioSourceParam = {
260
+ sourceType: audioSourceType,
261
+ value: audioSourceType === 'url'
262
+ ? this.getNodeParameter('mixAudioSourceUrl', i, '')
263
+ : '',
264
+ binaryProperty: audioSourceType === 'binary'
265
+ ? this.getNodeParameter('mixAudioSourceBinary', i, 'data')
266
+ : '',
267
+ };
268
+ const { paths: videoPaths, cleanup: videoCleanup } = await (0, utils_1.resolveInputs)(this, i, [
269
+ videoSourceParam,
270
+ ]);
271
+ const { paths: audioPaths, cleanup: audioCleanup } = await (0, utils_1.resolveInputs)(this, i, [
272
+ audioSourceParam,
273
+ ]);
274
+ cleanup = async () => {
275
+ await videoCleanup();
276
+ await audioCleanup();
277
+ };
278
+ const videoVol = this.getNodeParameter('videoVolume', i, 1.0);
279
+ const audioVol = this.getNodeParameter('audioVolume', i, 1.0);
280
+ const matchLength = this.getNodeParameter('matchLength', i, 'shortest');
281
+ // Get advanced mixing parameters directly
282
+ const enablePartialMix = this.getNodeParameter('enablePartialMix', i, false);
283
+ const advancedMixing = {
284
+ enablePartialMix,
285
+ startTime: enablePartialMix ? this.getNodeParameter('startTime', i, 0) : 0,
286
+ duration: enablePartialMix ? this.getNodeParameter('duration', i, undefined) : undefined,
287
+ loop: enablePartialMix ? this.getNodeParameter('loop', i, false) : false,
288
+ enableFadeIn: this.getNodeParameter('enableFadeIn', i, false),
289
+ fadeInDuration: this.getNodeParameter('fadeInDuration', i, 1),
290
+ enableFadeOut: this.getNodeParameter('enableFadeOut', i, false),
291
+ fadeOutDuration: this.getNodeParameter('fadeOutDuration', i, 1),
292
+ };
293
+ outputPath = await operations_1.executeMixAudio.call(this, videoPaths[0], audioPaths[0], videoVol, audioVol, matchLength, advancedMixing, i);
294
+ break;
295
+ }
296
+ // Subtitle Operations
297
+ case 'addSubtitle': {
298
+ const videoSourceParam = this.getNodeParameter('source', i);
299
+ const { paths: videoPaths, cleanup: videoCleanup } = await (0, utils_1.resolveInputs)(this, i, [videoSourceParam.source]);
300
+ const subFileParam = this.getNodeParameter('subtitleFileSource', i);
301
+ const { paths: subFilePaths, cleanup: subFileCleanup } = await (0, utils_1.resolveInputs)(this, i, [subFileParam.source]);
302
+ cleanup = async () => {
303
+ await videoCleanup();
304
+ await subFileCleanup();
305
+ };
306
+ // Collect style options from individual parameters
307
+ const style = {
308
+ fontKey: this.getNodeParameter('fontKey', i, 'noto-sans-kr'),
309
+ size: this.getNodeParameter('size', i, 48),
310
+ color: this.getNodeParameter('color', i, 'white'),
311
+ outlineWidth: this.getNodeParameter('outlineWidth', i, 1),
312
+ positionType: this.getNodeParameter('positionType', i, 'alignment'),
313
+ horizontalAlign: this.getNodeParameter('horizontalAlign', i, 'center'),
314
+ verticalAlign: this.getNodeParameter('verticalAlign', i, 'bottom'),
315
+ paddingX: this.getNodeParameter('paddingX', i, 20),
316
+ paddingY: this.getNodeParameter('paddingY', i, 20),
317
+ x: this.getNodeParameter('x', i, '(w-text_w)/2'),
318
+ y: this.getNodeParameter('y', i, 'h-th-50'),
319
+ };
320
+ outputPath = await operations_1.executeAddSubtitle.call(this, videoPaths[0], subFilePaths[0], style, i);
321
+ break;
322
+ }
323
+ case 'addText': {
324
+ const sourceParam = this.getNodeParameter('source', i);
325
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
326
+ cleanup = c;
327
+ // Get text content and timing
328
+ const text = this.getNodeParameter('text', i, 'Hello, n8n!');
329
+ const startTime = this.getNodeParameter('startTime', i, 0);
330
+ const endTime = this.getNodeParameter('endTime', i, 5);
331
+ // Collect style options from individual parameters
332
+ const textOptions = {
333
+ fontKey: this.getNodeParameter('fontKey', i, 'noto-sans-kr'),
334
+ size: this.getNodeParameter('size', i, 48),
335
+ color: this.getNodeParameter('color', i, 'white'),
336
+ outlineWidth: this.getNodeParameter('outlineWidth', i, 1),
337
+ positionType: this.getNodeParameter('positionType', i, 'alignment'),
338
+ horizontalAlign: this.getNodeParameter('horizontalAlign', i, 'center'),
339
+ verticalAlign: this.getNodeParameter('verticalAlign', i, 'bottom'),
340
+ paddingX: this.getNodeParameter('paddingX', i, 20),
341
+ paddingY: this.getNodeParameter('paddingY', i, 20),
342
+ x: this.getNodeParameter('x', i, '(w-text_w)/2'),
343
+ y: this.getNodeParameter('y', i, 'h-th-10'),
344
+ startTime,
345
+ endTime,
346
+ };
347
+ outputPath = await operations_1.executeAddText.call(this, paths[0], text, textOptions, i);
348
+ break;
349
+ }
350
+ case 'multiTransition': {
351
+ const sourcesParam = this.getNodeParameter('transitionSources', i, {});
352
+ const sourcesConfig = sourcesParam.sources || [];
353
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, sourcesConfig);
354
+ cleanup = c;
355
+ const transitionEffect = this.getNodeParameter('transitionEffect', i);
356
+ const transitionDuration = this.getNodeParameter('transitionDuration', i);
357
+ const transitionOutputFormat = this.getNodeParameter('transitionOutputFormat', i, 'mp4');
358
+ outputPath = await operations_1.executeMultiVideoTransition.call(this, paths, transitionEffect, transitionDuration, transitionOutputFormat, i);
359
+ break;
360
+ }
361
+ case 'singleFade': {
362
+ const sourceParam = this.getNodeParameter('fadeSource', i);
363
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
364
+ cleanup = c;
365
+ const fadeEffect = this.getNodeParameter('fadeEffect', i);
366
+ const fadeStartTime = this.getNodeParameter('fadeStartTime', i);
367
+ const fadeDuration = this.getNodeParameter('fadeDuration', i);
368
+ const outputFormat = this.getNodeParameter('transitionOutputFormat', i, 'mp4');
369
+ outputPath = await operations_1.executeSingleVideoFade.call(this, paths[0], fadeEffect, fadeStartTime, fadeDuration, outputFormat, i);
370
+ break;
371
+ }
372
+ case 'imageToVideo': {
373
+ const sourceParam = this.getNodeParameter('sourceImage', i, {});
374
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
375
+ cleanup = c;
376
+ const duration = this.getNodeParameter('duration', i, 10);
377
+ const videoSize = this.getNodeParameter('videoSize', i);
378
+ const outputFormat = this.getNodeParameter('imageOutputFormat', i, 'mp4');
379
+ outputPath = await operations_1.executeImageToVideo.call(this, paths[0], duration, videoSize, outputFormat, i);
380
+ break;
381
+ }
382
+ case 'stampImage': {
383
+ const sourceVideo = this.getNodeParameter('sourceVideo.source', i);
384
+ const stampImage = this.getNodeParameter('stampImage.source', i);
385
+ // Get individual stamp options
386
+ const stampOptions = {
387
+ width: this.getNodeParameter('width', i, 150),
388
+ height: this.getNodeParameter('height', i, -1),
389
+ x: this.getNodeParameter('x', i, '10'),
390
+ y: this.getNodeParameter('y', i, '10'),
391
+ rotation: this.getNodeParameter('rotation', i, 0),
392
+ enableTimeControl: this.getNodeParameter('enableTimeControl', i, false),
393
+ startTime: this.getNodeParameter('startTime', i, 0),
394
+ endTime: this.getNodeParameter('endTime', i, 5),
395
+ opacity: this.getNodeParameter('opacity', i, 1.0),
396
+ };
397
+ const { paths: videoPaths, cleanup: videoCleanup } = await (0, utils_1.resolveInputs)(this, i, [
398
+ sourceVideo,
399
+ ]);
400
+ const { paths: imagePaths, cleanup: imageCleanup } = await (0, utils_1.resolveInputs)(this, i, [
401
+ stampImage,
402
+ ]);
403
+ cleanup = async () => {
404
+ await videoCleanup();
405
+ await imageCleanup();
406
+ };
407
+ outputPath = await operations_1.executeStampImage.call(this, videoPaths[0], imagePaths[0], stampOptions, i);
408
+ break;
409
+ }
410
+ case 'separateAudio': {
411
+ const sourceParam = this.getNodeParameter('separateSource', i, {});
412
+ if (!sourceParam.source) {
413
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Video source is required. Please add a video source.', { itemIndex: i });
414
+ }
415
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
416
+ cleanup = c;
417
+ const videoFormat = this.getNodeParameter('separateVideoFormat', i, 'mp4');
418
+ const audioFormat = this.getNodeParameter('separateAudioFormat', i, 'mp3');
419
+ const audioCodec = this.getNodeParameter('separateAudioCodec', i, 'copy');
420
+ const audioBitrate = this.getNodeParameter('separateAudioBitrate', i, '192k');
421
+ const result = await operations_1.executeSeparateAudio.call(this, paths[0], videoFormat, audioFormat, audioCodec, audioBitrate, i);
422
+ // Get output field names
423
+ const videoFieldName = this.getNodeParameter('separateVideoFieldName', i, 'video');
424
+ const audioFieldName = this.getNodeParameter('separateAudioFieldName', i, 'audio');
425
+ // Read and prepare both binary outputs
426
+ const videoBinaryData = await fs.readFile(result.videoPath);
427
+ const audioBinaryData = await fs.readFile(result.audioPath);
428
+ const videoFileName = path.basename(result.videoPath);
429
+ const audioFileName = path.basename(result.audioPath);
430
+ const videoBinary = await this.helpers.prepareBinaryData(videoBinaryData, videoFileName);
431
+ const audioBinary = await this.helpers.prepareBinaryData(audioBinaryData, audioFileName);
432
+ // Clean up temp files
433
+ await fs.remove(result.videoPath);
434
+ await fs.remove(result.audioPath);
435
+ // Push result with both binary outputs
436
+ returnData.push({
437
+ json: {
438
+ success: true,
439
+ operation: 'separateAudio',
440
+ videoFormat,
441
+ audioFormat,
442
+ },
443
+ binary: {
444
+ [videoFieldName]: videoBinary,
445
+ [audioFieldName]: audioBinary,
446
+ },
447
+ pairedItem: { item: i },
448
+ });
449
+ // Skip normal output processing for this operation
450
+ await cleanup();
451
+ continue;
452
+ }
453
+ case 'overlayVideo': {
454
+ const mainSourceParam = this.getNodeParameter('overlayMainSource', i, {});
455
+ const overlaySourceParam = this.getNodeParameter('overlaySource', i, {});
456
+ if (!mainSourceParam.source) {
457
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Main video source is required. Please add a main video source.', { itemIndex: i });
458
+ }
459
+ if (!overlaySourceParam.source) {
460
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Overlay video source is required. Please add an overlay video source.', { itemIndex: i });
461
+ }
462
+ const { paths: mainPaths, cleanup: mainCleanup } = await (0, utils_1.resolveInputs)(this, i, [mainSourceParam.source]);
463
+ const { paths: overlayPaths, cleanup: overlayCleanup } = await (0, utils_1.resolveInputs)(this, i, [overlaySourceParam.source]);
464
+ cleanup = async () => {
465
+ await mainCleanup();
466
+ await overlayCleanup();
467
+ };
468
+ // Get overlay options
469
+ const sizeMode = this.getNodeParameter('overlaySizeMode', i, 'percentage');
470
+ const positionMode = this.getNodeParameter('overlayPositionMode', i, 'alignment');
471
+ const overlayOptions = {
472
+ // Position options
473
+ positionMode,
474
+ // Alignment mode options
475
+ horizontalAlign: positionMode === 'alignment' ? this.getNodeParameter('overlayHorizontalAlign', i, 'center') : undefined,
476
+ verticalAlign: positionMode === 'alignment' ? this.getNodeParameter('overlayVerticalAlign', i, 'middle') : undefined,
477
+ paddingX: positionMode === 'alignment' ? this.getNodeParameter('overlayPaddingX', i, 0) : undefined,
478
+ paddingY: positionMode === 'alignment' ? this.getNodeParameter('overlayPaddingY', i, 0) : undefined,
479
+ // Coordinates mode options
480
+ x: positionMode === 'coordinates' ? this.getNodeParameter('overlayX', i, '0') : undefined,
481
+ y: positionMode === 'coordinates' ? this.getNodeParameter('overlayY', i, '0') : undefined,
482
+ // Size options
483
+ sizeMode,
484
+ widthPercent: sizeMode === 'percentage' ? this.getNodeParameter('overlayWidthPercent', i, 50) : undefined,
485
+ heightMode: sizeMode === 'percentage' ? this.getNodeParameter('overlayHeightMode', i, 'auto') : undefined,
486
+ heightPercent: sizeMode === 'percentage' ? this.getNodeParameter('overlayHeightPercent', i, 50) : undefined,
487
+ widthPixels: sizeMode === 'pixels' ? this.getNodeParameter('overlayWidthPixels', i, 640) : undefined,
488
+ heightPixels: sizeMode === 'pixels' ? this.getNodeParameter('overlayHeightPixels', i, -1) : undefined,
489
+ opacity: this.getNodeParameter('overlayOpacity', i, 1),
490
+ enableTimeControl: this.getNodeParameter('overlayEnableTimeControl', i, false),
491
+ startTime: this.getNodeParameter('overlayStartTime', i, 0),
492
+ endTime: this.getNodeParameter('overlayEndTime', i, 0),
493
+ audioHandling: this.getNodeParameter('overlayAudioHandling', i, 'main'),
494
+ mainVolume: this.getNodeParameter('overlayMainVolume', i, 1),
495
+ overlayVolume: this.getNodeParameter('overlayOverlayVolume', i, 0.5),
496
+ outputFormat: this.getNodeParameter('overlayOutputFormat', i, 'mp4'),
497
+ };
498
+ outputPath = await operations_1.executeOverlayVideo.call(this, mainPaths[0], overlayPaths[0], overlayOptions, i);
499
+ break;
500
+ }
501
+ }
502
+ // Always cleanup after operations
503
+ await cleanup();
504
+ }
505
+ // ===================================
506
+ // FINAL OUTPUT PROCESSING
507
+ // ===================================
508
+ if (outputPath) {
509
+ // Operation resulted in a file to be returned
510
+ const binaryData = await fs.readFile(outputPath);
511
+ const fileName = path.basename(outputPath);
512
+ const binary = await this.helpers.prepareBinaryData(binaryData, fileName);
513
+ // Get the output field name from parameters (default to 'data')
514
+ const outputFieldName = this.getNodeParameter('outputFieldName', i, 'data');
515
+ await fs.remove(outputPath); // Clean up temp file
516
+ returnData.push({ json: {}, binary: { [outputFieldName]: binary }, pairedItem: { item: i } });
517
+ }
518
+ else if (resultData) {
519
+ // Operation resulted in JSON data
520
+ returnData.push({
521
+ json: {
522
+ success: true,
523
+ operation: this.getNodeParameter('operation', i),
524
+ data: resultData,
525
+ },
526
+ pairedItem: { item: i },
527
+ });
528
+ }
529
+ else if (resource !== 'font') {
530
+ // This case handles non-font operations that might not produce output
531
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Operation "${operation}" on resource "${resource}" did not produce an output.`, { itemIndex: i });
532
+ }
533
+ }
534
+ catch (error) {
535
+ // Ensure cleanup is called even if an error occurs
536
+ // Note: cleanup is only available for media operations, not font operations
537
+ if (resource !== 'font' && typeof cleanup === 'function') {
538
+ await cleanup().catch(() => {
539
+ // Ignore cleanup errors to avoid masking the original error
540
+ });
541
+ }
542
+ if (this.continueOnFail()) {
543
+ returnData.push({
544
+ json: {
545
+ error: error instanceof Error ? error.message : String(error),
546
+ operation: this.getNodeParameter('operation', i),
547
+ success: false,
548
+ },
549
+ pairedItem: { item: i },
550
+ });
551
+ continue;
552
+ }
553
+ throw error;
554
+ }
555
+ }
556
+ return [returnData];
557
+ }
558
+ }
559
+ exports.MediaFX = MediaFX;
Binary file
@@ -0,0 +1,2 @@
1
+ import { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2
+ export declare function executeAddSubtitle(this: IExecuteFunctions, video: string, subtitleFile: string, style: IDataObject, itemIndex: number): Promise<string>;