@ferchy/n8n-nodes-aimc-toolkit 0.1.22 → 0.1.24
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/README.md +10 -2
- package/dist/nodes/AimcMedia/AimcMedia.node.js +119 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,7 +161,7 @@ if (aiModel) {
|
|
|
161
161
|
- Metadata (ffprobe)
|
|
162
162
|
|
|
163
163
|
**Key Options**
|
|
164
|
-
- **Input Mode**: Binary or
|
|
164
|
+
- **Input Mode**: Binary, File Path, or URL (download from web)
|
|
165
165
|
- **Output Mode**: Binary or File Path
|
|
166
166
|
- **Output Format**: mp4, webm, mov, mp3, wav, and more
|
|
167
167
|
- **Extra FFmpeg Args**: one per line for advanced control
|
|
@@ -190,8 +190,16 @@ Video File Path: /path/to/video.mp4
|
|
|
190
190
|
Audio File Path: /path/to/audio.mp3
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
+
**Example: download and convert from URL**
|
|
194
|
+
```
|
|
195
|
+
Operation: Convert / Transcode
|
|
196
|
+
Input Mode: URL
|
|
197
|
+
Input URL: https://example.com/video.mp4
|
|
198
|
+
Output Format: webm
|
|
199
|
+
```
|
|
200
|
+
|
|
193
201
|
**Large Files**
|
|
194
|
-
Use **Input Mode = File Path** to avoid loading big files into memory.
|
|
202
|
+
Use **Input Mode = File Path** or **URL** to avoid loading big files into memory.
|
|
195
203
|
|
|
196
204
|
### AIMC Social Scraper
|
|
197
205
|
|
|
@@ -42,6 +42,7 @@ const fs = __importStar(require("fs"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const os = __importStar(require("os"));
|
|
44
44
|
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
45
|
+
const axios_1 = __importDefault(require("axios"));
|
|
45
46
|
let ffmpegConfigured = false;
|
|
46
47
|
let ffmpegChecked = null;
|
|
47
48
|
function resolveOptional(moduleName) {
|
|
@@ -116,6 +117,58 @@ async function writeTempFile(dir, fileName, data) {
|
|
|
116
117
|
await fs.promises.writeFile(filePath, data);
|
|
117
118
|
return filePath;
|
|
118
119
|
}
|
|
120
|
+
async function downloadFromUrl(url, tempDir) {
|
|
121
|
+
const response = await axios_1.default.get(url, {
|
|
122
|
+
responseType: 'arraybuffer',
|
|
123
|
+
timeout: 300000, // 5 minutes timeout
|
|
124
|
+
maxContentLength: 500 * 1024 * 1024, // 500MB max
|
|
125
|
+
headers: {
|
|
126
|
+
'User-Agent': 'Mozilla/5.0 (compatible; n8n-aimc-media/1.0)',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
// Try to get filename from URL or Content-Disposition header
|
|
130
|
+
let filename = 'downloaded_file';
|
|
131
|
+
const contentDisposition = response.headers['content-disposition'];
|
|
132
|
+
if (contentDisposition) {
|
|
133
|
+
const match = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
|
134
|
+
if (match && match[1]) {
|
|
135
|
+
filename = match[1].replace(/['"]/g, '');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Extract from URL
|
|
140
|
+
const urlPath = new URL(url).pathname;
|
|
141
|
+
const urlFilename = path.basename(urlPath);
|
|
142
|
+
if (urlFilename && urlFilename.includes('.')) {
|
|
143
|
+
filename = urlFilename;
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Try to guess extension from content-type
|
|
147
|
+
const contentType = response.headers['content-type'];
|
|
148
|
+
if (contentType) {
|
|
149
|
+
if (contentType.includes('video/mp4'))
|
|
150
|
+
filename = 'input.mp4';
|
|
151
|
+
else if (contentType.includes('video/webm'))
|
|
152
|
+
filename = 'input.webm';
|
|
153
|
+
else if (contentType.includes('video/quicktime'))
|
|
154
|
+
filename = 'input.mov';
|
|
155
|
+
else if (contentType.includes('audio/mpeg'))
|
|
156
|
+
filename = 'input.mp3';
|
|
157
|
+
else if (contentType.includes('audio/wav'))
|
|
158
|
+
filename = 'input.wav';
|
|
159
|
+
else if (contentType.includes('audio/ogg'))
|
|
160
|
+
filename = 'input.ogg';
|
|
161
|
+
else if (contentType.includes('video/'))
|
|
162
|
+
filename = 'input.mp4';
|
|
163
|
+
else if (contentType.includes('audio/'))
|
|
164
|
+
filename = 'input.mp3';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const filePath = path.join(tempDir, filename);
|
|
169
|
+
await fs.promises.writeFile(filePath, Buffer.from(response.data));
|
|
170
|
+
return filePath;
|
|
171
|
+
}
|
|
119
172
|
function parseExtraArgs(raw) {
|
|
120
173
|
if (!raw) {
|
|
121
174
|
return [];
|
|
@@ -165,8 +218,10 @@ class AimcMedia {
|
|
|
165
218
|
options: [
|
|
166
219
|
{ name: 'Binary', value: 'binary' },
|
|
167
220
|
{ name: 'File Path', value: 'filePath' },
|
|
221
|
+
{ name: 'URL', value: 'url' },
|
|
168
222
|
],
|
|
169
223
|
default: 'binary',
|
|
224
|
+
description: 'How to provide the input file: from a previous node (Binary), local file path, or download from URL',
|
|
170
225
|
},
|
|
171
226
|
{
|
|
172
227
|
displayName: 'Input Binary Property',
|
|
@@ -193,6 +248,20 @@ class AimcMedia {
|
|
|
193
248
|
},
|
|
194
249
|
placeholder: '/path/to/input.mp4',
|
|
195
250
|
},
|
|
251
|
+
{
|
|
252
|
+
displayName: 'Input URL',
|
|
253
|
+
name: 'inputUrl',
|
|
254
|
+
type: 'string',
|
|
255
|
+
default: '',
|
|
256
|
+
displayOptions: {
|
|
257
|
+
show: {
|
|
258
|
+
inputMode: ['url'],
|
|
259
|
+
operation: ['convert', 'compress', 'extractAudio', 'metadata'],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
placeholder: 'https://example.com/video.mp4',
|
|
263
|
+
description: 'URL to download the media file from',
|
|
264
|
+
},
|
|
196
265
|
{
|
|
197
266
|
displayName: 'Video Binary Property',
|
|
198
267
|
name: 'videoBinaryProperty',
|
|
@@ -243,6 +312,34 @@ class AimcMedia {
|
|
|
243
312
|
},
|
|
244
313
|
placeholder: '/path/to/audio.mp3',
|
|
245
314
|
},
|
|
315
|
+
{
|
|
316
|
+
displayName: 'Video URL',
|
|
317
|
+
name: 'videoUrl',
|
|
318
|
+
type: 'string',
|
|
319
|
+
default: '',
|
|
320
|
+
displayOptions: {
|
|
321
|
+
show: {
|
|
322
|
+
inputMode: ['url'],
|
|
323
|
+
operation: ['merge'],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
placeholder: 'https://example.com/video.mp4',
|
|
327
|
+
description: 'URL to download the video file from',
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
displayName: 'Audio URL',
|
|
331
|
+
name: 'audioUrl',
|
|
332
|
+
type: 'string',
|
|
333
|
+
default: '',
|
|
334
|
+
displayOptions: {
|
|
335
|
+
show: {
|
|
336
|
+
inputMode: ['url'],
|
|
337
|
+
operation: ['merge'],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
placeholder: 'https://example.com/audio.mp3',
|
|
341
|
+
description: 'URL to download the audio file from',
|
|
342
|
+
},
|
|
246
343
|
{
|
|
247
344
|
displayName: 'Output Mode',
|
|
248
345
|
name: 'outputMode',
|
|
@@ -458,7 +555,7 @@ class AimcMedia {
|
|
|
458
555
|
inputPath = await writeTempFile(tempDir, `input.${binary.fileExtension || 'bin'}`, buffer);
|
|
459
556
|
}
|
|
460
557
|
}
|
|
461
|
-
else {
|
|
558
|
+
else if (inputMode === 'filePath') {
|
|
462
559
|
if (operation === 'merge') {
|
|
463
560
|
videoPath = this.getNodeParameter('videoFilePath', index);
|
|
464
561
|
audioPath = this.getNodeParameter('audioFilePath', index);
|
|
@@ -470,6 +567,27 @@ class AimcMedia {
|
|
|
470
567
|
ensureFileExists(inputPath);
|
|
471
568
|
}
|
|
472
569
|
}
|
|
570
|
+
else if (inputMode === 'url') {
|
|
571
|
+
if (operation === 'merge') {
|
|
572
|
+
const videoUrl = this.getNodeParameter('videoUrl', index);
|
|
573
|
+
const audioUrl = this.getNodeParameter('audioUrl', index);
|
|
574
|
+
if (!videoUrl) {
|
|
575
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Video URL is required');
|
|
576
|
+
}
|
|
577
|
+
if (!audioUrl) {
|
|
578
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Audio URL is required');
|
|
579
|
+
}
|
|
580
|
+
videoPath = await downloadFromUrl(videoUrl, tempDir);
|
|
581
|
+
audioPath = await downloadFromUrl(audioUrl, tempDir);
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
const inputUrl = this.getNodeParameter('inputUrl', index);
|
|
585
|
+
if (!inputUrl) {
|
|
586
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Input URL is required');
|
|
587
|
+
}
|
|
588
|
+
inputPath = await downloadFromUrl(inputUrl, tempDir);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
473
591
|
if (operation === 'metadata') {
|
|
474
592
|
const targetPath = inputMode === 'binary' ? inputPath : inputPath;
|
|
475
593
|
const metadata = await new Promise((resolve, reject) => {
|