n8n-nodes-ffmpeg-bigboss 1.0.0
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 +55 -0
- package/dist/FFmpegBigBoss.node.js +493 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# n8n-nodes-ffmpeg-bigboss
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/n8n-nodes-ffmpeg-bigboss)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
**BigBoss** audiovisual processing node for n8n. Refactored from the ground up to provide reliable, production-ready FFmpeg capabilities.
|
|
7
|
+
|
|
8
|
+
## 🚀 Key Features
|
|
9
|
+
|
|
10
|
+
- **BigBoss Reliability**: Fixed known muxing issues with explicit stream mapping (`-map 0:v -map 1:a`).
|
|
11
|
+
- **Dual Input Modes**:
|
|
12
|
+
- **Binary**: Process data directly from n8n workflows.
|
|
13
|
+
- **File Path**: Handle large files directly from disk without memory overhead.
|
|
14
|
+
- **Two Operation Modes**:
|
|
15
|
+
- **Simple**: Easy presets for quick tasks.
|
|
16
|
+
- **BigBoss**: Full granular control over Codecs, CRF, Presets, and Custom Arguments.
|
|
17
|
+
|
|
18
|
+
## 📦 Operations
|
|
19
|
+
|
|
20
|
+
1. **Convert / Transcode**: Change format, codec, and resolution.
|
|
21
|
+
2. **Compress**: Optimize file size with CRF and Presets.
|
|
22
|
+
3. **Extract Audio**: Rip audio from video.
|
|
23
|
+
4. **Merge Video & Audio**: Combine video and audio files with perfect synchronization.
|
|
24
|
+
5. **Get Metadata**: Analyze media streams using `ffprobe`.
|
|
25
|
+
6. **Custom Command**: Run any raw FFmpeg command.
|
|
26
|
+
|
|
27
|
+
## 🛠️ Usage
|
|
28
|
+
|
|
29
|
+
### Merge Video & Audio (BigBoss Way)
|
|
30
|
+
1. Select **Operation**: `Merge Video & Audio`.
|
|
31
|
+
2. Choose **Input Source**: `File Path`.
|
|
32
|
+
3. Provide absolute paths for **Video File Path** and **Audio File Path**.
|
|
33
|
+
*Using File Paths avoids loading large binaries into n8n's memory, making the node faster and more stable.*
|
|
34
|
+
|
|
35
|
+
### Advanced Transcoding
|
|
36
|
+
1. Select **Operation**: `Convert / Transcode`.
|
|
37
|
+
2. Set **Mode** to `BigBoss`.
|
|
38
|
+
3. Select specific **Video Codec** (e.g., `H.264`) and **Audio Codec** (e.g., `AAC`).
|
|
39
|
+
|
|
40
|
+
## 📥 Installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install n8n-nodes-ffmpeg-bigboss
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 🏗️ Build from Source
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install
|
|
50
|
+
npm run build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
|
@@ -0,0 +1,493 @@
|
|
|
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.FFmpegBigBoss = void 0;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const uuid_1 = require("uuid");
|
|
44
|
+
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
45
|
+
const ffmpeg_1 = __importDefault(require("@ffmpeg-installer/ffmpeg"));
|
|
46
|
+
fluent_ffmpeg_1.default.setFfmpegPath(ffmpeg_1.default.path);
|
|
47
|
+
class FFmpegBigBoss {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.description = {
|
|
50
|
+
displayName: 'FFmpeg BigBoss',
|
|
51
|
+
name: 'ffmpegBigBoss',
|
|
52
|
+
icon: 'fa:film',
|
|
53
|
+
group: ['transform'],
|
|
54
|
+
version: 1,
|
|
55
|
+
description: 'Process video and audio using FFmpeg BigBoss capabilities',
|
|
56
|
+
defaults: {
|
|
57
|
+
name: 'FFmpeg BigBoss',
|
|
58
|
+
},
|
|
59
|
+
inputs: ['main'],
|
|
60
|
+
outputs: ['main'],
|
|
61
|
+
properties: [
|
|
62
|
+
{
|
|
63
|
+
displayName: 'Mode',
|
|
64
|
+
name: 'mode',
|
|
65
|
+
type: 'options',
|
|
66
|
+
options: [
|
|
67
|
+
{
|
|
68
|
+
name: 'Simple',
|
|
69
|
+
value: 'simple',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'BigBoss',
|
|
73
|
+
value: 'bigboss',
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
default: 'simple',
|
|
77
|
+
description: 'Toggle between Simple (easy presets) and BigBoss (granular control) options',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
displayName: 'Operation',
|
|
81
|
+
name: 'operation',
|
|
82
|
+
type: 'options',
|
|
83
|
+
noDataExpression: true,
|
|
84
|
+
options: [
|
|
85
|
+
{
|
|
86
|
+
name: 'Convert / Transcode',
|
|
87
|
+
value: 'convert',
|
|
88
|
+
description: 'Change format, resolution, or codec',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'Compress (Optimize)',
|
|
92
|
+
value: 'compress',
|
|
93
|
+
description: 'Reduce file size using CRF and presets',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'Extract Audio',
|
|
97
|
+
value: 'extractAudio',
|
|
98
|
+
description: 'Rip audio track from video',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'Merge Video & Audio',
|
|
102
|
+
value: 'merge',
|
|
103
|
+
description: 'Combine video and audio streams into a single file',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'Get Metadata',
|
|
107
|
+
value: 'metadata',
|
|
108
|
+
description: 'Analyze media streams',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Custom Command',
|
|
112
|
+
value: 'custom',
|
|
113
|
+
description: 'Run raw FFmpeg arguments',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
default: 'convert',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
displayName: 'Input Source',
|
|
120
|
+
name: 'inputSource',
|
|
121
|
+
type: 'options',
|
|
122
|
+
options: [
|
|
123
|
+
{
|
|
124
|
+
name: 'Binary Field',
|
|
125
|
+
value: 'binary',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'File Path',
|
|
129
|
+
value: 'path',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
default: 'binary',
|
|
133
|
+
displayOptions: {
|
|
134
|
+
hide: {
|
|
135
|
+
operation: ['merge'],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
displayName: 'Input File Path',
|
|
141
|
+
name: 'inputPath',
|
|
142
|
+
type: 'string',
|
|
143
|
+
default: '',
|
|
144
|
+
placeholder: '/path/to/video.mp4',
|
|
145
|
+
displayOptions: {
|
|
146
|
+
show: {
|
|
147
|
+
inputSource: ['path'],
|
|
148
|
+
},
|
|
149
|
+
hide: {
|
|
150
|
+
operation: ['merge'],
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
displayName: 'Input Binary Field',
|
|
156
|
+
name: 'binaryPropertyName',
|
|
157
|
+
type: 'string',
|
|
158
|
+
default: 'data',
|
|
159
|
+
required: true,
|
|
160
|
+
description: 'The name of the binary property containing the media file',
|
|
161
|
+
displayOptions: {
|
|
162
|
+
show: {
|
|
163
|
+
inputSource: ['binary'],
|
|
164
|
+
},
|
|
165
|
+
hide: {
|
|
166
|
+
operation: ['merge'],
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
displayName: 'Input Source',
|
|
172
|
+
name: 'mergeInputSource',
|
|
173
|
+
type: 'options',
|
|
174
|
+
options: [
|
|
175
|
+
{
|
|
176
|
+
name: 'Binary Fields',
|
|
177
|
+
value: 'binary',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: 'File Paths',
|
|
181
|
+
value: 'path',
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
default: 'binary',
|
|
185
|
+
displayOptions: {
|
|
186
|
+
show: {
|
|
187
|
+
operation: ['merge'],
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
displayName: 'Video File Path',
|
|
193
|
+
name: 'videoFilePath',
|
|
194
|
+
type: 'string',
|
|
195
|
+
default: '',
|
|
196
|
+
placeholder: '/path/to/video.mp4',
|
|
197
|
+
required: true,
|
|
198
|
+
displayOptions: {
|
|
199
|
+
show: {
|
|
200
|
+
operation: ['merge'],
|
|
201
|
+
mergeInputSource: ['path'],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
displayName: 'Audio File Path',
|
|
207
|
+
name: 'audioFilePath',
|
|
208
|
+
type: 'string',
|
|
209
|
+
default: '',
|
|
210
|
+
placeholder: '/path/to/audio.mp3',
|
|
211
|
+
required: true,
|
|
212
|
+
displayOptions: {
|
|
213
|
+
show: {
|
|
214
|
+
operation: ['merge'],
|
|
215
|
+
mergeInputSource: ['path'],
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
displayName: 'Video Binary Field',
|
|
221
|
+
name: 'videoBinaryProperty',
|
|
222
|
+
type: 'string',
|
|
223
|
+
default: 'video',
|
|
224
|
+
required: true,
|
|
225
|
+
displayOptions: {
|
|
226
|
+
show: {
|
|
227
|
+
operation: ['merge'],
|
|
228
|
+
mergeInputSource: ['binary'],
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
displayName: 'Audio Binary Field',
|
|
234
|
+
name: 'audioBinaryProperty',
|
|
235
|
+
type: 'string',
|
|
236
|
+
default: 'audio',
|
|
237
|
+
required: true,
|
|
238
|
+
displayOptions: {
|
|
239
|
+
show: {
|
|
240
|
+
operation: ['merge'],
|
|
241
|
+
mergeInputSource: ['binary'],
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
displayName: 'Output Format',
|
|
247
|
+
name: 'format',
|
|
248
|
+
type: 'options',
|
|
249
|
+
options: [
|
|
250
|
+
{ name: 'MP4', value: 'mp4' },
|
|
251
|
+
{ name: 'WebM', value: 'webm' },
|
|
252
|
+
{ name: 'AVI', value: 'avi' },
|
|
253
|
+
{ name: 'MOV', value: 'mov' },
|
|
254
|
+
{ name: 'GIF', value: 'gif' },
|
|
255
|
+
],
|
|
256
|
+
default: 'mp4',
|
|
257
|
+
displayOptions: {
|
|
258
|
+
show: {
|
|
259
|
+
operation: ['convert', 'merge'],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
displayName: 'Video Codec',
|
|
265
|
+
name: 'videoCodec',
|
|
266
|
+
type: 'options',
|
|
267
|
+
options: [
|
|
268
|
+
{ name: 'Auto', value: 'auto' },
|
|
269
|
+
{ name: 'Copy (Stream Copy)', value: 'copy' },
|
|
270
|
+
{ name: 'H.264 (libx264)', value: 'libx264' },
|
|
271
|
+
{ name: 'VP9 (libvpx-vp9)', value: 'libvpx-vp9' },
|
|
272
|
+
],
|
|
273
|
+
default: 'auto',
|
|
274
|
+
displayOptions: {
|
|
275
|
+
show: {
|
|
276
|
+
operation: ['convert', 'merge'],
|
|
277
|
+
mode: ['bigboss'],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
displayName: 'Audio Codec',
|
|
283
|
+
name: 'audioCodec',
|
|
284
|
+
type: 'options',
|
|
285
|
+
options: [
|
|
286
|
+
{ name: 'Auto', value: 'auto' },
|
|
287
|
+
{ name: 'AAC', value: 'aac' },
|
|
288
|
+
{ name: 'Opus (libopus)', value: 'libopus' },
|
|
289
|
+
],
|
|
290
|
+
default: 'auto',
|
|
291
|
+
displayOptions: {
|
|
292
|
+
show: {
|
|
293
|
+
operation: ['convert', 'merge'],
|
|
294
|
+
mode: ['bigboss'],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
displayName: 'Preset',
|
|
300
|
+
name: 'preset',
|
|
301
|
+
type: 'options',
|
|
302
|
+
options: [
|
|
303
|
+
{ name: 'Ultrafast', value: 'ultrafast' },
|
|
304
|
+
{ name: 'Superfast', value: 'superfast' },
|
|
305
|
+
{ name: 'Fast', value: 'fast' },
|
|
306
|
+
{ name: 'Medium', value: 'medium' },
|
|
307
|
+
{ name: 'Slow', value: 'slow' },
|
|
308
|
+
{ name: 'Veryslow', value: 'veryslow' },
|
|
309
|
+
],
|
|
310
|
+
default: 'medium',
|
|
311
|
+
displayOptions: {
|
|
312
|
+
show: {
|
|
313
|
+
operation: ['compress', 'convert'],
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
displayName: 'CRF (Quality)',
|
|
319
|
+
name: 'crf',
|
|
320
|
+
type: 'number',
|
|
321
|
+
default: 23,
|
|
322
|
+
typeOptions: { minValue: 0, maxValue: 51 },
|
|
323
|
+
description: '0 is lossless, 23 is default, 51 is worst',
|
|
324
|
+
displayOptions: {
|
|
325
|
+
show: {
|
|
326
|
+
operation: ['compress'],
|
|
327
|
+
mode: ['bigboss'],
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
displayName: 'Custom Arguments',
|
|
333
|
+
name: 'customArgs',
|
|
334
|
+
type: 'string',
|
|
335
|
+
default: '-c:v libx264 -preset slow -crf 22',
|
|
336
|
+
displayOptions: {
|
|
337
|
+
show: {
|
|
338
|
+
operation: ['custom'],
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
displayName: 'Output Extension',
|
|
344
|
+
name: 'outputExtension',
|
|
345
|
+
type: 'string',
|
|
346
|
+
default: 'mp4',
|
|
347
|
+
displayOptions: {
|
|
348
|
+
show: {
|
|
349
|
+
operation: ['custom'],
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async execute() {
|
|
357
|
+
const items = this.getInputData();
|
|
358
|
+
const returnData = [];
|
|
359
|
+
const tempDir = os.tmpdir();
|
|
360
|
+
const tempFilesToDelete = [];
|
|
361
|
+
try {
|
|
362
|
+
for (let i = 0; i < items.length; i++) {
|
|
363
|
+
const operation = this.getNodeParameter('operation', i);
|
|
364
|
+
let command;
|
|
365
|
+
let outputExtension = 'mp4';
|
|
366
|
+
if (operation === 'merge') {
|
|
367
|
+
const mergeSource = this.getNodeParameter('mergeInputSource', i);
|
|
368
|
+
let videoPath = '';
|
|
369
|
+
let audioPath = '';
|
|
370
|
+
if (mergeSource === 'path') {
|
|
371
|
+
videoPath = this.getNodeParameter('videoFilePath', i);
|
|
372
|
+
audioPath = this.getNodeParameter('audioFilePath', i);
|
|
373
|
+
if (!fs.existsSync(videoPath))
|
|
374
|
+
throw new Error(`Video not found: ${videoPath}`);
|
|
375
|
+
if (!fs.existsSync(audioPath))
|
|
376
|
+
throw new Error(`Audio not found: ${audioPath}`);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const vProp = this.getNodeParameter('videoBinaryProperty', i);
|
|
380
|
+
const aProp = this.getNodeParameter('audioBinaryProperty', i);
|
|
381
|
+
const vBuf = await this.helpers.getBinaryDataBuffer(i, vProp);
|
|
382
|
+
const aBuf = await this.helpers.getBinaryDataBuffer(i, aProp);
|
|
383
|
+
videoPath = path.join(tempDir, `vid_${(0, uuid_1.v4)()}`);
|
|
384
|
+
audioPath = path.join(tempDir, `aud_${(0, uuid_1.v4)()}`);
|
|
385
|
+
fs.writeFileSync(videoPath, vBuf);
|
|
386
|
+
fs.writeFileSync(audioPath, aBuf);
|
|
387
|
+
tempFilesToDelete.push(videoPath, audioPath);
|
|
388
|
+
}
|
|
389
|
+
command = (0, fluent_ffmpeg_1.default)();
|
|
390
|
+
command.input(videoPath);
|
|
391
|
+
command.input(audioPath);
|
|
392
|
+
command.outputOptions([
|
|
393
|
+
'-map 0:v',
|
|
394
|
+
'-map 1:a',
|
|
395
|
+
'-c:v copy',
|
|
396
|
+
'-c:a aac',
|
|
397
|
+
]);
|
|
398
|
+
outputExtension = this.getNodeParameter('format', i) || 'mp4';
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
const inputSource = this.getNodeParameter('inputSource', i);
|
|
402
|
+
let inputPath = '';
|
|
403
|
+
if (inputSource === 'path') {
|
|
404
|
+
inputPath = this.getNodeParameter('inputPath', i);
|
|
405
|
+
if (!fs.existsSync(inputPath))
|
|
406
|
+
throw new Error(`Input not found: ${inputPath}`);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
const prop = this.getNodeParameter('binaryPropertyName', i);
|
|
410
|
+
const buf = await this.helpers.getBinaryDataBuffer(i, prop);
|
|
411
|
+
inputPath = path.join(tempDir, `in_${(0, uuid_1.v4)()}`);
|
|
412
|
+
fs.writeFileSync(inputPath, buf);
|
|
413
|
+
tempFilesToDelete.push(inputPath);
|
|
414
|
+
}
|
|
415
|
+
command = (0, fluent_ffmpeg_1.default)(inputPath);
|
|
416
|
+
}
|
|
417
|
+
if (operation === 'convert') {
|
|
418
|
+
outputExtension = this.getNodeParameter('format', i);
|
|
419
|
+
const mode = this.getNodeParameter('mode', i);
|
|
420
|
+
if (mode === 'bigboss') {
|
|
421
|
+
const vCodec = this.getNodeParameter('videoCodec', i);
|
|
422
|
+
const aCodec = this.getNodeParameter('audioCodec', i);
|
|
423
|
+
if (vCodec !== 'auto')
|
|
424
|
+
command.videoCodec(vCodec);
|
|
425
|
+
if (aCodec !== 'auto')
|
|
426
|
+
command.audioCodec(aCodec);
|
|
427
|
+
}
|
|
428
|
+
const preset = this.getNodeParameter('preset', i);
|
|
429
|
+
command.outputOptions(`-preset ${preset}`);
|
|
430
|
+
}
|
|
431
|
+
else if (operation === 'compress') {
|
|
432
|
+
const crf = this.getNodeParameter('crf', i);
|
|
433
|
+
const preset = this.getNodeParameter('preset', i);
|
|
434
|
+
command.videoCodec('libx264').outputOptions(`-crf ${crf}`).outputOptions(`-preset ${preset}`);
|
|
435
|
+
}
|
|
436
|
+
else if (operation === 'custom') {
|
|
437
|
+
const args = this.getNodeParameter('customArgs', i);
|
|
438
|
+
outputExtension = this.getNodeParameter('outputExtension', i);
|
|
439
|
+
command.outputOptions(args.split(' ').filter(a => a));
|
|
440
|
+
}
|
|
441
|
+
if (operation === 'metadata') {
|
|
442
|
+
const metadata = await new Promise((resolve, reject) => {
|
|
443
|
+
command.ffprobe((err, data) => {
|
|
444
|
+
if (err)
|
|
445
|
+
reject(err);
|
|
446
|
+
else
|
|
447
|
+
resolve(data);
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
returnData.push({
|
|
451
|
+
json: metadata,
|
|
452
|
+
binary: items[i].binary,
|
|
453
|
+
});
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const outName = `out_${(0, uuid_1.v4)()}.${outputExtension}`;
|
|
457
|
+
const outPath = path.join(tempDir, outName);
|
|
458
|
+
await new Promise((resolve, reject) => {
|
|
459
|
+
command
|
|
460
|
+
.on('end', () => resolve(true))
|
|
461
|
+
.on('error', (err) => reject(new Error(`FFmpeg failed: ${err.message}`)))
|
|
462
|
+
.save(outPath);
|
|
463
|
+
});
|
|
464
|
+
const outBuf = fs.readFileSync(outPath);
|
|
465
|
+
const binaryData = await this.helpers.prepareBinaryData(outBuf, outName);
|
|
466
|
+
returnData.push({
|
|
467
|
+
json: items[i].json,
|
|
468
|
+
binary: {
|
|
469
|
+
data: binaryData
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
if (fs.existsSync(outPath))
|
|
473
|
+
fs.unlinkSync(outPath);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
if (this.continueOnFail()) {
|
|
478
|
+
returnData.push({ json: { error: error.message }, binary: {} });
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
throw error;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
finally {
|
|
485
|
+
tempFilesToDelete.forEach(f => {
|
|
486
|
+
if (fs.existsSync(f))
|
|
487
|
+
fs.unlinkSync(f);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
return [returnData];
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
exports.FFmpegBigBoss = FFmpegBigBoss;
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-ffmpeg-bigboss",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "BigBoss audiovisual processing node for n8n based on FFmpeg research",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n",
|
|
8
|
+
"ffmpeg",
|
|
9
|
+
"video",
|
|
10
|
+
"audio",
|
|
11
|
+
"transcode"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "isemo007",
|
|
15
|
+
"main": "index.js",
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && gulp build:icons",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"format": "prettier nodes --write",
|
|
20
|
+
"lint": "eslint nodes package.json",
|
|
21
|
+
"lintfix": "eslint nodes package.json --fix"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"n8n": {
|
|
27
|
+
"n8nNodesApiVersion": 1,
|
|
28
|
+
"nodes": [
|
|
29
|
+
"dist/FFmpegBigBoss.node.js"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^22.13.0",
|
|
34
|
+
"@typescript-eslint/parser": "~5.45",
|
|
35
|
+
"eslint-plugin-n8n-nodes-base": "^1.11.0",
|
|
36
|
+
"gulp": "^4.0.2",
|
|
37
|
+
"n8n-workflow": "^1.70.0",
|
|
38
|
+
"prettier": "^2.7.1",
|
|
39
|
+
"typescript": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"n8n-workflow": "*"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"n8n-core": "^1.75.0",
|
|
46
|
+
"fluent-ffmpeg": "^2.1.2",
|
|
47
|
+
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
48
|
+
"uuid": "^9.0.0"
|
|
49
|
+
}
|
|
50
|
+
}
|