@lee-jisoo/n8n-nodes-mediafx 1.6.20 → 1.6.21
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 +69 -1
- package/dist/nodes/MediaFX/MediaFX.node.js +29 -0
- package/dist/nodes/MediaFX/operations/addSubtitle.js +23 -4
- package/dist/nodes/MediaFX/operations/addTextToImage.d.ts +2 -0
- package/dist/nodes/MediaFX/operations/addTextToImage.js +131 -0
- package/dist/nodes/MediaFX/operations/index.d.ts +1 -0
- package/dist/nodes/MediaFX/operations/index.js +1 -0
- package/dist/nodes/MediaFX/properties/image.properties.js +298 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,17 @@ This is a custom n8n node for comprehensive, local media processing using FFmpeg
|
|
|
10
10
|
|
|
11
11
|
## 🆕 What's New in This Fork
|
|
12
12
|
|
|
13
|
+
### v1.6.21
|
|
14
|
+
**New Features**
|
|
15
|
+
|
|
16
|
+
- **🖼️ Add Text to Image**: Overlay text onto static images
|
|
17
|
+
- Font selection (bundled, user-uploaded, system fonts)
|
|
18
|
+
- Text styling: size, color
|
|
19
|
+
- **Outline**: configurable width and color for text border
|
|
20
|
+
- **Background Box**: optional background with color, opacity, and padding
|
|
21
|
+
- Positioning: alignment-based (9-point grid) or custom X/Y coordinates
|
|
22
|
+
- Output format matches input image format (PNG, JPG, etc.)
|
|
23
|
+
|
|
13
24
|
### v1.6.20
|
|
14
25
|
**New Features**
|
|
15
26
|
|
|
@@ -81,6 +92,27 @@ npm install @lee-jisoo/n8n-nodes-mediafx
|
|
|
81
92
|
RUN cd /home/node/.n8n/nodes && npm install @lee-jisoo/n8n-nodes-mediafx
|
|
82
93
|
```
|
|
83
94
|
|
|
95
|
+
## ⚠️ Troubleshooting
|
|
96
|
+
|
|
97
|
+
### Node not working after upgrade
|
|
98
|
+
If the node doesn't work properly after upgrading to a new version, try **uninstalling and reinstalling** the plugin:
|
|
99
|
+
|
|
100
|
+
**Via n8n Community Nodes:**
|
|
101
|
+
1. Go to **Settings > Community Nodes**
|
|
102
|
+
2. Find `@lee-jisoo/n8n-nodes-mediafx` and click **Uninstall**
|
|
103
|
+
3. Restart n8n
|
|
104
|
+
4. Go to **Settings > Community Nodes** again
|
|
105
|
+
5. Click **Install** and enter `@lee-jisoo/n8n-nodes-mediafx`
|
|
106
|
+
6. Restart n8n
|
|
107
|
+
|
|
108
|
+
**Manual Installation:**
|
|
109
|
+
```bash
|
|
110
|
+
cd ~/.n8n/nodes
|
|
111
|
+
npm uninstall @lee-jisoo/n8n-nodes-mediafx
|
|
112
|
+
npm install @lee-jisoo/n8n-nodes-mediafx
|
|
113
|
+
# Restart n8n
|
|
114
|
+
```
|
|
115
|
+
|
|
84
116
|
## Features
|
|
85
117
|
|
|
86
118
|
### Video Operations
|
|
@@ -103,6 +135,7 @@ RUN cd /home/node/.n8n/nodes && npm install @lee-jisoo/n8n-nodes-mediafx
|
|
|
103
135
|
### Image Operations
|
|
104
136
|
| Operation | Description |
|
|
105
137
|
|-----------|-------------|
|
|
138
|
+
| **Add Text** | Overlay text on image with styling, outline, background box ⭐ NEW |
|
|
106
139
|
| **Image to Video** | Create video from image with custom duration |
|
|
107
140
|
| **Stamp Image** | Add watermark with position, size, rotation, opacity, time control |
|
|
108
141
|
|
|
@@ -126,7 +159,42 @@ RUN cd /home/node/.n8n/nodes && npm install @lee-jisoo/n8n-nodes-mediafx
|
|
|
126
159
|
|
|
127
160
|
## Usage Examples
|
|
128
161
|
|
|
129
|
-
###
|
|
162
|
+
### Add Text to Image (New!)
|
|
163
|
+
Overlay text on an image with styling:
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"resource": "image",
|
|
167
|
+
"operation": "addTextToImage",
|
|
168
|
+
"sourceImageText": {
|
|
169
|
+
"source": { "sourceType": "binary", "binaryProperty": "data" }
|
|
170
|
+
},
|
|
171
|
+
"imageText": "Hello, World!",
|
|
172
|
+
"imageTextFontKey": "noto-sans-kr",
|
|
173
|
+
"imageTextSize": 48,
|
|
174
|
+
"imageTextColor": "white",
|
|
175
|
+
"imageTextOutlineWidth": 2,
|
|
176
|
+
"imageTextOutlineColor": "black",
|
|
177
|
+
"imageTextPositionType": "alignment",
|
|
178
|
+
"imageTextHorizontalAlign": "center",
|
|
179
|
+
"imageTextVerticalAlign": "bottom",
|
|
180
|
+
"imageTextPaddingY": 50
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
With background box:
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"resource": "image",
|
|
188
|
+
"operation": "addTextToImage",
|
|
189
|
+
"imageText": "Caption Text",
|
|
190
|
+
"imageTextEnableBackground": true,
|
|
191
|
+
"imageTextBackgroundColor": "black",
|
|
192
|
+
"imageTextBackgroundOpacity": 0.7,
|
|
193
|
+
"imageTextBoxPadding": 10
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Get Media Metadata
|
|
130
198
|
Extract metadata from video or audio files:
|
|
131
199
|
```json
|
|
132
200
|
{
|
|
@@ -406,6 +406,35 @@ class MediaFX {
|
|
|
406
406
|
outputPath = await operations_1.executeSingleVideoFade.call(this, paths[0], fadeEffect, fadeStartTime, fadeDuration, outputFormat, i);
|
|
407
407
|
break;
|
|
408
408
|
}
|
|
409
|
+
case 'addTextToImage': {
|
|
410
|
+
const sourceParam = this.getNodeParameter('sourceImageText', i, {});
|
|
411
|
+
if (!sourceParam.source) {
|
|
412
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Image source is required. Please add an image source.', { itemIndex: i });
|
|
413
|
+
}
|
|
414
|
+
const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
|
|
415
|
+
cleanup = c;
|
|
416
|
+
const text = this.getNodeParameter('imageText', i, 'Hello, n8n!');
|
|
417
|
+
const textOptions = {
|
|
418
|
+
fontKey: this.getNodeParameter('imageTextFontKey', i, 'noto-sans-kr'),
|
|
419
|
+
size: this.getNodeParameter('imageTextSize', i, 48),
|
|
420
|
+
color: this.getNodeParameter('imageTextColor', i, 'white'),
|
|
421
|
+
outlineWidth: this.getNodeParameter('imageTextOutlineWidth', i, 0),
|
|
422
|
+
outlineColor: this.getNodeParameter('imageTextOutlineColor', i, 'black'),
|
|
423
|
+
enableBackground: this.getNodeParameter('imageTextEnableBackground', i, false),
|
|
424
|
+
backgroundColor: this.getNodeParameter('imageTextBackgroundColor', i, 'black'),
|
|
425
|
+
backgroundOpacity: this.getNodeParameter('imageTextBackgroundOpacity', i, 0.5),
|
|
426
|
+
boxPadding: this.getNodeParameter('imageTextBoxPadding', i, 5),
|
|
427
|
+
positionType: this.getNodeParameter('imageTextPositionType', i, 'alignment'),
|
|
428
|
+
horizontalAlign: this.getNodeParameter('imageTextHorizontalAlign', i, 'center'),
|
|
429
|
+
verticalAlign: this.getNodeParameter('imageTextVerticalAlign', i, 'middle'),
|
|
430
|
+
paddingX: this.getNodeParameter('imageTextPaddingX', i, 20),
|
|
431
|
+
paddingY: this.getNodeParameter('imageTextPaddingY', i, 20),
|
|
432
|
+
x: this.getNodeParameter('imageTextX', i, '(w-text_w)/2'),
|
|
433
|
+
y: this.getNodeParameter('imageTextY', i, '(h-text_h)/2'),
|
|
434
|
+
};
|
|
435
|
+
outputPath = await operations_1.executeAddTextToImage.call(this, paths[0], text, textOptions, i);
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
409
438
|
case 'imageToVideo': {
|
|
410
439
|
const sourceParam = this.getNodeParameter('sourceImage', i, {});
|
|
411
440
|
const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
|
|
@@ -159,6 +159,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
|
159
159
|
// Find timestamp line (may be first or second line)
|
|
160
160
|
let timestampLine = '';
|
|
161
161
|
let textStartIndex = 0;
|
|
162
|
+
let inlineText = ''; // Text that may be on the same line as timestamp
|
|
162
163
|
for (let i = 0; i < lines.length; i++) {
|
|
163
164
|
if (lines[i].includes('-->')) {
|
|
164
165
|
timestampLine = lines[i];
|
|
@@ -168,15 +169,33 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|
|
168
169
|
}
|
|
169
170
|
if (!timestampLine)
|
|
170
171
|
continue;
|
|
171
|
-
// Parse timestamps: 00:00:01,000 --> 00:00:04,000
|
|
172
|
-
|
|
172
|
+
// Parse timestamps: 00:00:01,000 --> 00:00:04,000 [optional inline text]
|
|
173
|
+
// Extended regex to capture text after timestamp on same line
|
|
174
|
+
const match = timestampLine.match(/(\d{1,2}):(\d{2}):(\d{2})[,.](\d{3})\s*-->\s*(\d{1,2}):(\d{2}):(\d{2})[,.](\d{3})(?:\s+(.*))?/);
|
|
173
175
|
if (!match)
|
|
174
176
|
continue;
|
|
177
|
+
// Check if text exists on the same line as timestamp (non-standard SRT format)
|
|
178
|
+
if (match[9] && match[9].trim()) {
|
|
179
|
+
inlineText = match[9].trim();
|
|
180
|
+
}
|
|
175
181
|
// Convert to ASS time format: H:MM:SS.cc
|
|
176
182
|
const startTime = `${parseInt(match[1])}:${match[2]}:${match[3]}.${match[4].substring(0, 2)}`;
|
|
177
183
|
const endTime = `${parseInt(match[5])}:${match[6]}:${match[7]}.${match[8].substring(0, 2)}`;
|
|
178
|
-
// Get subtitle text
|
|
179
|
-
|
|
184
|
+
// Get subtitle text
|
|
185
|
+
// Priority: inline text (on timestamp line) > remaining lines
|
|
186
|
+
let text;
|
|
187
|
+
if (inlineText) {
|
|
188
|
+
// Non-standard format: text on same line as timestamp
|
|
189
|
+
// Also include any additional lines if present
|
|
190
|
+
const additionalLines = lines.slice(textStartIndex).filter(l => l.trim());
|
|
191
|
+
text = additionalLines.length > 0
|
|
192
|
+
? [inlineText, ...additionalLines].join('\\N')
|
|
193
|
+
: inlineText;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Standard format: text on separate lines
|
|
197
|
+
text = lines.slice(textStartIndex).join('\\N');
|
|
198
|
+
}
|
|
180
199
|
// Add horizontal padding when background is enabled
|
|
181
200
|
if (enableBackground && text.trim()) {
|
|
182
201
|
text = addTextPadding(text, paddingSpaces);
|
|
@@ -0,0 +1,131 @@
|
|
|
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 (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.executeAddTextToImage = void 0;
|
|
27
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
28
|
+
const path = __importStar(require("path"));
|
|
29
|
+
const ffmpeg = require("fluent-ffmpeg");
|
|
30
|
+
const utils_1 = require("../utils");
|
|
31
|
+
const fs = __importStar(require("fs-extra"));
|
|
32
|
+
function getPositionFromAlignment(horizontalAlign, verticalAlign, paddingX, paddingY) {
|
|
33
|
+
let x;
|
|
34
|
+
let y;
|
|
35
|
+
// Set X position based on horizontal alignment
|
|
36
|
+
switch (horizontalAlign) {
|
|
37
|
+
case 'left':
|
|
38
|
+
x = `${paddingX}`;
|
|
39
|
+
break;
|
|
40
|
+
case 'right':
|
|
41
|
+
x = `w-text_w-${paddingX}`;
|
|
42
|
+
break;
|
|
43
|
+
case 'center':
|
|
44
|
+
default:
|
|
45
|
+
x = '(w-text_w)/2';
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
// Set Y position based on vertical alignment
|
|
49
|
+
switch (verticalAlign) {
|
|
50
|
+
case 'top':
|
|
51
|
+
y = `${paddingY}`;
|
|
52
|
+
break;
|
|
53
|
+
case 'bottom':
|
|
54
|
+
y = `h-th-${paddingY}`;
|
|
55
|
+
break;
|
|
56
|
+
case 'middle':
|
|
57
|
+
default:
|
|
58
|
+
y = '(h-text_h)/2';
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
return { x, y };
|
|
62
|
+
}
|
|
63
|
+
async function executeAddTextToImage(imagePath, text, options, itemIndex) {
|
|
64
|
+
var _a, _b, _c;
|
|
65
|
+
// Get font (includes bundled, user, and system fonts)
|
|
66
|
+
const allFonts = (0, utils_1.getAvailableFonts)(true);
|
|
67
|
+
const fontKey = options.fontKey || 'noto-sans-kr';
|
|
68
|
+
const font = allFonts[fontKey];
|
|
69
|
+
if (!font || !font.path) {
|
|
70
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font '${fontKey}' is not valid or its file path is missing.`, { itemIndex });
|
|
71
|
+
}
|
|
72
|
+
const fontPath = font.path;
|
|
73
|
+
// Set default values for text options
|
|
74
|
+
const fontSize = options.size || 48;
|
|
75
|
+
const fontColor = options.color || 'white';
|
|
76
|
+
// Outline options
|
|
77
|
+
const outlineWidth = options.outlineWidth || 0;
|
|
78
|
+
const outlineColor = options.outlineColor || 'black';
|
|
79
|
+
// Background box options
|
|
80
|
+
const enableBackground = options.enableBackground || false;
|
|
81
|
+
const backgroundColor = options.backgroundColor || 'black';
|
|
82
|
+
const backgroundOpacity = (_a = options.backgroundOpacity) !== null && _a !== void 0 ? _a : 0.5;
|
|
83
|
+
const boxPadding = options.boxPadding || 5;
|
|
84
|
+
// Handle position based on position type
|
|
85
|
+
let positionX;
|
|
86
|
+
let positionY;
|
|
87
|
+
const positionType = options.positionType || 'alignment';
|
|
88
|
+
if (positionType === 'alignment') {
|
|
89
|
+
const horizontalAlign = options.horizontalAlign || 'center';
|
|
90
|
+
const verticalAlign = options.verticalAlign || 'middle';
|
|
91
|
+
const paddingX = (_b = options.paddingX) !== null && _b !== void 0 ? _b : 20;
|
|
92
|
+
const paddingY = (_c = options.paddingY) !== null && _c !== void 0 ? _c : 20;
|
|
93
|
+
const position = getPositionFromAlignment(horizontalAlign, verticalAlign, paddingX, paddingY);
|
|
94
|
+
positionX = position.x;
|
|
95
|
+
positionY = position.y;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Custom position
|
|
99
|
+
positionX = options.x || '(w-text_w)/2';
|
|
100
|
+
positionY = options.y || '(h-text_h)/2';
|
|
101
|
+
}
|
|
102
|
+
// Use the same extension as input image
|
|
103
|
+
const inputExt = path.extname(imagePath);
|
|
104
|
+
const outputPath = (0, utils_1.getTempFile)(inputExt);
|
|
105
|
+
// Escape single quotes in text
|
|
106
|
+
const escapedText = text.replace(/'/g, `''`);
|
|
107
|
+
// Build drawtext filter
|
|
108
|
+
let drawtext = `drawtext=fontfile=${fontPath}:text='${escapedText}':fontsize=${fontSize}:fontcolor=${fontColor}:x=${positionX}:y=${positionY}`;
|
|
109
|
+
// Add outline (border) if width > 0
|
|
110
|
+
if (outlineWidth > 0) {
|
|
111
|
+
drawtext += `:borderw=${outlineWidth}:bordercolor=${outlineColor}`;
|
|
112
|
+
}
|
|
113
|
+
// Add background box if enabled
|
|
114
|
+
if (enableBackground) {
|
|
115
|
+
drawtext += `:box=1:boxcolor=${backgroundColor}@${backgroundOpacity}:boxborderw=${boxPadding}`;
|
|
116
|
+
}
|
|
117
|
+
const command = ffmpeg(imagePath)
|
|
118
|
+
.videoFilters(drawtext)
|
|
119
|
+
.frames(1)
|
|
120
|
+
.save(outputPath);
|
|
121
|
+
try {
|
|
122
|
+
await (0, utils_1.runFfmpeg)(command);
|
|
123
|
+
return outputPath;
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
// Clean up output file if creation failed
|
|
127
|
+
await fs.remove(outputPath).catch(() => { });
|
|
128
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error adding text to image. FFmpeg error: ${error.message}`, { itemIndex });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.executeAddTextToImage = executeAddTextToImage;
|
|
@@ -20,6 +20,7 @@ __exportStar(require("./speed"), exports);
|
|
|
20
20
|
__exportStar(require("./mixAudio"), exports);
|
|
21
21
|
__exportStar(require("./addSubtitle"), exports);
|
|
22
22
|
__exportStar(require("./addText"), exports);
|
|
23
|
+
__exportStar(require("./addTextToImage"), exports);
|
|
23
24
|
__exportStar(require("./extractAudio"), exports);
|
|
24
25
|
__exportStar(require("./separateAudio"), exports);
|
|
25
26
|
__exportStar(require("./multiVideoTransition"), exports);
|
|
@@ -14,6 +14,11 @@ exports.imageProperties = [
|
|
|
14
14
|
},
|
|
15
15
|
},
|
|
16
16
|
options: [
|
|
17
|
+
{
|
|
18
|
+
name: 'Add Text',
|
|
19
|
+
value: 'addTextToImage',
|
|
20
|
+
description: 'Overlay text onto an image',
|
|
21
|
+
},
|
|
17
22
|
{
|
|
18
23
|
name: 'To Video',
|
|
19
24
|
value: 'imageToVideo',
|
|
@@ -28,6 +33,299 @@ exports.imageProperties = [
|
|
|
28
33
|
default: 'imageToVideo',
|
|
29
34
|
},
|
|
30
35
|
// =============================
|
|
36
|
+
// == ADD TEXT TO IMAGE FIELDS ==
|
|
37
|
+
// =============================
|
|
38
|
+
{
|
|
39
|
+
displayName: 'Source Image',
|
|
40
|
+
name: 'sourceImageText',
|
|
41
|
+
type: 'fixedCollection',
|
|
42
|
+
placeholder: 'Add Image Source',
|
|
43
|
+
displayOptions: {
|
|
44
|
+
show: {
|
|
45
|
+
resource: ['image'],
|
|
46
|
+
operation: ['addTextToImage'],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
default: {},
|
|
50
|
+
options: [
|
|
51
|
+
{
|
|
52
|
+
displayName: 'Source',
|
|
53
|
+
name: 'source',
|
|
54
|
+
values: [
|
|
55
|
+
{
|
|
56
|
+
displayName: 'Source Type', name: 'sourceType', type: 'options',
|
|
57
|
+
options: [{ name: 'URL', value: 'url' }, { name: 'Binary Data', value: 'binary' }],
|
|
58
|
+
default: 'url',
|
|
59
|
+
},
|
|
60
|
+
{ displayName: 'Value', name: 'value', type: 'string', default: '', placeholder: 'https://example.com/image.png', displayOptions: { show: { sourceType: ['url'] } } },
|
|
61
|
+
{ displayName: 'Binary Property', name: 'binaryProperty', type: 'string', default: 'data', displayOptions: { show: { sourceType: ['binary'] } } },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
displayName: 'Text',
|
|
68
|
+
name: 'imageText',
|
|
69
|
+
type: 'string',
|
|
70
|
+
default: 'Hello, n8n!',
|
|
71
|
+
required: true,
|
|
72
|
+
displayOptions: {
|
|
73
|
+
show: {
|
|
74
|
+
resource: ['image'],
|
|
75
|
+
operation: ['addTextToImage'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
description: 'The text to overlay on the image',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
displayName: 'Font',
|
|
82
|
+
name: 'imageTextFontKey',
|
|
83
|
+
type: 'options',
|
|
84
|
+
typeOptions: {
|
|
85
|
+
loadOptionsMethod: 'getFonts',
|
|
86
|
+
},
|
|
87
|
+
default: 'noto-sans-kr',
|
|
88
|
+
displayOptions: {
|
|
89
|
+
show: {
|
|
90
|
+
resource: ['image'],
|
|
91
|
+
operation: ['addTextToImage'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
description: 'Select a font for the text',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
displayName: 'Font Size',
|
|
98
|
+
name: 'imageTextSize',
|
|
99
|
+
type: 'number',
|
|
100
|
+
default: 48,
|
|
101
|
+
typeOptions: {
|
|
102
|
+
minValue: 1,
|
|
103
|
+
},
|
|
104
|
+
displayOptions: {
|
|
105
|
+
show: {
|
|
106
|
+
resource: ['image'],
|
|
107
|
+
operation: ['addTextToImage'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
description: 'Font size in pixels',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
displayName: 'Color',
|
|
114
|
+
name: 'imageTextColor',
|
|
115
|
+
type: 'string',
|
|
116
|
+
default: 'white',
|
|
117
|
+
displayOptions: {
|
|
118
|
+
show: {
|
|
119
|
+
resource: ['image'],
|
|
120
|
+
operation: ['addTextToImage'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
description: 'Text color (e.g., white, #FF0000, rgb(255,0,0))',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
displayName: 'Outline Width',
|
|
127
|
+
name: 'imageTextOutlineWidth',
|
|
128
|
+
type: 'number',
|
|
129
|
+
default: 0,
|
|
130
|
+
typeOptions: {
|
|
131
|
+
minValue: 0,
|
|
132
|
+
},
|
|
133
|
+
displayOptions: {
|
|
134
|
+
show: {
|
|
135
|
+
resource: ['image'],
|
|
136
|
+
operation: ['addTextToImage'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
description: 'Width of the text outline/border in pixels. Set to 0 for no outline.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
displayName: 'Outline Color',
|
|
143
|
+
name: 'imageTextOutlineColor',
|
|
144
|
+
type: 'string',
|
|
145
|
+
default: 'black',
|
|
146
|
+
displayOptions: {
|
|
147
|
+
show: {
|
|
148
|
+
resource: ['image'],
|
|
149
|
+
operation: ['addTextToImage'],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
description: 'Color of the text outline/border',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
displayName: 'Enable Background Box',
|
|
156
|
+
name: 'imageTextEnableBackground',
|
|
157
|
+
type: 'boolean',
|
|
158
|
+
default: false,
|
|
159
|
+
displayOptions: {
|
|
160
|
+
show: {
|
|
161
|
+
resource: ['image'],
|
|
162
|
+
operation: ['addTextToImage'],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
description: 'Whether to add a background box behind the text',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
displayName: 'Background Color',
|
|
169
|
+
name: 'imageTextBackgroundColor',
|
|
170
|
+
type: 'string',
|
|
171
|
+
default: 'black',
|
|
172
|
+
displayOptions: {
|
|
173
|
+
show: {
|
|
174
|
+
resource: ['image'],
|
|
175
|
+
operation: ['addTextToImage'],
|
|
176
|
+
imageTextEnableBackground: [true],
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
description: 'Color of the background box',
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
displayName: 'Background Opacity',
|
|
183
|
+
name: 'imageTextBackgroundOpacity',
|
|
184
|
+
type: 'number',
|
|
185
|
+
default: 0.5,
|
|
186
|
+
typeOptions: {
|
|
187
|
+
minValue: 0,
|
|
188
|
+
maxValue: 1,
|
|
189
|
+
numberStepSize: 0.1,
|
|
190
|
+
},
|
|
191
|
+
displayOptions: {
|
|
192
|
+
show: {
|
|
193
|
+
resource: ['image'],
|
|
194
|
+
operation: ['addTextToImage'],
|
|
195
|
+
imageTextEnableBackground: [true],
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
description: 'Opacity of the background box (0 = transparent, 1 = opaque)',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
displayName: 'Box Padding',
|
|
202
|
+
name: 'imageTextBoxPadding',
|
|
203
|
+
type: 'number',
|
|
204
|
+
default: 5,
|
|
205
|
+
typeOptions: {
|
|
206
|
+
minValue: 0,
|
|
207
|
+
},
|
|
208
|
+
displayOptions: {
|
|
209
|
+
show: {
|
|
210
|
+
resource: ['image'],
|
|
211
|
+
operation: ['addTextToImage'],
|
|
212
|
+
imageTextEnableBackground: [true],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
description: 'Padding around the text inside the background box',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
displayName: 'Position Type',
|
|
219
|
+
name: 'imageTextPositionType',
|
|
220
|
+
type: 'options',
|
|
221
|
+
options: [
|
|
222
|
+
{ name: 'Alignment', value: 'alignment' },
|
|
223
|
+
{ name: 'Custom', value: 'custom' },
|
|
224
|
+
],
|
|
225
|
+
default: 'alignment',
|
|
226
|
+
displayOptions: {
|
|
227
|
+
show: {
|
|
228
|
+
resource: ['image'],
|
|
229
|
+
operation: ['addTextToImage'],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
description: 'How to position the text on the image',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
displayName: 'Horizontal Alignment',
|
|
236
|
+
name: 'imageTextHorizontalAlign',
|
|
237
|
+
type: 'options',
|
|
238
|
+
options: [
|
|
239
|
+
{ name: 'Left', value: 'left' },
|
|
240
|
+
{ name: 'Center', value: 'center' },
|
|
241
|
+
{ name: 'Right', value: 'right' },
|
|
242
|
+
],
|
|
243
|
+
default: 'center',
|
|
244
|
+
displayOptions: {
|
|
245
|
+
show: {
|
|
246
|
+
resource: ['image'],
|
|
247
|
+
operation: ['addTextToImage'],
|
|
248
|
+
imageTextPositionType: ['alignment'],
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
description: 'Horizontal alignment of the text',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
displayName: 'Vertical Alignment',
|
|
255
|
+
name: 'imageTextVerticalAlign',
|
|
256
|
+
type: 'options',
|
|
257
|
+
options: [
|
|
258
|
+
{ name: 'Top', value: 'top' },
|
|
259
|
+
{ name: 'Middle', value: 'middle' },
|
|
260
|
+
{ name: 'Bottom', value: 'bottom' },
|
|
261
|
+
],
|
|
262
|
+
default: 'middle',
|
|
263
|
+
displayOptions: {
|
|
264
|
+
show: {
|
|
265
|
+
resource: ['image'],
|
|
266
|
+
operation: ['addTextToImage'],
|
|
267
|
+
imageTextPositionType: ['alignment'],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
description: 'Vertical alignment of the text',
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
displayName: 'Padding X',
|
|
274
|
+
name: 'imageTextPaddingX',
|
|
275
|
+
type: 'number',
|
|
276
|
+
default: 20,
|
|
277
|
+
displayOptions: {
|
|
278
|
+
show: {
|
|
279
|
+
resource: ['image'],
|
|
280
|
+
operation: ['addTextToImage'],
|
|
281
|
+
imageTextPositionType: ['alignment'],
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
description: 'Horizontal padding from the edge in pixels',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
displayName: 'Padding Y',
|
|
288
|
+
name: 'imageTextPaddingY',
|
|
289
|
+
type: 'number',
|
|
290
|
+
default: 20,
|
|
291
|
+
displayOptions: {
|
|
292
|
+
show: {
|
|
293
|
+
resource: ['image'],
|
|
294
|
+
operation: ['addTextToImage'],
|
|
295
|
+
imageTextPositionType: ['alignment'],
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
description: 'Vertical padding from the edge in pixels',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
displayName: 'Position X',
|
|
302
|
+
name: 'imageTextX',
|
|
303
|
+
type: 'string',
|
|
304
|
+
default: '(w-text_w)/2',
|
|
305
|
+
displayOptions: {
|
|
306
|
+
show: {
|
|
307
|
+
resource: ['image'],
|
|
308
|
+
operation: ['addTextToImage'],
|
|
309
|
+
imageTextPositionType: ['custom'],
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
description: "X position. Supports FFmpeg expressions like '10', '(w-text_w)/2', 'w-text_w-10'.",
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
displayName: 'Position Y',
|
|
316
|
+
name: 'imageTextY',
|
|
317
|
+
type: 'string',
|
|
318
|
+
default: '(h-text_h)/2',
|
|
319
|
+
displayOptions: {
|
|
320
|
+
show: {
|
|
321
|
+
resource: ['image'],
|
|
322
|
+
operation: ['addTextToImage'],
|
|
323
|
+
imageTextPositionType: ['custom'],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
description: "Y position. Supports FFmpeg expressions like '10', '(h-text_h)/2', 'h-th-10'.",
|
|
327
|
+
},
|
|
328
|
+
// =============================
|
|
31
329
|
// == IMAGE TO VIDEO FIELDS ==
|
|
32
330
|
// =============================
|
|
33
331
|
{
|
package/package.json
CHANGED