testingfaces commited on
Commit
6322e69
Β·
verified Β·
1 Parent(s): 42b270e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -75
app.py CHANGED
@@ -1,45 +1,65 @@
1
  """
2
  ClearWave AI Studio
3
- Fixed app.py β€” compatible with:
4
- - New denoiser.py (Cleanvoice API)
5
- - Our transcriber.py (Groq Whisper large-v3)
6
- - Our translator.py (Helsinki-NLP + Google fallback)
7
-
8
- FIXES:
9
- - transcriber.transcribe() returns tuple (text, lang, method) β€” handled correctly
10
- - translator.translate() takes (text, src_lang, tgt_lang) β€” handled correctly
11
- - Added all languages not just English
12
- - Added microphone input tab
13
- - Added stats output
14
- - Added API routes for /api/health and /api/process-url
15
- - Background job system for long audio (no timeouts)
16
- - Format converter for .mpeg/.mp4/.m4a etc.
17
  """
18
 
19
  import gradio as gr
20
  import os
21
- import uuid
22
- import json
23
- import base64
24
  import tempfile
25
  import subprocess
26
  import threading
27
  import time
28
  import requests as req
29
- import numpy as np
30
  import logging
 
31
  from datetime import datetime
32
 
33
  logging.basicConfig(level=logging.INFO)
34
  logger = logging.getLogger(__name__)
35
 
36
- from denoiser import Denoiser
37
- from transcriber import Transcriber
38
- from translator import Translator
39
-
40
- denoiser = Denoiser()
41
- transcriber = Transcriber()
42
- translator = Translator()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  LANGUAGES = {
45
  "Auto Detect": "auto",
@@ -90,23 +110,33 @@ threading.Thread(target=_cleanup, daemon=True).start()
90
 
91
  # ══════════════════════════════════════════════════════════════════════
92
  # FORMAT CONVERTER
 
 
 
93
  # ══════════════════════════════════════════════════════════════════════
94
- def convert_to_wav(path):
 
 
 
95
  if not path:
96
  return path
97
  ext = os.path.splitext(path)[1].lower()
98
- if ext in [".wav", ".mp3", ".flac", ".ogg", ".aac"]:
99
- return path
 
100
  try:
101
  out = path + "_converted.wav"
102
- r = subprocess.run(["ffmpeg", "-y", "-i", path,
103
- "-ar", "16000", "-ac", "1",
104
- "-acodec", "pcm_s16le", out],
105
- capture_output=True)
 
 
106
  if r.returncode == 0 and os.path.exists(out):
 
107
  return out
108
  except Exception as e:
109
- logger.warning(f"Convert error: {e}")
110
  return path
111
 
112
  # ══════════════════════════════════════════════════════════════════════
@@ -126,8 +156,7 @@ def run_pipeline(audio_path, src_lang="auto", tgt_lang="te",
126
  try:
127
  # ── Step 1: Denoise ──────────────────────────────────────────
128
  yield progress(1, "⏳ Step 1/4 β€” Cleaning audio (Cleanvoice)...")
129
- # FIX: Pass correct parameter names to denoiser
130
- res = denoiser.process(
131
  audio_path, out_dir,
132
  remove_fillers=opt_fillers,
133
  remove_stutters=opt_stutters,
@@ -138,9 +167,7 @@ def run_pipeline(audio_path, src_lang="auto", tgt_lang="te",
138
 
139
  # ── Step 2: Transcribe ───────────────────────────────────────
140
  yield progress(2, "⏳ Step 2/4 β€” Transcribing...")
141
-
142
- # transcriber.transcribe() returns (text, detected_lang, method)
143
- t_result = transcriber.transcribe(clean, src_lang)
144
  if isinstance(t_result, tuple):
145
  transcript, detected_lang, t_method = t_result
146
  elif isinstance(t_result, dict):
@@ -152,9 +179,8 @@ def run_pipeline(audio_path, src_lang="auto", tgt_lang="te",
152
  detected_lang = src_lang
153
  t_method = "unknown"
154
 
155
- # Clean fillers from transcript text too
156
  if opt_fillers:
157
- transcript = denoiser.clean_transcript_fillers(transcript)
158
 
159
  # ── Step 3: Translate ────────────────────────────────────────
160
  translation = transcript
@@ -162,8 +188,7 @@ def run_pipeline(audio_path, src_lang="auto", tgt_lang="te",
162
 
163
  if tgt_lang != "auto" and detected_lang != tgt_lang:
164
  yield progress(3, "⏳ Step 3/4 β€” Translating...")
165
- # translator.translate() returns (translated_text, method)
166
- t_out = translator.translate(transcript, detected_lang, tgt_lang)
167
  if isinstance(t_out, tuple):
168
  translation, tl_method = t_out
169
  else:
@@ -172,13 +197,14 @@ def run_pipeline(audio_path, src_lang="auto", tgt_lang="te",
172
  # ── Step 4: Export MP3 ───────────────────────────────────────
173
  yield progress(4, "⏳ Step 4/4 β€” Exporting...")
174
  final_mp3 = os.path.join(out_dir, "ClearWave_Final.mp3")
175
- # FIX: Use 320kbps MP3 + preserve original sample rate (was 46kbps at 16kHz β€” very low quality)
176
- subprocess.run(["ffmpeg", "-y", "-i", clean,
177
- "-codec:a", "libmp3lame",
178
- "-b:a", "320k", # 320kbps β€” high quality
179
- "-ar", "48000", # restore 48kHz sample rate
180
- "-ac", "2", # restore stereo
181
- final_mp3], capture_output=True)
 
182
 
183
  audio_path_out = final_mp3 if os.path.exists(final_mp3) else clean
184
 
@@ -229,7 +255,6 @@ def process_audio_gradio(upload_file, mic_audio, in_lang_name,
229
  out_lang_name, opt_fillers, opt_stutters,
230
  opt_silences):
231
 
232
- # ── Resolve audio input ───────────────────────────────────────────
233
  audio_path = None
234
  if upload_file is not None:
235
  if isinstance(upload_file, dict):
@@ -254,10 +279,9 @@ def process_audio_gradio(upload_file, mic_audio, in_lang_name,
254
  "", "", None, "")
255
  return
256
 
257
- # ── Convert format ────────────────────────────────────────────────
258
- audio_path = convert_to_wav(audio_path)
259
 
260
- # ── Force booleans ────────────────────────────────────────────────
261
  opt_fillers = bool(opt_fillers)
262
  opt_stutters = bool(opt_stutters)
263
  opt_silences = bool(opt_silences)
@@ -268,7 +292,6 @@ def process_audio_gradio(upload_file, mic_audio, in_lang_name,
268
  print(f"[Gradio] src={src_lang} tgt={tgt_lang} "
269
  f"fillers={opt_fillers} stutters={opt_stutters}")
270
 
271
- # ── Background job ────────────────────────────────────────────────
272
  job_id = _new_job()
273
  threading.Thread(
274
  target=_bg_run,
@@ -277,7 +300,6 @@ def process_audio_gradio(upload_file, mic_audio, in_lang_name,
277
  daemon=True,
278
  ).start()
279
 
280
- # ── Poll ──────────────────────────────────────────────────────────
281
  while True:
282
  time.sleep(1)
283
  job = _get_job(job_id)
@@ -315,7 +337,6 @@ with gr.Blocks(title="ClearWave AI Studio") as demo:
315
  gr.Markdown("# πŸŽ™οΈ ClearWave AI Studio\n### Professional Audio Enhancement")
316
  with gr.Row():
317
  with gr.Column(scale=1):
318
-
319
  with gr.Tabs():
320
  with gr.Tab("πŸ“ Upload File"):
321
  audio_in = gr.File(
@@ -349,32 +370,24 @@ with gr.Blocks(title="ClearWave AI Studio") as demo:
349
  opt_s = gr.Checkbox(label="πŸ” Remove Stutters", value=True)
350
  opt_z = gr.Checkbox(label="🀫 Remove Silences", value=True)
351
 
352
- btn = gr.Button("πŸš€ Process Audio",
353
- variant="primary", size="lg")
354
- status_out = gr.Textbox(label="Status",
355
- interactive=False, lines=2)
356
 
357
  with gr.Column(scale=2):
358
  with gr.Tabs():
359
  with gr.Tab("πŸ“ Text"):
360
  with gr.Row():
361
- transcript_out = gr.Textbox(
362
- label="Transcript", lines=12)
363
- translation_out = gr.Textbox(
364
- label="Translation", lines=12)
365
  with gr.Tab("πŸ”Š Clean Audio"):
366
- audio_out = gr.Audio(
367
- label="Enhanced Audio", type="filepath")
368
  with gr.Tab("πŸ“Š Stats"):
369
- stats_out = gr.Textbox(
370
- label="Stats", lines=10, interactive=False)
371
 
372
  btn.click(
373
  fn=process_audio_gradio,
374
- inputs=[audio_in, mic_in, in_lang, out_lang,
375
- opt_f, opt_s, opt_z],
376
- outputs=[status_out, transcript_out, translation_out,
377
- audio_out, stats_out],
378
  )
379
 
380
  # ══════════════════════════════════════════════════════════════════════
@@ -431,7 +444,7 @@ async def api_process_url(request: _Req):
431
  _update_job(job_id, status="downloading",
432
  message=f"Downloading... {pct}%")
433
  tmp.close()
434
- audio_path = convert_to_wav(tmp_path)
435
  for _ in run_pipeline(audio_path, src_lang, tgt_lang,
436
  opt_fillers, opt_stutters, opt_silences,
437
  job_id=job_id):
@@ -445,7 +458,7 @@ async def api_process_url(request: _Req):
445
  finally:
446
  for p in [tmp_path, audio_path]:
447
  try:
448
- if p and os.path.exists(p):
449
  os.unlink(p)
450
  except Exception:
451
  pass
@@ -474,7 +487,7 @@ async def api_list_jobs():
474
  for k, v in _jobs.items()}
475
  return _JSON({"jobs": s, "total": len(s)})
476
 
477
- logger.info("βœ… Routes: /api/health, /api/process-url, /api/job/{id}")
478
 
479
  if __name__ == "__main__":
480
  demo.launch(ssr_mode=False)
 
1
  """
2
  ClearWave AI Studio
3
+
4
+ STARTUP FIX:
5
+ Root cause of "stuck at startup":
6
+ - Transcriber() was initializing Groq client at import time
7
+ - Translator() was firing a background thread that downloaded
8
+ Helsinki-NLP models (~600MB) from HuggingFace immediately at startup
9
+ - On HuggingFace Spaces, this causes the container health check to time out
10
+
11
+ Fix: All three services are now lazy-initialized β€” nothing downloads or
12
+ connects until the first actual audio is processed.
 
 
 
 
13
  """
14
 
15
  import gradio as gr
16
  import os
 
 
 
17
  import tempfile
18
  import subprocess
19
  import threading
20
  import time
21
  import requests as req
 
22
  import logging
23
+ import uuid
24
  from datetime import datetime
25
 
26
  logging.basicConfig(level=logging.INFO)
27
  logger = logging.getLogger(__name__)
28
 
29
+ # ══════════════════════════════════════════════════════════════════════
30
+ # LAZY SERVICE INIT β€” nothing loads at startup
31
+ # ══════════════════════════════════════════════════════════════════════
32
+ _denoiser = None
33
+ _transcriber = None
34
+ _translator = None
35
+ _init_lock = threading.Lock()
36
+
37
+ def get_denoiser():
38
+ global _denoiser
39
+ if _denoiser is None:
40
+ with _init_lock:
41
+ if _denoiser is None:
42
+ from denoiser import Denoiser
43
+ _denoiser = Denoiser()
44
+ return _denoiser
45
+
46
+ def get_transcriber():
47
+ global _transcriber
48
+ if _transcriber is None:
49
+ with _init_lock:
50
+ if _transcriber is None:
51
+ from transcriber import Transcriber
52
+ _transcriber = Transcriber()
53
+ return _transcriber
54
+
55
+ def get_translator():
56
+ global _translator
57
+ if _translator is None:
58
+ with _init_lock:
59
+ if _translator is None:
60
+ from translator import Translator
61
+ _translator = Translator()
62
+ return _translator
63
 
64
  LANGUAGES = {
65
  "Auto Detect": "auto",
 
110
 
111
  # ══════════════════════════════════════════════════════════════════════
112
  # FORMAT CONVERTER
113
+ # NOTE: This is only used to handle exotic formats (mp4, webm, etc.)
114
+ # that Cleanvoice might not accept. For common formats (mp3, wav,
115
+ # aac, flac) we pass the file directly to Cleanvoice untouched.
116
  # ══════════════════════════════════════════════════════════════════════
117
+ CLEANVOICE_NATIVE = {".mp3", ".wav", ".flac", ".ogg", ".aac", ".m4a", ".opus"}
118
+
119
+ def maybe_convert_for_cleanvoice(path):
120
+ """Only convert truly exotic formats. Leave common ones untouched."""
121
  if not path:
122
  return path
123
  ext = os.path.splitext(path)[1].lower()
124
+ if ext in CLEANVOICE_NATIVE:
125
+ return path # pass through untouched
126
+ # Exotic format (mp4, webm, mpeg, amr, wma...) β†’ convert to wav
127
  try:
128
  out = path + "_converted.wav"
129
+ r = subprocess.run([
130
+ "ffmpeg", "-y", "-i", path,
131
+ "-vn", # drop video if present
132
+ "-acodec", "pcm_s16le", # PCM wav β€” universally accepted
133
+ out
134
+ ], capture_output=True)
135
  if r.returncode == 0 and os.path.exists(out):
136
+ print(f"[app] Converted exotic format {ext} β†’ WAV")
137
  return out
138
  except Exception as e:
139
+ logger.warning(f"Format conversion failed: {e}")
140
  return path
141
 
142
  # ══════════════════════════════════════════════════════════════════════
 
156
  try:
157
  # ── Step 1: Denoise ──────────────────────────────────────────
158
  yield progress(1, "⏳ Step 1/4 β€” Cleaning audio (Cleanvoice)...")
159
+ res = get_denoiser().process(
 
160
  audio_path, out_dir,
161
  remove_fillers=opt_fillers,
162
  remove_stutters=opt_stutters,
 
167
 
168
  # ── Step 2: Transcribe ───────────────────────────────────────
169
  yield progress(2, "⏳ Step 2/4 β€” Transcribing...")
170
+ t_result = get_transcriber().transcribe(clean, src_lang)
 
 
171
  if isinstance(t_result, tuple):
172
  transcript, detected_lang, t_method = t_result
173
  elif isinstance(t_result, dict):
 
179
  detected_lang = src_lang
180
  t_method = "unknown"
181
 
 
182
  if opt_fillers:
183
+ transcript = get_denoiser().clean_transcript_fillers(transcript)
184
 
185
  # ── Step 3: Translate ────────────────────────────────────────
186
  translation = transcript
 
188
 
189
  if tgt_lang != "auto" and detected_lang != tgt_lang:
190
  yield progress(3, "⏳ Step 3/4 β€” Translating...")
191
+ t_out = get_translator().translate(transcript, detected_lang, tgt_lang)
 
192
  if isinstance(t_out, tuple):
193
  translation, tl_method = t_out
194
  else:
 
197
  # ── Step 4: Export MP3 ───────────────────────────────────────
198
  yield progress(4, "⏳ Step 4/4 β€” Exporting...")
199
  final_mp3 = os.path.join(out_dir, "ClearWave_Final.mp3")
200
+ subprocess.run([
201
+ "ffmpeg", "-y", "-i", clean,
202
+ "-codec:a", "libmp3lame",
203
+ "-b:a", "320k",
204
+ "-ar", "48000",
205
+ "-ac", "2",
206
+ final_mp3
207
+ ], capture_output=True)
208
 
209
  audio_path_out = final_mp3 if os.path.exists(final_mp3) else clean
210
 
 
255
  out_lang_name, opt_fillers, opt_stutters,
256
  opt_silences):
257
 
 
258
  audio_path = None
259
  if upload_file is not None:
260
  if isinstance(upload_file, dict):
 
279
  "", "", None, "")
280
  return
281
 
282
+ # Only convert truly exotic formats β€” leave mp3/wav/aac untouched
283
+ audio_path = maybe_convert_for_cleanvoice(audio_path)
284
 
 
285
  opt_fillers = bool(opt_fillers)
286
  opt_stutters = bool(opt_stutters)
287
  opt_silences = bool(opt_silences)
 
292
  print(f"[Gradio] src={src_lang} tgt={tgt_lang} "
293
  f"fillers={opt_fillers} stutters={opt_stutters}")
294
 
 
295
  job_id = _new_job()
296
  threading.Thread(
297
  target=_bg_run,
 
300
  daemon=True,
301
  ).start()
302
 
 
303
  while True:
304
  time.sleep(1)
305
  job = _get_job(job_id)
 
337
  gr.Markdown("# πŸŽ™οΈ ClearWave AI Studio\n### Professional Audio Enhancement")
338
  with gr.Row():
339
  with gr.Column(scale=1):
 
340
  with gr.Tabs():
341
  with gr.Tab("πŸ“ Upload File"):
342
  audio_in = gr.File(
 
370
  opt_s = gr.Checkbox(label="πŸ” Remove Stutters", value=True)
371
  opt_z = gr.Checkbox(label="🀫 Remove Silences", value=True)
372
 
373
+ btn = gr.Button("πŸš€ Process Audio", variant="primary", size="lg")
374
+ status_out = gr.Textbox(label="Status", interactive=False, lines=2)
 
 
375
 
376
  with gr.Column(scale=2):
377
  with gr.Tabs():
378
  with gr.Tab("πŸ“ Text"):
379
  with gr.Row():
380
+ transcript_out = gr.Textbox(label="Transcript", lines=12)
381
+ translation_out = gr.Textbox(label="Translation", lines=12)
 
 
382
  with gr.Tab("πŸ”Š Clean Audio"):
383
+ audio_out = gr.Audio(label="Enhanced Audio", type="filepath")
 
384
  with gr.Tab("πŸ“Š Stats"):
385
+ stats_out = gr.Textbox(label="Stats", lines=10, interactive=False)
 
386
 
387
  btn.click(
388
  fn=process_audio_gradio,
389
+ inputs=[audio_in, mic_in, in_lang, out_lang, opt_f, opt_s, opt_z],
390
+ outputs=[status_out, transcript_out, translation_out, audio_out, stats_out],
 
 
391
  )
392
 
393
  # ══════════════════════════════════════════════════════════════════════
 
444
  _update_job(job_id, status="downloading",
445
  message=f"Downloading... {pct}%")
446
  tmp.close()
447
+ audio_path = maybe_convert_for_cleanvoice(tmp_path)
448
  for _ in run_pipeline(audio_path, src_lang, tgt_lang,
449
  opt_fillers, opt_stutters, opt_silences,
450
  job_id=job_id):
 
458
  finally:
459
  for p in [tmp_path, audio_path]:
460
  try:
461
+ if p and p != tmp_path and os.path.exists(p):
462
  os.unlink(p)
463
  except Exception:
464
  pass
 
487
  for k, v in _jobs.items()}
488
  return _JSON({"jobs": s, "total": len(s)})
489
 
490
+ logger.info("βœ… ClearWave AI Studio ready β€” services lazy-loaded on first use")
491
 
492
  if __name__ == "__main__":
493
  demo.launch(ssr_mode=False)