From eb1ea68f7ef2e646d54f80bd235d1d38c9b43692 Mon Sep 17 00:00:00 2001 From: wiiiii123 Date: Wed, 6 May 2026 20:53:44 +0700 Subject: [PATCH] fix(export): support native background blur --- electron/electron-env.d.ts | 1 + electron/ipc/export/native-video.test.ts | 32 +++++++- electron/ipc/export/native-video.ts | 12 ++- electron/ipc/nativeVideoExport.test.ts | 13 ++++ electron/ipc/nativeVideoExport.ts | 37 ++++++--- .../bin/win32-x64/helpers-manifest.json | 66 ++++++++-------- .../bin/win32-x64/recordly-gpu-export.exe | Bin 139264 -> 141312 bytes .../recordly-nvidia-cuda-compositor.exe | Bin 455680 -> 455680 bytes electron/native/gpu-export-probe/src/main.cpp | 73 +++++++++++++----- .../run-mp4-pipeline.mjs | 40 ++++++---- electron/preload.ts | 1 + ...rnVideoExporter.nativeStaticLayout.test.ts | 4 +- src/lib/exporter/modernVideoExporter.ts | 5 +- 13 files changed, 196 insertions(+), 88 deletions(-) diff --git a/electron/electron-env.d.ts b/electron/electron-env.d.ts index 05564d9b..a705a9f8 100644 --- a/electron/electron-env.d.ts +++ b/electron/electron-env.d.ts @@ -318,6 +318,7 @@ interface Window { offsetY: number; backgroundColor: string; backgroundImagePath?: string | null; + backgroundBlurPx?: number; borderRadius?: number; shadowIntensity?: number; webcamInputPath?: string | null; diff --git a/electron/ipc/export/native-video.test.ts b/electron/ipc/export/native-video.test.ts index 250622b6..6dae5fda 100644 --- a/electron/ipc/export/native-video.test.ts +++ b/electron/ipc/export/native-video.test.ts @@ -49,6 +49,7 @@ vi.mock("node:child_process", () => ({ import { app } from "electron"; import { buildExperimentalNvidiaCudaStaticLayoutArgs, + buildExperimentalWindowsGpuStaticLayoutArgs, buildNativeStaticLayoutTimelineSegments, buildNativeVideoAudioMuxArgs, getExperimentalNvidiaCudaExportSkipReason, @@ -348,9 +349,7 @@ describe("resolveExperimentalNvidiaCudaExportScriptPath", () => { throw new Error(`missing ${candidate}`); }); - expect(await resolveExperimentalNvidiaCudaExportScriptPath()).toBe( - unpackedScriptPath, - ); + expect(await resolveExperimentalNvidiaCudaExportScriptPath()).toBe(unpackedScriptPath); } finally { if (originalEnv === undefined) { delete process.env[envName]; @@ -416,6 +415,19 @@ describe("buildExperimentalNvidiaCudaStaticLayoutArgs", () => { expect(args).toEqual(expect.arrayContaining(["--timeline-map", "timeline-map.csv"])); }); + it("passes background blur to the CUDA wrapper", () => { + const args = buildExperimentalNvidiaCudaStaticLayoutArgs( + createNvidiaCudaSkipOptions({ + backgroundImagePath: "wallpaper.jpg", + backgroundBlurPx: 36, + }), + "output.mp4", + "work", + ); + + expect(args).toEqual(expect.arrayContaining(["--background-blur", "36"])); + }); + it("passes webcam source-time controls to the CUDA wrapper", () => { const args = buildExperimentalNvidiaCudaStaticLayoutArgs( createNvidiaCudaSkipOptions({ @@ -470,6 +482,20 @@ describe("buildExperimentalNvidiaCudaStaticLayoutArgs", () => { }); }); +describe("buildExperimentalWindowsGpuStaticLayoutArgs", () => { + it("passes background blur to the D3D11 compositor", () => { + const args = buildExperimentalWindowsGpuStaticLayoutArgs( + createNvidiaCudaSkipOptions({ + backgroundImagePath: "wallpaper.jpg", + backgroundBlurPx: 36, + }), + "output.mp4", + ); + + expect(args).toEqual(expect.arrayContaining(["--background-blur", "36"])); + }); +}); + describe("buildNativeStaticLayoutTimelineSegments", () => { it("derives contiguous output timeline ranges from edited-track source segments", () => { expect( diff --git a/electron/ipc/export/native-video.ts b/electron/ipc/export/native-video.ts index ede5be0c..264925bb 100644 --- a/electron/ipc/export/native-video.ts +++ b/electron/ipc/export/native-video.ts @@ -99,6 +99,7 @@ export interface NativeStaticLayoutExportOptions { offsetY: number; backgroundColor: string; backgroundImagePath?: string | null; + backgroundBlurPx?: number; borderRadius?: number; shadowIntensity?: number; webcamInputPath?: string | null; @@ -1699,11 +1700,12 @@ function getNvidiaCudaBitrateMbps(options: NativeStaticLayoutExportOptions) { return Math.max(1, Math.round(options.bitrate / 1_000_000)); } -function buildExperimentalWindowsGpuStaticLayoutArgs( +export function buildExperimentalWindowsGpuStaticLayoutArgs( options: NativeStaticLayoutExportOptions, outputPath: string, ) { const shadowPixels = Math.round(clampUnit(options.shadowIntensity ?? 0) * 64); + const backgroundBlurPx = Math.max(0, options.backgroundBlurPx ?? 0); const pixelCount = options.width * options.height; const surfacePoolSize = pixelCount <= 1920 * 1080 ? 12 : 8; const args = [ @@ -1743,6 +1745,9 @@ function buildExperimentalWindowsGpuStaticLayoutArgs( if (options.backgroundImagePath) { args.push("--background-image", options.backgroundImagePath); } + if (backgroundBlurPx > 0) { + args.push("--background-blur", formatCliNumber(backgroundBlurPx)); + } if (options.webcamInputPath) { const webcamShadowPixels = Math.round(clampUnit(options.webcamShadowIntensity ?? 0) * 64); args.push( @@ -2085,6 +2090,7 @@ export function buildExperimentalNvidiaCudaStaticLayoutArgs( workDir: string, ) { const background = convertHexColorToNv12(options.backgroundColor); + const backgroundBlurPx = Math.max(0, options.backgroundBlurPx ?? 0); const shadowIntensityPct = Math.round(clampUnit(options.shadowIntensity ?? 0) * 100); const shadowOffsetY = shadowIntensityPct > 0 ? Math.max(1, Math.round(options.height * 0.012)) : 0; @@ -2128,6 +2134,9 @@ export function buildExperimentalNvidiaCudaStaticLayoutArgs( if (options.backgroundImagePath) { args.push("--background-image", options.backgroundImagePath); } + if (backgroundBlurPx > 0) { + args.push("--background-blur", formatCliNumber(backgroundBlurPx)); + } if (shadowOffsetY > 0 && shadowIntensityPct > 0) { args.push( "--shadow-offset-y", @@ -2572,6 +2581,7 @@ export async function exportNativeStaticLayoutVideo( offsetY: options.offsetY, backgroundColor: options.backgroundColor, backgroundImagePath: options.backgroundImagePath, + backgroundBlurPx: options.backgroundBlurPx, borderRadius: options.borderRadius, shadowIntensity: options.shadowIntensity, durationSec: options.durationSec, diff --git a/electron/ipc/nativeVideoExport.test.ts b/electron/ipc/nativeVideoExport.test.ts index 2f5ab12e..5992cadf 100644 --- a/electron/ipc/nativeVideoExport.test.ts +++ b/electron/ipc/nativeVideoExport.test.ts @@ -205,6 +205,19 @@ describe("native static layout command builders", () => { expect(args).toEqual(expect.arrayContaining(["-frames:v", "1", "background.png"])); }); + it("pre-blurs image wallpapers for native fallback static backgrounds", () => { + const args = buildNativeStaticBackgroundRenderArgs({ + ...baseConfig, + outputPath: "background.png", + backgroundImagePath: "wallpaper.jpg", + backgroundBlurPx: 36, + }); + const filterComplex = args[args.indexOf("-filter_complex") + 1]; + + expect(filterComplex).toContain("[bg0]gblur=sigma=36:steps=2[bg_blur]"); + expect(filterComplex).toContain("[bg_blur]format=rgba[out]"); + }); + it("builds a precomposited static layout command with a squircle alpha mask", () => { const args = buildNativePrecompositedStaticLayoutArgs({ ...baseConfig, diff --git a/electron/ipc/nativeVideoExport.ts b/electron/ipc/nativeVideoExport.ts index c2c3d5ed..21eb12fd 100644 --- a/electron/ipc/nativeVideoExport.ts +++ b/electron/ipc/nativeVideoExport.ts @@ -77,6 +77,7 @@ export interface NativeStaticLayoutExportArgsConfig { offsetY: number; backgroundColor: string; backgroundImagePath?: string | null; + backgroundBlurPx?: number; staticBackgroundPath?: string | null; maskPath?: string | null; borderRadius?: number; @@ -185,16 +186,22 @@ function clampUnitInterval(value: number): number { } function formatFfmpegNumber(value: number): string { - return Number.isInteger(value) ? String(value) : value.toFixed(6).replace(/0+$/, "").replace(/\.$/, ""); + return Number.isInteger(value) + ? String(value) + : value.toFixed(6).replace(/0+$/, "").replace(/\.$/, ""); } function isPointInsidePolygon(x: number, y: number, points: Array<{ x: number; y: number }>) { let inside = false; - for (let index = 0, previousIndex = points.length - 1; index < points.length; previousIndex = index++) { + for ( + let index = 0, previousIndex = points.length - 1; + index < points.length; + previousIndex = index++ + ) { const current = points[index]; const previous = points[previousIndex]; const intersects = - (current.y > y) !== (previous.y > y) && + current.y > y !== previous.y > y && x < ((previous.x - current.x) * (y - current.y)) / (previous.y - current.y) + current.x; if (intersects) { @@ -370,6 +377,7 @@ export function buildNativeStaticBackgroundRenderArgs( config: NativeStaticLayoutExportArgsConfig, ): string[] { const backgroundColor = formatFfmpegColor(config.backgroundColor); + const backgroundBlurPx = Math.max(0, config.backgroundBlurPx ?? 0); const args = ["-y", "-hide_banner", "-loglevel", "error"]; if (config.backgroundImagePath) { args.push("-i", config.backgroundImagePath); @@ -402,6 +410,14 @@ export function buildNativeStaticBackgroundRenderArgs( : "[0:v]format=rgba[bg0]", ]; let currentBackgroundLabel = "bg0"; + if (backgroundBlurPx > 0 && config.backgroundImagePath) { + filterParts.push( + `[${currentBackgroundLabel}]gblur=sigma=${formatFfmpegNumber( + Math.min(96, backgroundBlurPx), + )}:steps=2[bg_blur]`, + ); + currentBackgroundLabel = "bg_blur"; + } if (shadowLayers.length > 0) { filterParts.push( @@ -494,9 +510,7 @@ export function buildNativePrecompositedStaticLayoutArgs( } const foregroundFilter = `[0:v]scale_cuda=w=${config.contentWidth}:h=${config.contentHeight}:format=nv12:passthrough=0,hwdownload,format=nv12,fps=${config.frameRate},format=rgba[fgbase]`; - const maskFilter = useMask - ? ";[2:v]format=gray[mask];[fgbase][mask]alphamerge[fg]" - : ""; + const maskFilter = useMask ? ";[2:v]format=gray[mask];[fgbase][mask]alphamerge[fg]" : ""; const foregroundLabel = useMask ? "fg" : "fgbase"; const filterComplex = `${foregroundFilter}${maskFilter};[1:v]format=rgba[bg];[bg][${foregroundLabel}]overlay=x=${config.offsetX}:y=${config.offsetY}:format=auto,trim=duration=${durationSec},setpts=PTS-STARTPTS,format=yuv420p[out]`; @@ -521,10 +535,7 @@ export function buildNativePrecompositedStaticLayoutArgs( return args; } -export function buildNativeConcatArgs(config: { - listPath: string; - outputPath: string; -}): string[] { +export function buildNativeConcatArgs(config: { listPath: string; outputPath: string }): string[] { return [ "-y", "-hide_banner", @@ -554,7 +565,11 @@ export function buildNativeStaticLayoutChunks( const safeChunkDuration = Math.max(1, Math.min(300, Math.floor(chunkDurationSec))); const chunks: NativeStaticLayoutChunk[] = []; - for (let startSec = 0, index = 0; startSec < durationSec; startSec += safeChunkDuration, index++) { + for ( + let startSec = 0, index = 0; + startSec < durationSec; + startSec += safeChunkDuration, index++ + ) { chunks.push({ index, startSec, diff --git a/electron/native/bin/win32-x64/helpers-manifest.json b/electron/native/bin/win32-x64/helpers-manifest.json index 3061bf01..047625ee 100644 --- a/electron/native/bin/win32-x64/helpers-manifest.json +++ b/electron/native/bin/win32-x64/helpers-manifest.json @@ -1,35 +1,35 @@ { - "version": 1, - "platform": "win32", - "arch": "x64", - "helpers": { - "wgc-capture": { - "binaryName": "wgc-capture.exe", - "binarySha256": "a0e2f0b98a903f89ab4706a56623f141604c3a311102e8914c2780745e94c635", - "sourceDir": "electron/native/wgc-capture", - "sourceFingerprint": "820d13741c659a0357d4d7944f3dbf4ce778b9716725b47ec088f573db0e7311", - "updatedAt": "2026-05-06T13:15:19.804Z" - }, - "cursor-monitor": { - "binaryName": "cursor-monitor.exe", - "binarySha256": "6ae6d91103b6e891a851e8ea5791e1c1f9aaab700134c18bc4c46cfffd7fdd12", - "sourceDir": "electron/native/cursor-monitor", - "sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e", - "updatedAt": "2026-05-05T18:42:26.007Z" - }, - "recordly-gpu-export": { - "binaryName": "recordly-gpu-export.exe", - "binarySha256": "e79cb823dd2cfade857db67696ae6456ae0ba3ae9381b6792c948ac648521bd4", - "sourceDir": "electron/native/gpu-export-probe", - "sourceFingerprint": "45aaa4c0cbeb45ecaa9ff0a87b870c33bd0d5b09fb5b3acef45b246970a12e7f", - "updatedAt": "2026-05-05T18:41:54.966Z" - }, - "recordly-nvidia-cuda-compositor": { - "binaryName": "recordly-nvidia-cuda-compositor.exe", - "binarySha256": "2913d78e2c1114d32c92fb4238749f7ff46131ec92cf3ad0b55af910c7cdbb07", - "sourceDir": "electron/native/nvidia-cuda-compositor", - "sourceFingerprint": "a60538981da7dd3d9645f59926d42f4f2f0f1f0663d3905e0416fdff8becb314", - "updatedAt": "2026-05-05T18:42:20.123Z" - } - } + "version": 1, + "platform": "win32", + "arch": "x64", + "helpers": { + "wgc-capture": { + "binaryName": "wgc-capture.exe", + "binarySha256": "a0e2f0b98a903f89ab4706a56623f141604c3a311102e8914c2780745e94c635", + "sourceDir": "electron/native/wgc-capture", + "sourceFingerprint": "820d13741c659a0357d4d7944f3dbf4ce778b9716725b47ec088f573db0e7311", + "updatedAt": "2026-05-06T13:15:19.804Z" + }, + "cursor-monitor": { + "binaryName": "cursor-monitor.exe", + "binarySha256": "6ae6d91103b6e891a851e8ea5791e1c1f9aaab700134c18bc4c46cfffd7fdd12", + "sourceDir": "electron/native/cursor-monitor", + "sourceFingerprint": "6ad1b8b50bb336f2a48937b06f5ec56d90b6ab4a3e56a4bca278cf67a5d3e52e", + "updatedAt": "2026-05-05T18:42:26.007Z" + }, + "recordly-gpu-export": { + "binaryName": "recordly-gpu-export.exe", + "binarySha256": "f147901f01dd5b410f67a5c0feae5064ea7556a878b333aa493e799498abb5e5", + "sourceDir": "electron/native/gpu-export-probe", + "sourceFingerprint": "056a4c41113c471379d0ec6095249c46a79b05fac5fc0e52ce0bfdf6de4d91e1", + "updatedAt": "2026-05-06T13:51:01.756Z" + }, + "recordly-nvidia-cuda-compositor": { + "binaryName": "recordly-nvidia-cuda-compositor.exe", + "binarySha256": "ec594808577011407fe40ea4e610a6b6fd2cbc7c5dde4627f48351a8a5a980c3", + "sourceDir": "electron/native/nvidia-cuda-compositor", + "sourceFingerprint": "6dd2a015326ac984c0a88581c809d628592f174e02a6813d24356c22e7384f1a", + "updatedAt": "2026-05-06T13:51:30.019Z" + } + } } diff --git a/electron/native/bin/win32-x64/recordly-gpu-export.exe b/electron/native/bin/win32-x64/recordly-gpu-export.exe index 5b1662d14f754322d0763b6189e7b7dbbea44568..462f7c3e9e72e49379502484a3ec5edaa03acebb 100644 GIT binary patch delta 41297 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 OV|k-ed+P=mHTpmJthk*3 diff --git a/electron/native/bin/win32-x64/recordly-nvidia-cuda-compositor.exe b/electron/native/bin/win32-x64/recordly-nvidia-cuda-compositor.exe index 9443ee8e9601eaaaa2d7a1526a111227dcc00f76..b31836233de665f50e16302167d8a33df09e1386 100644 GIT binary patch delta 52 zcmZp8A>HsodV>NZbE5n2W@W~9WkwKY0%GRv%8V=rML-hm&qY~)n05PeQ8vkY0K}3J AMF0Q* delta 52 zcmZp8A>HsodV>NZvz6JeW@W~9WkwKY0%GRv%8V=rML-hm&qY~)n05PeQ8vkY0JoJ8 A(*OVf diff --git a/electron/native/gpu-export-probe/src/main.cpp b/electron/native/gpu-export-probe/src/main.cpp index 9f34bdda..fd842ed2 100644 --- a/electron/native/gpu-export-probe/src/main.cpp +++ b/electron/native/gpu-export-probe/src/main.cpp @@ -60,6 +60,7 @@ struct Options { float backgroundR = 0.035f; float backgroundG = 0.035f; float backgroundB = 0.045f; + float backgroundBlurPx = 0.0f; std::wstring backgroundImagePath; std::wstring webcamInputPath; LONG webcamLeft = -1; @@ -124,9 +125,9 @@ struct ShaderConstants { float cursorAtlasAnchorY; float cursorAtlasAspect; float cursorBounceScale; - float cursorPadding0; - float cursorPadding1; - float cursorPadding2; + float backgroundBlurPx; + float backgroundBlurPadding0; + float backgroundBlurPadding1; float zoomEnabled; float zoomScale; float zoomX; @@ -380,6 +381,9 @@ Options parseOptions(int argc, wchar_t** argv) { options.radius = parseFloatArg(args, L"--radius", options.radius); options.shadow = parseFloatArg(args, L"--shadow", options.shadow); options.padding = parseFloatArg(args, L"--padding", options.padding); + options.backgroundBlurPx = std::max( + 0.0f, + parseFloatArg(args, L"--background-blur", options.backgroundBlurPx)); options.contentLeft = parseLongArg(args, L"--content-left", options.contentLeft); options.contentTop = parseLongArg(args, L"--content-top", options.contentTop); options.contentWidth = parseLongArg(args, L"--content-width", options.contentWidth); @@ -1498,9 +1502,9 @@ cbuffer CompositorConstants : register(b0) { float cursorAtlasAnchorY; float cursorAtlasAspect; float cursorBounceScale; - float cursorPadding0; - float cursorPadding1; - float cursorPadding2; + float backgroundBlurPx; + float backgroundBlurPadding0; + float backgroundBlurPadding1; float zoomEnabled; float zoomScale; float zoomX; @@ -1583,6 +1587,48 @@ float sampleCursorAtlasShadow(float2 cursorLocal, float cursorWidth, float curso return saturate(alpha); } +float2 getBackgroundCoverUv(float2 uv) { + float2 backgroundUv = uv; + float outputAspect = outputWidth / outputHeight; + float backgroundAspect = backgroundImageWidth / backgroundImageHeight; + if (backgroundAspect > outputAspect) { + backgroundUv.x = 0.5 + ((backgroundUv.x - 0.5) * (outputAspect / backgroundAspect)); + } else { + backgroundUv.y = 0.5 + ((backgroundUv.y - 0.5) * (backgroundAspect / outputAspect)); + } + return saturate(backgroundUv); +} + +float4 sampleBackground(float2 uv) { + if (backgroundImageEnabled <= 0.5) { + return float4(backgroundR, backgroundG, backgroundB, backgroundA); + } + + float2 backgroundUv = getBackgroundCoverUv(uv); + float safeBlur = min(max(backgroundBlurPx, 0.0), 96.0); + if (safeBlur <= 0.001) { + return backgroundTexture.Sample(linearSampler, backgroundUv); + } + + float2 texel = float2(1.0 / max(outputWidth, 1.0), 1.0 / max(outputHeight, 1.0)); + float2 r1 = texel * safeBlur * 0.35; + float2 r2 = texel * safeBlur * 0.70; + float4 color = backgroundTexture.Sample(linearSampler, backgroundUv) * 0.20; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2( r1.x, 0.0))) * 0.10; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(-r1.x, 0.0))) * 0.10; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(0.0, r1.y))) * 0.10; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(0.0, -r1.y))) * 0.10; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2( r2.x, r2.y))) * 0.05; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(-r2.x, r2.y))) * 0.05; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2( r2.x, -r2.y))) * 0.05; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(-r2.x, -r2.y))) * 0.05; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2( r2.x, 0.0))) * 0.05; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(-r2.x, 0.0))) * 0.05; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(0.0, r2.y))) * 0.05; + color += backgroundTexture.Sample(linearSampler, saturate(backgroundUv + float2(0.0, -r2.y))) * 0.05; + return color; +} + float4 main(PSIn input) : SV_Target { float2 outputSize = float2(outputWidth, outputHeight); float2 pixel = input.uv * outputSize; @@ -1604,18 +1650,7 @@ float4 main(PSIn input) : SV_Target { outsideAlpha * shadowA; - float4 background = float4(backgroundR, backgroundG, backgroundB, backgroundA); - if (backgroundImageEnabled > 0.5) { - float2 backgroundUv = input.uv; - float outputAspect = outputWidth / outputHeight; - float backgroundAspect = backgroundImageWidth / backgroundImageHeight; - if (backgroundAspect > outputAspect) { - backgroundUv.x = 0.5 + ((backgroundUv.x - 0.5) * (outputAspect / backgroundAspect)); - } else { - backgroundUv.y = 0.5 + ((backgroundUv.y - 0.5) * (backgroundAspect / outputAspect)); - } - background = backgroundTexture.Sample(linearSampler, saturate(backgroundUv)); - } + float4 background = sampleBackground(input.uv); float4 shadow = float4(shadowR, shadowG, shadowB, shadowAlpha); float4 content = contentTexture.Sample(linearSampler, saturate(contentPixel / outputSize)); @@ -2761,7 +2796,7 @@ float4 main(PSIn input) : SV_Target { cursorAtlasEnabled ? cursorAtlasEntry->anchorY : 0.0f, cursorAtlasEnabled ? cursorAtlasEntry->aspectRatio : 1.0f, cursorEnabled ? cursor.bounceScale : 1.0f, - 0.0f, + hasBackgroundImage_ ? options_.backgroundBlurPx : 0.0f, 0.0f, 0.0f, zoomEnabled ? 1.0f : 0.0f, diff --git a/electron/native/nvidia-cuda-compositor/run-mp4-pipeline.mjs b/electron/native/nvidia-cuda-compositor/run-mp4-pipeline.mjs index af9ced72..a9a5ae73 100644 --- a/electron/native/nvidia-cuda-compositor/run-mp4-pipeline.mjs +++ b/electron/native/nvidia-cuda-compositor/run-mp4-pipeline.mjs @@ -680,13 +680,7 @@ function ffprobeJson(args) { } async function ffprobeCsvAsync(args) { - const result = await runAsync(ffprobeCommand, [ - "-v", - "error", - ...args, - "-of", - "csv=p=0", - ]); + const result = await runAsync(ffprobeCommand, ["-v", "error", ...args, "-of", "csv=p=0"]); return result.stdout; } @@ -865,15 +859,23 @@ function roundedRectMaskExpression({ x, y, width, height, radius }) { return `${centerBand}+${middleBand}+${topLeft}+${topRight}+${bottomLeft}+${bottomRight}`; } -function createBackgroundFilter(videoInfo, shadowOptions) { - const base = `[0:v]scale=${videoInfo.width}:${videoInfo.height}:force_original_aspect_ratio=increase,crop=${videoInfo.width}:${videoInfo.height},format=rgba[bg]`; +function createBackgroundFilter(videoInfo, shadowOptions, blurPx = 0) { + const safeBlurPx = Math.max(0, Math.min(96, Math.round(Number.isFinite(blurPx) ? blurPx : 0))); + const scaled = `[0:v]scale=${videoInfo.width}:${videoInfo.height}:force_original_aspect_ratio=increase,crop=${videoInfo.width}:${videoInfo.height},format=rgba[bg_scaled]`; + const blurFilter = + safeBlurPx > 0 + ? `;[bg_scaled]boxblur=luma_radius=${safeBlurPx}:luma_power=1:chroma_radius=${safeBlurPx}:chroma_power=1:alpha_radius=${safeBlurPx}:alpha_power=1[bg]` + : ";[bg_scaled]null[bg]"; if (!shadowOptions) { return { filterArgs: [ - "-vf", - `scale=${videoInfo.width}:${videoInfo.height}:force_original_aspect_ratio=increase,crop=${videoInfo.width}:${videoInfo.height},format=nv12`, + "-filter_complex", + `${scaled}${blurFilter};[bg]format=nv12[out]`, + "-map", + "[out]", ], bakedShadow: false, + backgroundBlur: safeBlurPx, }; } @@ -894,13 +896,14 @@ function createBackgroundFilter(videoInfo, shadowOptions) { "-i", `color=c=black@0.0:s=${videoInfo.width}x${videoInfo.height}:d=1`, "-filter_complex", - `${base};${shadow};[bg][shadow]overlay=format=auto,format=nv12[out]`, + `${scaled}${blurFilter};${shadow};[bg][shadow]overlay=format=auto,format=nv12[out]`, "-map", "[out]", ], bakedShadow: true, shadowAlpha, shadowBlur, + backgroundBlur: safeBlurPx, }; } @@ -949,6 +952,7 @@ const backgroundY = Math.round(getNonNegativeNumberArg("--background-y", 16)); const backgroundU = Math.round(getNonNegativeNumberArg("--background-u", 128)); const backgroundV = Math.round(getNonNegativeNumberArg("--background-v", 128)); const backgroundImage = getArg("--background-image", ""); +const backgroundBlurPx = getNonNegativeNumberArg("--background-blur", 0); const backgroundNv12 = getArg("--background-nv12", ""); const shadowOffsetY = Math.round(getNonNegativeNumberArg("--shadow-offset-y", 0)); const shadowIntensityPct = Math.round(getNonNegativeNumberArg("--shadow-intensity-pct", 0)); @@ -1020,9 +1024,14 @@ const shouldBakeStaticShadow = contentHeight > 0 && shadowOffsetY > 0 && shadowIntensityPct > 0; -const backgroundSuffix = shouldBakeStaticShadow - ? `.shadow-${shadowOffsetY}-${shadowIntensityPct}` - : ""; +const backgroundSuffixParts = []; +if (shouldBakeStaticShadow) { + backgroundSuffixParts.push(`shadow-${shadowOffsetY}-${shadowIntensityPct}`); +} +if (backgroundBlurPx > 0) { + backgroundSuffixParts.push(`blur-${Math.round(backgroundBlurPx)}`); +} +const backgroundSuffix = backgroundSuffixParts.length ? `.${backgroundSuffixParts.join(".")}` : ""; const generatedBackgroundNv12Path = join(workDir, `${baseName}${backgroundSuffix}.background.nv12`); const generatedWebcamNv12Path = join( workDir, @@ -1098,6 +1107,7 @@ const backgroundFilter = createBackgroundFilter( intensityPct: shadowIntensityPct, } : null, + backgroundBlurPx, ); const backgroundConvertPromise = diff --git a/electron/preload.ts b/electron/preload.ts index 146beacc..b47ec5a1 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -215,6 +215,7 @@ contextBridge.exposeInMainWorld("electronAPI", { offsetY: number; backgroundColor: string; backgroundImagePath?: string | null; + backgroundBlurPx?: number; borderRadius?: number; shadowIntensity?: number; webcamInputPath?: string | null; diff --git a/src/lib/exporter/modernVideoExporter.nativeStaticLayout.test.ts b/src/lib/exporter/modernVideoExporter.nativeStaticLayout.test.ts index 539159d2..c97af08a 100644 --- a/src/lib/exporter/modernVideoExporter.nativeStaticLayout.test.ts +++ b/src/lib/exporter/modernVideoExporter.nativeStaticLayout.test.ts @@ -175,7 +175,7 @@ describe("ModernVideoExporter native static-layout eligibility", () => { ).toBe("unsupported-frame-overlay"); }); - it("reports background blur as the remaining native overlay blocker", () => { + it("allows native static-layout with background blur", () => { const exporter = createExporter({ backgroundBlur: 12 }); expect( @@ -187,7 +187,7 @@ describe("ModernVideoExporter native static-layout eligibility", () => { videoInfo, 60, ), - ).toBe("unsupported-background-blur"); + ).toBeNull(); }); it("allows non-tail trim timelines with native static-layout", () => { diff --git a/src/lib/exporter/modernVideoExporter.ts b/src/lib/exporter/modernVideoExporter.ts index 6d4eaa39..0ccb0702 100644 --- a/src/lib/exporter/modernVideoExporter.ts +++ b/src/lib/exporter/modernVideoExporter.ts @@ -1257,10 +1257,6 @@ export class ModernVideoExporter { return "unsupported-frame-overlay"; } - if (this.config.backgroundBlur > 0) { - return "unsupported-background-blur"; - } - return this.isDefaultCropRegion() ? null : "non-default-crop"; } @@ -1895,6 +1891,7 @@ export class ModernVideoExporter { offsetY, backgroundColor: background.backgroundColor, backgroundImagePath: background.backgroundImagePath ?? null, + backgroundBlurPx: Math.max(0, (this.config.backgroundBlur ?? 0) * 3), borderRadius, shadowIntensity, webcamInputPath: webcamOverlay?.inputPath ?? null,