@lee-jisoo/n8n-nodes-mediafx 1.6.2 → 1.6.3

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.
@@ -42,8 +42,6 @@ function escapeSubtitlePath(filePath) {
42
42
  }
43
43
  /**
44
44
  * Convert color to ASS format (&HAABBGGRR)
45
- * @param color - CSS color (hex #RRGGBB or named color)
46
- * @param opacity - Opacity 0-1 (0=transparent, 1=solid)
47
45
  */
48
46
  function colorToASS(color, opacity = 1) {
49
47
  let r = 'FF', g = 'FF', b = 'FF';
@@ -64,12 +62,11 @@ function colorToASS(color, opacity = 1) {
64
62
  magenta: 'FF00FF',
65
63
  orange: 'FFA500',
66
64
  };
67
- const hex = namedColors[color.toLowerCase()] || 'FFFFFF';
65
+ const hex = namedColors[(color || 'white').toLowerCase()] || 'FFFFFF';
68
66
  r = hex.substring(0, 2);
69
67
  g = hex.substring(2, 4);
70
68
  b = hex.substring(4, 6);
71
69
  }
72
- // ASS format: &HAABBGGRR (AA = alpha, 00=solid, FF=transparent)
73
70
  const alpha = Math.round((1 - opacity) * 255).toString(16).padStart(2, '0').toUpperCase();
74
71
  return `&H${alpha}${b}${g}${r}`.toUpperCase();
75
72
  }
@@ -85,47 +82,6 @@ function getASSAlignment(horizontalAlign, verticalAlign) {
85
82
  };
86
83
  return (_b = (_a = alignMap[verticalAlign]) === null || _a === void 0 ? void 0 : _a[horizontalAlign]) !== null && _b !== void 0 ? _b : 2;
87
84
  }
88
- /**
89
- * Parse SRT file and convert to ASS format
90
- */
91
- function convertSRTtoASS(srtContent, fontName, fontSize, primaryColor, outlineColor, outlineWidth, backgroundColor, backgroundOpacity, enableBackground, alignment, marginV) {
92
- // ASS Header
93
- const borderStyle = enableBackground ? 4 : 1; // 4=box, 1=outline+shadow
94
- const backColor = colorToASS(backgroundColor, backgroundOpacity);
95
- const assHeader = `[Script Info]
96
- Title: Converted from SRT
97
- ScriptType: v4.00+
98
- PlayResX: 1920
99
- PlayResY: 1080
100
- WrapStyle: 0
101
-
102
- [V4+ Styles]
103
- Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
104
- Style: Default,${fontName},${fontSize},${primaryColor},${primaryColor},${outlineColor},${backColor},0,0,0,0,100,100,0,0,${borderStyle},${outlineWidth},0,${alignment},20,20,${marginV},1
105
-
106
- [Events]
107
- Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
108
- `;
109
- // Parse SRT content
110
- const srtBlocks = srtContent.trim().split(/\n\s*\n/);
111
- const dialogueLines = [];
112
- for (const block of srtBlocks) {
113
- const lines = block.trim().split('\n');
114
- if (lines.length < 3)
115
- continue;
116
- // Parse timestamp line (format: 00:00:00,000 --> 00:00:00,000)
117
- const timestampLine = lines[1];
118
- const timestampMatch = timestampLine.match(/(\d{2}):(\d{2}):(\d{2})[,.](\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2})[,.](\d{3})/);
119
- if (!timestampMatch)
120
- continue;
121
- const startTime = `${timestampMatch[1]}:${timestampMatch[2]}:${timestampMatch[3]}.${timestampMatch[4].substring(0, 2)}`;
122
- const endTime = `${timestampMatch[5]}:${timestampMatch[6]}:${timestampMatch[7]}.${timestampMatch[8].substring(0, 2)}`;
123
- // Get text (lines 3+)
124
- const text = lines.slice(2).join('\\N').replace(/\r/g, '');
125
- dialogueLines.push(`Dialogue: 0,${startTime},${endTime},Default,,0,0,0,,${text}`);
126
- }
127
- return assHeader + dialogueLines.join('\n');
128
- }
129
85
  /**
130
86
  * Check if file is ASS/SSA format
131
87
  */
@@ -133,6 +89,27 @@ function isASSFile(filePath) {
133
89
  const ext = path.extname(filePath).toLowerCase();
134
90
  return ext === '.ass' || ext === '.ssa';
135
91
  }
92
+ /**
93
+ * Build force_style string for subtitles filter
94
+ */
95
+ function buildForceStyle(fontName, fontSize, primaryColor, outlineColor, outlineWidth, backColor, enableBackground, alignment, marginV) {
96
+ const borderStyle = enableBackground ? 4 : 1;
97
+ const styleParams = [
98
+ `FontName=${fontName}`,
99
+ `FontSize=${fontSize}`,
100
+ `PrimaryColour=${primaryColor}`,
101
+ `OutlineColour=${outlineColor}`,
102
+ `BackColour=${backColor}`,
103
+ `Bold=0`,
104
+ `Italic=0`,
105
+ `BorderStyle=${borderStyle}`,
106
+ `Outline=${outlineWidth}`,
107
+ `Shadow=0`,
108
+ `Alignment=${alignment}`,
109
+ `MarginV=${marginV}`,
110
+ ];
111
+ return styleParams.join(',');
112
+ }
136
113
  async function executeAddSubtitle(video, subtitleFile, style, itemIndex) {
137
114
  var _a, _b, _c, _d;
138
115
  const outputPath = (0, utils_1.getTempFile)(path.extname(video));
@@ -162,24 +139,22 @@ async function executeAddSubtitle(video, subtitleFile, style, itemIndex) {
162
139
  marginV = (_d = style.paddingY) !== null && _d !== void 0 ? _d : 20;
163
140
  }
164
141
  const alignment = getASSAlignment(horizontalAlign, verticalAlign);
165
- // 3. Prepare subtitle file
166
- let finalSubtitlePath = subtitleFile;
167
- let tempAssFile = null;
142
+ // 3. Convert colors to ASS format
143
+ const primaryColorASS = colorToASS(fontColor, 1);
144
+ const outlineColorASS = colorToASS(outlineColor, 1);
145
+ const backColorASS = colorToASS(backgroundColor, backgroundOpacity);
146
+ // 4. Build FFmpeg command
147
+ const escapedPath = escapeSubtitlePath(subtitleFile);
148
+ let subtitlesFilter;
168
149
  if (isASSFile(subtitleFile)) {
169
- // ASS file: use directly (ignore style options)
170
- finalSubtitlePath = subtitleFile;
150
+ // ASS/SSA file: use directly without force_style
151
+ subtitlesFilter = `subtitles='${escapedPath}'`;
171
152
  }
172
153
  else {
173
- // SRT file: convert to ASS with styles
174
- const srtContent = await fs.readFile(subtitleFile, 'utf-8');
175
- const assContent = convertSRTtoASS(srtContent, fontName, fontSize, colorToASS(fontColor, 1), colorToASS(outlineColor, 1), outlineWidth, backgroundColor, backgroundOpacity, enableBackground, alignment, marginV);
176
- tempAssFile = (0, utils_1.getTempFile)('.ass');
177
- await fs.writeFile(tempAssFile, assContent, 'utf-8');
178
- finalSubtitlePath = tempAssFile;
154
+ // SRT file: apply force_style
155
+ const forceStyle = buildForceStyle(fontName, fontSize, primaryColorASS, outlineColorASS, outlineWidth, backColorASS, enableBackground, alignment, marginV);
156
+ subtitlesFilter = `subtitles='${escapedPath}':force_style='${forceStyle}'`;
179
157
  }
180
- // 4. Build FFmpeg command
181
- const escapedPath = escapeSubtitlePath(finalSubtitlePath);
182
- const subtitlesFilter = `subtitles='${escapedPath}'`;
183
158
  const command = ffmpeg(video)
184
159
  .videoFilters([subtitlesFilter])
185
160
  .audioCodec('copy')
@@ -192,11 +167,5 @@ async function executeAddSubtitle(video, subtitleFile, style, itemIndex) {
192
167
  await fs.remove(outputPath).catch(() => { });
193
168
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Error adding subtitles to video. FFmpeg error: ${error.message}`, { itemIndex });
194
169
  }
195
- finally {
196
- // Clean up temp ASS file
197
- if (tempAssFile) {
198
- await fs.remove(tempAssFile).catch(() => { });
199
- }
200
- }
201
170
  }
202
171
  exports.executeAddSubtitle = executeAddSubtitle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lee-jisoo/n8n-nodes-mediafx",
3
- "version": "1.6.2",
3
+ "version": "1.6.3",
4
4
  "description": "N8N custom nodes for video editing and media processing (Enhanced fork with Speed control and Subtitle fixes)",
5
5
  "license": "MIT",
6
6
  "author": {