Offex's picture
Update app.py
fae0d9b verified
import gradio as gr
import os
import uuid
from pydub import AudioSegment
from pydub.silence import split_on_silence
import re
import click
# --- LOGIC SECTION (SAME) ---
def clean_file_name(file_path):
file_name = os.path.basename(file_path)
file_name, file_extension = os.path.splitext(file_name)
cleaned = re.sub(r'[^a-zA-Z\d]+', '_', file_name)
clean_file_name = re.sub(r'_+', '_', cleaned).strip('_')
random_uuid = uuid.uuid4().hex[:6]
clean_file_path = os.path.join(os.path.dirname(file_path), clean_file_name + f"_{random_uuid}" + file_extension)
return clean_file_path
def remove_silence(file_path, minimum_silence=50):
sound = AudioSegment.from_file(file_path)
audio_chunks = split_on_silence(sound, min_silence_len=100, silence_thresh=-45, keep_silence=minimum_silence)
combined = AudioSegment.empty()
for chunk in audio_chunks:
combined += chunk
output_path = clean_file_name(file_path)
combined.export(output_path)
return output_path
def calculate_duration(file_path):
audio = AudioSegment.from_file(file_path)
return len(audio) / 1000.0
def process_audio(audio_file, seconds=0.05):
if not audio_file:
return None, None, "⚠️ Please upload a file first."
keep_silence = int(seconds * 1000)
output_audio_file = remove_silence(audio_file, minimum_silence=keep_silence)
before = calculate_duration(audio_file)
after = calculate_duration(output_audio_file)
text = f"⏱️ Old: {before:.2f}s | ✅ New: {after:.2f}s | 🚀 Saved: {before-after:.2f}s"
return output_audio_file, output_audio_file, text
def reset_interface():
return None, 0.05, None, None, ""
# --- NEW MOBILE-OPTIMIZED UI ---
def ui():
# Clean Modern Font Theme
theme = gr.themes.Soft(
primary_hue="cyan",
secondary_hue="slate",
font=[gr.themes.GoogleFont("Poppins"), "sans-serif"], # Changed to Poppins (Cleaner)
).set(
body_background_fill="#050505",
block_background_fill="#121212",
block_border_width="0px",
input_background_fill="#1a1a1a"
)
css = """
/* MOBILE FIX: Container width 100% karega */
.gradio-container {
width: 100% !important;
max-width: 100% !important;
padding: 10px !important;
margin: 0 !important;
overflow-x: hidden;
}
/* Font Change */
* { font-family: 'Poppins', sans-serif !important; }
/* RGB Animation */
@keyframes rgb-flow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Title Styling - Mobile Friendly */
.rgb-text {
background: linear-gradient(45deg, #ff0000, #00ffd5, #002bff, #ff00c8);
background-size: 300%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-weight: 800;
font-size: 2rem; /* Thoda chota kiya mobile ke liye */
text-align: center;
animation: rgb-flow 8s linear infinite;
margin-bottom: 5px;
line-height: 1.2;
}
.subtitle {
text-align: center;
color: #888;
font-size: 0.9rem;
margin-bottom: 15px;
}
/* Card Styling */
.rgb-card {
background: #111;
border-radius: 15px;
padding: 2px; /* Border patla kiya */
margin-bottom: 10px;
position: relative;
}
.rgb-card::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: linear-gradient(45deg, #ff0000, #00ffd5, #002bff, #ff00c8);
background-size: 400%;
border-radius: 15px;
animation: rgb-flow 15s linear infinite;
opacity: 0.6;
}
.inner-card {
background: #0a0a0a;
border-radius: 13px;
padding: 15px;
}
/* Button Styling */
.process-btn {
background: linear-gradient(90deg, #00c6ff 0%, #0072ff 100%) !important;
color: white !important;
font-weight: 600 !important;
border: none !important;
box-shadow: 0 4px 15px rgba(0, 114, 255, 0.3);
}
.reset-btn {
background: #ff0055 !important;
color: white !important;
border: none !important;
}
/* Stats Box Text Fix */
.stat-box textarea {
background-color: #000 !important;
color: #00ff88 !important;
font-family: monospace;
font-size: 14px !important;
}
/* Mobile Specific Tweaks */
@media (max-width: 768px) {
.rgb-text { font-size: 1.5rem !important; } /* Mobile pe title chota */
.inner-card { padding: 10px !important; }
.process-btn, .reset-btn { width: 100%; margin-top: 5px; } /* Buttons full width */
}
"""
with gr.Blocks(theme=theme, css=css, title="Silence Remover Pro") as demo:
gr.HTML("""
<div class="rgb-text">SILENCE REMOVER</div>
<div class="subtitle">Clean Audio • Fast • AI Powered</div>
""")
with gr.Group(elem_classes="rgb-card"):
with gr.Column(elem_classes="inner-card"):
# Mobile par ye stack ho jayega (upar-niche)
with gr.Row():
# --- INPUT SECTION ---
with gr.Column(scale=1):
gr.Markdown("### 📤 UPLOAD")
audio_input = gr.Audio(
label="Source", type="filepath",
sources=["upload", "microphone"], show_label=False
)
gr.Markdown("### 🎚️ SETTING")
silence_threshold = gr.Slider(
minimum=0, maximum=1, step=0.01, value=0.05,
label="Keep Silence (Sec)"
)
submit_btn = gr.Button("🚀 PROCESS AUDIO", elem_classes="process-btn")
# --- OUTPUT SECTION ---
with gr.Column(scale=1):
gr.Markdown("### 🎧 RESULT")
audio_output = gr.Audio(
label="Clean Audio", show_label=False, interactive=False
)
with gr.Row():
file_output = gr.File(label="Download", file_count="single", scale=2)
clear_btn = gr.Button("❌ RESET", elem_classes="reset-btn", scale=1)
duration_output = gr.Textbox(
label="STATS", lines=2,
elem_classes="stat-box", show_label=True
)
gr.Markdown("""<div style="text-align: center; color: #555; font-size: 0.8rem; margin-top: 5px;">Made with ❤️</div>""")
submit_btn.click(
fn=process_audio,
inputs=[audio_input, silence_threshold],
outputs=[audio_output, file_output, duration_output]
)
clear_btn.click(
fn=reset_interface,
inputs=None,
outputs=[audio_input, silence_threshold, audio_output, file_output, duration_output]
)
return demo
@click.command()
@click.option("--debug", is_flag=True, default=False)
@click.option("--share", is_flag=True, default=False)
def main(debug, share):
demo = ui()
demo.queue().launch(debug=debug, share=share)
if __name__ == "__main__":
main()