Clearwave48 commited on
Commit
5dcefac
Β·
verified Β·
1 Parent(s): 428dcc2

Update denoiser.py

Browse files
Files changed (1) hide show
  1. denoiser.py +117 -14
denoiser.py CHANGED
@@ -1,33 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
 
 
2
  import requests
3
  import subprocess
4
- import re
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  class Denoiser:
7
  def __init__(self):
8
- self.api_key = os.environ.get("CLEANVOICE_API_KEY","")
 
 
 
 
9
 
10
- def process(self, audio_path, out_dir, **kwargs):
11
- # Pre-convert to WAV to ensure the API never fails on AAC/Opus
 
 
 
 
 
 
 
 
12
  standard_wav = os.path.join(out_dir, "internal_process.wav")
13
- subprocess.run(["ffmpeg", "-y", "-i", audio_path, "-ar", "16000", "-ac", "1", standard_wav], capture_output=True)
14
 
 
 
 
 
 
 
 
15
  try:
 
 
 
16
  from cleanvoice import Cleanvoice
17
  cv = Cleanvoice({'api_key': self.api_key})
18
- result = cv.process(standard_wav, remove_noise=True, studio_sound=True, **kwargs)
19
-
20
- audio_data = requests.get(result.audio.url).content
 
 
 
 
 
 
 
 
21
  clean_wav = os.path.join(out_dir, "clean_internal.wav")
22
  with open(clean_wav, "wb") as f:
23
  f.write(audio_data)
 
 
24
  return {'audio_path': clean_wav}
25
- except Exception:
 
 
26
  return {'audio_path': standard_wav}
27
 
28
- def clean_transcript_fillers(self, text):
29
- fillers = r'\b(um|uh|hmm|basically|like|ante|ane|matlab)\b'
30
- return re.sub(fillers, '', text, flags=re.IGNORECASE).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- def _remove_fillers(self, a, s, seg): return a, 0
33
- def _remove_stutters(self, a, s, seg): return a, 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Department 1 β€” Denoiser
3
+ Primary : Cleanvoice API β€” handles ALL audio enhancement:
4
+ noise removal, studio sound, fillers, stutters,
5
+ breaths, mouth sounds, long silences
6
+ Fallback : Returns preprocessed 16kHz mono WAV if API fails
7
+
8
+ FIXES APPLIED:
9
+ - Removed dead _remove_fillers / _remove_stutters stubs
10
+ - Added logging for better observability
11
+ - FFmpeg errors are now logged (not silently ignored)
12
+ - Temp file cleanup to prevent disk leaks
13
+ - Expanded filler word list for clean_transcript_fillers()
14
+ """
15
+
16
  import os
17
+ import re
18
+ import logging
19
  import requests
20
  import subprocess
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # ══════════════════════════════════════════════════════════════════
25
+ # FILLER WORDS β€” English + common Indic spoken fillers
26
+ # Used only for transcript text cleanup (not audio)
27
+ # ══════════════════════════════════════════════════════════════════
28
+ FILLER_PATTERN = re.compile(
29
+ r'\b(um+|uh+|hmm+|hm+|mhm|ah|oh|basically|literally|'
30
+ r'actually|honestly|like|you\s+know|i\s+mean|'
31
+ r'ante|ane|matlab|yaani|artha|mane)\b',
32
+ flags=re.IGNORECASE
33
+ )
34
+
35
 
36
  class Denoiser:
37
  def __init__(self):
38
+ self.api_key = os.environ.get("CLEANVOICE_API_KEY", "")
39
+ if self.api_key:
40
+ logger.info("[Denoiser] βœ… Cleanvoice API key found")
41
+ else:
42
+ logger.warning("[Denoiser] ⚠️ No CLEANVOICE_API_KEY β€” will use preprocessed WAV as fallback")
43
 
44
+ # ══════════════════════════════════════════════════════════════
45
+ # PUBLIC β€” MAIN PROCESS
46
+ # ══════════════════════════════════════════════════════════════
47
+ def process(self, audio_path: str, out_dir: str, **kwargs) -> dict:
48
+ """
49
+ Step 1: Convert audio to 16kHz mono WAV (required by Cleanvoice)
50
+ Step 2: Send to Cleanvoice API β€” it handles ALL enhancement
51
+ Step 3: Download cleaned audio and return path
52
+ Fallback: Return preprocessed WAV if API fails
53
+ """
54
  standard_wav = os.path.join(out_dir, "internal_process.wav")
 
55
 
56
+ # Convert to 16kHz mono WAV before sending to API
57
+ convert_ok = self._ffmpeg_convert(audio_path, standard_wav)
58
+ if not convert_ok:
59
+ logger.error("[Denoiser] FFmpeg conversion failed β€” using original file")
60
+ standard_wav = audio_path
61
+
62
+ # Send to Cleanvoice API
63
  try:
64
+ if not self.api_key:
65
+ raise ValueError("No CLEANVOICE_API_KEY set in environment")
66
+
67
  from cleanvoice import Cleanvoice
68
  cv = Cleanvoice({'api_key': self.api_key})
69
+
70
+ logger.info("[Denoiser] Sending audio to Cleanvoice API...")
71
+ result = cv.process(
72
+ standard_wav,
73
+ remove_noise=True,
74
+ studio_sound=True,
75
+ **kwargs # fillers, stutters, long_silences, breaths, mouth_sounds
76
+ )
77
+
78
+ # Download cleaned audio from Cleanvoice result URL
79
+ audio_data = requests.get(result.audio.url, timeout=60).content
80
  clean_wav = os.path.join(out_dir, "clean_internal.wav")
81
  with open(clean_wav, "wb") as f:
82
  f.write(audio_data)
83
+
84
+ logger.info("[Denoiser] βœ… Cleanvoice enhancement complete")
85
  return {'audio_path': clean_wav}
86
+
87
+ except Exception as e:
88
+ logger.warning(f"[Denoiser] Cleanvoice failed: {e} β€” using preprocessed WAV")
89
  return {'audio_path': standard_wav}
90
 
91
+ # ══════════════════════════════════════════════════════════════
92
+ # PUBLIC β€” TRANSCRIPT FILLER CLEANER
93
+ # Cleans filler words from transcript text only (not audio)
94
+ # Audio-level cleaning is done by Cleanvoice API
95
+ # ══════════════════════════════════════════════════════════════
96
+ def clean_transcript_fillers(self, text: str) -> str:
97
+ """Remove filler words from transcript text and fix extra spaces."""
98
+ cleaned = FILLER_PATTERN.sub('', text)
99
+ cleaned = re.sub(r'\s{2,}', ' ', cleaned).strip()
100
+ return cleaned
101
+
102
+ # ══════════════════════════════════════════════════════════════
103
+ # PRIVATE β€” FFmpeg Conversion
104
+ # ══════════════════════════════════════════════════════════════
105
+ def _ffmpeg_convert(self, input_path: str, output_path: str) -> bool:
106
+ """Convert any audio format to 16kHz mono PCM WAV. Returns True on success."""
107
+ try:
108
+ result = subprocess.run(
109
+ ["ffmpeg", "-y", "-i", input_path,
110
+ "-ar", "16000", "-ac", "1", "-acodec", "pcm_s16le",
111
+ output_path],
112
+ capture_output=True
113
+ )
114
+ if result.returncode == 0 and os.path.exists(output_path):
115
+ return True
116
+ logger.warning(f"[Denoiser] FFmpeg error: {result.stderr.decode()[:200]}")
117
+ return False
118
+ except Exception as e:
119
+ logger.error(f"[Denoiser] FFmpeg exception: {e}")
120
+ return False
121
 
122
+ # ══════════════════════════════════════════════════════════════
123
+ # PUBLIC β€” Temp File Cleanup (call after pipeline is done)
124
+ # ══════════════════════════════════════════════════════════════
125
+ def cleanup_temp_files(self, out_dir: str):
126
+ """Delete intermediate WAV files to prevent disk leaks."""
127
+ temp_files = [
128
+ os.path.join(out_dir, "internal_process.wav"),
129
+ ]
130
+ for f in temp_files:
131
+ if os.path.exists(f):
132
+ try:
133
+ os.remove(f)
134
+ logger.info(f"[Denoiser] Cleaned up: {f}")
135
+ except Exception as e:
136
+ logger.warning(f"[Denoiser] Could not delete {f}: {e}")