Offex commited on
Commit
2cd407b
Β·
verified Β·
1 Parent(s): f3167ab

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +298 -114
app.py CHANGED
@@ -2,55 +2,39 @@
2
  import gradio as gr
3
  import os
4
  import uuid
5
- from pydub import AudioSegment
6
  from pydub.silence import split_on_silence
7
- import re
8
  import time
 
9
 
10
 
 
 
 
 
11
  def clean_file_name(file_path):
12
- # Get the base file name and extension
13
  file_name = os.path.basename(file_path)
14
  file_name, file_extension = os.path.splitext(file_name)
15
-
16
- # Replace non-alphanumeric characters with an underscore
17
  cleaned = re.sub(r'[^a-zA-Z\d]+', '_', file_name)
18
-
19
- # Remove any multiple underscores
20
- clean_file_name = re.sub(r'_+', '_', cleaned).strip('_')
21
-
22
- # Generate a random UUID for uniqueness
23
  random_uuid = uuid.uuid4().hex[:6]
24
-
25
- # Combine cleaned file name with the original extension
26
- clean_file_path = os.path.join(os.path.dirname(file_path), clean_file_name + f"_{random_uuid}" + file_extension)
27
-
28
  return clean_file_path
29
 
30
 
31
-
32
- def remove_silence(file_path, minimum_silence=50):
33
- sound = AudioSegment.from_file(file_path) # auto-detects format
34
- audio_chunks = split_on_silence(sound,
35
- min_silence_len=100,
36
- silence_thresh=-45,
37
- keep_silence=minimum_silence)
38
- combined = AudioSegment.empty()
39
- for chunk in audio_chunks:
40
- combined += chunk
41
- output_path=clean_file_name(file_path)
42
- combined.export(output_path) # format inferred from output file extension
43
- return output_path
44
-
45
-
46
-
47
  def calculate_duration(file_path):
48
  audio = AudioSegment.from_file(file_path)
49
- duration_seconds = len(audio) / 1000.0 # pydub uses milliseconds
50
- return duration_seconds
51
 
52
 
53
- # Some user uploads may trigger Hugging Face privacy policy checks, which can lead to account suspension
 
 
 
54
  FILE_TIMESTAMPS = {}
55
 
56
  def track_file(file_path):
@@ -60,60 +44,150 @@ def track_file(file_path):
60
  def cleanup_tracked_files(max_age_seconds=3600):
61
  now = time.time()
62
  to_delete = []
63
-
64
  for file_path, created_time in FILE_TIMESTAMPS.items():
65
  if now - created_time > max_age_seconds:
66
  if os.path.exists(file_path):
67
  try:
68
  os.remove(file_path)
69
- print(f"πŸ—’οΈ Deleted: {file_path}")
70
  except Exception as e:
71
- print(f"⚠️ Error deleting {file_path}: {e}")
72
-
73
  to_delete.append(file_path)
74
-
75
- # Remove from tracking
76
  for f in to_delete:
77
  FILE_TIMESTAMPS.pop(f, None)
78
 
79
 
80
-
81
  def start_cleanup_worker(interval=3600):
82
  def worker():
83
  while True:
84
- print("πŸ”„ Cleaning tracked files...")
85
  cleanup_tracked_files()
86
  time.sleep(interval)
87
-
88
  import threading
89
  threading.Thread(target=worker, daemon=True).start()
90
 
91
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  def process_audio(audio_file, seconds=0.05):
93
  if audio_file is None:
94
- return None, None, "No file uploaded"
95
-
96
  if not os.path.exists(audio_file):
97
- return None, None, "File not found"
98
 
99
  track_file(audio_file)
100
-
101
  keep_silence = int(seconds * 1000)
 
 
 
 
 
 
102
 
103
- output_audio_file = remove_silence(
104
- audio_file,
105
- minimum_silence=keep_silence
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  )
107
 
108
- track_file(output_audio_file)
 
109
 
110
- before = calculate_duration(audio_file)
111
- after = calculate_duration(output_audio_file)
 
 
112
 
113
- text = f"Old Duration: {before:.3f} seconds \nNew Duration: {after:.3f} seconds"
 
 
114
 
115
- return output_audio_file, output_audio_file, text
116
 
 
 
 
117
 
118
  def ui():
119
  theme = gr.themes.Soft(
@@ -121,43 +195,35 @@ def ui():
121
  )
122
 
123
  css = """
124
- .gradio-container {max-width: none !important;}
125
- .tab-content {padding: 20px;}
126
-
127
- /* Primary button - BLUE by default */
128
- button.primary {
129
- background-color: #2563eb !important;
130
- color: white !important;
131
- font-weight: 600;
132
- border: none !important;
133
- border-radius: 10px;
134
- padding: 12px 18px;
135
- font-size: 1.05em;
136
- }
137
-
138
- button.primary:hover {
139
- background-color: #1e40af !important;
140
- }
141
 
142
- /* RGB Title Animation */
143
  @keyframes rgb-glow {
144
- 0% { color: #ff0080; text-shadow: 0 0 10px #ff0080, 0 0 20px #ff0080; }
145
- 16% { color: #ff8c00; text-shadow: 0 0 10px #ff8c00, 0 0 20px #ff8c00; }
146
- 33% { color: #ffe600; text-shadow: 0 0 10px #ffe600, 0 0 20px #ffe600; }
147
- 50% { color: #00ff88; text-shadow: 0 0 10px #00ff88, 0 0 20px #00ff88; }
148
- 66% { color: #00c8ff; text-shadow: 0 0 10px #00c8ff, 0 0 20px #00c8ff; }
149
- 83% { color: #a855f7; text-shadow: 0 0 10px #a855f7, 0 0 20px #a855f7; }
150
- 100% { color: #ff0080; text-shadow: 0 0 10px #ff0080, 0 0 20px #ff0080; }
151
  }
152
-
153
  @keyframes rgb-border {
154
- 0% { border-color: #ff0080; box-shadow: 0 0 8px #ff0080; }
155
- 16% { border-color: #ff8c00; box-shadow: 0 0 8px #ff8c00; }
156
- 33% { border-color: #ffe600; box-shadow: 0 0 8px #ffe600; }
157
- 50% { border-color: #00ff88; box-shadow: 0 0 8px #00ff88; }
158
- 66% { border-color: #00c8ff; box-shadow: 0 0 8px #00c8ff; }
159
- 83% { border-color: #a855f7; box-shadow: 0 0 8px #a855f7; }
160
- 100% { border-color: #ff0080; box-shadow: 0 0 8px #ff0080; }
 
 
 
 
 
 
 
 
 
161
  }
162
 
163
  .rgb-title {
@@ -168,77 +234,195 @@ def ui():
168
 
169
  .made-by-badge {
170
  display: inline-block;
171
- padding: 5px 18px;
172
  border: 2px solid #00c8ff;
173
  border-radius: 50px;
174
- font-size: 0.85em;
175
- font-weight: 700;
176
- letter-spacing: 2px;
177
  text-transform: uppercase;
178
  animation: rgb-glow 3s linear infinite, rgb-border 3s linear infinite;
179
- background: rgba(0,0,0,0.05);
180
- margin-top: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  }
182
  """
183
 
184
  with gr.Blocks(theme=theme, css=css) as demo:
185
 
186
- # Header
 
 
187
  gr.HTML("""
188
- <div style="text-align:center; margin:20px auto; max-width:800px;">
189
- <h1 class="rgb-title" style="font-size:2.4em; margin-bottom:6px;">
190
  πŸ”‡ Remove Silence From Audio
191
  </h1>
192
- <p style="font-size:1.05em; color:#555; margin:0 0 10px;">
193
  Upload an MP3 or WAV file, and it will remove silent parts from it.
194
  </p>
195
- <p style="font-size:0.8em; color:#999; margin-bottom:14px;">
196
  ⚠️ Please don't upload copyrighted content β€” it can take this Space offline.
197
  </p>
198
- <div class="made-by-badge">Made by Deepu</div>
199
  </div>
200
  """)
201
 
 
202
  with gr.Row():
203
- # LEFT: Inputs
204
  with gr.Column(scale=1):
205
  audio_input = gr.Audio(
206
- label="Upload Audio",
207
  type="filepath",
208
  sources=["upload", "microphone"]
209
  )
210
-
211
  silence_threshold = gr.Number(
212
  label="Keep Silence Upto (In seconds)",
213
  value=0.05
214
  )
 
215
 
216
- submit_btn = gr.Button(
217
- "πŸ”‡ Remove Silence",
218
- variant="primary"
219
- )
220
-
221
- # RIGHT: Outputs
222
  with gr.Column(scale=1):
223
- audio_output = gr.Audio(label="Play Audio")
224
- file_output = gr.File(label="Download Audio File")
225
- duration_output = gr.Textbox(label="Duration")
226
 
227
  submit_btn.click(
228
  fn=process_audio,
229
  inputs=[audio_input, silence_threshold],
230
- outputs=[audio_output, file_output, duration_output]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  )
232
 
233
  return demo
234
 
 
 
 
 
 
235
  import click
 
236
  @click.command()
237
  @click.option("--debug", is_flag=True, default=False, help="Enable debug mode.")
238
  @click.option("--share", is_flag=True, default=False, help="Enable sharing of the interface.")
239
  def main(debug, share):
240
  start_cleanup_worker()
241
- demo=ui()
242
  demo.queue().launch(debug=debug, share=share)
 
243
  if __name__ == "__main__":
244
  main()
 
2
  import gradio as gr
3
  import os
4
  import uuid
5
+ from pydub import AudioSegment, effects
6
  from pydub.silence import split_on_silence
7
+ import re
8
  import time
9
+ import numpy as np
10
 
11
 
12
+ # ─────────────────────────────────────────────
13
+ # UTILS
14
+ # ─────────────────────────────────────────────
15
+
16
  def clean_file_name(file_path):
 
17
  file_name = os.path.basename(file_path)
18
  file_name, file_extension = os.path.splitext(file_name)
 
 
19
  cleaned = re.sub(r'[^a-zA-Z\d]+', '_', file_name)
20
+ clean_name = re.sub(r'_+', '_', cleaned).strip('_')
 
 
 
 
21
  random_uuid = uuid.uuid4().hex[:6]
22
+ clean_file_path = os.path.join(
23
+ os.path.dirname(file_path),
24
+ clean_name + f"_{random_uuid}" + file_extension
25
+ )
26
  return clean_file_path
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def calculate_duration(file_path):
30
  audio = AudioSegment.from_file(file_path)
31
+ return len(audio) / 1000.0
 
32
 
33
 
34
+ # ─────────────────────────────────────────────
35
+ # FILE TRACKING / CLEANUP
36
+ # ─────────────────────────────────────────────
37
+
38
  FILE_TIMESTAMPS = {}
39
 
40
  def track_file(file_path):
 
44
  def cleanup_tracked_files(max_age_seconds=3600):
45
  now = time.time()
46
  to_delete = []
 
47
  for file_path, created_time in FILE_TIMESTAMPS.items():
48
  if now - created_time > max_age_seconds:
49
  if os.path.exists(file_path):
50
  try:
51
  os.remove(file_path)
52
+ print(f"Deleted: {file_path}")
53
  except Exception as e:
54
+ print(f"Error deleting {file_path}: {e}")
 
55
  to_delete.append(file_path)
 
 
56
  for f in to_delete:
57
  FILE_TIMESTAMPS.pop(f, None)
58
 
59
 
 
60
  def start_cleanup_worker(interval=3600):
61
  def worker():
62
  while True:
63
+ print("Cleaning tracked files...")
64
  cleanup_tracked_files()
65
  time.sleep(interval)
 
66
  import threading
67
  threading.Thread(target=worker, daemon=True).start()
68
 
69
+
70
+ # ─────────────────────────────────────────────
71
+ # CORE FUNCTIONS
72
+ # ─────────────────────────────────────────────
73
+
74
+ def remove_silence(file_path, minimum_silence=50):
75
+ sound = AudioSegment.from_file(file_path)
76
+ audio_chunks = split_on_silence(
77
+ sound,
78
+ min_silence_len=100,
79
+ silence_thresh=-45,
80
+ keep_silence=minimum_silence
81
+ )
82
+ combined = AudioSegment.empty()
83
+ for chunk in audio_chunks:
84
+ combined += chunk
85
+ output_path = clean_file_name(file_path)
86
+ combined.export(output_path)
87
+ return output_path
88
+
89
+
90
  def process_audio(audio_file, seconds=0.05):
91
  if audio_file is None:
92
+ return None, None, "No file uploaded", None
 
93
  if not os.path.exists(audio_file):
94
+ return None, None, "File not found", None
95
 
96
  track_file(audio_file)
 
97
  keep_silence = int(seconds * 1000)
98
+ output_audio_file = remove_silence(audio_file, minimum_silence=keep_silence)
99
+ track_file(output_audio_file)
100
+
101
+ before = calculate_duration(audio_file)
102
+ after = calculate_duration(output_audio_file)
103
+ text = f"Old Duration: {before:.3f} seconds\nNew Duration: {after:.3f} seconds"
104
 
105
+ # 4th value stored in State so enhancer can access it
106
+ return output_audio_file, output_audio_file, text, output_audio_file
107
+
108
+
109
+ def enhance_audio(file_path):
110
+ """
111
+ Enhancement Pipeline:
112
+ 1. Noise Reduction (noisereduce, if installed β€” optional)
113
+ 2. High-Pass Filter (scipy, removes rumble below 80 Hz)
114
+ 3. Normalization (pydub effects.normalize)
115
+ Gracefully skips unavailable libraries.
116
+ """
117
+ if file_path is None:
118
+ return None, None, "No audio to enhance. Please run Remove Silence first."
119
+ if not os.path.exists(file_path):
120
+ return None, None, "File not found."
121
+
122
+ sound = AudioSegment.from_file(file_path)
123
+ sample_rate = sound.frame_rate
124
+ channels = sound.channels
125
+ sample_width = sound.sample_width
126
+
127
+ # Convert to float32 in range [-1, 1]
128
+ raw = np.array(sound.get_array_of_samples(), dtype=np.float32)
129
+ if sample_width == 1:
130
+ raw = (raw - 128.0) / 128.0
131
+ elif sample_width == 2:
132
+ raw = raw / 32768.0
133
+ elif sample_width == 4:
134
+ raw = raw / 2147483648.0
135
+
136
+ if channels == 2:
137
+ raw = raw.reshape((-1, 2))
138
+
139
+ # Step 1 β€” Noise Reduction
140
+ try:
141
+ import noisereduce as nr
142
+ if channels == 2:
143
+ raw[:, 0] = nr.reduce_noise(y=raw[:, 0], sr=sample_rate, prop_decrease=0.75)
144
+ raw[:, 1] = nr.reduce_noise(y=raw[:, 1], sr=sample_rate, prop_decrease=0.75)
145
+ else:
146
+ raw = nr.reduce_noise(y=raw, sr=sample_rate, prop_decrease=0.75)
147
+ except Exception:
148
+ pass
149
+
150
+ # Step 2 β€” High-Pass Filter (remove rumble below 80 Hz)
151
+ try:
152
+ from scipy.signal import butter, sosfilt
153
+ sos = butter(4, 80, btype='hp', fs=sample_rate, output='sos')
154
+ if channels == 2:
155
+ raw[:, 0] = sosfilt(sos, raw[:, 0])
156
+ raw[:, 1] = sosfilt(sos, raw[:, 1])
157
+ else:
158
+ raw = sosfilt(sos, raw)
159
+ except Exception:
160
+ pass
161
+
162
+ if channels == 2:
163
+ raw = raw.flatten()
164
+
165
+ raw = np.clip(raw, -1.0, 1.0)
166
+ out_samples = (raw * 32768.0).astype(np.int16)
167
+
168
+ enhanced_segment = AudioSegment(
169
+ out_samples.tobytes(),
170
+ frame_rate = sample_rate,
171
+ sample_width = 2,
172
+ channels = channels
173
  )
174
 
175
+ # Step 3 β€” Normalize volume
176
+ enhanced_segment = effects.normalize(enhanced_segment)
177
 
178
+ base = os.path.splitext(clean_file_name(file_path))[0]
179
+ output_path = base + "_enhanced.wav"
180
+ enhanced_segment.export(output_path, format="wav")
181
+ track_file(output_path)
182
 
183
+ duration = calculate_duration(output_path)
184
+ text = f"Enhanced Successfully!\nDuration: {duration:.3f} seconds"
185
+ return output_path, output_path, text
186
 
 
187
 
188
+ # ─────────────────────────────────────────────
189
+ # UI
190
+ # ─────────────────────────────────────────────
191
 
192
  def ui():
193
  theme = gr.themes.Soft(
 
195
  )
196
 
197
  css = """
198
+ .gradio-container { max-width: none !important; }
199
+ .tab-content { padding: 20px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
 
201
  @keyframes rgb-glow {
202
+ 0% { color: #ff0080; text-shadow: 0 0 12px #ff0080, 0 0 24px #ff0080; }
203
+ 16% { color: #ff8c00; text-shadow: 0 0 12px #ff8c00, 0 0 24px #ff8c00; }
204
+ 33% { color: #ffe600; text-shadow: 0 0 12px #ffe600, 0 0 24px #ffe600; }
205
+ 50% { color: #00ff88; text-shadow: 0 0 12px #00ff88, 0 0 24px #00ff88; }
206
+ 66% { color: #00c8ff; text-shadow: 0 0 12px #00c8ff, 0 0 24px #00c8ff; }
207
+ 83% { color: #a855f7; text-shadow: 0 0 12px #a855f7, 0 0 24px #a855f7; }
208
+ 100% { color: #ff0080; text-shadow: 0 0 12px #ff0080, 0 0 24px #ff0080; }
209
  }
 
210
  @keyframes rgb-border {
211
+ 0% { border-color: #ff0080; box-shadow: 0 0 10px #ff0080; }
212
+ 16% { border-color: #ff8c00; box-shadow: 0 0 10px #ff8c00; }
213
+ 33% { border-color: #ffe600; box-shadow: 0 0 10px #ffe600; }
214
+ 50% { border-color: #00ff88; box-shadow: 0 0 10px #00ff88; }
215
+ 66% { border-color: #00c8ff; box-shadow: 0 0 10px #00c8ff; }
216
+ 83% { border-color: #a855f7; box-shadow: 0 0 10px #a855f7; }
217
+ 100% { border-color: #ff0080; box-shadow: 0 0 10px #ff0080; }
218
+ }
219
+ @keyframes gradient-shift {
220
+ 0% { background-position: 0% 50%; }
221
+ 50% { background-position: 100% 50%; }
222
+ 100% { background-position: 0% 50%; }
223
+ }
224
+ @keyframes pulse-glow {
225
+ 0%, 100% { box-shadow: 0 0 15px rgba(0,200,255,0.45), 0 4px 20px rgba(0,0,0,0.3); }
226
+ 50% { box-shadow: 0 0 38px rgba(168,85,247,0.65), 0 4px 20px rgba(0,0,0,0.3); }
227
  }
228
 
229
  .rgb-title {
 
234
 
235
  .made-by-badge {
236
  display: inline-block;
237
+ padding: 6px 22px;
238
  border: 2px solid #00c8ff;
239
  border-radius: 50px;
240
+ font-size: 0.82em;
241
+ font-weight: 800;
242
+ letter-spacing: 3px;
243
  text-transform: uppercase;
244
  animation: rgb-glow 3s linear infinite, rgb-border 3s linear infinite;
245
+ background: rgba(0,0,0,0.04);
246
+ margin-top: 10px;
247
+ }
248
+
249
+ /* Remove Silence primary button */
250
+ button.primary {
251
+ background: linear-gradient(135deg, #2563eb, #1e40af) !important;
252
+ color: white !important;
253
+ font-weight: 700 !important;
254
+ border: none !important;
255
+ border-radius: 12px !important;
256
+ padding: 13px 20px !important;
257
+ font-size: 1.05em !important;
258
+ box-shadow: 0 4px 15px rgba(37,99,235,0.4) !important;
259
+ transition: all 0.3s ease !important;
260
+ }
261
+ button.primary:hover {
262
+ background: linear-gradient(135deg, #1d4ed8, #1e3a8a) !important;
263
+ box-shadow: 0 6px 25px rgba(37,99,235,0.65) !important;
264
+ transform: translateY(-1px) !important;
265
+ }
266
+
267
+ /* Animated divider */
268
+ .enhance-divider {
269
+ margin: 36px auto 22px;
270
+ max-width: 95%;
271
+ height: 2px;
272
+ background: linear-gradient(90deg, transparent, #00c8ff, #a855f7, #ff0080, transparent);
273
+ background-size: 200% 200%;
274
+ animation: gradient-shift 4s ease infinite;
275
+ border-radius: 2px;
276
+ }
277
+
278
+ /* Enhance header card */
279
+ .enhance-header { text-align: center; margin-bottom: 20px; }
280
+ .enhance-label {
281
+ display: inline-flex;
282
+ flex-direction: column;
283
+ align-items: center;
284
+ gap: 4px;
285
+ background: linear-gradient(135deg, #0f172a, #1e293b);
286
+ border: 2px solid #00c8ff;
287
+ border-radius: 16px;
288
+ padding: 12px 32px;
289
+ animation: rgb-border 3s linear infinite;
290
+ }
291
+ .etitle {
292
+ font-size: 1.2em;
293
+ font-weight: 800;
294
+ letter-spacing: 2px;
295
+ text-transform: uppercase;
296
+ animation: rgb-glow 3s linear infinite;
297
+ }
298
+ .esub {
299
+ font-size: 0.75em;
300
+ color: #94a3b8;
301
+ letter-spacing: 1px;
302
+ }
303
+
304
+ /* Enhance button */
305
+ .enhance-btn button {
306
+ background: linear-gradient(135deg, #0f172a, #1e293b, #0f172a) !important;
307
+ background-size: 200% 200% !important;
308
+ animation: gradient-shift 4s ease infinite, pulse-glow 2.5s ease-in-out infinite !important;
309
+ color: #00c8ff !important;
310
+ font-weight: 800 !important;
311
+ border: 2px solid #00c8ff !important;
312
+ border-radius: 12px !important;
313
+ padding: 13px 20px !important;
314
+ font-size: 1.05em !important;
315
+ letter-spacing: 1px !important;
316
+ text-transform: uppercase !important;
317
+ transition: color 0.3s ease, background 0.3s ease, transform 0.2s ease !important;
318
+ }
319
+ .enhance-btn button:hover {
320
+ color: #ffffff !important;
321
+ background: linear-gradient(135deg, #00c8ff, #a855f7) !important;
322
+ border-color: transparent !important;
323
+ transform: translateY(-2px) !important;
324
+ box-shadow: 0 8px 30px rgba(0,200,255,0.55) !important;
325
+ animation: none !important;
326
  }
327
  """
328
 
329
  with gr.Blocks(theme=theme, css=css) as demo:
330
 
331
+ processed_file_state = gr.State(value=None)
332
+
333
+ # ── HEADER ──
334
  gr.HTML("""
335
+ <div style="text-align:center; margin:24px auto 20px; max-width:820px;">
336
+ <h1 class="rgb-title" style="font-size:2.5em; margin-bottom:6px;">
337
  πŸ”‡ Remove Silence From Audio
338
  </h1>
339
+ <p style="font-size:1.05em; color:#555; margin:0 0 8px;">
340
  Upload an MP3 or WAV file, and it will remove silent parts from it.
341
  </p>
342
+ <p style="font-size:0.8em; color:#999; margin-bottom:16px;">
343
  ⚠️ Please don't upload copyrighted content β€” it can take this Space offline.
344
  </p>
345
+ <div class="made-by-badge">✦ Made by Deepu ✦</div>
346
  </div>
347
  """)
348
 
349
+ # ── REMOVE SILENCE ──
350
  with gr.Row():
 
351
  with gr.Column(scale=1):
352
  audio_input = gr.Audio(
353
+ label="πŸ“‚ Upload Audio",
354
  type="filepath",
355
  sources=["upload", "microphone"]
356
  )
 
357
  silence_threshold = gr.Number(
358
  label="Keep Silence Upto (In seconds)",
359
  value=0.05
360
  )
361
+ submit_btn = gr.Button("πŸ”‡ Remove Silence", variant="primary")
362
 
 
 
 
 
 
 
363
  with gr.Column(scale=1):
364
+ audio_output = gr.Audio(label="β–Ά Play Audio")
365
+ file_output = gr.File(label="⬇ Download Audio File")
366
+ duration_output = gr.Textbox(label="πŸ“Š Duration Info")
367
 
368
  submit_btn.click(
369
  fn=process_audio,
370
  inputs=[audio_input, silence_threshold],
371
+ outputs=[audio_output, file_output, duration_output, processed_file_state]
372
+ )
373
+
374
+ # ── DIVIDER ──
375
+ gr.HTML('<div class="enhance-divider"></div>')
376
+
377
+ # ── ENHANCE HEADER ──
378
+ gr.HTML("""
379
+ <div class="enhance-header">
380
+ <div class="enhance-label">
381
+ <span class="etitle">✨ Audio Enhancer</span>
382
+ <span class="esub">Noise Reduction &nbsp;Β·&nbsp; High-Pass Filter &nbsp;Β·&nbsp; Volume Normalize</span>
383
+ </div>
384
+ </div>
385
+ """)
386
+
387
+ # ── ENHANCE SECTION ──
388
+ with gr.Row():
389
+ with gr.Column(scale=1):
390
+ enhance_btn = gr.Button(
391
+ "✨ Enhance Audio",
392
+ elem_classes=["enhance-btn"]
393
+ )
394
+ enhance_status = gr.Textbox(
395
+ label="⚑ Status",
396
+ interactive=False,
397
+ placeholder="Run Remove Silence first, then click Enhance..."
398
+ )
399
+
400
+ with gr.Column(scale=1):
401
+ enhanced_audio_output = gr.Audio(label="β–Ά Enhanced Audio")
402
+ enhanced_file_output = gr.File(label="⬇ Download Enhanced Audio")
403
+
404
+ enhance_btn.click(
405
+ fn=enhance_audio,
406
+ inputs=[processed_file_state],
407
+ outputs=[enhanced_audio_output, enhanced_file_output, enhance_status]
408
  )
409
 
410
  return demo
411
 
412
+
413
+ # ─────────────────────────────────────────────
414
+ # ENTRY POINT
415
+ # ─────────────────────────────────────────────
416
+
417
  import click
418
+
419
  @click.command()
420
  @click.option("--debug", is_flag=True, default=False, help="Enable debug mode.")
421
  @click.option("--share", is_flag=True, default=False, help="Enable sharing of the interface.")
422
  def main(debug, share):
423
  start_cleanup_worker()
424
+ demo = ui()
425
  demo.queue().launch(debug=debug, share=share)
426
+
427
  if __name__ == "__main__":
428
  main()