shaikrabbani-dev commited on
Commit
e3f0358
Β·
verified Β·
1 Parent(s): c8e18fb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +506 -120
app.py CHANGED
@@ -1,6 +1,6 @@
1
- # ========================================== #
2
- # 0. HUGGING FACE ZERO-GPU INITIALIZATION #
3
- # ========================================== #
4
  try:
5
  import spaces
6
  except ImportError:
@@ -9,187 +9,573 @@ except ImportError:
9
  def GPU(fn):
10
  return fn
11
 
 
 
 
 
12
  import os
 
13
  import tempfile
14
  import numpy as np
15
  import whisper
16
  import gradio as gr
 
17
  from groq import Groq
18
  from f5_tts.api import F5TTS
19
 
20
- # ========================================== #
21
- # 1. System Setup & Configuration #
22
- # ========================================== #
23
- # API key is read ONLY from the environment β€” never hardcode a real key here.
24
- # Set it in your terminal or Hugging Face Space "Secrets", not in this file.
25
- GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "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")
26
 
27
- # Reference voice sample used to clone your voice. This one file IS expected
28
- # to persist (it's your enrolled voice sample) β€” everything generated FROM it
29
- # during a session is not saved anywhere.
 
 
 
 
 
 
 
30
  REFERENCE_VOICE_FILE = "my_voice_fixed.wav"
31
- REFERENCE_TEXT = "Hi, my name is Rabbani. I am a software engineer at Gamana Solutions."
 
 
 
 
 
32
 
33
  if not os.path.exists(REFERENCE_VOICE_FILE):
34
  raise FileNotFoundError(
35
- f"Reference audio file '{REFERENCE_VOICE_FILE}' not found. "
36
- "Place your enrolled voice sample WAV in the app directory with this exact name "
37
- "before starting the app."
38
  )
39
 
40
- groq_client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None
 
 
 
 
 
41
  whisper_model = None
42
  f5tts = None
43
 
44
 
45
- # ========================================== #
46
- # 2. Dynamic Text Response Generation #
47
- # ========================================== #
48
- def get_ai_answer(question: str) -> str:
 
 
49
  global groq_client
 
50
  if not GROQ_API_KEY:
51
- return "Set GROQ_API_KEY as an environment variable (or a Space secret) to chat."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  try:
54
- if groq_client is None:
55
- groq_client = Groq(api_key=GROQ_API_KEY)
56
 
57
- chat_completion = groq_client.chat.completions.create(
 
 
 
 
 
58
  messages=[
 
59
  {
60
  "role": "system",
61
- "content": "You are an informative voice simulation assistant. Provide comprehensive, "
62
- "detailed, and clear explanations. Avoid single-sentence answers.",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  },
64
- {"role": "user", "content": question},
 
 
 
 
 
65
  ],
66
- model="llama-3.3-70b-versatile",
67
- max_tokens=250,
68
- timeout=15,
 
 
 
 
 
 
 
 
 
 
 
69
  )
70
- return chat_completion.choices[0].message.content.strip()
 
 
71
  except Exception as error:
72
- print(f"Groq API issue: {error}")
73
- return f"The AI engine is currently offline. Details: {error}"
74
-
75
-
76
- # ========================================== #
77
- # 3. Native Voice Synthesis Processing #
78
- # (in-memory only β€” nothing written to #
79
- # a permanent output file) #
80
- # ========================================== #
81
- def clone_and_speak(text: str):
82
- """Synthesize `text` in the cloned voice and return (sample_rate, audio_array)
83
- for direct in-memory playback. F5-TTS needs a real file path to write to, so we
84
- use an auto-deleted temp file as a scratch buffer, then discard it immediately β€”
85
- nothing about the generated audio is kept on disk after this function returns.
86
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  global f5tts
88
- if "offline" in text.lower() or "secret" in text.lower():
 
89
  return None
90
 
91
  try:
 
92
  if f5tts is None:
93
- print("Loading F5-TTS core neural simulator...")
 
 
94
  f5tts = F5TTS()
95
 
96
- with tempfile.TemporaryDirectory() as tmp_dir:
97
- scratch_path = os.path.join(tmp_dir, "scratch.wav")
 
 
 
 
 
 
98
  result = f5tts.infer(
 
99
  ref_file=REFERENCE_VOICE_FILE,
 
100
  ref_text=REFERENCE_TEXT,
 
101
  gen_text=text,
102
- file_wave=scratch_path, # F5-TTS writes here; deleted when the `with` block exits
 
103
  )
104
 
 
 
 
 
105
  if isinstance(result, tuple):
106
- audio_data, sample_rate = result[0], result[1]
 
 
 
107
  else:
108
- audio_data, sample_rate = result, 24000
109
 
110
- if hasattr(audio_data, "cpu"):
111
- audio_data = audio_data.cpu().numpy()
112
- elif hasattr(audio_data, "numpy"):
113
- audio_data = audio_data.numpy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
- audio_data = np.squeeze(np.asarray(audio_data))
116
- # Copy into a plain array we own β€” the temp dir (and scratch file) is
117
- # deleted as soon as this `with` block exits.
 
 
 
 
 
 
118
  audio_data = audio_data.copy()
119
 
120
- return sample_rate, audio_data
 
 
 
 
 
 
121
  except Exception as error:
122
- print(f"TTS synthesis failed: {error}")
 
 
 
 
123
  return None
124
 
125
 
126
- # ========================================== #
127
- # 4. Request Router Pipeline #
128
- # ========================================== #
 
129
  @spaces.GPU
130
- def chatbot_engine(text_input, audio_input):
131
- """`audio_input` is a filepath Gradio hands us for whatever the user just
132
- recorded. We transcribe it and never write a copy of our own β€” Gradio's own
133
- temp cache is outside app control, but this app keeps zero extra copies.
134
- """
135
- global whisper_model
136
- if whisper_model is None:
137
- print("Loading Whisper (tiny)...")
138
- whisper_model = whisper.load_model("tiny")
139
-
140
- if audio_input is not None:
141
- question = whisper_model.transcribe(audio_input, fp16=False)["text"].strip()
142
- elif text_input and text_input.strip():
143
- question = text_input.strip()
144
- else:
145
  return (
146
- "System Notification: Please enter a text question or record your microphone.",
147
- None,
148
- gr.update(),
149
- gr.update(),
150
  )
151
 
152
- ai_text_response = get_ai_answer(question)
153
- generated_audio = clone_and_speak(ai_text_response) # (sample_rate, array) or None, never a file
154
- return ai_text_response, generated_audio, gr.update(value=""), gr.update(value=None)
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
- # ========================================== #
158
- # 5. Full-Output Single-Page UI Canvas #
159
- # ========================================== #
160
- with gr.Blocks(title="Rabbani AI Voice Clone") as interface:
161
- gr.Markdown("# πŸŽ™οΈ Rabbani AI Voice Clone")
162
- gr.Markdown(
163
- "Type or speak your question below. Nothing you record or that gets generated "
164
- "is saved to disk β€” audio is processed in memory for this session only."
165
- )
166
 
167
- with gr.Row():
168
- with gr.Column(scale=1):
169
- gr.Markdown("### πŸ’¬ Input Interaction Canvas")
170
- text_prompt_box = gr.Textbox(
171
- placeholder="Type your custom question query here...", label="Your Written Question"
 
 
 
 
 
 
172
  )
173
- audio_prompt_box = gr.Audio(label="Or Record/Speak Your Question Directly", type="filepath")
174
- submit_btn = gr.Button("Send Question to AI Engine", variant="primary")
175
- clear_btn = gr.ClearButton(value="Clear Inputs")
176
-
177
- with gr.Column(scale=1):
178
- gr.Markdown("### πŸ“Š Cloned Output Responses")
179
- text_output_box = gr.Textbox(label="AI Answer Text Output", interactive=False)
180
- # type="numpy" here means Gradio plays back the (sample_rate, array) tuple
181
- # directly from memory β€” no output WAV file is ever written to disk.
182
- audio_output_player = gr.Audio(
183
- label="Rendered Cloned Response Audio Player",
184
- type="numpy",
185
- interactive=False,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  )
187
 
188
- submit_btn.click(
189
- fn=chatbot_engine,
190
- inputs=[text_prompt_box, audio_prompt_box],
191
- outputs=[text_output_box, audio_output_player, text_prompt_box, audio_prompt_box],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  )
193
- clear_btn.add([text_prompt_box, audio_prompt_box, text_output_box, audio_output_player])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
 
195
  interface.queue().launch()
 
1
+ # ==========================================
2
+ # 0. HUGGING FACE ZERO-GPU INITIALIZATION
3
+ # ==========================================
4
  try:
5
  import spaces
6
  except ImportError:
 
9
  def GPU(fn):
10
  return fn
11
 
12
+
13
+ # ==========================================
14
+ # 1. IMPORTS
15
+ # ==========================================
16
  import os
17
+ import re
18
  import tempfile
19
  import numpy as np
20
  import whisper
21
  import gradio as gr
22
+
23
  from groq import Groq
24
  from f5_tts.api import F5TTS
25
 
 
 
 
 
 
 
26
 
27
+ # ==========================================
28
+ # 2. CONFIGURATION
29
+ # ==========================================
30
+
31
+ # IMPORTANT:
32
+ # Set your Groq API key as an environment variable.
33
+ # Do NOT put your real API key directly in the code.
34
+ GROQ_API_KEY = os.environ.get("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")
35
+
36
+ # Your reference voice
37
  REFERENCE_VOICE_FILE = "my_voice_fixed.wav"
38
+
39
+ # Text spoken in your reference voice file
40
+ REFERENCE_TEXT = (
41
+ "Hi, my name is Rabbani. "
42
+ "I am a software engineer at Gamana Solutions."
43
+ )
44
 
45
  if not os.path.exists(REFERENCE_VOICE_FILE):
46
  raise FileNotFoundError(
47
+ f"Reference voice file '{REFERENCE_VOICE_FILE}' not found."
 
 
48
  )
49
 
50
+
51
+ # ==========================================
52
+ # 3. INITIALIZE MODELS
53
+ # ==========================================
54
+
55
+ groq_client = None
56
  whisper_model = None
57
  f5tts = None
58
 
59
 
60
+ # ==========================================
61
+ # 4. GROQ CLIENT
62
+ # ==========================================
63
+
64
+ def get_groq_client():
65
+
66
  global groq_client
67
+
68
  if not GROQ_API_KEY:
69
+ raise ValueError(
70
+ "GROQ_API_KEY is not configured."
71
+ )
72
+
73
+ if groq_client is None:
74
+ groq_client = Groq(
75
+ api_key=GROQ_API_KEY
76
+ )
77
+
78
+ return groq_client
79
+
80
+
81
+ # ==========================================
82
+ # 5. REMOVE FILLER WORDS
83
+ # ==========================================
84
+
85
+ def remove_filler_words(text):
86
+
87
+ if not text:
88
+ return ""
89
+
90
+ # Convert multiple spaces into one
91
+ text = re.sub(r"\s+", " ", text).strip()
92
+
93
+ filler_patterns = [
94
+
95
+ r"\bumm+\b",
96
+ r"\bum+\b",
97
+ r"\buh+\b",
98
+ r"\bah+\b",
99
+
100
+ r"\byou know\b",
101
+ r"\bi mean\b",
102
+
103
+ r"\bactually\b",
104
+ r"\bbasically\b",
105
+
106
+ r"\blike\b",
107
+ r"\bsort of\b",
108
+ r"\bkind of\b",
109
+
110
+ r"\bwell\b",
111
+ ]
112
+
113
+ for pattern in filler_patterns:
114
+ text = re.sub(
115
+ pattern,
116
+ "",
117
+ text,
118
+ flags=re.IGNORECASE
119
+ )
120
+
121
+ # Remove repeated spaces
122
+ text = re.sub(r"\s+", " ", text)
123
+
124
+ # Fix spaces before punctuation
125
+ text = re.sub(r"\s+([,.!?])", r"\1", text)
126
+
127
+ return text.strip()
128
+
129
+
130
+ # ==========================================
131
+ # 6. GRAMMAR CORRECTION
132
+ # ==========================================
133
+
134
+ def correct_grammar(text):
135
+
136
+ if not text:
137
+ return ""
138
 
139
  try:
 
 
140
 
141
+ client = get_groq_client()
142
+
143
+ response = client.chat.completions.create(
144
+
145
+ model="llama-3.3-70b-versatile",
146
+
147
  messages=[
148
+
149
  {
150
  "role": "system",
151
+ "content": """
152
+ You are a speech-cleaning and grammar-correction system.
153
+
154
+ Your task is ONLY to clean the user's spoken sentence.
155
+
156
+ Rules:
157
+
158
+ 1. Remove filler words.
159
+ 2. Correct grammar.
160
+ 3. Correct sentence structure.
161
+ 4. Remove unnecessary repetitions.
162
+ 5. Keep the original meaning.
163
+ 6. Do not add new information.
164
+ 7. Do not answer the question.
165
+ 8. Do not explain the correction.
166
+ 9. Return ONLY the corrected sentence.
167
+ 10. Keep the sentence natural and conversational.
168
+
169
+ Example:
170
+
171
+ Input:
172
+ "Umm, I want to know like how Salesforce LWC works, you know?"
173
+
174
+ Output:
175
+ "I want to know how Salesforce LWC works."
176
+
177
+ Input:
178
+ "Actually umm I am working on Salesforce and I want to create ten records."
179
+
180
+ Output:
181
+ "I am working on Salesforce, and I want to create ten records."
182
+ """
183
  },
184
+
185
+ {
186
+ "role": "user",
187
+ "content": text
188
+ }
189
+
190
  ],
191
+
192
+ max_tokens=200,
193
+
194
+ temperature=0.1,
195
+
196
+ timeout=15
197
+ )
198
+
199
+ cleaned_text = (
200
+ response
201
+ .choices[0]
202
+ .message
203
+ .content
204
+ .strip()
205
  )
206
+
207
+ return cleaned_text
208
+
209
  except Exception as error:
210
+
211
+ print(
212
+ f"Grammar correction error: {error}"
213
+ )
214
+
215
+ # Fallback:
216
+ # Return filler-cleaned text
217
+ return remove_filler_words(text)
218
+
219
+
220
+ # ==========================================
221
+ # 7. WHISPER TRANSCRIPTION
222
+ # ==========================================
223
+
224
+ def transcribe_audio(audio_file):
225
+
226
+ global whisper_model
227
+
228
+ if whisper_model is None:
229
+
230
+ print("Loading Whisper tiny model...")
231
+
232
+ whisper_model = whisper.load_model(
233
+ "tiny"
234
+ )
235
+
236
+ result = whisper_model.transcribe(
237
+ audio_file,
238
+ fp16=False
239
+ )
240
+
241
+ text = result["text"].strip()
242
+
243
+ return text
244
+
245
+
246
+ # ==========================================
247
+ # 8. F5-TTS VOICE CLONING
248
+ # ==========================================
249
+
250
+ def clone_voice(text):
251
+
252
  global f5tts
253
+
254
+ if not text:
255
  return None
256
 
257
  try:
258
+
259
  if f5tts is None:
260
+
261
+ print("Loading F5-TTS...")
262
+
263
  f5tts = F5TTS()
264
 
265
+ # Temporary output file
266
+ with tempfile.TemporaryDirectory() as temp_dir:
267
+
268
+ output_file = os.path.join(
269
+ temp_dir,
270
+ "generated.wav"
271
+ )
272
+
273
  result = f5tts.infer(
274
+
275
  ref_file=REFERENCE_VOICE_FILE,
276
+
277
  ref_text=REFERENCE_TEXT,
278
+
279
  gen_text=text,
280
+
281
+ file_wave=output_file
282
  )
283
 
284
+ # ==================================
285
+ # Get audio from F5-TTS
286
+ # ==================================
287
+
288
  if isinstance(result, tuple):
289
+
290
+ audio_data = result[0]
291
+ sample_rate = result[1]
292
+
293
  else:
 
294
 
295
+ audio_data = result
296
+ sample_rate = 24000
297
+
298
+ # ==================================
299
+ # Convert Tensor β†’ NumPy
300
+ # ==================================
301
+
302
+ if hasattr(
303
+ audio_data,
304
+ "cpu"
305
+ ):
306
+
307
+ audio_data = (
308
+ audio_data
309
+ .cpu()
310
+ .numpy()
311
+ )
312
+
313
+ elif hasattr(
314
+ audio_data,
315
+ "numpy"
316
+ ):
317
+
318
+ audio_data = (
319
+ audio_data
320
+ .numpy()
321
+ )
322
 
323
+ audio_data = np.asarray(
324
+ audio_data
325
+ )
326
+
327
+ audio_data = np.squeeze(
328
+ audio_data
329
+ )
330
+
331
+ # Make independent copy
332
  audio_data = audio_data.copy()
333
 
334
+ # Temporary file is deleted here
335
+
336
+ return (
337
+ sample_rate,
338
+ audio_data
339
+ )
340
+
341
  except Exception as error:
342
+
343
+ print(
344
+ f"F5-TTS error: {error}"
345
+ )
346
+
347
  return None
348
 
349
 
350
+ # ==========================================
351
+ # 9. MAIN VOICE PIPELINE
352
+ # ==========================================
353
+
354
  @spaces.GPU
355
+ def voice_pipeline(audio_input):
356
+
357
+ if audio_input is None:
358
+
 
 
 
 
 
 
 
 
 
 
 
359
  return (
360
+ "Please record your voice.",
361
+ "",
362
+ None
 
363
  )
364
 
365
+ try:
 
 
366
 
367
+ # ==================================
368
+ # STEP 1: Whisper
369
+ # ==================================
370
+
371
+ print(
372
+ "\nSTEP 1: Transcribing audio..."
373
+ )
374
+
375
+ original_text = transcribe_audio(
376
+ audio_input
377
+ )
378
+
379
+ print(
380
+ "Original:",
381
+ original_text
382
+ )
383
+
384
+
385
+ # ==================================
386
+ # STEP 2: Remove filler words
387
+ # ==================================
388
+
389
+ print(
390
+ "\nSTEP 2: Removing filler words..."
391
+ )
392
+
393
+ filler_removed_text = (
394
+ remove_filler_words(
395
+ original_text
396
+ )
397
+ )
398
+
399
+ print(
400
+ "Filler removed:",
401
+ filler_removed_text
402
+ )
403
 
 
 
 
 
 
 
 
 
 
404
 
405
+ # ==================================
406
+ # STEP 3: Grammar correction
407
+ # ==================================
408
+
409
+ print(
410
+ "\nSTEP 3: Correcting grammar..."
411
+ )
412
+
413
+ corrected_text = (
414
+ correct_grammar(
415
+ filler_removed_text
416
  )
417
+ )
418
+
419
+ print(
420
+ "Corrected:",
421
+ corrected_text
422
+ )
423
+
424
+
425
+ # ==================================
426
+ # STEP 4: Voice cloning
427
+ # ==================================
428
+
429
+ print(
430
+ "\nSTEP 4: Cloning voice..."
431
+ )
432
+
433
+ generated_audio = clone_voice(
434
+ corrected_text
435
+ )
436
+
437
+
438
+ if generated_audio is None:
439
+
440
+ return (
441
+ original_text,
442
+ corrected_text,
443
+ None
444
  )
445
 
446
+
447
+ # ==================================
448
+ # STEP 5: Return everything
449
+ # ==================================
450
+
451
+ return (
452
+ original_text,
453
+ corrected_text,
454
+ generated_audio
455
+ )
456
+
457
+ except Exception as error:
458
+
459
+ print(
460
+ f"Pipeline error: {error}"
461
+ )
462
+
463
+ return (
464
+ f"Error: {error}",
465
+ "",
466
+ None
467
+ )
468
+
469
+
470
+ # ==========================================
471
+ # 10. GRADIO UI
472
+ # ==========================================
473
+
474
+ with gr.Blocks(
475
+ title="Rabbani Voice Cleaner & Cloner"
476
+ ) as interface:
477
+
478
+ gr.Markdown(
479
+ "# πŸŽ™οΈ Voice Cleaner + Grammar Correction + Voice Cloning"
480
+ )
481
+
482
+ gr.Markdown(
483
+ """
484
+ Speak naturally into the microphone.
485
+
486
+ The system will:
487
+
488
+ **Your Voice β†’ Whisper β†’ Remove Fillers β†’ Correct Grammar β†’ Clone Your Voice**
489
+ """
490
+ )
491
+
492
+
493
+ # ======================================
494
+ # INPUT
495
+ # ======================================
496
+
497
+ gr.Markdown(
498
+ "## 🎀 Step 1 β€” Speak"
499
+ )
500
+
501
+ audio_input = gr.Audio(
502
+ sources=["microphone"],
503
+ type="filepath",
504
+ label="Record Your Voice"
505
+ )
506
+
507
+
508
+ process_button = gr.Button(
509
+ "Process Voice",
510
+ variant="primary"
511
+ )
512
+
513
+
514
+ # ======================================
515
+ # ORIGINAL TRANSCRIPTION
516
+ # ======================================
517
+
518
+ gr.Markdown(
519
+ "## πŸ“ Step 2 β€” Whisper Transcription"
520
+ )
521
+
522
+ original_output = gr.Textbox(
523
+ label="Original Speech",
524
+ interactive=False
525
+ )
526
+
527
+
528
+ # ======================================
529
+ # CORRECTED TEXT
530
+ # ======================================
531
+
532
+ gr.Markdown(
533
+ "## ✨ Step 3 β€” Corrected Sentence"
534
+ )
535
+
536
+ corrected_output = gr.Textbox(
537
+ label="Clean + Grammatically Correct Sentence",
538
+ interactive=False
539
+ )
540
+
541
+
542
+ # ======================================
543
+ # CLONED AUDIO
544
+ # ======================================
545
+
546
+ gr.Markdown(
547
+ "## πŸ”Š Step 4 β€” Cloned Voice"
548
+ )
549
+
550
+ cloned_audio = gr.Audio(
551
+ label="Your Cloned Voice",
552
+ type="numpy",
553
+ interactive=False
554
  )
555
+
556
+
557
+ # ======================================
558
+ # BUTTON EVENT
559
+ # ======================================
560
+
561
+ process_button.click(
562
+
563
+ fn=voice_pipeline,
564
+
565
+ inputs=[
566
+ audio_input
567
+ ],
568
+
569
+ outputs=[
570
+ original_output,
571
+ corrected_output,
572
+ cloned_audio
573
+ ]
574
+ )
575
+
576
+
577
+ # ==========================================
578
+ # 11. START APPLICATION
579
+ # ==========================================
580
 
581
  interface.queue().launch()