diff --git a/.gitignore b/.gitignore index 5751844a..34f9cff8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ dist-ssr *.bkp .idea/ public/config.json +public/assets/deepfilternet3 backend/synapse_tmp/* backend/synapse_tmp_othersite/* /coverage diff --git a/package.json b/package.json index c206ce9a..b034ec6b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "dev:full": "vite", "dev:embedded": "vite --config vite-embedded.config.js", "build": "yarn build:full", - "build:full": "NODE_OPTIONS=--max-old-space-size=16384 vite build", + "build:full": "yarn setup:assets && NODE_OPTIONS=--max-old-space-size=16384 vite build", "build:full:production": "yarn build:full", "build:full:development": "yarn build:full --mode development", "build:embedded": "yarn build:full --config vite-embedded.config.js", @@ -17,6 +17,7 @@ "build:sdk": "yarn build:full --config vite-sdk.config.js", "build:sdk:production": "yarn build:sdk", "serve": "vite preview", + "setup:assets": "node scripts/setup-noise-suppression-assets.js", "prettier:check": "prettier -c .", "prettier:format": "prettier -w .", "lint": "yarn lint:types && yarn lint:eslint && yarn lint:knip", diff --git a/scripts/setup-noise-suppression-assets.js b/scripts/setup-noise-suppression-assets.js new file mode 100644 index 00000000..241fbde2 --- /dev/null +++ b/scripts/setup-noise-suppression-assets.js @@ -0,0 +1,152 @@ +#!/usr/bin/env node + +/** + * Setup script to download DeepFilterNet3 assets for local bundling. + * This downloads the WASM binary and AI model from Mezon's CDN + * and places them in public/assets/deepfilternet3/ for bundling. + * + * Usage: + * node scripts/setup-noise-suppression-assets.js + * + * Environment variables: + * DEEPFILTERNET3_CDN_URL: Override the default CDN URL (optional) + */ + +import fs from "fs"; +import path from "path"; +import https from "https"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.join(__dirname, ".."); + +const CDN_URL = + process.env.DEEPFILTERNET3_CDN_URL || + "https://cdn.mezon.ai/AI/models/datas/noise_suppression/deepfilternet3"; + +const ASSETS_DIR = path.join(projectRoot, "public", "assets", "deepfilternet3"); +const V2_DIR = path.join(ASSETS_DIR, "v2"); +const PKG_DIR = path.join(V2_DIR, "pkg"); +const MODELS_DIR = path.join(V2_DIR, "models"); + +const FILES_TO_DOWNLOAD = [ + { + url: `${CDN_URL}/v2/pkg/df_bg.wasm`, + path: path.join(PKG_DIR, "df_bg.wasm"), + description: "WASM binary", + }, + { + url: `${CDN_URL}/v2/pkg/df_bg.wasm.d.ts`, + path: path.join(PKG_DIR, "df_bg.wasm.d.ts"), + description: "WASM TypeScript definitions", + optional: true, + }, + { + url: `${CDN_URL}/v2/models/DeepFilterNet3_onnx.tar.gz`, + path: path.join(MODELS_DIR, "DeepFilterNet3_onnx.tar.gz"), + description: "AI Model (ONNX format)", + }, +]; + +function ensureDir(dir) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + console.log(`✓ Created directory: ${dir}`); + } +} + +function downloadFile(fileUrl, filePath, isOptional = false) { + return new Promise((resolve, reject) => { + const fileName = path.basename(filePath); + + // Skip if already exists + if (fs.existsSync(filePath)) { + console.log(`✓ Already exists: ${fileName}`); + resolve(); + return; + } + + console.log(`⏳ Downloading ${fileName}...`); + + https + .get(fileUrl, (response) => { + // Handle redirects + if ( + response.statusCode === 301 || + response.statusCode === 302 || + response.statusCode === 307 + ) { + const redirectUrl = response.headers.location; + console.log(` Redirected to: ${redirectUrl}`); + downloadFile(redirectUrl, filePath, isOptional).then(resolve).catch(reject); + return; + } + + if (response.statusCode !== 200) { + const error = new Error( + `Download failed: HTTP ${response.statusCode} for ${fileName}`, + ); + if (isOptional) { + console.warn(`⚠ Optional file skipped: ${fileName}`); + resolve(); + } else { + reject(error); + } + return; + } + + const fileStream = fs.createWriteStream(filePath); + + response.pipe(fileStream); + + fileStream.on("finish", () => { + fileStream.close(); + const sizeMB = (fs.statSync(filePath).size / 1024 / 1024).toFixed(2); + console.log(`✓ Downloaded: ${fileName} (${sizeMB} MB)`); + resolve(); + }); + + fileStream.on("error", (err) => { + fs.unlink(filePath, () => {}); // Clean up partial file + reject(err); + }); + }) + .on("error", (err) => { + if (isOptional) { + console.warn(`⚠ Optional file skipped: ${fileName} (${err.message})`); + resolve(); + } else { + reject(err); + } + }); + }); +} + +async function main() { + try { + console.log("\n🚀 Setting up DeepFilterNet3 assets for bundling...\n"); + console.log(`📦 CDN URL: ${CDN_URL}`); + console.log(`📁 Asset directory: ${ASSETS_DIR}\n`); + + // Ensure directories exist + ensureDir(ASSETS_DIR); + ensureDir(V2_DIR); + ensureDir(PKG_DIR); + ensureDir(MODELS_DIR); + + // Download files + for (const file of FILES_TO_DOWNLOAD) { + await downloadFile(file.url, file.path, file.optional); + } + + console.log("\n✅ Asset setup complete!"); + console.log("\nAssets are ready for bundling. Next build will include them.\n"); + process.exit(0); + } catch (error) { + console.error("\n❌ Asset setup failed:", error.message); + process.exit(1); + } +} + +main(); diff --git a/src/livekit/NoiseSuppressionTransformer.ts b/src/livekit/NoiseSuppressionTransformer.ts index 2df562fe..cbae0bb7 100644 --- a/src/livekit/NoiseSuppressionTransformer.ts +++ b/src/livekit/NoiseSuppressionTransformer.ts @@ -34,14 +34,11 @@ export class NoiseSuppressionTransformer { // Clamp level between 0-1 const clampedLevel = Math.max(0, Math.min(1, level)); - // Determine asset URL based on environment - // In development, use local proxy to avoid CORS issues - // In production, use direct CDN or custom assetConfig - const isProduction = import.meta.env.PROD; - const assetUrl = isProduction - ? process.env.VITE_NOISE_SUPPRESSION_CDN_URL || - "https://cdn.mezon.ai/AI/models/datas/noise_suppression/deepfilternet3" - : `${window.location.origin}/assets/deepfilternet3`; + // Load from bundled local assets by default (avoids CDN/CORS issues), + // but allow override via env var for custom deployments. + const assetUrl = + import.meta.env.VITE_NOISE_SUPPRESSION_CDN_URL || + `${window.location.origin}/assets/deepfilternet3`; this.processor = new DeepFilterNoiseFilterProcessor({ sampleRate: this.sampleRate,