fix(denoise): ship the DeepFilterNet 3 worklet as a file so it starts in the desktop app
CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 32s
CI / Build & Quality Checks (pull_request) Successful in 3m17s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Playwright smoke (e2e) (pull_request) Successful in 18m41s

Reported: DeepFilterNet 3 "did not start" in a desktop call. Console:
"Loading the script blob:http://127.0.0.1:44548/… violates … script-src",
then "[lotus] denoise setProcessor failed … Failed to load worklet module".
deepfilternet3-noise-filter inlines its AudioWorklet as a string and loads it
from a blob: URL; audio worklets fall under script-src, and the desktop CSP
has no blob: there (the web call page has no script-src, so web was fine).

Instead of loosening the CSP, the build now writes the inline worklet to
denoise/deepfilternet/dfn-worklet.js and patches the copied module to load
it by URL (script-src self). The build fails if the library changes shape.

Verified in Chromium under the exact desktop CSP with the built assets:
before, DeepFilterNet fails as reported; after, it creates its node and, fed
4 s of white noise, cuts it by 60 dB (its configured level). RNNoise, Speex
and DTLN load as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
This commit is contained in:
Lotus CI
2026-10-28 20:19:02 -04:00
co-authored by Claude Opus 5.5
parent 843a48dd10
commit e8a04fee20
+31
View File
@@ -136,6 +136,36 @@ function denoiseSources() {
}; };
} }
// deepfilternet3-noise-filter inlines its AudioWorklet as a string and loads it
// from a blob: URL. Audio worklets fall under script-src, and the desktop app's
// CSP (rightly) has no blob: there, so DeepFilterNet 3 failed to start ("denoise
// setProcessor failed … Failed to load worklet module script: blob:…"). Write
// the worklet out as a real file next to the module and load it by URL instead,
// which script-src 'self' allows everywhere. Fails the build if the library's
// shape changes, rather than shipping a model that cannot start.
const DFN_WORKLET_FILE = 'dfn-worklet.js';
const DFN_BLOB_LOAD = ` const blob = new Blob([workletCode], { type: 'application/javascript' });
const blobUrl = URL.createObjectURL(blob);
await audioContext.audioWorklet.addModule(blobUrl);`;
function dfnWorkletToFile(esmPath) {
const src = fs.readFileSync(esmPath, 'utf8');
const literal = src.match(/^var workletCode = (".*");\s*$/m)?.[1];
const code = literal ? JSON.parse(literal) : undefined;
if (!code?.includes('registerProcessor(') || !src.includes(DFN_BLOB_LOAD)) {
throw new Error(
`[lotus-denoise] deepfilternet3-noise-filter changed shape (inline worklet / blob loader not found in ${esmPath}) — build aborted; update dfnWorkletToFile in vite.config.js.`,
);
}
fs.writeFileSync(path.join(path.dirname(esmPath), DFN_WORKLET_FILE), code);
fs.writeFileSync(
esmPath,
src.replace(
DFN_BLOB_LOAD,
` // [lotus] Loaded from a file, not a blob: URL (CSP script-src 'self').\n await audioContext.audioWorklet.addModule(new URL('./${DFN_WORKLET_FILE}', import.meta.url).href);`,
),
);
}
// Lotus ML noise suppression: ship the denoise worklets/wasm/models alongside // Lotus ML noise suppression: ship the denoise worklets/wasm/models alongside
// the vendored Element Call bundle. Runs after viteStaticCopy has populated // the vendored Element Call bundle. Runs after viteStaticCopy has populated
// dist/public/element-call (closeBundle order). [Gitea #293] The dev server // dist/public/element-call (closeBundle order). [Gitea #293] The dev server
@@ -212,6 +242,7 @@ function lotusDenoise() {
fs.mkdirSync(path.dirname(d), { recursive: true }); fs.mkdirSync(path.dirname(d), { recursive: true });
fs.copyFileSync(s, d); fs.copyFileSync(s, d);
}); });
dfnWorkletToFile(path.join(denoiseDir, 'deepfilternet/index.esm.js'));
// [lotus] DENOISE CUTOVER: the getUserMedia shim is no longer injected. // [lotus] DENOISE CUTOVER: the getUserMedia shim is no longer injected.
// Our forked Element Call now runs ML denoise in-source as a real LiveKit // Our forked Element Call now runs ML denoise in-source as a real LiveKit