shiveshnavin commited on
Commit
bdadca1
·
1 Parent(s): e67f931

Bubble text

Browse files
utils/bubble/Bubble.js CHANGED
@@ -25,11 +25,14 @@ const DEFAULT_FONT_FILE = (() => {
25
  const BaseBubbleTemplates = {
26
  // Simple text box: black text on white rounded box at top-center
27
  'simple-top-center': {
 
 
 
28
  bubbleText: {
29
  fontSize: 48,
30
  fontColor: '#000000',
31
- fontName: 'Arial',
32
- // drawtext box properties - we prefer generated rounded png background, so keep box flag to indicate background
33
  box: 1,
34
  boxColor: 'white',
35
  boxBorderW: 10
@@ -163,30 +166,9 @@ class BubbleMaker {
163
  let audioMap = '-map "[aout]" -c:a aac';
164
  let extraAudioInput = '';
165
  if (bubble.audioEffectFile) {
166
- let audioPath = bubble.audioEffectFile;
167
- // support stock names mapped to public/assets/audio/<name>.mp3
168
- const stock = ['click', 'pop', 'whoosh'];
169
- // Resolve stock or relative audio names dynamically
170
- // prefer audio-effects folder
171
- const assetsDir = path.join(process.cwd(), 'public', 'assets', 'audio-effects');
172
- if (audioPath === 'typewriter') audioPath = 'click';
173
- if (fs.existsSync(assetsDir)) {
174
- const files = fs.readdirSync(assetsDir);
175
- const match = files.find(f => path.parse(f).name === audioPath);
176
- if (match) {
177
- audioPath = path.join(assetsDir, match);
178
- }
179
- }
180
-
181
- // If still not an absolute path, check relative to cwd
182
- if (audioPath && !path.isAbsolute(audioPath)) {
183
- const candidate = path.join(process.cwd(), audioPath);
184
- if (fs.existsSync(candidate)) {
185
- audioPath = candidate;
186
- } else {
187
- (onLog || console.log)(`Audio effect file not found at ${candidate}, skipping audio effect`);
188
- audioPath = null;
189
- }
190
  }
191
 
192
  if (audioPath) {
@@ -241,7 +223,7 @@ class BubbleMaker {
241
  if (needsBg) {
242
  const paddingXpx = t.boxBorderW || 14;
243
  const paddingYpx = Math.max(8, Math.round(fontSize * 0.6));
244
- const bgInfo = await createTextBackgroundPng(t.text || '', fontSize, t.fontName, t.fontColor || fontColor, bubble.backgroundColor || t.boxColor || 'white', bubble.borderRadius || t.boxBorderW || 0, paddingXpx, paddingYpx);
245
  bgPath = bgInfo.path;
246
  const { x: bgX, y: bgY } = computeXY(bgInfo.width, bgInfo.height, extra, vw, vh);
247
  // center text within bg box
@@ -258,20 +240,8 @@ class BubbleMaker {
258
 
259
  if (bubble.audioEffectFile) {
260
  // Use filter_complex to mix audio
261
- let audioPath = bubble.audioEffectFile;
262
- // prefer audio-effects folder
263
- const assetsDir = path.join(process.cwd(), 'public', 'assets', 'audio-effects');
264
- if (audioPath === 'typewriter') audioPath = 'click';
265
- if (fs.existsSync(assetsDir)) {
266
- const files = fs.readdirSync(assetsDir);
267
- const match = files.find(f => path.parse(f).name === audioPath);
268
- if (match) audioPath = path.join(assetsDir, match);
269
- }
270
- if (audioPath && !path.isAbsolute(audioPath)) {
271
- const candidate = path.join(process.cwd(), audioPath);
272
- if (fs.existsSync(candidate)) audioPath = candidate;
273
- else audioPath = null;
274
- }
275
 
276
  if (audioPath) {
277
  const aVol = typeof bubble.audioEffectVolume === 'number' ? bubble.audioEffectVolume : 1.0;
@@ -325,9 +295,9 @@ export async function test() {
325
  const outDir = path.join(cwd, 'out');
326
  if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
327
 
328
- // Single sample: typing-top-center template. Only provide the text content.
329
  const typingSample = {
330
- templateName: 'typing-top-center',
331
  bubbleText: { text: 'TYPING TITLE' },
332
  fromSec: 0.5,
333
  toSec: 5.0
 
25
  const BaseBubbleTemplates = {
26
  // Simple text box: black text on white rounded box at top-center
27
  'simple-top-center': {
28
+ // top-level bubble background controls whether a box is rendered
29
+ backgroundColor: 'white',
30
+ borderRadius: 10,
31
  bubbleText: {
32
  fontSize: 48,
33
  fontColor: '#000000',
34
+ fontName: 'Gill Sans',
35
+ // keep box flag for compatibility (generator will prefer generated rounded background PNG)
36
  box: 1,
37
  boxColor: 'white',
38
  boxBorderW: 10
 
166
  let audioMap = '-map "[aout]" -c:a aac';
167
  let extraAudioInput = '';
168
  if (bubble.audioEffectFile) {
169
+ let audioPath = resolveAudioPath(bubble.audioEffectFile);
170
+ if (!audioPath) {
171
+ (onLog || console.log)(`Audio effect file not found for ${bubble.audioEffectFile}, skipping audio effect`);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  }
173
 
174
  if (audioPath) {
 
223
  if (needsBg) {
224
  const paddingXpx = t.boxBorderW || 14;
225
  const paddingYpx = Math.max(8, Math.round(fontSize * 0.6));
226
+ const bgInfo = await createTextBackgroundPng(t.text || '', fontSize, t.fontName, bubble.backgroundColor || t.boxColor || 'white', t.boxBorderW || 14, paddingXpx, paddingYpx, bubble.borderRadius || 0, t.fontColor || fontColor);
227
  bgPath = bgInfo.path;
228
  const { x: bgX, y: bgY } = computeXY(bgInfo.width, bgInfo.height, extra, vw, vh);
229
  // center text within bg box
 
240
 
241
  if (bubble.audioEffectFile) {
242
  // Use filter_complex to mix audio
243
+ let audioPath = resolveAudioPath(bubble.audioEffectFile);
244
+ if (!audioPath) audioPath = null;
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
  if (audioPath) {
247
  const aVol = typeof bubble.audioEffectVolume === 'number' ? bubble.audioEffectVolume : 1.0;
 
295
  const outDir = path.join(cwd, 'out');
296
  if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
297
 
298
+ // Single sample: simple-top-center template. Only provide the text content.
299
  const typingSample = {
300
+ templateName: 'simple-top-center',
301
  bubbleText: { text: 'TYPING TITLE' },
302
  fromSec: 0.5,
303
  toSec: 5.0
utils/bubble/bg-utils.js CHANGED
@@ -4,14 +4,14 @@ import { tempPath, escapeXml } from './helpers.js';
4
 
5
  // Accept positional args for compatibility with Bubble.js caller
6
  export async function createTextBackgroundPng(text, fontSize=40, fontName='Arial', boxColor='#ffffff', boxBorderW=0, paddingX=20, paddingY=8, radius=10, fontColor='#000000'){
7
- // estimate size and create an SVG with rounded rect and centered text
8
  const paddingXPx = Math.round(paddingX || 20);
9
  const paddingYPx = Math.round(paddingY || 8);
10
  const safeText = (text == null) ? '' : String(text);
11
  const estimatedWidth = Math.max(60, Math.round((fontSize || 40) * Math.max(1, safeText.length) * 0.6) + paddingXPx * 2);
12
  const estimatedHeight = Math.max(24, Math.round((fontSize || 40) * 1.4) + paddingYPx * 2);
13
  const rx = Math.max(0, Math.round(radius || 0));
14
- const svg = `<?xml version="1.0" encoding="utf-8"?>\n<svg xmlns='http://www.w3.org/2000/svg' width='${estimatedWidth}' height='${estimatedHeight}'>\n <rect x='0' y='0' width='100%' height='100%' rx='${rx}' ry='${rx}' fill='${boxColor || '#ffffff'}' stroke='none' />\n <text x='50%' y='50%' dominant-baseline='middle' text-anchor='middle' font-family='${escapeXml(fontName)}' font-size='${fontSize}' fill='${escapeXml(fontColor)}'>${escapeXml(safeText)}</text>\n</svg>`;
15
  const tmp = tempPath('text-bg','png');
16
  await sharp(Buffer.from(svg)).png().toFile(tmp);
17
  const meta = await sharp(tmp).metadata();
 
4
 
5
  // Accept positional args for compatibility with Bubble.js caller
6
  export async function createTextBackgroundPng(text, fontSize=40, fontName='Arial', boxColor='#ffffff', boxBorderW=0, paddingX=20, paddingY=8, radius=10, fontColor='#000000'){
7
+ // estimate size and create an SVG with rounded rect only (text is drawn by ffmpeg drawtext to ensure crisp font)
8
  const paddingXPx = Math.round(paddingX || 20);
9
  const paddingYPx = Math.round(paddingY || 8);
10
  const safeText = (text == null) ? '' : String(text);
11
  const estimatedWidth = Math.max(60, Math.round((fontSize || 40) * Math.max(1, safeText.length) * 0.6) + paddingXPx * 2);
12
  const estimatedHeight = Math.max(24, Math.round((fontSize || 40) * 1.4) + paddingYPx * 2);
13
  const rx = Math.max(0, Math.round(radius || 0));
14
+ const svg = `<?xml version="1.0" encoding="utf-8"?>\n<svg xmlns='http://www.w3.org/2000/svg' width='${estimatedWidth}' height='${estimatedHeight}'>\n <rect x='0' y='0' width='100%' height='100%' rx='${rx}' ry='${rx}' fill='${boxColor || '#ffffff'}' stroke='none' />\n</svg>`;
15
  const tmp = tempPath('text-bg','png');
16
  await sharp(Buffer.from(svg)).png().toFile(tmp);
17
  const meta = await sharp(tmp).metadata();
utils/bubble/helpers.js CHANGED
@@ -18,8 +18,7 @@ export function escapeText(s) {
18
 
19
  export function resolveAudioPath(audioKey) {
20
  if (!audioKey) return null;
21
- let key = audioKey;
22
- if (key === 'typewriter') key = 'click';
23
  const assetsDir = path.join(process.cwd(), 'public', 'assets', 'audio-effects');
24
  if (fs.existsSync(assetsDir)) {
25
  const files = fs.readdirSync(assetsDir);
 
18
 
19
  export function resolveAudioPath(audioKey) {
20
  if (!audioKey) return null;
21
+ const key = audioKey;
 
22
  const assetsDir = path.join(process.cwd(), 'public', 'assets', 'audio-effects');
23
  if (fs.existsSync(assetsDir)) {
24
  const files = fs.readdirSync(assetsDir);