n8n-nodes-ffmpeg-cli 0.1.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.
@@ -0,0 +1,5 @@
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class Ffmpeg implements INodeType {
3
+ description: INodeTypeDescription;
4
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
5
+ }
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Ffmpeg = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const fs_1 = require("fs");
6
+ const path_1 = require("path");
7
+ const crypto_1 = require("crypto");
8
+ class Ffmpeg {
9
+ constructor() {
10
+ this.description = {
11
+ displayName: 'FFmpeg',
12
+ name: 'ffmpeg',
13
+ group: ['transform'],
14
+ version: 1,
15
+ description: 'Run any FFmpeg command on media files',
16
+ defaults: {
17
+ name: 'FFmpeg',
18
+ },
19
+ inputs: ['main'],
20
+ outputs: ['main'],
21
+ properties: [
22
+ {
23
+ displayName: 'FFmpeg Command',
24
+ name: 'command',
25
+ type: 'string',
26
+ default: '-i {input} -c:v copy -c:a aac {output}',
27
+ description: 'FFmpeg arguments. Use placeholders: {input}, {output}, and any binary property name in curly braces like {audio}, {video}, {image} etc.',
28
+ typeOptions: {
29
+ rows: 4,
30
+ },
31
+ },
32
+ {
33
+ displayName: 'Input Binary Properties',
34
+ name: 'inputBinaries',
35
+ type: 'string',
36
+ default: 'data',
37
+ description: 'Comma-separated list of binary property names to make available as temp files. Each becomes a {name} placeholder in the command.',
38
+ },
39
+ {
40
+ displayName: 'Output File Extension',
41
+ name: 'outputExtension',
42
+ type: 'string',
43
+ default: 'mp4',
44
+ description: 'File extension for the output file (mp4, mp3, wav, gif, etc.)',
45
+ },
46
+ {
47
+ displayName: 'Output Binary Property',
48
+ name: 'outputBinaryProperty',
49
+ type: 'string',
50
+ default: 'data',
51
+ description: 'Name of the binary property to store the output file in',
52
+ },
53
+ {
54
+ displayName: 'Output File Name',
55
+ name: 'outputFileName',
56
+ type: 'string',
57
+ default: 'output.mp4',
58
+ description: 'File name for the output',
59
+ },
60
+ {
61
+ displayName: 'Timeout (seconds)',
62
+ name: 'timeout',
63
+ type: 'number',
64
+ default: 120,
65
+ description: 'Maximum time to wait for FFmpeg to finish',
66
+ },
67
+ ],
68
+ };
69
+ }
70
+ async execute() {
71
+ var _a;
72
+ const items = this.getInputData();
73
+ const returnData = [];
74
+ for (let i = 0; i < items.length; i++) {
75
+ const id = (0, crypto_1.randomUUID)();
76
+ const tmpDir = '/tmp';
77
+ const tempFiles = [];
78
+ try {
79
+ let command = this.getNodeParameter('command', i);
80
+ const inputBinaries = this.getNodeParameter('inputBinaries', i)
81
+ .split(',')
82
+ .map((s) => s.trim())
83
+ .filter((s) => s.length > 0);
84
+ const outputExtension = this.getNodeParameter('outputExtension', i);
85
+ const outputBinaryProperty = this.getNodeParameter('outputBinaryProperty', i);
86
+ const outputFileName = this.getNodeParameter('outputFileName', i);
87
+ const timeout = this.getNodeParameter('timeout', i) * 1000;
88
+ const outputPath = (0, path_1.join)(tmpDir, `ffmpeg_out_${id}.${outputExtension}`);
89
+ tempFiles.push(outputPath);
90
+ // Write each binary property to a temp file and replace placeholder
91
+ for (const propName of inputBinaries) {
92
+ if (items[i].binary && items[i].binary[propName]) {
93
+ const binaryData = await this.helpers.getBinaryDataBuffer(i, propName);
94
+ const mimeType = items[i].binary[propName].mimeType || '';
95
+ let ext = 'bin';
96
+ if (mimeType.includes('video'))
97
+ ext = 'mp4';
98
+ else if (mimeType.includes('audio/mpeg'))
99
+ ext = 'mp3';
100
+ else if (mimeType.includes('audio'))
101
+ ext = 'aac';
102
+ else if (mimeType.includes('image/png'))
103
+ ext = 'png';
104
+ else if (mimeType.includes('image'))
105
+ ext = 'jpg';
106
+ const filePath = (0, path_1.join)(tmpDir, `ffmpeg_${propName}_${id}.${ext}`);
107
+ (0, fs_1.writeFileSync)(filePath, binaryData);
108
+ tempFiles.push(filePath);
109
+ // Replace {propName} placeholder
110
+ const placeholder = new RegExp(`\\{${propName}\\}`, 'g');
111
+ command = command.replace(placeholder, filePath);
112
+ // Also replace {input} with the first binary if it matches 'data' or is the first one
113
+ if (propName === inputBinaries[0]) {
114
+ command = command.replace(/\{input\}/g, filePath);
115
+ }
116
+ }
117
+ }
118
+ // Replace {output} placeholder
119
+ command = command.replace(/\{output\}/g, outputPath);
120
+ // Run FFmpeg
121
+ const fullCommand = `ffmpeg -y ${command}`;
122
+ (0, child_process_1.execSync)(fullCommand, {
123
+ timeout,
124
+ stdio: ['pipe', 'pipe', 'pipe'],
125
+ });
126
+ // Read output and create binary
127
+ const outputData = (0, fs_1.readFileSync)(outputPath);
128
+ const mimeType = outputExtension === 'mp3' ? 'audio/mpeg'
129
+ : outputExtension === 'wav' ? 'audio/wav'
130
+ : outputExtension === 'gif' ? 'image/gif'
131
+ : outputExtension === 'png' ? 'image/png'
132
+ : outputExtension === 'jpg' ? 'image/jpeg'
133
+ : `video/${outputExtension}`;
134
+ const binaryData = await this.helpers.prepareBinaryData(Buffer.from(outputData), outputFileName, mimeType);
135
+ returnData.push({
136
+ json: {
137
+ success: true,
138
+ command: fullCommand,
139
+ outputSize: outputData.length,
140
+ },
141
+ binary: { [outputBinaryProperty]: binaryData },
142
+ });
143
+ }
144
+ catch (error) {
145
+ returnData.push({
146
+ json: {
147
+ success: false,
148
+ error: error.message,
149
+ stderr: ((_a = error.stderr) === null || _a === void 0 ? void 0 : _a.toString()) || '',
150
+ },
151
+ });
152
+ }
153
+ finally {
154
+ // Cleanup
155
+ for (const f of tempFiles) {
156
+ try {
157
+ (0, fs_1.unlinkSync)(f);
158
+ }
159
+ catch { }
160
+ }
161
+ }
162
+ }
163
+ return [returnData];
164
+ }
165
+ }
166
+ exports.Ffmpeg = Ffmpeg;
@@ -0,0 +1,175 @@
1
+ import {
2
+ IExecuteFunctions,
3
+ INodeExecutionData,
4
+ INodeType,
5
+ INodeTypeDescription,
6
+ } from 'n8n-workflow';
7
+
8
+ import { execSync } from 'child_process';
9
+ import { writeFileSync, readFileSync, unlinkSync } from 'fs';
10
+ import { join } from 'path';
11
+ import { randomUUID } from 'crypto';
12
+
13
+ export class Ffmpeg implements INodeType {
14
+ description: INodeTypeDescription = {
15
+ displayName: 'FFmpeg',
16
+ name: 'ffmpeg',
17
+ group: ['transform'],
18
+ version: 1,
19
+ description: 'Run any FFmpeg command on media files',
20
+ defaults: {
21
+ name: 'FFmpeg',
22
+ },
23
+ inputs: ['main'] as any,
24
+ outputs: ['main'] as any,
25
+ properties: [
26
+ {
27
+ displayName: 'FFmpeg Command',
28
+ name: 'command',
29
+ type: 'string',
30
+ default: '-i {input} -c:v copy -c:a aac {output}',
31
+ description: 'FFmpeg arguments. Use placeholders: {input}, {output}, and any binary property name in curly braces like {audio}, {video}, {image} etc.',
32
+ typeOptions: {
33
+ rows: 4,
34
+ },
35
+ },
36
+ {
37
+ displayName: 'Input Binary Properties',
38
+ name: 'inputBinaries',
39
+ type: 'string',
40
+ default: 'data',
41
+ description: 'Comma-separated list of binary property names to make available as temp files. Each becomes a {name} placeholder in the command.',
42
+ },
43
+ {
44
+ displayName: 'Output File Extension',
45
+ name: 'outputExtension',
46
+ type: 'string',
47
+ default: 'mp4',
48
+ description: 'File extension for the output file (mp4, mp3, wav, gif, etc.)',
49
+ },
50
+ {
51
+ displayName: 'Output Binary Property',
52
+ name: 'outputBinaryProperty',
53
+ type: 'string',
54
+ default: 'data',
55
+ description: 'Name of the binary property to store the output file in',
56
+ },
57
+ {
58
+ displayName: 'Output File Name',
59
+ name: 'outputFileName',
60
+ type: 'string',
61
+ default: 'output.mp4',
62
+ description: 'File name for the output',
63
+ },
64
+ {
65
+ displayName: 'Timeout (seconds)',
66
+ name: 'timeout',
67
+ type: 'number',
68
+ default: 120,
69
+ description: 'Maximum time to wait for FFmpeg to finish',
70
+ },
71
+ ],
72
+ };
73
+
74
+ async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
75
+ const items = this.getInputData();
76
+ const returnData: INodeExecutionData[] = [];
77
+
78
+ for (let i = 0; i < items.length; i++) {
79
+ const id = randomUUID();
80
+ const tmpDir = '/tmp';
81
+ const tempFiles: string[] = [];
82
+
83
+ try {
84
+ let command = this.getNodeParameter('command', i) as string;
85
+ const inputBinaries = (this.getNodeParameter('inputBinaries', i) as string)
86
+ .split(',')
87
+ .map((s) => s.trim())
88
+ .filter((s) => s.length > 0);
89
+ const outputExtension = this.getNodeParameter('outputExtension', i) as string;
90
+ const outputBinaryProperty = this.getNodeParameter('outputBinaryProperty', i) as string;
91
+ const outputFileName = this.getNodeParameter('outputFileName', i) as string;
92
+ const timeout = (this.getNodeParameter('timeout', i) as number) * 1000;
93
+
94
+ const outputPath = join(tmpDir, `ffmpeg_out_${id}.${outputExtension}`);
95
+ tempFiles.push(outputPath);
96
+
97
+ // Write each binary property to a temp file and replace placeholder
98
+ for (const propName of inputBinaries) {
99
+ if (items[i].binary && items[i].binary![propName]) {
100
+ const binaryData = await this.helpers.getBinaryDataBuffer(i, propName);
101
+ const mimeType = items[i].binary![propName].mimeType || '';
102
+ let ext = 'bin';
103
+ if (mimeType.includes('video')) ext = 'mp4';
104
+ else if (mimeType.includes('audio/mpeg')) ext = 'mp3';
105
+ else if (mimeType.includes('audio')) ext = 'aac';
106
+ else if (mimeType.includes('image/png')) ext = 'png';
107
+ else if (mimeType.includes('image')) ext = 'jpg';
108
+
109
+ const filePath = join(tmpDir, `ffmpeg_${propName}_${id}.${ext}`);
110
+ writeFileSync(filePath, binaryData);
111
+ tempFiles.push(filePath);
112
+
113
+ // Replace {propName} placeholder
114
+ const placeholder = new RegExp(`\\{${propName}\\}`, 'g');
115
+ command = command.replace(placeholder, filePath);
116
+
117
+ // Also replace {input} with the first binary if it matches 'data' or is the first one
118
+ if (propName === inputBinaries[0]) {
119
+ command = command.replace(/\{input\}/g, filePath);
120
+ }
121
+ }
122
+ }
123
+
124
+ // Replace {output} placeholder
125
+ command = command.replace(/\{output\}/g, outputPath);
126
+
127
+ // Run FFmpeg
128
+ const fullCommand = `ffmpeg -y ${command}`;
129
+ execSync(fullCommand, {
130
+ timeout,
131
+ stdio: ['pipe', 'pipe', 'pipe'],
132
+ });
133
+
134
+ // Read output and create binary
135
+ const outputData = readFileSync(outputPath);
136
+ const mimeType = outputExtension === 'mp3' ? 'audio/mpeg'
137
+ : outputExtension === 'wav' ? 'audio/wav'
138
+ : outputExtension === 'gif' ? 'image/gif'
139
+ : outputExtension === 'png' ? 'image/png'
140
+ : outputExtension === 'jpg' ? 'image/jpeg'
141
+ : `video/${outputExtension}`;
142
+
143
+ const binaryData = await this.helpers.prepareBinaryData(
144
+ Buffer.from(outputData),
145
+ outputFileName,
146
+ mimeType,
147
+ );
148
+
149
+ returnData.push({
150
+ json: {
151
+ success: true,
152
+ command: fullCommand,
153
+ outputSize: outputData.length,
154
+ },
155
+ binary: { [outputBinaryProperty]: binaryData },
156
+ });
157
+ } catch (error: any) {
158
+ returnData.push({
159
+ json: {
160
+ success: false,
161
+ error: error.message,
162
+ stderr: error.stderr?.toString() || '',
163
+ },
164
+ });
165
+ } finally {
166
+ // Cleanup
167
+ for (const f of tempFiles) {
168
+ try { unlinkSync(f); } catch {}
169
+ }
170
+ }
171
+ }
172
+
173
+ return [returnData];
174
+ }
175
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "n8n-nodes-ffmpeg-cli",
3
+ "version": "0.1.0",
4
+ "description": "Run any FFmpeg command directly in n8n",
5
+ "license": "MIT",
6
+ "author": "shain",
7
+ "main": "dist/nodes/Ffmpeg/Ffmpeg.node.js",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "n8n": {
13
+ "n8nNodesApiVersion": 1,
14
+ "nodes": [
15
+ "dist/nodes/Ffmpeg/Ffmpeg.node.js"
16
+ ]
17
+ },
18
+ "dependencies": {
19
+ "n8n-workflow": "*"
20
+ },
21
+ "devDependencies": {
22
+ "typescript": "^5.0.0",
23
+ "n8n-core": "*"
24
+ },
25
+ "peerDependencies": {
26
+ "n8n-workflow": "*"
27
+ }
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "module": "commonjs",
5
+ "target": "es2019",
6
+ "lib": ["es2019"],
7
+ "declaration": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "./dist",
10
+ "rootDir": ".",
11
+ "esModuleInterop": true,
12
+ "resolveJsonModule": true,
13
+ "forceConsistentCasingInFileNames": true
14
+ },
15
+ "include": ["nodes/**/*.ts"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }