shiveshnavin commited on
Commit
23a704f
·
1 Parent(s): 2887549

Render with bubbles working

Browse files
utils/AvatarRender.js CHANGED
@@ -2,6 +2,7 @@ import { spawn } from 'child_process';
2
  import os from 'os';
3
  import path from 'path';
4
  import fs from 'fs';
 
5
 
6
  const ffmpegLocation = os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg';
7
 
@@ -61,6 +62,7 @@ export class AvatarRenderer {
61
 
62
  // Burn captions for this section
63
  const captionFile = section.audioCaptionFile;
 
64
  if (captionFile && captionFile.endsWith('.ass')) {
65
  const subbedFile = path.join(outDir, `${jobId}_section${i}_subbed.mp4`);
66
  const esc = captionFile.replace(/\\/g, '/').replace(/:/g, '\\:');
@@ -73,9 +75,25 @@ export class AvatarRenderer {
73
  onLog && onLog(`Burning captions for section ${i}...\n`);
74
  await this.runFFmpeg(subArgs, controller, onLog);
75
  tempFiles.push(subbedFile);
76
- sectionCaptionedVideos.push(subbedFile);
77
  } else {
78
- sectionCaptionedVideos.push(sectionVideo);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
80
  }
81
 
@@ -128,9 +146,21 @@ export class AvatarRenderer {
128
  }
129
  }
130
 
131
- // Cleanup intermediate files
 
132
  for (const f of tempFiles) {
133
- // try { if (fs.existsSync(f)) fs.unlinkSync(f); } catch (_) { }
 
 
 
 
 
 
 
 
 
 
 
134
  }
135
 
136
  return resultFile;
 
2
  import os from 'os';
3
  import path from 'path';
4
  import fs from 'fs';
5
+ import Bubble from './bubble/Bubble.js';
6
 
7
  const ffmpegLocation = os.platform() === 'win32' ? 'ffmpeg.exe' : 'ffmpeg';
8
 
 
62
 
63
  // Burn captions for this section
64
  const captionFile = section.audioCaptionFile;
65
+ let sourceForBubbles;
66
  if (captionFile && captionFile.endsWith('.ass')) {
67
  const subbedFile = path.join(outDir, `${jobId}_section${i}_subbed.mp4`);
68
  const esc = captionFile.replace(/\\/g, '/').replace(/:/g, '\\:');
 
75
  onLog && onLog(`Burning captions for section ${i}...\n`);
76
  await this.runFFmpeg(subArgs, controller, onLog);
77
  tempFiles.push(subbedFile);
78
+ sourceForBubbles = subbedFile;
79
  } else {
80
+ sourceForBubbles = sectionVideo;
81
+ }
82
+
83
+ // Apply bubbles if present on this section
84
+ if (section.bubbles && Array.isArray(section.bubbles) && section.bubbles.length) {
85
+ const bubbledFile = path.join(outDir, `${jobId}_section${i}_bubbled.mp4`);
86
+ try {
87
+ onLog && onLog(`Applying ${section.bubbles.length} bubble(s) to section ${i}...\n`);
88
+ await Bubble.makeBubble(sourceForBubbles, section.bubbles, bubbledFile, onLog);
89
+ tempFiles.push(bubbledFile);
90
+ sectionCaptionedVideos.push(bubbledFile);
91
+ } catch (e) {
92
+ onLog && onLog(`Bubble application failed for section ${i}, using original: ${e}\n`);
93
+ sectionCaptionedVideos.push(sourceForBubbles);
94
+ }
95
+ } else {
96
+ sectionCaptionedVideos.push(sourceForBubbles);
97
  }
98
  }
99
 
 
146
  }
147
  }
148
 
149
+ // Cleanup intermediate files — keep only the final resultFile and the manuscript JSON
150
+ const manuscriptPath = path.join(process.cwd(), 'public', 'original_manuscript.json');
151
  for (const f of tempFiles) {
152
+ if (!f) continue;
153
+ try {
154
+ const abs = path.resolve(f);
155
+ // never delete the final result file or the manuscript file
156
+ if (abs === path.resolve(resultFile)) continue;
157
+ if (abs === path.resolve(manuscriptPath)) continue;
158
+ if (fs.existsSync(abs)) {
159
+ try { fs.unlinkSync(abs); onLog && onLog(`Deleted temp file: ${abs}\n`); } catch (e) { onLog && onLog(`Failed to delete temp file ${abs}: ${e}\n`); }
160
+ }
161
+ } catch (e) {
162
+ onLog && onLog(`Error while cleaning temp file ${f}: ${e}\n`);
163
+ }
164
  }
165
 
166
  return resultFile;
utils/bubble/Bubble.js CHANGED
@@ -553,9 +553,9 @@ export async function test() {
553
  toSec: 5.0
554
  };
555
 
556
- // Additional sample: simple-top-center-image uses an image or video asset centered at top with text
557
  const imageSample = {
558
- templateName: 'simple-top-center-image',
559
  // provide mediaAbsPath (what makeBubble expects) pointing to an image
560
  mediaAbsPath: path.join(cwd, 'public', 'media2.png'),
561
  bubbleText: { text: 'IMAGE ABOVE, TEXT CENTERED' },
@@ -614,4 +614,4 @@ export async function test() {
614
  }
615
  }
616
 
617
- test()
 
553
  toSec: 5.0
554
  };
555
 
556
+ // Additional sample: simple-top-center-media uses an image or video asset centered at top with text
557
  const imageSample = {
558
+ templateName: 'simple-top-center-media',
559
  // provide mediaAbsPath (what makeBubble expects) pointing to an image
560
  mediaAbsPath: path.join(cwd, 'public', 'media2.png'),
561
  bubbleText: { text: 'IMAGE ABOVE, TEXT CENTERED' },
 
614
  }
615
  }
616
 
617
+ // test()
utils/bubble/bubble-templates.js CHANGED
@@ -26,7 +26,7 @@ export const BaseBubbleTemplates = {
26
  // image-only variant: the caller must still provide the path when using this
27
  // template. animation defaults are pulled up to the top level so they survive
28
  // the case where the user specifies `mediaAbsPath` as a simple string.
29
- 'simple-top-center-image': {
30
  borderRadius: 20,
31
  normalizeAudio: true,
32
  audioEffectFile: 'woosh',
 
26
  // image-only variant: the caller must still provide the path when using this
27
  // template. animation defaults are pulled up to the top level so they survive
28
  // the case where the user specifies `mediaAbsPath` as a simple string.
29
+ 'simple-top-center-media': {
30
  borderRadius: 20,
31
  normalizeAudio: true,
32
  audioEffectFile: 'woosh',