@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 CHANGED
@@ -161,7 +161,7 @@ if (aiModel) {
161
161
  - Metadata (ffprobe)
162
162
 
163
163
  **Key Options**
164
- - **Input Mode**: Binary or File Path
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ferchy/n8n-nodes-aimc-toolkit",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "description": "AIMC Toolkit nodes for n8n: code execution and media operations.",
5
5
  "license": "MIT",
6
6
  "author": "Ferchy",