Jahmori-R commited on
Commit
194acbc
·
verified ·
1 Parent(s): d631d75

Sync from Jahmori-R/PureVoiceAI-HG via hub-sync

Browse files
Files changed (2) hide show
  1. app.py +158 -56
  2. requirements.txt +3 -0
app.py CHANGED
@@ -1,6 +1,8 @@
 
1
  import glob
2
  import os
3
- import tempfile
 
4
  from pathlib import Path
5
  import gradio as gr
6
  import spaces
@@ -8,7 +10,7 @@ import torch
8
  import torchaudio
9
  import transformers
10
  import numpy as np
11
- from huggingface_hub import sync_bucket
12
  from pyannote.audio import Pipeline
13
  from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
14
  from speechbrain.inference.separation import SepformerSeparation
@@ -18,13 +20,52 @@ from pydub import AudioSegment
18
  torch.backends.cuda.matmul.allow_tf32 = False
19
  torch.backends.cudnn.allow_tf32 = False
20
 
21
- # Temporary Directory Intializing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- temp_dir = tempfile.gettempdir()
24
- print(f"temp directory created: {temp_dir}")
25
 
26
  @spaces.GPU
27
- def process_audio(mixture):
 
 
 
 
 
 
 
 
28
  # GPU processing availability check
29
  device = "cuda" if torch.cuda.is_available() else "cpu"
30
 
@@ -32,20 +73,16 @@ def process_audio(mixture):
32
  token = os.environ.get("HF_TOKEN")
33
 
34
  # Name of separated file folder
35
- sources = Path(temp_dir) / "sources"
36
 
37
  # Creates separated directory
38
  Path.mkdir(sources, exist_ok=True)
39
 
40
- # Downloads RE-SepFormer model from huggingface buckets to space local directory
41
- # sync_bucket("hf://buckets/Jahmori-R/PureVoiceAISpace-storage", "./local")
42
-
43
-
44
-
45
- # ---------------------------------------------------------
46
- # 1. INITIALIZE MODELS
47
- # ---------------------------------------------------------
48
 
 
49
  print("Intializing models...")
50
 
51
  # Loading Resepformer Model
@@ -57,9 +94,9 @@ def process_audio(mixture):
57
  )
58
  print("RE-SepFormer model successfully loaded")
59
  except Exception as e:
60
- print(f"Failed to load RE-SepFormer model. Error: {e}")
61
- raise SystemExit()
62
 
 
63
  # Loading Pyannote Model
64
 
65
  try:
@@ -70,11 +107,12 @@ def process_audio(mixture):
70
  diarize.to(torch.device("cuda"))
71
  print("Pyannote model successfully loaded")
72
  except Exception as e:
73
- print(f"Failed to load Pyannote model. Error: {e}")
74
- raise SystemExit()
 
75
 
76
  # Loading Whisper Model
77
- transformers.logging.set_verbosity_error() # Prevents WhisperAI logging in cmd line
78
  try:
79
  transcription_model_id = "openai/whisper-large-v3-turbo"
80
  torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
@@ -98,22 +136,22 @@ def process_audio(mixture):
98
 
99
  print("Whisper model successfully loaded")
100
  except Exception as e:
101
- print(f"Failed to load Whisper model. Error: {e}")
102
- raise SystemExit()
103
-
104
- # ---------------------------------------------------------
105
- # LOAD & SEPARATE AUDIO
106
- # ---------------------------------------------------------
107
 
 
 
 
108
 
109
  # Calling ReSepformer model onto audio source
110
  assert separator is not None, "Failed to load the Sepformer model."
111
 
 
112
  # Running mixed audio onto separation model
113
  print("Separating Audio Mixture")
114
  est_sources = separator.separate_file(mixture)
115
  print("Mixture separated")
116
-
 
117
  # Saving separated sources to directory
118
  torchaudio.save(
119
  f"{sources}/speaker_1.wav", est_sources[:, :, 0].detach().cpu(), 16000
@@ -122,22 +160,46 @@ def process_audio(mixture):
122
  f"{sources}/speaker_2.wav", est_sources[:, :, 1].detach().cpu(), 16000
123
  )
124
 
125
- # ---------------------------------------------------------
126
- # VAD + TRANSCRIPTION ON SOURCES
127
- # ---------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  # Dictionary to store speaker information
130
  unfiltered_log = [[], []]
131
 
 
 
132
  # Storing both speakers
133
  file_paths = sorted(
134
- # Glob finds all similar files
135
- glob.glob(f"{sources}/speaker_*.wav")
136
  )
137
 
138
  spk_1 = file_paths[0]
139
  spk_2 = file_paths[1]
140
 
 
 
141
  # Individual Speaker Processing
142
  for i, path in enumerate(file_paths):
143
  speaker_key = f"speaker_{i + 1}"
@@ -156,14 +218,13 @@ def process_audio(mixture):
156
  }
157
  )
158
 
159
-
160
  # Creating audio segment from detected voice activity
161
  audio_seg = AudioSegment.from_file(path)
162
 
163
  print("Transcribing Chunks...")
164
  # Transcribing each voice activity detected by Pyannote
165
  for segment in unfiltered_log[i]:
166
- # Converting segment timestamps to milliseconds for pydub
167
  start_ms = segment["start"] * 1000
168
  end_ms = segment["end"] * 1000
169
  chunk = audio_seg[start_ms:end_ms]
@@ -172,26 +233,35 @@ def process_audio(mixture):
172
  samples = np.array(chunk.get_array_of_samples())
173
  samples = samples.astype(np.float32) / 32768.0
174
 
 
 
 
 
175
  # Generating transcription given chunk segment
176
- transcription = transcribe(
177
- samples,
178
- generate_kwargs={
179
- "language": "english",
180
- "condition_on_prev_tokens": True,
181
- },
182
- )
183
  # Appending each segment transcription to dictionary
184
- segment["transcription"] = transcription
185
 
 
186
  # Combined both speaker logs
187
  combined_log = unfiltered_log[0] + unfiltered_log[1]
188
 
189
  # Sorting by start time of timestamps
190
  filtered_log = sorted(combined_log, key=lambda x: x["start"])
 
191
 
 
192
  print("Task Finished")
193
- return (spk_1, spk_2, filtered_log)
194
 
 
 
 
 
 
 
 
 
195
 
196
  # Custom css for gradio interface
197
  css = """
@@ -203,31 +273,63 @@ css = """
203
  """
204
 
205
  # Gradio layout configuration
206
- with gr.Blocks(css=css) as demo:
207
  gr.Markdown("PureVoiceAI")
208
  with gr.Row(variant="panel"):
209
  audio_input = gr.Audio(
210
  type="filepath", label="Input Audio Mixture | 2 Speakers"
211
  )
212
  with gr.Row(elem_id="centered-row"):
213
- button = gr.Button(
214
  "Process",
215
  variant="primary",
216
  scale=0,
217
  )
218
-
219
- with gr.Row(variant="panel"):
220
- speaker_1_output = gr.Audio(label="Speaker 1", interactive=False)
221
- speaker_2_output = gr.Audio(label="Speaker 2", interactive=False)
222
-
223
- with gr.Row(variant="panel"):
224
- logs = gr.JSON(label="Conversation Log")
225
-
226
- button.click(
 
 
 
 
227
  fn=process_audio,
228
  inputs=audio_input,
229
- outputs=[speaker_1_output, speaker_2_output, logs],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  )
231
 
232
  if __name__ == "__main__":
233
- demo.launch(allowed_paths=[temp_dir], theme=gr.themes.Soft())
 
1
+ from time import perf_counter
2
  import glob
3
  import os
4
+ from dotenv import load_dotenv
5
+ import shutil
6
  from pathlib import Path
7
  import gradio as gr
8
  import spaces
 
10
  import torchaudio
11
  import transformers
12
  import numpy as np
13
+ import noisereduce as nr
14
  from pyannote.audio import Pipeline
15
  from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
16
  from speechbrain.inference.separation import SepformerSeparation
 
20
  torch.backends.cuda.matmul.allow_tf32 = False
21
  torch.backends.cudnn.allow_tf32 = False
22
 
23
+ # Loading token from .env
24
+ load_dotenv()
25
+
26
+ # Path checking for removing source directory every run
27
+ def reset_dir():
28
+ # Getting current source directory
29
+ sources = Path(__file__).parent / "sources"
30
+ if os.path.exists(sources):
31
+ try:
32
+ # Remove directorty recursively
33
+ shutil.rmtree(sources)
34
+ gr.Info("Process Reset")
35
+ except:
36
+ raise gr.Error("Reset Failed")
37
+
38
+ # To clear all Gradio Blocks
39
+ return None, None, None, None, None
40
+
41
+
42
+ # Formatting the transcription log to Gradio chat display
43
+ def chatFormat(transcription_log):
44
+ chat = []
45
+ for i in transcription_log:
46
+ name = i.get("speaker")
47
+ text = i.get("text")
48
+ start = i.get("start")
49
+ end = i.get("end")
50
+
51
+ title_format = f"**{name} - [{start}s - {end}s]**"
52
+ role = "user" if name == "speaker_2" else "assistant"
53
+
54
+ chat.append({"role": role, "content":text, "metadata":{"title":title_format}})
55
+
56
+ return chat
57
 
 
 
58
 
59
  @spaces.GPU
60
+ def process_audio(mixture, progress=gr.Progress()):
61
+
62
+ # Inference Time Processing
63
+ start_time = perf_counter()
64
+
65
+ progress(0, desc="Starting")
66
+
67
+ # Gradio processing alert
68
+ gr.Info("Processing Has Started", title="Notice")
69
  # GPU processing availability check
70
  device = "cuda" if torch.cuda.is_available() else "cpu"
71
 
 
73
  token = os.environ.get("HF_TOKEN")
74
 
75
  # Name of separated file folder
76
+ sources = Path(os.getcwd()) / "sources"
77
 
78
  # Creates separated directory
79
  Path.mkdir(sources, exist_ok=True)
80
 
81
+ # ---------------------------------------------------------
82
+ # 1. INITIALIZE MODELS
83
+ # ---------------------------------------------------------
 
 
 
 
 
84
 
85
+ progress(0.12, "Loading RE-SepFormer Model...")
86
  print("Intializing models...")
87
 
88
  # Loading Resepformer Model
 
94
  )
95
  print("RE-SepFormer model successfully loaded")
96
  except Exception as e:
97
+ raise RuntimeError("Failed to load RE-SepFormer model") from e
 
98
 
99
+ progress(0.25, "Loading Pyannote Model...")
100
  # Loading Pyannote Model
101
 
102
  try:
 
107
  diarize.to(torch.device("cuda"))
108
  print("Pyannote model successfully loaded")
109
  except Exception as e:
110
+ raise RuntimeError("Failed to load Pyannote model") from e
111
+
112
+ progress(0.38, "Loading Whisper Model...")
113
 
114
  # Loading Whisper Model
115
+ transformers.logging.set_verbosity_error() # Prevents WhisperAI logging in cmd line
116
  try:
117
  transcription_model_id = "openai/whisper-large-v3-turbo"
118
  torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32
 
136
 
137
  print("Whisper model successfully loaded")
138
  except Exception as e:
139
+ raise RuntimeError("Failed to load Whisper model") from e
 
 
 
 
 
140
 
141
+ # ---------------------------------------------------------
142
+ # LOAD & SEPARATE AUDIO
143
+ # ---------------------------------------------------------
144
 
145
  # Calling ReSepformer model onto audio source
146
  assert separator is not None, "Failed to load the Sepformer model."
147
 
148
+ progress(0.50,"Separating Mixtures...")
149
  # Running mixed audio onto separation model
150
  print("Separating Audio Mixture")
151
  est_sources = separator.separate_file(mixture)
152
  print("Mixture separated")
153
+
154
+ print(type(est_sources))
155
  # Saving separated sources to directory
156
  torchaudio.save(
157
  f"{sources}/speaker_1.wav", est_sources[:, :, 0].detach().cpu(), 16000
 
160
  f"{sources}/speaker_2.wav", est_sources[:, :, 1].detach().cpu(), 16000
161
  )
162
 
163
+ # Applying noise gate to remaining artefacts in sources
164
+ spk1_wave_torch,sr1 = torchaudio.load(f"{sources}/speaker_1.wav")
165
+ spk2_wave_torch,sr2 = torchaudio.load(f"{sources}/speaker_2.wav")
166
+
167
+ # Convert torch to numpy for noisereduce
168
+ spk1_wave_np = spk1_wave_torch.numpy()
169
+ spk2_wave_np = spk2_wave_torch.numpy()
170
+
171
+ # Apply noise gate onto waveforms
172
+ spk1_denoised_np = nr.reduce_noise(y=spk1_wave_np,sr=sr1,n_fft=512)
173
+ spk2_denoised_np = nr.reduce_noise(y=spk2_wave_np,sr=sr2,n_fft=512)
174
+
175
+ # Convert numpy data back to torch for saving
176
+ spk1_denoised = torch.from_numpy(spk1_denoised_np)
177
+ spk2_denoised = torch.from_numpy(spk2_denoised_np)
178
+
179
+ # Saving new denoised sources
180
+ torchaudio.save(f"{sources}/speaker_denoised_1.wav",spk1_denoised,sr1)
181
+ torchaudio.save(f"{sources}/speaker_denoised_2.wav",spk2_denoised,sr2)
182
+
183
+ # ---------------------------------------------------------
184
+ # VAD + TRANSCRIPTION ON SOURCES
185
+ # ---------------------------------------------------------
186
 
187
  # Dictionary to store speaker information
188
  unfiltered_log = [[], []]
189
 
190
+ progress(0.62, "Sorting Sources...")
191
+
192
  # Storing both speakers
193
  file_paths = sorted(
194
+ # Glob finds all similar files
195
+ glob.glob(f"{sources}/speaker_denoised_*.wav")
196
  )
197
 
198
  spk_1 = file_paths[0]
199
  spk_2 = file_paths[1]
200
 
201
+ progress(0.75, "VAD + Transcribing...")
202
+
203
  # Individual Speaker Processing
204
  for i, path in enumerate(file_paths):
205
  speaker_key = f"speaker_{i + 1}"
 
218
  }
219
  )
220
 
 
221
  # Creating audio segment from detected voice activity
222
  audio_seg = AudioSegment.from_file(path)
223
 
224
  print("Transcribing Chunks...")
225
  # Transcribing each voice activity detected by Pyannote
226
  for segment in unfiltered_log[i]:
227
+ # Converting segment timestamps to milliseconds for pydub segmentation
228
  start_ms = segment["start"] * 1000
229
  end_ms = segment["end"] * 1000
230
  chunk = audio_seg[start_ms:end_ms]
 
233
  samples = np.array(chunk.get_array_of_samples())
234
  samples = samples.astype(np.float32) / 32768.0
235
 
236
+ # Whisper Configutation
237
+ kwargs = {
238
+ "language": "english", # Language transcription
239
+ }
240
  # Generating transcription given chunk segment
241
+ transcription = transcribe(samples, generate_kwargs=kwargs)
242
+
 
 
 
 
 
243
  # Appending each segment transcription to dictionary
244
+ segment.update(transcription)
245
 
246
+ progress(0.88, "Sorting Speaker Logs....")
247
  # Combined both speaker logs
248
  combined_log = unfiltered_log[0] + unfiltered_log[1]
249
 
250
  # Sorting by start time of timestamps
251
  filtered_log = sorted(combined_log, key=lambda x: x["start"])
252
+ chat = chatFormat(filtered_log)
253
 
254
+ progress(1.00, "Task Finished")
255
  print("Task Finished")
 
256
 
257
+ end_time = perf_counter()
258
+
259
+ inference_time = f"{end_time - start_time:.2f} seconds"
260
+ return (spk_1, spk_2, chat, inference_time)
261
+
262
+ # ---------------------------------------------------------
263
+ # GRADIO INTERFACE CUSTOMIZATION
264
+ # ---------------------------------------------------------
265
 
266
  # Custom css for gradio interface
267
  css = """
 
273
  """
274
 
275
  # Gradio layout configuration
276
+ with gr.Blocks() as demo:
277
  gr.Markdown("PureVoiceAI")
278
  with gr.Row(variant="panel"):
279
  audio_input = gr.Audio(
280
  type="filepath", label="Input Audio Mixture | 2 Speakers"
281
  )
282
  with gr.Row(elem_id="centered-row"):
283
+ process_button = gr.Button(
284
  "Process",
285
  variant="primary",
286
  scale=0,
287
  )
288
+ reset_button = gr.Button("Reset", variant="stop", interactive=False, scale=0)
289
+ with gr.Row(variant="panel", visible=False) as source_row:
290
+ with gr.Column():
291
+ speaker_1_output = gr.Audio(label="Speaker 1", interactive=False)
292
+ speaker_2_output = gr.Audio(label="Speaker 2", interactive=False)
293
+ with gr.Column():
294
+ inference_time_log = gr.Textbox(label="Inference Time", interactive=False)
295
+
296
+ with gr.Row(variant="panel") as log_row:
297
+ logs = gr.Chatbot(label="Conversation Log",layout="panel")
298
+
299
+ # Process Button Functionality
300
+ process_button.click(
301
  fn=process_audio,
302
  inputs=audio_input,
303
+ outputs=[speaker_1_output, speaker_2_output, logs, inference_time_log],
304
+ ).then(
305
+ fn=lambda: (
306
+ gr.Row(visible=True),
307
+ gr.Button(interactive=False),
308
+ gr.Button(interactive=True),
309
+ ),
310
+ inputs=None,
311
+ outputs=[source_row, process_button, reset_button],
312
+ )
313
+
314
+ # Reset Button Functionality
315
+ reset_button.click(
316
+ fn=reset_dir,
317
+ outputs=[
318
+ audio_input,
319
+ speaker_1_output,
320
+ speaker_2_output,
321
+ inference_time_log,
322
+ logs,
323
+ ],
324
+ ).then(
325
+ fn=lambda: (
326
+ gr.Row(visible=False),
327
+ gr.Button(interactive=True),
328
+ gr.Button(interactive=False),
329
+ ),
330
+ inputs=None,
331
+ outputs=[source_row, process_button, reset_button],
332
  )
333
 
334
  if __name__ == "__main__":
335
+ demo.launch(theme=gr.themes.Soft(), css=css)
requirements.txt CHANGED
@@ -29,6 +29,7 @@ cuda-toolkit==13.0.2
29
  cycler==0.12.1
30
  cymem==2.0.13
31
  distlib==0.4.3
 
32
  einops==0.8.2
33
  fastapi==0.141.1
34
  filelock==3.32.7
@@ -73,6 +74,7 @@ murmurhash==1.0.15
73
  narwhals==2.26.0
74
  networkx==3.6.1
75
  nodeenv==1.10.0
 
76
  numba==0.67.0
77
  numpy==2.5.3
78
  nvidia-cublas==13.1.0.3
@@ -127,6 +129,7 @@ pyparsing==3.3.2
127
  pytest==7.4.0
128
  python-dateutil==2.9.0.post0
129
  python-discovery==1.6.1
 
130
  python-multipart==0.0.32
131
  pytorch-lightning==2.6.6
132
  pytorch-metric-learning==2.9.0
 
29
  cycler==0.12.1
30
  cymem==2.0.13
31
  distlib==0.4.3
32
+ dotenv==0.9.9
33
  einops==0.8.2
34
  fastapi==0.141.1
35
  filelock==3.32.7
 
74
  narwhals==2.26.0
75
  networkx==3.6.1
76
  nodeenv==1.10.0
77
+ noisereduce==3.0.3
78
  numba==0.67.0
79
  numpy==2.5.3
80
  nvidia-cublas==13.1.0.3
 
129
  pytest==7.4.0
130
  python-dateutil==2.9.0.post0
131
  python-discovery==1.6.1
132
+ python-dotenv==1.2.4
133
  python-multipart==0.0.32
134
  pytorch-lightning==2.6.6
135
  pytorch-metric-learning==2.9.0