Add lower-corner layouts, custom blur backgrounds, and classic blur fill.

Ship composition families, optional background images for lower-corner templates, and an optional blurred cover fill for classic letterbox.
This commit is contained in:
Atakan Doğan Özban
2026-08-07 00:51:37 +02:00
parent 03634d5100
commit 848607f9e0
25 changed files with 1081 additions and 122 deletions
+106
View File
@@ -5,6 +5,7 @@ import {
blurToBoxblur,
boxblurFilterSegment,
buildArtTrackFilterComplex,
buildClassicBlurFillFilterComplex,
clampBlurAmount,
clampTextPadding,
clampTitleArtistGap,
@@ -32,8 +33,22 @@ function testEnumsAndClamp() {
function testNormalize() {
const classic = normalizeLayoutSettings(null);
assert.equal(classic.template, null);
assert.equal(classic.blurFill, false);
assert.equal(classic.titleArtistGap, 10);
assert.equal(classic.textOffsetX, 0);
assert.equal(classic.titleBold, true);
assert.equal(
normalizeLayoutSettings({ blur_fill: true }).blurFill,
true,
);
assert.equal(
normalizeLayoutSettings({
template: "CENTERED_COMPACT",
blurFill: true,
}).blurFill,
false,
);
assert.equal(
normalizeLayoutSettings({
@@ -75,26 +90,117 @@ function testGeometryAndFilter() {
const tight = computeLayoutGeometry("COVER_LEFT_TEXT_RIGHT", 1280, 720, 40, 0, 0, 0);
assert.ok(Number(withGap.artistY) - Number(withGap.titleY) > Number(tight.artistY) - Number(tight.titleY));
const lowerLeft = computeLayoutGeometry("LOWER_LEFT_COVER_TEXT", 1280, 720, 48, 10, 0, 0);
assert.equal(lowerLeft.coverX, "48");
assert.equal(lowerLeft.coverY, "H-h-48");
assert.ok(Number(lowerLeft.titleX) > Number(lowerLeft.coverX));
assert.ok(Number(lowerLeft.titleY) > 300, "title should sit in the lower half with the cover");
const lowerRight = computeLayoutGeometry("LOWER_RIGHT_COVER_TEXT", 1280, 720, 48, 10, 0, 0);
assert.equal(lowerRight.coverX, "W-w-48");
assert.equal(lowerRight.coverY, "H-h-48");
assert.ok(String(lowerRight.titleX).startsWith("W-text_w-"));
const fc = buildArtTrackFilterComplex({
width: 1280,
height: 720,
layout: {
template: "CENTERED_COMPACT",
blurFill: false,
blurAmount: 40,
blurOpacity: 70,
textPadding: 40,
titleArtistGap: 18,
textOffsetX: 5,
textOffsetY: -8,
titleBold: true,
},
titleEscaped: sanitizeDrawtext("Hello:World"),
artistEscaped: sanitizeDrawtext("Artist"),
fontfileEscaped: null,
titleFontfileEscaped: "C\\:/fonts/Arimo-Bold.ttf",
});
assert.ok(fc.includes("split=2"));
assert.ok(fc.includes("blend=") || fc.includes("[blurred]"));
assert.ok(fc.includes("overlay="));
assert.ok(fc.includes("drawtext="));
assert.ok(fc.includes("fontfile='C\\:/fonts/Arimo-Bold.ttf'"));
assert.ok(fc.endsWith("[laid]"));
const regular = buildArtTrackFilterComplex({
width: 1280,
height: 720,
layout: {
template: "CENTERED_COMPACT",
blurFill: false,
blurAmount: 40,
blurOpacity: 70,
textPadding: 40,
titleArtistGap: 18,
textOffsetX: 0,
textOffsetY: 0,
titleBold: false,
},
titleEscaped: "Hi",
artistEscaped: null,
fontfileEscaped: "C\\:/fonts/Arimo-Regular.ttf",
});
assert.ok(regular.includes("fontfile='C\\:/fonts/Arimo-Regular.ttf'"));
assert.equal(regular.includes("borderw="), false);
const withSeparateBg = buildArtTrackFilterComplex({
width: 1280,
height: 720,
layout: {
template: "LOWER_LEFT_COVER_TEXT",
blurFill: false,
blurAmount: 40,
blurOpacity: 100,
textPadding: 48,
titleArtistGap: 10,
textOffsetX: 0,
textOffsetY: 0,
titleBold: true,
},
titleEscaped: "Song",
artistEscaped: "Artist",
separateBackgroundInputIndex: 2,
});
assert.equal(withSeparateBg.includes("[0:v]split=2"), false);
assert.ok(withSeparateBg.includes("[2:v]"));
assert.ok(withSeparateBg.includes("[0:v]scale="));
assert.ok(withSeparateBg.includes("overlay="));
const withoutSeparateBg = buildArtTrackFilterComplex({
width: 1280,
height: 720,
layout: {
template: "LOWER_LEFT_COVER_TEXT",
blurFill: false,
blurAmount: 40,
blurOpacity: 100,
textPadding: 48,
titleArtistGap: 10,
textOffsetX: 0,
textOffsetY: 0,
titleBold: true,
},
titleEscaped: "Song",
artistEscaped: null,
});
assert.ok(withoutSeparateBg.includes("[0:v]split=2"));
const classicBlur = buildClassicBlurFillFilterComplex({
width: 1280,
height: 720,
blurAmount: 55,
blurOpacity: 100,
});
assert.ok(classicBlur.includes("[0:v]split=2"));
assert.ok(classicBlur.includes("overlay=(W-w)/2:(H-h)/2"));
assert.equal(classicBlur.includes("pad="), false);
assert.equal(classicBlur.includes("drawtext="), false);
assert.ok(classicBlur.endsWith("[laid]"));
}
testEnumsAndClamp();