n8n-nodes-sb-render 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/LICENSE +21 -0
- package/README.md +442 -0
- package/dist/nodes/SbRender/SbRender.node.d.ts +10 -0
- package/dist/nodes/SbRender/SbRender.node.d.ts.map +1 -0
- package/dist/nodes/SbRender/SbRender.node.js +768 -0
- package/dist/nodes/SbRender/SbRender.node.js.map +1 -0
- package/dist/nodes/SbRender/interfaces/index.d.ts +155 -0
- package/dist/nodes/SbRender/interfaces/index.d.ts.map +1 -0
- package/dist/nodes/SbRender/interfaces/index.js +41 -0
- package/dist/nodes/SbRender/interfaces/index.js.map +1 -0
- package/dist/nodes/SbRender/sbrender.svg +7 -0
- package/dist/nodes/SbRender/services/AudioMixer.d.ts +26 -0
- package/dist/nodes/SbRender/services/AudioMixer.d.ts.map +1 -0
- package/dist/nodes/SbRender/services/AudioMixer.js +120 -0
- package/dist/nodes/SbRender/services/AudioMixer.js.map +1 -0
- package/dist/nodes/SbRender/services/FileManager.d.ts +37 -0
- package/dist/nodes/SbRender/services/FileManager.d.ts.map +1 -0
- package/dist/nodes/SbRender/services/FileManager.js +157 -0
- package/dist/nodes/SbRender/services/FileManager.js.map +1 -0
- package/dist/nodes/SbRender/services/SubtitleEngine.d.ts +64 -0
- package/dist/nodes/SbRender/services/SubtitleEngine.d.ts.map +1 -0
- package/dist/nodes/SbRender/services/SubtitleEngine.js +213 -0
- package/dist/nodes/SbRender/services/SubtitleEngine.js.map +1 -0
- package/dist/nodes/SbRender/services/VideoComposer.d.ts +28 -0
- package/dist/nodes/SbRender/services/VideoComposer.d.ts.map +1 -0
- package/dist/nodes/SbRender/services/VideoComposer.js +228 -0
- package/dist/nodes/SbRender/services/VideoComposer.js.map +1 -0
- package/dist/nodes/SbRender/utils/ffmpeg.d.ts +32 -0
- package/dist/nodes/SbRender/utils/ffmpeg.d.ts.map +1 -0
- package/dist/nodes/SbRender/utils/ffmpeg.js +107 -0
- package/dist/nodes/SbRender/utils/ffmpeg.js.map +1 -0
- package/dist/nodes/SbRender/utils/validation.d.ts +32 -0
- package/dist/nodes/SbRender/utils/validation.d.ts.map +1 -0
- package/dist/nodes/SbRender/utils/validation.js +210 -0
- package/dist/nodes/SbRender/utils/validation.js.map +1 -0
- package/index.js +3 -0
- package/package.json +69 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ISubtitleEngine, ISubtitleConfig } from '../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* SubtitleEngine Service
|
|
4
|
+
* Generates SRT and ASS subtitle files with customizable styling
|
|
5
|
+
*/
|
|
6
|
+
export declare class SubtitleEngine implements ISubtitleEngine {
|
|
7
|
+
/**
|
|
8
|
+
* Generate SRT subtitle file (simple format, limited styling)
|
|
9
|
+
*/
|
|
10
|
+
generateSRT(subtitles: ISubtitleConfig[]): string;
|
|
11
|
+
/**
|
|
12
|
+
* Generate ASS subtitle file with full styling support
|
|
13
|
+
*/
|
|
14
|
+
generateASS(subtitles: ISubtitleConfig[], videoWidth: number, videoHeight: number): string;
|
|
15
|
+
/**
|
|
16
|
+
* Write subtitle file to disk
|
|
17
|
+
*/
|
|
18
|
+
writeSubtitleFile(content: string, format: 'srt' | 'ass'): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Format time in SRT format (HH:MM:SS,mmm)
|
|
21
|
+
*/
|
|
22
|
+
private formatSRTTime;
|
|
23
|
+
/**
|
|
24
|
+
* Format time in ASS format (H:MM:SS.cc)
|
|
25
|
+
*/
|
|
26
|
+
private formatASSTime;
|
|
27
|
+
/**
|
|
28
|
+
* Generate ASS file header
|
|
29
|
+
*/
|
|
30
|
+
private generateASSHeader;
|
|
31
|
+
/**
|
|
32
|
+
* Generate ASS styles section
|
|
33
|
+
*/
|
|
34
|
+
private generateASSStyles;
|
|
35
|
+
/**
|
|
36
|
+
* Generate ASS events section
|
|
37
|
+
*/
|
|
38
|
+
private generateASSEvents;
|
|
39
|
+
/**
|
|
40
|
+
* Convert hex color to ASS color format (&HAABBGGRR)
|
|
41
|
+
*/
|
|
42
|
+
private hexToASSColor;
|
|
43
|
+
/**
|
|
44
|
+
* Get ASS alignment number based on position and alignment
|
|
45
|
+
*/
|
|
46
|
+
private getASSAlignment;
|
|
47
|
+
/**
|
|
48
|
+
* Calculate vertical margin based on position
|
|
49
|
+
*/
|
|
50
|
+
private getMarginV;
|
|
51
|
+
/**
|
|
52
|
+
* Convert number to 2-digit hex
|
|
53
|
+
*/
|
|
54
|
+
private toHex;
|
|
55
|
+
/**
|
|
56
|
+
* Pad number with zeros
|
|
57
|
+
*/
|
|
58
|
+
private pad;
|
|
59
|
+
/**
|
|
60
|
+
* Validate subtitle configuration
|
|
61
|
+
*/
|
|
62
|
+
validateSubtitles(subtitles: ISubtitleConfig[]): void;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=SubtitleEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubtitleEngine.d.ts","sourceRoot":"","sources":["../../../../nodes/SbRender/services/SubtitleEngine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEtE;;;GAGG;AACH,qBAAa,cAAe,YAAW,eAAe;IACpD;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,MAAM;IAejD;;OAEG;IACH,WAAW,CAAC,SAAS,EAAE,eAAe,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAQ1F;;OAEG;IACG,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;IAWhF;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAUzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAwBzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAgCzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAgBrB;;OAEG;IACH,OAAO,CAAC,eAAe;IAkBvB;;OAEG;IACH,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACH,OAAO,CAAC,KAAK;IAIb;;OAEG;IACH,OAAO,CAAC,GAAG;IAIX;;OAEG;IACH,iBAAiB,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,IAAI;CAyBtD"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SubtitleEngine = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
// import * as path from 'path'; // Unused
|
|
6
|
+
const tmp_promise_1 = require("tmp-promise");
|
|
7
|
+
/**
|
|
8
|
+
* SubtitleEngine Service
|
|
9
|
+
* Generates SRT and ASS subtitle files with customizable styling
|
|
10
|
+
*/
|
|
11
|
+
class SubtitleEngine {
|
|
12
|
+
/**
|
|
13
|
+
* Generate SRT subtitle file (simple format, limited styling)
|
|
14
|
+
*/
|
|
15
|
+
generateSRT(subtitles) {
|
|
16
|
+
const srtBlocks = [];
|
|
17
|
+
subtitles.forEach((subtitle, index) => {
|
|
18
|
+
const startTime = this.formatSRTTime(subtitle.startTime);
|
|
19
|
+
const endTime = this.formatSRTTime(subtitle.endTime);
|
|
20
|
+
srtBlocks.push(`${index + 1}\n${startTime} --> ${endTime}\n${subtitle.text}\n`);
|
|
21
|
+
});
|
|
22
|
+
return srtBlocks.join('\n');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate ASS subtitle file with full styling support
|
|
26
|
+
*/
|
|
27
|
+
generateASS(subtitles, videoWidth, videoHeight) {
|
|
28
|
+
const header = this.generateASSHeader(videoWidth, videoHeight);
|
|
29
|
+
const styles = this.generateASSStyles(subtitles);
|
|
30
|
+
const events = this.generateASSEvents(subtitles, videoWidth, videoHeight);
|
|
31
|
+
return `${header}\n\n${styles}\n\n${events}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Write subtitle file to disk
|
|
35
|
+
*/
|
|
36
|
+
async writeSubtitleFile(content, format) {
|
|
37
|
+
const { path: tempPath } = await (0, tmp_promise_1.file)({
|
|
38
|
+
prefix: 'sb-render-subtitle-',
|
|
39
|
+
postfix: `.${format}`,
|
|
40
|
+
keep: true,
|
|
41
|
+
});
|
|
42
|
+
await fs_1.promises.writeFile(tempPath, content, 'utf8');
|
|
43
|
+
return tempPath;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Format time in SRT format (HH:MM:SS,mmm)
|
|
47
|
+
*/
|
|
48
|
+
formatSRTTime(seconds) {
|
|
49
|
+
const hours = Math.floor(seconds / 3600);
|
|
50
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
51
|
+
const secs = Math.floor(seconds % 60);
|
|
52
|
+
const millis = Math.floor((seconds % 1) * 1000);
|
|
53
|
+
return `${this.pad(hours, 2)}:${this.pad(minutes, 2)}:${this.pad(secs, 2)},${this.pad(millis, 3)}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Format time in ASS format (H:MM:SS.cc)
|
|
57
|
+
*/
|
|
58
|
+
formatASSTime(seconds) {
|
|
59
|
+
const hours = Math.floor(seconds / 3600);
|
|
60
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
61
|
+
const secs = Math.floor(seconds % 60);
|
|
62
|
+
const centisecs = Math.floor((seconds % 1) * 100);
|
|
63
|
+
return `${hours}:${this.pad(minutes, 2)}:${this.pad(secs, 2)}.${this.pad(centisecs, 2)}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Generate ASS file header
|
|
67
|
+
*/
|
|
68
|
+
generateASSHeader(videoWidth, videoHeight) {
|
|
69
|
+
return `[Script Info]
|
|
70
|
+
Title: sb-render subtitles
|
|
71
|
+
ScriptType: v4.00+
|
|
72
|
+
WrapStyle: 0
|
|
73
|
+
PlayResX: ${videoWidth}
|
|
74
|
+
PlayResY: ${videoHeight}
|
|
75
|
+
ScaledBorderAndShadow: yes`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Generate ASS styles section
|
|
79
|
+
*/
|
|
80
|
+
generateASSStyles(subtitles) {
|
|
81
|
+
const stylesHeader = `[V4+ Styles]
|
|
82
|
+
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding`;
|
|
83
|
+
const uniqueStyles = new Map();
|
|
84
|
+
subtitles.forEach((subtitle, index) => {
|
|
85
|
+
const styleName = `Style${index}`;
|
|
86
|
+
const primaryColor = this.hexToASSColor(subtitle.fontColor);
|
|
87
|
+
const outlineColor = this.hexToASSColor(subtitle.borderColor || '#000000');
|
|
88
|
+
const backColor = this.hexToASSColor(subtitle.backgroundColor || '#000000', subtitle.backgroundOpacity);
|
|
89
|
+
const alignment = this.getASSAlignment(subtitle.alignment, subtitle.position);
|
|
90
|
+
const outline = subtitle.borderWidth || 2;
|
|
91
|
+
const styleDefinition = `Style: ${styleName},${subtitle.fontFamily},${subtitle.fontSize},${primaryColor},${primaryColor},${outlineColor},${backColor},0,0,0,0,100,100,0,0,1,${outline},2,${alignment},10,10,10,1`;
|
|
92
|
+
uniqueStyles.set(styleName, styleDefinition);
|
|
93
|
+
});
|
|
94
|
+
const styleLines = Array.from(uniqueStyles.values()).join('\n');
|
|
95
|
+
return `${stylesHeader}\n${styleLines}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate ASS events section
|
|
99
|
+
*/
|
|
100
|
+
generateASSEvents(subtitles, _videoWidth, videoHeight) {
|
|
101
|
+
const eventsHeader = `[Events]
|
|
102
|
+
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text`;
|
|
103
|
+
const eventLines = [];
|
|
104
|
+
subtitles.forEach((subtitle, index) => {
|
|
105
|
+
const startTime = this.formatASSTime(subtitle.startTime);
|
|
106
|
+
const endTime = this.formatASSTime(subtitle.endTime);
|
|
107
|
+
const styleName = `Style${index}`;
|
|
108
|
+
// Calculate position override if custom position
|
|
109
|
+
let positionTag = '';
|
|
110
|
+
if (subtitle.position === 'custom' && subtitle.customX !== undefined && subtitle.customY !== undefined) {
|
|
111
|
+
positionTag = `{\\pos(${subtitle.customX},${subtitle.customY})}`;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Use margin-based positioning
|
|
115
|
+
const marginV = this.getMarginV(subtitle.position, subtitle.customY, videoHeight);
|
|
116
|
+
eventLines.push(`Dialogue: 0,${startTime},${endTime},${styleName},,0,0,${marginV},,${positionTag}${subtitle.text}`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
eventLines.push(`Dialogue: 0,${startTime},${endTime},${styleName},,0,0,0,,${positionTag}${subtitle.text}`);
|
|
120
|
+
});
|
|
121
|
+
return `${eventsHeader}\n${eventLines.join('\n')}`;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Convert hex color to ASS color format (&HAABBGGRR)
|
|
125
|
+
*/
|
|
126
|
+
hexToASSColor(hex, opacity = 100) {
|
|
127
|
+
// Remove # if present
|
|
128
|
+
hex = hex.replace('#', '');
|
|
129
|
+
// Parse RGB
|
|
130
|
+
const r = parseInt(hex.substring(0, 2), 16);
|
|
131
|
+
const g = parseInt(hex.substring(2, 4), 16);
|
|
132
|
+
const b = parseInt(hex.substring(4, 6), 16);
|
|
133
|
+
// Convert opacity percentage to alpha (0-255, inverted for ASS)
|
|
134
|
+
const alpha = Math.round((100 - opacity) * 2.55);
|
|
135
|
+
// Format as &HAABBGGRR
|
|
136
|
+
return `&H${this.toHex(alpha)}${this.toHex(b)}${this.toHex(g)}${this.toHex(r)}`;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get ASS alignment number based on position and alignment
|
|
140
|
+
*/
|
|
141
|
+
getASSAlignment(alignment, position) {
|
|
142
|
+
// ASS alignment: 1-3 bottom, 4-6 middle, 7-9 top
|
|
143
|
+
// Within each row: 1/4/7 left, 2/5/8 center, 3/6/9 right
|
|
144
|
+
let baseAlignment = 0;
|
|
145
|
+
if (position === 'bottom')
|
|
146
|
+
baseAlignment = 1;
|
|
147
|
+
else if (position === 'middle')
|
|
148
|
+
baseAlignment = 4;
|
|
149
|
+
else if (position === 'top')
|
|
150
|
+
baseAlignment = 7;
|
|
151
|
+
else
|
|
152
|
+
baseAlignment = 5; // default to center-middle
|
|
153
|
+
if (alignment === 'left')
|
|
154
|
+
return baseAlignment;
|
|
155
|
+
if (alignment === 'center')
|
|
156
|
+
return baseAlignment + 1;
|
|
157
|
+
if (alignment === 'right')
|
|
158
|
+
return baseAlignment + 2;
|
|
159
|
+
return baseAlignment + 1; // default center
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Calculate vertical margin based on position
|
|
163
|
+
*/
|
|
164
|
+
getMarginV(position, customY, videoHeight) {
|
|
165
|
+
if (customY !== undefined)
|
|
166
|
+
return customY;
|
|
167
|
+
if (position === 'top')
|
|
168
|
+
return 50;
|
|
169
|
+
if (position === 'middle')
|
|
170
|
+
return Math.round(videoHeight / 2);
|
|
171
|
+
if (position === 'bottom')
|
|
172
|
+
return 100;
|
|
173
|
+
return 100; // default bottom
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Convert number to 2-digit hex
|
|
177
|
+
*/
|
|
178
|
+
toHex(num) {
|
|
179
|
+
return num.toString(16).padStart(2, '0').toUpperCase();
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Pad number with zeros
|
|
183
|
+
*/
|
|
184
|
+
pad(num, length) {
|
|
185
|
+
return num.toString().padStart(length, '0');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Validate subtitle configuration
|
|
189
|
+
*/
|
|
190
|
+
validateSubtitles(subtitles) {
|
|
191
|
+
subtitles.forEach((subtitle, index) => {
|
|
192
|
+
if (!subtitle.text || subtitle.text.trim() === '') {
|
|
193
|
+
throw new Error(`Subtitle ${index + 1}: Text cannot be empty`);
|
|
194
|
+
}
|
|
195
|
+
if (subtitle.startTime < 0) {
|
|
196
|
+
throw new Error(`Subtitle ${index + 1}: Start time cannot be negative`);
|
|
197
|
+
}
|
|
198
|
+
if (subtitle.endTime <= subtitle.startTime) {
|
|
199
|
+
throw new Error(`Subtitle ${index + 1}: End time must be greater than start time`);
|
|
200
|
+
}
|
|
201
|
+
if (subtitle.fontSize <= 0) {
|
|
202
|
+
throw new Error(`Subtitle ${index + 1}: Font size must be positive`);
|
|
203
|
+
}
|
|
204
|
+
if (subtitle.position === 'custom') {
|
|
205
|
+
if (subtitle.customX === undefined || subtitle.customY === undefined) {
|
|
206
|
+
throw new Error(`Subtitle ${index + 1}: Custom position requires customX and customY`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
exports.SubtitleEngine = SubtitleEngine;
|
|
213
|
+
//# sourceMappingURL=SubtitleEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SubtitleEngine.js","sourceRoot":"","sources":["../../../../nodes/SbRender/services/SubtitleEngine.ts"],"names":[],"mappings":";;;AAAA,2BAAoC;AACpC,0CAA0C;AAC1C,6CAA8C;AAG9C;;;GAGG;AACH,MAAa,cAAc;IACzB;;OAEG;IACH,WAAW,CAAC,SAA4B;QACtC,MAAM,SAAS,GAAa,EAAE,CAAC;QAE/B,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;YACpC,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YAErD,SAAS,CAAC,IAAI,CACZ,GAAG,KAAK,GAAG,CAAC,KAAK,SAAS,QAAQ,OAAO,KAAK,QAAQ,CAAC,IAAI,IAAI,CAChE,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,OAAO,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,SAA4B,EAAE,UAAkB,EAAE,WAAmB;QAC/E,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;QAC/D,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QAE1E,OAAO,GAAG,MAAM,OAAO,MAAM,OAAO,MAAM,EAAE,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,iBAAiB,CAAC,OAAe,EAAE,MAAqB;QAC5D,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAA,kBAAO,EAAC;YACvC,MAAM,EAAE,qBAAqB;YAC7B,OAAO,EAAE,IAAI,MAAM,EAAE;YACrB,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QAEH,MAAM,aAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9C,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,OAAe;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAEhD,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;IACrG,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,OAAe;QACnC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACzC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;QACtC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;QAElD,OAAO,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC;IAC3F,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,UAAkB,EAAE,WAAmB;QAC/D,OAAO;;;;YAIC,UAAU;YACV,WAAW;2BACI,CAAC;IAC1B,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,SAA4B;QACpD,MAAM,YAAY,GAAG;8OACqN,CAAC;QAE3O,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC;QAE/C,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;YACpC,MAAM,SAAS,GAAG,QAAQ,KAAK,EAAE,CAAC;YAClC,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;YAC3E,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,eAAe,IAAI,SAAS,EAAE,QAAQ,CAAC,iBAAiB,CAAC,CAAC;YAExG,MAAM,SAAS,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC9E,MAAM,OAAO,GAAG,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAC;YAE1C,MAAM,eAAe,GAAG,UAAU,SAAS,IAAI,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,QAAQ,IAAI,YAAY,IAAI,YAAY,IAAI,YAAY,IAAI,SAAS,0BAA0B,OAAO,MAAM,SAAS,aAAa,CAAC;YAElN,YAAY,CAAC,GAAG,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChE,OAAO,GAAG,YAAY,KAAK,UAAU,EAAE,CAAC;IAC1C,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,SAA4B,EAAE,WAAmB,EAAE,WAAmB;QAC9F,MAAM,YAAY,GAAG;gFACuD,CAAC;QAE7E,MAAM,UAAU,GAAa,EAAE,CAAC;QAEhC,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;YACpC,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;YACrD,MAAM,SAAS,GAAG,QAAQ,KAAK,EAAE,CAAC;YAElC,iDAAiD;YACjD,IAAI,WAAW,GAAG,EAAE,CAAC;YACrB,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;gBACvG,WAAW,GAAG,UAAU,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,OAAO,IAAI,CAAC;YACnE,CAAC;iBAAM,CAAC;gBACN,+BAA+B;gBAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;gBAClF,UAAU,CAAC,IAAI,CACb,eAAe,SAAS,IAAI,OAAO,IAAI,SAAS,SAAS,OAAO,KAAK,WAAW,GAAG,QAAQ,CAAC,IAAI,EAAE,CACnG,CAAC;gBACF,OAAO;YACT,CAAC;YAED,UAAU,CAAC,IAAI,CACb,eAAe,SAAS,IAAI,OAAO,IAAI,SAAS,YAAY,WAAW,GAAG,QAAQ,CAAC,IAAI,EAAE,CAC1F,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,OAAO,GAAG,YAAY,KAAK,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACrD,CAAC;IAED;;OAEG;IACK,aAAa,CAAC,GAAW,EAAE,OAAO,GAAG,GAAG;QAC9C,sBAAsB;QACtB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAE3B,YAAY;QACZ,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAE5C,gEAAgE;QAChE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QAEjD,uBAAuB;QACvB,OAAO,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,CAAC;IAED;;OAEG;IACK,eAAe,CAAC,SAAiB,EAAE,QAAgB;QACzD,iDAAiD;QACjD,yDAAyD;QAEzD,IAAI,aAAa,GAAG,CAAC,CAAC;QAEtB,IAAI,QAAQ,KAAK,QAAQ;YAAE,aAAa,GAAG,CAAC,CAAC;aACxC,IAAI,QAAQ,KAAK,QAAQ;YAAE,aAAa,GAAG,CAAC,CAAC;aAC7C,IAAI,QAAQ,KAAK,KAAK;YAAE,aAAa,GAAG,CAAC,CAAC;;YAC1C,aAAa,GAAG,CAAC,CAAC,CAAC,2BAA2B;QAEnD,IAAI,SAAS,KAAK,MAAM;YAAE,OAAO,aAAa,CAAC;QAC/C,IAAI,SAAS,KAAK,QAAQ;YAAE,OAAO,aAAa,GAAG,CAAC,CAAC;QACrD,IAAI,SAAS,KAAK,OAAO;YAAE,OAAO,aAAa,GAAG,CAAC,CAAC;QAEpD,OAAO,aAAa,GAAG,CAAC,CAAC,CAAC,iBAAiB;IAC7C,CAAC;IAED;;OAEG;IACK,UAAU,CAAC,QAAgB,EAAE,OAA2B,EAAE,WAAmB;QACnF,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC;QAE1C,IAAI,QAAQ,KAAK,KAAK;YAAE,OAAO,EAAE,CAAC;QAClC,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC;QAC9D,IAAI,QAAQ,KAAK,QAAQ;YAAE,OAAO,GAAG,CAAC;QAEtC,OAAO,GAAG,CAAC,CAAC,iBAAiB;IAC/B,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,GAAW;QACvB,OAAO,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACzD,CAAC;IAED;;OAEG;IACK,GAAG,CAAC,GAAW,EAAE,MAAc;QACrC,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9C,CAAC;IAED;;OAEG;IACH,iBAAiB,CAAC,SAA4B;QAC5C,SAAS,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE;YACpC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBAClD,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACjE,CAAC;YAED,IAAI,QAAQ,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,GAAG,CAAC,iCAAiC,CAAC,CAAC;YAC1E,CAAC;YAED,IAAI,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,GAAG,CAAC,4CAA4C,CAAC,CAAC;YACrF,CAAC;YAED,IAAI,QAAQ,CAAC,QAAQ,IAAI,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,GAAG,CAAC,8BAA8B,CAAC,CAAC;YACvE,CAAC;YAED,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACnC,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;oBACrE,MAAM,IAAI,KAAK,CAAC,YAAY,KAAK,GAAG,CAAC,gDAAgD,CAAC,CAAC;gBACzF,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AA9OD,wCA8OC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { IVideoComposer, IVideoMetadata, ISbRenderNodeParams } from '../interfaces';
|
|
2
|
+
/**
|
|
3
|
+
* VideoComposer Service
|
|
4
|
+
* Handles final video composition with audio and subtitles
|
|
5
|
+
*/
|
|
6
|
+
export declare class VideoComposer implements IVideoComposer {
|
|
7
|
+
/**
|
|
8
|
+
* Compose final video with audio and subtitles
|
|
9
|
+
*/
|
|
10
|
+
compose(videoPath: string, audioPath: string | null, subtitlePath: string | null, outputPath: string, config: ISbRenderNodeParams): Promise<Buffer>;
|
|
11
|
+
/**
|
|
12
|
+
* Compose with complex audio mixing
|
|
13
|
+
*/
|
|
14
|
+
composeWithAudioMix(videoPath: string, bgmPath: string | null, narrationPath: string | null, subtitlePath: string | null, audioFilterChain: string, outputPath: string, config: ISbRenderNodeParams): Promise<Buffer>;
|
|
15
|
+
/**
|
|
16
|
+
* Get video metadata (duration, resolution, codec)
|
|
17
|
+
*/
|
|
18
|
+
getVideoMetadata(videoPath: string): Promise<IVideoMetadata>;
|
|
19
|
+
/**
|
|
20
|
+
* Get CRF value based on quality setting
|
|
21
|
+
*/
|
|
22
|
+
private getCRF;
|
|
23
|
+
/**
|
|
24
|
+
* Validate output configuration
|
|
25
|
+
*/
|
|
26
|
+
validateConfig(config: ISbRenderNodeParams): void;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=VideoComposer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VideoComposer.d.ts","sourceRoot":"","sources":["../../../../nodes/SbRender/services/VideoComposer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAKzF;;;GAGG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD;;OAEG;IACG,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,MAAM,CAAC;IAoFlB;;OAEG;IACG,mBAAmB,CACvB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,gBAAgB,EAAE,MAAM,EACxB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,MAAM,CAAC;IAqFlB;;OAEG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IA4BlE;;OAEG;IACH,OAAO,CAAC,MAAM;IAcd;;OAEG;IACH,cAAc,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI;CAiBlD"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.VideoComposer = void 0;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
|
|
9
|
+
const ffmpeg_1 = __importDefault(require("@ffmpeg-installer/ffmpeg"));
|
|
10
|
+
// Set FFmpeg path
|
|
11
|
+
fluent_ffmpeg_1.default.setFfmpegPath(ffmpeg_1.default.path);
|
|
12
|
+
/**
|
|
13
|
+
* VideoComposer Service
|
|
14
|
+
* Handles final video composition with audio and subtitles
|
|
15
|
+
*/
|
|
16
|
+
class VideoComposer {
|
|
17
|
+
/**
|
|
18
|
+
* Compose final video with audio and subtitles
|
|
19
|
+
*/
|
|
20
|
+
async compose(videoPath, audioPath, subtitlePath, outputPath, config) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
try {
|
|
23
|
+
const command = (0, fluent_ffmpeg_1.default)(videoPath);
|
|
24
|
+
// Add audio inputs if present
|
|
25
|
+
if (audioPath) {
|
|
26
|
+
// Audio is already mixed, we'll map it
|
|
27
|
+
}
|
|
28
|
+
// Video filters
|
|
29
|
+
const videoFilters = [];
|
|
30
|
+
// Add subtitle overlay if present
|
|
31
|
+
if (subtitlePath) {
|
|
32
|
+
// Escape path for FFmpeg filter
|
|
33
|
+
const escapedPath = subtitlePath.replace(/\\/g, '/').replace(/:/g, '\\:');
|
|
34
|
+
videoFilters.push(`ass=${escapedPath}`);
|
|
35
|
+
}
|
|
36
|
+
// Apply video filters if any
|
|
37
|
+
if (videoFilters.length > 0) {
|
|
38
|
+
command.videoFilters(videoFilters);
|
|
39
|
+
}
|
|
40
|
+
// Set output codec and quality
|
|
41
|
+
const crf = this.getCRF(config.quality, config.customCRF);
|
|
42
|
+
command
|
|
43
|
+
.videoCodec(config.videoCodec)
|
|
44
|
+
.outputOptions([
|
|
45
|
+
`-crf ${crf}`,
|
|
46
|
+
'-preset medium',
|
|
47
|
+
'-movflags +faststart', // Enable streaming
|
|
48
|
+
]);
|
|
49
|
+
// Audio codec
|
|
50
|
+
if (audioPath) {
|
|
51
|
+
command
|
|
52
|
+
.audioCodec('aac')
|
|
53
|
+
.audioBitrate('192k');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
command.noAudio();
|
|
57
|
+
}
|
|
58
|
+
// Set output format
|
|
59
|
+
command.format(config.outputFormat);
|
|
60
|
+
// Save to output path
|
|
61
|
+
command.output(outputPath);
|
|
62
|
+
// Handle events
|
|
63
|
+
command.on('start', (commandLine) => {
|
|
64
|
+
console.log('FFmpeg command:', commandLine);
|
|
65
|
+
});
|
|
66
|
+
command.on('progress', (progress) => {
|
|
67
|
+
if (progress.percent) {
|
|
68
|
+
console.log(`Processing: ${Math.round(progress.percent)}% done`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
command.on('end', async () => {
|
|
72
|
+
try {
|
|
73
|
+
// Read the output file
|
|
74
|
+
const buffer = await fs_1.promises.readFile(outputPath);
|
|
75
|
+
resolve(buffer);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
reject(new Error(`Failed to read output file: ${error}`));
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
command.on('error', (error) => {
|
|
82
|
+
reject(new Error(`FFmpeg error: ${error.message}`));
|
|
83
|
+
});
|
|
84
|
+
// Run the command
|
|
85
|
+
command.run();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
reject(new Error(`Video composition failed: ${error}`));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Compose with complex audio mixing
|
|
94
|
+
*/
|
|
95
|
+
async composeWithAudioMix(videoPath, bgmPath, narrationPath, subtitlePath, audioFilterChain, outputPath, config) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
try {
|
|
98
|
+
const command = (0, fluent_ffmpeg_1.default)(videoPath);
|
|
99
|
+
// Add BGM input
|
|
100
|
+
if (bgmPath) {
|
|
101
|
+
command.input(bgmPath);
|
|
102
|
+
}
|
|
103
|
+
// Add narration input
|
|
104
|
+
if (narrationPath) {
|
|
105
|
+
command.input(narrationPath);
|
|
106
|
+
}
|
|
107
|
+
// Apply complex audio filter
|
|
108
|
+
if (audioFilterChain) {
|
|
109
|
+
command.complexFilter(audioFilterChain);
|
|
110
|
+
}
|
|
111
|
+
// Video filters
|
|
112
|
+
const videoFilters = [];
|
|
113
|
+
// Add subtitle overlay if present
|
|
114
|
+
if (subtitlePath) {
|
|
115
|
+
const escapedPath = subtitlePath.replace(/\\/g, '/').replace(/:/g, '\\:');
|
|
116
|
+
videoFilters.push(`ass=${escapedPath}`);
|
|
117
|
+
}
|
|
118
|
+
if (videoFilters.length > 0) {
|
|
119
|
+
command.videoFilters(videoFilters);
|
|
120
|
+
}
|
|
121
|
+
// Map video and mixed audio
|
|
122
|
+
command.outputOptions([
|
|
123
|
+
'-map 0:v',
|
|
124
|
+
audioFilterChain ? '-map [mixed]' : '',
|
|
125
|
+
].filter(Boolean));
|
|
126
|
+
// Set output codec and quality
|
|
127
|
+
const crf = this.getCRF(config.quality, config.customCRF);
|
|
128
|
+
command
|
|
129
|
+
.videoCodec(config.videoCodec)
|
|
130
|
+
.outputOptions([
|
|
131
|
+
`-crf ${crf}`,
|
|
132
|
+
'-preset medium',
|
|
133
|
+
'-movflags +faststart',
|
|
134
|
+
])
|
|
135
|
+
.audioCodec('aac')
|
|
136
|
+
.audioBitrate('192k')
|
|
137
|
+
.format(config.outputFormat)
|
|
138
|
+
.output(outputPath);
|
|
139
|
+
// Handle events
|
|
140
|
+
command.on('start', (commandLine) => {
|
|
141
|
+
console.log('FFmpeg command:', commandLine);
|
|
142
|
+
});
|
|
143
|
+
command.on('progress', (progress) => {
|
|
144
|
+
if (progress.percent) {
|
|
145
|
+
console.log(`Processing: ${Math.round(progress.percent)}% done`);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
command.on('end', async () => {
|
|
149
|
+
try {
|
|
150
|
+
const buffer = await fs_1.promises.readFile(outputPath);
|
|
151
|
+
resolve(buffer);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
reject(new Error(`Failed to read output file: ${error}`));
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
command.on('error', (error) => {
|
|
158
|
+
reject(new Error(`FFmpeg error: ${error.message}`));
|
|
159
|
+
});
|
|
160
|
+
command.run();
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
reject(new Error(`Video composition failed: ${error}`));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get video metadata (duration, resolution, codec)
|
|
169
|
+
*/
|
|
170
|
+
async getVideoMetadata(videoPath) {
|
|
171
|
+
return new Promise((resolve, reject) => {
|
|
172
|
+
fluent_ffmpeg_1.default.ffprobe(videoPath, (error, metadata) => {
|
|
173
|
+
if (error) {
|
|
174
|
+
reject(new Error(`Failed to get video metadata: ${error.message}`));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const videoStream = metadata.streams.find((s) => s.codec_type === 'video');
|
|
178
|
+
const audioStream = metadata.streams.find((s) => s.codec_type === 'audio');
|
|
179
|
+
if (!videoStream) {
|
|
180
|
+
reject(new Error('No video stream found in file'));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
resolve({
|
|
184
|
+
duration: metadata.format.duration || 0,
|
|
185
|
+
width: videoStream.width || 1920,
|
|
186
|
+
height: videoStream.height || 1080,
|
|
187
|
+
hasAudio: !!audioStream,
|
|
188
|
+
videoCodec: videoStream.codec_name || 'unknown',
|
|
189
|
+
audioCodec: audioStream === null || audioStream === void 0 ? void 0 : audioStream.codec_name,
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get CRF value based on quality setting
|
|
196
|
+
*/
|
|
197
|
+
getCRF(quality, customCRF) {
|
|
198
|
+
if (quality === 'custom' && customCRF !== undefined) {
|
|
199
|
+
return Math.max(0, Math.min(51, customCRF));
|
|
200
|
+
}
|
|
201
|
+
const crfMap = {
|
|
202
|
+
low: 28,
|
|
203
|
+
medium: 23,
|
|
204
|
+
high: 18,
|
|
205
|
+
};
|
|
206
|
+
return crfMap[quality] || 23;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Validate output configuration
|
|
210
|
+
*/
|
|
211
|
+
validateConfig(config) {
|
|
212
|
+
const validFormats = ['mp4', 'mov', 'webm'];
|
|
213
|
+
if (!validFormats.includes(config.outputFormat)) {
|
|
214
|
+
throw new Error(`Invalid output format: ${config.outputFormat}`);
|
|
215
|
+
}
|
|
216
|
+
const validCodecs = ['libx264', 'libx265', 'vp9'];
|
|
217
|
+
if (!validCodecs.includes(config.videoCodec)) {
|
|
218
|
+
throw new Error(`Invalid video codec: ${config.videoCodec}`);
|
|
219
|
+
}
|
|
220
|
+
if (config.quality === 'custom') {
|
|
221
|
+
if (config.customCRF === undefined || config.customCRF < 0 || config.customCRF > 51) {
|
|
222
|
+
throw new Error('Custom CRF must be between 0 and 51');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
exports.VideoComposer = VideoComposer;
|
|
228
|
+
//# sourceMappingURL=VideoComposer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VideoComposer.js","sourceRoot":"","sources":["../../../../nodes/SbRender/services/VideoComposer.ts"],"names":[],"mappings":";;;;;;AAAA,2BAAoC;AACpC,kEAAmC;AACnC,sEAAuD;AAGvD,kBAAkB;AAClB,uBAAM,CAAC,aAAa,CAAC,gBAAe,CAAC,IAAI,CAAC,CAAC;AAE3C;;;GAGG;AACH,MAAa,aAAa;IACxB;;OAEG;IACH,KAAK,CAAC,OAAO,CACX,SAAiB,EACjB,SAAwB,EACxB,YAA2B,EAC3B,UAAkB,EAClB,MAA2B;QAE3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAA,uBAAM,EAAC,SAAS,CAAC,CAAC;gBAElC,8BAA8B;gBAC9B,IAAI,SAAS,EAAE,CAAC;oBACd,uCAAuC;gBACzC,CAAC;gBAED,gBAAgB;gBAChB,MAAM,YAAY,GAAa,EAAE,CAAC;gBAElC,kCAAkC;gBAClC,IAAI,YAAY,EAAE,CAAC;oBACjB,gCAAgC;oBAChC,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;oBAC1E,YAAY,CAAC,IAAI,CAAC,OAAO,WAAW,EAAE,CAAC,CAAC;gBAC1C,CAAC;gBAED,6BAA6B;gBAC7B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC5B,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;gBACrC,CAAC;gBAED,+BAA+B;gBAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;gBAE1D,OAAO;qBACJ,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC;qBAC7B,aAAa,CAAC;oBACb,QAAQ,GAAG,EAAE;oBACb,gBAAgB;oBAChB,sBAAsB,EAAE,mBAAmB;iBAC5C,CAAC,CAAC;gBAEL,cAAc;gBACd,IAAI,SAAS,EAAE,CAAC;oBACd,OAAO;yBACJ,UAAU,CAAC,KAAK,CAAC;yBACjB,YAAY,CAAC,MAAM,CAAC,CAAC;gBAC1B,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,CAAC;gBAED,oBAAoB;gBACpB,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;gBAEpC,sBAAsB;gBACtB,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBAE3B,gBAAgB;gBAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,WAAmB,EAAE,EAAE;oBAC1C,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;gBAC9C,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,QAA8B,EAAE,EAAE;oBACxD,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;wBACrB,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBACnE,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;oBAC3B,IAAI,CAAC;wBACH,uBAAuB;wBACvB,MAAM,MAAM,GAAG,MAAM,aAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;wBAC7C,OAAO,CAAC,MAAM,CAAC,CAAC;oBAClB,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,MAAM,CAAC,IAAI,KAAK,CAAC,+BAA+B,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC5D,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;oBACnC,MAAM,CAAC,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACtD,CAAC,CAAC,CAAC;gBAEH,kBAAkB;gBAClB,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,KAAK,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,mBAAmB,CACvB,SAAiB,EACjB,OAAsB,EACtB,aAA4B,EAC5B,YAA2B,EAC3B,gBAAwB,EACxB,UAAkB,EAClB,MAA2B;QAE3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAA,uBAAM,EAAC,SAAS,CAAC,CAAC;gBAElC,gBAAgB;gBAChB,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACzB,CAAC;gBAED,sBAAsB;gBACtB,IAAI,aAAa,EAAE,CAAC;oBAClB,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;gBAC/B,CAAC;gBAED,6BAA6B;gBAC7B,IAAI,gBAAgB,EAAE,CAAC;oBACrB,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;gBAC1C,CAAC;gBAED,gBAAgB;gBAChB,MAAM,YAAY,GAAa,EAAE,CAAC;gBAElC,kCAAkC;gBAClC,IAAI,YAAY,EAAE,CAAC;oBACjB,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;oBAC1E,YAAY,CAAC,IAAI,CAAC,OAAO,WAAW,EAAE,CAAC,CAAC;gBAC1C,CAAC;gBAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC5B,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;gBACrC,CAAC;gBAED,4BAA4B;gBAC5B,OAAO,CAAC,aAAa,CAAC;oBACpB,UAAU;oBACV,gBAAgB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE;iBACvC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;gBAEnB,+BAA+B;gBAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;gBAE1D,OAAO;qBACJ,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC;qBAC7B,aAAa,CAAC;oBACb,QAAQ,GAAG,EAAE;oBACb,gBAAgB;oBAChB,sBAAsB;iBACvB,CAAC;qBACD,UAAU,CAAC,KAAK,CAAC;qBACjB,YAAY,CAAC,MAAM,CAAC;qBACpB,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;qBAC3B,MAAM,CAAC,UAAU,CAAC,CAAC;gBAEtB,gBAAgB;gBAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,WAAmB,EAAE,EAAE;oBAC1C,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;gBAC9C,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,QAA8B,EAAE,EAAE;oBACxD,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;wBACrB,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;oBACnE,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE;oBAC3B,IAAI,CAAC;wBACH,MAAM,MAAM,GAAG,MAAM,aAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;wBAC7C,OAAO,CAAC,MAAM,CAAC,CAAC;oBAClB,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,MAAM,CAAC,IAAI,KAAK,CAAC,+BAA+B,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC5D,CAAC;gBACH,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;oBACnC,MAAM,CAAC,IAAI,KAAK,CAAC,iBAAiB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACtD,CAAC,CAAC,CAAC;gBAEH,OAAO,CAAC,GAAG,EAAE,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,KAAK,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,gBAAgB,CAAC,SAAiB;QACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,uBAAM,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;gBAC5C,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBACpE,OAAO;gBACT,CAAC;gBAED,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;gBAC3E,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,OAAO,CAAC,CAAC;gBAE3E,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,MAAM,CAAC,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;oBACnD,OAAO;gBACT,CAAC;gBAED,OAAO,CAAC;oBACN,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC;oBACvC,KAAK,EAAE,WAAW,CAAC,KAAK,IAAI,IAAI;oBAChC,MAAM,EAAE,WAAW,CAAC,MAAM,IAAI,IAAI;oBAClC,QAAQ,EAAE,CAAC,CAAC,WAAW;oBACvB,UAAU,EAAE,WAAW,CAAC,UAAU,IAAI,SAAS;oBAC/C,UAAU,EAAE,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,UAAU;iBACpC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,MAAM,CAAC,OAAe,EAAE,SAAkB;QAChD,IAAI,OAAO,KAAK,QAAQ,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YACpD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC;QAC9C,CAAC;QAED,MAAM,MAAM,GAA2B;YACrC,GAAG,EAAE,EAAE;YACP,MAAM,EAAE,EAAE;YACV,IAAI,EAAE,EAAE;SACT,CAAC;QAEF,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC/B,CAAC;IAED;;OAEG;IACH,cAAc,CAAC,MAA2B;QACxC,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,0BAA0B,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,MAAM,WAAW,GAAG,CAAC,SAAS,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;QAClD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,KAAK,CAAC,wBAAwB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC;QAC/D,CAAC;QAED,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAChC,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,GAAG,CAAC,IAAI,MAAM,CAAC,SAAS,GAAG,EAAE,EAAE,CAAC;gBACpF,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACzD,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAlQD,sCAkQC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FFmpeg utility functions
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Format seconds to HH:MM:SS format
|
|
6
|
+
*/
|
|
7
|
+
export declare function formatTime(seconds: number): string;
|
|
8
|
+
/**
|
|
9
|
+
* Pad number with zeros
|
|
10
|
+
*/
|
|
11
|
+
export declare function pad(num: number, length: number): string;
|
|
12
|
+
/**
|
|
13
|
+
* Escape special characters for FFmpeg filter syntax
|
|
14
|
+
*/
|
|
15
|
+
export declare function escapeFilterString(str: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Escape file path for FFmpeg
|
|
18
|
+
*/
|
|
19
|
+
export declare function escapeFilePath(path: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Convert percentage to decimal
|
|
22
|
+
*/
|
|
23
|
+
export declare function percentageToDecimal(percentage: number): number;
|
|
24
|
+
/**
|
|
25
|
+
* Get file extension from path
|
|
26
|
+
*/
|
|
27
|
+
export declare function getFileExtension(path: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Validate FFmpeg is available
|
|
30
|
+
*/
|
|
31
|
+
export declare function validateFFmpegAvailable(): Promise<boolean>;
|
|
32
|
+
//# sourceMappingURL=ffmpeg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ffmpeg.d.ts","sourceRoot":"","sources":["../../../../nodes/SbRender/utils/ffmpeg.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAInD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAOhE"}
|