trysem commited on
Commit
8bdeb34
·
verified ·
1 Parent(s): ccdedf5

Create dfnv3-base

Browse files
Files changed (1) hide show
  1. dfnv3-base +405 -0
dfnv3-base ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>DeepFilterNet Audio Cleaner</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/lucide@latest"></script>
9
+ <script>
10
+ tailwind.config = {
11
+ darkMode: 'class',
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ primary: {
16
+ 50: '#eff6ff',
17
+ 100: '#dbeafe',
18
+ 500: '#3b82f6',
19
+ 600: '#2563eb',
20
+ 900: '#1e3a8a',
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ </script>
27
+ <style>
28
+ .drag-active {
29
+ border-color: #3b82f6 !important;
30
+ background-color: rgba(59, 130, 246, 0.1) !important;
31
+ }
32
+ .hidden { display: none !important; }
33
+
34
+ /* Spinner Animation */
35
+ .spinner {
36
+ animation: spin 1s linear infinite;
37
+ }
38
+ @keyframes spin {
39
+ from { transform: rotate(0deg); }
40
+ to { transform: rotate(360deg); }
41
+ }
42
+ </style>
43
+ </head>
44
+ <body class="bg-slate-900 text-slate-200 min-h-screen font-sans selection:bg-primary-500 selection:text-white pb-12">
45
+
46
+ <div class="max-w-4xl mx-auto px-4 pt-12">
47
+
48
+ <!-- Header Section -->
49
+ <header class="text-center mb-10">
50
+ <div class="inline-flex items-center justify-center p-3 bg-primary-900/50 rounded-2xl mb-4 border border-primary-500/30 text-primary-500">
51
+ <i data-lucide="mic" class="w-8 h-8"></i>
52
+ <i data-lucide="sparkles" class="w-5 h-5 ml-1 absolute transform translate-x-4 -translate-y-4 text-yellow-400"></i>
53
+ </div>
54
+ <h1 class="text-4xl font-bold mb-3 text-white">DeepFilterNet Offline Cleaner</h1>
55
+ <p class="text-slate-400 max-w-2xl mx-auto text-lg">
56
+ Remove background noise and enhance speech clarity entirely in your browser.
57
+ Powered by the <a href="https://github.com/Rikorose/DeepFilterNet" target="_blank" class="text-primary-500 hover:underline">DeepFilterNet3 AI model</a>.
58
+ <span class="text-green-400 font-medium text-sm block mt-2">
59
+ <i data-lucide="shield-check" class="w-4 h-4 inline align-text-bottom"></i>
60
+ 100% Client-Side Privacy: Your audio never leaves your device.
61
+ </span>
62
+ </p>
63
+ </header>
64
+
65
+ <!-- Main Content Area -->
66
+ <main class="bg-slate-800 rounded-2xl border border-slate-700 shadow-xl overflow-hidden">
67
+
68
+ <!-- Settings & Controls -->
69
+ <div class="p-6 border-b border-slate-700 bg-slate-800/50 flex flex-col md:flex-row items-center justify-between gap-4">
70
+ <div class="flex items-center gap-3">
71
+ <i data-lucide="sliders-horizontal" class="w-5 h-5 text-slate-400"></i>
72
+ <label for="noiseLevel" class="text-sm font-medium">Noise Reduction Level:</label>
73
+ <span id="noiseLevelValue" class="text-primary-500 font-bold w-8 text-center">100</span>
74
+ </div>
75
+ <input type="range" id="noiseLevel" min="0" max="100" value="100" class="w-full md:w-64 accent-primary-500 h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer">
76
+ </div>
77
+
78
+ <!-- Upload Zone -->
79
+ <div class="p-8">
80
+ <div id="dropzone" class="border-2 border-dashed border-slate-600 rounded-xl p-10 text-center cursor-pointer transition-all duration-200 hover:border-primary-500 hover:bg-slate-700/30 group">
81
+ <input type="file" id="fileInput" accept="audio/*" class="hidden">
82
+ <i data-lucide="upload-cloud" class="w-12 h-12 mx-auto text-slate-400 group-hover:text-primary-500 transition-colors mb-4"></i>
83
+ <h3 class="text-xl font-semibold text-white mb-2">Drag & Drop Audio File</h3>
84
+ <p class="text-slate-400 text-sm">or click to browse from your device</p>
85
+ <p class="text-slate-500 text-xs mt-4">Supports WAV, MP3, AAC, OGG</p>
86
+ </div>
87
+
88
+ <!-- Status & Progress UI -->
89
+ <div id="statusContainer" class="hidden mt-8 p-6 bg-slate-900/50 rounded-xl border border-slate-700">
90
+ <div class="flex items-center gap-4 mb-2">
91
+ <i data-lucide="loader-2" id="statusIcon" class="w-6 h-6 text-primary-500 spinner"></i>
92
+ <h4 id="statusTitle" class="text-lg font-medium text-white">Initializing Engine...</h4>
93
+ </div>
94
+ <p id="statusMessage" class="text-sm text-slate-400 ml-10">Loading WebAssembly and DeepFilterNet3 neural network models...</p>
95
+ </div>
96
+
97
+ <!-- Results UI -->
98
+ <div id="resultsContainer" class="hidden mt-8 space-y-6">
99
+ <div class="grid md:grid-cols-2 gap-6">
100
+ <!-- Original Audio -->
101
+ <div class="bg-slate-900 rounded-xl p-5 border border-slate-700">
102
+ <h4 class="text-sm font-semibold text-slate-400 mb-3 uppercase tracking-wider flex items-center gap-2">
103
+ <i data-lucide="headphones" class="w-4 h-4"></i> Original Audio
104
+ </h4>
105
+ <audio id="originalAudio" controls class="w-full h-10 rounded outline-none"></audio>
106
+ </div>
107
+
108
+ <!-- Cleaned Audio -->
109
+ <div class="bg-primary-900/20 rounded-xl p-5 border border-primary-500/30">
110
+ <h4 class="text-sm font-semibold text-primary-400 mb-3 uppercase tracking-wider flex items-center gap-2">
111
+ <i data-lucide="check-circle-2" class="w-4 h-4"></i> Cleaned Audio
112
+ </h4>
113
+ <audio id="cleanedAudio" controls class="w-full h-10 rounded outline-none"></audio>
114
+ </div>
115
+ </div>
116
+
117
+ <!-- Action Buttons -->
118
+ <div class="flex justify-center pt-4">
119
+ <button id="downloadBtn" class="flex items-center gap-2 bg-primary-600 hover:bg-primary-500 text-white px-6 py-3 rounded-xl font-medium transition-all shadow-lg shadow-primary-500/20 hover:shadow-primary-500/40">
120
+ <i data-lucide="download" class="w-5 h-5"></i>
121
+ Download Cleaned Audio (.wav)
122
+ </button>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </main>
127
+ </div>
128
+
129
+ <!-- Application Logic -->
130
+ <script type="module">
131
+ // --- 1. SHARED-ARRAY-BUFFER POLYFILL ---
132
+ if (typeof window.SharedArrayBuffer === 'undefined') {
133
+ window.SharedArrayBuffer = window.ArrayBuffer;
134
+ }
135
+
136
+ // --- 2. HUGGING FACE DIRECT FETCH INTERCEPTOR ---
137
+ // Since you uploaded the files directly to the root of your Hugging Face repo,
138
+ // we intercept the library's requests (which expect nested /v2/pkg/ folders)
139
+ // and strictly redirect them to your raw Hugging Face file URLs.
140
+ const originalFetch = window.fetch;
141
+ window.fetch = async function(...args) {
142
+ let request = args[0];
143
+ let reqUrl = typeof request === 'string' ? request : (request && request.url);
144
+
145
+ if (reqUrl && typeof reqUrl === 'string') {
146
+ let newUrl = null;
147
+
148
+ if (reqUrl.includes('df_bg.wasm')) {
149
+ console.log("Redirecting WASM fetch to Hugging Face...");
150
+ newUrl = 'https://huggingface.co/trysem/DeepFilterNet3/resolve/main/df_bg.wasm';
151
+ } else if (reqUrl.includes('DeepFilterNet3_onnx.tar.gz')) {
152
+ console.log("Redirecting Model fetch to Hugging Face...");
153
+ newUrl = 'https://huggingface.co/trysem/DeepFilterNet3/resolve/main/DeepFilterNet3_onnx.tar.gz';
154
+ }
155
+
156
+ // If we have a match, swap the URL while preserving all original fetch configurations
157
+ if (newUrl) {
158
+ if (request instanceof Request) {
159
+ // Rebuild the request to keep internal library headers/signals intact
160
+ args[0] = new Request(newUrl, request);
161
+ } else {
162
+ args[0] = newUrl;
163
+ }
164
+ }
165
+ }
166
+
167
+ // Let normal requests pass completely unfiltered
168
+ return originalFetch.apply(this, args);
169
+ };
170
+
171
+ // Initialize Icons
172
+ lucide.createIcons();
173
+
174
+ // UI Elements
175
+ const dropzone = document.getElementById('dropzone');
176
+ const fileInput = document.getElementById('fileInput');
177
+ const noiseLevel = document.getElementById('noiseLevel');
178
+ const noiseLevelValue = document.getElementById('noiseLevelValue');
179
+
180
+ const statusContainer = document.getElementById('statusContainer');
181
+ const statusIcon = document.getElementById('statusIcon');
182
+ const statusTitle = document.getElementById('statusTitle');
183
+ const statusMessage = document.getElementById('statusMessage');
184
+
185
+ const resultsContainer = document.getElementById('resultsContainer');
186
+ const originalAudio = document.getElementById('originalAudio');
187
+ const cleanedAudio = document.getElementById('cleanedAudio');
188
+ const downloadBtn = document.getElementById('downloadBtn');
189
+
190
+ let cleanedBlobUrl = null;
191
+
192
+ // Settings Listener
193
+ noiseLevel.addEventListener('input', (e) => {
194
+ noiseLevelValue.textContent = e.target.value;
195
+ });
196
+
197
+ // Drag and Drop Listeners
198
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
199
+ dropzone.addEventListener(eventName, preventDefaults, false);
200
+ });
201
+
202
+ function preventDefaults(e) {
203
+ e.preventDefault();
204
+ e.stopPropagation();
205
+ }
206
+
207
+ ['dragenter', 'dragover'].forEach(eventName => {
208
+ dropzone.addEventListener(eventName, () => dropzone.classList.add('drag-active'), false);
209
+ });
210
+
211
+ ['dragleave', 'drop'].forEach(eventName => {
212
+ dropzone.addEventListener(eventName, () => dropzone.classList.remove('drag-active'), false);
213
+ });
214
+
215
+ dropzone.addEventListener('drop', (e) => {
216
+ let dt = e.dataTransfer;
217
+ let files = dt.files;
218
+ if (files.length > 0) handleFile(files[0]);
219
+ });
220
+
221
+ dropzone.addEventListener('click', () => fileInput.click());
222
+ fileInput.addEventListener('change', function() {
223
+ if (this.files.length > 0) handleFile(this.files[0]);
224
+ });
225
+
226
+ function setStatus(state, title, message) {
227
+ statusContainer.classList.remove('hidden');
228
+ resultsContainer.classList.add('hidden');
229
+ statusTitle.textContent = title;
230
+ statusMessage.textContent = message;
231
+
232
+ if (state === 'error') {
233
+ statusIcon.setAttribute('data-lucide', 'alert-circle');
234
+ statusIcon.classList.remove('spinner');
235
+ statusIcon.classList.add('text-red-500');
236
+ } else if (state === 'done') {
237
+ statusIcon.setAttribute('data-lucide', 'check-circle-2');
238
+ statusIcon.classList.remove('spinner');
239
+ statusIcon.classList.replace('text-primary-500', 'text-green-500');
240
+ } else {
241
+ statusIcon.setAttribute('data-lucide', 'loader-2');
242
+ statusIcon.classList.add('spinner');
243
+ statusIcon.classList.replace('text-green-500', 'text-primary-500');
244
+ statusIcon.classList.replace('text-red-500', 'text-primary-500');
245
+ }
246
+ lucide.createIcons();
247
+ }
248
+
249
+ async function handleFile(file) {
250
+ if (!file.type.startsWith('audio/')) {
251
+ setStatus('error', 'Invalid File Type', 'Please upload a valid audio file format.');
252
+ return;
253
+ }
254
+
255
+ try {
256
+ // Prepare original audio player
257
+ const originalUrl = URL.createObjectURL(file);
258
+ originalAudio.src = originalUrl;
259
+
260
+ // 1. Decode original file into AudioBuffer
261
+ setStatus('loading', 'Decoding Audio...', 'Processing raw audio data...');
262
+ const audioData = await file.arrayBuffer();
263
+
264
+ // DeepFilterNet targets 48kHz for optimal quality
265
+ const targetSampleRate = 48000;
266
+ const decodeCtx = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: targetSampleRate });
267
+ const audioBuffer = await decodeCtx.decodeAudioData(audioData);
268
+
269
+ setStatus('loading', 'Loading AI Model...', 'Fetching WebAssembly & Weights (~17MB)...');
270
+
271
+ // Import v1.2.1 which features SIMD optimizations and accurate APIs
272
+ const dfModule = await import('https://esm.sh/deepfilternet3-noise-filter@1.2.1');
273
+ const ProcessorClass = dfModule.DeepFilterNet3Core || dfModule.DeepFilterNoiseFilterProcessor;
274
+
275
+ if (!ProcessorClass) throw new Error("Could not initialize the DeepFilterNet AI Processor.");
276
+
277
+ // Initialize DeepFilterNet configuration
278
+ const level = parseInt(noiseLevel.value, 10);
279
+
280
+ const proc = new ProcessorClass({
281
+ sampleRate: targetSampleRate,
282
+ noiseReductionLevel: level,
283
+ // The fetch interceptor above handles the exact routing,
284
+ // but we provide your base URL here to satisfy the config requirements.
285
+ assetConfig: { cdnUrl: 'https://huggingface.co/trysem/DeepFilterNet3/resolve/main' }
286
+ });
287
+
288
+ // Execute Initialization
289
+ await proc.initialize();
290
+
291
+ // 2. Set up Offline Rendering Context for faster-than-realtime processing
292
+ setStatus('loading', 'Cleaning Audio...', 'Running the neural network over the audio frames...');
293
+
294
+ // We use mono processing as DeepFilterNet is single-channel optimized for voice
295
+ const offlineCtx = new OfflineAudioContext(1, audioBuffer.length, targetSampleRate);
296
+ const sourceNode = offlineCtx.createBufferSource();
297
+
298
+ // Mixdown to mono if stereo
299
+ if (audioBuffer.numberOfChannels > 1) {
300
+ const monoBuffer = offlineCtx.createBuffer(1, audioBuffer.length, targetSampleRate);
301
+ const monoData = monoBuffer.getChannelData(0);
302
+ const left = audioBuffer.getChannelData(0);
303
+ const right = audioBuffer.getChannelData(1);
304
+ for (let i = 0; i < audioBuffer.length; i++) {
305
+ monoData[i] = (left[i] + right[i]) / 2;
306
+ }
307
+ sourceNode.buffer = monoBuffer;
308
+ } else {
309
+ sourceNode.buffer = audioBuffer;
310
+ }
311
+
312
+ // Connect to DeepFilter AI node
313
+ const dfNode = await proc.createAudioWorkletNode(offlineCtx);
314
+ sourceNode.connect(dfNode);
315
+ dfNode.connect(offlineCtx.destination);
316
+ sourceNode.start(0);
317
+
318
+ // Render out the cleaned audio
319
+ const renderedBuffer = await offlineCtx.startRendering();
320
+
321
+ // 3. Convert resulting buffer to a downloadable WAV
322
+ setStatus('loading', 'Finalizing...', 'Encoding cleaned audio to WAV format...');
323
+ const wavBlob = audioBufferToWav(renderedBuffer);
324
+
325
+ if (cleanedBlobUrl) URL.revokeObjectURL(cleanedBlobUrl);
326
+ cleanedBlobUrl = URL.createObjectURL(wavBlob);
327
+
328
+ // Show UI
329
+ setStatus('done', 'Processing Complete!', 'Your audio has been successfully cleaned.');
330
+ cleanedAudio.src = cleanedBlobUrl;
331
+ resultsContainer.classList.remove('hidden');
332
+
333
+ // Set up Download button
334
+ downloadBtn.onclick = () => {
335
+ const a = document.createElement('a');
336
+ a.style.display = 'none';
337
+ a.href = cleanedBlobUrl;
338
+ a.download = `Cleaned_${file.name.split('.')[0] || 'audio'}.wav`;
339
+ document.body.appendChild(a);
340
+ a.click();
341
+ setTimeout(() => document.body.removeChild(a), 100);
342
+ };
343
+
344
+ } catch (err) {
345
+ console.error(err);
346
+ setStatus('error', 'Processing Failed', err.message || 'An error occurred during audio processing. Check console for details.');
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Converts an AudioBuffer to a valid WAV Blob.
352
+ * DeepFilterNet outputs standard Float32, we convert to 16-bit PCM WAV.
353
+ */
354
+ function audioBufferToWav(buffer) {
355
+ const numOfChan = buffer.numberOfChannels;
356
+ const length = buffer.length * numOfChan * 2 + 44;
357
+ const bufferData = new ArrayBuffer(length);
358
+ const view = new DataView(bufferData);
359
+ const channels = [];
360
+ let i, sample, offset = 0, pos = 0;
361
+
362
+ // Write WAV Header
363
+ setUint32(0x46464952); // "RIFF"
364
+ setUint32(length - 8); // file length - 8
365
+ setUint32(0x45564157); // "WAVE"
366
+ setUint32(0x20746d66); // "fmt " chunk
367
+ setUint32(16); // length = 16
368
+ setUint16(1); // PCM (uncompressed)
369
+ setUint16(numOfChan);
370
+ setUint32(buffer.sampleRate);
371
+ setUint32(buffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
372
+ setUint16(numOfChan * 2); // block-align
373
+ setUint16(16); // 16-bit
374
+ setUint32(0x61746164); // "data" - chunk
375
+ setUint32(length - pos - 4); // chunk length
376
+
377
+ // Read channel data
378
+ for (i = 0; i < buffer.numberOfChannels; i++) {
379
+ channels.push(buffer.getChannelData(i));
380
+ }
381
+
382
+ // Write Interleaved Audio Data
383
+ while (pos < buffer.length) {
384
+ for (i = 0; i < numOfChan; i++) {
385
+ sample = Math.max(-1, Math.min(1, channels[i][pos]));
386
+ sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0;
387
+ view.setInt16(offset, sample, true);
388
+ offset += 2;
389
+ }
390
+ pos++;
391
+ }
392
+ return new Blob([bufferData], { type: "audio/wav" });
393
+
394
+ function setUint16(data) {
395
+ view.setUint16(offset, data, true);
396
+ offset += 2;
397
+ }
398
+ function setUint32(data) {
399
+ view.setUint32(offset, data, true);
400
+ offset += 4;
401
+ }
402
+ }
403
+ </script>
404
+ </body>
405
+ </html>