@lee-jisoo/n8n-nodes-mediafx 1.6.13 → 1.6.15

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.ko-KR.md CHANGED
@@ -9,7 +9,20 @@
9
9
  <!-- Optional: Add a GIF of the node in action here -->
10
10
  <!-- <p align="center"><img src="link/to/your/demo.gif" alt="MediaFX Node Demo"></p> -->
11
11
 
12
- ## v1.4.2의 새로운 기능
12
+ ## v1.6.15의 새로운 기능
13
+
14
+ - **🔍 미디어 분석 (Probe)**: 비디오/오디오 파일에서 메타정보를 추출하는 새로운 기능
15
+ - 포맷 정보: 파일명, 포맷, 재생시간, 파일크기, 비트레이트
16
+ - 비디오 스트림: 코덱, 해상도, 프레임레이트, 화면비율, 픽셀포맷
17
+ - 오디오 스트림: 코덱, 샘플레이트, 채널 수, 채널 레이아웃
18
+ - 태그: 제목, 아티스트, 앨범 등 파일에 포함된 메타데이터
19
+
20
+ - **🔤 시스템 폰트 지원**: 시스템에 설치된 폰트를 사용 가능
21
+ - 글꼴 > 목록 작업에서 "시스템 폰트 포함" 옵션으로 시스템 폰트 스캔
22
+ - 텍스트/자막 작업에서 시스템 폰트 경로 직접 지정 가능
23
+ - macOS, Linux, Windows의 시스템 폰트 디렉토리 지원
24
+
25
+ ## v1.4.2의 기능
13
26
 
14
27
  - **버그 수정**: 비디오 소스가 올바르게 설정되지 않은 경우 오디오 분리 작업에서 "did not produce an output" 오류 수정
15
28
  - 소스가 누락된 경우 명확한 오류 메시지와 함께 적절한 유효성 검사 추가
@@ -104,6 +117,9 @@
104
117
  - `문자열 추가`: 비디오에 텍스트 오버레이를 추가합니다.
105
118
  - `자막 추가`: `.srt` 파일에서 자막을 추가합니다.
106
119
 
120
+ #### **분석 (Probe)** 리소스
121
+ - `메타정보 조회`: 비디오/오디오 파일에서 포맷 정보, 스트림 정보, 태그 등 상세한 메타데이터를 추출합니다.
122
+
107
123
  #### **글꼴** 리소스
108
124
  - `목록`: 사용 가능한 모든 시스템 및 사용자 업로드 글꼴 목록을 가져옵니다.
109
125
  - `업로드`: 텍스트 작업에 사용할 사용자 지정 글꼴 파일(`.ttf`, `.otf`)을 업로드합니다.
@@ -111,6 +127,75 @@
111
127
 
112
128
  ## 사용 예제
113
129
 
130
+ ### 미디어 메타정보 조회 (v1.6.15 신규)
131
+ 비디오 또는 오디오 파일에서 메타정보를 추출합니다.
132
+
133
+ ```json
134
+ {
135
+ "resource": "probe",
136
+ "operation": "getMetadata",
137
+ "probeSource": {
138
+ "source": { "sourceType": "binary", "binaryProperty": "data" }
139
+ }
140
+ }
141
+ ```
142
+
143
+ 출력 예시:
144
+ ```json
145
+ {
146
+ "success": true,
147
+ "operation": "getMetadata",
148
+ "format": {
149
+ "filename": "video.mp4",
150
+ "formatName": "mov,mp4,m4a,3gp,3g2,mj2",
151
+ "duration": 120.5,
152
+ "size": 15728640,
153
+ "bitRate": 1048576
154
+ },
155
+ "video": {
156
+ "codec": "h264",
157
+ "width": 1920,
158
+ "height": 1080,
159
+ "frameRate": 30,
160
+ "aspectRatio": "16:9"
161
+ },
162
+ "audio": {
163
+ "codec": "aac",
164
+ "sampleRate": 48000,
165
+ "channels": 2,
166
+ "channelLayout": "stereo"
167
+ },
168
+ "hasVideo": true,
169
+ "hasAudio": true
170
+ }
171
+ ```
172
+
173
+ ### 시스템 폰트 사용하기 (v1.6.15 신규)
174
+
175
+ 먼저 시스템에 설치된 폰트 목록을 확인합니다:
176
+ ```json
177
+ {
178
+ "resource": "font",
179
+ "operation": "list",
180
+ "filterOptions": {
181
+ "includeSystemFonts": true,
182
+ "fontType": "system"
183
+ }
184
+ }
185
+ ```
186
+
187
+ 시스템 폰트로 자막 추가하기:
188
+ ```json
189
+ {
190
+ "resource": "subtitle",
191
+ "operation": "addSubtitle",
192
+ "fontSource": "system",
193
+ "systemFontPath": "/System/Library/Fonts/AppleSDGothicNeo.ttc",
194
+ "size": 48,
195
+ "color": "white"
196
+ }
197
+ ```
198
+
114
199
  ### Merge 노드와 함께 사용하기 (v1.4.0 신규)
115
200
  n8n의 Merge 노드를 사용하여 여러 비디오 입력을 결합할 때:
116
201
 
package/README.md CHANGED
@@ -10,6 +10,38 @@ 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.15
14
+ **New Features**
15
+
16
+ - **🔍 Get Metadata (Probe)**: Extract comprehensive metadata from video/audio files
17
+ - Format info: filename, format, duration, size, bitrate
18
+ - Video stream: codec, resolution, frame rate, aspect ratio, pixel format
19
+ - Audio stream: codec, sample rate, channels, channel layout
20
+ - Tags: title, artist, album, and other embedded metadata
21
+
22
+ - **🔤 System Font Support**: Use fonts installed on your system
23
+ - Scan system fonts with Font > List operation (enable "Include System Fonts")
24
+ - Directly specify system font path in Text/Subtitle operations
25
+ - Supports macOS, Linux, and Windows system font directories
26
+
27
+ ### v1.6.14
28
+ **Subtitle Enhancements (v1.6.1 ~ v1.6.14)**
29
+
30
+ - **🎨 Background Box Padding**: Fixed text sticking to background box edges
31
+ - Added horizontal padding (`\h` hard spaces) when background is enabled
32
+ - Padding automatically scales with font size
33
+
34
+ - **🎯 Full ASS Format Support**: Complete subtitle styling overhaul
35
+ - SRT to ASS auto-conversion for advanced styling
36
+ - Dynamic video resolution detection (supports vertical videos like 1080x1920)
37
+ - Proper `BorderStyle=3` implementation for opaque background boxes
38
+ - Fixed background color transparency with correct `Outline=0, Shadow=1` settings
39
+
40
+ - **📍 Flexible Positioning**:
41
+ - 9-point alignment grid (top/middle/bottom × left/center/right)
42
+ - Customizable padding (X/Y margins)
43
+ - Outline width and color options
44
+
13
45
  ### v1.6.0
14
46
  - **🎬 Speed Operation**: Adjust video playback speed (slow motion or fast forward)
15
47
  - Speed range: 0.25x to 4x
@@ -80,6 +112,11 @@ RUN cd /home/node/.n8n/nodes && npm install @lee-jisoo/n8n-nodes-mediafx
80
112
  | **Add String** | Burn text overlay with styling |
81
113
  | **Add Subtitle** | Add subtitles from SRT file ⭐ FIXED |
82
114
 
115
+ ### Probe Operations
116
+ | Operation | Description |
117
+ |-----------|-------------|
118
+ | **Get Metadata** | Extract metadata from video/audio files (format, streams, tags) |
119
+
83
120
  ### Font Operations
84
121
  | Operation | Description |
85
122
  |-----------|-------------|
@@ -89,7 +126,75 @@ RUN cd /home/node/.n8n/nodes && npm install @lee-jisoo/n8n-nodes-mediafx
89
126
 
90
127
  ## Usage Examples
91
128
 
92
- ### Speed Adjustment (New!)
129
+ ### Get Media Metadata (New!)
130
+ Extract metadata from video or audio files:
131
+ ```json
132
+ {
133
+ "resource": "probe",
134
+ "operation": "getMetadata",
135
+ "probeSource": {
136
+ "source": { "sourceType": "binary", "binaryProperty": "data" }
137
+ }
138
+ }
139
+ ```
140
+
141
+ Example output:
142
+ ```json
143
+ {
144
+ "success": true,
145
+ "operation": "getMetadata",
146
+ "format": {
147
+ "filename": "video.mp4",
148
+ "formatName": "mov,mp4,m4a,3gp,3g2,mj2",
149
+ "duration": 120.5,
150
+ "size": 15728640,
151
+ "bitRate": 1048576
152
+ },
153
+ "video": {
154
+ "codec": "h264",
155
+ "width": 1920,
156
+ "height": 1080,
157
+ "frameRate": 30,
158
+ "aspectRatio": "16:9"
159
+ },
160
+ "audio": {
161
+ "codec": "aac",
162
+ "sampleRate": 48000,
163
+ "channels": 2,
164
+ "channelLayout": "stereo"
165
+ },
166
+ "hasVideo": true,
167
+ "hasAudio": true
168
+ }
169
+ ```
170
+
171
+ ### Using System Fonts (New!)
172
+
173
+ First, list available system fonts:
174
+ ```json
175
+ {
176
+ "resource": "font",
177
+ "operation": "list",
178
+ "filterOptions": {
179
+ "includeSystemFonts": true,
180
+ "fontType": "system"
181
+ }
182
+ }
183
+ ```
184
+
185
+ Add subtitles with a system font:
186
+ ```json
187
+ {
188
+ "resource": "subtitle",
189
+ "operation": "addSubtitle",
190
+ "fontSource": "system",
191
+ "systemFontPath": "/System/Library/Fonts/Helvetica.ttc",
192
+ "size": 48,
193
+ "color": "white"
194
+ }
195
+ ```
196
+
197
+ ### Speed Adjustment
93
198
  Create a 2x speed video:
94
199
  ```json
95
200
  {
@@ -33,6 +33,7 @@ const operations_1 = require("./operations");
33
33
  const audio_properties_1 = require("./properties/audio.properties");
34
34
  const font_properties_1 = require("./properties/font.properties");
35
35
  const image_properties_1 = require("./properties/image.properties");
36
+ const probe_properties_1 = require("./properties/probe.properties");
36
37
  const resources_properties_1 = require("./properties/resources.properties");
37
38
  const subtitle_properties_1 = require("./properties/subtitle.properties");
38
39
  const video_properties_1 = require("./properties/video.properties");
@@ -59,6 +60,7 @@ class MediaFX {
59
60
  ...audio_properties_1.audioProperties,
60
61
  ...subtitle_properties_1.subtitleProperties,
61
62
  ...image_properties_1.imageProperties,
63
+ ...probe_properties_1.probeProperties,
62
64
  ...font_properties_1.fontProperties,
63
65
  ],
64
66
  };
@@ -126,6 +128,7 @@ class MediaFX {
126
128
  };
127
129
  }
128
130
  async execute() {
131
+ var _a;
129
132
  const items = this.getInputData();
130
133
  const returnData = [];
131
134
  // Periodically clean up old temporary files (every 10th execution)
@@ -150,7 +153,8 @@ class MediaFX {
150
153
  case 'list': {
151
154
  const filterOptions = this.getNodeParameter('filterOptions', i, {});
152
155
  const fontTypeFilter = filterOptions.fontType || 'all';
153
- const allFonts = (0, utils_1.getAvailableFonts)();
156
+ const includeSystemFonts = (_a = filterOptions.includeSystemFonts) !== null && _a !== void 0 ? _a : false;
157
+ const allFonts = (0, utils_1.getAvailableFonts)(includeSystemFonts);
154
158
  if (fontTypeFilter === 'all') {
155
159
  resultData = allFonts;
156
160
  }
@@ -196,6 +200,34 @@ class MediaFX {
196
200
  }
197
201
  }
198
202
  // ===================================
203
+ // PROBE RESOURCE OPERATIONS
204
+ // ===================================
205
+ else if (resource === 'probe') {
206
+ switch (operation) {
207
+ case 'getMetadata': {
208
+ const sourceParam = this.getNodeParameter('probeSource', i, {});
209
+ if (!sourceParam.source) {
210
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Media source is required. Please add a media source.', { itemIndex: i });
211
+ }
212
+ const { paths, cleanup: c } = await (0, utils_1.resolveInputs)(this, i, [sourceParam.source]);
213
+ cleanup = c;
214
+ const metadata = await operations_1.executeGetMetadata.call(this, paths[0], i);
215
+ // Push result with metadata as JSON
216
+ returnData.push({
217
+ json: {
218
+ success: true,
219
+ operation: 'getMetadata',
220
+ ...metadata,
221
+ },
222
+ pairedItem: { item: i },
223
+ });
224
+ // Cleanup and continue to next item
225
+ await cleanup();
226
+ continue;
227
+ }
228
+ }
229
+ }
230
+ // ===================================
199
231
  // MEDIA RESOURCE OPERATIONS
200
232
  // ===================================
201
233
  else {
@@ -304,8 +336,11 @@ class MediaFX {
304
336
  await subFileCleanup();
305
337
  };
306
338
  // Collect style options from individual parameters
339
+ const fontSource = this.getNodeParameter('fontSource', i, 'bundled');
307
340
  const style = {
308
- fontKey: this.getNodeParameter('fontKey', i, 'noto-sans-kr'),
341
+ fontSource,
342
+ fontKey: fontSource === 'bundled' ? this.getNodeParameter('fontKey', i, 'noto-sans-kr') : undefined,
343
+ systemFontPath: fontSource === 'system' ? this.getNodeParameter('systemFontPath', i, '') : undefined,
309
344
  size: this.getNodeParameter('size', i, 48),
310
345
  color: this.getNodeParameter('color', i, 'white'),
311
346
  outlineWidth: this.getNodeParameter('outlineWidth', i, 1),
@@ -333,8 +368,11 @@ class MediaFX {
333
368
  const startTime = this.getNodeParameter('startTime', i, 0);
334
369
  const endTime = this.getNodeParameter('endTime', i, 5);
335
370
  // Collect style options from individual parameters
371
+ const textFontSource = this.getNodeParameter('fontSource', i, 'bundled');
336
372
  const textOptions = {
337
- fontKey: this.getNodeParameter('fontKey', i, 'noto-sans-kr'),
373
+ fontSource: textFontSource,
374
+ fontKey: textFontSource === 'bundled' ? this.getNodeParameter('fontKey', i, 'noto-sans-kr') : undefined,
375
+ systemFontPath: textFontSource === 'system' ? this.getNodeParameter('systemFontPath', i, '') : undefined,
338
376
  size: this.getNodeParameter('size', i, 48),
339
377
  color: this.getNodeParameter('color', i, 'white'),
340
378
  outlineWidth: this.getNodeParameter('outlineWidth', i, 1),
@@ -190,12 +190,26 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
190
190
  async function executeAddSubtitle(video, subtitleFile, style, itemIndex) {
191
191
  var _a, _b, _c, _d, _e;
192
192
  const outputPath = (0, utils_1.getTempFile)(path.extname(video));
193
- // 1. Get Font
194
- const allFonts = (0, utils_1.getAvailableFonts)();
195
- const fontKey = style.fontKey || 'noto-sans-kr';
196
- const font = allFonts[fontKey];
197
- if (!font || !font.path) {
198
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font key '${fontKey}' is not valid or its file path is missing.`, { itemIndex });
193
+ // 1. Get Font (support both bundled and system fonts)
194
+ const fontSource = style.fontSource || 'bundled';
195
+ let font = null;
196
+ if (fontSource === 'system') {
197
+ const systemFontPath = style.systemFontPath;
198
+ if (!systemFontPath) {
199
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'System font path is required when using system fonts.', { itemIndex });
200
+ }
201
+ font = (0, utils_1.getFontByPath)(systemFontPath);
202
+ if (!font) {
203
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `System font not found at path '${systemFontPath}'. Please check the path and ensure it's a valid font file (TTF, OTF, TTC).`, { itemIndex });
204
+ }
205
+ }
206
+ else {
207
+ const allFonts = (0, utils_1.getAvailableFonts)();
208
+ const fontKey = style.fontKey || 'noto-sans-kr';
209
+ font = allFonts[fontKey] || null;
210
+ if (!font || !font.path) {
211
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font key '${fontKey}' is not valid or its file path is missing.`, { itemIndex });
212
+ }
199
213
  }
200
214
  const fontName = font.name || 'Sans';
201
215
  const fontSize = style.size || 48;
@@ -62,11 +62,26 @@ function getPositionFromAlignment(horizontalAlign, verticalAlign, paddingX, padd
62
62
  }
63
63
  async function executeAddText(video, text, options, itemIndex) {
64
64
  var _a, _b, _c, _d;
65
- const allFonts = (0, utils_1.getAvailableFonts)();
66
- const fontKey = options.fontKey || 'noto-sans-kr';
67
- const font = allFonts[fontKey];
68
- if (!font || !font.path) {
69
- throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font key '${fontKey}' is not valid or its file path is missing.`, { itemIndex });
65
+ // Support both bundled and system fonts
66
+ const fontSource = options.fontSource || 'bundled';
67
+ let font = null;
68
+ if (fontSource === 'system') {
69
+ const systemFontPath = options.systemFontPath;
70
+ if (!systemFontPath) {
71
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'System font path is required when using system fonts.', { itemIndex });
72
+ }
73
+ font = (0, utils_1.getFontByPath)(systemFontPath);
74
+ if (!font) {
75
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `System font not found at path '${systemFontPath}'. Please check the path and ensure it's a valid font file (TTF, OTF, TTC).`, { itemIndex });
76
+ }
77
+ }
78
+ else {
79
+ const allFonts = (0, utils_1.getAvailableFonts)();
80
+ const fontKey = options.fontKey || 'noto-sans-kr';
81
+ font = allFonts[fontKey] || null;
82
+ if (!font || !font.path) {
83
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Selected font key '${fontKey}' is not valid or its file path is missing.`, { itemIndex });
84
+ }
70
85
  }
71
86
  const fontPath = font.path;
72
87
  // Set default values for text options
@@ -0,0 +1,41 @@
1
+ import { IExecuteFunctions } from 'n8n-workflow';
2
+ import { IDataObject } from 'n8n-workflow';
3
+ interface StreamInfo {
4
+ index: number;
5
+ type: string;
6
+ codec: string;
7
+ codecLongName?: string;
8
+ profile?: string;
9
+ width?: number;
10
+ height?: number;
11
+ aspectRatio?: string;
12
+ frameRate?: number;
13
+ bitRate?: number;
14
+ pixelFormat?: string;
15
+ sampleRate?: number;
16
+ channels?: number;
17
+ channelLayout?: string;
18
+ bitsPerSample?: number;
19
+ duration?: number;
20
+ language?: string;
21
+ title?: string;
22
+ }
23
+ interface MediaMetadata {
24
+ format: {
25
+ filename: string;
26
+ formatName: string;
27
+ formatLongName?: string;
28
+ duration: number;
29
+ size: number;
30
+ bitRate: number;
31
+ probeScore?: number;
32
+ };
33
+ streams: StreamInfo[];
34
+ video?: StreamInfo;
35
+ audio?: StreamInfo;
36
+ hasVideo: boolean;
37
+ hasAudio: boolean;
38
+ tags?: IDataObject;
39
+ }
40
+ export declare function executeGetMetadata(this: IExecuteFunctions, inputPath: string, _itemIndex: number): Promise<MediaMetadata>;
41
+ export {};
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.executeGetMetadata = void 0;
4
+ const ffmpeg = require("fluent-ffmpeg");
5
+ async function executeGetMetadata(inputPath, _itemIndex) {
6
+ return new Promise((resolve, reject) => {
7
+ ffmpeg.ffprobe(inputPath, (err, metadata) => {
8
+ if (err) {
9
+ return reject(new Error(`Failed to probe media file: ${err.message}`));
10
+ }
11
+ const streams = [];
12
+ let videoStream;
13
+ let audioStream;
14
+ for (const stream of metadata.streams) {
15
+ const streamInfo = {
16
+ index: stream.index,
17
+ type: stream.codec_type || 'unknown',
18
+ codec: stream.codec_name || 'unknown',
19
+ codecLongName: stream.codec_long_name,
20
+ profile: stream.profile !== undefined ? String(stream.profile) : undefined,
21
+ };
22
+ if (stream.codec_type === 'video') {
23
+ streamInfo.width = stream.width;
24
+ streamInfo.height = stream.height;
25
+ streamInfo.aspectRatio = stream.display_aspect_ratio;
26
+ streamInfo.pixelFormat = stream.pix_fmt;
27
+ // Calculate frame rate from r_frame_rate (e.g., "30000/1001" or "30/1")
28
+ if (stream.r_frame_rate) {
29
+ const parts = stream.r_frame_rate.split('/');
30
+ if (parts.length === 2) {
31
+ const num = parseFloat(parts[0]);
32
+ const den = parseFloat(parts[1]);
33
+ if (den !== 0) {
34
+ streamInfo.frameRate = Math.round((num / den) * 100) / 100;
35
+ }
36
+ }
37
+ }
38
+ if (stream.bit_rate) {
39
+ streamInfo.bitRate = parseInt(stream.bit_rate, 10);
40
+ }
41
+ if (stream.duration) {
42
+ streamInfo.duration = parseFloat(stream.duration);
43
+ }
44
+ // Extract tags
45
+ if (stream.tags) {
46
+ streamInfo.language = stream.tags.language;
47
+ streamInfo.title = stream.tags.title;
48
+ }
49
+ if (!videoStream) {
50
+ videoStream = streamInfo;
51
+ }
52
+ }
53
+ else if (stream.codec_type === 'audio') {
54
+ streamInfo.sampleRate = stream.sample_rate ? parseInt(String(stream.sample_rate), 10) : undefined;
55
+ streamInfo.channels = stream.channels;
56
+ streamInfo.channelLayout = stream.channel_layout;
57
+ streamInfo.bitsPerSample = stream.bits_per_sample;
58
+ if (stream.bit_rate) {
59
+ streamInfo.bitRate = parseInt(stream.bit_rate, 10);
60
+ }
61
+ if (stream.duration) {
62
+ streamInfo.duration = parseFloat(stream.duration);
63
+ }
64
+ // Extract tags
65
+ if (stream.tags) {
66
+ streamInfo.language = stream.tags.language;
67
+ streamInfo.title = stream.tags.title;
68
+ }
69
+ if (!audioStream) {
70
+ audioStream = streamInfo;
71
+ }
72
+ }
73
+ streams.push(streamInfo);
74
+ }
75
+ const format = metadata.format;
76
+ const result = {
77
+ format: {
78
+ filename: format.filename || inputPath,
79
+ formatName: format.format_name || 'unknown',
80
+ formatLongName: format.format_long_name,
81
+ duration: format.duration || 0,
82
+ size: format.size || 0,
83
+ bitRate: format.bit_rate ? parseInt(String(format.bit_rate), 10) : 0,
84
+ probeScore: format.probe_score,
85
+ },
86
+ streams,
87
+ hasVideo: !!videoStream,
88
+ hasAudio: !!audioStream,
89
+ };
90
+ if (videoStream) {
91
+ result.video = videoStream;
92
+ }
93
+ if (audioStream) {
94
+ result.audio = audioStream;
95
+ }
96
+ // Extract format tags (metadata like title, artist, album, etc.)
97
+ if (format.tags) {
98
+ result.tags = format.tags;
99
+ }
100
+ resolve(result);
101
+ });
102
+ });
103
+ }
104
+ exports.executeGetMetadata = executeGetMetadata;
@@ -11,3 +11,4 @@ export * from './singleVideoFade';
11
11
  export * from './imageToVideo';
12
12
  export * from './stampImage';
13
13
  export * from './overlayVideo';
14
+ export * from './getMetadata';
@@ -27,3 +27,4 @@ __exportStar(require("./singleVideoFade"), exports);
27
27
  __exportStar(require("./imageToVideo"), exports);
28
28
  __exportStar(require("./stampImage"), exports);
29
29
  __exportStar(require("./overlayVideo"), exports);
30
+ __exportStar(require("./getMetadata"), exports);
@@ -160,6 +160,13 @@ exports.fontProperties = [
160
160
  },
161
161
  default: {},
162
162
  options: [
163
+ {
164
+ displayName: 'Include System Fonts',
165
+ name: 'includeSystemFonts',
166
+ type: 'boolean',
167
+ default: false,
168
+ description: 'Whether to include fonts installed on the system (may be slow on first scan)',
169
+ },
163
170
  {
164
171
  displayName: 'Font Type',
165
172
  name: 'fontType',
@@ -169,6 +176,7 @@ exports.fontProperties = [
169
176
  { name: 'Korean Fonts', value: 'korean' },
170
177
  { name: 'Global Fonts', value: 'global' },
171
178
  { name: 'User Fonts', value: 'user' },
179
+ { name: 'System Fonts', value: 'system' },
172
180
  { name: 'Fallback Fonts', value: 'fallback' },
173
181
  ],
174
182
  default: 'all',
@@ -0,0 +1,2 @@
1
+ import { INodeProperties } from 'n8n-workflow';
2
+ export declare const probeProperties: INodeProperties[];
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.probeProperties = void 0;
4
+ exports.probeProperties = [
5
+ // Probe Operations
6
+ {
7
+ displayName: 'Operation',
8
+ name: 'operation',
9
+ type: 'options',
10
+ noDataExpression: true,
11
+ displayOptions: {
12
+ show: {
13
+ resource: ['probe'],
14
+ },
15
+ },
16
+ options: [
17
+ {
18
+ name: 'Get Metadata',
19
+ value: 'getMetadata',
20
+ description: 'Extract metadata information from video or audio files',
21
+ },
22
+ ],
23
+ default: 'getMetadata',
24
+ },
25
+ // ===================
26
+ // GET METADATA FIELDS
27
+ // ===================
28
+ {
29
+ displayName: 'Media Source',
30
+ name: 'probeSource',
31
+ type: 'fixedCollection',
32
+ placeholder: 'Add Media Source',
33
+ displayOptions: {
34
+ show: {
35
+ resource: ['probe'],
36
+ operation: ['getMetadata'],
37
+ },
38
+ },
39
+ default: {},
40
+ options: [
41
+ {
42
+ displayName: 'Source',
43
+ name: 'source',
44
+ values: [
45
+ {
46
+ displayName: 'Source Type',
47
+ name: 'sourceType',
48
+ type: 'options',
49
+ options: [
50
+ {
51
+ name: 'URL',
52
+ value: 'url',
53
+ },
54
+ {
55
+ name: 'Binary Data from Previous Node',
56
+ value: 'binary',
57
+ },
58
+ ],
59
+ default: 'url',
60
+ },
61
+ {
62
+ displayName: 'Value',
63
+ name: 'value',
64
+ type: 'string',
65
+ default: '',
66
+ placeholder: 'https://example.com/video.mp4 or https://example.com/audio.mp3',
67
+ description: 'URL of the media file to analyze',
68
+ displayOptions: { show: { sourceType: ['url'] } },
69
+ },
70
+ {
71
+ displayName: 'Binary Property',
72
+ name: 'binaryProperty',
73
+ type: 'string',
74
+ default: 'data',
75
+ description: 'Name of the binary property from the previous node',
76
+ placeholder: 'e.g., data',
77
+ displayOptions: { show: { sourceType: ['binary'] } },
78
+ },
79
+ ],
80
+ },
81
+ ],
82
+ description: 'The media file (video or audio) to analyze',
83
+ },
84
+ ];
@@ -24,6 +24,11 @@ exports.resourceSelection = [
24
24
  name: 'Image',
25
25
  value: 'image',
26
26
  },
27
+ {
28
+ name: 'Probe',
29
+ value: 'probe',
30
+ description: 'Analyze media files and extract metadata information',
31
+ },
27
32
  {
28
33
  name: 'Font',
29
34
  value: 'font',
@@ -129,6 +129,22 @@ exports.subtitleProperties = [
129
129
  // ===================
130
130
  // COMMON FONT & STYLE OPTIONS
131
131
  // ===================
132
+ {
133
+ displayName: 'Font Source',
134
+ name: 'fontSource',
135
+ type: 'options',
136
+ options: [
137
+ { name: 'Bundled/Uploaded Fonts', value: 'bundled', description: 'Use fonts bundled with MediaFX or uploaded by user' },
138
+ { name: 'System Font Path', value: 'system', description: 'Specify path to a system font file' },
139
+ ],
140
+ default: 'bundled',
141
+ displayOptions: {
142
+ show: {
143
+ resource: ['subtitle'],
144
+ },
145
+ },
146
+ description: 'Choose font source type',
147
+ },
132
148
  {
133
149
  displayName: 'Font Key',
134
150
  name: 'fontKey',
@@ -138,10 +154,25 @@ exports.subtitleProperties = [
138
154
  displayOptions: {
139
155
  show: {
140
156
  resource: ['subtitle'],
157
+ fontSource: ['bundled'],
141
158
  },
142
159
  },
143
160
  description: 'Font to use for the text',
144
161
  },
162
+ {
163
+ displayName: 'System Font Path',
164
+ name: 'systemFontPath',
165
+ type: 'string',
166
+ default: '',
167
+ placeholder: '/System/Library/Fonts/AppleSDGothicNeo.ttc',
168
+ displayOptions: {
169
+ show: {
170
+ resource: ['subtitle'],
171
+ fontSource: ['system'],
172
+ },
173
+ },
174
+ description: 'Full path to system font file (TTF, OTF, TTC). Use Font > List operation with "Include System Fonts" to find available fonts.',
175
+ },
145
176
  {
146
177
  displayName: 'Font Size',
147
178
  name: 'size',
@@ -31,8 +31,10 @@ export declare function runFfmpeg(command: ffmpeg.FfmpegCommand): Promise<void>;
31
31
  export declare function getVideoStreamInfo(filePath: string): Promise<ffmpeg.FfprobeStream | undefined>;
32
32
  export declare function fileHasAudio(filePath: string): Promise<boolean>;
33
33
  export declare const REGISTERED_FONTS: IDataObject;
34
+ export declare function getSystemFonts(): IDataObject;
34
35
  export declare function getUserFonts(): IDataObject;
35
- export declare function getAvailableFonts(): IDataObject;
36
+ export declare function getAvailableFonts(includeSystemFonts?: boolean): IDataObject;
37
+ export declare function getFontByPath(fontPath: string): IDataObject | null;
36
38
  export declare function validateFontKey(fontKey: string): void;
37
39
  export declare function saveUserFont(fontKey: string, fontName: string, description: string, originalFilename: string, buffer: Buffer): {
38
40
  fontKey: string;
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.checkTransitionSupport = exports.getFFmpegCapabilities = exports.deleteUserFont = exports.saveUserFont = exports.validateFontKey = exports.getAvailableFonts = exports.getUserFonts = exports.REGISTERED_FONTS = exports.fileHasAudio = exports.getVideoStreamInfo = exports.runFfmpeg = exports.createSilentAudio = exports.getDuration = exports.resolveInputs = exports.createTempFileFromBuffer = exports.downloadSource = exports.cleanupOldTempFiles = exports.getTempFile = exports.verifyFfmpegAvailability = void 0;
29
+ exports.checkTransitionSupport = exports.getFFmpegCapabilities = exports.deleteUserFont = exports.saveUserFont = exports.validateFontKey = exports.getFontByPath = exports.getAvailableFonts = exports.getUserFonts = exports.getSystemFonts = exports.REGISTERED_FONTS = exports.fileHasAudio = exports.getVideoStreamInfo = exports.runFfmpeg = exports.createSilentAudio = exports.getDuration = exports.resolveInputs = exports.createTempFileFromBuffer = exports.downloadSource = exports.cleanupOldTempFiles = exports.getTempFile = exports.verifyFfmpegAvailability = void 0;
30
30
  const fs = __importStar(require("fs-extra"));
31
31
  const path = __importStar(require("path"));
32
32
  const uuid_1 = require("uuid");
@@ -308,6 +308,28 @@ exports.fileHasAudio = fileHasAudio;
308
308
  const BASE_FONTS_DIR = path.resolve(__dirname, '..', '..', 'fonts');
309
309
  const USER_FONTS_DIR = path.join(BASE_FONTS_DIR, 'user');
310
310
  const USER_FONTS_JSON = path.join(USER_FONTS_DIR, 'user-fonts.json');
311
+ // System font directories by platform
312
+ const SYSTEM_FONT_DIRS = {
313
+ darwin: [
314
+ '/System/Library/Fonts',
315
+ '/Library/Fonts',
316
+ path.join(os.homedir(), 'Library/Fonts'),
317
+ ],
318
+ linux: [
319
+ '/usr/share/fonts',
320
+ '/usr/local/share/fonts',
321
+ path.join(os.homedir(), '.fonts'),
322
+ path.join(os.homedir(), '.local/share/fonts'),
323
+ ],
324
+ win32: [
325
+ 'C:\\Windows\\Fonts',
326
+ path.join(os.homedir(), 'AppData\\Local\\Microsoft\\Windows\\Fonts'),
327
+ ],
328
+ };
329
+ // Cache for system fonts (to avoid repeated filesystem scans)
330
+ let systemFontsCache = null;
331
+ let systemFontsCacheTime = 0;
332
+ const SYSTEM_FONTS_CACHE_TTL = 60 * 60 * 1000; // 1 hour cache
311
333
  // System-registered fonts (must exist in BASE_FONTS_DIR)
312
334
  exports.REGISTERED_FONTS = {
313
335
  'noto-sans-kr': { name: 'Noto Sans KR', filename: 'NotoSansKR-Regular.ttf', description: 'Google Noto Sans KR', type: 'korean' },
@@ -317,6 +339,62 @@ exports.REGISTERED_FONTS = {
317
339
  'inter': { name: 'Inter', filename: 'Inter-Regular.ttf', description: 'Inter UI Font', type: 'global' },
318
340
  'dejavu-sans': { name: 'DejaVu Sans', filename: 'DejaVuSans.ttf', description: 'Default fallback font', type: 'fallback' },
319
341
  };
342
+ // Scan a directory for font files
343
+ function scanFontDirectory(dir, fonts) {
344
+ if (!fs.existsSync(dir))
345
+ return;
346
+ try {
347
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
348
+ for (const entry of entries) {
349
+ const fullPath = path.join(dir, entry.name);
350
+ if (entry.isDirectory()) {
351
+ // Recursively scan subdirectories
352
+ scanFontDirectory(fullPath, fonts);
353
+ }
354
+ else if (entry.isFile()) {
355
+ const ext = path.extname(entry.name).toLowerCase();
356
+ if (ext === '.ttf' || ext === '.otf' || ext === '.ttc' || ext === '.otc') {
357
+ // Generate a unique key from the filename
358
+ const baseName = path.basename(entry.name, ext);
359
+ const fontKey = `system-${baseName.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
360
+ // Skip if already exists (avoid duplicates)
361
+ if (!fonts[fontKey]) {
362
+ fonts[fontKey] = {
363
+ name: baseName,
364
+ filename: entry.name,
365
+ path: fullPath,
366
+ description: `System font from ${dir}`,
367
+ type: 'system',
368
+ };
369
+ }
370
+ }
371
+ }
372
+ }
373
+ }
374
+ catch (error) {
375
+ // Ignore permission errors for system directories
376
+ console.warn(`Could not scan font directory ${dir}:`, error.message);
377
+ }
378
+ }
379
+ // Get system fonts with caching
380
+ function getSystemFonts() {
381
+ const now = Date.now();
382
+ // Return cached result if still valid
383
+ if (systemFontsCache && (now - systemFontsCacheTime) < SYSTEM_FONTS_CACHE_TTL) {
384
+ return systemFontsCache;
385
+ }
386
+ const fonts = {};
387
+ const platform = os.platform();
388
+ const fontDirs = SYSTEM_FONT_DIRS[platform] || [];
389
+ for (const dir of fontDirs) {
390
+ scanFontDirectory(dir, fonts);
391
+ }
392
+ // Update cache
393
+ systemFontsCache = fonts;
394
+ systemFontsCacheTime = now;
395
+ return fonts;
396
+ }
397
+ exports.getSystemFonts = getSystemFonts;
320
398
  // Helper functions for font management
321
399
  function ensureUserFontsDirectory() {
322
400
  if (!fs.existsSync(USER_FONTS_DIR)) {
@@ -341,13 +419,13 @@ function saveUserFonts(userFonts) {
341
419
  ensureUserFontsDirectory();
342
420
  fs.writeFileSync(USER_FONTS_JSON, JSON.stringify(userFonts, null, 2));
343
421
  }
344
- function getAvailableFonts() {
422
+ function getAvailableFonts(includeSystemFonts = false) {
345
423
  const fonts = {};
346
- // Add registered fonts
424
+ // Add registered fonts (bundled with the package)
347
425
  for (const [key, font] of Object.entries(exports.REGISTERED_FONTS)) {
348
426
  const fontPath = path.join(BASE_FONTS_DIR, font.filename);
349
427
  if (fs.existsSync(fontPath)) {
350
- fonts[key] = { ...font, path: fontPath, type: font.type || 'system' };
428
+ fonts[key] = { ...font, path: fontPath, type: font.type || 'bundled' };
351
429
  }
352
430
  }
353
431
  // Add user fonts
@@ -358,9 +436,38 @@ function getAvailableFonts() {
358
436
  fonts[key] = { ...font, path: fontPath, type: 'user' };
359
437
  }
360
438
  }
439
+ // Add system fonts if requested
440
+ if (includeSystemFonts) {
441
+ const systemFonts = getSystemFonts();
442
+ for (const [key, font] of Object.entries(systemFonts)) {
443
+ // Don't override bundled or user fonts with system fonts
444
+ if (!fonts[key]) {
445
+ fonts[key] = font;
446
+ }
447
+ }
448
+ }
361
449
  return fonts;
362
450
  }
363
451
  exports.getAvailableFonts = getAvailableFonts;
452
+ // Get font info by path (for system fonts specified by path)
453
+ function getFontByPath(fontPath) {
454
+ if (!fs.existsSync(fontPath)) {
455
+ return null;
456
+ }
457
+ const ext = path.extname(fontPath).toLowerCase();
458
+ if (ext !== '.ttf' && ext !== '.otf' && ext !== '.ttc' && ext !== '.otc') {
459
+ return null;
460
+ }
461
+ const baseName = path.basename(fontPath, ext);
462
+ return {
463
+ name: baseName,
464
+ filename: path.basename(fontPath),
465
+ path: fontPath,
466
+ description: 'System font',
467
+ type: 'system',
468
+ };
469
+ }
470
+ exports.getFontByPath = getFontByPath;
364
471
  function validateFontKey(fontKey) {
365
472
  const keyPattern = /^[a-zA-Z0-9_-]{3,50}$/;
366
473
  if (!keyPattern.test(fontKey)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lee-jisoo/n8n-nodes-mediafx",
3
- "version": "1.6.13",
3
+ "version": "1.6.15",
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": {