Make layout blur, opacity, and watermark position apply in rendered videos.
Still-image encodes ignored boxblur and 1s black fills; switch to gblur plus full-length black frames, and honor brand watermark position.
This commit is contained in:
@@ -1,20 +1,25 @@
|
|||||||
/**
|
/**
|
||||||
* Modular FFmpeg filter fragments for the Songs2VID brand watermark overlay.
|
* Modular FFmpeg filter fragments for the Songs2VID brand watermark overlay.
|
||||||
* Always bottom-right with padding; scales to a fraction of frame width.
|
* Honors corner/center position + padding; scales to a fraction of frame width.
|
||||||
*/
|
*/
|
||||||
|
import { overlayXy, type WatermarkPosition } from "../watermark";
|
||||||
|
|
||||||
export function buildBrandWatermarkOverlayFilters(options: {
|
export function buildBrandWatermarkOverlayFilters(options: {
|
||||||
baseLabel: string;
|
baseLabel: string;
|
||||||
watermarkInputIndex: number;
|
watermarkInputIndex: number;
|
||||||
watermarkWidthPx: number;
|
watermarkWidthPx: number;
|
||||||
|
position?: WatermarkPosition;
|
||||||
offsetX?: number;
|
offsetX?: number;
|
||||||
offsetY?: number;
|
offsetY?: number;
|
||||||
outLabel?: string;
|
outLabel?: string;
|
||||||
}): string[] {
|
}): string[] {
|
||||||
const ox = options.offsetX ?? 20;
|
const ox = options.offsetX ?? 20;
|
||||||
const oy = options.offsetY ?? 20;
|
const oy = options.offsetY ?? 20;
|
||||||
|
const position = options.position ?? "bottom-right";
|
||||||
|
const { x, y } = overlayXy(position, ox, oy);
|
||||||
const out = options.outLabel ?? "vout";
|
const out = options.outLabel ?? "vout";
|
||||||
return [
|
return [
|
||||||
`[${options.watermarkInputIndex}:v]scale=${options.watermarkWidthPx}:-1[wm]`,
|
`[${options.watermarkInputIndex}:v]scale=${options.watermarkWidthPx}:-1[wm]`,
|
||||||
`[${options.baseLabel}][wm]overlay=W-w-${ox}:H-h-${oy}[${out}]`,
|
`[${options.baseLabel}][wm]overlay=${x}:${y}[${out}]`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,6 +360,7 @@ export async function encodeVideo(options: {
|
|||||||
baseLabel,
|
baseLabel,
|
||||||
watermarkInputIndex: defaultWmInputIndex,
|
watermarkInputIndex: defaultWmInputIndex,
|
||||||
watermarkWidthPx: watermarkWidth,
|
watermarkWidthPx: watermarkWidth,
|
||||||
|
position: settings.position,
|
||||||
offsetX: settings.offsetX,
|
offsetX: settings.offsetX,
|
||||||
offsetY: settings.offsetY,
|
offsetY: settings.offsetY,
|
||||||
}),
|
}),
|
||||||
|
|||||||
+19
-10
@@ -64,7 +64,7 @@ export type LayoutSettings = {
|
|||||||
* Ignored when `template` is set (art-track always uses a blur fill).
|
* Ignored when `template` is set (art-track always uses a blur fill).
|
||||||
*/
|
*/
|
||||||
blurFill: boolean;
|
blurFill: boolean;
|
||||||
/** 0–100 → FFmpeg boxblur intensity. */
|
/** 0-100 - FFmpeg gblur intensity. */
|
||||||
blurAmount: number;
|
blurAmount: number;
|
||||||
/** 0–100 → how visible the blurred fill is (vs black). */
|
/** 0–100 → how visible the blurred fill is (vs black). */
|
||||||
blurOpacity: number;
|
blurOpacity: number;
|
||||||
@@ -134,12 +134,21 @@ export function blurToBoxblur(blurAmount: number): { radius: number; power: numb
|
|||||||
return { radius, power };
|
return { radius, power };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Still-image cover inputs (-loop 1 -r 1) respond poorly to boxblur; gblur is
|
||||||
|
* reliable and maps 0-100 to a visible sigma range.
|
||||||
|
*/
|
||||||
export function boxblurFilterSegment(blurAmount: number): string {
|
export function boxblurFilterSegment(blurAmount: number): string {
|
||||||
const bb = blurToBoxblur(blurAmount);
|
const amount = clampBlurAmount(blurAmount, 0);
|
||||||
if (!bb) return "";
|
if (amount <= 0) return "";
|
||||||
return `boxblur=luma_radius=${bb.radius}:luma_power=${bb.power}:chroma_radius=${bb.radius}:chroma_power=${bb.power}`;
|
const sigma = Math.max(0.8, (amount / 100) * 24);
|
||||||
|
return `gblur=sigma=${sigma.toFixed(2)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Solid black used when fading blur opacity; must outlast any track length. */
|
||||||
|
const BLACK_FRAME = (width: number, height: number, label: string) =>
|
||||||
|
`color=c=black:s=${width}x${height}:d=86400[${label}]`;
|
||||||
|
|
||||||
type RawLayoutInput = {
|
type RawLayoutInput = {
|
||||||
template?: unknown;
|
template?: unknown;
|
||||||
layoutTemplate?: unknown;
|
layoutTemplate?: unknown;
|
||||||
@@ -516,14 +525,14 @@ export function buildArtTrackFilterComplex(opts: {
|
|||||||
if (opacity >= 0.999) {
|
if (opacity >= 0.999) {
|
||||||
parts.push(`${bgSrc}${bgChain}[blurred]`);
|
parts.push(`${bgSrc}${bgChain}[blurred]`);
|
||||||
} else if (opacity <= 0.001) {
|
} else if (opacity <= 0.001) {
|
||||||
parts.push(`color=c=black:s=${W}x${H}:d=1[blurred]`);
|
parts.push(BLACK_FRAME(W, H, "blurred"));
|
||||||
} else {
|
} else {
|
||||||
const a = opacity.toFixed(3);
|
const a = opacity.toFixed(3);
|
||||||
const b = (1 - opacity).toFixed(3);
|
const b = (1 - opacity).toFixed(3);
|
||||||
parts.push(
|
parts.push(
|
||||||
`${bgSrc}${bgChain}[blur_raw]`,
|
`${bgSrc}${bgChain}[blur_raw]`,
|
||||||
`color=c=black:s=${W}x${H}:d=1[blk]`,
|
BLACK_FRAME(W, H, "blk"),
|
||||||
`[blur_raw][blk]blend=all_expr='A*${a}+B*${b}':shortest=1[blurred]`,
|
`[blur_raw][blk]blend=all_expr='A*${a}+B*${b}'[blurred]`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,14 +569,14 @@ export function buildClassicBlurFillFilterComplex(opts: {
|
|||||||
if (opacity >= 0.999) {
|
if (opacity >= 0.999) {
|
||||||
parts.push(`[bg]${bgChain}[blurred]`);
|
parts.push(`[bg]${bgChain}[blurred]`);
|
||||||
} else if (opacity <= 0.001) {
|
} else if (opacity <= 0.001) {
|
||||||
parts.push(`color=c=black:s=${W}x${H}:d=1[blurred]`);
|
parts.push(BLACK_FRAME(W, H, "blurred"));
|
||||||
} else {
|
} else {
|
||||||
const a = opacity.toFixed(3);
|
const a = opacity.toFixed(3);
|
||||||
const b = (1 - opacity).toFixed(3);
|
const b = (1 - opacity).toFixed(3);
|
||||||
parts.push(
|
parts.push(
|
||||||
`[bg]${bgChain}[blur_raw]`,
|
`[bg]${bgChain}[blur_raw]`,
|
||||||
`color=c=black:s=${W}x${H}:d=1[blk]`,
|
BLACK_FRAME(W, H, "blk"),
|
||||||
`[blur_raw][blk]blend=all_expr='A*${a}+B*${b}':shortest=1[blurred]`,
|
`[blur_raw][blk]blend=all_expr='A*${a}+B*${b}'[blurred]`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -26,7 +26,7 @@ function testEnumsAndClamp() {
|
|||||||
assert.equal(blurToBoxblur(0), null);
|
assert.equal(blurToBoxblur(0), null);
|
||||||
const mid = blurToBoxblur(50);
|
const mid = blurToBoxblur(50);
|
||||||
assert.ok(mid && mid.radius >= 1 && mid.power >= 1);
|
assert.ok(mid && mid.radius >= 1 && mid.power >= 1);
|
||||||
assert.ok(boxblurFilterSegment(60).includes("boxblur="));
|
assert.ok(boxblurFilterSegment(60).includes("gblur="));
|
||||||
assert.equal(boxblurFilterSegment(0), "");
|
assert.equal(boxblurFilterSegment(0), "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,10 +197,21 @@ function testGeometryAndFilter() {
|
|||||||
blurOpacity: 100,
|
blurOpacity: 100,
|
||||||
});
|
});
|
||||||
assert.ok(classicBlur.includes("[0:v]split=2"));
|
assert.ok(classicBlur.includes("[0:v]split=2"));
|
||||||
|
assert.ok(classicBlur.includes("gblur=") || classicBlur.includes("[blurred]"));
|
||||||
assert.ok(classicBlur.includes("overlay=(W-w)/2:(H-h)/2"));
|
assert.ok(classicBlur.includes("overlay=(W-w)/2:(H-h)/2"));
|
||||||
assert.equal(classicBlur.includes("pad="), false);
|
assert.equal(classicBlur.includes("pad="), false);
|
||||||
assert.equal(classicBlur.includes("drawtext="), false);
|
assert.equal(classicBlur.includes("drawtext="), false);
|
||||||
assert.ok(classicBlur.endsWith("[laid]"));
|
assert.ok(classicBlur.endsWith("[laid]"));
|
||||||
|
|
||||||
|
const classicOpacity = buildClassicBlurFillFilterComplex({
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
blurAmount: 40,
|
||||||
|
blurOpacity: 50,
|
||||||
|
});
|
||||||
|
assert.ok(classicOpacity.includes("d=86400"));
|
||||||
|
assert.equal(classicOpacity.includes("shortest=1"), false);
|
||||||
|
assert.ok(classicOpacity.includes("blend="));
|
||||||
}
|
}
|
||||||
|
|
||||||
testEnumsAndClamp();
|
testEnumsAndClamp();
|
||||||
|
|||||||
Reference in New Issue
Block a user