Spaces:
Running
Running
Commit ·
5635fbe
1
Parent(s): 59d8ec1
Add bubble fullscreenre
Browse files- render-server.js +1 -1
- utils/bubble/Bubble.js +37 -13
- utils/bubble/bubble-templates.js +4 -4
- utils/bubble/layout.js +3 -3
render-server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import app from './app.js';
|
| 2 |
-
let port = process.env.PORT ||
|
| 3 |
import Bubble from './utils/bubble/Bubble.js';
|
| 4 |
import express from 'express';
|
| 5 |
import { createServer } from 'http';
|
|
|
|
| 1 |
import app from './app.js';
|
| 2 |
+
let port = process.env.PORT || 8089
|
| 3 |
import Bubble from './utils/bubble/Bubble.js';
|
| 4 |
import express from 'express';
|
| 5 |
import { createServer } from 'http';
|
utils/bubble/Bubble.js
CHANGED
|
@@ -334,22 +334,32 @@ class BubbleMaker {
|
|
| 334 |
|
| 335 |
const imgExts = ['.png', '.jpg', '.jpeg', '.webp', '.bmp', '.gif'];
|
| 336 |
const isImageFile = imgExts.includes(path.extname(overlayPath).toLowerCase());
|
| 337 |
-
const padX = extra.paddingX
|
| 338 |
-
const padY = extra.paddingY
|
| 339 |
let ow = null, oh = null;
|
| 340 |
if (extra.size === 'half') ow = Math.round(vw * 0.45);
|
| 341 |
else if (extra.size !== 'full') ow = Math.round(vw * 0.30);
|
| 342 |
|
| 343 |
let overlayMeta;
|
| 344 |
try { overlayMeta = await FFMpegUtils.getMediaMetadata(overlayPath); } catch (e) { overlayMeta = null; }
|
|
|
|
|
|
|
|
|
|
| 345 |
if (extra.size === 'full') {
|
| 346 |
-
const
|
| 347 |
-
|
| 348 |
if (overlayMeta && overlayMeta.video && overlayMeta.video.width && overlayMeta.video.height) {
|
| 349 |
-
const
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
} else {
|
| 352 |
-
|
|
|
|
| 353 |
}
|
| 354 |
} else if (overlayMeta && overlayMeta.video && overlayMeta.video.width && overlayMeta.video.height) {
|
| 355 |
const ar = overlayMeta.video.width / overlayMeta.video.height;
|
|
@@ -386,7 +396,14 @@ class BubbleMaker {
|
|
| 386 |
: `-i "${overlayPath}"`;
|
| 387 |
inputs.push(overlayFlag);
|
| 388 |
|
| 389 |
-
let scalePart
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
if (isFade) scalePart += `,fade=t=in:st=${from}:d=${animDur}:alpha=1,fade=t=out:st=${fadeOut}:d=${animDur}:alpha=1`;
|
| 391 |
scalePart += `[ov${inputIndex}]`;
|
| 392 |
filterParts.push(scalePart);
|
|
@@ -423,8 +440,8 @@ class BubbleMaker {
|
|
| 423 |
if (isMedia) {
|
| 424 |
const imgExts = ['.png', '.jpg', '.jpeg', '.webp', '.bmp', '.gif'];
|
| 425 |
const isImageFile = imgExts.includes(path.extname(overlayPath).toLowerCase());
|
| 426 |
-
const padX = extra.paddingX
|
| 427 |
-
const padY = extra.paddingY
|
| 428 |
let ow = null, oh = null;
|
| 429 |
if (extra.size === 'half') ow = Math.round(vw * 0.45);
|
| 430 |
else if (extra.size !== 'full') ow = Math.round(vw * 0.30);
|
|
@@ -475,7 +492,14 @@ class BubbleMaker {
|
|
| 475 |
? (mainDuration > 0 ? `-loop 1 -t ${mainDuration} -i "${overlayPath}"` : `-loop 1 -i "${overlayPath}"`)
|
| 476 |
: `-i "${overlayPath}"`;
|
| 477 |
|
| 478 |
-
let overlayFilter
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
if (isFade) overlayFilter += `,fade=t=in:st=${from}:d=${animDur}:alpha=1,fade=t=out:st=${fadeOut}:d=${animDur}:alpha=1`;
|
| 480 |
overlayFilter += `[ov];[0:v][ov]overlay=${xExpr}:${yExpr}:enable='between(t,${from},${to})'[vout]`;
|
| 481 |
|
|
@@ -487,8 +511,8 @@ class BubbleMaker {
|
|
| 487 |
const fontColor = t.fontColor || '#FFFFFF';
|
| 488 |
const posX = extra.positionX || 'center';
|
| 489 |
const posY = extra.positionY || 'center';
|
| 490 |
-
const padX = extra.paddingX
|
| 491 |
-
const padY = extra.paddingY
|
| 492 |
|
| 493 |
let fontfilePath = null;
|
| 494 |
if (t.fontName) {
|
|
|
|
| 334 |
|
| 335 |
const imgExts = ['.png', '.jpg', '.jpeg', '.webp', '.bmp', '.gif'];
|
| 336 |
const isImageFile = imgExts.includes(path.extname(overlayPath).toLowerCase());
|
| 337 |
+
const padX = typeof extra.paddingX === 'number' ? extra.paddingX : 5;
|
| 338 |
+
const padY = typeof extra.paddingY === 'number' ? extra.paddingY : 5;
|
| 339 |
let ow = null, oh = null;
|
| 340 |
if (extra.size === 'half') ow = Math.round(vw * 0.45);
|
| 341 |
else if (extra.size !== 'full') ow = Math.round(vw * 0.30);
|
| 342 |
|
| 343 |
let overlayMeta;
|
| 344 |
try { overlayMeta = await FFMpegUtils.getMediaMetadata(overlayPath); } catch (e) { overlayMeta = null; }
|
| 345 |
+
let cropOverlay = false;
|
| 346 |
+
let scaledW = null;
|
| 347 |
+
let scaledH = null;
|
| 348 |
if (extra.size === 'full') {
|
| 349 |
+
const targetW = Math.max(1, Math.round(vw * (1 - 2 * padX / 100)));
|
| 350 |
+
const targetH = Math.max(1, Math.round(vh * (1 - 2 * padY / 100)));
|
| 351 |
if (overlayMeta && overlayMeta.video && overlayMeta.video.width && overlayMeta.video.height) {
|
| 352 |
+
const srcW = overlayMeta.video.width;
|
| 353 |
+
const srcH = overlayMeta.video.height;
|
| 354 |
+
const scaleFactor = Math.max(targetW / srcW, targetH / srcH);
|
| 355 |
+
scaledW = Math.max(1, Math.round(srcW * scaleFactor));
|
| 356 |
+
scaledH = Math.max(1, Math.round(srcH * scaleFactor));
|
| 357 |
+
ow = targetW;
|
| 358 |
+
oh = targetH;
|
| 359 |
+
cropOverlay = true;
|
| 360 |
} else {
|
| 361 |
+
ow = targetW;
|
| 362 |
+
oh = targetH;
|
| 363 |
}
|
| 364 |
} else if (overlayMeta && overlayMeta.video && overlayMeta.video.width && overlayMeta.video.height) {
|
| 365 |
const ar = overlayMeta.video.width / overlayMeta.video.height;
|
|
|
|
| 396 |
: `-i "${overlayPath}"`;
|
| 397 |
inputs.push(overlayFlag);
|
| 398 |
|
| 399 |
+
let scalePart;
|
| 400 |
+
if (cropOverlay && scaledW && scaledH) {
|
| 401 |
+
const cropX = Math.max(0, Math.round((scaledW - ow) / 2));
|
| 402 |
+
const cropY = Math.max(0, Math.round((scaledH - oh) / 2));
|
| 403 |
+
scalePart = `[${inputIndex}:v]scale=${scaledW}:${scaledH},crop=${ow}:${oh}:${cropX}:${cropY},format=rgba`;
|
| 404 |
+
} else {
|
| 405 |
+
scalePart = `[${inputIndex}:v]scale=${ow}:${oh},format=rgba`;
|
| 406 |
+
}
|
| 407 |
if (isFade) scalePart += `,fade=t=in:st=${from}:d=${animDur}:alpha=1,fade=t=out:st=${fadeOut}:d=${animDur}:alpha=1`;
|
| 408 |
scalePart += `[ov${inputIndex}]`;
|
| 409 |
filterParts.push(scalePart);
|
|
|
|
| 440 |
if (isMedia) {
|
| 441 |
const imgExts = ['.png', '.jpg', '.jpeg', '.webp', '.bmp', '.gif'];
|
| 442 |
const isImageFile = imgExts.includes(path.extname(overlayPath).toLowerCase());
|
| 443 |
+
const padX = typeof extra.paddingX === 'number' ? extra.paddingX : 5;
|
| 444 |
+
const padY = typeof extra.paddingY === 'number' ? extra.paddingY : 5;
|
| 445 |
let ow = null, oh = null;
|
| 446 |
if (extra.size === 'half') ow = Math.round(vw * 0.45);
|
| 447 |
else if (extra.size !== 'full') ow = Math.round(vw * 0.30);
|
|
|
|
| 492 |
? (mainDuration > 0 ? `-loop 1 -t ${mainDuration} -i "${overlayPath}"` : `-loop 1 -i "${overlayPath}"`)
|
| 493 |
: `-i "${overlayPath}"`;
|
| 494 |
|
| 495 |
+
let overlayFilter;
|
| 496 |
+
if (cropOverlay && scaledW && scaledH) {
|
| 497 |
+
const cropX = Math.max(0, Math.round((scaledW - ow) / 2));
|
| 498 |
+
const cropY = Math.max(0, Math.round((scaledH - oh) / 2));
|
| 499 |
+
overlayFilter = `[1:v]scale=${scaledW}:${scaledH},crop=${ow}:${oh}:${cropX}:${cropY},format=rgba`;
|
| 500 |
+
} else {
|
| 501 |
+
overlayFilter = `[1:v]scale=${ow}:${oh},format=rgba`;
|
| 502 |
+
}
|
| 503 |
if (isFade) overlayFilter += `,fade=t=in:st=${from}:d=${animDur}:alpha=1,fade=t=out:st=${fadeOut}:d=${animDur}:alpha=1`;
|
| 504 |
overlayFilter += `[ov];[0:v][ov]overlay=${xExpr}:${yExpr}:enable='between(t,${from},${to})'[vout]`;
|
| 505 |
|
|
|
|
| 511 |
const fontColor = t.fontColor || '#FFFFFF';
|
| 512 |
const posX = extra.positionX || 'center';
|
| 513 |
const posY = extra.positionY || 'center';
|
| 514 |
+
const padX = typeof extra.paddingX === 'number' ? extra.paddingX : 5;
|
| 515 |
+
const padY = typeof extra.paddingY === 'number' ? extra.paddingY : 5;
|
| 516 |
|
| 517 |
let fontfilePath = null;
|
| 518 |
if (t.fontName) {
|
utils/bubble/bubble-templates.js
CHANGED
|
@@ -37,11 +37,11 @@ export const BaseBubbleTemplates = {
|
|
| 37 |
durationSec: 0.1
|
| 38 |
},
|
| 39 |
bubbleExtra: {
|
| 40 |
-
positionX: '
|
| 41 |
-
positionY: '
|
| 42 |
size: 'full', // | 'half' // useful for images and videos
|
| 43 |
-
paddingX:
|
| 44 |
-
paddingY:
|
| 45 |
}
|
| 46 |
}
|
| 47 |
};
|
|
|
|
| 37 |
durationSec: 0.1
|
| 38 |
},
|
| 39 |
bubbleExtra: {
|
| 40 |
+
positionX: 'full',
|
| 41 |
+
positionY: 'full',
|
| 42 |
size: 'full', // | 'half' // useful for images and videos
|
| 43 |
+
paddingX: 0, // number in percentage
|
| 44 |
+
paddingY: 0 // number in percentage
|
| 45 |
}
|
| 46 |
}
|
| 47 |
};
|
utils/bubble/layout.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
| 1 |
export function scaleTemplate(value, scale) {
|
| 2 |
if (typeof value === 'number') return Math.round(value * scale);
|
| 3 |
-
if (typeof value === 'string' && value.match(/^\d+(px)?$/)) return Math.round(parseInt(value,10) * scale);
|
| 4 |
return value;
|
| 5 |
}
|
| 6 |
|
| 7 |
// compute overlay x,y from overlay width/height, extra positioning and video size
|
| 8 |
export function computeXY(overlayW, overlayH, extra = {}, vw = 0, vh = 0) {
|
| 9 |
-
const padX = extra.paddingX
|
| 10 |
-
const padY = extra.paddingY
|
| 11 |
const posX = extra.positionX || 'center';
|
| 12 |
const posY = extra.positionY || 'center';
|
| 13 |
let x = 0, y = 0;
|
|
|
|
| 1 |
export function scaleTemplate(value, scale) {
|
| 2 |
if (typeof value === 'number') return Math.round(value * scale);
|
| 3 |
+
if (typeof value === 'string' && value.match(/^\d+(px)?$/)) return Math.round(parseInt(value, 10) * scale);
|
| 4 |
return value;
|
| 5 |
}
|
| 6 |
|
| 7 |
// compute overlay x,y from overlay width/height, extra positioning and video size
|
| 8 |
export function computeXY(overlayW, overlayH, extra = {}, vw = 0, vh = 0) {
|
| 9 |
+
const padX = typeof extra.paddingX === 'number' ? extra.paddingX : 5;
|
| 10 |
+
const padY = typeof extra.paddingY === 'number' ? extra.paddingY : 5;
|
| 11 |
const posX = extra.positionX || 'center';
|
| 12 |
const posY = extra.positionY || 'center';
|
| 13 |
let x = 0, y = 0;
|