@ferchy/n8n-nodes-aimc-toolkit 0.1.4
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.
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/dist/nodes/AimcCode/AimcCode.node.d.ts +5 -0
- package/dist/nodes/AimcCode/AimcCode.node.js +502 -0
- package/dist/nodes/AimcCode/aimc-code.svg +31 -0
- package/dist/nodes/AimcMedia/AimcMedia.node.d.ts +5 -0
- package/dist/nodes/AimcMedia/AimcMedia.node.js +587 -0
- package/dist/nodes/AimcMedia/aimc-media.svg +23 -0
- package/index.js +6 -0
- package/package.json +98 -0
|
@@ -0,0 +1,587 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.AimcMedia = void 0;
|
|
40
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
45
|
+
let ffmpegConfigured = false;
|
|
46
|
+
let ffmpegChecked = null;
|
|
47
|
+
function resolveOptional(moduleName) {
|
|
48
|
+
try {
|
|
49
|
+
const resolved = require(moduleName);
|
|
50
|
+
if (typeof resolved === 'string') {
|
|
51
|
+
return resolved;
|
|
52
|
+
}
|
|
53
|
+
if (resolved && typeof resolved.path === 'string') {
|
|
54
|
+
return resolved.path;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
function configureFfmpeg() {
|
|
63
|
+
if (ffmpegConfigured) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const envFfmpeg = process.env.FFMPEG_PATH;
|
|
67
|
+
const envFfprobe = process.env.FFPROBE_PATH;
|
|
68
|
+
const staticFfmpeg = resolveOptional('ffmpeg-static');
|
|
69
|
+
const staticFfprobe = resolveOptional('ffprobe-static');
|
|
70
|
+
if (envFfmpeg) {
|
|
71
|
+
fluent_ffmpeg_1.default.setFfmpegPath(envFfmpeg);
|
|
72
|
+
}
|
|
73
|
+
else if (staticFfmpeg) {
|
|
74
|
+
fluent_ffmpeg_1.default.setFfmpegPath(staticFfmpeg);
|
|
75
|
+
}
|
|
76
|
+
if (envFfprobe) {
|
|
77
|
+
fluent_ffmpeg_1.default.setFfprobePath(envFfprobe);
|
|
78
|
+
}
|
|
79
|
+
else if (staticFfprobe) {
|
|
80
|
+
fluent_ffmpeg_1.default.setFfprobePath(staticFfprobe);
|
|
81
|
+
}
|
|
82
|
+
ffmpegConfigured = true;
|
|
83
|
+
}
|
|
84
|
+
async function ensureFfmpegAvailable() {
|
|
85
|
+
if (ffmpegChecked) {
|
|
86
|
+
if (!ffmpegChecked.ok) {
|
|
87
|
+
throw new Error(ffmpegChecked.message || 'FFmpeg not available');
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await new Promise((resolve, reject) => {
|
|
93
|
+
fluent_ffmpeg_1.default.getAvailableFormats((err) => {
|
|
94
|
+
if (err) {
|
|
95
|
+
reject(err);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
ffmpegChecked = { ok: true };
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error && error.message
|
|
105
|
+
? error.message
|
|
106
|
+
: 'FFmpeg not available';
|
|
107
|
+
ffmpegChecked = { ok: false, message };
|
|
108
|
+
throw new Error(message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function createTempDir() {
|
|
112
|
+
return fs.promises.mkdtemp(path.join(os.tmpdir(), 'aimc-media-'));
|
|
113
|
+
}
|
|
114
|
+
async function writeTempFile(dir, fileName, data) {
|
|
115
|
+
const filePath = path.join(dir, fileName);
|
|
116
|
+
await fs.promises.writeFile(filePath, data);
|
|
117
|
+
return filePath;
|
|
118
|
+
}
|
|
119
|
+
function parseExtraArgs(raw) {
|
|
120
|
+
if (!raw) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
return raw
|
|
124
|
+
.split('\n')
|
|
125
|
+
.map((entry) => entry.trim())
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
}
|
|
128
|
+
function ensureFileExists(filePath) {
|
|
129
|
+
if (!fs.existsSync(filePath)) {
|
|
130
|
+
throw new Error(`File does not exist: ${filePath}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
class AimcMedia {
|
|
134
|
+
constructor() {
|
|
135
|
+
this.description = {
|
|
136
|
+
displayName: 'AIMC Media',
|
|
137
|
+
name: 'aimcMedia',
|
|
138
|
+
icon: 'file:aimc-media.svg',
|
|
139
|
+
group: ['transform'],
|
|
140
|
+
version: 1,
|
|
141
|
+
description: 'Media operations using FFmpeg.',
|
|
142
|
+
defaults: {
|
|
143
|
+
name: 'AIMC Media',
|
|
144
|
+
},
|
|
145
|
+
inputs: ['main'],
|
|
146
|
+
outputs: ['main'],
|
|
147
|
+
properties: [
|
|
148
|
+
{
|
|
149
|
+
displayName: 'Operation',
|
|
150
|
+
name: 'operation',
|
|
151
|
+
type: 'options',
|
|
152
|
+
options: [
|
|
153
|
+
{ name: 'Convert / Transcode', value: 'convert' },
|
|
154
|
+
{ name: 'Compress', value: 'compress' },
|
|
155
|
+
{ name: 'Extract Audio', value: 'extractAudio' },
|
|
156
|
+
{ name: 'Merge Video + Audio', value: 'merge' },
|
|
157
|
+
{ name: 'Metadata', value: 'metadata' },
|
|
158
|
+
],
|
|
159
|
+
default: 'convert',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
displayName: 'Input Mode',
|
|
163
|
+
name: 'inputMode',
|
|
164
|
+
type: 'options',
|
|
165
|
+
options: [
|
|
166
|
+
{ name: 'Binary', value: 'binary' },
|
|
167
|
+
{ name: 'File Path', value: 'filePath' },
|
|
168
|
+
],
|
|
169
|
+
default: 'binary',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
displayName: 'Input Binary Property',
|
|
173
|
+
name: 'binaryProperty',
|
|
174
|
+
type: 'string',
|
|
175
|
+
default: 'data',
|
|
176
|
+
displayOptions: {
|
|
177
|
+
show: {
|
|
178
|
+
inputMode: ['binary'],
|
|
179
|
+
operation: ['convert', 'compress', 'extractAudio', 'metadata'],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
displayName: 'Input File Path',
|
|
185
|
+
name: 'inputFilePath',
|
|
186
|
+
type: 'string',
|
|
187
|
+
default: '',
|
|
188
|
+
displayOptions: {
|
|
189
|
+
show: {
|
|
190
|
+
inputMode: ['filePath'],
|
|
191
|
+
operation: ['convert', 'compress', 'extractAudio', 'metadata'],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
placeholder: '/path/to/input.mp4',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
displayName: 'Video Binary Property',
|
|
198
|
+
name: 'videoBinaryProperty',
|
|
199
|
+
type: 'string',
|
|
200
|
+
default: 'video',
|
|
201
|
+
displayOptions: {
|
|
202
|
+
show: {
|
|
203
|
+
inputMode: ['binary'],
|
|
204
|
+
operation: ['merge'],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
displayName: 'Audio Binary Property',
|
|
210
|
+
name: 'audioBinaryProperty',
|
|
211
|
+
type: 'string',
|
|
212
|
+
default: 'audio',
|
|
213
|
+
displayOptions: {
|
|
214
|
+
show: {
|
|
215
|
+
inputMode: ['binary'],
|
|
216
|
+
operation: ['merge'],
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
displayName: 'Video File Path',
|
|
222
|
+
name: 'videoFilePath',
|
|
223
|
+
type: 'string',
|
|
224
|
+
default: '',
|
|
225
|
+
displayOptions: {
|
|
226
|
+
show: {
|
|
227
|
+
inputMode: ['filePath'],
|
|
228
|
+
operation: ['merge'],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
placeholder: '/path/to/video.mp4',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
displayName: 'Audio File Path',
|
|
235
|
+
name: 'audioFilePath',
|
|
236
|
+
type: 'string',
|
|
237
|
+
default: '',
|
|
238
|
+
displayOptions: {
|
|
239
|
+
show: {
|
|
240
|
+
inputMode: ['filePath'],
|
|
241
|
+
operation: ['merge'],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
placeholder: '/path/to/audio.mp3',
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
displayName: 'Output Mode',
|
|
248
|
+
name: 'outputMode',
|
|
249
|
+
type: 'options',
|
|
250
|
+
options: [
|
|
251
|
+
{ name: 'Binary', value: 'binary' },
|
|
252
|
+
{ name: 'File Path', value: 'filePath' },
|
|
253
|
+
],
|
|
254
|
+
default: 'binary',
|
|
255
|
+
displayOptions: {
|
|
256
|
+
show: {
|
|
257
|
+
operation: ['convert', 'compress', 'extractAudio', 'merge'],
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
displayName: 'Output Binary Property',
|
|
263
|
+
name: 'outputBinaryProperty',
|
|
264
|
+
type: 'string',
|
|
265
|
+
default: 'data',
|
|
266
|
+
displayOptions: {
|
|
267
|
+
show: {
|
|
268
|
+
outputMode: ['binary'],
|
|
269
|
+
operation: ['convert', 'compress', 'extractAudio', 'merge'],
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
displayName: 'Output File Path',
|
|
275
|
+
name: 'outputFilePath',
|
|
276
|
+
type: 'string',
|
|
277
|
+
default: '',
|
|
278
|
+
placeholder: '/path/to/output.mp4',
|
|
279
|
+
displayOptions: {
|
|
280
|
+
show: {
|
|
281
|
+
outputMode: ['filePath'],
|
|
282
|
+
operation: ['convert', 'compress', 'extractAudio', 'merge'],
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
displayName: 'Output Format',
|
|
288
|
+
name: 'outputFormat',
|
|
289
|
+
type: 'options',
|
|
290
|
+
options: [
|
|
291
|
+
{ name: 'MP4', value: 'mp4' },
|
|
292
|
+
{ name: 'MOV', value: 'mov' },
|
|
293
|
+
{ name: 'WebM', value: 'webm' },
|
|
294
|
+
{ name: 'MP3', value: 'mp3' },
|
|
295
|
+
{ name: 'WAV', value: 'wav' },
|
|
296
|
+
{ name: 'AAC', value: 'aac' },
|
|
297
|
+
{ name: 'M4A', value: 'm4a' },
|
|
298
|
+
{ name: 'FLAC', value: 'flac' },
|
|
299
|
+
{ name: 'OGG', value: 'ogg' },
|
|
300
|
+
{ name: 'Custom', value: 'custom' },
|
|
301
|
+
],
|
|
302
|
+
default: 'mp4',
|
|
303
|
+
displayOptions: {
|
|
304
|
+
show: {
|
|
305
|
+
operation: ['convert', 'compress', 'extractAudio', 'merge'],
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
displayName: 'Custom Output Format',
|
|
311
|
+
name: 'customOutputFormat',
|
|
312
|
+
type: 'string',
|
|
313
|
+
default: '',
|
|
314
|
+
placeholder: 'mkv',
|
|
315
|
+
displayOptions: {
|
|
316
|
+
show: {
|
|
317
|
+
outputFormat: ['custom'],
|
|
318
|
+
operation: ['convert', 'compress', 'extractAudio', 'merge'],
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
displayName: 'Video Codec',
|
|
324
|
+
name: 'videoCodec',
|
|
325
|
+
type: 'string',
|
|
326
|
+
default: '',
|
|
327
|
+
placeholder: 'libx264',
|
|
328
|
+
displayOptions: {
|
|
329
|
+
show: {
|
|
330
|
+
operation: ['convert'],
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
displayName: 'Audio Codec',
|
|
336
|
+
name: 'audioCodec',
|
|
337
|
+
type: 'string',
|
|
338
|
+
default: '',
|
|
339
|
+
placeholder: 'aac',
|
|
340
|
+
displayOptions: {
|
|
341
|
+
show: {
|
|
342
|
+
operation: ['convert'],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
displayName: 'CRF',
|
|
348
|
+
name: 'videoCrf',
|
|
349
|
+
type: 'number',
|
|
350
|
+
default: 23,
|
|
351
|
+
typeOptions: {
|
|
352
|
+
minValue: 0,
|
|
353
|
+
maxValue: 51,
|
|
354
|
+
},
|
|
355
|
+
displayOptions: {
|
|
356
|
+
show: {
|
|
357
|
+
operation: ['compress'],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
displayName: 'Preset',
|
|
363
|
+
name: 'videoPreset',
|
|
364
|
+
type: 'options',
|
|
365
|
+
options: [
|
|
366
|
+
{ name: 'Ultrafast', value: 'ultrafast' },
|
|
367
|
+
{ name: 'Superfast', value: 'superfast' },
|
|
368
|
+
{ name: 'Veryfast', value: 'veryfast' },
|
|
369
|
+
{ name: 'Faster', value: 'faster' },
|
|
370
|
+
{ name: 'Fast', value: 'fast' },
|
|
371
|
+
{ name: 'Medium', value: 'medium' },
|
|
372
|
+
{ name: 'Slow', value: 'slow' },
|
|
373
|
+
{ name: 'Slower', value: 'slower' },
|
|
374
|
+
{ name: 'Veryslow', value: 'veryslow' },
|
|
375
|
+
],
|
|
376
|
+
default: 'medium',
|
|
377
|
+
displayOptions: {
|
|
378
|
+
show: {
|
|
379
|
+
operation: ['compress'],
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
displayName: 'Audio Bitrate',
|
|
385
|
+
name: 'audioBitrate',
|
|
386
|
+
type: 'string',
|
|
387
|
+
default: '128k',
|
|
388
|
+
placeholder: '128k',
|
|
389
|
+
displayOptions: {
|
|
390
|
+
show: {
|
|
391
|
+
operation: ['compress'],
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
displayName: 'Additional Output Options',
|
|
397
|
+
name: 'additionalOutputOptions',
|
|
398
|
+
type: 'string',
|
|
399
|
+
default: '',
|
|
400
|
+
description: 'One option per line, e.g. -vf scale=1280:-2',
|
|
401
|
+
typeOptions: {
|
|
402
|
+
rows: 4,
|
|
403
|
+
},
|
|
404
|
+
displayOptions: {
|
|
405
|
+
show: {
|
|
406
|
+
operation: ['convert', 'compress', 'extractAudio', 'merge'],
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
async execute() {
|
|
414
|
+
configureFfmpeg();
|
|
415
|
+
try {
|
|
416
|
+
await ensureFfmpegAvailable();
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
const message = error instanceof Error ? error.message : 'FFmpeg not available';
|
|
420
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `FFmpeg not found. Install FFmpeg or set FFMPEG_PATH/FFPROBE_PATH. ${message}`);
|
|
421
|
+
}
|
|
422
|
+
const items = this.getInputData();
|
|
423
|
+
const results = [];
|
|
424
|
+
for (let index = 0; index < items.length; index++) {
|
|
425
|
+
const item = items[index];
|
|
426
|
+
const operation = this.getNodeParameter('operation', index);
|
|
427
|
+
const inputMode = this.getNodeParameter('inputMode', index);
|
|
428
|
+
const outputMode = this.getNodeParameter('outputMode', index, 'binary');
|
|
429
|
+
const outputFormatParam = this.getNodeParameter('outputFormat', index, 'mp4');
|
|
430
|
+
const customOutputFormat = this.getNodeParameter('customOutputFormat', index, '');
|
|
431
|
+
let outputFormat = outputFormatParam === 'custom' ? customOutputFormat : outputFormatParam;
|
|
432
|
+
if (operation === 'extractAudio' && outputFormatParam === 'mp4' && !customOutputFormat) {
|
|
433
|
+
outputFormat = 'mp3';
|
|
434
|
+
}
|
|
435
|
+
if (operation !== 'metadata' && !outputFormat) {
|
|
436
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Output format is required.');
|
|
437
|
+
}
|
|
438
|
+
const tempDir = await createTempDir();
|
|
439
|
+
try {
|
|
440
|
+
let inputPath = '';
|
|
441
|
+
let videoPath = '';
|
|
442
|
+
let audioPath = '';
|
|
443
|
+
if (inputMode === 'binary') {
|
|
444
|
+
if (operation === 'merge') {
|
|
445
|
+
const videoProp = this.getNodeParameter('videoBinaryProperty', index);
|
|
446
|
+
const audioProp = this.getNodeParameter('audioBinaryProperty', index);
|
|
447
|
+
const videoBinary = this.helpers.assertBinaryData(index, videoProp);
|
|
448
|
+
const audioBinary = this.helpers.assertBinaryData(index, audioProp);
|
|
449
|
+
const videoBuffer = await this.helpers.getBinaryDataBuffer(index, videoProp);
|
|
450
|
+
const audioBuffer = await this.helpers.getBinaryDataBuffer(index, audioProp);
|
|
451
|
+
videoPath = await writeTempFile(tempDir, `video.${videoBinary.fileExtension || 'bin'}`, videoBuffer);
|
|
452
|
+
audioPath = await writeTempFile(tempDir, `audio.${audioBinary.fileExtension || 'bin'}`, audioBuffer);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
const binaryProperty = this.getNodeParameter('binaryProperty', index);
|
|
456
|
+
const binary = this.helpers.assertBinaryData(index, binaryProperty);
|
|
457
|
+
const buffer = await this.helpers.getBinaryDataBuffer(index, binaryProperty);
|
|
458
|
+
inputPath = await writeTempFile(tempDir, `input.${binary.fileExtension || 'bin'}`, buffer);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
if (operation === 'merge') {
|
|
463
|
+
videoPath = this.getNodeParameter('videoFilePath', index);
|
|
464
|
+
audioPath = this.getNodeParameter('audioFilePath', index);
|
|
465
|
+
ensureFileExists(videoPath);
|
|
466
|
+
ensureFileExists(audioPath);
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
inputPath = this.getNodeParameter('inputFilePath', index);
|
|
470
|
+
ensureFileExists(inputPath);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (operation === 'metadata') {
|
|
474
|
+
const targetPath = inputMode === 'binary' ? inputPath : inputPath;
|
|
475
|
+
const metadata = await new Promise((resolve, reject) => {
|
|
476
|
+
fluent_ffmpeg_1.default.ffprobe(targetPath, (err, data) => {
|
|
477
|
+
if (err) {
|
|
478
|
+
const hint = err.message.includes('ffprobe')
|
|
479
|
+
? 'ffprobe not found. Install ffprobe or set FFPROBE_PATH.'
|
|
480
|
+
: err.message;
|
|
481
|
+
reject(new Error(hint));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
resolve(data);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
results.push({
|
|
488
|
+
json: {
|
|
489
|
+
...item.json,
|
|
490
|
+
media: {
|
|
491
|
+
operation,
|
|
492
|
+
metadata,
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const outputModeValue = outputMode;
|
|
499
|
+
const outputPathParam = this.getNodeParameter('outputFilePath', index, '');
|
|
500
|
+
const outputPath = outputModeValue === 'filePath' && outputPathParam
|
|
501
|
+
? outputPathParam
|
|
502
|
+
: path.join(tempDir, `output.${outputFormat || 'mp4'}`);
|
|
503
|
+
const extraOptions = parseExtraArgs(this.getNodeParameter('additionalOutputOptions', index, ''));
|
|
504
|
+
let command = (0, fluent_ffmpeg_1.default)();
|
|
505
|
+
if (operation === 'merge') {
|
|
506
|
+
command = command.input(videoPath).input(audioPath);
|
|
507
|
+
command.outputOptions(['-map 0:v:0', '-map 1:a:0']);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
command = command.input(inputPath);
|
|
511
|
+
}
|
|
512
|
+
if (operation === 'convert') {
|
|
513
|
+
const videoCodec = this.getNodeParameter('videoCodec', index, '');
|
|
514
|
+
const audioCodec = this.getNodeParameter('audioCodec', index, '');
|
|
515
|
+
if (videoCodec) {
|
|
516
|
+
command.videoCodec(videoCodec);
|
|
517
|
+
}
|
|
518
|
+
if (audioCodec) {
|
|
519
|
+
command.audioCodec(audioCodec);
|
|
520
|
+
}
|
|
521
|
+
if (outputFormat) {
|
|
522
|
+
command.format(outputFormat);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (operation === 'compress') {
|
|
526
|
+
const crf = this.getNodeParameter('videoCrf', index, 23);
|
|
527
|
+
const preset = this.getNodeParameter('videoPreset', index, 'medium');
|
|
528
|
+
const audioBitrate = this.getNodeParameter('audioBitrate', index, '128k');
|
|
529
|
+
command.videoCodec('libx264');
|
|
530
|
+
command.outputOptions([`-crf ${crf}`, `-preset ${preset}`]);
|
|
531
|
+
command.audioBitrate(audioBitrate);
|
|
532
|
+
if (outputFormat) {
|
|
533
|
+
command.format(outputFormat);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (operation === 'extractAudio') {
|
|
537
|
+
command.noVideo();
|
|
538
|
+
if (outputFormat) {
|
|
539
|
+
command.format(outputFormat);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (extraOptions.length) {
|
|
543
|
+
command.outputOptions(extraOptions);
|
|
544
|
+
}
|
|
545
|
+
await new Promise((resolve, reject) => {
|
|
546
|
+
command
|
|
547
|
+
.on('error', (err) => reject(err))
|
|
548
|
+
.on('end', () => resolve())
|
|
549
|
+
.save(outputPath);
|
|
550
|
+
});
|
|
551
|
+
let outputItem = {
|
|
552
|
+
json: {
|
|
553
|
+
...item.json,
|
|
554
|
+
media: {
|
|
555
|
+
operation,
|
|
556
|
+
outputFormat,
|
|
557
|
+
outputPath: outputModeValue === 'filePath' ? outputPath : undefined,
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
if (outputModeValue === 'binary') {
|
|
562
|
+
const outputBinaryProperty = this.getNodeParameter('outputBinaryProperty', index, 'data');
|
|
563
|
+
const data = await fs.promises.readFile(outputPath);
|
|
564
|
+
const binaryData = await this.helpers.prepareBinaryData(data, path.basename(outputPath));
|
|
565
|
+
outputItem = {
|
|
566
|
+
...outputItem,
|
|
567
|
+
binary: {
|
|
568
|
+
[outputBinaryProperty]: binaryData,
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
results.push(outputItem);
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
576
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Media operation failed: ${message}`);
|
|
577
|
+
}
|
|
578
|
+
finally {
|
|
579
|
+
if (tempDir) {
|
|
580
|
+
await fs.promises.rm(tempDir, { recursive: true, force: true });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return [results];
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
exports.AimcMedia = AimcMedia;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="aimc-media-bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0%" stop-color="#0A1021"/>
|
|
5
|
+
<stop offset="100%" stop-color="#0B1B3A"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="aimc-media-play" x1="0" y1="0" x2="1" y2="1">
|
|
8
|
+
<stop offset="0%" stop-color="#7DD3FC"/>
|
|
9
|
+
<stop offset="100%" stop-color="#0EA5E9"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
<rect x="6" y="6" width="52" height="52" rx="10" fill="url(#aimc-media-bg)"/>
|
|
13
|
+
<polygon points="26,20 26,44 46,32" fill="url(#aimc-media-play)"/>
|
|
14
|
+
<g stroke="#1E3A8A" stroke-width="1.5" stroke-linecap="round" opacity="0.85">
|
|
15
|
+
<path d="M14 18V30"/>
|
|
16
|
+
<path d="M18 16V28"/>
|
|
17
|
+
<path d="M22 14V26"/>
|
|
18
|
+
<path d="M42 38V50"/>
|
|
19
|
+
<path d="M46 36V48"/>
|
|
20
|
+
<path d="M50 34V46"/>
|
|
21
|
+
</g>
|
|
22
|
+
<path d="M14 48C22 40 30 40 38 48" stroke="#38BDF8" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
|
23
|
+
</svg>
|