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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +442 -0
  3. package/dist/nodes/SbRender/SbRender.node.d.ts +10 -0
  4. package/dist/nodes/SbRender/SbRender.node.d.ts.map +1 -0
  5. package/dist/nodes/SbRender/SbRender.node.js +768 -0
  6. package/dist/nodes/SbRender/SbRender.node.js.map +1 -0
  7. package/dist/nodes/SbRender/interfaces/index.d.ts +155 -0
  8. package/dist/nodes/SbRender/interfaces/index.d.ts.map +1 -0
  9. package/dist/nodes/SbRender/interfaces/index.js +41 -0
  10. package/dist/nodes/SbRender/interfaces/index.js.map +1 -0
  11. package/dist/nodes/SbRender/sbrender.svg +7 -0
  12. package/dist/nodes/SbRender/services/AudioMixer.d.ts +26 -0
  13. package/dist/nodes/SbRender/services/AudioMixer.d.ts.map +1 -0
  14. package/dist/nodes/SbRender/services/AudioMixer.js +120 -0
  15. package/dist/nodes/SbRender/services/AudioMixer.js.map +1 -0
  16. package/dist/nodes/SbRender/services/FileManager.d.ts +37 -0
  17. package/dist/nodes/SbRender/services/FileManager.d.ts.map +1 -0
  18. package/dist/nodes/SbRender/services/FileManager.js +157 -0
  19. package/dist/nodes/SbRender/services/FileManager.js.map +1 -0
  20. package/dist/nodes/SbRender/services/SubtitleEngine.d.ts +64 -0
  21. package/dist/nodes/SbRender/services/SubtitleEngine.d.ts.map +1 -0
  22. package/dist/nodes/SbRender/services/SubtitleEngine.js +213 -0
  23. package/dist/nodes/SbRender/services/SubtitleEngine.js.map +1 -0
  24. package/dist/nodes/SbRender/services/VideoComposer.d.ts +28 -0
  25. package/dist/nodes/SbRender/services/VideoComposer.d.ts.map +1 -0
  26. package/dist/nodes/SbRender/services/VideoComposer.js +228 -0
  27. package/dist/nodes/SbRender/services/VideoComposer.js.map +1 -0
  28. package/dist/nodes/SbRender/utils/ffmpeg.d.ts +32 -0
  29. package/dist/nodes/SbRender/utils/ffmpeg.d.ts.map +1 -0
  30. package/dist/nodes/SbRender/utils/ffmpeg.js +107 -0
  31. package/dist/nodes/SbRender/utils/ffmpeg.js.map +1 -0
  32. package/dist/nodes/SbRender/utils/validation.d.ts +32 -0
  33. package/dist/nodes/SbRender/utils/validation.d.ts.map +1 -0
  34. package/dist/nodes/SbRender/utils/validation.js +210 -0
  35. package/dist/nodes/SbRender/utils/validation.js.map +1 -0
  36. package/index.js +3 -0
  37. package/package.json +69 -0
@@ -0,0 +1,768 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SbRender = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const FileManager_1 = require("./services/FileManager");
6
+ const AudioMixer_1 = require("./services/AudioMixer");
7
+ const SubtitleEngine_1 = require("./services/SubtitleEngine");
8
+ const VideoComposer_1 = require("./services/VideoComposer");
9
+ const validation_1 = require("./utils/validation");
10
+ const interfaces_1 = require("./interfaces");
11
+ /**
12
+ * sb-render Node
13
+ * Render videos with customizable subtitles, BGM, and narration
14
+ */
15
+ class SbRender {
16
+ constructor() {
17
+ this.description = {
18
+ displayName: 'SB Render',
19
+ name: 'sbRender',
20
+ icon: 'file:sbrender.svg',
21
+ group: ['transform'],
22
+ version: 1,
23
+ subtitle: '={{$parameter["operation"]}}',
24
+ description: 'Render videos with subtitles, BGM, and narration using FFmpeg',
25
+ defaults: {
26
+ name: 'SB Render',
27
+ },
28
+ inputs: ['main'],
29
+ outputs: ['main'],
30
+ properties: [
31
+ // Resource
32
+ {
33
+ displayName: 'Resource',
34
+ name: 'resource',
35
+ type: 'options',
36
+ noDataExpression: true,
37
+ options: [
38
+ {
39
+ name: 'Video',
40
+ value: 'Video',
41
+ },
42
+ ],
43
+ default: 'Video',
44
+ },
45
+ // Operation
46
+ {
47
+ displayName: 'Operation',
48
+ name: 'operation',
49
+ type: 'options',
50
+ noDataExpression: true,
51
+ displayOptions: {
52
+ show: {
53
+ resource: ['Video'],
54
+ },
55
+ },
56
+ options: [
57
+ {
58
+ name: 'Render',
59
+ value: 'Render',
60
+ description: 'Compose video with audio and subtitles',
61
+ action: 'Render video',
62
+ },
63
+ ],
64
+ default: 'Render',
65
+ },
66
+ // === VIDEO INPUT SECTION ===
67
+ {
68
+ displayName: 'Video Source',
69
+ name: 'videoSource',
70
+ type: 'options',
71
+ displayOptions: {
72
+ show: {
73
+ resource: ['Video'],
74
+ operation: ['Render'],
75
+ },
76
+ },
77
+ options: [
78
+ {
79
+ name: 'URL',
80
+ value: 'url',
81
+ },
82
+ {
83
+ name: 'Binary Data',
84
+ value: 'binary',
85
+ },
86
+ ],
87
+ default: 'url',
88
+ description: 'Source of the video file',
89
+ },
90
+ {
91
+ displayName: 'Video URL',
92
+ name: 'videoUrl',
93
+ type: 'string',
94
+ displayOptions: {
95
+ show: {
96
+ resource: ['Video'],
97
+ operation: ['Render'],
98
+ videoSource: ['url'],
99
+ },
100
+ },
101
+ default: '',
102
+ placeholder: 'https://example.com/video.mp4',
103
+ required: true,
104
+ description: 'URL of the video file to process',
105
+ },
106
+ {
107
+ displayName: 'Video Binary Property',
108
+ name: 'videoBinaryProperty',
109
+ type: 'string',
110
+ displayOptions: {
111
+ show: {
112
+ resource: ['Video'],
113
+ operation: ['Render'],
114
+ videoSource: ['binary'],
115
+ },
116
+ },
117
+ default: 'data',
118
+ required: true,
119
+ placeholder: 'data',
120
+ description: 'Name of the binary property containing the video',
121
+ },
122
+ // === BGM SECTION ===
123
+ {
124
+ displayName: 'Enable Background Music (BGM)',
125
+ name: 'enableBGM',
126
+ type: 'boolean',
127
+ displayOptions: {
128
+ show: {
129
+ resource: ['Video'],
130
+ operation: ['Render'],
131
+ },
132
+ },
133
+ default: false,
134
+ description: 'Whether to add background music to the video',
135
+ },
136
+ {
137
+ displayName: 'BGM Source',
138
+ name: 'bgmSource',
139
+ type: 'options',
140
+ displayOptions: {
141
+ show: {
142
+ resource: ['Video'],
143
+ operation: ['Render'],
144
+ enableBGM: [true],
145
+ },
146
+ },
147
+ options: [
148
+ {
149
+ name: 'URL',
150
+ value: 'url',
151
+ },
152
+ {
153
+ name: 'Binary Data',
154
+ value: 'binary',
155
+ },
156
+ ],
157
+ default: 'url',
158
+ description: 'Source of the BGM file',
159
+ },
160
+ {
161
+ displayName: 'BGM URL',
162
+ name: 'bgmUrl',
163
+ type: 'string',
164
+ displayOptions: {
165
+ show: {
166
+ resource: ['Video'],
167
+ operation: ['Render'],
168
+ enableBGM: [true],
169
+ bgmSource: ['url'],
170
+ },
171
+ },
172
+ default: '',
173
+ placeholder: 'https://example.com/bgm.mp3',
174
+ description: 'URL of the background music file',
175
+ },
176
+ {
177
+ displayName: 'BGM Binary Property',
178
+ name: 'bgmBinaryProperty',
179
+ type: 'string',
180
+ displayOptions: {
181
+ show: {
182
+ resource: ['Video'],
183
+ operation: ['Render'],
184
+ enableBGM: [true],
185
+ bgmSource: ['binary'],
186
+ },
187
+ },
188
+ default: 'data',
189
+ placeholder: 'data',
190
+ description: 'Name of the binary property containing the BGM',
191
+ },
192
+ {
193
+ displayName: 'BGM Volume',
194
+ name: 'bgmVolume',
195
+ type: 'number',
196
+ displayOptions: {
197
+ show: {
198
+ resource: ['Video'],
199
+ operation: ['Render'],
200
+ enableBGM: [true],
201
+ },
202
+ },
203
+ typeOptions: {
204
+ minValue: 0,
205
+ maxValue: 100,
206
+ },
207
+ default: 30,
208
+ description: 'Volume of background music (0-100)',
209
+ },
210
+ {
211
+ displayName: 'BGM Fade In (Seconds)',
212
+ name: 'bgmFadeIn',
213
+ type: 'number',
214
+ displayOptions: {
215
+ show: {
216
+ resource: ['Video'],
217
+ operation: ['Render'],
218
+ enableBGM: [true],
219
+ },
220
+ },
221
+ typeOptions: {
222
+ minValue: 0,
223
+ },
224
+ default: 2,
225
+ description: 'Fade-in duration for BGM in seconds',
226
+ },
227
+ {
228
+ displayName: 'BGM Fade Out (Seconds)',
229
+ name: 'bgmFadeOut',
230
+ type: 'number',
231
+ displayOptions: {
232
+ show: {
233
+ resource: ['Video'],
234
+ operation: ['Render'],
235
+ enableBGM: [true],
236
+ },
237
+ },
238
+ typeOptions: {
239
+ minValue: 0,
240
+ },
241
+ default: 2,
242
+ description: 'Fade-out duration for BGM in seconds',
243
+ },
244
+ // === NARRATION SECTION ===
245
+ {
246
+ displayName: 'Enable Narration',
247
+ name: 'enableNarration',
248
+ type: 'boolean',
249
+ displayOptions: {
250
+ show: {
251
+ resource: ['Video'],
252
+ operation: ['Render'],
253
+ },
254
+ },
255
+ default: false,
256
+ description: 'Whether to add narration audio to the video',
257
+ },
258
+ {
259
+ displayName: 'Narration Source',
260
+ name: 'narrationSource',
261
+ type: 'options',
262
+ displayOptions: {
263
+ show: {
264
+ resource: ['Video'],
265
+ operation: ['Render'],
266
+ enableNarration: [true],
267
+ },
268
+ },
269
+ options: [
270
+ {
271
+ name: 'URL',
272
+ value: 'url',
273
+ },
274
+ {
275
+ name: 'Binary Data',
276
+ value: 'binary',
277
+ },
278
+ ],
279
+ default: 'url',
280
+ description: 'Source of the narration file',
281
+ },
282
+ {
283
+ displayName: 'Narration URL',
284
+ name: 'narrationUrl',
285
+ type: 'string',
286
+ displayOptions: {
287
+ show: {
288
+ resource: ['Video'],
289
+ operation: ['Render'],
290
+ enableNarration: [true],
291
+ narrationSource: ['url'],
292
+ },
293
+ },
294
+ default: '',
295
+ placeholder: 'https://example.com/narration.mp3',
296
+ description: 'URL of the narration audio file',
297
+ },
298
+ {
299
+ displayName: 'Narration Binary Property',
300
+ name: 'narrationBinaryProperty',
301
+ type: 'string',
302
+ displayOptions: {
303
+ show: {
304
+ resource: ['Video'],
305
+ operation: ['Render'],
306
+ enableNarration: [true],
307
+ narrationSource: ['binary'],
308
+ },
309
+ },
310
+ default: 'data',
311
+ placeholder: 'data',
312
+ description: 'Name of the binary property containing the narration',
313
+ },
314
+ {
315
+ displayName: 'Narration Volume',
316
+ name: 'narrationVolume',
317
+ type: 'number',
318
+ displayOptions: {
319
+ show: {
320
+ resource: ['Video'],
321
+ operation: ['Render'],
322
+ enableNarration: [true],
323
+ },
324
+ },
325
+ typeOptions: {
326
+ minValue: 0,
327
+ maxValue: 100,
328
+ },
329
+ default: 80,
330
+ description: 'Volume of narration (0-100)',
331
+ },
332
+ {
333
+ displayName: 'Narration Delay (Seconds)',
334
+ name: 'narrationDelay',
335
+ type: 'number',
336
+ displayOptions: {
337
+ show: {
338
+ resource: ['Video'],
339
+ operation: ['Render'],
340
+ enableNarration: [true],
341
+ },
342
+ },
343
+ typeOptions: {
344
+ minValue: 0,
345
+ },
346
+ default: 0,
347
+ description: 'Delay before narration starts in seconds',
348
+ },
349
+ // === SUBTITLE SECTION ===
350
+ {
351
+ displayName: 'Enable Subtitles',
352
+ name: 'enableSubtitles',
353
+ type: 'boolean',
354
+ displayOptions: {
355
+ show: {
356
+ resource: ['Video'],
357
+ operation: ['Render'],
358
+ },
359
+ },
360
+ default: false,
361
+ description: 'Whether to add subtitles to the video',
362
+ },
363
+ {
364
+ displayName: 'Subtitles',
365
+ name: 'subtitles',
366
+ placeholder: 'Add Subtitle',
367
+ type: 'fixedCollection',
368
+ typeOptions: {
369
+ multipleValues: true,
370
+ },
371
+ displayOptions: {
372
+ show: {
373
+ resource: ['Video'],
374
+ operation: ['Render'],
375
+ enableSubtitles: [true],
376
+ },
377
+ },
378
+ default: {},
379
+ options: [
380
+ {
381
+ name: 'subtitle',
382
+ displayName: 'Subtitle',
383
+ values: [
384
+ {
385
+ displayName: 'Alignment',
386
+ name: 'alignment',
387
+ type: 'options',
388
+ options: [
389
+ {
390
+ name: 'Left',
391
+ value: 'left',
392
+ },
393
+ {
394
+ name: 'Center',
395
+ value: 'center',
396
+ },
397
+ {
398
+ name: 'Right',
399
+ value: 'right',
400
+ },
401
+ ],
402
+ default: 'center',
403
+ description: 'Text alignment',
404
+ },
405
+ {
406
+ displayName: 'Background Color',
407
+ name: 'backgroundColor',
408
+ type: 'color',
409
+ default: '#000000',
410
+ description: 'Background color of the subtitle',
411
+ },
412
+ {
413
+ displayName: 'Background Opacity',
414
+ name: 'backgroundOpacity',
415
+ type: 'number',
416
+ default: 80,
417
+ description: 'Opacity of the background (0-100)',
418
+ },
419
+ {
420
+ displayName: 'Border Color',
421
+ name: 'borderColor',
422
+ type: 'color',
423
+ default: '#000000',
424
+ description: 'Color of the subtitle border',
425
+ },
426
+ {
427
+ displayName: 'Border Width',
428
+ name: 'borderWidth',
429
+ type: 'number',
430
+ default: 2,
431
+ description: 'Width of the subtitle border in pixels',
432
+ },
433
+ {
434
+ displayName: 'Custom X Position',
435
+ name: 'customX',
436
+ type: 'number',
437
+ default: 960,
438
+ description: 'Horizontal position in pixels (for custom position)',
439
+ },
440
+ {
441
+ displayName: 'Custom Y Position',
442
+ name: 'customY',
443
+ type: 'number',
444
+ default: 980,
445
+ description: 'Vertical position in pixels (for custom position)',
446
+ },
447
+ {
448
+ displayName: 'End Time (Seconds)',
449
+ name: 'endTime',
450
+ type: 'number',
451
+ default: 5,
452
+ description: 'When the subtitle disappears',
453
+ },
454
+ {
455
+ displayName: 'Font Color',
456
+ name: 'fontColor',
457
+ type: 'color',
458
+ default: '#FFFFFF',
459
+ description: 'Color of the subtitle text',
460
+ },
461
+ {
462
+ displayName: 'Font Family',
463
+ name: 'fontFamily',
464
+ type: 'string',
465
+ default: 'Arial',
466
+ description: 'Font family for the subtitle',
467
+ },
468
+ {
469
+ displayName: 'Font Size',
470
+ name: 'fontSize',
471
+ type: 'number',
472
+ default: 48,
473
+ description: 'Size of the subtitle text',
474
+ },
475
+ {
476
+ displayName: 'Position',
477
+ name: 'position',
478
+ type: 'options',
479
+ options: [
480
+ {
481
+ name: 'Top',
482
+ value: 'top',
483
+ },
484
+ {
485
+ name: 'Middle',
486
+ value: 'middle',
487
+ },
488
+ {
489
+ name: 'Bottom',
490
+ value: 'bottom',
491
+ },
492
+ {
493
+ name: 'Custom',
494
+ value: 'custom',
495
+ },
496
+ ],
497
+ default: 'bottom',
498
+ description: 'Vertical position of the subtitle',
499
+ },
500
+ {
501
+ displayName: 'Start Time (Seconds)',
502
+ name: 'startTime',
503
+ type: 'number',
504
+ default: 0,
505
+ description: 'When the subtitle appears',
506
+ },
507
+ {
508
+ displayName: 'Text',
509
+ name: 'text',
510
+ type: 'string',
511
+ default: '',
512
+ description: 'Subtitle text content',
513
+ },
514
+ ],
515
+ },
516
+ ],
517
+ },
518
+ // === OUTPUT SECTION ===
519
+ {
520
+ displayName: 'Output Format',
521
+ name: 'outputFormat',
522
+ type: 'options',
523
+ displayOptions: {
524
+ show: {
525
+ resource: ['Video'],
526
+ operation: ['Render'],
527
+ },
528
+ },
529
+ options: [
530
+ {
531
+ name: 'MP4',
532
+ value: 'mp4',
533
+ },
534
+ {
535
+ name: 'MOV',
536
+ value: 'mov',
537
+ },
538
+ {
539
+ name: 'WebM',
540
+ value: 'webm',
541
+ },
542
+ ],
543
+ default: 'mp4',
544
+ description: 'Output video format',
545
+ },
546
+ {
547
+ displayName: 'Video Codec',
548
+ name: 'videoCodec',
549
+ type: 'options',
550
+ displayOptions: {
551
+ show: {
552
+ resource: ['Video'],
553
+ operation: ['Render'],
554
+ },
555
+ },
556
+ options: [
557
+ {
558
+ name: 'H.264 (Libx264)',
559
+ value: 'libx264',
560
+ },
561
+ {
562
+ name: 'H.265 (Libx265)',
563
+ value: 'libx265',
564
+ },
565
+ {
566
+ name: 'VP9',
567
+ value: 'vp9',
568
+ },
569
+ ],
570
+ default: 'libx264',
571
+ description: 'Video codec for encoding',
572
+ },
573
+ {
574
+ displayName: 'Quality',
575
+ name: 'quality',
576
+ type: 'options',
577
+ displayOptions: {
578
+ show: {
579
+ resource: ['Video'],
580
+ operation: ['Render'],
581
+ },
582
+ },
583
+ options: [
584
+ {
585
+ name: 'Low',
586
+ value: 'low',
587
+ },
588
+ {
589
+ name: 'Medium',
590
+ value: 'medium',
591
+ },
592
+ {
593
+ name: 'High',
594
+ value: 'high',
595
+ },
596
+ {
597
+ name: 'Custom',
598
+ value: 'custom',
599
+ },
600
+ ],
601
+ default: 'high',
602
+ description: 'Output quality preset',
603
+ },
604
+ {
605
+ displayName: 'Custom CRF',
606
+ name: 'customCRF',
607
+ type: 'number',
608
+ displayOptions: {
609
+ show: {
610
+ resource: ['Video'],
611
+ operation: ['Render'],
612
+ quality: ['custom'],
613
+ },
614
+ },
615
+ typeOptions: {
616
+ minValue: 0,
617
+ maxValue: 51,
618
+ },
619
+ default: 18,
620
+ description: 'Custom CRF value (0-51, lower is better quality)',
621
+ },
622
+ {
623
+ displayName: 'Output Binary Property',
624
+ name: 'outputBinaryProperty',
625
+ type: 'string',
626
+ displayOptions: {
627
+ show: {
628
+ resource: ['Video'],
629
+ operation: ['Render'],
630
+ },
631
+ },
632
+ default: 'data',
633
+ description: 'Name of the binary property to store the rendered video',
634
+ },
635
+ ],
636
+ };
637
+ }
638
+ async execute() {
639
+ var _a, _b, _c, _d, _e;
640
+ const items = this.getInputData();
641
+ const returnData = [];
642
+ // Initialize services
643
+ const fileManager = new FileManager_1.FileManager();
644
+ const audioMixer = new AudioMixer_1.AudioMixer();
645
+ const subtitleEngine = new SubtitleEngine_1.SubtitleEngine();
646
+ const videoComposer = new VideoComposer_1.VideoComposer();
647
+ // Helper: Get media file from URL or binary data
648
+ const getMediaFile = async (source, url, binaryProperty, itemIndex) => {
649
+ if (source === 'url' && url) {
650
+ return await fileManager.downloadFile(url);
651
+ }
652
+ if (source === 'binary' && binaryProperty) {
653
+ const binaryData = this.helpers.assertBinaryData(itemIndex, binaryProperty);
654
+ const buffer = await this.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
655
+ const getExtension = (mimeType) => {
656
+ const mimeMap = {
657
+ 'video/mp4': '.mp4',
658
+ 'video/quicktime': '.mov',
659
+ 'video/webm': '.webm',
660
+ 'audio/mpeg': '.mp3',
661
+ 'audio/wav': '.wav',
662
+ 'audio/aac': '.aac',
663
+ };
664
+ return mimeMap[mimeType] || '';
665
+ };
666
+ const extension = binaryData.fileExtension || getExtension(binaryData.mimeType);
667
+ return await fileManager.extractBinary(buffer, extension);
668
+ }
669
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid media source configuration', { itemIndex });
670
+ };
671
+ try {
672
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
673
+ try {
674
+ const resource = this.getNodeParameter('resource', itemIndex);
675
+ const operation = this.getNodeParameter('operation', itemIndex);
676
+ if (resource === 'Video' && operation === 'Render') {
677
+ // Get all parameters
678
+ const params = this.getNodeParameter('*', itemIndex);
679
+ // Validate parameters
680
+ try {
681
+ (0, validation_1.validateParams)(params);
682
+ }
683
+ catch (error) {
684
+ if (error instanceof validation_1.ValidationError) {
685
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, { itemIndex });
686
+ }
687
+ throw error;
688
+ }
689
+ // 1. Download/extract video
690
+ const videoPath = await getMediaFile(params.videoSource, params.videoUrl, params.videoBinaryProperty, itemIndex);
691
+ // 2. Get video metadata
692
+ const metadata = await videoComposer.getVideoMetadata(videoPath);
693
+ // 3. Download/extract BGM and narration if enabled
694
+ let bgmPath = null;
695
+ let narrationPath = null;
696
+ if (params.enableBGM && params.bgmSource) {
697
+ bgmPath = await getMediaFile(params.bgmSource, params.bgmUrl, params.bgmBinaryProperty, itemIndex);
698
+ }
699
+ if (params.enableNarration && params.narrationSource) {
700
+ narrationPath = await getMediaFile(params.narrationSource, params.narrationUrl, params.narrationBinaryProperty, itemIndex);
701
+ }
702
+ // 4. Generate audio filter chain
703
+ let audioFilterChain = '';
704
+ if (params.enableBGM || params.enableNarration) {
705
+ const audioConfig = {
706
+ videoDuration: metadata.duration,
707
+ bgmPath: bgmPath || undefined,
708
+ bgmVolume: (_a = params.bgmVolume) !== null && _a !== void 0 ? _a : interfaces_1.DEFAULTS.bgmVolume,
709
+ bgmFadeIn: (_b = params.bgmFadeIn) !== null && _b !== void 0 ? _b : interfaces_1.DEFAULTS.bgmFadeIn,
710
+ bgmFadeOut: (_c = params.bgmFadeOut) !== null && _c !== void 0 ? _c : interfaces_1.DEFAULTS.bgmFadeOut,
711
+ narrationPath: narrationPath || undefined,
712
+ narrationVolume: (_d = params.narrationVolume) !== null && _d !== void 0 ? _d : interfaces_1.DEFAULTS.narrationVolume,
713
+ narrationDelay: (_e = params.narrationDelay) !== null && _e !== void 0 ? _e : interfaces_1.DEFAULTS.narrationDelay,
714
+ };
715
+ audioFilterChain = audioMixer.getAudioFilterChain(audioConfig, metadata.hasAudio);
716
+ }
717
+ // 5. Generate subtitles if enabled
718
+ let subtitlePath = null;
719
+ if (params.enableSubtitles && params.subtitles) {
720
+ const subtitleArray = params.subtitles.subtitle || [];
721
+ if (subtitleArray.length > 0) {
722
+ const assContent = subtitleEngine.generateASS(subtitleArray, metadata.width, metadata.height);
723
+ subtitlePath = await subtitleEngine.writeSubtitleFile(assContent, 'ass');
724
+ }
725
+ }
726
+ // 6. Compose final video
727
+ const outputPath = await fileManager.createTempFile(`.${params.outputFormat}`);
728
+ const videoBuffer = await videoComposer.composeWithAudioMix(videoPath, bgmPath, narrationPath, subtitlePath, audioFilterChain, outputPath, params);
729
+ // 7. Return result with binary data
730
+ const binaryPropertyName = params.outputBinaryProperty || 'data';
731
+ const result = {
732
+ json: {
733
+ success: true,
734
+ duration: metadata.duration,
735
+ width: metadata.width,
736
+ height: metadata.height,
737
+ },
738
+ binary: {
739
+ [binaryPropertyName]: await this.helpers.prepareBinaryData(videoBuffer, `rendered.${params.outputFormat}`, `video/${params.outputFormat}`),
740
+ },
741
+ pairedItem: itemIndex,
742
+ };
743
+ returnData.push(result);
744
+ }
745
+ }
746
+ catch (error) {
747
+ if (this.continueOnFail()) {
748
+ returnData.push({
749
+ json: {
750
+ error: error instanceof Error ? error.message : String(error),
751
+ },
752
+ pairedItem: itemIndex,
753
+ });
754
+ continue;
755
+ }
756
+ throw error;
757
+ }
758
+ }
759
+ return [returnData];
760
+ }
761
+ finally {
762
+ // Cleanup temporary files
763
+ await fileManager.cleanup();
764
+ }
765
+ }
766
+ }
767
+ exports.SbRender = SbRender;
768
+ //# sourceMappingURL=SbRender.node.js.map