Files changed (5) hide show
  1. README.md +1 -1
  2. app.py +63 -181
  3. new_ui.py +0 -532
  4. packages.txt +0 -1
  5. requirements.txt +1 -5
README.md CHANGED
@@ -4,7 +4,7 @@ emoji: 🦀
4
  colorFrom: pink
5
  colorTo: gray
6
  sdk: gradio
7
- sdk_version: 5.25.0
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
 
4
  colorFrom: pink
5
  colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 5.25.1
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
app.py CHANGED
@@ -1,205 +1,82 @@
 
1
  import gradio as gr
2
  import os
3
  import uuid
4
  from pydub import AudioSegment
5
  from pydub.silence import split_on_silence
6
  import re
7
- import time
8
- import subprocess
9
- import threading
10
-
11
- # gr.close_all()
12
 
13
  def clean_file_name(file_path):
 
14
  file_name = os.path.basename(file_path)
15
  file_name, file_extension = os.path.splitext(file_name)
16
 
 
17
  cleaned = re.sub(r'[^a-zA-Z\d]+', '_', file_name)
18
 
 
19
  clean_file_name = re.sub(r'_+', '_', cleaned).strip('_')
20
 
21
- if clean_file_name.endswith('_tmp'):
22
- clean_file_name = clean_file_name[:-4]
23
-
24
  random_uuid = uuid.uuid4().hex[:6]
25
 
26
- clean_file_path = os.path.join(
27
- os.path.dirname(file_path),
28
- f"{clean_file_name}_{random_uuid}{file_extension}"
29
- )
30
 
31
  return clean_file_path
32
 
33
 
34
- # def remove_silence(file_path, minimum_silence=50):
35
- # sound = AudioSegment.from_file(file_path) # auto-detects format
36
- # audio_chunks = split_on_silence(sound,
37
- # min_silence_len=100,
38
- # silence_thresh=-45,
39
- # keep_silence=minimum_silence)
40
- # combined = AudioSegment.empty()
41
- # for chunk in audio_chunks:
42
- # combined += chunk
43
- # output_path=clean_file_name(file_path)
44
- # combined.export(output_path)
45
- # return output_path
46
-
47
 
48
  def remove_silence(file_path, minimum_silence=50):
49
- sound = AudioSegment.from_file(file_path)
50
-
51
- # Try splitting with default -45 dBFS
52
- audio_chunks = split_on_silence(
53
- sound,
54
- min_silence_len=100,
55
- silence_thresh=-45,
56
- keep_silence=minimum_silence
57
- )
58
-
59
- # If no chunks were extracted (e.g. whole file is quieter than -45dBFS)
60
- # retry with a dynamic threshold relative to the audio's actual loudness
61
- if not audio_chunks:
62
- dynamic_thresh = sound.dBFS - 16
63
- audio_chunks = split_on_silence(
64
- sound,
65
- min_silence_len=100,
66
- silence_thresh=dynamic_thresh,
67
- keep_silence=minimum_silence
68
- )
69
-
70
  combined = AudioSegment.empty()
71
  for chunk in audio_chunks:
72
  combined += chunk
73
-
74
- if len(combined) == 0:
75
- combined = sound
76
-
77
- output_path = clean_file_name(file_path)
78
-
79
- ext = os.path.splitext(output_path)[1].lower().replace('.', '')
80
- if not ext:
81
- ext = "wav"
82
- output_path += ".wav"
83
-
84
- combined.export(output_path, format=ext)
85
  return output_path
86
 
 
 
87
  def calculate_duration(file_path):
88
  audio = AudioSegment.from_file(file_path)
89
- duration_seconds = len(audio) / 1000.0
90
  return duration_seconds
91
 
92
- FILE_TIMESTAMPS = {}
93
-
94
- def track_file(file_path):
95
- FILE_TIMESTAMPS[file_path] = time.time()
96
-
97
- def cleanup_tracked_files(max_age_seconds=3600):
98
- now = time.time()
99
- to_delete = []
100
-
101
- for file_path, created_time in list(FILE_TIMESTAMPS.items()):
102
- if now - created_time > max_age_seconds:
103
- if os.path.exists(file_path):
104
- try:
105
- os.remove(file_path)
106
- print(f"🗑️ Deleted: {file_path}")
107
- except Exception as e:
108
- print(f"⚠️ Error deleting {file_path}: {e}")
109
-
110
- to_delete.append(file_path)
111
-
112
- for f in to_delete:
113
- FILE_TIMESTAMPS.pop(f, None)
114
-
115
-
116
- _CLEANUP_STARTED = False
117
-
118
- def start_cleanup_worker(interval=3600):
119
- global _CLEANUP_STARTED
120
- if _CLEANUP_STARTED:
121
- return
122
- _CLEANUP_STARTED = True
123
-
124
- def worker():
125
- while True:
126
- cleanup_tracked_files()
127
- time.sleep(interval)
128
-
129
- threading.Thread(target=worker, daemon=True).start()
130
-
131
- def convert_to_wav(audio_path):
132
- if not os.path.isfile(audio_path):
133
- return None
134
-
135
- file_name = os.path.splitext(os.path.basename(audio_path))[0]
136
- clean_name = re.sub(r'[^a-zA-Z0-9]+', '_', file_name)
137
- clean_name = re.sub(r'_+', '_', clean_name).strip('_')
138
-
139
- wav_path = os.path.join(
140
- os.path.dirname(audio_path),
141
- f"{clean_name}_tmp.wav"
142
- )
143
-
144
- try:
145
- subprocess.run(
146
- [
147
- "ffmpeg",
148
- "-y",
149
- "-i", audio_path,
150
- wav_path
151
- ],
152
- stdout=subprocess.DEVNULL,
153
- stderr=subprocess.DEVNULL,
154
- check=True
155
- )
156
-
157
- if os.path.isfile(wav_path) and os.path.getsize(wav_path) > 0:
158
- return wav_path
159
-
160
- except Exception as e:
161
- print(f"⚠️ FFmpeg conversion error: {e}")
162
- pass
163
-
164
- return None
165
 
166
  def process_audio(audio_file, seconds=0.05):
167
- if audio_file is None:
168
- return None, None, "No file uploaded"
169
-
170
- if not os.path.exists(audio_file):
171
- return None, None, "File not found"
172
-
173
- track_file(audio_file)
174
-
175
- converted_audio = convert_to_wav(audio_file)
176
-
177
- if converted_audio:
178
- track_file(converted_audio)
179
- audio_file = converted_audio
180
- else:
181
- return None, None, "Invalid file format or conversion failed"
182
-
183
  keep_silence = int(seconds * 1000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
 
185
- try:
186
- before = calculate_duration(audio_file)
187
-
188
- output_audio_file = remove_silence(
189
- audio_file,
190
- minimum_silence=keep_silence
191
- )
192
-
193
- track_file(output_audio_file)
194
-
195
- after = calculate_duration(output_audio_file)
196
- text = f"Old Duration: {before:.3f} Seconds \nNew Duration: {after:.3f} Seconds"
197
-
198
- return output_audio_file, output_audio_file, text
199
-
200
- except Exception as e:
201
- print(f"⚠️ Error processing audio: {e}")
202
- return None, None, f"An error occurred during processing: {str(e)}"
203
 
204
  def ui():
205
  theme = gr.themes.Soft(
@@ -228,6 +105,7 @@ def ui():
228
 
229
  with gr.Blocks(theme=theme, css=css) as demo:
230
 
 
231
  gr.HTML("""
232
  <div style="text-align:center; margin:20px auto; max-width:800px;">
233
  <h1 style="font-size:2.4em; margin-bottom:6px;">
@@ -235,21 +113,20 @@ def ui():
235
  </h1>
236
 
237
  <p style="font-size:1.05em; color:#555; margin:0 0 10px;">
238
- Upload an Audio file, and it will remove silent parts from it.
239
- </p>
240
- <p style="font-size:0.8em; color:#999;">
241
- ⚠️ Please don’t upload copyrighted content — it can take this Space offline.
242
  </p>
 
243
  <p style="font-size:0.9em; color:#777;">
244
- Install locally on your computer, enjoy unlimited runs with no waiting queue
245
- <a href="https://github.com/NeuralFalconYT/Remove-Silence-From-Audio" target="_blank" style="text-decoration:none;">
246
- Download Link
247
  </a>
248
  </p>
249
  </div>
250
  """)
251
-
252
  with gr.Row():
 
253
  with gr.Column(scale=1):
254
  audio_input = gr.Audio(
255
  label="Upload Audio",
@@ -267,21 +144,26 @@ def ui():
267
  variant="primary"
268
  )
269
 
 
270
  with gr.Column(scale=1):
271
  audio_output = gr.Audio(label="Play Audio")
272
  file_output = gr.File(label="Download Audio File")
273
- duration_output = gr.Textbox(label="Duration", lines=2)
274
 
275
  submit_btn.click(
276
- fn=process_audio,
277
  inputs=[audio_input, silence_threshold],
278
  outputs=[audio_output, file_output, duration_output]
279
  )
280
 
281
  return demo
282
 
283
- start_cleanup_worker()
284
-
285
- demo= ui()
286
- demo.queue().launch()
287
- # demo.queue().launch(debug=True, share=True)
 
 
 
 
 
1
+
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
 
9
  def clean_file_name(file_path):
10
+ # Get the base file name and extension
11
  file_name = os.path.basename(file_path)
12
  file_name, file_extension = os.path.splitext(file_name)
13
 
14
+ # Replace non-alphanumeric characters with an underscore
15
  cleaned = re.sub(r'[^a-zA-Z\d]+', '_', file_name)
16
 
17
+ # Remove any multiple underscores
18
  clean_file_name = re.sub(r'_+', '_', cleaned).strip('_')
19
 
20
+ # Generate a random UUID for uniqueness
 
 
21
  random_uuid = uuid.uuid4().hex[:6]
22
 
23
+ # Combine cleaned file name with the original extension
24
+ clean_file_path = os.path.join(os.path.dirname(file_path), clean_file_name + f"_{random_uuid}" + file_extension)
 
 
25
 
26
  return clean_file_path
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  def remove_silence(file_path, minimum_silence=50):
31
+ sound = AudioSegment.from_file(file_path) # auto-detects format
32
+ audio_chunks = split_on_silence(sound,
33
+ min_silence_len=100,
34
+ silence_thresh=-45,
35
+ keep_silence=minimum_silence)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  combined = AudioSegment.empty()
37
  for chunk in audio_chunks:
38
  combined += chunk
39
+ output_path=clean_file_name(file_path)
40
+ combined.export(output_path) # format inferred from output file extension
 
 
 
 
 
 
 
 
 
 
41
  return output_path
42
 
43
+
44
+
45
  def calculate_duration(file_path):
46
  audio = AudioSegment.from_file(file_path)
47
+ duration_seconds = len(audio) / 1000.0 # pydub uses milliseconds
48
  return duration_seconds
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  def process_audio(audio_file, seconds=0.05):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  keep_silence = int(seconds * 1000)
53
+ output_audio_file = remove_silence(audio_file, minimum_silence=keep_silence)
54
+ before = calculate_duration(audio_file)
55
+ after = calculate_duration(output_audio_file)
56
+ text = f"Old Duration: {before:.3f} seconds \nNew Duration: {after:.3f} seconds"
57
+ return output_audio_file, output_audio_file, text
58
+
59
+ # def ui():
60
+ # theme = gr.themes.Soft(font=[gr.themes.GoogleFont("Source Sans Pro"), "Arial", "sans-serif"])
61
+ # css = ".gradio-container {max-width: none !important;} .tab-content {padding: 20px;}"
62
+ # demo = gr.Interface(
63
+ # fn=process_audio,
64
+ # inputs=[
65
+ # gr.Audio(label="Upload Audio", type="filepath", sources=['upload', 'microphone']),
66
+ # gr.Number(label="Keep Silence Upto (In seconds)", value=0.05)
67
+ # ],
68
+ # outputs=[
69
+ # gr.Audio(label="Play Audio"),
70
+ # gr.File(label="Download Audio File"),
71
+ # gr.Textbox(label="Duration")
72
+ # ],
73
+ # title="Remove Silence From Audio",
74
+ # description="Upload an MP3 or WAV file, and it will remove silent parts from it.",
75
+ # theme=theme,
76
+ # css=css,
77
+ # )
78
+ # return demo
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
  def ui():
82
  theme = gr.themes.Soft(
 
105
 
106
  with gr.Blocks(theme=theme, css=css) as demo:
107
 
108
+ # Header
109
  gr.HTML("""
110
  <div style="text-align:center; margin:20px auto; max-width:800px;">
111
  <h1 style="font-size:2.4em; margin-bottom:6px;">
 
113
  </h1>
114
 
115
  <p style="font-size:1.05em; color:#555; margin:0 0 10px;">
116
+ Upload an MP3 or WAV file, and it will remove silent parts from it.
 
 
 
117
  </p>
118
+
119
  <p style="font-size:0.9em; color:#777;">
120
+ Made by
121
+ <a href="https://github.com/NeuralFalconYT" target="_blank" style="text-decoration:none;">
122
+ NeuralFalconYT
123
  </a>
124
  </p>
125
  </div>
126
  """)
127
+
128
  with gr.Row():
129
+ # LEFT: Inputs
130
  with gr.Column(scale=1):
131
  audio_input = gr.Audio(
132
  label="Upload Audio",
 
144
  variant="primary"
145
  )
146
 
147
+ # RIGHT: Outputs
148
  with gr.Column(scale=1):
149
  audio_output = gr.Audio(label="Play Audio")
150
  file_output = gr.File(label="Download Audio File")
151
+ duration_output = gr.Textbox(label="Duration")
152
 
153
  submit_btn.click(
154
+ fn=process_audio, # <-- your function
155
  inputs=[audio_input, silence_threshold],
156
  outputs=[audio_output, file_output, duration_output]
157
  )
158
 
159
  return demo
160
 
161
+ import click
162
+ @click.command()
163
+ @click.option("--debug", is_flag=True, default=False, help="Enable debug mode.")
164
+ @click.option("--share", is_flag=True, default=False, help="Enable sharing of the interface.")
165
+ def main(debug, share):
166
+ demo=ui()
167
+ demo.queue().launch(debug=debug, share=share)
168
+ if __name__ == "__main__":
169
+ main()
new_ui.py DELETED
@@ -1,532 +0,0 @@
1
- import gradio as gr
2
- import os
3
- import uuid
4
- from pydub import AudioSegment
5
- from pydub.silence import split_on_silence
6
- import re
7
- import time
8
- import subprocess
9
- import threading
10
-
11
- # ─── File Helpers ─────────────────────────────────────────────────────────────
12
-
13
- def clean_file_name(file_path):
14
- file_name = os.path.basename(file_path)
15
- file_name, file_extension = os.path.splitext(file_name)
16
- cleaned = re.sub(r'[^a-zA-Z\d]+', '_', file_name)
17
- clean_name = re.sub(r'_+', '_', cleaned).strip('_')
18
- if clean_name.endswith('_tmp'):
19
- clean_name = clean_name[:-4]
20
- random_uuid = uuid.uuid4().hex[:6]
21
- clean_file_path = os.path.join(
22
- os.path.dirname(file_path),
23
- f"{clean_name}_{random_uuid}{file_extension}"
24
- )
25
- return clean_file_path
26
-
27
- def calculate_duration(file_path):
28
- audio = AudioSegment.from_file(file_path)
29
- return len(audio) / 1000.0
30
-
31
-
32
- # ─── File Tracking & Cleanup ──────────────────────────────────────────────────
33
-
34
- FILE_TIMESTAMPS = {}
35
-
36
- def track_file(file_path):
37
- FILE_TIMESTAMPS[file_path] = time.time()
38
-
39
- def cleanup_tracked_files(max_age_seconds=3600):
40
- now = time.time()
41
- to_delete = []
42
- for file_path, created_time in list(FILE_TIMESTAMPS.items()):
43
- if now - created_time > max_age_seconds:
44
- if os.path.exists(file_path):
45
- try:
46
- os.remove(file_path)
47
- except Exception as e:
48
- pass
49
- to_delete.append(file_path)
50
- for f in to_delete:
51
- FILE_TIMESTAMPS.pop(f, None)
52
-
53
- _CLEANUP_STARTED = False
54
-
55
- def start_cleanup_worker(interval=3600):
56
- global _CLEANUP_STARTED
57
- if _CLEANUP_STARTED:
58
- return
59
- _CLEANUP_STARTED = True
60
- def worker():
61
- while True:
62
- cleanup_tracked_files()
63
- time.sleep(interval)
64
- threading.Thread(target=worker, daemon=True).start()
65
-
66
-
67
- # ─── Audio Conversion ─────────────────────────────────────────────────────────
68
-
69
- def convert_to_wav(audio_path):
70
- if not os.path.isfile(audio_path):
71
- return None
72
-
73
- # Get extension
74
- ext = os.path.splitext(audio_path)[1].lower()
75
-
76
- # If already mp3 or wav, return original file
77
- if ext in [".mp3", ".wav"]:
78
- return audio_path
79
-
80
- # Clean filename
81
- file_name = os.path.splitext(os.path.basename(audio_path))[0]
82
- clean_name = re.sub(r'[^a-zA-Z0-9]+', '_', file_name)
83
- clean_name = re.sub(r'_+', '_', clean_name).strip('_')
84
-
85
- # Output wav path
86
- wav_path = os.path.join(
87
- os.path.dirname(audio_path),
88
- f"{clean_name}_tmp.wav"
89
- )
90
-
91
- try:
92
- subprocess.run(
93
- [
94
- "ffmpeg",
95
- "-y",
96
- "-i", audio_path,
97
- "-ar", "16000",
98
- "-ac", "1",
99
- wav_path
100
- ],
101
- stdout=subprocess.DEVNULL,
102
- stderr=subprocess.DEVNULL,
103
- check=True
104
- )
105
-
106
- if os.path.isfile(wav_path) and os.path.getsize(wav_path) > 0:
107
- return wav_path
108
-
109
- except Exception:
110
- pass
111
-
112
- return None
113
-
114
-
115
- # ───pydub ─────────────────────────────────────────────
116
-
117
-
118
- # def remove_silence_pydub(file_path, minimum_silence=50):
119
- # sound = AudioSegment.from_file(file_path) # auto-detects format
120
- # audio_chunks = split_on_silence(sound,
121
- # min_silence_len=100,
122
- # silence_thresh=-45,
123
- # keep_silence=minimum_silence)
124
- # combined = AudioSegment.empty()
125
- # for chunk in audio_chunks:
126
- # combined += chunk
127
- # output_path=clean_file_name(file_path)
128
- # combined.export(output_path)
129
- # return output_path
130
-
131
-
132
- def remove_silence_pydub(file_path, minimum_silence=50):
133
- sound = AudioSegment.from_file(file_path)
134
-
135
- # Try splitting with default -45 dBFS
136
- audio_chunks = split_on_silence(
137
- sound,
138
- min_silence_len=100,
139
- silence_thresh=-45,
140
- keep_silence=minimum_silence
141
- )
142
-
143
- # If no chunks were extracted (e.g. whole file is quieter than -45dBFS)
144
- # retry with a dynamic threshold relative to the audio's actual loudness
145
- if not audio_chunks:
146
- dynamic_thresh = sound.dBFS - 16
147
- audio_chunks = split_on_silence(
148
- sound,
149
- min_silence_len=100,
150
- silence_thresh=dynamic_thresh,
151
- keep_silence=minimum_silence
152
- )
153
-
154
- combined = AudioSegment.empty()
155
- for chunk in audio_chunks:
156
- combined += chunk
157
-
158
- if len(combined) == 0:
159
- combined = sound
160
-
161
- output_path = clean_file_name(file_path)
162
-
163
- ext = os.path.splitext(output_path)[1].lower().replace('.', '')
164
- if not ext:
165
- ext = "wav"
166
- output_path += ".wav"
167
-
168
- combined.export(output_path, format=ext)
169
- return output_path
170
-
171
-
172
-
173
- # ─── Main Processing ──────────────────────────────────────────────────────────
174
-
175
- def process_audio(audio_file, seconds_float):
176
- if audio_file is None:
177
- return None, None, ""
178
- if not os.path.exists(audio_file):
179
- return None, None, ""
180
-
181
- try:
182
- seconds = max(0.0, float(seconds_float))
183
- except ValueError:
184
- seconds = 0.05
185
-
186
- track_file(audio_file)
187
- converted_audio = convert_to_wav(audio_file)
188
-
189
- if converted_audio:
190
- track_file(converted_audio)
191
- audio_file = converted_audio
192
- else:
193
- return None, None, "Invalid file format or conversion failed."
194
-
195
- keep_silence = int(seconds * 1000)
196
-
197
- try:
198
- before = calculate_duration(audio_file)
199
-
200
- # Process strictly with PyDub
201
- output_audio_file = remove_silence_pydub(
202
- audio_file,
203
- minimum_silence=keep_silence
204
- )
205
-
206
- track_file(output_audio_file)
207
- after = calculate_duration(output_audio_file)
208
-
209
- removed = before - after
210
- percent = (removed / before * 100) if before > 0 else 0
211
-
212
- def fmt(s):
213
- m = int(s // 60)
214
- sec = s % 60
215
- if m > 0:
216
- return f"{m}m {sec:.1f}s"
217
- return f"{sec:.2f}s"
218
-
219
- mode_label = ""
220
-
221
- # Sleek, Dark-Themed Result Card
222
- result_html = f"""
223
- <div style="margin-top: 15px; background: #1c1c1c; border: 1px solid #333; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 10px rgba(0,0,0,0.5);">
224
- <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333;">
225
-
226
- <div style="flex: 1; padding: 18px 10px; text-align: center; border-right: 1px solid #333;">
227
- <div style="font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">Original</div>
228
- <div style="font-size: 22px; font-weight: 700; color: #f4f4f5; font-family: 'Inter', sans-serif;">{fmt(before)}</div>
229
- </div>
230
-
231
- <div style="flex: 1; padding: 18px 10px; text-align: center; border-right: 1px solid #333; background: #222;">
232
- <div style="font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">New</div>
233
- <div style="font-size: 22px; font-weight: 700; color: #3b82f6; font-family: 'Inter', sans-serif;">{fmt(after)}</div>
234
- </div>
235
-
236
- <div style="flex: 1; padding: 18px 10px; text-align: center; background: #1c1c1c;">
237
- <div style="font-size: 11px; color: #888; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px;">Removed</div>
238
- <div style="font-size: 22px; font-weight: 700; color: #ef4444; font-family: 'Inter', sans-serif;">{percent:.1f}%</div>
239
- <div style="font-size: 12px; color: #666; margin-top: 4px; font-weight: 500;">{fmt(removed)}</div>
240
- </div>
241
-
242
- </div>
243
- """
244
- return output_audio_file, output_audio_file, result_html
245
-
246
- except Exception as e:
247
- return None, None, f"<p style='color:#ef4444; font-family:Inter,sans-serif; font-size:13px; margin-top:10px; padding:12px; background:#2a1111; border-radius:8px; border:1px solid #5a1a1a;'>Error: {str(e)}</p>"
248
-
249
-
250
- # -----------------------------
251
- # CSS FOR LAYOUT & THEMING
252
- # -----------------------------
253
-
254
- css = """
255
- /* APP & MAIN */
256
- body, html {
257
- background: #171717 !important;
258
- font-family: 'Inter', sans-serif !important;
259
- color: white !important;
260
- margin: 0;
261
- padding: 0;
262
- }
263
-
264
- /* 🟢 FORCING THE WIDE LAYOUT 🟢 */
265
- .gradio-container {
266
- max-width: 1500px !important; /* Forces container to stretch out */
267
- width: 95% !important; /* Uses 95% of the screen width */
268
- margin: auto !important;
269
- background: #171717 !important;
270
- padding: 20px 24px !important;
271
- }
272
-
273
- /* REMOVE DEFAULT FOOTER */
274
- footer { display: none !important; }
275
-
276
- .dark {
277
- --body-background-fill: #171717 !important;
278
- --block-background-fill: #262626 !important;
279
- --block-border-color: #333 !important;
280
- }
281
-
282
- /* TOPBAR */
283
- .topbar {
284
- display: flex;
285
- justify-content: space-between;
286
- align-items: center;
287
- margin-bottom: 25px;
288
- }
289
-
290
- .logo {
291
- display: flex;
292
- align-items: center;
293
- gap: 12px;
294
- }
295
-
296
- .logo-icon {
297
- width: 32px;
298
- height: 32px;
299
- border-radius: 8px;
300
- background: white;
301
- color: black;
302
- display: flex;
303
- align-items: center;
304
- justify-content: center;
305
- font-weight: 700;
306
- font-size: 15px;
307
- }
308
-
309
- .logo-text {
310
- font-size: 18px;
311
- font-weight: 600;
312
- }
313
-
314
- .nav {
315
- display: flex;
316
- gap: 22px;
317
- align-items: center;
318
- }
319
-
320
- .nav a {
321
- color: #a1a1aa;
322
- text-decoration: none;
323
- cursor: pointer;
324
- transition: 0.2s;
325
- font-size: 13px;
326
- font-weight: 500;
327
- }
328
-
329
- .nav a:hover {
330
- color: white;
331
- }
332
-
333
- .nav-yt { color: #ff4e4e !important; }
334
- .nav-kofi { color: #29abe0 !important; }
335
-
336
- /* HERO */
337
- .hero {
338
- text-align: center;
339
- margin-bottom: 35px;
340
- }
341
-
342
- .hero h1 {
343
- font-size: 42px;
344
- font-weight: 800;
345
- letter-spacing: -0.05em;
346
- margin-bottom: 6px;
347
- }
348
-
349
- /* BLUE HERO TEXT */
350
- .hero span {
351
- color: #3b82f6;
352
- }
353
-
354
- .hero p {
355
- color: #a1a1aa;
356
- font-size: 15px;
357
- margin: 4px 0 16px 0;
358
- }
359
-
360
- .badges {
361
- display: flex;
362
- justify-content: center;
363
- gap: 10px;
364
- }
365
-
366
- .badge {
367
- background: #262626;
368
- border: 1px solid #333;
369
- padding: 6px 14px;
370
- border-radius: 999px;
371
- color: #d4d4d8;
372
- font-size: 11px;
373
- font-weight: 500;
374
- }
375
-
376
- /* CARDS / PANELS */
377
- .panel {
378
- background: #262626 !important;
379
- border: 1px solid #333 !important;
380
- border-radius: 14px !important;
381
- padding: 24px !important;
382
- box-shadow: 0 4px 12px rgba(0,0,0,0.15) !important;
383
- }
384
-
385
- /* FORCE SIDE BY SIDE ON DESKTOP */
386
- @media (min-width: 768px) {
387
- .side-by-side {
388
- flex-wrap: nowrap !important;
389
- }
390
- }
391
-
392
- /* TITLES */
393
- .section-title {
394
- font-size: 12px;
395
- text-transform: uppercase;
396
- color: #888;
397
- letter-spacing: 1px;
398
- margin-bottom: 14px;
399
- font-weight: 600;
400
- }
401
-
402
- /* AUDIO COMPONENT */
403
- audio {
404
- border-radius: 8px !important;
405
- background: #1f1f1f !important;
406
- }
407
-
408
- /* CUSTOM BLUE BUTTON */
409
- #process-btn {
410
- background: #3b82f6 !important;
411
- color: white !important;
412
- border: none !important;
413
- border-radius: 8px !important;
414
- height: 48px !important;
415
- font-size: 15px !important;
416
- font-weight: 600 !important;
417
- margin-top: 14px !important;
418
- transition: all 0.2s ease-in-out !important;
419
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3) !important;
420
- }
421
-
422
- #process-btn:hover {
423
- background: #2563eb !important;
424
- box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4) !important;
425
- transform: translateY(-1px);
426
- }
427
-
428
- /* INPUTS */
429
- .gr-textbox, .gr-dropdown {
430
- background: #1f1f1f !important;
431
- border: 1px solid #3a3a3a !important;
432
- border-radius: 8px !important;
433
- }
434
-
435
- /* MOBILE RESPONSIVENESS */
436
- @media(max-width: 900px) {
437
- .topbar { flex-direction: column; gap: 16px; }
438
- .nav { flex-wrap: wrap; justify-content: center; }
439
- }
440
- """
441
-
442
- # -----------------------------
443
- # START BACKGROUND WORKERS
444
- # -----------------------------
445
- start_cleanup_worker()
446
-
447
- # -----------------------------
448
- # UI
449
- # -----------------------------
450
-
451
- with gr.Blocks(
452
- theme=gr.themes.Base(),
453
- css=css,
454
- title="Remove Silence"
455
- ) as demo:
456
-
457
- # TOPBAR
458
- gr.HTML("""
459
- <div class="topbar">
460
- <div class="logo">
461
- <div class="logo-icon">R</div>
462
- <div class="logo-text">Remove Silence</div>
463
- </div>
464
- <div class="nav">
465
- <a href="https://www.youtube.com/@neuralfalcon/" target="_blank" class="nav-yt">Subscribe YouTube</a>
466
- <a href="https://ko-fi.com/neuralfalcon" target="_blank" class="nav-kofi">Donate</a>
467
- <a href="https://github.com/NeuralFalconYT/Remove-Silence-From-Audio" target="_blank">GitHub</a>
468
- <a href="mailto:NeuralFalcon@proton.me" target="_blank">Mail</a>
469
- <a href="https://x.com/NeuralFalcon" target="_blank">X</a>
470
- </div>
471
- </div>
472
- """)
473
-
474
- # HERO
475
- gr.HTML("""
476
- <div class="hero">
477
- <h1>REMOVE <span>SILENCE</span></h1>
478
- <p>Drop your audio and instantly clean pauses for Shorts, TikTok & Reels</p>
479
- <div class="badges">
480
- <div class="badge">100% Free</div>
481
- <div class="badge">No Sign-Up</div>
482
- </div>
483
- </div>
484
- """)
485
-
486
- with gr.Row(equal_height=False, elem_classes="side-by-side"):
487
-
488
- # LEFT PANEL (UPLOAD) - Adjusted min_width to stretch beautifully in the new wide container
489
- with gr.Column(scale=1, min_width=450):
490
- with gr.Group(elem_classes="panel"):
491
- gr.HTML('<div class="section-title">Upload & Settings</div>')
492
-
493
- input_audio = gr.Audio(
494
- type="filepath",
495
- label="",
496
- show_label=False
497
- )
498
-
499
- with gr.Row():
500
- keep_silence = gr.Number(
501
- label="Keep Silence Upto (In seconds)",
502
- value=0.05
503
- )
504
-
505
- # Blue submit button
506
- process_btn = gr.Button("Remove Silence", elem_id="process-btn")
507
-
508
- # RIGHT PANEL (RESULT)
509
- with gr.Column(scale=1, min_width=450):
510
- with gr.Group(elem_classes="panel"):
511
- gr.HTML('<div class="section-title">Result</div>')
512
-
513
- output_audio = gr.Audio(
514
- label="Play Audio",
515
- show_label=False
516
- )
517
-
518
- download_audio = gr.File(
519
- label="Download Audio",
520
- show_label=False
521
- )
522
-
523
- stats_html = gr.HTML()
524
-
525
- # PROCESS
526
- process_btn.click(
527
- fn=process_audio,
528
- inputs=[input_audio, keep_silence],
529
- outputs=[output_audio, download_audio, stats_html]
530
- )
531
-
532
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
packages.txt DELETED
@@ -1 +0,0 @@
1
- ffmpeg
 
 
requirements.txt CHANGED
@@ -1,7 +1,3 @@
1
- #click>=8.1.8
2
  pydub==0.25.1
3
  gradio>=5.25.0
4
- #torch==2.10.0
5
- #torchaudio==2.10.0
6
- #torchcodec==0.10.0
7
- numpy
 
 
1
  pydub==0.25.1
2
  gradio>=5.25.0
3
+ click>=8.1.8